From 7415efea78962e626cc3de4e4cf6afc47c3a556c Mon Sep 17 00:00:00 2001 From: mahendra-918 Date: Fri, 23 May 2025 01:46:13 +0530 Subject: [PATCH 1/2] fixed --- .../add_or_update_alarm_controller.dart | 101 +++++- .../views/add_or_update_alarm_view.dart | 14 +- .../addOrUpdateAlarm/views/qr_barcode.dart | 48 +++ .../views/scheduling_options_tile.dart | 294 ++++++++++++++++++ .../home/controllers/home_controller.dart | 6 + lib/app/utils/utils.dart | 41 ++- 6 files changed, 491 insertions(+), 13 deletions(-) create mode 100644 lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart create mode 100644 lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart diff --git a/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart b/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart index 47a081f2..ab86dab1 100644 --- a/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart +++ b/lib/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart @@ -752,6 +752,8 @@ class AddOrUpdateAlarmController extends GetxController { timeToAlarm.value = Utils.timeUntilAlarm( TimeOfDay.fromDateTime(selectedTime.value), repeatDays, + ringOn: isFutureDate.value, + alarmDate: selectedDate.value.toString(), ); repeatDays.value = alarmRecord.value.days; @@ -862,6 +864,8 @@ class AddOrUpdateAlarmController extends GetxController { timeToAlarm.value = Utils.timeUntilAlarm( TimeOfDay.fromDateTime(selectedTime.value), repeatDays, + ringOn: isFutureDate.value, + alarmDate: selectedDate.value.toString(), ); // store initial values of the variables @@ -912,7 +916,12 @@ class AddOrUpdateAlarmController extends GetxController { selectedTime.listen((time) { debugPrint('CHANGED CHANGED CHANGED CHANGED'); timeToAlarm.value = - Utils.timeUntilAlarm(TimeOfDay.fromDateTime(time), repeatDays); + Utils.timeUntilAlarm( + TimeOfDay.fromDateTime(time), + repeatDays, + ringOn: isFutureDate.value, + alarmDate: selectedDate.value.toString(), + ); _compareAndSetChange('selectedTime', time); }); @@ -1553,6 +1562,96 @@ class AddOrUpdateAlarmController extends GetxController { return int.parse(dialCodeA).compareTo(int.parse(dialCodeB)); } + + void showCustomDaysDialog(BuildContext context) { + final List daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + final List tempDays = List.from(repeatDays); + + Get.dialog( + Dialog( + backgroundColor: themeController.secondaryBackgroundColor.value, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Select Days', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: themeController.primaryTextColor.value, + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.center, + children: List.generate( + 7, + (index) => StatefulBuilder( + builder: (context, setState) { + return InkWell( + onTap: () { + setState(() { + tempDays[index] = !tempDays[index]; + }); + }, + child: CircleAvatar( + backgroundColor: tempDays[index] + ? themeController.primaryColor.value + : themeController.secondaryBackgroundColor.value, + child: Text( + daysOfWeek[index], + style: TextStyle( + color: tempDays[index] + ? Colors.white + : themeController.primaryTextColor.value, + ), + ), + ), + ); + }, + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'Cancel', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: themeController.primaryColor.value, + ), + onPressed: () { + repeatDays.value = tempDays; + daysRepeating.value = Utils.getRepeatDays(repeatDays); + Get.back(); + }, + child: const Text( + 'Save', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } } class LimitRange extends TextInputFormatter { diff --git a/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart b/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart index ea0f9682..e667a5ab 100644 --- a/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart +++ b/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart @@ -33,6 +33,10 @@ import 'package:ultimate_alarm_clock/app/utils/utils.dart'; import '../controllers/add_or_update_alarm_controller.dart'; import 'alarm_date_tile.dart'; import 'guardian_angel.dart'; +import 'scheduling_options_tile.dart'; +import 'repeat_once_tile.dart'; +import 'repeat_tile.dart'; +import 'screen_activity_tile.dart'; class AddOrUpdateAlarmView extends GetView { AddOrUpdateAlarmView({super.key}); @@ -786,15 +790,7 @@ class AddOrUpdateAlarmView extends GetView { () => controller.alarmSettingType.value == 0 ? Column( children: [ - AlarmDateTile( - controller: controller, - themeController: themeController, - ), - Divider( - color: themeController - .primaryDisabledTextColor.value, - ), - RepeatTile( + SchedulingOptionsTile( controller: controller, themeController: themeController, ), diff --git a/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart b/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart new file mode 100644 index 00000000..b83b5bee --- /dev/null +++ b/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import '../../settings/controllers/theme_controller.dart'; +import '../controllers/add_or_update_alarm_controller.dart'; + +// This is just a simple wrapper to maintain compatibility +// The actual implementation is in qr_bar_code_tile.dart +class QrBarCode extends StatelessWidget { + const QrBarCode({ + super.key, + required this.controller, + required this.themeController, + }); + + final AddOrUpdateAlarmController controller; + final ThemeController themeController; + + @override + Widget build(BuildContext context) { + // Just forward the parameters to the existing implementation + return ListTile( + title: Row( + children: [ + FittedBox( + alignment: Alignment.centerLeft, + fit: BoxFit.scaleDown, + child: Text( + 'QR/Bar Code', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + ), + ], + ), + onTap: () async { + await controller.requestQrPermission(context); + }, + trailing: Text( + controller.isQrEnabled.value ? 'Enabled' : 'Off', + style: TextStyle( + color: controller.isQrEnabled.value + ? themeController.primaryTextColor.value + : themeController.primaryDisabledTextColor.value, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart new file mode 100644 index 00000000..bb84d5e9 --- /dev/null +++ b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart @@ -0,0 +1,294 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../settings/controllers/theme_controller.dart'; +import '../controllers/add_or_update_alarm_controller.dart'; + +class SchedulingOptionsTile extends StatelessWidget { + const SchedulingOptionsTile({ + super.key, + required this.controller, + required this.themeController, + }); + + final AddOrUpdateAlarmController controller; + final ThemeController themeController; + + @override + Widget build(BuildContext context) { + return Obx(() => Column( + children: [ + Container( + decoration: BoxDecoration( + color: themeController.secondaryBackgroundColor.value, + borderRadius: BorderRadius.circular(10), + ), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + children: [ + // Tabs for scheduling options + Container( + decoration: BoxDecoration( + color: themeController.secondaryBackgroundColor.value.withOpacity(0.8), + borderRadius: BorderRadius.circular(10), + ), + margin: const EdgeInsets.all(8), + child: Row( + children: [ + // One-time tab + Expanded( + child: GestureDetector( + onTap: () { + if (!controller.isFutureDate.value) { + controller.isFutureDate.value = true; + + // Show snackbar if we're disabling repeat + if (controller.repeatDays.any((day) => day)) { + controller.repeatDays.value = List.filled(7, false); + controller.daysRepeating.value = 'Never'.tr; + + Get.snackbar( + 'Scheduling Changed', + 'Repeat days have been turned off since you enabled "Ring On"', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: themeController.secondaryBackgroundColor.value, + colorText: themeController.primaryTextColor.value, + duration: const Duration(seconds: 3), + mainButton: TextButton( + onPressed: () { + controller.isFutureDate.value = false; + controller.repeatDays.value = List.filled(7, true); + controller.daysRepeating.value = 'Everyday'.tr; + Get.back(); + }, + child: Text( + 'Undo', + style: TextStyle( + color: themeController.primaryTextColor.value, + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + } else { + // If already in one-time mode, show date picker + controller.datePicker(context); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: controller.isFutureDate.value + ? themeController.primaryColor.value + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + 'One-time', + style: TextStyle( + color: controller.isFutureDate.value + ? Colors.white + : themeController.primaryTextColor.value, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + // Recurring tab + Expanded( + child: GestureDetector( + onTap: () { + if (controller.isFutureDate.value) { + controller.isFutureDate.value = false; + + // Show dialog to select repeat days + Get.bottomSheet( + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: themeController.secondaryBackgroundColor.value, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Select Repeat Days', + style: TextStyle( + color: themeController.primaryTextColor.value, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + ListTile( + title: Text( + 'Everyday', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + onTap: () { + controller.repeatDays.value = List.filled(7, true); + controller.daysRepeating.value = 'Everyday'.tr; + Get.back(); + }, + ), + ListTile( + title: Text( + 'Weekdays', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + onTap: () { + controller.repeatDays.value = [true, true, true, true, true, false, false]; + controller.daysRepeating.value = 'Weekdays'.tr; + Get.back(); + }, + ), + ListTile( + title: Text( + 'Weekends', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + onTap: () { + controller.repeatDays.value = [false, false, false, false, false, true, true]; + controller.daysRepeating.value = 'Weekends'.tr; + Get.back(); + }, + ), + ListTile( + title: Text( + 'Custom', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + onTap: () { + Get.back(); + controller.showCustomDaysDialog(context); + }, + ), + ], + ), + ), + ); + } else { + // If already in recurring mode, show custom days dialog + controller.showCustomDaysDialog(context); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: !controller.isFutureDate.value + ? themeController.primaryColor.value + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + 'Recurring', + style: TextStyle( + color: !controller.isFutureDate.value + ? Colors.white + : themeController.primaryTextColor.value, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ], + ), + ), + // Content based on selected tab + Container( + padding: const EdgeInsets.all(16), + child: controller.isFutureDate.value + ? _buildOneTimeContent() + : _buildRecurringContent(), + ), + ], + ), + ), + ], + )); + } + + Widget _buildOneTimeContent() { + return InkWell( + onTap: () => controller.datePicker(Get.context!), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Ring On Date', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + Row( + children: [ + Text( + controller.selectedDate.value.toString().substring(0, 10), + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + const SizedBox(width: 8), + Icon( + Icons.calendar_today, + color: themeController.primaryColor.value, + size: 20, + ), + ], + ), + ], + ), + ); + } + + Widget _buildRecurringContent() { + return InkWell( + onTap: () => controller.showCustomDaysDialog(Get.context!), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Repeat', + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + Row( + children: [ + Text( + controller.daysRepeating.value, + style: TextStyle( + color: themeController.primaryTextColor.value, + ), + ), + const SizedBox(width: 8), + Icon( + Icons.chevron_right, + color: themeController.primaryColor.value, + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 449ca94f..8a809d56 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -327,6 +327,8 @@ class HomeController extends GetxController { String timeToAlarm = Utils.timeUntilAlarm( Utils.stringToTimeOfDay(latestAlarm.alarmTime), latestAlarm.days, + ringOn: latestAlarm.ringOn, + alarmDate: latestAlarm.alarmDate, ); alarmTime.value = 'Rings in $timeToAlarm'; // This function is necessary when alarms are deleted/enabled @@ -362,6 +364,8 @@ class HomeController extends GetxController { timeToAlarm = Utils.timeUntilAlarm( Utils.stringToTimeOfDay(latestAlarm.alarmTime), latestAlarm.days, + ringOn: latestAlarm.ringOn, + alarmDate: latestAlarm.alarmDate, ); alarmTime.value = 'Rings in $timeToAlarm'; @@ -377,6 +381,8 @@ class HomeController extends GetxController { timeToAlarm = Utils.timeUntilAlarm( Utils.stringToTimeOfDay(latestAlarm.alarmTime), latestAlarm.days, + ringOn: latestAlarm.ringOn, + alarmDate: latestAlarm.alarmDate, ); alarmTime.value = 'Rings in $timeToAlarm'; }); diff --git a/lib/app/utils/utils.dart b/lib/app/utils/utils.dart index 12fd6525..da5a4b06 100644 --- a/lib/app/utils/utils.dart +++ b/lib/app/utils/utils.dart @@ -271,7 +271,7 @@ class Utils { return deg * (pi / 180); } - static String timeUntilAlarm(TimeOfDay alarmTime, List days) { + static String timeUntilAlarm(TimeOfDay alarmTime, List days, {bool ringOn = false, String? alarmDate}) { final now = DateTime.now(); final todayAlarm = DateTime( now.year, @@ -283,8 +283,43 @@ class Utils { Duration duration; + // Handle future date alarms with ringOn flag + if (ringOn && alarmDate != null) { + try { + final DateTime targetDate = DateTime.parse(alarmDate.trim()); + final futureAlarm = DateTime( + targetDate.year, + targetDate.month, + targetDate.day, + alarmTime.hour, + alarmTime.minute, + ); + + if (now.isBefore(futureAlarm)) { + duration = futureAlarm.difference(now); + } else { + // If the alarm date is in the past, treat it as a regular one-time alarm + if (now.isBefore(todayAlarm)) { + duration = todayAlarm.difference(now); + } else { + // Schedule the alarm for the next day + final nextAlarm = todayAlarm.add(const Duration(days: 1)); + duration = nextAlarm.difference(now); + } + } + } catch (e) { + // If there's an error parsing the date, fall back to regular behavior + if (now.isBefore(todayAlarm)) { + duration = todayAlarm.difference(now); + } else { + // Schedule the alarm for the next day + final nextAlarm = todayAlarm.add(const Duration(days: 1)); + duration = nextAlarm.difference(now); + } + } + } // Check if the alarm is a one-time alarm - if (days.every((day) => !day)) { + else if (days.every((day) => !day)) { if (now.isBefore(todayAlarm)) { duration = todayAlarm.difference(now); } else { @@ -336,7 +371,7 @@ class Utils { ? '$hours hour $minutes minute' : '$hours hour $minutes minutes'; } else { - return '$hours hour $minutes minutes'; + return '$hours hours $minutes minutes'; } } else if (duration.inDays == 1) { return '1 day'; From ee4aae882dde24400ecc68dcf2cc30ca9805e6d1 Mon Sep 17 00:00:00 2001 From: mahendra-918 Date: Fri, 23 May 2025 03:04:19 +0530 Subject: [PATCH 2/2] added UI --- .../addOrUpdateAlarm/views/qr_barcode.dart | 48 ------------------- .../views/scheduling_options_tile.dart | 15 +++--- lib/app/utils/utils.dart | 10 ++-- 3 files changed, 12 insertions(+), 61 deletions(-) delete mode 100644 lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart diff --git a/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart b/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart deleted file mode 100644 index b83b5bee..00000000 --- a/lib/app/modules/addOrUpdateAlarm/views/qr_barcode.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../settings/controllers/theme_controller.dart'; -import '../controllers/add_or_update_alarm_controller.dart'; - -// This is just a simple wrapper to maintain compatibility -// The actual implementation is in qr_bar_code_tile.dart -class QrBarCode extends StatelessWidget { - const QrBarCode({ - super.key, - required this.controller, - required this.themeController, - }); - - final AddOrUpdateAlarmController controller; - final ThemeController themeController; - - @override - Widget build(BuildContext context) { - // Just forward the parameters to the existing implementation - return ListTile( - title: Row( - children: [ - FittedBox( - alignment: Alignment.centerLeft, - fit: BoxFit.scaleDown, - child: Text( - 'QR/Bar Code', - style: TextStyle( - color: themeController.primaryTextColor.value, - ), - ), - ), - ], - ), - onTap: () async { - await controller.requestQrPermission(context); - }, - trailing: Text( - controller.isQrEnabled.value ? 'Enabled' : 'Off', - style: TextStyle( - color: controller.isQrEnabled.value - ? themeController.primaryTextColor.value - : themeController.primaryDisabledTextColor.value, - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart index bb84d5e9..f126f512 100644 --- a/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart +++ b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart @@ -26,7 +26,7 @@ class SchedulingOptionsTile extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( children: [ - // Tabs for scheduling options + Container( decoration: BoxDecoration( color: themeController.secondaryBackgroundColor.value.withOpacity(0.8), @@ -35,14 +35,13 @@ class SchedulingOptionsTile extends StatelessWidget { margin: const EdgeInsets.all(8), child: Row( children: [ - // One-time tab + Expanded( child: GestureDetector( onTap: () { if (!controller.isFutureDate.value) { controller.isFutureDate.value = true; - // Show snackbar if we're disabling repeat if (controller.repeatDays.any((day) => day)) { controller.repeatDays.value = List.filled(7, false); controller.daysRepeating.value = 'Never'.tr; @@ -72,7 +71,7 @@ class SchedulingOptionsTile extends StatelessWidget { ); } } else { - // If already in one-time mode, show date picker + controller.datePicker(context); } }, @@ -98,14 +97,14 @@ class SchedulingOptionsTile extends StatelessWidget { ), ), ), - // Recurring tab + Expanded( child: GestureDetector( onTap: () { if (controller.isFutureDate.value) { controller.isFutureDate.value = false; - // Show dialog to select repeat days + Get.bottomSheet( Container( padding: const EdgeInsets.all(16), @@ -184,7 +183,7 @@ class SchedulingOptionsTile extends StatelessWidget { ), ); } else { - // If already in recurring mode, show custom days dialog + controller.showCustomDaysDialog(context); } }, @@ -213,7 +212,7 @@ class SchedulingOptionsTile extends StatelessWidget { ], ), ), - // Content based on selected tab + Container( padding: const EdgeInsets.all(16), child: controller.isFutureDate.value diff --git a/lib/app/utils/utils.dart b/lib/app/utils/utils.dart index da5a4b06..b7b12bdc 100644 --- a/lib/app/utils/utils.dart +++ b/lib/app/utils/utils.dart @@ -283,7 +283,7 @@ class Utils { Duration duration; - // Handle future date alarms with ringOn flag + if (ringOn && alarmDate != null) { try { final DateTime targetDate = DateTime.parse(alarmDate.trim()); @@ -298,21 +298,21 @@ class Utils { if (now.isBefore(futureAlarm)) { duration = futureAlarm.difference(now); } else { - // If the alarm date is in the past, treat it as a regular one-time alarm + if (now.isBefore(todayAlarm)) { duration = todayAlarm.difference(now); } else { - // Schedule the alarm for the next day + final nextAlarm = todayAlarm.add(const Duration(days: 1)); duration = nextAlarm.difference(now); } } } catch (e) { - // If there's an error parsing the date, fall back to regular behavior + if (now.isBefore(todayAlarm)) { duration = todayAlarm.difference(now); } else { - // Schedule the alarm for the next day + final nextAlarm = todayAlarm.add(const Duration(days: 1)); duration = nextAlarm.difference(now); }