@@ -25,6 +25,7 @@ import androidx.appcompat.app.AppCompatActivity
2525import androidx.core.os.BundleCompat
2626import androidx.fragment.app.Fragment
2727import androidx.fragment.app.setFragmentResult
28+ import androidx.fragment.app.setFragmentResultListener
2829import androidx.recyclerview.widget.DividerItemDecoration
2930import androidx.recyclerview.widget.LinearLayoutManager
3031import androidx.recyclerview.widget.RecyclerView
@@ -37,6 +38,10 @@ import com.ichi2.anki.dialogs.DeckSelectionDialog
3738import com.ichi2.anki.launchCatchingTask
3839import com.ichi2.anki.libanki.DeckId
3940import 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
4045import com.ichi2.anki.withProgress
4146import kotlinx.serialization.SerializationException
4247import timber.log.Timber
@@ -46,7 +51,8 @@ import timber.log.Timber
4651 */
4752class 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