From 756e50c63b2735241f82fac3c476552404cff4ef Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Fri, 20 Sep 2024 00:33:07 +0500 Subject: [PATCH 1/8] Fix alarm label on notification screen --- .../screens/alarm_notification_screen.dart | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/alarm/screens/alarm_notification_screen.dart b/lib/alarm/screens/alarm_notification_screen.dart index ec92e689..d4e82fe6 100644 --- a/lib/alarm/screens/alarm_notification_screen.dart +++ b/lib/alarm/screens/alarm_notification_screen.dart @@ -117,28 +117,34 @@ class _AlarmNotificationScreenState extends State { children: [ if (_currentIndex <= 0) Expanded( - flex: 1, - child: Column( - children: [ - const Spacer(), - if (alarm.label.isNotEmpty) + flex: 2, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + const Spacer(), + if (alarm.label.isNotEmpty) + Text( + alarm.label, + style: Theme.of(context).textTheme.displayMedium, + textAlign: TextAlign.center, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + const DigitalClock( + // dateTime: Date, + horizontalAlignment: ElementAlignment.center, + shouldShowDate: false, + shouldShowSeconds: false, + ), + const SizedBox(height: 8), Text( - alarm.label, - style: Theme.of(context).textTheme.titleMedium, + "Alarm", + style: Theme.of(context).textTheme.headlineMedium, ), - const SizedBox(height: 8), - const DigitalClock( - // dateTime: Date, - horizontalAlignment: ElementAlignment.center, - shouldShowDate: false, - shouldShowSeconds: false, - ), - const SizedBox(height: 8), - Text( - "Alarm", - style: Theme.of(context).textTheme.headlineMedium, - ), - ], + ], + ), ), ), Expanded( From 662c329d1e968fd2ac9348a6835116700c2b0ee3 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Sat, 21 Sep 2024 14:37:45 +0500 Subject: [PATCH 2/8] Fix duplicate items when importing backup --- lib/alarm/types/alarm.dart | 4 ++ lib/common/types/tag.dart | 6 +++ lib/common/types/time.dart | 14 ++++++ lib/settings/data/backup_options.dart | 70 ++++++++++++++++----------- lib/settings/types/setting.dart | 7 +-- lib/settings/types/setting_group.dart | 4 ++ lib/settings/types/setting_item.dart | 10 ++-- lib/theme/types/color_scheme.dart | 16 ++++++ lib/theme/types/style_theme.dart | 12 ++++- lib/timer/types/time_duration.dart | 14 ++++++ lib/timer/types/timer.dart | 6 ++- lib/timer/types/timer_preset.dart | 5 ++ 12 files changed, 128 insertions(+), 40 deletions(-) diff --git a/lib/alarm/types/alarm.dart b/lib/alarm/types/alarm.dart index bd23d5df..d4f004e3 100644 --- a/lib/alarm/types/alarm.dart +++ b/lib/alarm/types/alarm.dart @@ -450,4 +450,8 @@ class Alarm extends CustomizableListItem { 'settings': _settings.valueToJson(), 'skippedTime': _skippedTime?.millisecondsSinceEpoch, }; + + bool isEqualTo(Alarm other) { + return _time == other._time && _settings.isEqualTo(other._settings); + } } diff --git a/lib/common/types/tag.dart b/lib/common/types/tag.dart index d665fbfe..0e1fae0e 100644 --- a/lib/common/types/tag.dart +++ b/lib/common/types/tag.dart @@ -49,4 +49,10 @@ class Tag extends ListItem { description = other.description; color = other.color; } + + bool isEqualTo(Tag other) { + return name == other.name && + description == other.description && + color == other.color; + } } diff --git a/lib/common/types/time.dart b/lib/common/types/time.dart index 4cd27f5c..74f29bce 100644 --- a/lib/common/types/time.dart +++ b/lib/common/types/time.dart @@ -69,4 +69,18 @@ class Time extends JsonSerializable { return time >= startTime || time <= endTime; } } + + @override + bool operator ==(Object other) { + if (other is Time) { + return hour == other.hour && + minute == other.minute && + second == other.second; + } + return false; + } + + @override + int get hashCode => Object.hash(hour, minute, second); + } diff --git a/lib/settings/data/backup_options.dart b/lib/settings/data/backup_options.dart index c6fc30f3..01c76d5a 100644 --- a/lib/settings/data/backup_options.dart +++ b/lib/settings/data/backup_options.dart @@ -4,6 +4,7 @@ import 'package:clock_app/alarm/logic/update_alarms.dart'; import 'package:clock_app/alarm/types/alarm.dart'; import 'package:clock_app/app.dart'; import 'package:clock_app/clock/types/city.dart'; +import 'package:clock_app/common/types/tag.dart'; import 'package:clock_app/common/utils/json_serialize.dart'; import 'package:clock_app/common/utils/list_storage.dart'; import 'package:clock_app/settings/data/settings_schema.dart'; @@ -27,11 +28,13 @@ final backupOptions = [ return await loadTextFile("tags"); }, decode: (context, value) async { - await saveList("tags", [ - ...listFromString(value) - .map((tag) => TimerPreset.from(tag)), - ...await loadList("tags") - ]); + final existingItems = await loadList("tags"); + final itemsToAdd = listFromString(value) + .where((tag) => + !existingItems.any((existingTag) => existingTag.isEqualTo(tag))) + .map((tag) => Tag.from(tag)); + + await saveList("tags", [...itemsToAdd, ...existingItems]); }, ), BackupOption( @@ -45,10 +48,15 @@ final backupOptions = [ return listToString(customColorSchemes); }, decode: (context, value) async { + final existingItems = await loadList("color_schemes"); + final itemsToAdd = listFromString(value) + .where((colorScheme) => !existingItems.any((existingColorScheme) => + existingColorScheme.isEqualTo(colorScheme))) + .map((scheme) => ColorSchemeData.from(scheme)); + await saveList("color_schemes", [ - ...listFromString(value) - .map((scheme) => ColorSchemeData.from(scheme)), - ...await loadList("color_schemes") + ...itemsToAdd, + ...existingItems, ]); if (context.mounted) App.refreshTheme(context); }, @@ -63,11 +71,13 @@ final backupOptions = [ return listToString(customThemes); }, decode: (context, value) async { - await saveList("style_themes", [ - ...listFromString(value) - .map((theme) => StyleTheme.from(theme)), - ...await loadList("style_themes") - ]); + final existingItems = await loadList("style_themes"); + final itemsToAdd = listFromString(value) + .where((theme) => !existingItems + .any((existingTheme) => existingTheme.isEqualTo(theme))) + .map((theme) => StyleTheme.from(theme)); + await saveList( + "style_themes", [...itemsToAdd, ...existingItems]); if (context.mounted) App.refreshTheme(context); }, ), @@ -94,10 +104,12 @@ final backupOptions = [ return await loadTextFile("alarms"); }, decode: (context, value) async { - await saveList("alarms", [ - ...listFromString(value).map((alarm) => Alarm.fromAlarm(alarm)), - ...await loadList("alarms") - ]); + final existingItems = await loadList("alarms"); + final itemsToAdd = listFromString(value) + .where((alarm) => !existingItems + .any((existingAlarm) => existingAlarm.isEqualTo(alarm))) + .map((alarm) => Alarm.fromAlarm(alarm)); + await saveList("alarms", [...itemsToAdd, ...existingItems]); await updateAlarms("Updated alarms on importing backup"); }, ), @@ -108,11 +120,12 @@ final backupOptions = [ return await loadTextFile("timers"); }, decode: (context, value) async { - await saveList("timers", [ - ...listFromString(value) - .map((timer) => ClockTimer.from(timer)), - ...await loadList("timers") - ]); + final existingItems = await loadList("timers"); + final itemsToAdd = listFromString(value) + .where((timer) => !existingItems + .any((existingTimer) => existingTimer.isEqualTo(timer))) + .map((timer) => ClockTimer.from(timer)); + await saveList("timers", [...itemsToAdd, ...existingItems]); await updateTimers("Updated timers on importing backup"); }, ), @@ -151,11 +164,14 @@ final backupOptions = [ return await loadTextFile("timer_presets"); }, decode: (context, value) async { - await saveList("timer_presets", [ - ...listFromString(value) - .map((preset) => TimerPreset.from(preset)), - ...await loadList("timer_presets") - ]); + final existingItems = await loadList("timer_presets"); + final itemsToAdd = listFromString(value) + .where((preset) => !existingItems + .any((existingPreset) => existingPreset.isEqualTo(preset))) + .map((preset) => TimerPreset.from(preset)); + + await saveList( + "timer_presets", [...itemsToAdd, ...existingItems]); }, ), ]; diff --git a/lib/settings/types/setting.dart b/lib/settings/types/setting.dart index af975507..1acbdae7 100644 --- a/lib/settings/types/setting.dart +++ b/lib/settings/types/setting.dart @@ -68,7 +68,8 @@ abstract class Setting extends SettingItem { } } -class CustomizableListSetting extends Setting> { +class CustomizableListSetting + extends Setting> { List possibleItems; Widget Function(T item, [VoidCallback?, VoidCallback?]) cardBuilder; Widget Function(T item) addCardBuilder; @@ -198,8 +199,6 @@ class ListSetting extends Setting> { ); } - - Widget getItemAddCard(T item) { return addCardBuilder(item); } @@ -209,7 +208,6 @@ class ListSetting extends Setting> { return cardBuilder(item, onDelete, onDuplicate); } - @override dynamic valueToJson() { return _value.map((e) => e.toJson()).toList(); @@ -222,7 +220,6 @@ class ListSetting extends Setting> { } } - class CustomSetting extends Setting { // The screen that will be navigated to when this setting is tapped. Widget Function(BuildContext, CustomSetting) screenBuilder; diff --git a/lib/settings/types/setting_group.dart b/lib/settings/types/setting_group.dart index f72263f7..c2a7cc41 100644 --- a/lib/settings/types/setting_group.dart +++ b/lib/settings/types/setting_group.dart @@ -171,6 +171,10 @@ class SettingGroup extends SettingItem { } } + bool isEqualTo(SettingGroup other) { + return json.encode(valueToJson()) == json.encode(other.valueToJson()); + } + @override dynamic valueToJson() { Json json = {}; diff --git a/lib/settings/types/setting_item.dart b/lib/settings/types/setting_item.dart index 49d162c3..606c8587 100644 --- a/lib/settings/types/setting_item.dart +++ b/lib/settings/types/setting_item.dart @@ -16,11 +16,9 @@ abstract class SettingItem { // Settings which influence whether this setting is enabled List enableSettings; - String displayName(BuildContext context) => - getLocalizedName(context); + String displayName(BuildContext context) => getLocalizedName(context); - String displayDescription(BuildContext context) => - getDescription(context); + String displayDescription(BuildContext context) => getDescription(context); bool get isEnabled { for (var enableSetting in enableSettings) { @@ -48,8 +46,8 @@ abstract class SettingItem { return path.reversed.toList(); } - SettingItem( - this.name, this.getLocalizedName, this.getDescription, this.searchTags, this.enableConditions ) + SettingItem(this.name, this.getLocalizedName, this.getDescription, + this.searchTags, this.enableConditions) : id = name, _settingListeners = [], enableSettings = []; diff --git a/lib/theme/types/color_scheme.dart b/lib/theme/types/color_scheme.dart index 24be3a5c..ece52d7e 100644 --- a/lib/theme/types/color_scheme.dart +++ b/lib/theme/types/color_scheme.dart @@ -97,6 +97,22 @@ class ColorSchemeData extends ThemeItem { ColorSchemeData.fromJson(Json json) : super.fromJson(json, colorSchemeSettingsSchema.copy()); + + bool isEqualTo(ColorSchemeData other) { + return background == other.background && + error == other.error && + accent == other.accent && + onError == other.onError && + card == other.card && + onCard == other.onCard && + onAccent == other.onAccent && + onBackground == other.onBackground && + shadow == other.shadow && + outline == other.outline && + useAccentAsShadow == other.useAccentAsShadow && + useAccentAsOutline == other.useAccentAsOutline && + name == other.name; + } } ColorScheme getColorScheme(ColorSchemeData colorSchemeData) { diff --git a/lib/theme/types/style_theme.dart b/lib/theme/types/style_theme.dart index c4775334..f0be1244 100644 --- a/lib/theme/types/style_theme.dart +++ b/lib/theme/types/style_theme.dart @@ -40,7 +40,7 @@ class StyleTheme extends ThemeItem { .setValueWithoutNotify(borderWidth); } - StyleTheme.from(StyleTheme colorSchemeData) : super.from(colorSchemeData); + StyleTheme.from(StyleTheme super.colorSchemeData) : super.from(); @override String get name => settings.getSetting("Name").value; @@ -65,4 +65,14 @@ class StyleTheme extends ThemeItem { StyleTheme.fromJson(Json json) : super.fromJson(json, styleThemeSettingsSchema.copy()); + + bool isEqualTo(StyleTheme other) { + return name == other.name && + shadowElevation == other.shadowElevation && + shadowOpacity == other.shadowOpacity && + shadowBlurRadius == other.shadowBlurRadius && + shadowSpreadRadius == other.shadowSpreadRadius && + borderRadius == other.borderRadius && + borderWidth == other.borderWidth; + } } diff --git a/lib/timer/types/time_duration.dart b/lib/timer/types/time_duration.dart index fd7bc61f..7deeecf1 100644 --- a/lib/timer/types/time_duration.dart +++ b/lib/timer/types/time_duration.dart @@ -112,4 +112,18 @@ class TimeDuration extends JsonSerializable { minutes = json != null ? json['minutes'] ?? 0 : 0, seconds = json != null ? json['seconds'] ?? 0 : 0, milliseconds = json != null ? json['milliseconds'] ?? 0 : 0; + + @override + bool operator ==(Object other) { + if (other is TimeDuration) { + return hours == other.hours && + minutes == other.minutes && + seconds == other.seconds && + milliseconds == other.milliseconds; + } + return false; + } + + @override + int get hashCode => Object.hash(hours, minutes, seconds, milliseconds); } diff --git a/lib/timer/types/timer.dart b/lib/timer/types/timer.dart index 87f8ca2f..6cd2b9e7 100644 --- a/lib/timer/types/timer.dart +++ b/lib/timer/types/timer.dart @@ -153,7 +153,7 @@ class ClockTimer extends CustomizableListItem { } Future snooze() async { - TimeDuration addedDuration = TimeDuration(minutes: addLength.floor()); + TimeDuration addedDuration = TimeDuration(minutes: addLength.floor()); _currentDuration = addedDuration; _milliSecondsRemainingOnPause = addedDuration.inSeconds * 1000; await start(); @@ -262,4 +262,8 @@ class ClockTimer extends CustomizableListItem { copy() { return ClockTimer.from(this); } + + bool isEqualTo(ClockTimer other) { + return _duration == other._duration && _settings.isEqualTo(other._settings); + } } diff --git a/lib/timer/types/timer_preset.dart b/lib/timer/types/timer_preset.dart index 4a15b3a6..aef325c5 100644 --- a/lib/timer/types/timer_preset.dart +++ b/lib/timer/types/timer_preset.dart @@ -1,6 +1,7 @@ import 'package:clock_app/common/types/json.dart'; import 'package:clock_app/common/types/list_item.dart'; import 'package:clock_app/common/utils/id.dart'; +import 'package:clock_app/developer/logic/logger.dart'; import 'package:clock_app/timer/types/time_duration.dart'; class TimerPreset extends ListItem { @@ -45,6 +46,10 @@ class TimerPreset extends ListItem { } } + bool isEqualTo(TimerPreset other) { + return name == other.name && duration == other.duration; + } + @override copy() { return TimerPreset(name, duration); From c405d82f2c58ee6a75f2932317bc922d5d511b36 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Mon, 23 Sep 2024 15:45:39 +0500 Subject: [PATCH 3/8] Fix timer now showing minutes when 0 --- lib/timer/types/time_duration.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/timer/types/time_duration.dart b/lib/timer/types/time_duration.dart index 7deeecf1..214dee94 100644 --- a/lib/timer/types/time_duration.dart +++ b/lib/timer/types/time_duration.dart @@ -86,8 +86,9 @@ class TimeDuration extends JsonSerializable { if (inMilliseconds == 0) return "0"; String twoDigits(int n) => n.toString().padLeft(2, "0").substring(0, 2); String hoursString = hours > 0 ? '$hours:' : ''; - String minutesString = - minutes > 0 ? (hours > 0 ? '${twoDigits(minutes)}:' : '$minutes:') : ''; + String minutesString = (minutes > 0 || hours > 0) + ? (hours > 0 ? '${twoDigits(minutes)}:' : '$minutes:') + : ''; String secondsString = (hours > 0 || minutes > 0) ? twoDigits(seconds) : '$seconds'; String millisecondsString = From df9fe0706243d732b224da406902ab2a5cec23e4 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Fri, 27 Sep 2024 20:29:48 +0500 Subject: [PATCH 4/8] Make alarm notification not dismissed on button press --- lib/notifications/logic/alarm_notifications.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/notifications/logic/alarm_notifications.dart b/lib/notifications/logic/alarm_notifications.dart index 79513988..6b961ab9 100644 --- a/lib/notifications/logic/alarm_notifications.dart +++ b/lib/notifications/logic/alarm_notifications.dart @@ -43,7 +43,7 @@ void showAlarmNotification({ key: dismissActionKey, label: '$dismissActionLabel All', actionType: ActionType.SilentAction, - autoDismissible: true, + autoDismissible: false, )); } else { if (showSnoozeButton) { @@ -52,7 +52,7 @@ void showAlarmNotification({ key: snoozeActionKey, label: snoozeActionLabel, actionType: ActionType.SilentAction, - autoDismissible: true, + autoDismissible: false, )); } @@ -61,7 +61,7 @@ void showAlarmNotification({ key: dismissActionKey, label: "${tasksRequired ? "Solve tasks to " : ""}$dismissActionLabel", actionType: tasksRequired ? ActionType.Default : ActionType.SilentAction, - autoDismissible: tasksRequired ? false : true, + autoDismissible: false, )); } From 867237331c793f00f0144eb290f50e89e16ec321 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Sat, 28 Sep 2024 00:22:01 +0500 Subject: [PATCH 5/8] Add memory task game --- lib/alarm/data/alarm_task_schemas.dart | 17 ++ lib/alarm/widgets/tasks/memory_task.dart | 245 +++++++++++++++++++++ lib/alarm/widgets/tasks/sequence_task.dart | 4 +- lib/l10n/app_en.arb | 4 +- lib/system/logic/handle_boot.dart | 11 +- 5 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 lib/alarm/widgets/tasks/memory_task.dart diff --git a/lib/alarm/data/alarm_task_schemas.dart b/lib/alarm/data/alarm_task_schemas.dart index 78361a32..49f9a72a 100644 --- a/lib/alarm/data/alarm_task_schemas.dart +++ b/lib/alarm/data/alarm_task_schemas.dart @@ -1,5 +1,6 @@ import 'package:clock_app/alarm/types/alarm_task.dart'; import 'package:clock_app/alarm/widgets/tasks/math_task.dart'; +import 'package:clock_app/alarm/widgets/tasks/memory_task.dart'; import 'package:clock_app/alarm/widgets/tasks/retype_task.dart'; import 'package:clock_app/alarm/widgets/tasks/sequence_task.dart'; import 'package:clock_app/settings/types/setting.dart'; @@ -98,4 +99,20 @@ Map alarmTaskSchemasMap = { return SequenceTask(onSolve: onSolve, settings: settings); }, ), + AlarmTaskType.memory: AlarmTaskSchema( + (context) => AppLocalizations.of(context)!.memoryTask, + SettingGroup("memorySettings", + (context) => AppLocalizations.of(context)!.memoryTask, [ + SliderSetting( + "numberOfPairs", + (context) => AppLocalizations.of(context)!.numberOfPairsSetting, + 3, + 10, + 3, + snapLength: 1), + ]), + (onSolve, settings) { + return MemoryTask(onSolve: onSolve, settings: settings); + }, + ), }; diff --git a/lib/alarm/widgets/tasks/memory_task.dart b/lib/alarm/widgets/tasks/memory_task.dart new file mode 100644 index 00000000..2a949e49 --- /dev/null +++ b/lib/alarm/widgets/tasks/memory_task.dart @@ -0,0 +1,245 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:clock_app/common/widgets/card_container.dart'; +import 'package:clock_app/settings/types/setting_group.dart'; +import 'package:flutter/material.dart'; + +class MemoryTask extends StatefulWidget { + const MemoryTask({ + super.key, + required this.onSolve, + required this.settings, + }); + + final VoidCallback onSolve; + final SettingGroup settings; + + @override + State createState() => _MemoryTaskState(); +} + +class _MemoryTaskState extends State with TickerProviderStateMixin { + late final int numberOfPairs = + widget.settings.getSetting("numberOfPairs").value.toInt(); + + late List _cards; + CardModel? _firstCard; + bool _isWaiting = false; + + @override + void initState() { + super.initState(); + _initializeCards(); + } + + void _initializeCards() { + // Generate pairs of cards + List cardValues = [ + ...List.generate(numberOfPairs, (index) => index + 1), + ...List.generate(numberOfPairs, (index) => index + 1) + ]; + // cardValues.addAll(cardValues); // Duplicate for pairs + cardValues.shuffle(); // Shuffle the cards + + _cards = cardValues + .map((value) => CardModel(value: value, isFlipped: false)) + .toList(); + } + + void _onCardTap(CardModel card) { + if (_isWaiting || card.isFlipped) return; + + setState(() { + card.isFlipped = true; + }); + + if (_firstCard == null) { + _firstCard = card; + } else { + if (_firstCard!.value == card.value) { + // Match found + _firstCard!.isCompleted = true; + card.isCompleted = true; + _firstCard = null; + + if (_cards.every((card) => card.isFlipped)) { + // All cards are flipped + Future.delayed(const Duration(seconds: 1), () { + widget.onSolve(); + }); + } + } else { + // No match, flip back after delay + _isWaiting = true; + Future.delayed(const Duration(seconds: 1), () { + setState(() { + card.isFlipped = false; + _firstCard!.isFlipped = false; + _firstCard = null; + _isWaiting = false; + }); + }); + } + } + } + + @override + Widget build(BuildContext context) { + ThemeData theme = Theme.of(context); + ColorScheme colorScheme = theme.colorScheme; + TextTheme textTheme = theme.textTheme; + int gridSize = (sqrt(_cards.length)).floor(); + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Text( + "Match card pairs", + style: textTheme.headlineMedium, + ), + const SizedBox(height: 16.0), + SizedBox( + width: double.infinity, + // height: 512, + child: GridView.builder( + itemCount: _cards.length, + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridSize, + ), + itemBuilder: (context, index) { + CardModel card = _cards[index]; + return GestureDetector( + key: ValueKey(card), + onTap: () => _onCardTap(card), + child: FlipCard( + isFlipped: card.isFlipped, + front: CardContainer( + margin: const EdgeInsets.all(4.0), + color: colorScheme.primary, + child: Center( + child: Text( + '?', + style: textTheme.displayMedium?.copyWith( + color: colorScheme.onPrimary, + ), + ), + ), + ), + back: CardContainer( + margin: const EdgeInsets.all(4.0), + color: card.isCompleted ? Colors.green : Colors.orangeAccent, + child: Center( + child: Text( + '${card.value}', + style: textTheme.displayMedium?.copyWith( + color: Colors.white, + + ), + ), + ), + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +class CardModel { + final int value; + bool isCompleted = false; + bool isFlipped; + + CardModel({required this.value, this.isFlipped = false}); +} + +class FlipCard extends StatefulWidget { + final Widget front; + final Widget back; + final bool isFlipped; + + const FlipCard({ + super.key, + required this.front, + required this.back, + required this.isFlipped, + }); + + @override + State createState() => _FlipCardState(); +} + +class _FlipCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + + _animation = Tween(begin: 0, end: 1).animate(_controller); + + if (widget.isFlipped) { + _controller.value = 1; + } + } + + @override + void didUpdateWidget(FlipCard oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.isFlipped != oldWidget.isFlipped) { + if (widget.isFlipped) { + _controller.forward(); + } else { + _controller.reverse(); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + final angle = _animation.value * pi; + final transform = Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateY(angle); + + Widget content; + if (angle <= pi / 2) { + content = widget.front; + } else { + content = widget.back; + transform.rotateY(pi); + } + + return Transform( + transform: transform, + alignment: Alignment.center, + child: content, + ); + }, + ); + } +} diff --git a/lib/alarm/widgets/tasks/sequence_task.dart b/lib/alarm/widgets/tasks/sequence_task.dart index 8496c674..9de6820b 100644 --- a/lib/alarm/widgets/tasks/sequence_task.dart +++ b/lib/alarm/widgets/tasks/sequence_task.dart @@ -7,10 +7,10 @@ import 'package:flutter/material.dart'; class SequenceTask extends StatefulWidget { const SequenceTask({ - Key? key, + super.key, required this.onSolve, required this.settings, - }) : super(key: key); + }); final VoidCallback onSolve; final SettingGroup settings; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 86275552..e993b9ce 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -369,7 +369,9 @@ "@sequenceLengthSetting": {}, "sequenceGridSizeSetting": "Grid size", "@sequenceGridSizeSetting": {}, - "numberOfProblemsSetting": "Number of Problems", + "memoryTask": "Memory", + "numberOfPairsSetting": "Number of pairs", + "numberOfProblemsSetting": "Number of problems", "@numberOfProblemsSetting": {}, "saveReminderAlert": "Do you want to leave without saving?", "@saveReminderAlert": {}, diff --git a/lib/system/logic/handle_boot.dart b/lib/system/logic/handle_boot.dart index 67c16986..53c4bb2e 100644 --- a/lib/system/logic/handle_boot.dart +++ b/lib/system/logic/handle_boot.dart @@ -13,12 +13,15 @@ void handleBoot() async { // File('$appDataDirectory/log-dart.txt') // .writeAsStringSync(message, mode: FileMode.append); // - FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.onError = (FlutterErrorDetails details) { logger.f("Error in handleBoot isolate: ${details.exception.toString()}"); }; await initializeIsolate(); - - await updateAlarms("handleBoot(): Update alarms on system boot"); - await updateTimers("handleBoot(): Update timers on system boot"); + try { + await updateAlarms("handleBoot(): Update alarms on system boot"); + await updateTimers("handleBoot(): Update timers on system boot"); + } catch (e) { + logger.f("Error in handleBoot isolate: ${e.toString()}"); + } } From 5fb5574d8d71fd1b2d6ad5ef31acd884cfaf6420 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Thu, 3 Oct 2024 22:03:05 +0500 Subject: [PATCH 6/8] Change notification action type to background --- lib/alarm/logic/alarm_reminder_notifications.dart | 4 ++-- lib/notifications/logic/alarm_notifications.dart | 14 ++++++++------ .../logic/notification_callbacks.dart | 6 +++--- lib/stopwatch/logic/stopwatch_notification.dart | 6 +++--- lib/timer/logic/timer_notification.dart | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/alarm/logic/alarm_reminder_notifications.dart b/lib/alarm/logic/alarm_reminder_notifications.dart index 9ae05cd0..c5f2ce73 100644 --- a/lib/alarm/logic/alarm_reminder_notifications.dart +++ b/lib/alarm/logic/alarm_reminder_notifications.dart @@ -62,7 +62,7 @@ Future createAlarmReminderNotification( key: "alarm_skip", label: 'Skip alarm${tasksRequired ? " (Solve tasks)" : ""}', actionType: - tasksRequired ? ActionType.Default : ActionType.SilentAction, + tasksRequired ? ActionType.Default : ActionType.SilentBackgroundAction, autoDismissible: true, ) ], @@ -110,7 +110,7 @@ Future createSnoozeNotification(int id, DateTime time) async { key: "alarm_skip_snooze", label: 'Dismiss alarm${tasksRequired ? " (Solve tasks)" : ""}', actionType: - tasksRequired ? ActionType.Default : ActionType.SilentAction, + tasksRequired ? ActionType.Default : ActionType.SilentBackgroundAction, autoDismissible: true, ) ], diff --git a/lib/notifications/logic/alarm_notifications.dart b/lib/notifications/logic/alarm_notifications.dart index 6b961ab9..6d0827fe 100644 --- a/lib/notifications/logic/alarm_notifications.dart +++ b/lib/notifications/logic/alarm_notifications.dart @@ -42,8 +42,8 @@ void showAlarmNotification({ showInCompactView: true, key: dismissActionKey, label: '$dismissActionLabel All', - actionType: ActionType.SilentAction, - autoDismissible: false, + actionType: ActionType.SilentBackgroundAction, + autoDismissible: true, )); } else { if (showSnoozeButton) { @@ -51,8 +51,8 @@ void showAlarmNotification({ showInCompactView: true, key: snoozeActionKey, label: snoozeActionLabel, - actionType: ActionType.SilentAction, - autoDismissible: false, + actionType: ActionType.SilentBackgroundAction, + autoDismissible: true, )); } @@ -60,8 +60,10 @@ void showAlarmNotification({ showInCompactView: true, key: dismissActionKey, label: "${tasksRequired ? "Solve tasks to " : ""}$dismissActionLabel", - actionType: tasksRequired ? ActionType.Default : ActionType.SilentAction, - autoDismissible: false, + actionType: tasksRequired + ? ActionType.Default + : ActionType.SilentBackgroundAction, + autoDismissible: tasksRequired ? false : true, )); } diff --git a/lib/notifications/logic/notification_callbacks.dart b/lib/notifications/logic/notification_callbacks.dart index 2901434c..c326359f 100644 --- a/lib/notifications/logic/notification_callbacks.dart +++ b/lib/notifications/logic/notification_callbacks.dart @@ -17,7 +17,7 @@ Future onNotificationCreatedMethod( case alarmNotificationChannelKey: Payload payload = receivedNotification.payload!; int? scheduleId = int.tryParse(payload['scheduleId']); - if (scheduleId == null) return; + if (scheduleId == null) return; // AlarmNotificationManager.handleNotificationCreated(receivedNotification); break; } @@ -36,7 +36,7 @@ Future onDismissActionReceivedMethod( switch (receivedAction.channelKey) { case alarmNotificationChannelKey: - handleAlarmNotificationDismiss( + await handleAlarmNotificationDismiss( receivedAction, AlarmDismissType.dismiss); break; } @@ -49,7 +49,7 @@ Future onActionReceivedMethod(ReceivedAction receivedAction) async { switch (receivedAction.channelKey) { case alarmNotificationChannelKey: - handleAlarmNotificationAction(receivedAction); + await handleAlarmNotificationAction(receivedAction); break; case reminderNotificationChannelKey: switch (receivedAction.buttonKeyPressed) { diff --git a/lib/stopwatch/logic/stopwatch_notification.dart b/lib/stopwatch/logic/stopwatch_notification.dart index 0ec6cf10..a2af9256 100644 --- a/lib/stopwatch/logic/stopwatch_notification.dart +++ b/lib/stopwatch/logic/stopwatch_notification.dart @@ -18,21 +18,21 @@ Future updateStopwatchNotification(ClockStopwatch stopwatch) async { showInCompactView: true, key: "stopwatch_toggle_state", label: stopwatch.isRunning ? 'Pause' : 'Start', - actionType: ActionType.SilentAction, + actionType: ActionType.SilentBackgroundAction, autoDismissible: false, ), NotificationActionButton( showInCompactView: true, key: "stopwatch_reset", label: 'Reset', - actionType: ActionType.SilentAction, + actionType: ActionType.SilentBackgroundAction, autoDismissible: false, ), NotificationActionButton( showInCompactView: true, key: "stopwatch_lap", label: 'Lap', - actionType: ActionType.SilentAction, + actionType: ActionType.SilentBackgroundAction, autoDismissible: false, ) ]); diff --git a/lib/timer/logic/timer_notification.dart b/lib/timer/logic/timer_notification.dart index 3cecb357..3f29db52 100644 --- a/lib/timer/logic/timer_notification.dart +++ b/lib/timer/logic/timer_notification.dart @@ -35,7 +35,7 @@ Future updateTimerNotification(ClockTimer timer, int count) async { showInCompactView: true, key: "timer_reset_all", label: 'Reset all', - actionType: ActionType.SilentAction, + actionType: ActionType.SilentBackgroundAction, autoDismissible: false, )); } From 5cc70aaa1af360779849f0f303bdb0a074254222 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Tue, 5 Nov 2024 00:12:13 +0500 Subject: [PATCH 7/8] Add option to disable background service --- lib/app.dart | 27 ++++++++++++- lib/l10n/app_en.arb | 2 + lib/main.dart | 5 +-- .../data/general_settings_schema.dart | 39 +++++++++++++------ lib/system/logic/background_service.dart | 14 +++++++ 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index a15cd55b..8fdf3d2a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -12,6 +12,7 @@ import 'package:clock_app/settings/data/settings_schema.dart'; import 'package:clock_app/settings/types/setting.dart'; import 'package:clock_app/settings/types/setting_group.dart'; import 'package:clock_app/system/data/app_info.dart'; +import 'package:clock_app/system/logic/background_service.dart'; import 'package:clock_app/theme/types/color_scheme.dart'; import 'package:clock_app/theme/theme.dart'; import 'package:clock_app/theme/types/style_theme.dart'; @@ -39,6 +40,11 @@ class App extends StatefulWidget { _AppState state = context.findAncestorStateOfType<_AppState>()!; state.refreshTheme(); } + + static void updateBackgroundService(BuildContext context) { + _AppState state = context.findAncestorStateOfType<_AppState>()!; + state.updateBackgroundService(); + } } class AppTheme { @@ -55,6 +61,8 @@ class _AppState extends State { late SettingGroup _styleSettings; late Setting _animationSpeedSetting; late SettingGroup _generalSettings; + late Setting _useBackgroundServiceSetting; + late Setting _backgroundServiceIntervalSetting; @override void initState() { @@ -68,9 +76,16 @@ class _AppState extends State { _colorSettings = _appearanceSettings.getGroup("Colors"); _styleSettings = _appearanceSettings.getGroup("Style"); _generalSettings = appSettings.getGroup("General"); - _animationSpeedSetting = - _appearanceSettings.getGroup("Animations").getSetting("Animation Speed"); + _animationSpeedSetting = _appearanceSettings + .getGroup("Animations") + .getSetting("Animation Speed"); _animationSpeedSetting.addListener(setAnimationSpeed); + _backgroundServiceIntervalSetting = _generalSettings + .getGroup('Reliability') + .getSetting('backgroundServiceInterval'); + _useBackgroundServiceSetting = _generalSettings + .getGroup('Reliability') + .getSetting('useBackgroundService'); setAnimationSpeed(_animationSpeedSetting.value); } @@ -83,6 +98,14 @@ class _AppState extends State { setState(() {}); } + void updateBackgroundService() { + if (_useBackgroundServiceSetting.value) { + initBackgroundService(interval: _backgroundServiceIntervalSetting.value); + } else { + stopBackgroundService(); + } + } + @override void dispose() { stopwatchNotificationInterval?.cancel(); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e993b9ce..7596b366 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -751,6 +751,8 @@ "@showForegroundNotification": {}, "showForegroundNotificationDescription": "Show a persistent notification to keep app alive", "@showForegroundNotificationDescription": {}, + "useBackgroundServiceSetting": "Use Background Service", + "useBackgroundServiceSettingDescription": "Might help keep the app alive in the background", "notificationPermissionDescription": "Allow notifications to be showed", "@notificationPermissionDescription": {}, "extraAnimationSettingDescription": "Show animations that are not polished and might cause frame drops in low-end devices", diff --git a/lib/main.dart b/lib/main.dart index 8e5dc5a1..fc2069e2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import 'package:clock_app/app.dart'; import 'package:clock_app/audio/logic/audio_session.dart'; import 'package:clock_app/audio/types/ringtone_player.dart'; import 'package:clock_app/common/data/paths.dart'; -import 'package:clock_app/developer/logic/logger.dart'; import 'package:clock_app/navigation/types/app_visibility.dart'; import 'package:clock_app/notifications/logic/foreground_task.dart'; import 'package:clock_app/notifications/logic/notifications.dart'; @@ -47,8 +46,8 @@ void main() async { await initializeStorage(); await initializeSettings(); - updateAlarms("Update Alarms on Start"); - updateTimers("Update Timers on Start"); + await updateAlarms("Update Alarms on Start"); + await updateTimers("Update Timers on Start"); AppVisibility.initialize(); initForegroundTask(); initBackgroundService(); diff --git a/lib/settings/data/general_settings_schema.dart b/lib/settings/data/general_settings_schema.dart index 1492892f..3044e917 100644 --- a/lib/settings/data/general_settings_schema.dart +++ b/lib/settings/data/general_settings_schema.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:app_settings/app_settings.dart'; import 'package:auto_start_flutter/auto_start_flutter.dart'; +import 'package:background_fetch/background_fetch.dart'; import 'package:clock_app/app.dart'; import 'package:clock_app/audio/screens/ringtones_screen.dart'; import 'package:clock_app/clock/types/time.dart'; @@ -16,6 +17,7 @@ import 'package:clock_app/notifications/logic/notifications.dart'; import 'package:clock_app/settings/screens/tags_screen.dart'; import 'package:clock_app/settings/types/setting.dart'; import 'package:clock_app/settings/types/setting_action.dart'; +import 'package:clock_app/settings/types/setting_enable_condition.dart'; import 'package:clock_app/settings/types/setting_group.dart'; import 'package:clock_app/settings/types/setting_link.dart'; import 'package:clock_app/system/logic/background_service.dart'; @@ -266,22 +268,36 @@ SettingGroup generalSettingsSchema = SettingGroup( .showForegroundNotificationDescription, searchTags: ["foreground", "notification"], ), - SliderSetting( - "backgroundServiceInterval", + SwitchSetting( + "useBackgroundService", (context) => - AppLocalizations.of(context)!.backgroundServiceIntervalSetting, - 15, - 300, - 60, - unit: "m", - snapLength: 15, + AppLocalizations.of(context)!.useBackgroundServiceSetting, + false, getDescription: (context) => AppLocalizations.of(context)! - .backgroundServiceIntervalSettingDescription, - searchTags: ["background", "service", "interval"], + .useBackgroundServiceSettingDescription, onChange: (context, value) { - initBackgroundService(interval: value.toInt()); + stopBackgroundService(); }, + searchTags: ["background", "service"], ), + SliderSetting( + "backgroundServiceInterval", + (context) => + AppLocalizations.of(context)!.backgroundServiceIntervalSetting, + 15, + 300, + 60, + unit: "m", + snapLength: 15, + getDescription: (context) => AppLocalizations.of(context)! + .backgroundServiceIntervalSettingDescription, + searchTags: ["background", "service", "interval"], + onChange: (context, value) { + initBackgroundService(interval: value.toInt()); + }, + enableConditions: [ + ValueCondition(["useBackgroundService"], (value) => value == true) + ]), SettingAction( "Ignore Battery Optimizations", (context) => @@ -330,7 +346,6 @@ SettingGroup generalSettingsSchema = SettingGroup( }, getDescription: (context) => AppLocalizations.of(context)! .batteryOptimizationSettingDescription, - ), SettingAction( "Allow Notifications", diff --git a/lib/system/logic/background_service.dart b/lib/system/logic/background_service.dart index cd73e2f0..9a4f0714 100644 --- a/lib/system/logic/background_service.dart +++ b/lib/system/logic/background_service.dart @@ -1,6 +1,7 @@ import 'package:background_fetch/background_fetch.dart'; import 'package:clock_app/alarm/logic/update_alarms.dart'; import 'package:clock_app/developer/logic/logger.dart'; +import 'package:clock_app/settings/data/settings_schema.dart'; import 'package:clock_app/system/logic/initialize_isolate.dart'; import 'package:clock_app/timer/logic/update_timers.dart'; import 'package:flutter/material.dart'; @@ -39,6 +40,10 @@ Future initBackgroundService({int interval = 60}) async { }); } +Future stopBackgroundService() async { + await BackgroundFetch.stop(); +} + // [Android-only] This "Headless Task" is run when the Android app is terminated with `enableHeadless: true` @pragma('vm:entry-point') void handleBackgroundServiceTask(HeadlessTask task) async { @@ -66,5 +71,14 @@ void handleBackgroundServiceTask(HeadlessTask task) async { } void registerHeadlessBackgroundService() { + if (appSettings + .getGroup('General') + .getGroup('Reliability') + .getSetting('useBackgroundService') + .value == + false) { + return; + } + BackgroundFetch.registerHeadlessTask(handleBackgroundServiceTask); } From c7abcd73a03c670046d60f321e517c231e450a45 Mon Sep 17 00:00:00 2001 From: AhsanSarwar45 Date: Tue, 5 Nov 2024 00:15:53 +0500 Subject: [PATCH 8/8] Disable auto backup --- android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 161e27e9..76edb8c1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ android:label="@string/app_name" tools:replace="android:label" android:name="${applicationName}" + android:allowBackup="false" android:icon="@mipmap/ic_launcher">