Compare commits
23
Commits
main
...
2c3e52ba53
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3e52ba53 | ||
|
|
62af007799 | ||
|
|
8f3afc3bd7 | ||
|
|
e6951b4c25 | ||
|
|
cd97dada2a | ||
|
|
6778b6426f | ||
|
|
1b9e096b77 | ||
|
|
8113ce9c22 | ||
|
|
06de421ee2 | ||
|
|
e7d6735d27 | ||
|
|
79cae9570d | ||
|
|
7fed7bfe16 | ||
|
|
02469faf52 | ||
|
|
3ceb0896a4 | ||
|
|
c7ff39e55c | ||
|
|
a33c4bbb12 | ||
|
|
5fc290dbcb | ||
|
|
463a2c8fda | ||
|
|
dcae40497d | ||
|
|
a440c29043 | ||
|
|
e1eafdca59 | ||
|
|
4af3683b73 | ||
|
|
505aff4232 |
@@ -34,6 +34,26 @@ class AppTheme {
|
|||||||
side: BorderSide(color: colorScheme.secondaryContainer, width: 2),
|
side: BorderSide(color: colorScheme.secondaryContainer, width: 2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ButtonStyle(
|
||||||
|
minimumSize: WidgetStatePropertyAll(Size(0, 56)),
|
||||||
|
shape: WidgetStateOutlinedBorder.resolveWith(
|
||||||
|
(states) =>
|
||||||
|
RoundedRectangleBorder(borderRadius: universalBorderRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownMenuTheme: DropdownMenuThemeData(
|
||||||
|
inputDecorationTheme: InputDecorationThemeData(
|
||||||
|
border: OutlineInputBorder(borderRadius: universalBorderRadius),
|
||||||
|
),
|
||||||
|
menuStyle: MenuStyle(
|
||||||
|
shape: WidgetStateOutlinedBorder.resolveWith(
|
||||||
|
(states) =>
|
||||||
|
RoundedRectangleBorder(borderRadius: universalBorderRadius),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'app_theme.dart';
|
import 'app_theme.dart';
|
||||||
import 'model/repositories/local_repository.dart';
|
import 'repositories/local_repository.dart';
|
||||||
import 'pages/locations_overview_page.dart';
|
import 'pages/locations_overview_page.dart';
|
||||||
import 'pages/task_edit_page.dart';
|
import 'pages/task_edit_page.dart';
|
||||||
import 'pages/task_overview_page.dart';
|
import 'pages/task_overview_page.dart';
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import 'location_alarm.dart';
|
|
||||||
import 'time_alarm.dart';
|
|
||||||
|
|
||||||
abstract class Alarm {
|
|
||||||
String get id;
|
|
||||||
String get taskId;
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
factory Alarm.fromJson(Map<String, dynamic> json) {
|
|
||||||
if (json.containsKey('triggerAt')) {
|
|
||||||
return TimeAlarm.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.containsKey('location')) {
|
|
||||||
return LocationAlarm.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ArgumentError('Unknown alarm type');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (other is! Alarm) return false;
|
|
||||||
|
|
||||||
return hashCode == other.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => taskId.hashCode;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import 'fixed_time_alarm.dart';
|
||||||
|
import 'location_alarm.dart';
|
||||||
|
import 'relative_time_alarm.dart';
|
||||||
|
|
||||||
|
abstract class Alarm {
|
||||||
|
String get id;
|
||||||
|
AlarmType get alarmType;
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
factory Alarm.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json.containsKey('alarmType')) {
|
||||||
|
switch (AlarmType.fromJson(json['alarmType'])) {
|
||||||
|
case AlarmType.timeFixed:
|
||||||
|
return FixedTimeAlarm.fromJson(json);
|
||||||
|
case AlarmType.timeRelative:
|
||||||
|
return RelativeTimeAlarm.fromJson(json);
|
||||||
|
case AlarmType.location:
|
||||||
|
return LocationAlarm.fromJson(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ArgumentError('Unknown alarm type');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! Alarm) return false;
|
||||||
|
|
||||||
|
return hashCode == other.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => id.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AlarmType {
|
||||||
|
timeFixed,
|
||||||
|
timeRelative,
|
||||||
|
location;
|
||||||
|
|
||||||
|
String toJson() => name;
|
||||||
|
static AlarmType fromJson(String json) => values.byName(json);
|
||||||
|
}
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
import 'alarm.dart';
|
import 'alarm.dart';
|
||||||
|
|
||||||
class TimeAlarm implements Alarm {
|
class FixedTimeAlarm implements Alarm {
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@override
|
|
||||||
final String taskId;
|
|
||||||
|
|
||||||
final DateTime triggerAt;
|
final DateTime triggerAt;
|
||||||
|
|
||||||
const TimeAlarm({
|
@override
|
||||||
required this.id,
|
final AlarmType alarmType;
|
||||||
required this.taskId,
|
|
||||||
required this.triggerAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory TimeAlarm.fromJson(Map<String, dynamic> json) {
|
const FixedTimeAlarm({required this.id, required this.triggerAt})
|
||||||
return TimeAlarm(
|
: alarmType = AlarmType.timeFixed;
|
||||||
|
|
||||||
|
factory FixedTimeAlarm.fromJson(Map<String, dynamic> json) {
|
||||||
|
return FixedTimeAlarm(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
taskId: json['taskId'] as String,
|
|
||||||
triggerAt: DateTime.parse(json['triggerAt'] as String),
|
triggerAt: DateTime.parse(json['triggerAt'] as String),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -27,8 +23,8 @@ class TimeAlarm implements Alarm {
|
|||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'id': id,
|
'id': id,
|
||||||
'taskId': taskId,
|
|
||||||
'triggerAt': triggerAt.toIso8601String(),
|
'triggerAt': triggerAt.toIso8601String(),
|
||||||
|
'alarmType': alarmType.toJson(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,26 @@
|
|||||||
import 'alarm.dart';
|
import 'alarm.dart';
|
||||||
import 'location.dart';
|
import '../location.dart';
|
||||||
|
|
||||||
class LocationAlarm implements Alarm {
|
class LocationAlarm implements Alarm {
|
||||||
@override
|
@override
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
@override
|
|
||||||
final String taskId;
|
|
||||||
|
|
||||||
final Location location;
|
final Location location;
|
||||||
|
|
||||||
final int radiusMeters;
|
final int radiusMeters;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final AlarmType alarmType;
|
||||||
|
|
||||||
const LocationAlarm({
|
const LocationAlarm({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.taskId,
|
|
||||||
required this.location,
|
required this.location,
|
||||||
required this.radiusMeters,
|
required this.radiusMeters,
|
||||||
});
|
}) : alarmType = AlarmType.location;
|
||||||
|
|
||||||
factory LocationAlarm.fromJson(Map<String, dynamic> json) {
|
factory LocationAlarm.fromJson(Map<String, dynamic> json) {
|
||||||
return LocationAlarm(
|
return LocationAlarm(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
taskId: json['taskId'] as String,
|
|
||||||
location: Location.fromJson(json['location'] as Map<String, dynamic>),
|
location: Location.fromJson(json['location'] as Map<String, dynamic>),
|
||||||
radiusMeters: json['radiusMeters'] as int,
|
radiusMeters: json['radiusMeters'] as int,
|
||||||
);
|
);
|
||||||
@@ -32,9 +30,9 @@ class LocationAlarm implements Alarm {
|
|||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'id': id,
|
'id': id,
|
||||||
'taskId': taskId,
|
|
||||||
'location': location.toJson(),
|
'location': location.toJson(),
|
||||||
'radiusMeters': radiusMeters,
|
'radiusMeters': radiusMeters,
|
||||||
|
'alarmType': alarmType.toJson(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import 'alarm.dart';
|
||||||
|
|
||||||
|
class RelativeTimeAlarm implements Alarm {
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
final DateTime triggerAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final AlarmType alarmType;
|
||||||
|
|
||||||
|
const RelativeTimeAlarm({required this.id, required this.triggerAt})
|
||||||
|
: alarmType = AlarmType.timeRelative;
|
||||||
|
|
||||||
|
factory RelativeTimeAlarm.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RelativeTimeAlarm(
|
||||||
|
id: json['id'] as String,
|
||||||
|
triggerAt: DateTime.parse(json['triggerAt'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'triggerAt': triggerAt.toIso8601String(),
|
||||||
|
'alarmType': alarmType.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import '../alarm/alarm.dart' show AlarmType, Alarm;
|
||||||
|
|
||||||
|
abstract class CreateAlarmRequest {
|
||||||
|
const CreateAlarmRequest({required this.alarmType});
|
||||||
|
|
||||||
|
final AlarmType alarmType;
|
||||||
|
|
||||||
|
Alarm toAlarm(String id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import '../alarm/alarm.dart';
|
||||||
|
import '../alarm/fixed_time_alarm.dart';
|
||||||
|
import 'create_alarm_request.dart';
|
||||||
|
|
||||||
|
class CreateFixedTimeAlarmRequest extends CreateAlarmRequest {
|
||||||
|
final DateTime? triggerAt;
|
||||||
|
|
||||||
|
const CreateFixedTimeAlarmRequest({this.triggerAt})
|
||||||
|
: super(alarmType: AlarmType.timeFixed);
|
||||||
|
|
||||||
|
CreateFixedTimeAlarmRequest.fromAlarm(FixedTimeAlarm alarm)
|
||||||
|
: triggerAt = alarm.triggerAt,
|
||||||
|
super(alarmType: AlarmType.timeFixed);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FixedTimeAlarm toAlarm(String id) =>
|
||||||
|
FixedTimeAlarm(id: id, triggerAt: triggerAt!);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import '../alarm/alarm.dart' show AlarmType;
|
||||||
|
import '../alarm/location_alarm.dart';
|
||||||
|
import '../location.dart';
|
||||||
|
import 'create_alarm_request.dart';
|
||||||
|
|
||||||
|
class CreateLocationAlarmRequest extends CreateAlarmRequest {
|
||||||
|
final Location? location;
|
||||||
|
|
||||||
|
final int? radiusMeters;
|
||||||
|
|
||||||
|
const CreateLocationAlarmRequest({this.location, this.radiusMeters})
|
||||||
|
: super(alarmType: AlarmType.location);
|
||||||
|
|
||||||
|
CreateLocationAlarmRequest.fromAlarm(LocationAlarm alarm)
|
||||||
|
: location = alarm.location,
|
||||||
|
radiusMeters = alarm.radiusMeters,
|
||||||
|
super(alarmType: AlarmType.location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
LocationAlarm toAlarm(String id) =>
|
||||||
|
LocationAlarm(id: id, location: location!, radiusMeters: radiusMeters!);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import '../alarm/alarm.dart' show AlarmType;
|
||||||
|
import '../alarm/relative_time_alarm.dart';
|
||||||
|
import 'create_alarm_request.dart';
|
||||||
|
|
||||||
|
class CreateRelativeTimeAlarmRequest extends CreateAlarmRequest {
|
||||||
|
final DateTime? triggerAt;
|
||||||
|
|
||||||
|
const CreateRelativeTimeAlarmRequest({this.triggerAt})
|
||||||
|
: super(alarmType: AlarmType.timeRelative);
|
||||||
|
|
||||||
|
CreateRelativeTimeAlarmRequest.fromAlarm(RelativeTimeAlarm alarm)
|
||||||
|
: triggerAt = alarm.triggerAt,
|
||||||
|
super(alarmType: AlarmType.timeRelative);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RelativeTimeAlarm toAlarm(String id) =>
|
||||||
|
RelativeTimeAlarm(id: id, triggerAt: triggerAt!);
|
||||||
|
}
|
||||||
+6
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import '../alarm/alarm.dart';
|
||||||
import '../task.dart';
|
import '../task.dart';
|
||||||
|
|
||||||
class CreateTaskRequest {
|
class CreateTaskRequest {
|
||||||
@@ -9,6 +10,7 @@ class CreateTaskRequest {
|
|||||||
final String category;
|
final String category;
|
||||||
final List<Task> subtasks;
|
final List<Task> subtasks;
|
||||||
final String url;
|
final String url;
|
||||||
|
final List<Alarm> alarms;
|
||||||
|
|
||||||
CreateTaskRequest({
|
CreateTaskRequest({
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -19,6 +21,7 @@ class CreateTaskRequest {
|
|||||||
required this.category,
|
required this.category,
|
||||||
required this.subtasks,
|
required this.subtasks,
|
||||||
required this.url,
|
required this.url,
|
||||||
|
required this.alarms,
|
||||||
});
|
});
|
||||||
|
|
||||||
CreateTaskRequest.fromTask(Task task)
|
CreateTaskRequest.fromTask(Task task)
|
||||||
@@ -29,7 +32,8 @@ class CreateTaskRequest {
|
|||||||
isCompleted = task.isCompleted,
|
isCompleted = task.isCompleted,
|
||||||
category = task.category,
|
category = task.category,
|
||||||
subtasks = task.subtasks,
|
subtasks = task.subtasks,
|
||||||
url = task.url;
|
url = task.url,
|
||||||
|
alarms = task.alarms;
|
||||||
|
|
||||||
Task toTask({required String id}) {
|
Task toTask({required String id}) {
|
||||||
return Task(
|
return Task(
|
||||||
@@ -42,6 +46,7 @@ class CreateTaskRequest {
|
|||||||
category: category,
|
category: category,
|
||||||
subtasks: subtasks,
|
subtasks: subtasks,
|
||||||
url: url,
|
url: url,
|
||||||
|
alarms: alarms,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import 'alarm/alarm.dart';
|
||||||
import 'location.dart';
|
import 'location.dart';
|
||||||
|
|
||||||
class Task {
|
class Task {
|
||||||
@@ -10,6 +11,7 @@ class Task {
|
|||||||
final String category;
|
final String category;
|
||||||
final List<Task> subtasks;
|
final List<Task> subtasks;
|
||||||
final String url;
|
final String url;
|
||||||
|
final List<Alarm> alarms;
|
||||||
|
|
||||||
Task({
|
Task({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -21,6 +23,7 @@ class Task {
|
|||||||
this.category = '',
|
this.category = '',
|
||||||
this.subtasks = const [],
|
this.subtasks = const [],
|
||||||
this.url = '',
|
this.url = '',
|
||||||
|
required this.alarms,
|
||||||
});
|
});
|
||||||
|
|
||||||
Task copyWith({
|
Task copyWith({
|
||||||
@@ -32,7 +35,7 @@ class Task {
|
|||||||
bool? isCompleted,
|
bool? isCompleted,
|
||||||
String? category,
|
String? category,
|
||||||
List<Task>? subtasks,
|
List<Task>? subtasks,
|
||||||
List<DateTime>? alarms,
|
List<Alarm>? alarms,
|
||||||
Location? location,
|
Location? location,
|
||||||
String? url,
|
String? url,
|
||||||
}) {
|
}) {
|
||||||
@@ -46,6 +49,7 @@ class Task {
|
|||||||
category: category ?? this.category,
|
category: category ?? this.category,
|
||||||
subtasks: subtasks ?? this.subtasks,
|
subtasks: subtasks ?? this.subtasks,
|
||||||
url: url ?? this.url,
|
url: url ?? this.url,
|
||||||
|
alarms: alarms ?? this.alarms,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +70,11 @@ class Task {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
url: json['url'] as String? ?? '',
|
url: json['url'] as String? ?? '',
|
||||||
|
alarms:
|
||||||
|
(json['alarms'] as List<dynamic>?)
|
||||||
|
?.map((e) => Alarm.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +89,7 @@ class Task {
|
|||||||
'category': category,
|
'category': category,
|
||||||
'subtasks': subtasks.map((e) => e.toJson()).toList(),
|
'subtasks': subtasks.map((e) => e.toJson()).toList(),
|
||||||
'url': url,
|
'url': url,
|
||||||
|
'alarms': alarms.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../app_theme.dart';
|
import '../app_theme.dart';
|
||||||
import '../model/callback_models/create_task_request.dart';
|
import '../model/alarm/alarm.dart';
|
||||||
|
import '../model/alarm/fixed_time_alarm.dart';
|
||||||
|
import '../model/alarm/location_alarm.dart';
|
||||||
|
import '../model/alarm/relative_time_alarm.dart';
|
||||||
|
import '../model/requests/create_alarm_request.dart';
|
||||||
|
import '../model/requests/create_fixed_time_alarm_request.dart';
|
||||||
|
import '../model/requests/create_location_alarm_request.dart';
|
||||||
|
import '../model/requests/create_relative_time_alarm_request.dart';
|
||||||
|
import '../model/requests/create_task_request.dart';
|
||||||
import '../model/extensions/controller_context.dart';
|
import '../model/extensions/controller_context.dart';
|
||||||
import '../model/task.dart';
|
import '../model/task.dart';
|
||||||
import '../service/controllers/task_controller.dart';
|
import '../service/controllers/task_controller.dart';
|
||||||
import '../service/tools.dart';
|
import '../service/tools.dart';
|
||||||
|
import '../widgets/alarm_widgets/alarm_form_widget.dart';
|
||||||
import '../widgets/time_selector.dart';
|
import '../widgets/time_selector.dart';
|
||||||
|
|
||||||
class TaskEditPage extends StatefulWidget {
|
class TaskEditPage extends StatefulWidget {
|
||||||
@@ -32,6 +41,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
final formKey = GlobalKey<FormState>(debugLabel: 'taskEditFormKey');
|
final formKey = GlobalKey<FormState>(debugLabel: 'taskEditFormKey');
|
||||||
bool didFormChange = false;
|
bool didFormChange = false;
|
||||||
bool isDueTimeEnabled = false;
|
bool isDueTimeEnabled = false;
|
||||||
|
final List<CreateAlarmRequest> alarmRequests = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
@@ -50,6 +60,18 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
isDueTimeEnabled = true;
|
isDueTimeEnabled = true;
|
||||||
}
|
}
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
|
alarmRequests.addAll(
|
||||||
|
task!.alarms.map((alarm) {
|
||||||
|
if (alarm is FixedTimeAlarm) {
|
||||||
|
return CreateFixedTimeAlarmRequest.fromAlarm(alarm);
|
||||||
|
} else if (alarm is RelativeTimeAlarm) {
|
||||||
|
return CreateRelativeTimeAlarmRequest.fromAlarm(alarm);
|
||||||
|
} else if (alarm is LocationAlarm) {
|
||||||
|
return CreateLocationAlarmRequest.fromAlarm(alarm);
|
||||||
|
}
|
||||||
|
throw TypeError();
|
||||||
|
}),
|
||||||
|
);
|
||||||
pageTitle = task!.title;
|
pageTitle = task!.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +102,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: MediaQuery.of(context).size.width * 0.05,
|
horizontal: MediaQuery.of(context).size.width * 0.05,
|
||||||
),
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: AppTheme.formColumnSpacing,
|
spacing: AppTheme.formColumnSpacing,
|
||||||
children: [
|
children: [
|
||||||
@@ -119,8 +142,58 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
textInputAction: TextInputAction.next,
|
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, alarmRequest) in alarmRequests.indexed)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AlarmForm(
|
||||||
|
request: alarmRequest,
|
||||||
|
onChanged: (updated) {
|
||||||
|
setState(() {
|
||||||
|
alarmRequests[index] = updated;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
alarmRequests.removeAt(index);
|
||||||
|
}),
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 120),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
@@ -131,6 +204,7 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onSavePressed() {
|
void onSavePressed() {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
Navigator.of(context).pop(
|
Navigator.of(context).pop(
|
||||||
CreateTaskRequest(
|
CreateTaskRequest(
|
||||||
title: titleController.text,
|
title: titleController.text,
|
||||||
@@ -143,9 +217,22 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
category: categoryController.text,
|
category: categoryController.text,
|
||||||
subtasks: [],
|
subtasks: [],
|
||||||
url: urlController.text,
|
url: urlController.text,
|
||||||
|
alarms: alarmRequests.map<Alarm>((e) {
|
||||||
|
if (e is CreateFixedTimeAlarmRequest) {
|
||||||
|
return e.toAlarm(generateId());
|
||||||
|
}
|
||||||
|
if (e is CreateRelativeTimeAlarmRequest) {
|
||||||
|
return e.toAlarm(generateId());
|
||||||
|
}
|
||||||
|
if (e is LocationAlarm) {
|
||||||
|
return e.toAlarm(generateId());
|
||||||
|
}
|
||||||
|
throw TypeError();
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onPopInvoked(bool didPop, Object? result) {
|
void onPopInvoked(bool didPop, Object? result) {
|
||||||
if (!didPop) {
|
if (!didPop) {
|
||||||
@@ -174,4 +261,17 @@ class _TaskEditPageState extends State<TaskEditPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onAddAlarmPressed(AlarmType alarmType) {
|
||||||
|
switch (alarmType) {
|
||||||
|
case AlarmType.location:
|
||||||
|
alarmRequests.add(CreateLocationAlarmRequest());
|
||||||
|
case AlarmType.timeFixed:
|
||||||
|
alarmRequests.add(CreateFixedTimeAlarmRequest());
|
||||||
|
|
||||||
|
case AlarmType.timeRelative:
|
||||||
|
alarmRequests.add(CreateFixedTimeAlarmRequest());
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../model/callback_models/create_task_request.dart';
|
import '../model/requests/create_task_request.dart';
|
||||||
import '../model/extensions/controller_context.dart';
|
import '../model/extensions/controller_context.dart';
|
||||||
import '../model/task.dart';
|
import '../model/task.dart';
|
||||||
import '../service/controllers/task_controller.dart';
|
import '../service/controllers/task_controller.dart';
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import '../../alarm.dart';
|
import '../../model/alarm/alarm.dart';
|
||||||
|
|
||||||
abstract class AlarmRepository {
|
abstract class AlarmRepository {
|
||||||
// Create
|
// Create
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import '../../location.dart';
|
import '../../model/location.dart';
|
||||||
|
|
||||||
abstract class LocationRepository {
|
abstract class LocationRepository {
|
||||||
// Create
|
// Create
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import '../../task.dart';
|
import '../../model/task.dart';
|
||||||
|
|
||||||
abstract class TaskRepository {
|
abstract class TaskRepository {
|
||||||
// Create
|
// Create
|
||||||
+3
-3
@@ -2,9 +2,9 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../alarm.dart';
|
import '../model/alarm/alarm.dart';
|
||||||
import '../location.dart';
|
import '../model/location.dart';
|
||||||
import '../task.dart';
|
import '../model/task.dart';
|
||||||
import 'interfaces/alarm_repository.dart';
|
import 'interfaces/alarm_repository.dart';
|
||||||
import 'interfaces/location_repository.dart';
|
import 'interfaces/location_repository.dart';
|
||||||
import 'interfaces/task_repository.dart';
|
import 'interfaces/task_repository.dart';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../model/alarm.dart';
|
import '../../model/alarm/alarm.dart';
|
||||||
import '../../model/repositories/interfaces/alarm_repository.dart';
|
import '../../repositories/interfaces/alarm_repository.dart';
|
||||||
|
|
||||||
class AlarmController extends ChangeNotifier {
|
class AlarmController extends ChangeNotifier {
|
||||||
AlarmController(AlarmRepository repository) : _repository = repository {
|
AlarmController(AlarmRepository repository) : _repository = repository {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../model/location.dart';
|
import '../../model/location.dart';
|
||||||
import '../../model/repositories/interfaces/location_repository.dart';
|
import '../../repositories/interfaces/location_repository.dart';
|
||||||
|
|
||||||
class LocationController extends ChangeNotifier {
|
class LocationController extends ChangeNotifier {
|
||||||
LocationController(LocationRepository repository) : _repository = repository {
|
LocationController(LocationRepository repository) : _repository = repository {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart' show ChangeNotifier;
|
import 'package:flutter/material.dart' show ChangeNotifier;
|
||||||
|
|
||||||
import '../../model/repositories/interfaces/task_repository.dart';
|
import '../../repositories/interfaces/task_repository.dart';
|
||||||
import '../../model/task.dart';
|
import '../../model/task.dart';
|
||||||
|
|
||||||
class TaskController extends ChangeNotifier {
|
class TaskController extends ChangeNotifier {
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ String? timeValidator(String? value) {
|
|||||||
return 'Not a valid time format';
|
return 'Not a valid time format';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? wholeNumberValidator(String? value) {
|
||||||
|
if (value == null || value.isEmpty) return null;
|
||||||
|
|
||||||
|
if (RegExp(r'^[0-9]*$').hasMatch(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return 'Not a valid number';
|
||||||
|
}
|
||||||
|
|
||||||
String? dateTimeValidator(String? value) {
|
String? dateTimeValidator(String? value) {
|
||||||
if (value == null || value.isEmpty) return null;
|
if (value == null || value.isEmpty) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../model/alarm/alarm.dart' show AlarmType;
|
||||||
|
import '../../model/requests/create_alarm_request.dart';
|
||||||
|
import '../../model/requests/create_fixed_time_alarm_request.dart';
|
||||||
|
import '../../model/requests/create_location_alarm_request.dart';
|
||||||
|
import '../../model/requests/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<CreateAlarmRequest> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../app_theme.dart' show AppTheme;
|
||||||
|
import '../../model/requests/create_fixed_time_alarm_request.dart';
|
||||||
|
import '../../service/tools.dart';
|
||||||
|
import '../../service/validators.dart';
|
||||||
|
|
||||||
|
class FixedTimeAlarmForm extends StatefulWidget {
|
||||||
|
const FixedTimeAlarmForm({
|
||||||
|
super.key,
|
||||||
|
required this.request,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
final CreateFixedTimeAlarmRequest request;
|
||||||
|
final ValueChanged<CreateFixedTimeAlarmRequest> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FixedTimeAlarmForm> createState() => _FixedTimeAlarmFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FixedTimeAlarmFormState extends State<FixedTimeAlarmForm> {
|
||||||
|
final dueDateController = TextEditingController();
|
||||||
|
final dueTimeController = TextEditingController();
|
||||||
|
final dueDateFocusNode = FocusNode();
|
||||||
|
final dueTimeFocusNode = FocusNode();
|
||||||
|
bool isDueTimeEnabled = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
if (widget.request.triggerAt != null) {
|
||||||
|
dueDateController.text = getIsoDateString(widget.request.triggerAt!);
|
||||||
|
dueTimeController.text = TimeOfDay.fromDateTime(
|
||||||
|
widget.request.triggerAt!,
|
||||||
|
).format(context);
|
||||||
|
isDueTimeEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dueDateController.addListener(onDueDateChanged);
|
||||||
|
dueTimeController.addListener(onDueTimeChanged);
|
||||||
|
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Flex(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: 3,
|
||||||
|
child: TextFormField(
|
||||||
|
focusNode: dueDateFocusNode,
|
||||||
|
controller: dueDateController,
|
||||||
|
onFieldSubmitted: (_) {
|
||||||
|
isDueTimeEnabled ? dueDateFocusNode.nextFocus() : null;
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
dueTimeFocusNode.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.calendar_month),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
return notEmptyValidator(value) ?? dateTimeValidator(value);
|
||||||
|
},
|
||||||
|
keyboardType: TextInputType.datetime,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsGeometry.only(left: AppTheme.formColumnSpacing),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
flex: 2,
|
||||||
|
child: TextFormField(
|
||||||
|
focusNode: dueTimeFocusNode,
|
||||||
|
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
||||||
|
controller: dueTimeController,
|
||||||
|
enabled: isDueTimeEnabled,
|
||||||
|
onChanged: (value) {},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: Text('Due Time'),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await onOpenTimePickerPressed();
|
||||||
|
if (result != null && context.mounted) {
|
||||||
|
dueTimeController.text = result.format(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.schedule),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: timeValidator,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DateTime?> onOpenCalendarPickerPressed() {
|
||||||
|
return showDialog<DateTime?>(
|
||||||
|
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<TimeOfDay?> onOpenTimePickerPressed() {
|
||||||
|
return showDialog<TimeOfDay?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => TimePickerDialog(
|
||||||
|
initialTime: TimeOfDay.fromDateTime(
|
||||||
|
widget.request.triggerAt ?? DateTime.now(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void maybeEnableDueTime(String value) {
|
||||||
|
if (value.isNotEmpty && dateTimeValidator(value) == null) {
|
||||||
|
setState(() {
|
||||||
|
isDueTimeEnabled = true;
|
||||||
|
});
|
||||||
|
} else if (isDueTimeEnabled) {
|
||||||
|
setState(() {
|
||||||
|
isDueTimeEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDueDateChanged() {
|
||||||
|
final String value = dueDateController.text;
|
||||||
|
maybeEnableDueTime(value);
|
||||||
|
if (dateTimeValidator(value) == null) {
|
||||||
|
final dateTime = DateTime.tryParse(
|
||||||
|
'$value ${dueTimeController.text}'.trim(),
|
||||||
|
);
|
||||||
|
if (dateTime != null) {
|
||||||
|
widget.onChanged(CreateFixedTimeAlarmRequest(triggerAt: dateTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDueTimeChanged() {
|
||||||
|
final String value = dueTimeController.text;
|
||||||
|
if (timeValidator(value) == null) {
|
||||||
|
final dateTime = DateTime.tryParse(
|
||||||
|
'${dueDateController.text} $value'.trim(),
|
||||||
|
);
|
||||||
|
if (dateTime != null) {
|
||||||
|
widget.onChanged(CreateFixedTimeAlarmRequest(triggerAt: dateTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../model/requests/create_alarm_request.dart';
|
||||||
|
import '../../model/requests/create_location_alarm_request.dart';
|
||||||
|
|
||||||
|
class LocationAlarmForm extends StatelessWidget {
|
||||||
|
const LocationAlarmForm({
|
||||||
|
super.key,
|
||||||
|
required this.request,
|
||||||
|
required this.onChanged,
|
||||||
|
});
|
||||||
|
final CreateLocationAlarmRequest request;
|
||||||
|
final ValueChanged<CreateAlarmRequest> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../model/requests/create_alarm_request.dart';
|
||||||
|
import '../../model/requests/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<CreateAlarmRequest> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../app_theme.dart';
|
||||||
import '../service/tools.dart' show getIsoDateString;
|
import '../service/tools.dart' show getIsoDateString;
|
||||||
import '../service/validators.dart';
|
import '../service/validators.dart';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ class TimeSelector extends StatefulWidget {
|
|||||||
required this.dueDateController,
|
required this.dueDateController,
|
||||||
required this.dueTimeController,
|
required this.dueTimeController,
|
||||||
this.formKey,
|
this.formKey,
|
||||||
|
this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DateTime? initialDueDateTime;
|
final DateTime? initialDueDateTime;
|
||||||
@@ -18,6 +20,7 @@ class TimeSelector extends StatefulWidget {
|
|||||||
final FocusNode? nextFocusNode;
|
final FocusNode? nextFocusNode;
|
||||||
final TextEditingController dueDateController;
|
final TextEditingController dueDateController;
|
||||||
final TextEditingController dueTimeController;
|
final TextEditingController dueTimeController;
|
||||||
|
final void Function(DateTime dateTime)? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TimeSelector> createState() => _TimeSelectorState();
|
State<TimeSelector> createState() => _TimeSelectorState();
|
||||||
@@ -47,7 +50,18 @@ class _TimeSelectorState extends State<TimeSelector> {
|
|||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
focusNode: dueDateFocusNode,
|
focusNode: dueDateFocusNode,
|
||||||
controller: widget.dueDateController,
|
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: (_) {
|
onFieldSubmitted: (_) {
|
||||||
isDueTimeEnabled
|
isDueTimeEnabled
|
||||||
? dueDateFocusNode.nextFocus()
|
? dueDateFocusNode.nextFocus()
|
||||||
@@ -74,7 +88,9 @@ class _TimeSelectorState extends State<TimeSelector> {
|
|||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(padding: EdgeInsetsGeometry.only(left: 10)),
|
Padding(
|
||||||
|
padding: EdgeInsetsGeometry.only(left: AppTheme.formColumnSpacing),
|
||||||
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
@@ -82,6 +98,16 @@ class _TimeSelectorState extends State<TimeSelector> {
|
|||||||
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
onFieldSubmitted: (_) => dueTimeFocusNode.nextFocus(),
|
||||||
controller: widget.dueTimeController,
|
controller: widget.dueTimeController,
|
||||||
enabled: isDueTimeEnabled,
|
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(
|
decoration: InputDecoration(
|
||||||
label: Text('Due Time'),
|
label: Text('Due Time'),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
|
|||||||
Reference in New Issue
Block a user