4141
4242public final class DueReminderScheduler {
4343
44+ private static final int SNOOZE_MINUTES = 5 ;
4445 static final String CHANNEL_ID = "due_reminders" ;
4546 private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter .ofLocalizedDateTime (FormatStyle .MEDIUM );
4647
@@ -84,27 +85,7 @@ public static void scheduleCard(@NonNull Context context, @Nullable Card card) {
8485 return ;
8586 }
8687
87- final var alarmManager = (AlarmManager ) context .getSystemService (Context .ALARM_SERVICE );
88- if (alarmManager == null ) {
89- DeckLog .warn ("Could not schedule due reminder because AlarmManager is unavailable." );
90- return ;
91- }
92-
93- final long dueAtMillis = card .getDueDate ().toEpochMilli ();
94- final PendingIntent pendingIntent = createReminderPendingIntent (context , card .getLocalId ());
95-
96- if (canScheduleExactAlarms (alarmManager )) {
97- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
98- alarmManager .setExactAndAllowWhileIdle (AlarmManager .RTC_WAKEUP , dueAtMillis , pendingIntent );
99- } else {
100- alarmManager .setExact (AlarmManager .RTC_WAKEUP , dueAtMillis , pendingIntent );
101- }
102- } else if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
103- alarmManager .setAndAllowWhileIdle (AlarmManager .RTC_WAKEUP , dueAtMillis , pendingIntent );
104- } else {
105- alarmManager .set (AlarmManager .RTC_WAKEUP , dueAtMillis , pendingIntent );
106- }
107-
88+ scheduleAt (context , card .getLocalId (), card .getDueDate ().toEpochMilli ());
10889 DeckLog .info ("Scheduled due reminder for card" , card .getLocalId (), "at" , card .getDueDate ());
10990 }
11091
@@ -144,6 +125,7 @@ public static void showReminder(@NonNull Context context, long cardLocalId) {
144125 final PendingIntent openCardPendingIntent = createOpenCardPendingIntent (context , account , board , cardLocalId );
145126 final PendingIntent fullScreenPendingIntent = createFullScreenPendingIntent (context , cardLocalId );
146127 final PendingIntent completePendingIntent = createCompletePendingIntent (context , cardLocalId );
128+ final PendingIntent snoozePendingIntent = createSnoozePendingIntent (context , cardLocalId );
147129
148130 final String title = context .getString (R .string .local_due_reminder_notification_title , safeTitle (fullCard .getCard ()));
149131 final String dueAt = fullCard .getCard ().getDueDate ()
@@ -162,6 +144,7 @@ public static void showReminder(@NonNull Context context, long cardLocalId) {
162144 .setContentIntent (openCardPendingIntent )
163145 .setFullScreenIntent (fullScreenPendingIntent , true )
164146 .addAction (R .drawable .ic_outline_check_circle_24 , context .getString (R .string .local_due_reminder_mark_complete ), completePendingIntent )
147+ .addAction (R .drawable .ic_time_24 , context .getString (R .string .local_due_reminder_snooze ), snoozePendingIntent )
165148 .setAutoCancel (true )
166149 .build ();
167150
@@ -174,6 +157,24 @@ public static void showReminder(@NonNull Context context, long cardLocalId) {
174157 }
175158 }
176159
160+ public static void snooze (@ NonNull Context context , long cardLocalId ) {
161+ if (!isEnabled (context )) {
162+ return ;
163+ }
164+
165+ final var dataBaseAdapter = new DataBaseAdapter (context .getApplicationContext ());
166+ final FullCard fullCard = dataBaseAdapter .getFullCardByLocalIdDirectly (cardLocalId );
167+ if (fullCard == null || !shouldNotify (fullCard .getCard ())) {
168+ DeckLog .info ("Skipping snooze for stale card" , cardLocalId );
169+ return ;
170+ }
171+
172+ final long snoozedUntilMillis = Instant .now ().plusSeconds (SNOOZE_MINUTES * 60L ).toEpochMilli ();
173+ scheduleAt (context , cardLocalId , snoozedUntilMillis );
174+ NotificationManagerCompat .from (context ).cancel (notificationId (cardLocalId ));
175+ DeckLog .info ("Snoozed due reminder for card" , cardLocalId , "until" , Instant .ofEpochMilli (snoozedUntilMillis ));
176+ }
177+
177178 public static void markComplete (@ NonNull Context context , long cardLocalId ) {
178179 final var dataBaseAdapter = new DataBaseAdapter (context .getApplicationContext ());
179180 if (dataBaseAdapter .markCardDoneDirectly (cardLocalId , Instant .now ())) {
@@ -200,6 +201,28 @@ private static boolean canScheduleExactAlarms(@NonNull AlarmManager alarmManager
200201 return Build .VERSION .SDK_INT < Build .VERSION_CODES .S || alarmManager .canScheduleExactAlarms ();
201202 }
202203
204+ private static void scheduleAt (@ NonNull Context context , long cardLocalId , long triggerAtMillis ) {
205+ final var alarmManager = (AlarmManager ) context .getSystemService (Context .ALARM_SERVICE );
206+ if (alarmManager == null ) {
207+ DeckLog .warn ("Could not schedule due reminder because AlarmManager is unavailable." );
208+ return ;
209+ }
210+
211+ final PendingIntent pendingIntent = createReminderPendingIntent (context , cardLocalId );
212+
213+ if (canScheduleExactAlarms (alarmManager )) {
214+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
215+ alarmManager .setExactAndAllowWhileIdle (AlarmManager .RTC_WAKEUP , triggerAtMillis , pendingIntent );
216+ } else {
217+ alarmManager .setExact (AlarmManager .RTC_WAKEUP , triggerAtMillis , pendingIntent );
218+ }
219+ } else if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
220+ alarmManager .setAndAllowWhileIdle (AlarmManager .RTC_WAKEUP , triggerAtMillis , pendingIntent );
221+ } else {
222+ alarmManager .set (AlarmManager .RTC_WAKEUP , triggerAtMillis , pendingIntent );
223+ }
224+ }
225+
203226 private static boolean shouldSchedule (@ NonNull Card card ) {
204227 return shouldNotify (card ) && card .getDueDate ().isAfter (Instant .now ());
205228 }
@@ -249,6 +272,15 @@ private static PendingIntent createCompletePendingIntent(@NonNull Context contex
249272 );
250273 }
251274
275+ private static PendingIntent createSnoozePendingIntent (@ NonNull Context context , long cardLocalId ) {
276+ return PendingIntent .getBroadcast (
277+ context ,
278+ notificationId (cardLocalId ) + 300_000 ,
279+ DueReminderReceiver .createIntent (context , DueReminderReceiver .ACTION_SNOOZE , cardLocalId ),
280+ FLAG_IMMUTABLE | FLAG_CANCEL_CURRENT
281+ );
282+ }
283+
252284 private static PendingIntent createFullScreenPendingIntent (@ NonNull Context context , long cardLocalId ) {
253285 return PendingIntent .getActivity (
254286 context ,
0 commit comments