Skip to content

Commit 997043a

Browse files
david-allisonmikehardy
authored andcommitted
fix(fsrs-error): add link to deck options
If the following error appears: The provided FSRS parameters are invalid. Leave them blank to use the default parameters. Then the user could not perform any actions to fix this. Provide a 'help' link which opens the deck options Fixes 18984 (cherry picked from commit 5fb9997)
1 parent c993054 commit 997043a

File tree

2 files changed

+104
-17
lines changed

2 files changed

+104
-17
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ import com.ichi2.anki.CollectionManager.TR
3636
import com.ichi2.anki.CollectionManager.withCol
3737
import com.ichi2.anki.CrashReportData.Companion.throwIfDialogUnusable
3838
import com.ichi2.anki.CrashReportData.Companion.toCrashReportData
39+
import com.ichi2.anki.CrashReportData.HelpAction
40+
import com.ichi2.anki.CrashReportData.HelpAction.AnkiBackendLink
41+
import com.ichi2.anki.CrashReportData.HelpAction.OpenDeckOptions
3942
import com.ichi2.anki.common.annotations.UseContextParameter
4043
import com.ichi2.anki.exception.StorageAccessException
4144
import com.ichi2.anki.libanki.Collection
45+
import com.ichi2.anki.pages.DeckOptionsDestination
4246
import com.ichi2.anki.snackbar.showSnackbar
4347
import com.ichi2.anki.utils.openUrl
4448
import com.ichi2.utils.create
@@ -260,17 +264,24 @@ fun Context.showError(
260264
title(R.string.vague_error)
261265
message(text = message)
262266
positiveButton(R.string.dialog_ok)
263-
if (crashReportData?.helpLink != null) {
267+
if (crashReportData?.helpAction != null) {
264268
neutralButton(R.string.help)
265269
}
266-
if (crashReportData?.reportException == true) {
270+
if (crashReportData?.reportableException == true) {
267271
Timber.w("sending crash report on close")
268272
setOnDismissListener { crashReportData.sendCrashReport() }
269273
}
270274
}.apply {
271275
// setup the help link. Link is non-null if neutralButton exists.
272276
setOnShowListener {
273-
neutralButton?.setOnClickListener { openUrl(crashReportData!!.helpLink!!) }
277+
neutralButton?.setOnClickListener {
278+
lifecycle.coroutineScope.launch {
279+
val shouldDismiss = crashReportData!!.helpAction!!.execute(context = context)
280+
if (shouldDismiss) {
281+
dismiss()
282+
}
283+
}
284+
}
274285
}
275286
setupEnterKeyHandler()
276287
show()
@@ -282,6 +293,26 @@ fun Context.showError(
282293
}
283294
}
284295

296+
/**
297+
* @return Whether the dialog should be dismissed
298+
*/
299+
suspend fun HelpAction.execute(context: Context): Boolean {
300+
when (this) {
301+
is AnkiBackendLink -> {
302+
context.openUrl(this.link)
303+
return false
304+
}
305+
OpenDeckOptions -> {
306+
// if we're in the error dialog, we have no context of the deck which caused the exception
307+
// assume it's the current deck
308+
val openCurrentDeckOptions = DeckOptionsDestination.fromCurrentDeck()
309+
context.startActivity(openCurrentDeckOptions.toIntent(context))
310+
// dismiss the dialog - the user should have resolved the issue
311+
return true
312+
}
313+
}
314+
}
315+
285316
/** In most cases, you'll want [AnkiActivity.withProgress]
286317
* instead. This lower-level routine can be used to integrate your own
287318
* progress UI.
@@ -575,29 +606,66 @@ private fun Activity.showError(
575606
data class CrashReportData(
576607
val exception: Throwable,
577608
val origin: String,
578-
val reportException: Boolean,
609+
val reportableException: Boolean,
579610
) {
580611
/**
581612
* Optional link to a help page regarding the error
582613
*
583614
* For example: https://docs.ankiweb.net/templates/errors.html#no-cloze-filter-on-cloze-notetype
615+
* Or opening the deck options
584616
*/
585-
val helpLink: Uri?
586-
get() =
587-
try {
588-
(exception as? BackendException)
589-
?.getDesktopHelpPageLink(CollectionManager.getBackend())
590-
?.toUri()
591-
} catch (e: Exception) {
592-
Timber.w(e)
593-
null
594-
}
617+
val helpAction: HelpAction?
618+
get() = HelpAction.from(exception)
619+
620+
fun shouldReportException(): Boolean {
621+
if (!reportableException) return false
622+
if (exception.isInvalidFsrsParametersException()) return false
623+
return true
624+
}
595625

596626
fun sendCrashReport() {
597-
if (!reportException) return
627+
if (!shouldReportException()) {
628+
Timber.i("skipped crash report due to further validation")
629+
return
630+
}
598631
CrashReportService.sendExceptionReport(exception, origin)
599632
}
600633

634+
/**
635+
* Optional action to provide more information about an error
636+
*
637+
* Examples:
638+
* - Open https://docs.ankiweb.net/templates/errors.html#no-cloze-filter-on-cloze-notetype
639+
* - Open the deck options
640+
*/
641+
sealed class HelpAction {
642+
data class AnkiBackendLink(
643+
val link: Uri,
644+
) : HelpAction()
645+
646+
/** Open the deck options for the current deck */
647+
data object OpenDeckOptions : HelpAction()
648+
649+
companion object {
650+
fun from(e: Throwable): HelpAction? {
651+
val link =
652+
try {
653+
(e as? BackendException)
654+
?.getDesktopHelpPageLink(CollectionManager.getBackend())
655+
?.toUri()
656+
} catch (e: Exception) {
657+
Timber.w(e)
658+
null
659+
}
660+
661+
if (link != null) return AnkiBackendLink(link)
662+
if (e.isInvalidFsrsParametersException()) return OpenDeckOptions
663+
664+
return null
665+
}
666+
}
667+
}
668+
601669
companion object {
602670
@UseContextParameter("context")
603671
fun Throwable.toCrashReportData(
@@ -607,15 +675,15 @@ data class CrashReportData(
607675
exception = this,
608676
// Appears as 'ManageNotetypes'
609677
origin = context::class.java.simpleName,
610-
reportException = reportException,
678+
reportableException = reportException,
611679
)
612680

613681
/**
614682
* If [throwOnShowError] is set, throws the exception from the crash report data
615683
*
616684
* So unit tests can fail if an unexpected exception is thrown
617685
*
618-
* Note: this occurs regardless of the status of [reportException]
686+
* Note: this occurs regardless of the status of [reportableException]
619687
*
620688
* @param message The message of the thrown [IllegalStateException]
621689
* @throws IllegalStateException with [exception] as an innerException if the receiver
@@ -627,5 +695,13 @@ data class CrashReportData(
627695
if (this == null) throw IllegalStateException(message)
628696
throw IllegalStateException(message, exception)
629697
}
698+
699+
private fun Throwable.isInvalidFsrsParametersException(): Boolean =
700+
try {
701+
// `TR` may fail in an error-reporting context
702+
message == TR.deckConfigInvalidParameters()
703+
} catch (_: Throwable) {
704+
false
705+
}
630706
}
631707
}

AnkiDroid/src/main/java/com/ichi2/anki/pages/CongratsPage.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import android.content.Context
1919
import android.content.Intent
2020
import android.os.Bundle
2121
import android.view.View
22+
import androidx.annotation.CheckResult
2223
import androidx.appcompat.app.AlertDialog
2324
import androidx.fragment.app.FragmentActivity
2425
import androidx.fragment.app.setFragmentResultListener
@@ -295,6 +296,16 @@ class DeckOptionsDestination(
295296
deckId = deckId,
296297
isFiltered = withCol { decks.isFiltered(deckId) },
297298
)
299+
300+
@CheckResult
301+
suspend fun fromCurrentDeck() =
302+
withCol {
303+
val deckId = decks.getCurrentId()
304+
DeckOptionsDestination(
305+
deckId = deckId,
306+
isFiltered = decks.isFiltered(deckId),
307+
)
308+
}
298309
}
299310
}
300311

0 commit comments

Comments
 (0)