@@ -21,6 +21,7 @@ import android.content.SharedPreferences
2121import androidx.annotation.VisibleForTesting
2222import androidx.core.content.edit
2323import com.ichi2.anki.AnkiDroidApp
24+ import com.ichi2.anki.CollectionManager.withCol
2425import com.ichi2.anki.common.utils.android.isRobolectric
2526import com.ichi2.anki.libanki.DeckId
2627import kotlinx.serialization.InternalSerializationApi
@@ -229,11 +230,19 @@ object ReviewRemindersDatabase {
229230 }
230231
231232 /* *
232- * Get the [ReviewReminder]s for a specific deck.
233+ * Get the [ReviewReminder]s for a specific deck. Deletes the review reminders for this deck if the deck does not exist.
233234 * @throws SerializationException If the reminders map has not been stored in SharedPreferences as a valid JSON string.
234235 * @throws IllegalArgumentException If the decoded reminders map is not a HashMap<[ReviewReminderId], [ReviewReminder]>.
235236 */
236- fun getRemindersForDeck (did : DeckId ): HashMap <ReviewReminderId , ReviewReminder > = getRemindersForKey(DECK_SPECIFIC_KEY + did)
237+ suspend fun getRemindersForDeck (did : DeckId ): HashMap <ReviewReminderId , ReviewReminder > {
238+ val doesDeckExist = withCol { decks.have(did) }
239+ return if (doesDeckExist) {
240+ getRemindersForKey(DECK_SPECIFIC_KEY + did)
241+ } else {
242+ deleteAllRemindersForDeck(did)
243+ hashMapOf()
244+ }
245+ }
237246
238247 /* *
239248 * Get the app-wide [ReviewReminder]s.
@@ -244,15 +253,43 @@ object ReviewRemindersDatabase {
244253
245254 /* *
246255 * Get all [ReviewReminder]s that are associated with a specific deck, all in a single flattened map.
256+ * For each deck, deletes the deck's review reminders if the deck does not exist.
247257 * @throws SerializationException If the reminders maps have not been stored in SharedPreferences as valid JSON strings.
248258 * @throws IllegalArgumentException If the decoded reminders maps are not instances of HashMap<[ReviewReminderId], [ReviewReminder]>.
249259 */
250- fun getAllDeckSpecificReminders (): HashMap <ReviewReminderId , ReviewReminder > =
251- remindersSharedPrefs
252- .all
253- .filterKeys { it.startsWith(DECK_SPECIFIC_KEY ) }
254- .flatMap { (key, value) -> decodeJson(value.toString(), deckKeyForMigrationPurposes = key).entries }
255- .associateTo(hashMapOf()) { it.toPair() }
260+ suspend fun getAllDeckSpecificReminders (): HashMap <ReviewReminderId , ReviewReminder > {
261+ // Get all deck-specific reminders
262+ val deckSpecificRemindersMap =
263+ remindersSharedPrefs
264+ .all
265+ .filterKeys { it.startsWith(DECK_SPECIFIC_KEY ) }
266+ .toMutableMap()
267+ // Delete deck-specific reminders for decks that do not exist
268+ // Opens a SharedPreferences transaction and the collection only once
269+ remindersSharedPrefs.edit {
270+ withCol {
271+ deckSpecificRemindersMap.entries.removeIf { (key, _) ->
272+ val did = key.removePrefix(DECK_SPECIFIC_KEY ).toLong()
273+ val doesDeckExist = decks.have(did)
274+ if (doesDeckExist) {
275+ false // Keep this group of review reminders
276+ } else {
277+ Timber .d(" Deleting review reminders for deck $did " )
278+ remove(key) // Remove from SharedPreferences
279+ true // Remove from deckSpecificRemindersMap
280+ }
281+ }
282+ }
283+ }
284+ // Decode the remaining deck-specific reminders and return
285+ return deckSpecificRemindersMap
286+ .flatMap { (key, value) ->
287+ decodeJson(
288+ value.toString(),
289+ deckKeyForMigrationPurposes = key,
290+ ).entries
291+ }.associateTo(hashMapOf()) { it.toPair() }
292+ }
256293
257294 /* *
258295 * Edit the [ReviewReminder]s for a specific key.
@@ -273,17 +310,24 @@ object ReviewRemindersDatabase {
273310 }
274311
275312 /* *
276- * Edit the [ReviewReminder]s for a specific deck.
313+ * Edit the [ReviewReminder]s for a specific deck. Deletes the review reminders for this deck if the deck does not exist.
277314 * This assumes the resulting map contains only reminders of scope [ReviewReminderScope.DeckSpecific].
278315 * @param did
279316 * @param reminderEditor A lambda that takes the current map and returns the updated map.
280317 * @throws SerializationException If the current reminders map has not been stored in SharedPreferences as a valid JSON string.
281318 * @throws IllegalArgumentException If the decoded current reminders map is not a HashMap<[ReviewReminderId], [ReviewReminder]>.
282319 */
283- fun editRemindersForDeck (
320+ suspend fun editRemindersForDeck (
284321 did : DeckId ,
285322 reminderEditor : (HashMap <ReviewReminderId , ReviewReminder >) -> Map <ReviewReminderId , ReviewReminder >,
286- ) = editRemindersForKey(DECK_SPECIFIC_KEY + did, reminderEditor)
323+ ) {
324+ val doesDeckExist = withCol { decks.have(did) }
325+ if (doesDeckExist) {
326+ editRemindersForKey(DECK_SPECIFIC_KEY + did, reminderEditor)
327+ } else {
328+ deleteAllRemindersForDeck(did)
329+ }
330+ }
287331
288332 /* *
289333 * Edit the app-wide [ReviewReminder]s.
@@ -294,4 +338,21 @@ object ReviewRemindersDatabase {
294338 */
295339 fun editAllAppWideReminders (reminderEditor : (HashMap <ReviewReminderId , ReviewReminder >) -> Map <ReviewReminderId , ReviewReminder >) =
296340 editRemindersForKey(APP_WIDE_KEY , reminderEditor)
341+
342+ /* *
343+ * Delete all [ReviewReminder]s for a specific deck.
344+ * Fully removes the stored JSON string representing the stored review reminders from SharedPreferences.
345+ * Does nothing if no review reminders for this deck have been stored.
346+ *
347+ * Public so that if a notification is being fired for a deck that has been deleted, the notification can be
348+ * cancelled and the review reminders deleted. In general, deleting review reminders when a deck has been deleted
349+ * is handled lazily: i.e., we do not immediately delete reminders for a deck when it is deleted but rather
350+ * wait until the reminders are requested for display or for notification to check if a deletion should be performed.
351+ */
352+ fun deleteAllRemindersForDeck (did : DeckId ) {
353+ Timber .d(" Deleting review reminders for deck $did " )
354+ remindersSharedPrefs.edit {
355+ remove(DECK_SPECIFIC_KEY + did)
356+ }
357+ }
297358}
0 commit comments