improved ux
This commit is contained in:
@@ -17,17 +17,20 @@ class TaskEditPage extends StatefulWidget {
|
||||
|
||||
class _TaskEditPageState extends State<TaskEditPage> {
|
||||
Task? task;
|
||||
bool isInitialized = true;
|
||||
bool isInitialized = false;
|
||||
final titleController = TextEditingController();
|
||||
final descriptionController = TextEditingController();
|
||||
final categoryController = TextEditingController();
|
||||
final urlController = TextEditingController();
|
||||
final dueDateController = TextEditingController();
|
||||
final dueTimeController = TextEditingController();
|
||||
late String pageTitle;
|
||||
String pageTitle = 'Create Task';
|
||||
final dueDateFocusNode = FocusNode();
|
||||
final dueTimeFocusNode = FocusNode();
|
||||
final categoryFocusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>(debugLabel: 'taskEditFormKey');
|
||||
bool didFormChange = false;
|
||||
bool isDueTimeEnabled = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -38,15 +41,16 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
descriptionController.text = task!.description;
|
||||
categoryController.text = task!.category;
|
||||
urlController.text = task!.url;
|
||||
dueDateController.text = task!.due != null
|
||||
? getIsoDateString(task!.due!)
|
||||
: '';
|
||||
dueTimeController.text = task!.due != null
|
||||
? TimeOfDay.fromDateTime(task!.due!).format(context)
|
||||
: '';
|
||||
if (task!.due != null) {
|
||||
dueDateController.text = getIsoDateString(task!.due!);
|
||||
dueTimeController.text = TimeOfDay.fromDateTime(
|
||||
task!.due!,
|
||||
).format(context);
|
||||
isDueTimeEnabled = true;
|
||||
}
|
||||
isInitialized = true;
|
||||
pageTitle = task!.title;
|
||||
}
|
||||
pageTitle = task?.title ?? 'CreateTask';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -66,7 +70,11 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
canPop: !didFormChange,
|
||||
onChanged: () => didFormChange = true,
|
||||
onPopInvokedWithResult: onPopInvoked,
|
||||
autovalidateMode: AutovalidateMode.onUnfocus,
|
||||
key: formKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.05,
|
||||
@@ -91,14 +99,22 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
TextFormField(
|
||||
focusNode: dueDateFocusNode,
|
||||
controller: dueDateController,
|
||||
onFieldSubmitted: (_) => dueDateFocusNode.nextFocus(),
|
||||
onChanged: maybeEnableDueTime,
|
||||
onFieldSubmitted: (_) {
|
||||
isDueTimeEnabled
|
||||
? dueDateFocusNode.nextFocus()
|
||||
: categoryFocusNode.requestFocus();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
label: Text('Due Date'),
|
||||
suffix: IconButton(
|
||||
onPressed: () async {
|
||||
final result = await onOpenCalendarPickerPressed();
|
||||
if (result != null) {
|
||||
dueDateController.text = getIsoDateString(result);
|
||||
final dateString = getIsoDateString(result);
|
||||
dueDateController.text = dateString;
|
||||
maybeEnableDueTime(dateString);
|
||||
formKey.currentState?.validate();
|
||||
dueTimeFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
@@ -113,9 +129,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
focusNode: dueTimeFocusNode,
|
||||
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
||||
controller: dueTimeController,
|
||||
enabled: DateTime.tryParse(dueDateController.text) != null
|
||||
? true
|
||||
: false,
|
||||
enabled: isDueTimeEnabled,
|
||||
decoration: InputDecoration(
|
||||
label: Text('Due Time'),
|
||||
suffix: IconButton(
|
||||
@@ -172,8 +186,9 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
Future<TimeOfDay?> onOpenTimePickerPressed() {
|
||||
return showDialog<TimeOfDay?>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
TimePickerDialog(initialTime: TimeOfDay(hour: 0, minute: 0)),
|
||||
builder: (context) => TimePickerDialog(
|
||||
initialTime: TimeOfDay.fromDateTime(task?.due ?? DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,7 +198,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
start: null,
|
||||
due: DateTime.tryParse(dueDateController.text),
|
||||
due: null,
|
||||
isCompleted: false,
|
||||
category: categoryController.text,
|
||||
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