mirror of
https://github.com/Iconica-Development/flutter_availability.git
synced 2025-05-19 13:13:44 +02:00
feat: add availability creation and modification screen
This commit is contained in:
parent
952d11f417
commit
2e99ab5cf2
1 changed files with 452 additions and 0 deletions
|
@ -0,0 +1,452 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_availability/src/config/availability_options.dart";
|
||||
import "package:flutter_availability_data_interface/flutter_availability_data_interface.dart";
|
||||
import "package:intl/intl.dart";
|
||||
|
||||
///
|
||||
class AvailabilityDayOverview extends StatefulWidget {
|
||||
///
|
||||
const AvailabilityDayOverview({
|
||||
required this.userId,
|
||||
required this.service,
|
||||
required this.options,
|
||||
required this.date,
|
||||
required this.onAvailabilitySaved,
|
||||
this.initialAvailability,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user whose availability is being managed
|
||||
final String userId;
|
||||
|
||||
/// The service to use for managing availability
|
||||
final AvailabilityDataInterface service;
|
||||
|
||||
/// The configuration option for the availability overview
|
||||
final AvailabilityOptions options;
|
||||
|
||||
/// The date for which the availability is being managed
|
||||
final DateTime date;
|
||||
|
||||
/// The initial availability for the day
|
||||
final AvailabilityModel? initialAvailability;
|
||||
|
||||
/// Callback for when the availability is saved
|
||||
final Function() onAvailabilitySaved;
|
||||
|
||||
@override
|
||||
State<AvailabilityDayOverview> createState() =>
|
||||
_AvailabilityDayOverviewState();
|
||||
}
|
||||
|
||||
class _AvailabilityDayOverviewState extends State<AvailabilityDayOverview> {
|
||||
late TextEditingController _startDateController;
|
||||
late TextEditingController _endDateController;
|
||||
late AvailabilityModel _availability;
|
||||
bool _clearAvailableToday = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_availability = widget.initialAvailability ??
|
||||
AvailabilityModel(
|
||||
userId: widget.userId,
|
||||
startDate: widget.date,
|
||||
endDate: widget.date,
|
||||
breaks: [],
|
||||
);
|
||||
_startDateController = TextEditingController(
|
||||
text: DateFormat("HH:mm").format(_availability.startDate),
|
||||
);
|
||||
_endDateController = TextEditingController(
|
||||
text: DateFormat("HH:mm").format(_availability.endDate),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_startDateController.dispose();
|
||||
_endDateController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<TimeOfDay?> _selectTime(TextEditingController controller) async {
|
||||
var picked = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
controller.text = picked.format(context);
|
||||
});
|
||||
return picked;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Future<void> updateAvailabilityStart() async {
|
||||
var selectedTime = await _selectTime(_startDateController);
|
||||
if (selectedTime == null) return;
|
||||
|
||||
var updatedStartDate = _availability.startDate.copyWith(
|
||||
hour: selectedTime.hour,
|
||||
minute: selectedTime.minute,
|
||||
);
|
||||
setState(() {
|
||||
_availability = _availability.copyWith(startDate: updatedStartDate);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateAvailabilityEnd() async {
|
||||
var selectedTime = await _selectTime(_endDateController);
|
||||
if (selectedTime == null) return;
|
||||
|
||||
var updatedEndDate = _availability.endDate.copyWith(
|
||||
hour: selectedTime.hour,
|
||||
minute: selectedTime.minute,
|
||||
);
|
||||
setState(() {
|
||||
_availability = _availability.copyWith(endDate: updatedEndDate);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onClickAddPause() async {
|
||||
var newBreak = await AvailabilityBreakSelectionDialog.show(context, null);
|
||||
if (newBreak != null) {
|
||||
setState(() {
|
||||
_availability.breaks.add(newBreak);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onClickSave() async {
|
||||
if (_clearAvailableToday) {
|
||||
// remove the availability for the user
|
||||
if (_availability.id != null) {
|
||||
await widget.service.deleteAvailabilityForUser(
|
||||
widget.userId,
|
||||
_availability.id!,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// add an availability for the user
|
||||
await widget.service.createAvailabilityForUser(
|
||||
widget.userId,
|
||||
_availability,
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
widget.onAvailabilitySaved();
|
||||
}
|
||||
}
|
||||
|
||||
var theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMd().format(widget.date),
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _clearAvailableToday,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_clearAvailableToday = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text("Clear availability for today"),
|
||||
],
|
||||
),
|
||||
Opacity(
|
||||
opacity: _clearAvailableToday ? 0.5 : 1,
|
||||
child: IgnorePointer(
|
||||
ignoring: _clearAvailableToday,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: updateAvailabilityStart,
|
||||
child: AbsorbPointer(
|
||||
child: TextField(
|
||||
controller: _startDateController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Begin tijd",
|
||||
suffixIcon: Icon(Icons.access_time),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Text("tot"),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: updateAvailabilityEnd,
|
||||
child: AbsorbPointer(
|
||||
child: TextField(
|
||||
controller: _endDateController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Eind tijd",
|
||||
suffixIcon: Icon(Icons.access_time),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Text("Add pause (optional)"),
|
||||
ListView(
|
||||
shrinkWrap: true,
|
||||
children: _availability.breaks.map(
|
||||
(breakModel) {
|
||||
var start =
|
||||
DateFormat("HH:mm").format(breakModel.startTime);
|
||||
var end =
|
||||
DateFormat("HH:mm").format(breakModel.endTime);
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
var updatedBreak =
|
||||
await AvailabilityBreakSelectionDialog.show(
|
||||
context,
|
||||
breakModel,
|
||||
);
|
||||
if (updatedBreak != null) {
|
||||
setState(() {
|
||||
_availability.breaks.remove(breakModel);
|
||||
_availability.breaks.add(updatedBreak);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.lightBlue,
|
||||
),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${breakModel.duration.inMinutes}"
|
||||
" minutes | ",
|
||||
),
|
||||
Text(
|
||||
"$start - "
|
||||
"$end",
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_availability.breaks.remove(breakModel);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onClickAddPause,
|
||||
child: const Text("Add"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: () async => onClickSave(),
|
||||
child: Text(widget.options.translations.availabilitySave),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
class AvailabilityBreakSelectionDialog extends StatefulWidget {
|
||||
///
|
||||
const AvailabilityBreakSelectionDialog({
|
||||
required this.initialBreak,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The initial break to show in the dialog if any
|
||||
final AvailabilityBreakModel? initialBreak;
|
||||
|
||||
/// Opens the dialog to add a break
|
||||
static Future<AvailabilityBreakModel?> show(
|
||||
BuildContext context,
|
||||
AvailabilityBreakModel? initialBreak,
|
||||
) async =>
|
||||
showDialog<AvailabilityBreakModel>(
|
||||
context: context,
|
||||
builder: (context) => AvailabilityBreakSelectionDialog(
|
||||
initialBreak: initialBreak,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
State<AvailabilityBreakSelectionDialog> createState() =>
|
||||
_AvailabilityBreakSelectionDialogState();
|
||||
}
|
||||
|
||||
class _AvailabilityBreakSelectionDialogState
|
||||
extends State<AvailabilityBreakSelectionDialog> {
|
||||
late TextEditingController _durationController;
|
||||
late TextEditingController _startPauseController;
|
||||
late TextEditingController _endPauseController;
|
||||
late AvailabilityBreakModel _breakModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_breakModel = widget.initialBreak ??
|
||||
AvailabilityBreakModel(
|
||||
startTime: DateTime.now(),
|
||||
endTime: DateTime.now(),
|
||||
);
|
||||
_durationController = TextEditingController(
|
||||
text: _breakModel.duration.inMinutes.toString(),
|
||||
);
|
||||
_startPauseController = TextEditingController(
|
||||
text: DateFormat("HH:mm").format(_breakModel.startTime),
|
||||
);
|
||||
_endPauseController = TextEditingController(
|
||||
text: DateFormat("HH:mm").format(_breakModel.endTime),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_durationController.dispose();
|
||||
_startPauseController.dispose();
|
||||
_endPauseController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<TimeOfDay?> _selectTime(
|
||||
BuildContext context,
|
||||
TextEditingController controller,
|
||||
) async {
|
||||
var picked = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
controller.text = picked.format(context);
|
||||
});
|
||||
return picked;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void onUpdateDuration() {
|
||||
var duration = int.tryParse(_durationController.text);
|
||||
if (duration != null) {
|
||||
setState(() {
|
||||
_breakModel = _breakModel.copyWith(
|
||||
duration: Duration(minutes: duration),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onUpdateStart() async {
|
||||
var selectedTime = await _selectTime(context, _startPauseController);
|
||||
if (selectedTime == null) return;
|
||||
|
||||
var updatedStartTime = _breakModel.startTime.copyWith(
|
||||
hour: selectedTime.hour,
|
||||
minute: selectedTime.minute,
|
||||
);
|
||||
setState(() {
|
||||
_breakModel = _breakModel.copyWith(startTime: updatedStartTime);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> onUpdateEnd() async {
|
||||
var selectedTime = await _selectTime(context, _endPauseController);
|
||||
if (selectedTime == null) return;
|
||||
|
||||
var updatedEndTime = _breakModel.endTime.copyWith(
|
||||
hour: selectedTime.hour,
|
||||
minute: selectedTime.minute,
|
||||
);
|
||||
setState(() {
|
||||
_breakModel = _breakModel.copyWith(endTime: updatedEndTime);
|
||||
});
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Pauze toevoegen"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// textfield for duration in minutes
|
||||
TextField(
|
||||
controller: _durationController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Duration in minutes",
|
||||
),
|
||||
onChanged: (_) => onUpdateDuration(),
|
||||
),
|
||||
TextField(
|
||||
controller: _startPauseController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Start time",
|
||||
suffixIcon: Icon(Icons.access_time),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async => onUpdateStart(),
|
||||
),
|
||||
TextField(
|
||||
controller: _endPauseController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "End time",
|
||||
suffixIcon: Icon(Icons.access_time),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () async => onUpdateEnd(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text("Cancel"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("Save"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(_breakModel);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue