From 5d41c2ad5b6e83230f19f92e07dedaed5e94c445 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sun, 3 Nov 2024 17:44:22 +0100 Subject: [PATCH] NF: improve SyncErrorDialog Use an enum and constants --- .../main/java/com/ichi2/anki/DeckPicker.kt | 9 +- .../src/main/java/com/ichi2/anki/Sync.kt | 2 +- .../com/ichi2/anki/dialogs/ImportDialog.kt | 60 +++--- .../ichi2/anki/dialogs/MediaCheckDialog.kt | 102 +++++----- .../com/ichi2/anki/dialogs/SyncErrorDialog.kt | 190 +++++++++--------- .../anki/dialogs/AsyncDialogFragmentsTest.kt | 2 +- 6 files changed, 173 insertions(+), 192 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index cb96b6da0569..1b6c0fc4ea46 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -1804,7 +1804,7 @@ open class DeckPicker : * Show a specific sync error dialog * @param dialogType id of dialog to show */ - override fun showSyncErrorDialog(dialogType: Int) { + override fun showSyncErrorDialog(dialogType: SyncErrorDialog.Type) { showSyncErrorDialog(dialogType, "") } @@ -1813,10 +1813,7 @@ open class DeckPicker : * @param dialogType id of dialog to show * @param message text to show */ - override fun showSyncErrorDialog( - dialogType: Int, - message: String?, - ) { + override fun showSyncErrorDialog(dialogType: SyncErrorDialog.Type, message: String?) { val newFragment: AsyncDialogFragment = newInstance(dialogType, message) showAsyncDialogFragment(newFragment, Channel.SYNC) } @@ -1939,7 +1936,7 @@ open class DeckPicker : if (hkey!!.isEmpty()) { Timber.w("User not logged in") pullToSyncWrapper.isRefreshing = false - showSyncErrorDialog(SyncErrorDialog.DIALOG_USER_NOT_LOGGED_IN_SYNC) + showSyncErrorDialog(SyncErrorDialog.Type.DIALOG_USER_NOT_LOGGED_IN_SYNC) return } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt index 9bc0ba7b5817..55deb364e609 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt @@ -286,7 +286,7 @@ private suspend fun handleNormalSync( SyncCollectionResponse.ChangesRequired.FULL_SYNC -> { deckPicker.mediaUsnOnConflict = mediaUsn - deckPicker.showSyncErrorDialog(SyncErrorDialog.DIALOG_SYNC_CONFLICT_RESOLUTION) + deckPicker.showSyncErrorDialog(SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_RESOLUTION) } SyncCollectionResponse.ChangesRequired.NORMAL_SYNC, diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt index 4c1f3547da4f..501adb89e723 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt @@ -19,7 +19,10 @@ package com.ichi2.anki.dialogs import android.os.Bundle import androidx.annotation.CheckResult import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf import com.ichi2.anki.R +import com.ichi2.anki.dialogs.ImportDialog.Type.DIALOG_IMPORT_ADD_CONFIRM +import com.ichi2.anki.dialogs.ImportDialog.Type.DIALOG_IMPORT_REPLACE_CONFIRM import com.ichi2.anki.utils.ext.dismissAllDialogFragments import com.ichi2.utils.negativeButton import com.ichi2.utils.positiveButton @@ -27,36 +30,27 @@ import timber.log.Timber import java.net.URLDecoder class ImportDialog : AsyncDialogFragment() { - enum class Type( - val code: Int, - ) { - DIALOG_IMPORT_ADD_CONFIRM(0), - DIALOG_IMPORT_REPLACE_CONFIRM(1), - ; - - companion object { - fun fromCode(code: Int) = Type.entries.first { code == it.code } - } - } - interface ImportDialogListener { fun importAdd(importPath: String) fun importReplace(importPath: String) } + private val dialogType: Type + get() = Type.fromCode(requireArguments().getInt(IMPORT_DIALOG_TYPE_KEY)) + + private val packagePath: String + get() = requireArguments().getString(IMPORT_DIALOG_PACKAGE_PATH_KEY)!! + override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog { super.onCreate(savedInstanceState) - val type = Type.fromCode(requireArguments().getInt(IMPORT_DIALOG_TYPE_KEY)) val dialog = AlertDialog.Builder(requireActivity()) dialog.setCancelable(true) - val packagePath = requireArguments().getString(IMPORT_DIALOG_PACKAGE_PATH_KEY)!! val displayFileName = filenameFromPath(convertToDisplayName(packagePath)) - return when (type) { - Type.DIALOG_IMPORT_ADD_CONFIRM -> { - dialog - .setTitle(R.string.import_title) + return when (dialogType) { + DIALOG_IMPORT_ADD_CONFIRM -> { + dialog.setTitle(R.string.import_title) .setMessage(res().getString(R.string.import_dialog_message_add, displayFileName)) .positiveButton(R.string.import_message_add) { (activity as ImportDialogListener).importAdd(packagePath) @@ -64,9 +58,8 @@ class ImportDialog : AsyncDialogFragment() { }.negativeButton(R.string.dialog_cancel) .create() } - Type.DIALOG_IMPORT_REPLACE_CONFIRM -> { - dialog - .setTitle(R.string.import_title) + DIALOG_IMPORT_REPLACE_CONFIRM -> { + dialog.setTitle(R.string.import_title) .setMessage(res().getString(R.string.import_message_replace_confirm, displayFileName)) .positiveButton(R.string.dialog_positive_replace) { (activity as ImportDialogListener).importReplace(packagePath) @@ -98,6 +91,13 @@ class ImportDialog : AsyncDialogFragment() { return res().getString(R.string.import_title) } + enum class Type(val code: Int) { + DIALOG_IMPORT_ADD_CONFIRM(0), DIALOG_IMPORT_REPLACE_CONFIRM(1); + companion object { + fun fromCode(code: Int) = Type.entries.first { code == it.code } + } + } + companion object { const val IMPORT_DIALOG_TYPE_KEY = "dialogType" const val IMPORT_DIALOG_PACKAGE_PATH_KEY = "packagePath" @@ -109,17 +109,13 @@ class ImportDialog : AsyncDialogFragment() { * @param packagePath the path of the package to import */ @CheckResult - fun newInstance( - dialogType: Type, - packagePath: String, - ): ImportDialog { - val f = ImportDialog() - val args = Bundle() - args.putInt(IMPORT_DIALOG_TYPE_KEY, dialogType.code) - args.putString(IMPORT_DIALOG_PACKAGE_PATH_KEY, packagePath) - f.arguments = args - return f - } + fun newInstance(dialogType: Type, packagePath: String): ImportDialog = + ImportDialog().apply { + arguments = bundleOf( + IMPORT_DIALOG_TYPE_KEY to dialogType.code, + IMPORT_DIALOG_PACKAGE_PATH_KEY to packagePath + ) + } private fun filenameFromPath(path: String): String = path.split("/").toTypedArray()[path.split("/").toTypedArray().size - 1] } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt index 3094d1dc52cf..4ea4f7145a92 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/MediaCheckDialog.kt @@ -15,14 +15,12 @@ import androidx.core.os.bundleOf import com.ichi2.anki.AnkiActivity import com.ichi2.anki.DeckPicker import com.ichi2.anki.R +import com.ichi2.anki.dialogs.MediaCheckDialog.Type.DIALOG_CONFIRM_MEDIA_CHECK +import com.ichi2.anki.dialogs.MediaCheckDialog.Type.DIALOG_MEDIA_CHECK_RESULTS import com.ichi2.anki.showError import com.ichi2.anki.utils.ext.dismissAllDialogFragments import com.ichi2.libanki.MediaCheckResult -/** - * Key for an array of strings of name of invalid media - */ -const val INVALID = "invalid" class MediaCheckDialog : AsyncDialogFragment() { interface MediaCheckDialogListener { fun showMediaCheckDialog(dialogType: Type) @@ -32,12 +30,24 @@ class MediaCheckDialog : AsyncDialogFragment() { fun deleteUnused(unused: List) } + private val dialogType: Type + get() = Type.fromCode(requireArguments().getInt(MEDIA_CHECK_DIALOG_TYPE_KEY)) + + private val noHave: List? + get() = requireArguments().getStringArrayList(NO_HAVE) + + private val unused: List? + get() = requireArguments().getStringArrayList(UNUSED) + + private val invalid: List? + get() = requireArguments().getStringArrayList(INVALID) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { super.onCreate(savedInstanceState) val dialog = AlertDialog.Builder(requireContext()) .setTitle(notificationTitle) - return when (typeFromArguments()) { - Type.DIALOG_CONFIRM_MEDIA_CHECK -> { + return when (dialogType) { + DIALOG_CONFIRM_MEDIA_CHECK -> { dialog.setMessage(notificationMessage) .setPositiveButton(R.string.dialog_ok) { _, _ -> (activity as MediaCheckDialogListener?)?.mediaCheck() @@ -46,22 +56,22 @@ class MediaCheckDialog : AsyncDialogFragment() { activity?.dismissAllDialogFragments() }.create() } - Type.DIALOG_MEDIA_CHECK_RESULTS -> { - val noHave = requireArguments().getStringArrayList(NO_HAVE) - val unused = requireArguments().getStringArrayList(UNUSED) - val invalid = requireArguments().getStringArrayList(INVALID) + DIALOG_MEDIA_CHECK_RESULTS -> { + val noHave = noHave!! + val unused = unused!! + val invalid = invalid!! // Generate report val report = StringBuilder() - if (invalid!!.isNotEmpty()) { + if (invalid.isNotEmpty()) { report.append(String.format(res().getString(R.string.check_media_invalid), invalid.size)) } - if (unused!!.isNotEmpty()) { + if (unused.isNotEmpty()) { if (report.isNotEmpty()) { report.append("\n") } report.append(String.format(res().getString(R.string.check_media_unused), unused.size)) } - if (noHave!!.isNotEmpty()) { + if (noHave.isNotEmpty()) { if (report.isNotEmpty()) { report.append("\n") } @@ -111,31 +121,19 @@ class MediaCheckDialog : AsyncDialogFragment() { } override val notificationMessage: String - get() = when (typeFromArguments()) { - Type.DIALOG_CONFIRM_MEDIA_CHECK -> res().getString(R.string.check_media_warning) - Type.DIALOG_MEDIA_CHECK_RESULTS -> res().getString(R.string.check_media_acknowledge) + get() = when (dialogType) { + DIALOG_CONFIRM_MEDIA_CHECK -> res().getString(R.string.check_media_warning) + DIALOG_MEDIA_CHECK_RESULTS -> res().getString(R.string.check_media_acknowledge) } override val notificationTitle: String - get() = when (typeFromArguments()) { - Type.DIALOG_CONFIRM_MEDIA_CHECK -> { - res().getString(R.string.check_media_title) - } - - Type.DIALOG_MEDIA_CHECK_RESULTS -> { - res().getString(R.string.app_name) - } + get() = when (dialogType) { + DIALOG_CONFIRM_MEDIA_CHECK -> res().getString(R.string.check_media_title) + DIALOG_MEDIA_CHECK_RESULTS -> res().getString(R.string.app_name) } - private fun typeFromArguments() = Type.fromCode(requireArguments().getInt(MEDIA_CHECK_DIALOG_TYPE_KEY)) - override val dialogHandlerMessage: MediaCheckCompleteDialog get() { - val dialogType = typeFromArguments() - val noHave = requireArguments().getStringArrayList(NO_HAVE) - val unused = requireArguments().getStringArrayList(UNUSED) - val invalid = requireArguments().getStringArrayList(INVALID) - return MediaCheckCompleteDialog(dialogType, noHave, unused, invalid) } @@ -165,40 +163,36 @@ class MediaCheckDialog : AsyncDialogFragment() { */ const val UNUSED = "unused" + /** + * Key for an array of strings of name of invalid media + */ + const val INVALID = "invalid" + @CheckResult - fun newInstance(dialogType: Type): MediaCheckDialog { - val f = MediaCheckDialog() - val args = Bundle() - args.putInt(MEDIA_CHECK_DIALOG_TYPE_KEY, dialogType.code) - f.arguments = args - return f - } + fun newInstance(dialogType: Type) = MediaCheckDialog().apply { arguments = bundleOf(MEDIA_CHECK_DIALOG_TYPE_KEY to dialogType.code) } // TODO Instead of putting string arrays into the bundle, - // make MediaCheckResult parcelable with @Parcelize and put it instead. - // TODO Extract keys to constants - fun newInstance(dialogType: Type, checkList: MediaCheckResult): MediaCheckDialog { - val f = MediaCheckDialog() - val args = Bundle() - args.putStringArrayList(NO_HAVE, ArrayList(checkList.missingFileNames)) - args.putStringArrayList(UNUSED, ArrayList(checkList.unusedFileNames)) - args.putStringArrayList(INVALID, ArrayList(checkList.invalidFileNames)) - args.putInt(MEDIA_CHECK_DIALOG_TYPE_KEY, dialogType.code) - f.arguments = args - return f + // make MediaCheckResult parcelable with @Parcelize and put it instead + fun newInstance(dialogType: Type, checkList: MediaCheckResult) = MediaCheckDialog().apply { + arguments = bundleOf( + NO_HAVE to ArrayList(checkList.missingFileNames), + UNUSED to ArrayList(checkList.unusedFileNames), + INVALID to ArrayList(checkList.invalidFileNames), + MEDIA_CHECK_DIALOG_TYPE_KEY to dialogType.code + ) } } class MediaCheckCompleteDialog( private val dialogType: Type, - private val noHave: ArrayList?, - private val unused: ArrayList?, - private val invalid: ArrayList?, + private val noHave: List?, + private val unused: List?, + private val invalid: List? ) : DialogHandlerMessage(WhichDialogHandler.MSG_SHOW_MEDIA_CHECK_COMPLETE_DIALOG, "MediaCheckCompleteDialog") { override fun handleAsyncMessage(activity: AnkiActivity) { // Media check results when (dialogType) { - Type.DIALOG_MEDIA_CHECK_RESULTS -> { + DIALOG_MEDIA_CHECK_RESULTS -> { // we may be called via any AnkiActivity but media check is a DeckPicker thing if (activity !is DeckPicker) { showError( @@ -212,7 +206,7 @@ class MediaCheckDialog : AsyncDialogFragment() { val checkList = MediaCheckResult(noHave ?: arrayListOf(), unused ?: arrayListOf(), invalid ?: arrayListOf()) activity.showMediaCheckDialog(dialogType, checkList) } - Type.DIALOG_CONFIRM_MEDIA_CHECK -> { } + DIALOG_CONFIRM_MEDIA_CHECK -> { } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt index 63e5165f7fbf..a4c00704ea4e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt @@ -21,26 +21,31 @@ import android.net.Uri import android.os.Bundle import android.os.Message import androidx.annotation.CheckResult -import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf import com.ichi2.anki.AnkiActivity import com.ichi2.anki.ConflictResolution import com.ichi2.anki.DeckPicker import com.ichi2.anki.R +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_CONNECTION_ERROR +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_MEDIA_SYNC_ERROR +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_BASIC_CHECK_ERROR +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_RESOLUTION +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CORRUPT_COLLECTION +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_SANITY_ERROR +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE +import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_USER_NOT_LOGGED_IN_SYNC import com.ichi2.anki.joinSyncMessages import com.ichi2.anki.showError import com.ichi2.anki.utils.ext.dismissAllDialogFragments class SyncErrorDialog : AsyncDialogFragment() { interface SyncErrorDialogListener { - fun showSyncErrorDialog(dialogType: Int) - - fun showSyncErrorDialog( - dialogType: Int, - message: String?, - ) - + fun showSyncErrorDialog(dialogType: Type) + fun showSyncErrorDialog(dialogType: Type, message: String?) fun loginToSyncServer() fun sync(conflict: ConflictResolution? = null) @@ -52,14 +57,16 @@ class SyncErrorDialog : AsyncDialogFragment() { fun requireSyncErrorDialogListener() = activity as SyncErrorDialogListener + /** The type of the sync error dialog*/ + private val dialogType: Type + get() = Type.fromCode(requireArguments().getInt(SYNC_ERROR_DIALOG_TYPE_KEY)) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { super.onCreate(savedInstanceState) - val dialog = - AlertDialog - .Builder(requireContext()) - .setTitle(title) - .setMessage(message) - return when (requireArguments().getInt("dialogType")) { + val dialog = AlertDialog.Builder(requireContext()) + .setTitle(title) + .setMessage(message) + return when (dialogType) { DIALOG_USER_NOT_LOGGED_IN_SYNC -> { // User not logged in; take them to login screen dialog @@ -159,21 +166,22 @@ class SyncErrorDialog : AsyncDialogFragment() { }.setNegativeButton(R.string.dialog_cancel) { _, _ -> } .create() } - else -> null!! } } private val title: String - get() = - when (requireArguments().getInt("dialogType")) { - DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.not_logged_in_title) - DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE -> - res().getString( - R.string.sync_conflict_replace_title, - ) - DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_title_new) - else -> res().getString(R.string.sync_error) - } + get() = when (dialogType) { + DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.not_logged_in_title) + DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE -> res().getString(R.string.sync_conflict_replace_title) + DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_title_new) + DIALOG_CONNECTION_ERROR, + DIALOG_SYNC_SANITY_ERROR, + DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL, + DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE, + DIALOG_MEDIA_SYNC_ERROR, + DIALOG_SYNC_CORRUPT_COLLECTION, + DIALOG_SYNC_BASIC_CHECK_ERROR -> res().getString(R.string.sync_error) + } /** * Get the title which is shown in notification bar when dialog fragment can't be shown @@ -182,7 +190,7 @@ class SyncErrorDialog : AsyncDialogFragment() { */ override val notificationTitle: String get() { - return if (requireArguments().getInt("dialogType") == DIALOG_USER_NOT_LOGGED_IN_SYNC) { + return if (dialogType == DIALOG_USER_NOT_LOGGED_IN_SYNC) { res().getString(R.string.sync_error) } else { title @@ -190,27 +198,20 @@ class SyncErrorDialog : AsyncDialogFragment() { } private val message: String? - get() = - when (requireArguments().getInt("dialogType")) { - DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.login_create_account_message) - DIALOG_CONNECTION_ERROR -> res().getString(R.string.connection_error_message) - DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_message_new) - DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL -> - res().getString( - R.string.sync_conflict_local_confirm_new, - ) - DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE -> - res().getString( - R.string.sync_conflict_remote_confirm_new, - ) - DIALOG_SYNC_CORRUPT_COLLECTION -> { - val syncMessage = requireArguments().getString("dialogMessage") - val repairUrl = res().getString(R.string.repair_deck) - val dialogMessage = res().getString(R.string.sync_corrupt_database, repairUrl) - joinSyncMessages(dialogMessage, syncMessage) - } - else -> requireArguments().getString("dialogMessage") + get() = when (dialogType) { + DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.login_create_account_message) + DIALOG_CONNECTION_ERROR -> res().getString(R.string.connection_error_message) + DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_message_new) + DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL -> res().getString(R.string.sync_conflict_local_confirm_new) + DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE -> res().getString(R.string.sync_conflict_remote_confirm_new) + DIALOG_SYNC_CORRUPT_COLLECTION -> { + val syncMessage = requireArguments().getString(DIALOG_MESSAGE_KEY) + val repairUrl = res().getString(R.string.repair_deck) + val dialogMessage = res().getString(R.string.sync_corrupt_database, repairUrl) + joinSyncMessages(dialogMessage, syncMessage) } + else -> requireArguments().getString(DIALOG_MESSAGE_KEY) + } /** * Get the message which is shown in notification bar when dialog fragment can't be shown @@ -219,7 +220,7 @@ class SyncErrorDialog : AsyncDialogFragment() { */ override val notificationMessage: String? get() { - return if (requireArguments().getInt("dialogType") == DIALOG_USER_NOT_LOGGED_IN_SYNC) { + return if (dialogType == DIALOG_USER_NOT_LOGGED_IN_SYNC) { res().getString(R.string.not_logged_in_title) } else { message @@ -228,8 +229,7 @@ class SyncErrorDialog : AsyncDialogFragment() { override val dialogHandlerMessage: SyncErrorDialogMessageHandler get() { - val dialogType = requireArguments().getInt("dialogType") - val dialogMessage = requireArguments().getString("dialogMessage") + val dialogMessage = requireArguments().getString(DIALOG_MESSAGE_KEY) return SyncErrorDialogMessageHandler(dialogType, dialogMessage) } @@ -241,34 +241,35 @@ class SyncErrorDialog : AsyncDialogFragment() { activity?.dismissAllDialogFragments() } + enum class Type(val code: Int) { + DIALOG_USER_NOT_LOGGED_IN_SYNC(0), + DIALOG_CONNECTION_ERROR(1), + DIALOG_SYNC_CONFLICT_RESOLUTION(2), + DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL(3), + DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE(4), + DIALOG_SYNC_SANITY_ERROR(5), + DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL(6), + DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE(7), + DIALOG_MEDIA_SYNC_ERROR(8), + DIALOG_SYNC_CORRUPT_COLLECTION(9), + DIALOG_SYNC_BASIC_CHECK_ERROR(10); + + companion object { + fun fromCode(code: Int) = Type.entries.first { code == it.code } + } + } + companion object { - const val DIALOG_USER_NOT_LOGGED_IN_SYNC = 0 - const val DIALOG_CONNECTION_ERROR = 1 - const val DIALOG_SYNC_CONFLICT_RESOLUTION = 2 - const val DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL = 3 - const val DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE = 4 - const val DIALOG_SYNC_SANITY_ERROR = 6 - const val DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL = 7 - const val DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE = 8 - const val DIALOG_MEDIA_SYNC_ERROR = 9 - const val DIALOG_SYNC_CORRUPT_COLLECTION = 10 - const val DIALOG_SYNC_BASIC_CHECK_ERROR = 11 - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - val dialogTypes = - arrayOf( - DIALOG_USER_NOT_LOGGED_IN_SYNC, - DIALOG_CONNECTION_ERROR, - DIALOG_SYNC_CONFLICT_RESOLUTION, - DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, - DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE, - DIALOG_SYNC_SANITY_ERROR, - DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL, - DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE, - DIALOG_MEDIA_SYNC_ERROR, - DIALOG_SYNC_CORRUPT_COLLECTION, - DIALOG_SYNC_BASIC_CHECK_ERROR, - ) + /** + * Key for the ordinal of the sync error in Type + */ + const val SYNC_ERROR_DIALOG_TYPE_KEY = "dialogType" + + /** + * Key for the message to display in the dialog + */ + const val DIALOG_MESSAGE_KEY = "dialogMessage" /** * A set of dialogs belonging to AnkiActivity which deal with sync problems @@ -277,22 +278,17 @@ class SyncErrorDialog : AsyncDialogFragment() { * @param dialogMessage A string which can be optionally used to set the dialog message */ @CheckResult - fun newInstance( - dialogType: Int, - dialogMessage: String?, - ): SyncErrorDialog { - val f = SyncErrorDialog() - val args = Bundle() - args.putInt("dialogType", dialogType) - args.putString("dialogMessage", dialogMessage) - f.arguments = args - return f + fun newInstance(dialogType: Type, dialogMessage: String?) = SyncErrorDialog().apply { + arguments = bundleOf( + SYNC_ERROR_DIALOG_TYPE_KEY to dialogType.code, + DIALOG_MESSAGE_KEY to dialogMessage + ) } } class SyncErrorDialogMessageHandler( - private val dialogType: Int, - private val dialogMessage: String?, + private val dialogType: Type, + private val dialogMessage: String? ) : DialogHandlerMessage(WhichDialogHandler.MSG_SHOW_SYNC_ERROR_DIALOG, "SyncErrorDialog") { override fun handleAsyncMessage(activity: AnkiActivity) { // we may be called via any AnkiActivity but media check is a DeckPicker thing @@ -308,20 +304,18 @@ class SyncErrorDialog : AsyncDialogFragment() { activity.showSyncErrorDialog(dialogType, dialogMessage) } - override fun toMessage(): Message = - Message.obtain().apply { - what = this@SyncErrorDialogMessageHandler.what - data = - bundleOf( - "dialogType" to dialogType, - "dialogMessage" to dialogMessage, - ) - } + override fun toMessage(): Message = Message.obtain().apply { + what = this@SyncErrorDialogMessageHandler.what + data = bundleOf( + SYNC_ERROR_DIALOG_TYPE_KEY to dialogType, + DIALOG_MESSAGE_KEY to dialogMessage + ) + } companion object { fun fromMessage(message: Message): SyncErrorDialogMessageHandler { - val dialogType = message.data.getInt("dialogType") - val dialogMessage = message.data.getString("dialogMessage") + val dialogType = Type.fromCode(message.data.getInt(SYNC_ERROR_DIALOG_TYPE_KEY)) + val dialogMessage = message.data.getString(DIALOG_MESSAGE_KEY) return SyncErrorDialogMessageHandler(dialogType, dialogMessage) } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/AsyncDialogFragmentsTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/AsyncDialogFragmentsTest.kt index 5f3dc9ec861b..baccbdd7a3e0 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/AsyncDialogFragmentsTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/AsyncDialogFragmentsTest.kt @@ -26,7 +26,7 @@ import org.junit.runner.RunWith class AsyncDialogFragmentsTest { @Test fun `SyncErrorDialog does not require context`() { - for (dialogType in SyncErrorDialog.dialogTypes) { + for (dialogType in SyncErrorDialog.Type.entries) { val instance = SyncErrorDialog.newInstance(dialogType, dialogMessage = null) assertDoesNotThrow("$dialogType message required a context") { instance.notificationMessage }