1616
1717package com.ichi2.anki.reviewreminders
1818
19- import android.Manifest
2019import android.content.Context
2120import android.content.Intent
22- import android.os.Build
2321import android.os.Bundle
22+ import android.view.Menu
23+ import android.view.MenuInflater
24+ import android.view.MenuItem
2425import android.view.View
25- import androidx.activity.result.contract.ActivityResultContracts
26- import androidx.annotation.RequiresApi
2726import androidx.appcompat.app.AppCompatActivity
2827import androidx.core.os.BundleCompat
28+ import androidx.core.view.MenuProvider
2929import androidx.core.view.isVisible
3030import androidx.fragment.app.Fragment
31+ import androidx.fragment.app.activityViewModels
32+ import androidx.fragment.app.commit
3133import androidx.fragment.app.setFragmentResult
3234import androidx.fragment.app.setFragmentResultListener
35+ import androidx.lifecycle.Lifecycle
36+ import androidx.lifecycle.flowWithLifecycle
37+ import androidx.lifecycle.lifecycleScope
3338import androidx.recyclerview.widget.DividerItemDecoration
3439import androidx.recyclerview.widget.LinearLayoutManager
3540import com.google.android.material.snackbar.Snackbar
@@ -43,15 +48,13 @@ import com.ichi2.anki.launchCatchingTask
4348import com.ichi2.anki.libanki.DeckId
4449import com.ichi2.anki.model.SelectableDeck
4550import com.ichi2.anki.services.AlarmManagerService
46- import com.ichi2.anki.settings.Prefs
4751import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
4852import com.ichi2.anki.snackbar.SnackbarBuilder
4953import com.ichi2.anki.snackbar.showSnackbar
5054import com.ichi2.anki.utils.ext.showDialogFragment
5155import com.ichi2.anki.withProgress
52- import com.ichi2.utils.Permissions
53- import com.ichi2.utils.Permissions.requestPermissionThroughDialogOrSettings
5456import dev.androidbroadcast.vbpd.viewBinding
57+ import kotlinx.coroutines.launch
5558import timber.log.Timber
5659
5760/* *
@@ -75,24 +78,18 @@ class ScheduleReminders :
7578
7679 private val binding by viewBinding(FragmentScheduleRemindersBinding ::bind)
7780
81+ private val troubleshootingViewModel: ReminderTroubleshootingViewModel by activityViewModels {
82+ reminderTroubleshootingViewModelFactory(requireContext())
83+ }
84+
7885 private lateinit var adapter: ScheduleRemindersAdapter
7986
87+ private var troubleshootingSnackbar: Snackbar ? = null
88+
8089 override val baseSnackbarBuilder: SnackbarBuilder = {
8190 anchorView = binding.floatingActionButtonAdd
8291 }
8392
84- private var notificationPermissionSnackbar: Snackbar ? = null
85-
86- /* *
87- * Launches the OS dialog for requesting notification permissions.
88- * If notification permissions are not granted, a small persistent Snackbar reminder about it shows up.
89- * When the user clicks the "Enable" action on the Snackbar, this launcher is used.
90- */
91- private val notificationPermissionLauncher =
92- registerForActivityResult(
93- ActivityResultContracts .RequestPermission (),
94- ) { isGranted -> Timber .i(" Notification permission result: $isGranted " ) }
95-
9693 /* *
9794 * The reminders currently being displayed in the UI. To make changes to this list show up on screen,
9895 * use [triggerUIUpdate]. Note that editing this map does not also automatically write to the database.
@@ -116,10 +113,55 @@ class ScheduleReminders :
116113 // Set up toolbar
117114 reloadToolbarText()
118115 (requireActivity() as AppCompatActivity ).setSupportActionBar(binding.toolbar)
116+ requireActivity().addMenuProvider(
117+ object : MenuProvider {
118+ override fun onCreateMenu (
119+ menu : Menu ,
120+ menuInflater : MenuInflater ,
121+ ) {
122+ menuInflater.inflate(R .menu.schedule_reminders, menu)
123+ }
119124
125+ override fun onMenuItemSelected (menuItem : MenuItem ): Boolean =
126+ when (menuItem.itemId) {
127+ R .id.action_troubleshoot -> {
128+ openTroubleshootingScreen()
129+ true
130+ }
131+ else -> false
132+ }
133+ },
134+ viewLifecycleOwner,
135+ )
120136 // Set up add button
121137 binding.floatingActionButtonAdd.setOnClickListener { addReminder() }
122138
139+ // Troubleshoot snackbar: shown persistently when checks find a warning/error.
140+ // Tapping "Fix" opens the full troubleshooting screen.
141+ lifecycleScope.launch {
142+ troubleshootingViewModel.state
143+ .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle .State .STARTED )
144+ .collect { state ->
145+ val message =
146+ when (state.summaryStatus) {
147+ SummaryStatus .Ok , SummaryStatus .Warning -> {
148+ troubleshootingSnackbar?.dismiss()
149+ troubleshootingSnackbar = null
150+ return @collect
151+ }
152+ SummaryStatus .Error -> " Reminders are unavailable"
153+ }
154+ if (troubleshootingSnackbar?.isShown == true ) {
155+ troubleshootingSnackbar?.setText(message)
156+ return @collect
157+ }
158+ troubleshootingSnackbar =
159+ showSnackbar(text = message, duration = Snackbar .LENGTH_INDEFINITE ) {
160+ setAction(" Fix" ) { openTroubleshootingScreen() }
161+ }
162+ }
163+ }
164+
123165 // Set up recycler view
124166 val layoutManager = LinearLayoutManager (requireContext())
125167 binding.recyclerView.layoutManager = layoutManager
@@ -365,6 +407,22 @@ class ScheduleReminders :
365407 }
366408 }
367409
410+ /* *
411+ * Opens a screen where the user can see why reminders may not fire as expected
412+ * @see ReminderTroubleshootingFragment
413+ */
414+ private fun openTroubleshootingScreen () {
415+ troubleshootingSnackbar?.dismiss()
416+ parentFragmentManager.commit {
417+ replace(
418+ R .id.fragment_container,
419+ ReminderTroubleshootingFragment (),
420+ SingleFragmentActivity .FRAGMENT_TAG ,
421+ )
422+ addToBackStack(null )
423+ }
424+ }
425+
368426 /* *
369427 * The method that runs when the "+" icon is pressed, allowing the user to create a new review reminder.
370428 * Opens [AddEditReminderDialog] in [AddEditReminderDialog.DialogMode.Add] mode.
@@ -423,35 +481,7 @@ class ScheduleReminders :
423481
424482 override fun onResume () {
425483 super .onResume()
426- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
427- checkForNotificationPermissions()
428- }
429- }
430-
431- /* *
432- * Shows a persistent snackbar if the user has not granted notification permissions.
433- */
434- @RequiresApi(Build .VERSION_CODES .TIRAMISU )
435- private fun checkForNotificationPermissions () {
436- if (! Prefs .reminderNotifsRequestShown || Permissions .canPostNotifications(requireContext())) {
437- notificationPermissionSnackbar?.dismiss()
438- return
439- }
440-
441- notificationPermissionSnackbar =
442- showSnackbar(
443- text = " Notifications are disabled" ,
444- duration = Snackbar .LENGTH_INDEFINITE ,
445- ) {
446- setAction(" Enable" ) {
447- requestPermissionThroughDialogOrSettings(
448- activity = requireActivity(),
449- permission = Manifest .permission.POST_NOTIFICATIONS ,
450- permissionRequestedFlag = Prefs ::notificationsPermissionRequested,
451- permissionRequestLauncher = notificationPermissionLauncher,
452- )
453- }
454- }
484+ troubleshootingViewModel.refreshChecks()
455485 }
456486
457487 companion object {
0 commit comments