Skip to content

Commit

Permalink
Fix range alarm not working and date picker being stuck in past
Browse files Browse the repository at this point in the history
  • Loading branch information
AhsanSarwar45 committed Sep 9, 2024
1 parent 93190c5 commit c37c6d3
Show file tree
Hide file tree
Showing 14 changed files with 108 additions and 71 deletions.
3 changes: 1 addition & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission
android:name="android.permission.SCHEDULE_EXACT_ALARM"
android:maxSdkVersion="32" />
android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Expand Down
9 changes: 5 additions & 4 deletions lib/alarm/data/alarm_settings_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ SettingGroup alarmSettingsSchema = SettingGroup(
DateTimeSetting(
"Date Range",
(context) => AppLocalizations.of(context)!.alarmRangeSetting,
[DateTime.now(), DateTime.now().add(const Duration(days: 2))],
[],
rangeOnly: true,
enableConditions: [
ValueCondition(["Type"], (value) => value == RangeAlarmSchedule)
Expand Down Expand Up @@ -312,9 +312,10 @@ SettingGroup alarmSettingsSchema = SettingGroup(
ListSetting<AlarmTask>(
"Tasks",
(context) => AppLocalizations.of(context)!.tasksSetting,
kDebugMode
? [AlarmTask(AlarmTaskType.math), AlarmTask(AlarmTaskType.sequence)]
: [],
[],
// kDebugMode
// ? [AlarmTask(AlarmTaskType.math), AlarmTask(AlarmTaskType.sequence)]
// : [],
alarmTaskSchemasMap.keys.map((key) => AlarmTask(key)).toList(),
addCardBuilder: (item) => AlarmTaskCard(task: item, isAddCard: true),
cardBuilder: (item, [onDelete, onDuplicate]) => AlarmTaskCard(
Expand Down
66 changes: 43 additions & 23 deletions lib/alarm/logic/alarm_isolate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,31 +80,45 @@ void stopScheduledNotification(List<dynamic> message) {
void triggerAlarm(int scheduleId, Json params) async {
logger.i("Alarm triggered $scheduleId");
if (params == null) {
logger.e("Params was null when triggering alarm");
logger.e("Params was null when triggering alarm");
return;
}

Alarm? alarm = getAlarmById(scheduleId);
DateTime now = DateTime.now();


// Note: this won't effect the variable `alarm` as we have already retrieved that
await updateAlarms("triggerAlarm(): Updating all alarms on trigger");

// Ignore in the following cases:
// 1. Alarm was deleted and somehow wasn't cancelled
// 2. Alarm is disabled and somehow wasn't cancelled
// 3. Alarm is set to skip the next alarm
// 4. Alarm is set to ring in the future but somehow was triggered
// 5. Alarm is ringing 1 hour later than its time
if (alarm == null ||
alarm.isEnabled == false ||
alarm.shouldSkipNextAlarm ||
alarm.currentScheduleDateTime == null ||
now.millisecondsSinceEpoch <
alarm.currentScheduleDateTime!.millisecondsSinceEpoch ||
now.millisecondsSinceEpoch >
alarm.currentScheduleDateTime!.millisecondsSinceEpoch +
1000 * 60 * 60) {
logger.i("Skipping alarm $scheduleId");
// Skip the alarm in the following cases:
if (alarm == null) {
logger.i("Skipping alarm $scheduleId because it doesn't exist");
return;
}
if (alarm.isEnabled == false) {
logger.i("Skipping alarm $scheduleId because it is disabled");
return;
}
if (alarm.shouldSkipNextAlarm) {
logger.i(
"Skipping alarm $scheduleId because it is set to skip the next alarm");
return;
}
if (alarm.currentScheduleDateTime == null) {
logger.i(
"Skipping alarm $scheduleId because it has no scheduled date");
return;
}
if (now.millisecondsSinceEpoch <
alarm.currentScheduleDateTime!.millisecondsSinceEpoch) {
logger.i(
"Skipping alarm $scheduleId because it is set to ring in the future. Current time: $now, Scheduled time: ${alarm.currentScheduleDateTime}");
return;
}
if (now.millisecondsSinceEpoch >
alarm.currentScheduleDateTime!.millisecondsSinceEpoch + 1000 * 60 * 60) {
logger.i(
"Skipping alarm $scheduleId because it was set to ring more than an hour ago. Current time: $now, Scheduled time: ${alarm.currentScheduleDateTime}");
return;
}

Expand All @@ -116,13 +130,21 @@ void triggerAlarm(int scheduleId, Json params) async {

// Remove any existing alarm notifications
if (RingingManager.isAlarmRinging) {
await removeAlarmNotification(
ScheduledNotificationType.alarm);
await removeAlarmNotification(ScheduledNotificationType.alarm);
}

RingtonePlayer.playAlarm(alarm);
RingingManager.ringAlarm(scheduleId);


/*
Ports to set the volume of the alarm. As the RingtonePlayer only.
As the RingtonePlayer only exists in this isolate, when other isolate
(e.g the main UI isolate) want to change the alarm volumen, they have to send
message over a port.
In this case, this is used by the AlarmNotificationScreen to lower the volume
of alarm while solving tasks.
*/
ReceivePort receivePort = ReceivePort();
IsolateNameServer.removePortNameMapping(setAlarmVolumePortName);
IsolateNameServer.registerPortWithName(
Expand All @@ -134,7 +156,6 @@ void triggerAlarm(int scheduleId, Json params) async {
String timeFormatString = await loadTextFile("time_format_string");
String title = alarm.label.isEmpty ? "Alarm Ringing..." : alarm.label;


showAlarmNotification(
type: ScheduledNotificationType.alarm,
scheduleIds: [scheduleId],
Expand Down Expand Up @@ -189,8 +210,7 @@ void triggerTimer(int scheduleId, Json params) async {

// Remove any existing timer notifications
if (RingingManager.isTimerRinging) {
await removeAlarmNotification(
ScheduledNotificationType.timer);
await removeAlarmNotification(ScheduledNotificationType.timer);
}

RingtonePlayer.playTimer(timer);
Expand Down
41 changes: 19 additions & 22 deletions lib/alarm/logic/alarm_time.dart
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
import 'package:clock_app/common/types/time.dart';
import 'package:clock_app/common/utils/date_time.dart';
import 'package:clock_app/debug/logic/logger.dart';

// Calculates the DateTime when the provided `time` will next occur
DateTime getDailyAlarmDate(
DateTime getScheduleDateForTime(
Time time, {
DateTime? scheduleStartDate,
int interval = 1,
}) {
if (scheduleStartDate != null && scheduleStartDate.isAfter(DateTime.now())) {
return DateTime(scheduleStartDate.year, scheduleStartDate.month,
scheduleStartDate.day, time.hour, time.minute, time.second);
}

// logger.d('getDailyAlarmDate: $time, $scheduleStartDate, $interval');
// if (scheduleStartDate != null && scheduleStartDate.isAfter(DateTime.now())) {
// return DateTime(scheduleStartDate.year, scheduleStartDate.month,
// scheduleStartDate.day, time.hour, time.minute, time.second);
// }
//
// If a date has not been provided, assume it to be today
DateTime scheduleDate = DateTime.now();
DateTime scheduleDate = scheduleStartDate ?? DateTime.now();
DateTime alarmTime;

if (time.toHours() > scheduleDate.toHours()) {
// If the time is in the future, set the alarm for today
// if (time.toHours() > scheduleDate.toHours()) {
// // If the time is in the future, set the alarm for today
alarmTime = DateTime(scheduleDate.year, scheduleDate.month,
scheduleDate.day, time.hour, time.minute, time.second);
} else {
// If the time has already passed, set the alarm for next occurence
if (scheduleStartDate != null) {
scheduleDate = scheduleStartDate;
}

while (scheduleDate.isBefore(DateTime.now())) {
scheduleDate = scheduleDate.add(Duration(days: interval));
// } else {
while (alarmTime.isBefore(DateTime.now())) {
alarmTime = alarmTime.add(Duration(days: interval));
}

alarmTime = DateTime(scheduleDate.year, scheduleDate.month,
scheduleDate.day, time.hour, time.minute, time.second);
}
// alarmTime = DateTime(scheduleDate.year, scheduleDate.month,
// scheduleDate.day, time.hour, time.minute, time.second);
// }

return alarmTime;
}

// Calculates the DateTime when the provided `time` will next occur on the
// provided `weekday`
DateTime getWeeklyAlarmDate(Time time, int weekday) {
DateTime dateTime = getDailyAlarmDate(time);
DateTime getWeeklyScheduleDateForTIme(Time time, int weekday) {
DateTime dateTime = getScheduleDateForTime(time);
while (dateTime.weekday != weekday) {
dateTime = dateTime.add(const Duration(days: 1));
}
Expand Down
12 changes: 8 additions & 4 deletions lib/alarm/logic/schedule_alarm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ Future<void> scheduleAlarm(
bool alarmClock = true,
bool snooze = false,
}) async {
if (startDate.isBefore(DateTime.now())) {
throw Exception('Attempted to schedule alarm in the past ($startDate)');
DateTime now = DateTime.now();
if (startDate.isBefore(now)) {
throw Exception(
'Attempted to schedule alarm in the past. Schedule time: $startDate, current time: $now');
}

if (!Platform.environment.containsKey('FLUTTER_TEST')) {
Expand Down Expand Up @@ -90,7 +92,8 @@ Future<void> scheduleAlarm(
},
);

logger.i('Scheduled alarm $scheduleId for $startDate of type ${type.name}: $description');
logger.i(
'Scheduled alarm $scheduleId for $startDate of type ${type.name}: $description');
}
}

Expand Down Expand Up @@ -134,5 +137,6 @@ Future<void> scheduleSnoozeAlarm(int scheduleId, Duration delay,
await createSnoozeNotification(scheduleId, DateTime.now().add(delay));
}

logger.i('Scheduled snooze alarm $scheduleId for ${DateTime.now().add(delay)} with type ${type.name}: $description');
logger.i(
'Scheduled snooze alarm $scheduleId for ${DateTime.now().add(delay)} with type ${type.name}: $description');
}
2 changes: 2 additions & 0 deletions lib/alarm/types/ringing_manager.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

// This class is used to keep track of what alarms/timers are currently ringing
class RingingManager {
static int _ringingAlarmId = -1;
static final List<int> _ringingTimerIds = [];
Expand Down
2 changes: 1 addition & 1 deletion lib/alarm/types/schedules/daily_alarm_schedule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DailyAlarmSchedule extends AlarmSchedule {
@override
Future<void> schedule(Time time, String description,
[bool alarmClock = false]) async {
DateTime alarmDate = getDailyAlarmDate(time);
DateTime alarmDate = getScheduleDateForTime(time);
await _alarmRunner.schedule(alarmDate, description, alarmClock);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/alarm/types/schedules/once_alarm_schedule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class OnceAlarmSchedule extends AlarmSchedule {
if (currentScheduleDateTime?.isBefore(DateTime.now()) ?? false) {
_isDisabled = true;
} else {
DateTime alarmDate = getDailyAlarmDate(time);
DateTime alarmDate = getScheduleDateForTime(time);
await _alarmRunner.schedule(alarmDate, description, alarmClock);
_isDisabled = false;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/alarm/types/schedules/range_alarm_schedule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class RangeAlarmSchedule extends AlarmSchedule {
int intervalDays = interval == RangeInterval.daily ? 1 : 7;
// All the dates are not scheduled at once
// Instead we schedule the next date after the current one is finished
DateTime alarmDate = getDailyAlarmDate(time,
DateTime alarmDate = getScheduleDateForTime(time,
scheduleStartDate: startDate, interval: intervalDays);
if (alarmDate.isAfter(endDate)) {
_isFinished = true;
Expand Down
8 changes: 4 additions & 4 deletions lib/alarm/types/schedules/weekly_alarm_schedule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ class WeeklyAlarmSchedule extends AlarmSchedule {

@override
Future<void> schedule(Time time,String description, [bool alarmClock = false]) async {
for (WeekdaySchedule weekdaySchedule in _weekdaySchedules) {
weekdaySchedule.alarmRunner.cancel();
}
// for (WeekdaySchedule weekdaySchedule in _weekdaySchedules) {
// await weekdaySchedule.alarmRunner.cancel();
// }

// We schedule the next occurence for each weekday.
// Subsequent occurences will be scheduled after the first one passes.
Expand All @@ -102,7 +102,7 @@ class WeeklyAlarmSchedule extends AlarmSchedule {
}

for (WeekdaySchedule weekdaySchedule in _weekdaySchedules) {
DateTime alarmDate = getWeeklyAlarmDate(time, weekdaySchedule.weekday);
DateTime alarmDate = getWeeklyScheduleDateForTIme(time, weekdaySchedule.weekday);
await weekdaySchedule.alarmRunner.schedule(alarmDate,description, alarmClock);
}
}
Expand Down
21 changes: 13 additions & 8 deletions lib/common/widgets/fields/date_picker_bottom_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ class _DatePickerBottomSheetState extends State<DatePickerBottomSheet> {
DateTime? _rangeEndDate;
DateTime _focusedDate = DateTime.now();
late Weekday firstWeekday = appSettings
.getGroup("General")
.getGroup("Display")
.getSetting("First Day of Week")
.value;

.getGroup("General")
.getGroup("Display")
.getSetting("First Day of Week")
.value;

bool get _isSaveEnabled =>
widget.rangeOnly ? _selectedDates.length == 2 : _selectedDates.isNotEmpty;
Expand All @@ -46,8 +45,13 @@ class _DatePickerBottomSheetState extends State<DatePickerBottomSheet> {
? DateTime.now()
: widget.initialDates.first;
if (widget.rangeOnly) {
_rangeStartDate = widget.initialDates.first;
_rangeEndDate = widget.initialDates.last;
if (widget.initialDates.isEmpty) {
_rangeStartDate = DateTime.now();
_rangeEndDate = DateTime.now().add(const Duration(days: 2));
} else {
_rangeStartDate = widget.initialDates.first;
_rangeEndDate = widget.initialDates.last;
}
}
}

Expand Down Expand Up @@ -199,7 +203,8 @@ class _DatePickerBottomSheetState extends State<DatePickerBottomSheet> {
availableCalendarFormats: const {
CalendarFormat.month: 'Month',
},
startingDayOfWeek: StartingDayOfWeek.values[firstWeekday.id - 1],
startingDayOfWeek:
StartingDayOfWeek.values[firstWeekday.id - 1],
rowHeight: 48,
headerStyle: HeaderStyle(
// headerMargin: EdgeInsets.symmetric(vertical: 8.0),
Expand Down
2 changes: 1 addition & 1 deletion lib/debug/logic/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var logger = Logger(
filter: FileLogFilter(),
output: FileLoggerOutput(),
printer: PrettyPrinter(
methodCount: 5, // Number of method calls to be displayed
methodCount: 0, // Number of method calls to be displayed
errorMethodCount: 8, // Number of method calls if stacktrace is provided
lineLength: 80, // Width of the output
colors: true, // Colorful log messages
Expand Down
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.3+1"
background_fetch:
dependency: "direct main"
description:
name: background_fetch
sha256: e9f26ae54d88310b7ac2a68f2f9fcee0081a4d5f11100f233a70702021e7ac4f
url: "https://pub.dev"
source: hosted
version: "1.3.7"
boolean_selector:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies:
flutter_animate: ^4.5.0
quick_actions: ^1.0.7
file_picker: ^8.0.7
background_fetch: ^1.3.7
# animated_reorderable_list: ^1.1.1
# animated_reorderable_list:
# path: "../animated_reorderable_list"
Expand Down

0 comments on commit c37c6d3

Please sign in to comment.