diff --git a/lib/pages/task_edit_page.dart b/lib/pages/task_edit_page.dart index 50a33e6..dac716f 100644 --- a/lib/pages/task_edit_page.dart +++ b/lib/pages/task_edit_page.dart @@ -6,6 +6,7 @@ import '../model/task.dart'; import '../service/task_controller.dart'; import '../service/tools.dart'; import '../service/validators.dart'; +import '../widgets/time_selector.dart'; class TaskEditPage extends StatefulWidget { static const routeName = '/edit'; @@ -96,69 +97,12 @@ class _TaskEditPageState extends State { minLines: 3, maxLines: 10, ), - Flex( - direction: Axis.horizontal, - - children: [ - Flexible( - flex: 3, - child: TextFormField( - focusNode: dueDateFocusNode, - controller: dueDateController, - onChanged: maybeEnableDueTime, - onFieldSubmitted: (_) { - isDueTimeEnabled - ? dueDateFocusNode.nextFocus() - : categoryFocusNode.requestFocus(); - }, - decoration: InputDecoration( - label: Text('Due Date'), - suffixIcon: IconButton( - onPressed: () async { - final result = await onOpenCalendarPickerPressed(); - if (result != null) { - final dateString = getIsoDateString(result); - dueDateController.text = dateString; - maybeEnableDueTime(dateString); - formKey.currentState?.validate(); - dueTimeFocusNode.requestFocus(); - } - }, - icon: Icon(Icons.calendar_month), - ), - ), - validator: dateTimeValidator, - keyboardType: TextInputType.datetime, - textInputAction: TextInputAction.next, - ), - ), - Padding(padding: EdgeInsetsGeometry.only(left: 10)), - Flexible( - flex: 2, - child: TextFormField( - focusNode: dueTimeFocusNode, - onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(), - controller: dueTimeController, - enabled: isDueTimeEnabled, - decoration: InputDecoration( - label: Text('Due Time'), - suffixIcon: IconButton( - onPressed: () async { - final result = await onOpenTimePickerPressed(); - if (result != null && context.mounted) { - dueTimeController.text = result.format(context); - categoryFocusNode.requestFocus(); - } - }, - icon: Icon(Icons.schedule), - ), - ), - validator: timeValidator, - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - ), - ), - ], + TimeSelector( + initialDueDateTime: task?.due, + nextFocusNode: categoryFocusNode, + dueDateController: dueDateController, + dueTimeController: dueTimeController, + formKey: formKey, ), TextFormField( focusNode: categoryFocusNode, @@ -184,27 +128,6 @@ class _TaskEditPageState extends State { ); } - Future onOpenCalendarPickerPressed() { - return showDialog( - context: context, - builder: (context) => DatePickerDialog( - firstDate: DateTime(DateTime.now().year - 100), - lastDate: DateTime(DateTime.now().year + 100), - initialDate: - DateTime.tryParse(dueDateController.text) ?? DateTime.now(), - ), - ); - } - - Future onOpenTimePickerPressed() { - return showDialog( - context: context, - builder: (context) => TimePickerDialog( - initialTime: TimeOfDay.fromDateTime(task?.due ?? DateTime.now()), - ), - ); - } - void onSavePressed() { Navigator.of(context).pop( CreateTaskRequest( @@ -251,18 +174,4 @@ class _TaskEditPageState extends State { }); } } - - void maybeEnableDueTime(String value) { - if (value.isNotEmpty && - dateTimeValidator(value) == null && - !isDueTimeEnabled) { - setState(() { - isDueTimeEnabled = true; - }); - } else if (isDueTimeEnabled) { - setState(() { - isDueTimeEnabled = false; - }); - } - } } diff --git a/lib/widgets/time_selector.dart b/lib/widgets/time_selector.dart new file mode 100644 index 0000000..9471084 --- /dev/null +++ b/lib/widgets/time_selector.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; + +import '../service/tools.dart' show getIsoDateString; +import '../service/validators.dart'; + +class TimeSelector extends StatefulWidget { + const TimeSelector({ + super.key, + this.initialDueDateTime, + this.nextFocusNode, + required this.dueDateController, + required this.dueTimeController, + this.formKey, + }); + + final DateTime? initialDueDateTime; + final GlobalKey? formKey; + final FocusNode? nextFocusNode; + final TextEditingController dueDateController; + final TextEditingController dueTimeController; + + @override + State createState() => _TimeSelectorState(); +} + +class _TimeSelectorState extends State { + final dueDateFocusNode = FocusNode(); + + final dueTimeFocusNode = FocusNode(); + + bool isDueTimeEnabled = false; + + @override + void initState() { + isDueTimeEnabled = widget.initialDueDateTime != null; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Flex( + direction: Axis.horizontal, + + children: [ + Flexible( + flex: 3, + child: TextFormField( + focusNode: dueDateFocusNode, + controller: widget.dueDateController, + onChanged: maybeEnableDueTime, + onFieldSubmitted: (_) { + isDueTimeEnabled + ? dueDateFocusNode.nextFocus() + : widget.nextFocusNode?.requestFocus(); + }, + decoration: InputDecoration( + label: Text('Due Date'), + suffixIcon: IconButton( + onPressed: () async { + final result = await onOpenCalendarPickerPressed(); + if (result != null) { + final dateString = getIsoDateString(result); + widget.dueDateController.text = dateString; + maybeEnableDueTime(dateString); + widget.formKey?.currentState?.validate(); + dueTimeFocusNode.requestFocus(); + } + }, + icon: Icon(Icons.calendar_month), + ), + ), + validator: dateTimeValidator, + keyboardType: TextInputType.datetime, + textInputAction: TextInputAction.next, + ), + ), + Padding(padding: EdgeInsetsGeometry.only(left: 10)), + Flexible( + flex: 2, + child: TextFormField( + focusNode: dueTimeFocusNode, + onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(), + controller: widget.dueTimeController, + enabled: isDueTimeEnabled, + decoration: InputDecoration( + label: Text('Due Time'), + suffixIcon: IconButton( + onPressed: () async { + final result = await onOpenTimePickerPressed(); + if (result != null && context.mounted) { + widget.dueTimeController.text = result.format(context); + widget.nextFocusNode?.requestFocus(); + } + }, + icon: Icon(Icons.schedule), + ), + ), + validator: timeValidator, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + ), + ), + ], + ); + } + + Future onOpenCalendarPickerPressed() { + return showDialog( + context: context, + builder: (context) => DatePickerDialog( + firstDate: DateTime(DateTime.now().year - 100), + lastDate: DateTime(DateTime.now().year + 100), + initialDate: + DateTime.tryParse(widget.dueDateController.text) ?? DateTime.now(), + ), + ); + } + + Future onOpenTimePickerPressed() { + return showDialog( + context: context, + builder: (context) => TimePickerDialog( + initialTime: TimeOfDay.fromDateTime( + widget.initialDueDateTime ?? DateTime.now(), + ), + ), + ); + } + + void maybeEnableDueTime(String value) { + if (value.isNotEmpty && dateTimeValidator(value) == null) { + setState(() { + isDueTimeEnabled = true; + }); + } else if (isDueTimeEnabled) { + setState(() { + isDueTimeEnabled = false; + }); + } + } +}