@@ -19,6 +19,7 @@ package com.ichi2.anki
1919import android.app.Activity
2020import android.app.Dialog
2121import android.content.Context
22+ import android.content.ContextWrapper
2223import android.content.DialogInterface
2324import android.database.sqlite.SQLiteDatabaseCorruptException
2425import android.net.Uri
@@ -37,9 +38,6 @@ import anki.collection.Progress
3738import com.ichi2.anki.CollectionManager.TR
3839import com.ichi2.anki.CrashReportData.Companion.throwIfDialogUnusable
3940import com.ichi2.anki.CrashReportData.Companion.toCrashReportData
40- import com.ichi2.anki.CrashReportData.HelpAction
41- import com.ichi2.anki.CrashReportData.HelpAction.AnkiBackendLink
42- import com.ichi2.anki.CrashReportData.HelpAction.OpenDeckOptions
4341import com.ichi2.anki.android.AnkiBroadcastReceiver
4442import com.ichi2.anki.common.annotations.UseContextParameter
4543import com.ichi2.anki.common.coroutines.applicationScope
@@ -49,6 +47,7 @@ import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
4947import com.ichi2.anki.exception.StorageAccessException
5048import com.ichi2.anki.pages.DeckOptionsDestination
5149import com.ichi2.anki.snackbar.showSnackbar
50+ import com.ichi2.anki.ui.internationalization.sentenceCase
5251import com.ichi2.anki.utils.openUrl
5352import com.ichi2.utils.create
5453import com.ichi2.utils.message
@@ -282,16 +281,16 @@ fun Context.showError(
282281
283282 Timber .i(" Error dialog displayed" )
284283
284+ val helpAction = crashReportData?.helpAction?.takeIf { it.canExecute(this ) }
285+
285286 try {
286287 AlertDialog
287288 .Builder (this )
288289 .create {
289290 title(R .string.vague_error)
290291 message(text = message)
291292 positiveButton(R .string.dialog_ok)
292- if (crashReportData?.helpAction != null ) {
293- neutralButton(R .string.help)
294- }
293+ helpAction?.let { neutralButton(text = it.buttonText(this @showError)) }
295294 if (crashReportData?.reportableException == true ) {
296295 Timber .w(" sending crash report on close" )
297296 setOnDismissListener { crashReportData.sendCrashReport() }
@@ -301,10 +300,7 @@ fun Context.showError(
301300 setOnShowListener {
302301 neutralButton?.setOnClickListener {
303302 lifecycle.coroutineScope.launch {
304- val shouldDismiss = crashReportData!! .helpAction!! .execute(context = context)
305- if (shouldDismiss) {
306- dismiss()
307- }
303+ if (helpAction!! .execute(this @showError)) dismiss()
308304 }
309305 }
310306 }
@@ -318,25 +314,13 @@ fun Context.showError(
318314 }
319315}
320316
321- /* *
322- * @return Whether the dialog should be dismissed
323- */
324- suspend fun HelpAction.execute (context : Context ): Boolean {
317+ /* * The dialog's [Context] is wrapped (e.g. ContextThemeWrapper); walk the chain to find the activity. */
318+ internal tailrec fun Context.findAnkiActivity (): AnkiActivity ? =
325319 when (this ) {
326- is AnkiBackendLink -> {
327- context.openUrl(this .link)
328- return false
329- }
330- OpenDeckOptions -> {
331- // if we're in the error dialog, we have no context of the deck which caused the exception
332- // assume it's the current deck
333- val openCurrentDeckOptions = DeckOptionsDestination .fromCurrentDeck()
334- context.startActivity(openCurrentDeckOptions.toIntent(context))
335- // dismiss the dialog - the user should have resolved the issue
336- return true
337- }
320+ is AnkiActivity -> this
321+ is ContextWrapper -> baseContext.findAnkiActivity()
322+ else -> null
338323 }
339- }
340324
341325/* * In most cases, you'll want [AnkiActivity.withProgress]
342326 * instead. This lower-level routine can be used to integrate your own
@@ -679,6 +663,7 @@ data class CrashReportData(
679663 fun shouldReportException (): Boolean {
680664 if (! reportableException) return false
681665 if (exception.isInvalidFsrsParametersException()) return false
666+ if (exception.isDeckNotFoundInLimitsMapException()) return false
682667 if (exception is BackendInvalidInputException && exception.message == " missing template" ) return false
683668 return true
684669 }
@@ -699,12 +684,50 @@ data class CrashReportData(
699684 * - Open the deck options
700685 */
701686 sealed class HelpAction {
687+ /* * Label for the 'help' button on the error dialog. Defaults to "Help". */
688+ open fun buttonText (context : Context ): CharSequence = context.getString(R .string.help)
689+
690+ /* * `false` hides the help button. */
691+ open fun canExecute (context : Context ): Boolean = true
692+
693+ /* * Perform the action. @return whether the error dialog should be dismissed. */
694+ abstract suspend fun execute (context : Context ): Boolean
695+
702696 data class AnkiBackendLink (
703697 val link : Uri ,
704- ) : HelpAction()
698+ ) : HelpAction() {
699+ override suspend fun execute (context : Context ): Boolean {
700+ context.openUrl(link)
701+ return false
702+ }
703+ }
705704
706705 /* * Open the deck options for the current deck */
707- data object OpenDeckOptions : HelpAction ()
706+ data object OpenDeckOptions : HelpAction () {
707+ override suspend fun execute (context : Context ): Boolean {
708+ // if we're in the error dialog, we have no context of the deck which caused the exception
709+ // assume it's the current deck
710+ val openCurrentDeckOptions = DeckOptionsDestination .fromCurrentDeck()
711+ context.startActivity(openCurrentDeckOptions.toIntent(context))
712+ // dismiss the dialog - the user should have resolved the issue
713+ return true
714+ }
715+ }
716+
717+ /* * Opens 'Check Database' */
718+ data object OpenCheckDatabase : HelpAction () {
719+ override fun buttonText (context : Context ): CharSequence = with (context) { TR .sentenceCase.checkDatabase }
720+
721+ override fun canExecute (context : Context ): Boolean = context.findAnkiActivity() != null
722+
723+ override suspend fun execute (context : Context ): Boolean {
724+ Timber .i(" Opening 'Check Database'" )
725+ context.findAnkiActivity()!! .showDatabaseErrorDialog(
726+ errorDialogType = DatabaseErrorDialogType .DIALOG_CONFIRM_DATABASE_CHECK ,
727+ )
728+ return true
729+ }
730+ }
708731
709732 companion object {
710733 fun from (e : Throwable ): HelpAction ? {
@@ -720,6 +743,7 @@ data class CrashReportData(
720743
721744 if (link != null ) return AnkiBackendLink (link)
722745 if (e.isInvalidFsrsParametersException()) return OpenDeckOptions
746+ if (e.isDeckNotFoundInLimitsMapException()) return OpenCheckDatabase
723747
724748 return null
725749 }
@@ -768,5 +792,9 @@ data class CrashReportData(
768792 } catch (_: Throwable ) {
769793 false
770794 }
795+
796+ @VisibleForTesting
797+ internal fun Throwable.isDeckNotFoundInLimitsMapException (): Boolean =
798+ this is BackendInvalidInputException && message == " deck not found in limits map"
771799 }
772800}
0 commit comments