[READ-ONLY] Mirror of https://github.com/probablykasper/notifier. Android app for scheduling notifications
android app flutter notifications
0

Configure Feed

Select the types of activity you want to include in your feed.

Fix scheduling

Kasper (Jun 2, 2022, 8:22 AM +0200) a0576f92 762b4188

+122 -40
+1 -1
lib/edit_dialog.dart
··· 211 211 child: Row( 212 212 children: <Widget>[ 213 213 //* REPEAT 214 - DropdownButton<Repeat>( 214 + DropdownButton<String>( 215 215 onChanged: (newValue) { 216 216 if (newValue != null) { 217 217 setState(() {
+25 -3
lib/list_page.dart
··· 30 30 Widget; 31 31 import 'package:get/state_manager.dart'; 32 32 import 'package:notifier/edit_dialog.dart'; 33 + import 'package:notifier/main.dart'; 33 34 import 'package:notifier/scheduler.dart'; 34 35 import 'notification_item.dart' show NotificationItem; 35 36 import 'theme.dart' show CustomTheme, toggleDarkMode; 36 37 37 38 class Controller extends GetxController { 38 39 var items = <NotificationItem>[].obs; 40 + 41 + load() async { 42 + var prefs = await prefsFuture; 43 + var jsonItems = prefs.getStringList("notificationItems"); 44 + if (jsonItems == null) { 45 + return; 46 + } 47 + items.value = jsonItems.map((jsonItem) { 48 + return NotificationItem.fromJson(jsonItem); 49 + }).toList(); 50 + } 51 + 52 + saveAndSchedule() async { 53 + var prefs = await prefsFuture; 54 + var jsonItems = items.map((item) => item.toJson()).toList(); 55 + prefs.setStringList("notificationItems", jsonItems); 56 + scheduleNotifications(); 57 + } 39 58 } 40 59 41 60 class ListPage extends StatelessWidget { ··· 43 62 Widget build(context) { 44 63 // Instantiate your class using Get.put() to make it available for all "child" routes there. 45 64 final Controller c = Get.put(Controller()); 65 + c.load(); 46 66 47 67 return Scaffold( 48 68 floatingActionButton: FloatingActionButton( ··· 53 73 editMode: false, 54 74 onSave: (item) { 55 75 c.items.add(item); 56 - scheduleNotifications(); 76 + c.saveAndSchedule(); 57 77 }, 58 78 )); 59 79 }), ··· 109 129 // '[notifier] Running setNotifications() before opening edit dialog'); 110 130 // await listModel.setNotifications(appIsOpen: true); 111 131 // } 132 + var item = NotificationItem.fromJson(c.items[index].toJson()); 133 + item.lastScheduledDate = item.getNextFutureDate(); 112 134 Get.dialog(EditDialog( 113 - item: NotificationItem.fromJson(c.items[index].toJson()), 135 + item: item, 114 136 editMode: true, 115 137 onSave: (item) { 116 138 c.items[index] = item; 117 - scheduleNotifications(); 139 + c.saveAndSchedule(); 118 140 }, 119 141 )); 120 142 },
+92 -32
lib/notification_item.dart
··· 1 1 import 'dart:convert' show json; 2 2 import 'package:awesome_notifications/awesome_notifications.dart' 3 - show AwesomeNotifications, NotificationContent, NotificationCalendar; 3 + show 4 + AwesomeNotifications, 5 + NotificationCalendar, 6 + NotificationContent, 7 + NotificationSchedule; 4 8 5 - enum Repeat { 6 - never, 7 - daily, 8 - weekly, 9 - monthly, 10 - yearly, 9 + abstract class Repeat { 10 + static const never = "never"; 11 + static const daily = "daily"; 12 + static const weekly = "weekly"; 13 + static const monthly = "monthly"; 14 + static const yearly = "yearly"; 11 15 } 12 16 13 17 class NotificationItem { 14 18 String title; 15 19 String description; 16 20 bool disabled; 17 - Repeat repeat; 21 + String repeat; 18 22 DateTime originalDate; 19 23 DateTime? lastScheduledDate; 20 24 ··· 42 46 ); 43 47 } 44 48 45 - bool timeHasPassed() => 46 - originalDate.millisecondsSinceEpoch < 47 - DateTime.now().millisecondsSinceEpoch; 48 - 49 49 DateTime getLatestDate() { 50 50 return lastScheduledDate ?? originalDate; 51 + } 52 + 53 + bool timeHasPassed() { 54 + return getLatestDate().millisecondsSinceEpoch < 55 + DateTime.now().millisecondsSinceEpoch; 51 56 } 52 57 53 58 factory NotificationItem.fromJson(String str) { ··· 58 63 disabled: map['disabled'], 59 64 repeat: map['repeat'], 60 65 originalDate: DateTime.fromMillisecondsSinceEpoch(map['originalDate']), 61 - lastScheduledDate: DateTime.fromMillisecondsSinceEpoch(map['lastDate']), 66 + lastScheduledDate: map['lastDate'] == null 67 + ? null 68 + : DateTime.fromMillisecondsSinceEpoch(map['lastDate']), 62 69 ); 63 70 } 64 71 ··· 74 81 return json.encode(map); 75 82 } 76 83 77 - DateTime? getNextDate() { 78 - if (lastScheduledDate == null) { 84 + // Get the next date from the last scheduled date 85 + DateTime? _getDirectlyNextDate(DateTime? fromDate) { 86 + if (fromDate == null) { 79 87 return originalDate; 88 + } else if (fromDate.isAfter(DateTime.now())) { 89 + return fromDate; 80 90 } else if (repeat == Repeat.never) { 81 91 return null; 82 92 } else if (repeat == Repeat.daily) { 83 93 return DateTime( 84 - lastScheduledDate!.year, 85 - lastScheduledDate!.month, 86 - lastScheduledDate!.day + 1, 94 + fromDate.year, 95 + fromDate.month, 96 + fromDate.day + 1, 87 97 originalDate.hour, 88 98 originalDate.minute, 89 99 originalDate.second, 90 100 originalDate.millisecond); 91 101 } else if (repeat == Repeat.weekly) { 92 102 return DateTime( 93 - lastScheduledDate!.year, 94 - lastScheduledDate!.month, 95 - lastScheduledDate!.day + 7, 103 + fromDate.year, 104 + fromDate.month, 105 + fromDate.day + 7, 96 106 originalDate.hour, 97 107 originalDate.minute, 98 108 originalDate.second, 99 109 originalDate.millisecond); 100 110 } else if (repeat == Repeat.monthly) { 101 111 return DateTime( 102 - lastScheduledDate!.year, 103 - lastScheduledDate!.month + 1, 112 + fromDate.year, 113 + fromDate.month + 1, 104 114 originalDate.day, 105 115 originalDate.hour, 106 116 originalDate.minute, ··· 108 118 originalDate.millisecond); 109 119 } else if (repeat == Repeat.yearly) { 110 120 return DateTime( 111 - lastScheduledDate!.year, 121 + fromDate.year, 112 122 originalDate.month, 113 123 originalDate.day, 114 124 originalDate.hour, ··· 121 131 } 122 132 } 123 133 134 + /// Get the next date for scheduling a notification. 135 + /// Returns null for already scheduled non-repeating dates. 136 + DateTime? getNextNotificationDate() { 137 + var directlyNextDate = _getDirectlyNextDate(lastScheduledDate); 138 + if (directlyNextDate == null) { 139 + return null; 140 + } 141 + // If the next date is far in the past, skip forward to the most recent date, 142 + // but not into the future because then we could be 143 + var now = DateTime.now(); 144 + var nextDate = directlyNextDate; 145 + print("getNextNotificationDate"); 146 + while (nextDate.isBefore(now)) { 147 + var nextNextDate = _getDirectlyNextDate(nextDate); 148 + if (nextNextDate != null && nextNextDate.isBefore(now)) { 149 + nextDate = nextNextDate; 150 + } else { 151 + break; 152 + } 153 + } 154 + print("-getNextNotificationDate"); 155 + return nextDate; 156 + } 157 + 158 + /// Get the next date for scheduling a notification, but only in the future. 159 + /// Returns null for already scheduled non-repeating dates. 160 + DateTime? getNextFutureDate() { 161 + var directlyNextDate = _getDirectlyNextDate(lastScheduledDate); 162 + if (directlyNextDate == null) { 163 + return null; 164 + } 165 + // If the next date is far in the past, skip forward to the most recent date, 166 + // but not into the future because then we could be 167 + var now = DateTime.now(); 168 + var nextDate = directlyNextDate; 169 + while (nextDate.isBefore(now)) { 170 + var nextNextDate = _getDirectlyNextDate(nextDate); 171 + if (nextNextDate != null) { 172 + nextDate = nextNextDate; 173 + } else { 174 + break; 175 + } 176 + } 177 + return nextDate; 178 + } 179 + 124 180 scheduleAt(int id, DateTime date) { 125 - AwesomeNotifications().createNotification( 126 - content: NotificationContent( 127 - id: id, 128 - channelKey: 'scheduled_notifications', 129 - title: title, 130 - body: description, 131 - ), 132 - schedule: NotificationCalendar( 181 + NotificationSchedule? schedule = null; 182 + if (date.isAfter(DateTime.now())) { 183 + schedule = NotificationCalendar( 133 184 year: date.year, 134 185 month: date.month, 135 186 day: date.day, ··· 138 189 second: date.second, 139 190 preciseAlarm: true, 140 191 allowWhileIdle: true, 192 + ); 193 + } 194 + AwesomeNotifications().createNotification( 195 + content: NotificationContent( 196 + id: id, 197 + channelKey: 'scheduled_notifications', 198 + title: title, 199 + body: description, 141 200 ), 201 + schedule: schedule, 142 202 ); 143 203 } 144 204 }
+4 -4
lib/scheduler.dart
··· 1 - import 'dart:isolate' show Isolate; 2 1 import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart' 3 2 show AndroidAlarmManager; 4 3 import 'package:awesome_notifications/awesome_notifications.dart' ··· 19 18 void handler() async { 20 19 var prefs = await SharedPreferences.getInstance(); 21 20 final DateTime now = DateTime.now(); 22 - final int isolateId = Isolate.current.hashCode; 23 - print("[$now] Hello, world! isolate=$isolateId"); 21 + print("[$now] Notification schedule handler"); 24 22 await AwesomeNotifications().cancelAll(); 25 23 26 24 var jsonItems = prefs.getStringList("notificationItems"); ··· 30 28 var items = jsonItems.map((jsonItem) { 31 29 return NotificationItem.fromJson(jsonItem); 32 30 }).toList(); 31 + print("ITEMS $jsonItems"); 33 32 for (var i = 0; i < items.length; i++) { 34 - var nextDate = items[i].getNextDate(); 33 + var nextDate = items[i].getNextNotificationDate(); 35 34 if (nextDate != null) { 35 + print("scheduleAt $nextDate ${items[i].repeat} \"${items[i].title}\""); 36 36 items[i].scheduleAt(i, nextDate); 37 37 items[i].lastScheduledDate = nextDate; 38 38 }