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

More backend integration work (undo, progress in GUI, cancellation, refactoring) #11740

Merged
merged 15 commits into from
Jul 22, 2022
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
51 changes: 40 additions & 11 deletions AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.webkit.WebViewAssetLoader
import anki.collection.OpChanges
import com.afollestad.materialdialogs.MaterialDialog
import com.drakeet.drawer.FullDraggableContainer
import com.google.android.material.snackbar.Snackbar
Expand Down Expand Up @@ -98,6 +99,8 @@ import com.ichi2.utils.HashUtil.HashSetInit
import com.ichi2.utils.KotlinCleanup
import com.ichi2.utils.MaxExecFunction
import com.ichi2.utils.WebViewDebugging.initializeDebugging
import kotlinx.coroutines.Job
import net.ankiweb.rsdroid.BackendFactory
import net.ankiweb.rsdroid.RustCleanup
import timber.log.Timber
import java.io.*
Expand All @@ -120,7 +123,8 @@ abstract class AbstractFlashcardViewer :
TagsDialogListener,
WhiteboardMultiTouchMethods,
AutomaticallyAnswered,
OnPageFinishedCallback {
OnPageFinishedCallback,
ChangeManager.Subscriber {
private var mTtsInitialized = false
private var mReplayOnTtsInit = false
private var mAnkiDroidJsAPI: AnkiDroidJsAPI? = null
Expand Down Expand Up @@ -277,6 +281,10 @@ abstract class AbstractFlashcardViewer :
displayCardAnswer()
}

init {
ChangeManager.subscribe(this)
}

// Event handler for eases (answer buttons)
inner class SelectEaseHandler : View.OnClickListener, OnTouchListener {
private var mPrevCard: Card? = null
Expand Down Expand Up @@ -826,21 +834,33 @@ abstract class AbstractFlashcardViewer :
}
}

open fun undo() {
open fun undo(): Job? {
if (isUndoAvailable) {
val res = resources
val undoName = col.undoName(res)
Undo().runWithHandler(
answerCardHandler(false)
.alsoExecuteAfter {
showThemedToast(
this@AbstractFlashcardViewer,
res.getString(R.string.undo_succeeded, undoName),
true
)
fun legacyUndo() {
Undo().runWithHandler(
answerCardHandler(false)
.alsoExecuteAfter {
showThemedToast(
this@AbstractFlashcardViewer,
res.getString(R.string.undo_succeeded, undoName),
true
)
}
)
}
if (BackendFactory.defaultLegacySchema) {
legacyUndo()
} else {
return launchCatchingCollectionTask { col ->
if (!backendUndoAndShowPopup(col)) {
legacyUndo()
}
)
}
}
}
return null
}

private fun finishNoStorageAvailable() {
Expand Down Expand Up @@ -2559,6 +2579,15 @@ abstract class AbstractFlashcardViewer :
return AnkiDroidJsAPI(this)
}

override fun opExecuted(changes: OpChanges, handler: Any?) {
if ((changes.studyQueues || changes.noteText || changes.card) && handler !== this) {
// executing this only for the refresh side effects; there may be a better way
Undo().runWithHandler(
answerCardHandler(false)
)
}
}

companion object {
/**
* Result codes that are returned when this activity finishes.
Expand Down
30 changes: 14 additions & 16 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendBackups.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ import com.ichi2.libanki.CollectionV16
import com.ichi2.libanki.awaitBackupCompletion
import com.ichi2.libanki.createBackup
import kotlinx.coroutines.*
import timber.log.Timber

fun DeckPicker.performBackupInBackground() {
val col = CollectionHelper.getInstance().getCol(baseContext).newBackend
catchingLifecycleScope(this) {
launchCatchingCollectionTask { col ->
// Wait a second to allow the deck list to finish loading first, or it
// will hang until the first stage of the backup completes.
delay(1000)
Expand All @@ -35,21 +33,21 @@ fun DeckPicker.performBackupInBackground() {
}

fun DeckPicker.importColpkg(colpkgPath: String) {
val deckPicker = this
catchingLifecycleScope(this) {
runInBackground {
val helper = CollectionHelper.getInstance()
val backend = helper.getOrCreateBackend(baseContext)
backend.withProgress({
if (it.hasImporting()) {
// TODO: show progress in GUI
Timber.i("%s", it.importing)
launchCatchingTask {
val helper = CollectionHelper.getInstance()
val backend = helper.getOrCreateBackend(baseContext)
runInBackgroundWithProgress(
backend,
extractProgress = {
if (progress.hasImporting()) {
text = progress.importing
}
}) {
helper.importColpkg(baseContext, colpkgPath)
}
},
) {
helper.importColpkg(baseContext, colpkgPath)
}
deckPicker.updateDeckList()
invalidateOptionsMenu()
updateDeckList()
}
}

Expand Down
46 changes: 25 additions & 21 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendImporting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@ package com.ichi2.anki
import anki.import_export.ImportResponse
import com.ichi2.libanki.exportAnkiPackage
import com.ichi2.libanki.importAnkiPackage
import com.ichi2.libanki.undoableOp
import net.ankiweb.rsdroid.Translations
import timber.log.Timber

fun DeckPicker.importApkg(apkgPath: String) {
val deckPicker = this
val col = CollectionHelper.getInstance().getCol(deckPicker.baseContext).newBackend
catchingLifecycleScope(this) {
val report = col.opWithProgress({
if (it.hasImporting()) {
// TODO: show progress in GUI
Timber.i("%s", it.importing)
fun DeckPicker.importApkgs(apkgPaths: List<String>) {
launchCatchingCollectionTask { col ->
for (apkgPath in apkgPaths) {
val report = runInBackgroundWithProgress(
col.backend,
extractProgress = {
if (progress.hasImporting()) {
text = progress.importing
}
},
) {
undoableOp {
col.importAnkiPackage(apkgPath)
}
}
}) {
importAnkiPackage(apkgPath)
showSimpleMessageDialog(summarizeReport(col.tr, report))
}
showSimpleMessageDialog(summarizeReport(col.tr, report))
}
}

Expand Down Expand Up @@ -65,15 +69,15 @@ fun DeckPicker.exportApkg(
withMedia: Boolean,
deckId: Long?
) {
val deckPicker = this
val col = CollectionHelper.getInstance().getCol(deckPicker.baseContext).newBackend
catchingLifecycleScope(this) {
runInBackgroundWithProgress(col, {
if (it.hasExporting()) {
// TODO: show progress in GUI
Timber.i("%s", it.exporting)
}
}) {
launchCatchingCollectionTask { col ->
runInBackgroundWithProgress(
col.backend,
extractProgress = {
if (progress.hasExporting()) {
text = progress.exporting
}
},
) {
col.exportAnkiPackage(apkgPath, withScheduling, withMedia, deckId)
}
}
Expand Down
48 changes: 48 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/BackendUndo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/***************************************************************************************
* Copyright (c) 2022 Ankitects Pty Ltd <https://apps.ankiweb.net> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/

package com.ichi2.anki

import com.ichi2.anki.UIUtils.showSimpleSnackbar
import com.ichi2.libanki.CollectionV16
import com.ichi2.libanki.undoNew
import com.ichi2.libanki.undoableOp
import com.ichi2.utils.BlocksSchemaUpgrade
import net.ankiweb.rsdroid.BackendException

suspend fun AnkiActivity.backendUndoAndShowPopup(col: CollectionV16): Boolean {
return try {
val changes = runInBackgroundWithProgress() {
undoableOp {
col.undoNew()
}
}
showSimpleSnackbar(
this,
col.tr.undoActionUndone(changes.operation),
false
)
true
} catch (exc: BackendException) {
@BlocksSchemaUpgrade("Backend module should export this as a separate Exception")
if (exc.localizedMessage == "UndoEmpty") {
// backend undo queue empty
false
} else {
throw exc
}
}
}
36 changes: 34 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.SearchView
import anki.collection.OpChanges
import com.afollestad.materialdialogs.list.SingleChoiceListener
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
Expand Down Expand Up @@ -97,6 +98,7 @@ import com.ichi2.utils.HashUtil.HashMapInit
import com.ichi2.utils.Permissions.hasStorageAccessPermission
import com.ichi2.utils.TagsUtil.getUpdatedTags
import com.ichi2.widget.WidgetStatus.update
import net.ankiweb.rsdroid.BackendFactory
import net.ankiweb.rsdroid.RustCleanup
import timber.log.Timber
import java.lang.Exception
Expand All @@ -113,7 +115,12 @@ import kotlin.math.min
@Suppress("LeakingThis") // The class is only 'open' due to testing
@KotlinCleanup("scan through this class and add attributes - not started")
@KotlinCleanup("Add TextUtils.isNotNullOrEmpty accepting nulls and use it. Remove TextUtils import")
open class CardBrowser : NavigationDrawerActivity(), SubtitleListener, DeckSelectionListener, TagsDialogListener {
open class CardBrowser :
NavigationDrawerActivity(),
SubtitleListener,
DeckSelectionListener,
TagsDialogListener,
ChangeManager.Subscriber {
@KotlinCleanup("using ?. and let keyword would be good here")
override fun onDeckSelected(deck: SelectableDeck?) {
if (deck == null) {
Expand Down Expand Up @@ -256,6 +263,10 @@ open class CardBrowser : NavigationDrawerActivity(), SubtitleListener, DeckSelec
private var mUnmountReceiver: BroadcastReceiver? = null
private val orderSingleChoiceDialogListener: SingleChoiceListener = { _, index: Int, _ -> changeCardOrder(index) }

init {
ChangeManager.subscribe(this)
}

@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
fun changeCardOrder(which: Int) {
if (which != mOrder) {
Expand Down Expand Up @@ -1271,7 +1282,15 @@ open class CardBrowser : NavigationDrawerActivity(), SubtitleListener, DeckSelec
@VisibleForTesting
fun onUndo() {
if (col.undoAvailable()) {
Undo().runWithHandler(mUndoHandler)
if (BackendFactory.defaultLegacySchema) {
Undo().runWithHandler(mUndoHandler)
} else {
launchCatchingCollectionTask { col ->
if (!backendUndoAndShowPopup(col)) {
Undo().runWithHandler(mUndoHandler)
}
}
}
}
}

Expand Down Expand Up @@ -2628,6 +2647,19 @@ open class CardBrowser : NavigationDrawerActivity(), SubtitleListener, DeckSelec
searchCards()
}

override fun opExecuted(changes: OpChanges, handler: Any?) {
if ((
changes.browserSidebar ||
changes.browserTable ||
changes.noteText ||
changes.card
) && handler !== this
) {
// executing this only for the refresh side effects; there may be a better way
Undo().runWithHandler(mUndoHandler)
}
}

companion object {
@JvmField
var sCardBrowserCard: Card? = null
Expand Down
Loading