Skip to content

Commit

Permalink
refactor: DialogHandler
Browse files Browse the repository at this point in the history
Move serialisation + handling to classes instead of being in 2/3 places
(DialogHandler/DeckPicker/Dialog Class)

Keep enum: `WhichDialogHandler` to ensure that we do not reuse int values
  • Loading branch information
david-allison authored and mikehardy committed Mar 6, 2023
1 parent 38234eb commit d6bbea7
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 164 deletions.
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, Collec
} catch (e: IllegalStateException) {
Timber.w(e)
// Store a persistent message to SharedPreferences instructing AnkiDroid to show dialog
DialogHandler.storeMessage(newFragment.dialogHandlerMessage)
DialogHandler.storeMessage(newFragment.dialogHandlerMessage?.toMessage())
// Show a basic notification to the user in the notification bar in the meantime
val title = newFragment.notificationTitle
val message = newFragment.notificationMessage
Expand Down
56 changes: 45 additions & 11 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import androidx.core.content.edit
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
Expand Down Expand Up @@ -1186,26 +1187,20 @@ open class DeckPicker :
// If libanki determines it's necessary to confirm the full sync then show a confirmation dialog
// We have to show the dialog via the DialogHandler since this method is called via an async task
val res = resources
val handlerMessage = Message.obtain()
handlerMessage.what = DialogHandler.MSG_SHOW_FORCE_FULL_SYNC_DIALOG
val handlerMessageData = Bundle()
handlerMessageData.putString(
"message",
"""
val message = """
${res.getString(R.string.full_sync_confirmation_upgrade)}
${res.getString(R.string.full_sync_confirmation)}
""".trimIndent()
)
handlerMessage.data = handlerMessageData
dialogHandler.sendMessage(handlerMessage)
""".trimIndent()

dialogHandler.sendMessage(ForceFullSyncDialog(message).toMessage())
}
}
automaticSync()
}

private fun showCollectionErrorDialog() {
dialogHandler.sendEmptyMessage(DialogHandler.MSG_SHOW_COLLECTION_LOADING_ERROR_DIALOG)
dialogHandler.sendMessage(CollectionLoadingErrorDialog().toMessage())
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down Expand Up @@ -2674,3 +2669,42 @@ enum class SyncIconState {
* @param errList: a string describing the errors. Null if no error.
*/
data class ImporterData(val impList: List<AnkiPackageImporter>?, val errList: String?)

class CollectionLoadingErrorDialog : DialogHandlerMessage(
WhichDialogHandler.MSG_SHOW_COLLECTION_LOADING_ERROR_DIALOG,
"CollectionLoadErrorDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
// Collection could not be opened
deckPicker.showDatabaseErrorDialog(DatabaseErrorDialogType.DIALOG_LOAD_FAILED)
}

override fun toMessage() = emptyMessage(this.what)
}

class ForceFullSyncDialog(val message: String?) : DialogHandlerMessage(
which = WhichDialogHandler.MSG_SHOW_FORCE_FULL_SYNC_DIALOG,
analyticName = "ForceFullSyncDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
// Confirmation dialog for forcing full sync
val dialog = ConfirmationDialog()
val confirm = Runnable {
// Bypass the check once the user confirms
CollectionHelper.instance.getCol(AnkiDroidApp.instance)!!.modSchemaNoCheck()
}
dialog.setConfirm(confirm)
dialog.setArgs(message)
deckPicker.showDialogFragment(dialog)
}

override fun toMessage(): Message = Message.obtain().apply {
what = this@ForceFullSyncDialog.what
data = bundleOf("message" to message)
}

companion object {
fun fromMessage(message: Message): DialogHandlerMessage =
ForceFullSyncDialog(message.data.getString("message"))
}
}
46 changes: 41 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,24 @@ import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.core.content.FileProvider
import com.ichi2.anki.UIUtils.showThemedToast
import com.ichi2.anki.dialogs.DialogHandler
import com.ichi2.anki.dialogs.DialogHandler.Companion.storeMessage
import com.ichi2.anki.dialogs.DialogHandlerMessage
import com.ichi2.anki.servicelayer.ScopedStorageService
import com.ichi2.anki.services.ReminderService
import com.ichi2.themes.Themes.disableXiaomiForceDarkMode
import com.ichi2.utils.FileUtil
import com.ichi2.utils.ImportUtils.handleFileImport
import com.ichi2.utils.ImportUtils.isInvalidViewIntent
import com.ichi2.utils.ImportUtils.showImportUnsuccessfulDialog
import com.ichi2.utils.NetworkUtils
import com.ichi2.utils.Permissions.hasStorageAccessPermission
import com.ichi2.utils.copyToClipboard
import com.ichi2.utils.trimToLength
import timber.log.Timber
import java.io.File
import java.util.function.Consumer
import kotlin.math.max
import kotlin.math.min

/**
* Class which handles how the application responds to different intents, forcing it to always be single task,
Expand Down Expand Up @@ -209,11 +212,8 @@ class IntentHandler : Activity() {
* Send a Message to AnkiDroidApp so that the DialogMessageHandler forces a sync
*/
fun sendDoSyncMsg() {
// Create a new message for DialogHandler
val handlerMessage = Message.obtain()
handlerMessage.what = DialogHandler.MSG_DO_SYNC
// Store the message in AnkiDroidApp message holder, which is loaded later in AnkiActivity.onResume
storeMessage(handlerMessage)
storeMessage(DoSync().toMessage())
}

fun copyStringToClipboardIntent(context: Context, textToCopy: String) =
Expand All @@ -223,5 +223,41 @@ class IntentHandler : Activity() {
// 25000 * 2 (bytes per char) = 50,000 bytes <<< 500KB
it.putExtra(CLIPBOARD_INTENT_EXTRA_DATA, textToCopy.trimToLength(25000))
}

class DoSync : DialogHandlerMessage(
which = WhichDialogHandler.MSG_DO_SYNC,
analyticName = "DoSyncDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
val preferences = AnkiDroidApp.getSharedPrefs(deckPicker)
val res = deckPicker.resources
val hkey = preferences.getString("hkey", "")
val millisecondsSinceLastSync = millisecondsSinceLastSync(preferences)
val limited = millisecondsSinceLastSync < INTENT_SYNC_MIN_INTERVAL
if (!limited && hkey!!.isNotEmpty() && NetworkUtils.isOnline) {
deckPicker.sync()
} else {
val err = res.getString(R.string.sync_error)
if (limited) {
val remainingTimeInSeconds = max((INTENT_SYNC_MIN_INTERVAL - millisecondsSinceLastSync) / 1000, 1)
// getQuantityString needs an int
val remaining = min(Int.MAX_VALUE.toLong(), remainingTimeInSeconds).toInt()
val message = res.getQuantityString(R.plurals.sync_automatic_sync_needs_more_time, remaining, remaining)
deckPicker.showSimpleNotification(err, message, Channel.SYNC)
} else {
deckPicker.showSimpleNotification(err, res.getString(R.string.youre_offline), Channel.SYNC)
}
}
deckPicker.finishWithoutAnimation()
}

override fun toMessage(): Message = emptyMessage(this.what)

companion object {
const val INTENT_SYNC_MIN_INTERVAL = (
2 * 60000 // 2min minimum sync interval
).toLong()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
****************************************************************************************/
package com.ichi2.anki.dialogs

import android.os.Message
import com.ichi2.anki.analytics.AnalyticsDialogFragment

abstract class AsyncDialogFragment : AnalyticsDialogFragment() {
Expand All @@ -25,5 +24,5 @@ abstract class AsyncDialogFragment : AnalyticsDialogFragment() {
the onPostExecute() method of an AsyncTask */
abstract val notificationMessage: String?
abstract val notificationTitle: String
open val dialogHandlerMessage: Message? get() = null
open val dialogHandlerMessage: DialogHandlerMessage? get() = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.os.Bundle
import android.os.Message
import android.os.Parcelable
import android.view.KeyEvent
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.WhichButton
import com.afollestad.materialdialogs.actions.setActionButtonEnabled
Expand Down Expand Up @@ -406,15 +407,8 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
override val notificationMessage: String? get() = message
override val notificationTitle: String get() = resources.getString(R.string.answering_error_title)

override val dialogHandlerMessage: Message
get() {
val msg = Message.obtain()
msg.what = DialogHandler.MSG_SHOW_DATABASE_ERROR_DIALOG
val b = Bundle()
b.putParcelable("dialog", requireDialogType())
msg.data = b
return msg
}
override val dialogHandlerMessage
get() = ShowDatabaseErrorDialog(requireDialogType())

private fun requireDialogType() = requireArguments().getParcelableCompat<DatabaseErrorDialogType>("dialog")!!

Expand Down Expand Up @@ -462,4 +456,28 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
return f
}
}

/** Database error dialog */
class ShowDatabaseErrorDialog(val dialogType: DatabaseErrorDialogType) : DialogHandlerMessage(
which = WhichDialogHandler.MSG_SHOW_DATABASE_ERROR_DIALOG,
analyticName = "DatabaseErrorDialog"
) {
override fun handleAsyncMessage(deckPicker: DeckPicker) {
deckPicker.showDatabaseErrorDialog(dialogType)
}

override fun toMessage(): Message = Message.obtain().apply {
what = this@ShowDatabaseErrorDialog.what
data = bundleOf(
"dialog" to dialogType
)
}

companion object {
fun fromMessage(message: Message): ShowDatabaseErrorDialog {
val dialogType = message.data.getParcelableCompat<DatabaseErrorDialogType>("dialog")!!
return ShowDatabaseErrorDialog(dialogType)
}
}
}
}
Loading

0 comments on commit d6bbea7

Please sign in to comment.