diff --git a/lib/pages/task_edit_page.dart b/lib/pages/task_edit_page.dart index a799715..91bea9f 100644 --- a/lib/pages/task_edit_page.dart +++ b/lib/pages/task_edit_page.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; import '../app_theme.dart'; +import '../model/alarm.dart'; +import '../model/callback_models/create_alarm_request.dart'; +import '../model/callback_models/create_fixed_time_alarm_request.dart'; +import '../model/callback_models/create_location_alarm_request.dart'; import '../model/callback_models/create_task_request.dart'; import '../model/extensions/controller_context.dart'; import '../model/task.dart'; import '../service/controllers/task_controller.dart'; import '../service/tools.dart'; +import '../widgets/alarm_widgets/alarm_form_widget.dart'; import '../widgets/time_selector.dart'; class TaskEditPage extends StatefulWidget { @@ -32,6 +37,7 @@ class _TaskEditPageState extends State { final formKey = GlobalKey(debugLabel: 'taskEditFormKey'); bool didFormChange = false; bool isDueTimeEnabled = false; + final List alarms = []; @override void didChangeDependencies() { @@ -80,46 +86,84 @@ class _TaskEditPageState extends State { padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.05, ), - child: Column( - spacing: AppTheme.formColumnSpacing, - children: [ - SizedBox(height: 6), - TextFormField( - autofocus: true, - controller: titleController, - decoration: InputDecoration(label: Text('Title')), - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - ), - TextFormField( - controller: descriptionController, - decoration: InputDecoration(label: Text('Description')), - keyboardType: TextInputType.multiline, - textInputAction: TextInputAction.newline, - minLines: 3, - maxLines: 10, - ), - TimeSelector( - initialDueDateTime: task?.due, - nextFocusNode: categoryFocusNode, - dueDateController: dueDateController, - dueTimeController: dueTimeController, - formKey: formKey, - ), - TextFormField( - focusNode: categoryFocusNode, - controller: categoryController, - decoration: InputDecoration(label: Text('Category')), - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - ), - TextFormField( - controller: urlController, - decoration: InputDecoration(label: Text('Url')), - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - ), - ], + child: SingleChildScrollView( + child: Column( + spacing: AppTheme.formColumnSpacing, + children: [ + SizedBox(height: 6), + TextFormField( + autofocus: true, + controller: titleController, + decoration: InputDecoration(label: Text('Title')), + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + ), + TextFormField( + controller: descriptionController, + decoration: InputDecoration(label: Text('Description')), + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.newline, + minLines: 3, + maxLines: 10, + ), + TimeSelector( + initialDueDateTime: task?.due, + nextFocusNode: categoryFocusNode, + dueDateController: dueDateController, + dueTimeController: dueTimeController, + formKey: formKey, + ), + TextFormField( + focusNode: categoryFocusNode, + controller: categoryController, + decoration: InputDecoration(label: Text('Category')), + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + ), + TextFormField( + controller: urlController, + decoration: InputDecoration(label: Text('Url')), + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + ), + Text( + 'Alarms', + style: Theme.of(context).textTheme.headlineLarge, + ), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + spacing: AppTheme.formColumnSpacing, + children: [ + ElevatedButton.icon( + onPressed: () => onAddAlarmPressed(AlarmType.timeFixed), + label: Text('Fixed Time'), + icon: Icon(Icons.alarm_add), + ), + ElevatedButton.icon( + onPressed: () => + onAddAlarmPressed(AlarmType.timeRelative), + label: Text('Relative Time'), + icon: Icon(Icons.timer_outlined), + ), + ElevatedButton.icon( + onPressed: () => onAddAlarmPressed(AlarmType.location), + label: Text('Location'), + icon: Icon(Icons.location_on), + ), + for (final (index, alarm) in alarms.indexed) + AlarmForm( + request: alarm, + onChanged: (updated) { + setState(() { + alarms[index] = updated; + }); + }, + ), + ], + ), + ], + ), ), ), ), @@ -174,4 +218,17 @@ class _TaskEditPageState extends State { }); } } + + void onAddAlarmPressed(AlarmType alarmType) { + switch (alarmType) { + case AlarmType.location: + alarms.add(CreateLocationAlarmRequest()); + case AlarmType.timeFixed: + alarms.add(CreateFixedTimeAlarmRequest()); + + case AlarmType.timeRelative: + alarms.add(CreateFixedTimeAlarmRequest()); + } + setState(() {}); + } } diff --git a/lib/widgets/alarm_widgets/alarm_form_widget.dart b/lib/widgets/alarm_widgets/alarm_form_widget.dart new file mode 100644 index 0000000..b684560 --- /dev/null +++ b/lib/widgets/alarm_widgets/alarm_form_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../../model/alarm.dart' show AlarmType; +import '../../model/callback_models/create_alarm_request.dart'; +import '../../model/callback_models/create_fixed_time_alarm_request.dart'; +import '../../model/callback_models/create_location_alarm_request.dart'; +import '../../model/callback_models/create_relative_time_alarm_request.dart'; +import 'fixed_time_alarm_form.dart'; +import 'location_alarm_form.dart'; +import 'relative_time_alarm_form.dart'; + +class AlarmForm extends StatelessWidget { + const AlarmForm({super.key, required this.request, required this.onChanged}); + + final CreateAlarmRequest request; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + switch (request.alarmType) { + case AlarmType.timeFixed: + return FixedTimeAlarmForm( + request: request as CreateFixedTimeAlarmRequest, + onChanged: onChanged, + ); + + case AlarmType.timeRelative: + return RelativeTimeAlarmForm( + request: request as CreateRelativeTimeAlarmRequest, + onChanged: onChanged, + ); + + case AlarmType.location: + return LocationAlarmForm( + request: request as CreateLocationAlarmRequest, + onChanged: onChanged, + ); + } + } +} diff --git a/lib/widgets/alarm_widgets/fixed_time_alarm_form.dart b/lib/widgets/alarm_widgets/fixed_time_alarm_form.dart new file mode 100644 index 0000000..ac4be9e --- /dev/null +++ b/lib/widgets/alarm_widgets/fixed_time_alarm_form.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../../model/callback_models/create_alarm_request.dart'; +import '../../model/callback_models/create_fixed_time_alarm_request.dart'; + +class FixedTimeAlarmForm extends StatelessWidget { + const FixedTimeAlarmForm({ + super.key, + required this.request, + required this.onChanged, + }); + final CreateFixedTimeAlarmRequest request; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/widgets/alarm_widgets/location_alarm_form.dart b/lib/widgets/alarm_widgets/location_alarm_form.dart new file mode 100644 index 0000000..45f94a7 --- /dev/null +++ b/lib/widgets/alarm_widgets/location_alarm_form.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../../model/callback_models/create_alarm_request.dart'; +import '../../model/callback_models/create_location_alarm_request.dart'; + +class LocationAlarmForm extends StatelessWidget { + const LocationAlarmForm({ + super.key, + required this.request, + required this.onChanged, + }); + final CreateLocationAlarmRequest request; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/widgets/alarm_widgets/relative_time_alarm_form.dart b/lib/widgets/alarm_widgets/relative_time_alarm_form.dart new file mode 100644 index 0000000..c382860 --- /dev/null +++ b/lib/widgets/alarm_widgets/relative_time_alarm_form.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../../model/callback_models/create_alarm_request.dart'; +import '../../model/callback_models/create_relative_time_alarm_request.dart'; + +class RelativeTimeAlarmForm extends StatelessWidget { + const RelativeTimeAlarmForm({ + super.key, + required this.request, + required this.onChanged, + }); + final CreateRelativeTimeAlarmRequest request; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/widgets/time_selector.dart b/lib/widgets/time_selector.dart index 16ca5a5..70decdf 100644 --- a/lib/widgets/time_selector.dart +++ b/lib/widgets/time_selector.dart @@ -12,6 +12,7 @@ class TimeSelector extends StatefulWidget { required this.dueDateController, required this.dueTimeController, this.formKey, + this.onChanged, }); final DateTime? initialDueDateTime; @@ -19,6 +20,7 @@ class TimeSelector extends StatefulWidget { final FocusNode? nextFocusNode; final TextEditingController dueDateController; final TextEditingController dueTimeController; + final void Function(DateTime dateTime)? onChanged; @override State createState() => _TimeSelectorState(); @@ -48,7 +50,18 @@ class _TimeSelectorState extends State { child: TextFormField( focusNode: dueDateFocusNode, controller: widget.dueDateController, - onChanged: maybeEnableDueTime, + onChanged: (value) { + maybeEnableDueTime(value); + if (dateTimeValidator(value) == null && + widget.onChanged != null) { + final dateTime = DateTime.tryParse( + '$value ${widget.dueTimeController.text}'.trim(), + ); + if (dateTime != null) { + widget.onChanged!(dateTime); + } + } + }, onFieldSubmitted: (_) { isDueTimeEnabled ? dueDateFocusNode.nextFocus() @@ -85,6 +98,16 @@ class _TimeSelectorState extends State { onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(), controller: widget.dueTimeController, enabled: isDueTimeEnabled, + onChanged: (value) { + if (timeValidator(value) == null && widget.onChanged != null) { + final dateTime = DateTime.tryParse( + '${widget.dueDateController.text} $value'.trim(), + ); + if (dateTime != null) { + widget.onChanged!(dateTime); + } + } + }, decoration: InputDecoration( label: Text('Due Time'), suffixIcon: IconButton(