diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e8f9718c..b4a9535b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,8 +5,6 @@
android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-
-
@@ -14,12 +12,8 @@
android:name="android.permission.VIBRATE" />
-
-
-
-
+
+
> alarmSortOptions = [
ListSortOption((context) => AppLocalizations.of(context)!.timeOfDayDesc, sortTimeOfDayDescending),
];
-int sortRemainingTimeDescending(Alarm a, Alarm b) {
+int sortRemainingTimeAscending(Alarm a, Alarm b) {
if (a.currentScheduleDateTime == null && b.currentScheduleDateTime == null) {
return 0;
} else if (a.currentScheduleDateTime == null) {
@@ -29,7 +29,7 @@ int sortRemainingTimeDescending(Alarm a, Alarm b) {
return remainingB.compareTo(remainingA);
}
-int sortRemainingTimeAscending(Alarm a, Alarm b) {
+int sortRemainingTimeDescending(Alarm a, Alarm b) {
if (a.currentScheduleDateTime == null && b.currentScheduleDateTime == null) {
return 0;
} else if (a.currentScheduleDateTime == null) {
diff --git a/lib/alarm/logic/new_alarm_snackbar.dart b/lib/alarm/logic/new_alarm_snackbar.dart
index e0d2f881..25985cee 100644
--- a/lib/alarm/logic/new_alarm_snackbar.dart
+++ b/lib/alarm/logic/new_alarm_snackbar.dart
@@ -1,30 +1,76 @@
import 'package:clock_app/alarm/types/alarm.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-String getNewAlarmSnackbarText(Alarm alarm) {
+String getRemainingAlarmTimeText(BuildContext context, Alarm alarm) {
Duration etaNextAlarm =
alarm.currentScheduleDateTime!.difference(DateTime.now().toLocal());
String etaText = '';
+ AppLocalizations localizations = AppLocalizations.of(context)!;
+
+ if (etaNextAlarm.inDays > 0) {
+ etaText = localizations.daysString(etaNextAlarm.inDays);
+ } else if (etaNextAlarm.inHours > 0) {
+ int hours = etaNextAlarm.inHours;
+ int minutes = etaNextAlarm.inMinutes % 60;
+ if (minutes > 0) {
+ etaText = localizations.combinedTime(localizations.hoursString(hours),
+ localizations.minutesString(minutes));
+ } else {
+ etaText = localizations.hoursString(hours);
+ }
+ } else if (etaNextAlarm.inMinutes > 0) {
+ int minutes = etaNextAlarm.inMinutes;
+ etaText = localizations.minutesString(minutes);
+ } else {
+ etaText = localizations.lessThanOneMinute;
+ }
+
+ return etaText;
+}
+
+String getShortRemainingAlarmTimeText(BuildContext context, Alarm alarm) {
+ Duration etaNextAlarm =
+ alarm.currentScheduleDateTime!.difference(DateTime.now().toLocal());
+
+ String etaText = '';
+
+ AppLocalizations localizations = AppLocalizations.of(context)!;
+
if (etaNextAlarm.inDays > 0) {
- int days = etaNextAlarm.inDays;
- String dayTextSuffix = days <= 1 ? 'day' : 'days';
- etaText = '$days $dayTextSuffix';
+ etaText = localizations.daysString(etaNextAlarm.inDays);
} else if (etaNextAlarm.inHours > 0) {
int hours = etaNextAlarm.inHours;
int minutes = etaNextAlarm.inMinutes % 60;
- String hourTextSuffix = hours <= 1 ? 'hour' : 'hours';
- String minuteTextSuffix = minutes <= 1 ? 'minute' : 'minutes';
- String hoursText = '$hours $hourTextSuffix';
- String minutesText = minutes == 0 ? '' : ' and $minutes $minuteTextSuffix';
- etaText = '$hoursText$minutesText';
+ if (minutes > 0) {
+ etaText = '${localizations.shortHoursString(hours)} ${localizations.shortMinutesString(minutes)}';
+ } else {
+ etaText = localizations.shortHoursString(hours);
+ }
} else if (etaNextAlarm.inMinutes > 0) {
int minutes = etaNextAlarm.inMinutes;
- String minuteTextSuffix = minutes <= 1 ? 'minute' : 'minutes';
- etaText = '$minutes $minuteTextSuffix';
+ etaText = localizations.shortMinutesString(minutes);
} else {
- etaText = 'less than 1 minute';
+ etaText = localizations.shortMinutesString(1);
}
- return 'Alarm will ring in $etaText';
+ return etaText;
+}
+
+String getNewAlarmText(BuildContext context, Alarm alarm) {
+ AppLocalizations localizations = AppLocalizations.of(context)!;
+
+ final etaText = getRemainingAlarmTimeText(context, alarm);
+
+ return localizations.alarmRingInMessage(etaText);
+}
+
+String getNextAlarmText(BuildContext context, Alarm alarm) {
+ AppLocalizations localizations = AppLocalizations.of(context)!;
+
+ final etaText = getShortRemainingAlarmTimeText(context, alarm);
+
+ return localizations.nextAlarmIn(etaText);
}
diff --git a/lib/alarm/screens/alarm_screen.dart b/lib/alarm/screens/alarm_screen.dart
index cc96b771..4e7aba13 100644
--- a/lib/alarm/screens/alarm_screen.dart
+++ b/lib/alarm/screens/alarm_screen.dart
@@ -2,6 +2,7 @@ import 'package:clock_app/alarm/data/alarm_list_filters.dart';
import 'package:clock_app/alarm/data/alarm_sort_options.dart';
import 'package:clock_app/alarm/logic/new_alarm_snackbar.dart';
import 'package:clock_app/alarm/types/alarm.dart';
+import 'package:clock_app/alarm/utils/next_alarm.dart';
import 'package:clock_app/alarm/widgets/alarm_card.dart';
import 'package:clock_app/alarm/widgets/alarm_description.dart';
import 'package:clock_app/alarm/widgets/alarm_time_picker.dart';
@@ -10,18 +11,18 @@ import 'package:clock_app/common/types/list_filter.dart';
import 'package:clock_app/common/types/picker_result.dart';
import 'package:clock_app/common/types/time.dart';
import 'package:clock_app/common/utils/snackbar.dart';
+import 'package:clock_app/common/widgets/card_container.dart';
import 'package:clock_app/common/widgets/fab.dart';
import 'package:clock_app/common/widgets/list/customize_list_item_screen.dart';
import 'package:clock_app/common/widgets/list/persistent_list_view.dart';
import 'package:clock_app/common/widgets/time_picker.dart';
import 'package:clock_app/settings/data/settings_schema.dart';
+import 'package:clock_app/settings/types/listener_manager.dart';
import 'package:clock_app/settings/types/setting.dart';
import 'package:flutter/material.dart';
import 'package:great_list_view/great_list_view.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
-
typedef AlarmCardBuilder = Widget Function(
BuildContext context,
int index,
@@ -40,6 +41,8 @@ class _AlarmScreenState extends State {
late Setting _showInstantAlarmButton;
late Setting _showFilters;
late Setting _showSort;
+ late Setting _showNextAlarm;
+ late Alarm? nextAlarm;
void update(value) {
setState(() {});
@@ -53,20 +56,22 @@ class _AlarmScreenState extends State {
_showInstantAlarmButton = appSettings
.getGroup("Developer Options")
.getSetting("Show Instant Alarm Button");
- _showFilters = appSettings
- .getGroup("Alarm")
- .getGroup("Filters")
- .getSetting("Show Filters");
- _showSort = appSettings
- .getGroup("Alarm")
- .getGroup("Filters")
- .getSetting("Show Sort");
+ final filtersGroup = appSettings.getGroup("Alarm").getGroup("Filters");
+ _showFilters = filtersGroup.getSetting("Show Filters");
+ _showSort = filtersGroup.getSetting("Show Sort");
+ _showNextAlarm = filtersGroup.getSetting("Show Next Alarm");
+
// appSettings.getGroup("Accessibility").getSetting("Left Handed Mode");
_showInstantAlarmButton.addListener(update);
_showFilters.addListener(update);
+ _showNextAlarm.addListener(update);
_showSort.addListener(update);
+ ListenerManager.addOnChangeListener("alarms", update);
+
+ nextAlarm = getNextAlarm();
+
// ListenerManager().addListener();
}
@@ -75,6 +80,8 @@ class _AlarmScreenState extends State {
_showInstantAlarmButton.removeListener(update);
_showFilters.removeListener(update);
_showSort.removeListener(update);
+ _showNextAlarm.removeListener(update);
+ ListenerManager.removeOnChangeListener("alarms", update);
super.dispose();
}
@@ -119,14 +126,17 @@ class _AlarmScreenState extends State {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
DateTime? nextScheduleDateTime = alarm.currentScheduleDateTime;
if (nextScheduleDateTime == null) return;
- ScaffoldMessenger.of(context).showSnackBar(
- getSnackbar(getNewAlarmSnackbarText(alarm), fab: true, navBar: true));
+ ScaffoldMessenger.of(context).showSnackBar(getSnackbar(
+ getNewAlarmText(context, alarm),
+ fab: true,
+ navBar: true));
});
}
Future _handleEnableChangeAlarm(Alarm alarm, bool value) async {
if (!alarm.canBeDisabledWhenSnoozed && !value && alarm.isSnoozed) {
- showSnackBar(context, AppLocalizations.of(context)!.cannotDisableAlarmWhileSnoozedSnackbar,
+ showSnackBar(context,
+ AppLocalizations.of(context)!.cannotDisableAlarmWhileSnoozedSnackbar,
fab: true, navBar: true);
} else {
await alarm.setIsEnabled(value,
@@ -140,8 +150,12 @@ class _AlarmScreenState extends State {
List alarms, bool value) async {
for (var alarm in alarms) {
if (!alarm.canBeDisabledWhenSnoozed && !value && alarm.isSnoozed) {
- showSnackBar(context, AppLocalizations.of(context)!.cannotDisableAlarmWhileSnoozedSnackbar,
- fab: true, navBar: true);
+ showSnackBar(
+ context,
+ AppLocalizations.of(context)!
+ .cannotDisableAlarmWhileSnoozedSnackbar,
+ fab: true,
+ navBar: true);
} else {
await alarm.setIsEnabled(value,
"_handleEnableChangeMultipleAlarms(): Alarm enable set to $value by user");
@@ -173,6 +187,24 @@ class _AlarmScreenState extends State {
_listController.changeItems((alarms) {});
}
+ List> getListFilterItems() {
+ List> listFilterItems =
+ _showFilters.value ? [...alarmListFilters] : [];
+
+ if (nextAlarm != null && _showNextAlarm.value) {
+ if (nextAlarm!.currentScheduleDateTime != null) {
+ listFilterItems.insert(
+ 0,
+ ListFilter(
+ (context) => getNextAlarmText(context, nextAlarm!),
+ (alarm) => alarm.id == nextAlarm!.id,
+ ));
+ }
+ }
+
+ return listFilterItems;
+ }
+
@override
Widget build(BuildContext context) {
Future selectTime() async {
@@ -221,29 +253,38 @@ class _AlarmScreenState extends State {
},
placeholderText: AppLocalizations.of(context)!.noAlarmMessage,
reloadOnPop: true,
- listFilters: _showFilters.value ? alarmListFilters : [],
+ onSaveItems: (items) {
+ nextAlarm = getNextAlarm();
+ setState(() {});
+ },
+ // header: getNextAlarmWidget(),
+ listFilters: getListFilterItems(),
customActions: _showFilters.value
? [
ListFilterCustomAction(
- name: AppLocalizations.of(context)!.enableAllFilteredAlarmsAction,
+ name: AppLocalizations.of(context)!
+ .enableAllFilteredAlarmsAction,
icon: Icons.alarm_on_rounded,
action: (alarms) {
_handleEnableChangeMultiple(alarms, true);
}),
ListFilterCustomAction(
- name: AppLocalizations.of(context)!.disableAllFilteredAlarmsAction,
+ name: AppLocalizations.of(context)!
+ .disableAllFilteredAlarmsAction,
icon: Icons.alarm_off_rounded,
action: (alarms) {
_handleEnableChangeMultiple(alarms, false);
}),
ListFilterCustomAction(
- name: AppLocalizations.of(context)!.skipAllFilteredAlarmsAction,
+ name: AppLocalizations.of(context)!
+ .skipAllFilteredAlarmsAction,
icon: Icons.skip_next_rounded,
action: (alarms) {
_handleSkipChangeMultiple(alarms, true);
}),
ListFilterCustomAction(
- name: AppLocalizations.of(context)!.cancelSkipAllFilteredAlarmsAction,
+ name: AppLocalizations.of(context)!
+ .cancelSkipAllFilteredAlarmsAction,
icon: Icons.skip_next_rounded,
action: (alarms) {
_handleSkipChangeMultiple(alarms, false);
diff --git a/lib/alarm/utils/next_alarm.dart b/lib/alarm/utils/next_alarm.dart
new file mode 100644
index 00000000..ebb8eaf3
--- /dev/null
+++ b/lib/alarm/utils/next_alarm.dart
@@ -0,0 +1,16 @@
+
+
+import 'package:clock_app/alarm/types/alarm.dart';
+import 'package:clock_app/common/utils/json_serialize.dart';
+import 'package:clock_app/common/utils/list_storage.dart';
+
+Alarm? getNextAlarm () {
+ List alarms = loadListSync('alarms');
+ if (alarms.isEmpty) return null;
+ alarms.sort((a, b) {
+ if (a.currentScheduleDateTime == null) return 1;
+ if (b.currentScheduleDateTime == null) return -1;
+ return a.currentScheduleDateTime!.compareTo(b.currentScheduleDateTime!);
+ });
+ return alarms.first;
+}
diff --git a/lib/common/utils/json_serialize.dart b/lib/common/utils/json_serialize.dart
index 55c8016d..c052c95f 100644
--- a/lib/common/utils/json_serialize.dart
+++ b/lib/common/utils/json_serialize.dart
@@ -9,6 +9,7 @@ import 'package:clock_app/common/types/tag.dart';
import 'package:clock_app/common/types/time.dart';
import 'package:clock_app/clock/types/city.dart';
import 'package:clock_app/common/types/json.dart';
+import 'package:clock_app/stopwatch/types/lap.dart';
import 'package:clock_app/stopwatch/types/stopwatch.dart';
import 'package:clock_app/theme/types/color_scheme.dart';
import 'package:clock_app/theme/types/style_theme.dart';
@@ -27,6 +28,7 @@ final fromJsonFactories = {
StyleTheme: (Json json) => StyleTheme.fromJson(json),
AlarmTask: (Json json) => AlarmTask.fromJson(json),
Time: (Json json) => Time.fromJson(json),
+ Lap: (Json json) => Lap.fromJson(json),
TimeOfDay: (Json json) => TimeOfDayUtils.fromJson(json),
FileItem: (Json json) => FileItem.fromJson(json),
AlarmEvent: (Json json) => AlarmEvent.fromJson(json),
@@ -34,19 +36,20 @@ final fromJsonFactories = {
Tag: (Json json) => Tag.fromJson(json),
};
-
String listToString(List items) => json.encode(
items.map((item) => item.toJson()).toList(),
);
List listFromString(String encodedItems) {
if (!fromJsonFactories.containsKey(T)) {
- throw Exception("No fromJson factory for type '$T'");
+ throw Exception(
+ "No fromJson factory for type '$T'. Please add one in the file 'common/utils/json_serialize.dart'");
}
try {
- return (json.decode(encodedItems) as List)
- .map((json) => fromJsonFactories[T]!(json))
- .toList();
+ List rawList = json.decode(encodedItems) as List;
+ Function fromJson = fromJsonFactories[T]!;
+ List list = rawList.map((json) => fromJson(json)).toList();
+ return list;
} catch (e) {
debugPrint("Error decoding string: ${e.toString()}");
rethrow;
diff --git a/lib/common/utils/list_storage.dart b/lib/common/utils/list_storage.dart
index 437e38c4..71383cf5 100644
--- a/lib/common/utils/list_storage.dart
+++ b/lib/common/utils/list_storage.dart
@@ -61,12 +61,13 @@ Future> loadList(String key) async {
Future saveList(
String key, List list) async {
- await saveTextFile(key, listToString(list));
+ await saveTextFile(key, listToString(list));
}
Future initList(
- String key, List value) async {
- await initTextFile(key, listToString(value));
+ String key, List list) async {
+
+ await initTextFile(key, listToString(list));
}
Future initTextFile(String key, String value) async {
diff --git a/lib/common/widgets/list/custom_list_view.dart b/lib/common/widgets/list/custom_list_view.dart
index 9e4f3fe2..429eb7cb 100644
--- a/lib/common/widgets/list/custom_list_view.dart
+++ b/lib/common/widgets/list/custom_list_view.dart
@@ -39,6 +39,8 @@ class CustomListView- extends StatefulWidget {
this.sortOptions = const [],
this.initialSortIndex = 0,
this.onChangeSortIndex,
+ this.header,
+
});
final List
- items;
@@ -60,6 +62,7 @@ class CustomListView
- extends StatefulWidget {
final List> customActions;
final List> sortOptions;
final Function(int index)? onChangeSortIndex;
+ final Widget? header;
@override
State createState() => _CustomListViewState
- ();
@@ -414,6 +417,7 @@ class _CustomListViewState
-
),
),
),
+ if(widget.header != null) widget.header!,
Expanded(
flex: 1,
child: Stack(children: [
@@ -462,6 +466,8 @@ class _CustomListViewState
-
reorderDecorationBuilder:
widget.isReorderable ? reorderableListDecorator : null,
footer: const SizedBox(height: 64 + 80),
+ // header: widget.header,
+
// cacheExtent: double.infinity,
),
),
diff --git a/lib/common/widgets/list/persistent_list_view.dart b/lib/common/widgets/list/persistent_list_view.dart
index ac453fc8..fb7b3b6d 100644
--- a/lib/common/widgets/list/persistent_list_view.dart
+++ b/lib/common/widgets/list/persistent_list_view.dart
@@ -75,6 +75,8 @@ class PersistentListView
- extends StatefulWidget {
this.listFilters = const [],
this.customActions = const [],
this.sortOptions = const [],
+ this.header,
+ this.onSaveItems = null,
// this.initialSortIndex = 0,
});
@@ -91,10 +93,12 @@ class PersistentListView
- extends StatefulWidget {
final bool isDuplicateEnabled;
final bool reloadOnPop;
final bool shouldInsertOnTop;
+ final Widget? header;
// final int initialSortIndex;
final List> listFilters;
final List> customActions;
final List> sortOptions;
+ final Function(List
- items)? onSaveItems;
@override
State createState() => _PersistentListViewState
- ();
@@ -159,10 +163,12 @@ class _PersistentListViewState
-
}
}
- void _saveItems() {
+ void _saveItems () async {
if (widget.saveTag.isNotEmpty) {
- saveList
- (widget.saveTag, _items);
+ await saveList
- (widget.saveTag, _items);
}
+ widget.onSaveItems?.call(_items);
+
}
void _handleChangeSort(int index) {
@@ -191,6 +197,7 @@ class _PersistentListViewState
-
sortOptions: widget.sortOptions,
initialSortIndex: _initialSortIndex,
onChangeSortIndex: _handleChangeSort,
+ header: widget.header,
);
}
}
diff --git a/lib/common/widgets/time_picker.dart b/lib/common/widgets/time_picker.dart
index c86a596a..8f651041 100644
--- a/lib/common/widgets/time_picker.dart
+++ b/lib/common/widgets/time_picker.dart
@@ -2820,8 +2820,15 @@ class _TimePickerDialogState extends State
TextTheme textTheme = theme.textTheme;
ColorScheme colorScheme = theme.colorScheme;
- bool use24hMode = MediaQuery.of(context).alwaysUse24HourFormat ||
- appSettings.getSetting("Time Format").value == TimeFormat.h24;
+ TimeFormat timeFormat = appSettings.getSetting("Time Format").value;
+
+ bool use24hMode = false;
+ if (timeFormat == TimeFormat.device) {
+ use24hMode = MediaQuery.of(context).alwaysUse24HourFormat;
+ } else {
+ use24hMode =
+ appSettings.getSetting("Time Format").value == TimeFormat.h24;
+ }
switch (type) {
case TimePickerType.spinner:
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index a95d89ae..a94311d7 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -674,7 +674,35 @@
"editTagLabel": "Edit Tag",
"@editTagLabel": {},
"tagNamePlaceholder": "Tag name",
- "@tagNamePlaceholder": {}
-
-
+ "@tagNamePlaceholder": {},
+ "hoursString": "{count, plural, =0{} =1{1 hour} other{{count} hours}}",
+ "@hoursString": {},
+ "minutesString": "{count, plural, =0{} =1{1 minute} other{{count} minutes}}",
+ "@minutesString": {},
+ "secondsString": "{count, plural, =0{} =1{1 second} other{{count} seconds}}",
+ "@secondsString": {},
+ "daysString": "{count, plural, =0{} =1{1 day} other{{count} days}}",
+ "@daysString": {},
+ "weeksString": "{count, plural, =0{} =1{1 week} other{{count} weeks}}",
+ "@weeksString": {},
+ "monthsString": "{count, plural, =0{} =1{1 month} other{{count} months}}",
+ "@monthsString": {},
+ "yearsString": "{count, plural, =0{} =1{1 year} other{{count} years}}",
+ "@yearsString": {},
+ "lessThanOneMinute": "less than 1 minute",
+ "@lessThanOneMinute": {},
+ "alarmRingInMessage": "Alarm will ring in {duration}",
+ "@alarmRingInMessage": {},
+ "nextAlarmIn": "Next: {duration}",
+ "@nextAlarmIn": {},
+ "combinedTime": "{hours} and {minutes}",
+ "@combinedTime": {},
+ "shortHoursString": "{hours}h",
+ "@shortTimeFormat": {},
+ "shortMinutesString": "{minutes}m",
+ "@shortMinutesString": {},
+ "shortSecondsString": "{seconds}s",
+ "@shortSecondsString": {},
+ "showNextAlarm": "Show Next Alarm",
+ "@showNextAlarm": {}
}
diff --git a/lib/navigation/screens/nav_scaffold.dart b/lib/navigation/screens/nav_scaffold.dart
index 0770b4a8..904a6acf 100644
--- a/lib/navigation/screens/nav_scaffold.dart
+++ b/lib/navigation/screens/nav_scaffold.dart
@@ -57,7 +57,7 @@ class _NavScaffoldState extends State {
DateTime? nextScheduleDateTime = alarm.currentScheduleDateTime;
if (nextScheduleDateTime == null) return;
ScaffoldMessenger.of(context).showSnackBar(
- getSnackbar(getNewAlarmSnackbarText(alarm), fab: true, navBar: true));
+ getSnackbar(getNewAlarmText(context, alarm), fab: true, navBar: true));
});
}
diff --git a/lib/settings/data/alarm_app_settings_schema.dart b/lib/settings/data/alarm_app_settings_schema.dart
index 89a3a6c3..eaf9076a 100644
--- a/lib/settings/data/alarm_app_settings_schema.dart
+++ b/lib/settings/data/alarm_app_settings_schema.dart
@@ -77,6 +77,9 @@ SettingGroup alarmAppSettingsSchema = SettingGroup(
(context) => AppLocalizations.of(context)!.showFiltersSetting, true),
SwitchSetting("Show Sort",
(context) => AppLocalizations.of(context)!.showSortSetting, true),
+ SwitchSetting("Show Next Alarm",
+ (context) => AppLocalizations.of(context)!.showNextAlarm, false),
+
]),
SettingGroup(
"Notifications",
diff --git a/lib/settings/data/general_settings_schema.dart b/lib/settings/data/general_settings_schema.dart
index 1bbd4174..1843e658 100644
--- a/lib/settings/data/general_settings_schema.dart
+++ b/lib/settings/data/general_settings_schema.dart
@@ -59,11 +59,11 @@ final dateFormatOptions = [
final longDateFormatOptions = [
_getDateSettingOption("EEE, MMM d"),
- _getDateSettingOption("EEE, MMMM d "),
+ _getDateSettingOption("EEE, MMMM d"),
_getDateSettingOption("EEE, d MMM"),
_getDateSettingOption("EEE, d MMMM"),
_getDateSettingOption("EEEE, MMM d"),
- _getDateSettingOption("EEEE, MMMM d "),
+ _getDateSettingOption("EEEE, MMMM d"),
_getDateSettingOption("EEEE, d MMM"),
_getDateSettingOption("EEEE, d MMMM"),
];
diff --git a/lib/stopwatch/screens/stopwatch_screen.dart b/lib/stopwatch/screens/stopwatch_screen.dart
index a4645316..68a3bf13 100644
--- a/lib/stopwatch/screens/stopwatch_screen.dart
+++ b/lib/stopwatch/screens/stopwatch_screen.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:clock_app/common/types/list_controller.dart';
import 'package:clock_app/common/utils/list_storage.dart';
+import 'package:clock_app/common/widgets/card_container.dart';
import 'package:clock_app/common/widgets/list/custom_list_view.dart';
import 'package:clock_app/common/widgets/fab.dart';
import 'package:clock_app/notifications/data/notification_channel.dart';
@@ -17,8 +18,6 @@ import 'package:clock_app/stopwatch/widgets/stopwatch_ticker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
-
class StopwatchScreen extends StatefulWidget {
const StopwatchScreen({super.key});
@@ -30,9 +29,8 @@ class _StopwatchScreenState extends State {
final _listController = ListController();
late Setting _showNotificationSetting;
-
- late final ClockStopwatch _stopwatch;
+ late final ClockStopwatch _stopwatch;
void update(dynamic value) {
setState(() {});
@@ -42,7 +40,7 @@ class _StopwatchScreenState extends State {
void initState() {
super.initState();
_stopwatch = loadListSync('stopwatches').first;
-
+
_showNotificationSetting =
appSettings.getGroup("Stopwatch").getSetting("Show Notification");
@@ -68,7 +66,6 @@ class _StopwatchScreenState extends State {
@override
void dispose() {
-
// updateNotificationInterval?.cancel();
// updateNotificationInterval = null;
@@ -88,18 +85,23 @@ class _StopwatchScreenState extends State {
void _handleAddLap() {
if (_stopwatch.currentLapTime.inMilliseconds == 0) return;
+ _stopwatch.finishLap(_stopwatch.laps.first);
+ _listController.changeItems((laps) => {});
_listController.addItem(_stopwatch.getLap());
saveList('stopwatches', [_stopwatch]);
showProgressNotification();
}
void _handleToggleState() {
+ if (_stopwatch.isStopped) {
+ _listController.addItem(_stopwatch.getLap());
+ }
setState(() {
_stopwatch.toggleState();
});
+
saveList('stopwatches', [_stopwatch]);
if (_stopwatch.isRunning) {
- // ticker!.start();
showProgressNotification();
} else {
stopwatchNotificationInterval?.cancel();
@@ -137,16 +139,18 @@ class _StopwatchScreenState extends State {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- StopwatchTicker(stopwatch:_stopwatch),
- const SizedBox(height: 8),
+ StopwatchTicker(stopwatch: _stopwatch),
+ const SizedBox(height: 8),
Expanded(
child: CustomListView(
items: _stopwatch.laps,
listController: _listController,
- itemBuilder: (lap) => LapCard(
- key: ValueKey(lap),
- lap: lap,
- ),
+ itemBuilder: (lap) => lap.isActive
+ ? ActiveLapCard(stopwatch: _stopwatch)
+ : LapCard(
+ key: ValueKey(lap),
+ lap: lap,
+ ),
placeholderText: AppLocalizations.of(context)!.noLapsMessage,
isDeleteEnabled: false,
isDuplicateEnabled: false,
@@ -182,5 +186,3 @@ class _StopwatchScreenState extends State {
);
}
}
-
-
diff --git a/lib/stopwatch/types/lap.dart b/lib/stopwatch/types/lap.dart
index e5f3a7d3..803c96c2 100644
--- a/lib/stopwatch/types/lap.dart
+++ b/lib/stopwatch/types/lap.dart
@@ -3,27 +3,45 @@ import 'package:clock_app/common/types/list_item.dart';
import 'package:clock_app/timer/types/time_duration.dart';
class Lap extends ListItem {
- late int number;
- late TimeDuration lapTime;
- late TimeDuration elapsedTime;
+ late int _number;
+ late TimeDuration _lapTime;
+ late TimeDuration _elapsedTime;
+ late bool _isActive;
+ int get number => _number;
+ bool get isActive => _isActive;
+ set isActive(bool value) => _isActive = value;
+ set lapTime(TimeDuration value) => _lapTime = value;
+ set elapsedTime(TimeDuration value) => _elapsedTime = value;
+ TimeDuration get lapTime => _lapTime;
+ TimeDuration get elapsedTime => _elapsedTime;
@override
int get id => number;
@override
bool get isDeletable => false;
- Lap({required this.elapsedTime, required this.number, required this.lapTime});
+ Lap(
+ {required int number,
+ TimeDuration elapsedTime = const TimeDuration(),
+ TimeDuration lapTime = const TimeDuration(),
+ bool isActive = false})
+ : _lapTime = lapTime,
+ _number = number,
+ _elapsedTime = elapsedTime,
+ _isActive = isActive;
Lap.fromJson(Json? json) {
if (json == null) {
- number = 0;
- lapTime = TimeDuration.zero;
- elapsedTime = TimeDuration.zero;
+ _number = 0;
+ _lapTime = TimeDuration.zero;
+ _elapsedTime = TimeDuration.zero;
+ _isActive = false;
return;
}
- number = json['number'] ?? 0;
- lapTime = TimeDuration.fromJson(json['lapTime']);
- elapsedTime = TimeDuration.fromJson(json['elapsedTime']);
+ _number = json['number'] ?? 0;
+ _lapTime = TimeDuration.fromJson(json['lapTime']);
+ _elapsedTime = TimeDuration.fromJson(json['elapsedTime']);
+ _isActive = json['isActive'] ?? false;
}
@override
@@ -31,17 +49,23 @@ class Lap extends ListItem {
'number': number,
'lapTime': lapTime.toJson(),
'elapsedTime': elapsedTime.toJson(),
+ 'isActive': _isActive,
};
@override
copy() {
- return Lap(elapsedTime: elapsedTime, number: number, lapTime: lapTime);
+ return Lap(
+ elapsedTime: elapsedTime,
+ number: number,
+ lapTime: lapTime,
+ isActive: _isActive);
}
@override
void copyFrom(other) {
- number = other.number;
- lapTime = TimeDuration.from(other.lapTime);
- elapsedTime = TimeDuration.from(other.elapsedTime);
+ _number = other.number;
+ _lapTime = TimeDuration.from(other.lapTime);
+ _elapsedTime = TimeDuration.from(other.elapsedTime);
+ _isActive = other._isActive;
}
}
diff --git a/lib/stopwatch/types/stopwatch.dart b/lib/stopwatch/types/stopwatch.dart
index 19a88ef4..cc3a2a68 100644
--- a/lib/stopwatch/types/stopwatch.dart
+++ b/lib/stopwatch/types/stopwatch.dart
@@ -1,10 +1,12 @@
import 'package:clock_app/common/types/json.dart';
import 'package:clock_app/common/types/timer_state.dart';
import 'package:clock_app/common/utils/duration.dart';
+import 'package:clock_app/common/utils/json_serialize.dart';
import 'package:clock_app/stopwatch/types/lap.dart';
import 'package:clock_app/timer/types/time_duration.dart';
import 'package:flutter/material.dart';
+// All time units are in milliseconds
class ClockStopwatch extends JsonSerializable {
int _elapsedMillisecondsOnPause = 0;
DateTime _startTime = DateTime(0);
@@ -16,28 +18,37 @@ class ClockStopwatch extends JsonSerializable {
int get id => _id;
List get laps => _laps;
+ List get finishedLaps => _laps.where((lap) => !lap.isActive).toList();
int get elapsedMilliseconds => _state == TimerState.running
? DateTime.now().difference(_startTime).toTimeDuration().inMilliseconds
: _elapsedMillisecondsOnPause;
+ TimeDuration get elapsedTime =>
+ TimeDuration.fromMilliseconds(elapsedMilliseconds);
bool get isRunning => _state == TimerState.running;
+ bool get isStopped => _state == TimerState.stopped;
bool get isStarted =>
_state == TimerState.running || _state == TimerState.paused;
TimerState get state => _state;
TimeDuration get currentLapTime =>
- TimeDuration.fromMilliseconds(elapsedMilliseconds -
- (_laps.isNotEmpty ? _laps.first.elapsedTime.inMilliseconds : 0));
- Lap? get previousLap => _laps.isNotEmpty ? _laps.first : null;
+ TimeDuration.fromMilliseconds(elapsedMilliseconds - lastLapElapsedTime);
+ int get lastLapElapsedTime {
+ if (finishedLaps.isEmpty) return 0;
+ return finishedLaps.first.elapsedTime.inMilliseconds;
+ }
+
+ Lap? get previousLap => finishedLaps.isNotEmpty ? finishedLaps.first : null;
Lap? get fastestLap => _fastestLap;
Lap? get slowestLap => _slowestLap;
Lap? get averageLap {
- if (_laps.isEmpty) return null;
- var totalMilliseconds = _laps.fold(
+ if (finishedLaps.isEmpty) return null;
+ var totalMilliseconds = finishedLaps.fold(
0, (previousValue, lap) => previousValue + lap.lapTime.inMilliseconds);
return Lap(
- elapsedTime:
- TimeDuration.fromMilliseconds(totalMilliseconds ~/ _laps.length),
+ elapsedTime: TimeDuration.fromMilliseconds(
+ totalMilliseconds ~/ finishedLaps.length),
number: _laps.length + 1,
- lapTime: TimeDuration.fromMilliseconds(totalMilliseconds ~/ _laps.length),
+ lapTime: TimeDuration.fromMilliseconds(
+ totalMilliseconds ~/ finishedLaps.length),
);
}
@@ -95,28 +106,36 @@ class ClockStopwatch extends JsonSerializable {
}
void updateFastestAndSlowestLap() {
- _fastestLap = _laps.reduce((value, element) =>
+ if (finishedLaps.isEmpty) return;
+ _fastestLap = finishedLaps.reduce((value, element) =>
value.lapTime.inMilliseconds < element.lapTime.inMilliseconds
? value
: element);
- _slowestLap = _laps.reduce((value, element) =>
+ _slowestLap = finishedLaps.reduce((value, element) =>
value.lapTime.inMilliseconds > element.lapTime.inMilliseconds
? value
: element);
}
void addLap() {
- if (currentLapTime.inMilliseconds == 0) return;
+ if (_laps.isNotEmpty) {
+ if (currentLapTime.inMilliseconds == 0) return;
+ finishLap(_laps.first);
+ updateFastestAndSlowestLap();
+ }
_laps.insert(0, getLap());
- updateFastestAndSlowestLap();
}
+ void finishLap(Lap lap) {
+ // This needs to be set before elapsedTime and isActive
+ lap.lapTime = currentLapTime;
+ lap.elapsedTime = TimeDuration.fromMilliseconds(elapsedMilliseconds);
+ lap.isActive = false;
+ }
+
+ //
Lap getLap() {
- return Lap(
- elapsedTime: TimeDuration.fromMilliseconds(elapsedMilliseconds),
- number: _laps.length + 1,
- lapTime: currentLapTime,
- );
+ return Lap(number: finishedLaps.length + 1, isActive: true);
}
@override
@@ -126,7 +145,7 @@ class ClockStopwatch extends JsonSerializable {
'elapsedMillisecondsOnPause': _elapsedMillisecondsOnPause,
'startTime': _startTime.toIso8601String(),
'state': _state.toString(),
- 'laps': _laps.map((e) => e.toJson()).toList(),
+ 'laps': listToString(_laps),
};
}
@@ -143,6 +162,8 @@ class ClockStopwatch extends JsonSerializable {
(e) => e.toString() == (json['state'] ?? ''),
orElse: () => TimerState.stopped);
_id = json['id'] ?? UniqueKey().hashCode;
- _laps = ((json['laps'] ?? []) as List).map((e) => Lap.fromJson(e)).toList();
+ // _finishedLaps = [];
+ _laps = listFromString(json['laps'] ?? '');
+ updateFastestAndSlowestLap();
}
}
diff --git a/lib/stopwatch/widgets/lap_card.dart b/lib/stopwatch/widgets/lap_card.dart
index 5ce040a5..bb279d7d 100644
--- a/lib/stopwatch/widgets/lap_card.dart
+++ b/lib/stopwatch/widgets/lap_card.dart
@@ -1,23 +1,67 @@
import 'package:clock_app/stopwatch/types/lap.dart';
+import 'package:clock_app/stopwatch/types/stopwatch.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
-class LapCard extends StatefulWidget {
- const LapCard({super.key, required this.lap, this.onInit});
+class LapCard extends StatelessWidget {
+ const LapCard({super.key, required this.lap});
final Lap lap;
- final VoidCallback? onInit;
@override
- State createState() => _LapCardState();
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ children: [
+ Text('${lap.number}'),
+ const SizedBox(width: 16),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(lap.lapTime.toTimeString(showMilliseconds: true),
+ style: Theme.of(context).textTheme.displaySmall),
+ Text(
+ '${AppLocalizations.of(context)!.elapsedTime}: ${lap.elapsedTime.toTimeString(showMilliseconds: true)}'),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class ActiveLapCard extends StatefulWidget {
+ const ActiveLapCard({
+ super.key,
+ required this.stopwatch,
+ });
+
+ final ClockStopwatch stopwatch;
+
+ @override
+ State createState() => _ActiveLapCardState();
}
-class _LapCardState extends State {
+class _ActiveLapCardState extends State {
+ late Ticker ticker;
+
+ void tick(Duration elapsed) {
+ setState(() {});
+ }
+
@override
void initState() {
+ ticker = Ticker(tick);
+ ticker.start();
super.initState();
- // widget.onInit?.call();
+ }
+
+ @override
+ void dispose() {
+ ticker.dispose();
+ super.dispose();
}
@override
@@ -26,15 +70,17 @@ class _LapCardState extends State {
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
- Text('${widget.lap.number}'),
+ Text('${widget.stopwatch.laps.length}'),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(widget.lap.lapTime.toTimeString(showMilliseconds: true),
+ Text(
+ widget.stopwatch.currentLapTime
+ .toTimeString(showMilliseconds: true),
style: Theme.of(context).textTheme.displaySmall),
Text(
- '${AppLocalizations.of(context)!.elapsedTime}: ${widget.lap.elapsedTime.toTimeString(showMilliseconds: true)}'),
+ '${AppLocalizations.of(context)!.elapsedTime}: ${widget.stopwatch.elapsedTime.toTimeString(showMilliseconds: true)}'),
],
),
],
diff --git a/lib/stopwatch/widgets/stopwatch_ticker.dart b/lib/stopwatch/widgets/stopwatch_ticker.dart
index 5a3bae65..9bbe4a5f 100644
--- a/lib/stopwatch/widgets/stopwatch_ticker.dart
+++ b/lib/stopwatch/widgets/stopwatch_ticker.dart
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
class StopwatchTicker extends StatefulWidget {
const StopwatchTicker({super.key, required this.stopwatch});
@@ -31,10 +30,6 @@ class _StopwatchTickerState extends State {
}
void tick(Duration elapsed) {
- // var t = elapsed.inMicroseconds * 1e-6;
- // double radius = 100;
- // drawState.x = radius * math.sin(t);
- // drawState.y = radius * math.cos(t);
setState(() {});
}
@@ -77,9 +72,6 @@ class _StopwatchTickerState extends State {
ticker.stop();
ticker.dispose();
- // updateNotificationInterval?.cancel();
- // updateNotificationInterval = null;
-
super.dispose();
}
diff --git a/lib/timer/screens/timer_notification_screen.dart b/lib/timer/screens/timer_notification_screen.dart
index 21a8e95e..4a291c79 100644
--- a/lib/timer/screens/timer_notification_screen.dart
+++ b/lib/timer/screens/timer_notification_screen.dart
@@ -36,13 +36,13 @@ class _TimerNotificationScreenState extends State {
);
void _addTime() {
- AlarmNotificationManager.snoozeAlarm(
- widget.scheduleIds[0], ScheduledNotificationType.timer);
+ AlarmNotificationManager.dismissNotification(widget.scheduleIds[0],
+ AlarmDismissType.snooze, ScheduledNotificationType.timer);
}
void _stop() {
- AlarmNotificationManager.dismissAlarm(
- widget.scheduleIds[0], ScheduledNotificationType.timer);
+ AlarmNotificationManager.dismissNotification(widget.scheduleIds[0],
+ AlarmDismissType.dismiss, ScheduledNotificationType.timer);
}
@override
diff --git a/pubspec.yaml b/pubspec.yaml
index d00008a4..25068e71 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,7 +2,7 @@ name: clock_app
description: An alarm, clock, timer and stowatch app.
publish_to: "none" # Remove this line if you wish to publish to pub.dev
-version: 0.5.0-beta3+22
+version: 0.5.1+24
environment:
sdk: ">=2.18.6 <4.0.0"
@@ -41,6 +41,8 @@ dependencies:
git:
url: https://github.com/AhsanSarwar45/great_list_view
ref: master
+ # great_list_view:
+ # path: "../great_list_view"
get_storage: ^2.1.1
queue: ^3.1.0+2
table_calendar: ^3.0.8