improved ux
This commit is contained in:
@@ -17,17 +17,20 @@ class TaskEditPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _TaskEditPageState extends State<TaskEditPage> {
|
class _TaskEditPageState extends State<TaskEditPage> {
|
||||||
Task? task;
|
Task? task;
|
||||||
bool isInitialized = true;
|
bool isInitialized = false;
|
||||||
final titleController = TextEditingController();
|
final titleController = TextEditingController();
|
||||||
final descriptionController = TextEditingController();
|
final descriptionController = TextEditingController();
|
||||||
final categoryController = TextEditingController();
|
final categoryController = TextEditingController();
|
||||||
final urlController = TextEditingController();
|
final urlController = TextEditingController();
|
||||||
final dueDateController = TextEditingController();
|
final dueDateController = TextEditingController();
|
||||||
final dueTimeController = TextEditingController();
|
final dueTimeController = TextEditingController();
|
||||||
late String pageTitle;
|
String pageTitle = 'Create Task';
|
||||||
final dueDateFocusNode = FocusNode();
|
final dueDateFocusNode = FocusNode();
|
||||||
final dueTimeFocusNode = FocusNode();
|
final dueTimeFocusNode = FocusNode();
|
||||||
final categoryFocusNode = FocusNode();
|
final categoryFocusNode = FocusNode();
|
||||||
|
final formKey = GlobalKey<FormState>(debugLabel: 'taskEditFormKey');
|
||||||
|
bool didFormChange = false;
|
||||||
|
bool isDueTimeEnabled = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
@@ -38,15 +41,16 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
descriptionController.text = task!.description;
|
descriptionController.text = task!.description;
|
||||||
categoryController.text = task!.category;
|
categoryController.text = task!.category;
|
||||||
urlController.text = task!.url;
|
urlController.text = task!.url;
|
||||||
dueDateController.text = task!.due != null
|
if (task!.due != null) {
|
||||||
? getIsoDateString(task!.due!)
|
dueDateController.text = getIsoDateString(task!.due!);
|
||||||
: '';
|
dueTimeController.text = TimeOfDay.fromDateTime(
|
||||||
dueTimeController.text = task!.due != null
|
task!.due!,
|
||||||
? TimeOfDay.fromDateTime(task!.due!).format(context)
|
).format(context);
|
||||||
: '';
|
isDueTimeEnabled = true;
|
||||||
isInitialized = true;
|
}
|
||||||
|
isInitialized = true;
|
||||||
|
pageTitle = task!.title;
|
||||||
}
|
}
|
||||||
pageTitle = task?.title ?? 'CreateTask';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -66,7 +70,11 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Form(
|
body: Form(
|
||||||
|
canPop: !didFormChange,
|
||||||
|
onChanged: () => didFormChange = true,
|
||||||
|
onPopInvokedWithResult: onPopInvoked,
|
||||||
autovalidateMode: AutovalidateMode.onUnfocus,
|
autovalidateMode: AutovalidateMode.onUnfocus,
|
||||||
|
key: formKey,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: MediaQuery.of(context).size.width * 0.05,
|
horizontal: MediaQuery.of(context).size.width * 0.05,
|
||||||
@@ -91,14 +99,22 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
TextFormField(
|
TextFormField(
|
||||||
focusNode: dueDateFocusNode,
|
focusNode: dueDateFocusNode,
|
||||||
controller: dueDateController,
|
controller: dueDateController,
|
||||||
onFieldSubmitted: (_) => dueDateFocusNode.nextFocus(),
|
onChanged: maybeEnableDueTime,
|
||||||
|
onFieldSubmitted: (_) {
|
||||||
|
isDueTimeEnabled
|
||||||
|
? dueDateFocusNode.nextFocus()
|
||||||
|
: categoryFocusNode.requestFocus();
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
label: Text('Due Date'),
|
label: Text('Due Date'),
|
||||||
suffix: IconButton(
|
suffix: IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await onOpenCalendarPickerPressed();
|
final result = await onOpenCalendarPickerPressed();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
dueDateController.text = getIsoDateString(result);
|
final dateString = getIsoDateString(result);
|
||||||
|
dueDateController.text = dateString;
|
||||||
|
maybeEnableDueTime(dateString);
|
||||||
|
formKey.currentState?.validate();
|
||||||
dueTimeFocusNode.requestFocus();
|
dueTimeFocusNode.requestFocus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -113,9 +129,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
focusNode: dueTimeFocusNode,
|
focusNode: dueTimeFocusNode,
|
||||||
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
||||||
controller: dueTimeController,
|
controller: dueTimeController,
|
||||||
enabled: DateTime.tryParse(dueDateController.text) != null
|
enabled: isDueTimeEnabled,
|
||||||
? true
|
|
||||||
: false,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
label: Text('Due Time'),
|
label: Text('Due Time'),
|
||||||
suffix: IconButton(
|
suffix: IconButton(
|
||||||
@@ -172,8 +186,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
Future<TimeOfDay?> onOpenTimePickerPressed() {
|
Future<TimeOfDay?> onOpenTimePickerPressed() {
|
||||||
return showDialog<TimeOfDay?>(
|
return showDialog<TimeOfDay?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => TimePickerDialog(
|
||||||
TimePickerDialog(initialTime: TimeOfDay(hour: 0, minute: 0)),
|
initialTime: TimeOfDay.fromDateTime(task?.due ?? DateTime.now()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +198,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
title: titleController.text,
|
title: titleController.text,
|
||||||
description: descriptionController.text,
|
description: descriptionController.text,
|
||||||
start: null,
|
start: null,
|
||||||
due: DateTime.tryParse(dueDateController.text),
|
due: null,
|
||||||
isCompleted: false,
|
isCompleted: false,
|
||||||
category: categoryController.text,
|
category: categoryController.text,
|
||||||
subtasks: [],
|
subtasks: [],
|
||||||
@@ -193,4 +208,46 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onPopInvoked(bool didPop, Object? result) {
|
||||||
|
if (!didPop) {
|
||||||
|
showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text('Unsaved Changes'),
|
||||||
|
content: Text(
|
||||||
|
'Are you sure you want to cancel editing? Unsaved changes will be lost.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: Text('No'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: Text('Yes'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
if (result != null && result && context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void maybeEnableDueTime(String value) {
|
||||||
|
if (value.isNotEmpty &&
|
||||||
|
dateTimeValidator(value) == null &&
|
||||||
|
!isDueTimeEnabled) {
|
||||||
|
setState(() {
|
||||||
|
isDueTimeEnabled = true;
|
||||||
|
});
|
||||||
|
} else if (isDueTimeEnabled) {
|
||||||
|
setState(() {
|
||||||
|
isDueTimeEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user