Skip to content

Commit a6475b8

Browse files
committed
feat(reminders): integrate AddEditReminderDialog into ScheduleReminders
GSoC 2025: Review Reminders - Adds a fragment result listener to ScheduleReminders to detect when an AddEditReminderDialog has completed, and if so, edits the database and UI accordingly. - Filled out the `addReminder` and `editReminder` methods to show the AddEditReminderDialog.
1 parent 6a9e714 commit a6475b8

File tree

1 file changed

+155
-2
lines changed

1 file changed

+155
-2
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleReminders.kt

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.appcompat.app.AppCompatActivity
2525
import androidx.core.os.BundleCompat
2626
import androidx.fragment.app.Fragment
2727
import androidx.fragment.app.setFragmentResult
28+
import androidx.fragment.app.setFragmentResultListener
2829
import androidx.recyclerview.widget.DividerItemDecoration
2930
import androidx.recyclerview.widget.LinearLayoutManager
3031
import androidx.recyclerview.widget.RecyclerView
@@ -37,6 +38,10 @@ import com.ichi2.anki.dialogs.DeckSelectionDialog
3738
import com.ichi2.anki.launchCatchingTask
3839
import com.ichi2.anki.libanki.DeckId
3940
import com.ichi2.anki.showError
41+
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
42+
import com.ichi2.anki.snackbar.SnackbarBuilder
43+
import com.ichi2.anki.snackbar.showSnackbar
44+
import com.ichi2.anki.utils.ext.showDialogFragment
4045
import com.ichi2.anki.withProgress
4146
import kotlinx.serialization.SerializationException
4247
import timber.log.Timber
@@ -46,7 +51,8 @@ import timber.log.Timber
4651
*/
4752
class ScheduleReminders :
4853
Fragment(R.layout.fragment_schedule_reminders),
49-
DeckSelectionDialog.DeckSelectionListener {
54+
DeckSelectionDialog.DeckSelectionListener,
55+
BaseSnackbarBuilderProvider {
5056
/**
5157
* Whether this fragment has been opened to edit all review reminders or just a specific deck's reminders.
5258
* @see ReviewReminderScope
@@ -63,6 +69,10 @@ class ScheduleReminders :
6369
private lateinit var recyclerView: RecyclerView
6470
private lateinit var adapter: ScheduleRemindersAdapter
6571

72+
override val baseSnackbarBuilder: SnackbarBuilder = {
73+
anchorView = requireView().findViewById<ExtendedFloatingActionButton>(R.id.schedule_reminders_add_reminder_fab)
74+
}
75+
6676
/**
6777
* The reminders currently being displayed in the UI. To make changes to this list show up on screen,
6878
* use [triggerUIUpdate]. Note that editing this map does not also automatically write to the database.
@@ -109,6 +119,26 @@ class ScheduleReminders :
109119

110120
// Retrieve reminders based on the editing scope
111121
launchCatchingTask { loadDatabaseRemindersIntoUI() }
122+
123+
// If the user creates or edits a review reminder, the dialog for doing so opens
124+
// Once their changes are complete, the dialog closes and this fragment is reloaded
125+
// Hence, we check for any fragment results here and update the database accordingly
126+
setFragmentResultListener(ADD_EDIT_DIALOG_RESULT_REQUEST_KEY) { _, bundle ->
127+
val modeOfFinishedDialog =
128+
BundleCompat.getParcelable(
129+
requireArguments(),
130+
ACTIVE_DIALOG_MODE_ARGUMENTS_KEY,
131+
AddEditReminderDialog.DialogMode::class.java,
132+
) ?: return@setFragmentResultListener
133+
val newOrModifiedReminder =
134+
BundleCompat.getParcelable(
135+
bundle,
136+
ADD_EDIT_DIALOG_RESULT_REQUEST_KEY,
137+
ReviewReminder::class.java,
138+
)
139+
Timber.d("Dialog result received with recent dialog mode: %s", modeOfFinishedDialog)
140+
handleAddEditDialogResult(newOrModifiedReminder, modeOfFinishedDialog)
141+
}
112142
}
113143

114144
private fun reloadToolbarText() {
@@ -141,6 +171,112 @@ class ScheduleReminders :
141171
Timber.d("Database review reminders successfully loaded")
142172
}
143173

174+
/**
175+
* When a [AddEditReminderDialog] instance finishes, we handle the result of the dialog fragment via this method.
176+
*/
177+
private fun handleAddEditDialogResult(
178+
newOrModifiedReminder: ReviewReminder?,
179+
modeOfFinishedDialog: AddEditReminderDialog.DialogMode,
180+
) {
181+
Timber.d("Handling add/edit dialog result: mode=%s reminder=%s", modeOfFinishedDialog, newOrModifiedReminder)
182+
updateDatabaseForAddEditDialog(newOrModifiedReminder, modeOfFinishedDialog)
183+
updateUIForAddEditDialog(newOrModifiedReminder, modeOfFinishedDialog)
184+
// Feedback
185+
showSnackbar(
186+
when (modeOfFinishedDialog) {
187+
is AddEditReminderDialog.DialogMode.Add -> "Successfully added new review reminder"
188+
is AddEditReminderDialog.DialogMode.Edit -> {
189+
when (newOrModifiedReminder) {
190+
null -> "Successfully deleted review reminder"
191+
else -> "Successfully edited review reminder"
192+
}
193+
}
194+
},
195+
)
196+
}
197+
198+
/**
199+
* Write the new or modified reminder to the database.
200+
* @see handleAddEditDialogResult
201+
*/
202+
private fun updateDatabaseForAddEditDialog(
203+
newOrModifiedReminder: ReviewReminder?,
204+
modeOfFinishedDialog: AddEditReminderDialog.DialogMode,
205+
) {
206+
launchCatchingTask {
207+
catchDatabaseExceptions {
208+
if (modeOfFinishedDialog is AddEditReminderDialog.DialogMode.Edit) {
209+
// Delete the existing reminder if we're in edit mode
210+
// This action must be separated from writing the modified reminder because the user may have updated the reminder's deck,
211+
// meaning we need to delete the old reminder in the old deck, then add a new reminder to the new deck
212+
val reminderToDelete = modeOfFinishedDialog.reminderToBeEdited
213+
Timber.d("Deleting old reminder from database")
214+
when (reminderToDelete.scope) {
215+
is ReviewReminderScope.Global -> ReviewRemindersDatabase.editAllAppWideReminders(deleteReminder(reminderToDelete))
216+
is ReviewReminderScope.DeckSpecific ->
217+
ReviewRemindersDatabase.editRemindersForDeck(
218+
reminderToDelete.scope.did,
219+
deleteReminder(reminderToDelete),
220+
)
221+
}
222+
}
223+
newOrModifiedReminder?.let { reminder ->
224+
Timber.d("Writing new or modified reminder to database")
225+
when (reminder.scope) {
226+
is ReviewReminderScope.Global -> ReviewRemindersDatabase.editAllAppWideReminders(upsertReminder(reminder))
227+
is ReviewReminderScope.DeckSpecific ->
228+
ReviewRemindersDatabase.editRemindersForDeck(
229+
reminder.scope.did,
230+
upsertReminder(reminder),
231+
)
232+
}
233+
}
234+
}
235+
}
236+
}
237+
238+
/**
239+
* Lambda that can be fed into [ReviewRemindersDatabase.editRemindersForDeck] or
240+
* [ReviewRemindersDatabase.editAllAppWideReminders] which deletes the given review reminder.
241+
*/
242+
private fun deleteReminder(reminder: ReviewReminder) =
243+
{ reminders: HashMap<ReviewReminderId, ReviewReminder> ->
244+
reminders.remove(reminder.id)
245+
reminders
246+
}
247+
248+
/**
249+
* Lambda that can be fed into [ReviewRemindersDatabase.editRemindersForDeck] or
250+
* [ReviewRemindersDatabase.editAllAppWideReminders] which updates the given review reminder if it
251+
* exists or inserts it if it doesn't (an "upsert" operation)
252+
*/
253+
private fun upsertReminder(reminder: ReviewReminder) =
254+
{ reminders: HashMap<ReviewReminderId, ReviewReminder> ->
255+
reminders[reminder.id] = reminder
256+
reminders
257+
}
258+
259+
/**
260+
* Update the RecyclerView with the new or modified reminder.
261+
* @see handleAddEditDialogResult
262+
*/
263+
private fun updateUIForAddEditDialog(
264+
newOrModifiedReminder: ReviewReminder?,
265+
modeOfFinishedDialog: AddEditReminderDialog.DialogMode,
266+
) {
267+
if (modeOfFinishedDialog is AddEditReminderDialog.DialogMode.Edit) {
268+
Timber.d("Deleting old reminder from UI")
269+
reminders.remove(modeOfFinishedDialog.reminderToBeEdited.id)
270+
}
271+
newOrModifiedReminder?.let {
272+
if (scheduleRemindersScope == ReviewReminderScope.Global || scheduleRemindersScope == it.scope) {
273+
Timber.d("Adding new reminder to UI")
274+
reminders[it.id] = it
275+
}
276+
}
277+
triggerUIUpdate()
278+
}
279+
144280
/**
145281
* Sets a TextView's text based on a [ReviewReminderScope].
146282
* The text is either the scope's associated deck's name, or "All Decks" if the scope is global.
@@ -151,7 +287,7 @@ class ScheduleReminders :
151287
view: TextView,
152288
) {
153289
when (scope) {
154-
is ReviewReminderScope.Global -> view.text = "All Decks"
290+
is ReviewReminderScope.Global -> view.text = getString(R.string.card_browser_all_decks)
155291
is ReviewReminderScope.DeckSpecific -> {
156292
launchCatchingTask {
157293
val deckName = cachedDeckNames.getOrPut(scope.did) { scope.getDeckName() }
@@ -201,6 +337,11 @@ class ScheduleReminders :
201337
*/
202338
private fun addReminder() {
203339
Timber.d("Adding new review reminder")
340+
val dialogMode = AddEditReminderDialog.DialogMode.Add(scheduleRemindersScope)
341+
val dialog = AddEditReminderDialog.getInstance(dialogMode)
342+
// Save the dialog mode so that we refer back to it once the dialog closes
343+
requireArguments().putParcelable(ACTIVE_DIALOG_MODE_ARGUMENTS_KEY, dialogMode)
344+
showDialogFragment(dialog)
204345
}
205346

206347
/**
@@ -209,6 +350,11 @@ class ScheduleReminders :
209350
*/
210351
private fun editReminder(reminder: ReviewReminder) {
211352
Timber.d("Editing review reminder: %s", reminder.id)
353+
val dialogMode = AddEditReminderDialog.DialogMode.Edit(reminder)
354+
val dialog = AddEditReminderDialog.getInstance(dialogMode)
355+
// Save the dialog mode so that we refer back to it once the dialog closes
356+
requireArguments().putParcelable(ACTIVE_DIALOG_MODE_ARGUMENTS_KEY, dialogMode)
357+
showDialogFragment(dialog)
212358
}
213359

214360
/**
@@ -265,8 +411,15 @@ class ScheduleReminders :
265411
*/
266412
const val DECK_SELECTION_RESULT_REQUEST_KEY = "reminder_deck_selection_result_request_key"
267413

414+
/**
415+
* TODO: Move to string resources for translation once review reminders are stable.
416+
*/
268417
private const val SERIALIZATION_ERROR_MESSAGE =
269418
"Something went wrong. A serialization error was encountered while working with review reminders."
419+
420+
/**
421+
* TODO: Move to string resources for translation once review reminders are stable.
422+
*/
270423
private const val DATA_TYPE_ERROR_MESSAGE =
271424
"Something went wrong. An unexpected data type was found while working with review reminders."
272425

0 commit comments

Comments
 (0)