@@ -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
@@ -38,6 +39,10 @@ import com.ichi2.anki.launchCatchingTask
3839import com.ichi2.anki.libanki.DeckId
3940import com.ichi2.anki.model.SelectableDeck
4041import com.ichi2.anki.showError
42+ import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
43+ import com.ichi2.anki.snackbar.SnackbarBuilder
44+ import com.ichi2.anki.snackbar.showSnackbar
45+ import com.ichi2.anki.utils.ext.showDialogFragment
4146import com.ichi2.anki.withProgress
4247import kotlinx.serialization.SerializationException
4348import timber.log.Timber
@@ -47,7 +52,8 @@ import timber.log.Timber
4752 */
4853class ScheduleReminders :
4954 Fragment (R .layout.fragment_schedule_reminders),
50- DeckSelectionDialog .DeckSelectionListener {
55+ DeckSelectionDialog .DeckSelectionListener ,
56+ BaseSnackbarBuilderProvider {
5157 /* *
5258 * Whether this fragment has been opened to edit all review reminders or just a specific deck's reminders.
5359 * @see ReviewReminderScope
@@ -64,6 +70,10 @@ class ScheduleReminders :
6470 private lateinit var recyclerView: RecyclerView
6571 private lateinit var adapter: ScheduleRemindersAdapter
6672
73+ override val baseSnackbarBuilder: SnackbarBuilder = {
74+ anchorView = requireView().findViewById<ExtendedFloatingActionButton >(R .id.schedule_reminders_add_reminder_fab)
75+ }
76+
6777 /* *
6878 * The reminders currently being displayed in the UI. To make changes to this list show up on screen,
6979 * use [triggerUIUpdate]. Note that editing this map does not also automatically write to the database.
@@ -110,6 +120,26 @@ class ScheduleReminders :
110120
111121 // Retrieve reminders based on the editing scope
112122 launchCatchingTask { loadDatabaseRemindersIntoUI() }
123+
124+ // If the user creates or edits a review reminder, the dialog for doing so opens
125+ // Once their changes are complete, the dialog closes and this fragment is reloaded
126+ // Hence, we check for any fragment results here and update the database accordingly
127+ setFragmentResultListener(ADD_EDIT_DIALOG_RESULT_REQUEST_KEY ) { _, bundle ->
128+ val modeOfFinishedDialog =
129+ BundleCompat .getParcelable(
130+ requireArguments(),
131+ ACTIVE_DIALOG_MODE_ARGUMENTS_KEY ,
132+ AddEditReminderDialog .DialogMode ::class .java,
133+ ) ? : return @setFragmentResultListener
134+ val newOrModifiedReminder =
135+ BundleCompat .getParcelable(
136+ bundle,
137+ ADD_EDIT_DIALOG_RESULT_REQUEST_KEY ,
138+ ReviewReminder ::class .java,
139+ )
140+ Timber .d(" Dialog result received with recent dialog mode: %s" , modeOfFinishedDialog)
141+ handleAddEditDialogResult(newOrModifiedReminder, modeOfFinishedDialog)
142+ }
113143 }
114144
115145 private fun reloadToolbarText () {
@@ -142,6 +172,112 @@ class ScheduleReminders :
142172 Timber .d(" Database review reminders successfully loaded" )
143173 }
144174
175+ /* *
176+ * When a [AddEditReminderDialog] instance finishes, we handle the result of the dialog fragment via this method.
177+ */
178+ private fun handleAddEditDialogResult (
179+ newOrModifiedReminder : ReviewReminder ? ,
180+ modeOfFinishedDialog : AddEditReminderDialog .DialogMode ,
181+ ) {
182+ Timber .d(" Handling add/edit dialog result: mode=%s reminder=%s" , modeOfFinishedDialog, newOrModifiedReminder)
183+ updateDatabaseForAddEditDialog(newOrModifiedReminder, modeOfFinishedDialog)
184+ updateUIForAddEditDialog(newOrModifiedReminder, modeOfFinishedDialog)
185+ // Feedback
186+ showSnackbar(
187+ when (modeOfFinishedDialog) {
188+ is AddEditReminderDialog .DialogMode .Add -> " Successfully added new review reminder"
189+ is AddEditReminderDialog .DialogMode .Edit -> {
190+ when (newOrModifiedReminder) {
191+ null -> " Successfully deleted review reminder"
192+ else -> " Successfully edited review reminder"
193+ }
194+ }
195+ },
196+ )
197+ }
198+
199+ /* *
200+ * Write the new or modified reminder to the database.
201+ * @see handleAddEditDialogResult
202+ */
203+ private fun updateDatabaseForAddEditDialog (
204+ newOrModifiedReminder : ReviewReminder ? ,
205+ modeOfFinishedDialog : AddEditReminderDialog .DialogMode ,
206+ ) {
207+ launchCatchingTask {
208+ catchDatabaseExceptions {
209+ if (modeOfFinishedDialog is AddEditReminderDialog .DialogMode .Edit ) {
210+ // Delete the existing reminder if we're in edit mode
211+ // This action must be separated from writing the modified reminder because the user may have updated the reminder's deck,
212+ // meaning we need to delete the old reminder in the old deck, then add a new reminder to the new deck
213+ val reminderToDelete = modeOfFinishedDialog.reminderToBeEdited
214+ Timber .d(" Deleting old reminder from database" )
215+ when (reminderToDelete.scope) {
216+ is ReviewReminderScope .Global -> ReviewRemindersDatabase .editAllAppWideReminders(deleteReminder(reminderToDelete))
217+ is ReviewReminderScope .DeckSpecific ->
218+ ReviewRemindersDatabase .editRemindersForDeck(
219+ reminderToDelete.scope.did,
220+ deleteReminder(reminderToDelete),
221+ )
222+ }
223+ }
224+ newOrModifiedReminder?.let { reminder ->
225+ Timber .d(" Writing new or modified reminder to database" )
226+ when (reminder.scope) {
227+ is ReviewReminderScope .Global -> ReviewRemindersDatabase .editAllAppWideReminders(upsertReminder(reminder))
228+ is ReviewReminderScope .DeckSpecific ->
229+ ReviewRemindersDatabase .editRemindersForDeck(
230+ reminder.scope.did,
231+ upsertReminder(reminder),
232+ )
233+ }
234+ }
235+ }
236+ }
237+ }
238+
239+ /* *
240+ * Lambda that can be fed into [ReviewRemindersDatabase.editRemindersForDeck] or
241+ * [ReviewRemindersDatabase.editAllAppWideReminders] which deletes the given review reminder.
242+ */
243+ private fun deleteReminder (reminder : ReviewReminder ) =
244+ { reminders: HashMap <ReviewReminderId , ReviewReminder > ->
245+ reminders.remove(reminder.id)
246+ reminders
247+ }
248+
249+ /* *
250+ * Lambda that can be fed into [ReviewRemindersDatabase.editRemindersForDeck] or
251+ * [ReviewRemindersDatabase.editAllAppWideReminders] which updates the given review reminder if it
252+ * exists or inserts it if it doesn't (an "upsert" operation)
253+ */
254+ private fun upsertReminder (reminder : ReviewReminder ) =
255+ { reminders: HashMap <ReviewReminderId , ReviewReminder > ->
256+ reminders[reminder.id] = reminder
257+ reminders
258+ }
259+
260+ /* *
261+ * Update the RecyclerView with the new or modified reminder.
262+ * @see handleAddEditDialogResult
263+ */
264+ private fun updateUIForAddEditDialog (
265+ newOrModifiedReminder : ReviewReminder ? ,
266+ modeOfFinishedDialog : AddEditReminderDialog .DialogMode ,
267+ ) {
268+ if (modeOfFinishedDialog is AddEditReminderDialog .DialogMode .Edit ) {
269+ Timber .d(" Deleting old reminder from UI" )
270+ reminders.remove(modeOfFinishedDialog.reminderToBeEdited.id)
271+ }
272+ newOrModifiedReminder?.let {
273+ if (scheduleRemindersScope == ReviewReminderScope .Global || scheduleRemindersScope == it.scope) {
274+ Timber .d(" Adding new reminder to UI" )
275+ reminders[it.id] = it
276+ }
277+ }
278+ triggerUIUpdate()
279+ }
280+
145281 /* *
146282 * Sets a TextView's text based on a [ReviewReminderScope].
147283 * The text is either the scope's associated deck's name, or "All Decks" if the scope is global.
@@ -152,7 +288,7 @@ class ScheduleReminders :
152288 view : TextView ,
153289 ) {
154290 when (scope) {
155- is ReviewReminderScope .Global -> view.text = " All Decks "
291+ is ReviewReminderScope .Global -> view.text = getString( R .string.card_browser_all_decks)
156292 is ReviewReminderScope .DeckSpecific -> {
157293 launchCatchingTask {
158294 val deckName = cachedDeckNames.getOrPut(scope.did) { scope.getDeckName() }
@@ -202,6 +338,11 @@ class ScheduleReminders :
202338 */
203339 private fun addReminder () {
204340 Timber .d(" Adding new review reminder" )
341+ val dialogMode = AddEditReminderDialog .DialogMode .Add (scheduleRemindersScope)
342+ val dialog = AddEditReminderDialog .getInstance(dialogMode)
343+ // Save the dialog mode so that we refer back to it once the dialog closes
344+ requireArguments().putParcelable(ACTIVE_DIALOG_MODE_ARGUMENTS_KEY , dialogMode)
345+ showDialogFragment(dialog)
205346 }
206347
207348 /* *
@@ -210,6 +351,11 @@ class ScheduleReminders :
210351 */
211352 private fun editReminder (reminder : ReviewReminder ) {
212353 Timber .d(" Editing review reminder: %s" , reminder.id)
354+ val dialogMode = AddEditReminderDialog .DialogMode .Edit (reminder)
355+ val dialog = AddEditReminderDialog .getInstance(dialogMode)
356+ // Save the dialog mode so that we refer back to it once the dialog closes
357+ requireArguments().putParcelable(ACTIVE_DIALOG_MODE_ARGUMENTS_KEY , dialogMode)
358+ showDialogFragment(dialog)
213359 }
214360
215361 /* *
@@ -266,8 +412,15 @@ class ScheduleReminders :
266412 */
267413 const val DECK_SELECTION_RESULT_REQUEST_KEY = " reminder_deck_selection_result_request_key"
268414
415+ /* *
416+ * TODO: Move to string resources for translation once review reminders are stable.
417+ */
269418 private const val SERIALIZATION_ERROR_MESSAGE =
270419 " Something went wrong. A serialization error was encountered while working with review reminders."
420+
421+ /* *
422+ * TODO: Move to string resources for translation once review reminders are stable.
423+ */
271424 private const val DATA_TYPE_ERROR_MESSAGE =
272425 " Something went wrong. An unexpected data type was found while working with review reminders."
273426
0 commit comments