Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: DialogHandler #13359

Merged
merged 1 commit into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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