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/scheduling_options_tile.dart b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart new file mode 100644 index 00000000..f126f512 --- /dev/null +++ b/lib/app/modules/addOrUpdateAlarm/views/scheduling_options_tile.dart @@ -0,0 +1,293 @@ +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: [ + + Container( + decoration: BoxDecoration( + color: themeController.secondaryBackgroundColor.value.withOpacity(0.8), + borderRadius: BorderRadius.circular(10), + ), + margin: const EdgeInsets.all(8), + child: Row( + children: [ + + Expanded( + child: GestureDetector( + onTap: () { + if (!controller.isFutureDate.value) { + controller.isFutureDate.value = true; + + 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 { + + 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, + ), + ), + ), + ), + ), + ), + + Expanded( + child: GestureDetector( + onTap: () { + if (controller.isFutureDate.value) { + controller.isFutureDate.value = false; + + + 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 { + + 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, + ), + ), + ), + ), + ), + ), + ], + ), + ), + + 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..b7b12bdc 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; + + 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 (now.isBefore(todayAlarm)) { + duration = todayAlarm.difference(now); + } else { + + final nextAlarm = todayAlarm.add(const Duration(days: 1)); + duration = nextAlarm.difference(now); + } + } + } catch (e) { + + if (now.isBefore(todayAlarm)) { + duration = todayAlarm.difference(now); + } else { + + 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';