diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
index 6f61c04ddaec..b745ea4ec091 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
@@ -800,23 +800,32 @@ open class DeckPicker :
Timber.d("onCreateOptionsMenu()")
floatingActionMenu.closeFloatingActionMenu(applyRiseAndShrinkAnimation = false)
menuInflater.inflate(R.menu.deck_picker, menu)
- menu.findItem(R.id.action_export)?.title = TR.exportingExport()
- setupMediaSyncMenuItem(menu)
menu.findItem(R.id.deck_picker_action_filter)?.let {
toolbarSearchItem = it
setupSearchIcon(it)
toolbarSearchView = it.actionView as SearchView
}
toolbarSearchView?.maxWidth = Integer.MAX_VALUE
- // redraw menu synchronously to avoid flicker
- updateMenuFromState(menu)
+
+ if (fragmented && studyoptionsFrame!!.visibility == View.VISIBLE) {
+ menu.setGroupVisible(R.id.allItems, false)
+ } else {
+ menu.findItem(R.id.action_export)?.title = TR.exportingExport()
+ setupMediaSyncMenuItem(menu)
+ // redraw menu synchronously to avoid flicker
+ updateMenuFromState(menu)
+ }
// ...then launch a task to possibly update the visible icons.
// Store the job so that tests can easily await it. In the future
// this may be better done by injecting a custom test scheduler
// into CollectionManager, and awaiting that.
createMenuJob = launchCatchingTask {
updateMenuState()
- updateMenuFromState(menu)
+ if (fragmented) {
+ updateSearchFromState(menu)
+ } else {
+ updateMenuFromState(menu)
+ }
}
return super.onCreateOptionsMenu(menu)
}
@@ -824,7 +833,7 @@ open class DeckPicker :
private var migrationProgressPublishingJob: Job? = null
private var cachedMigrationProgressMenuItemActionView: View? = null
- private fun setupMediaSyncMenuItem(menu: Menu) {
+ public fun setupMediaSyncMenuItem(menu: Menu) {
// shouldn't be necessary, but `invalidateOptionsMenu()` is called way more than necessary
syncMediaProgressJob?.cancel()
@@ -970,7 +979,7 @@ open class DeckPicker :
searchDecksIcon = menuItem
}
- private fun updateMenuFromState(menu: Menu) {
+ fun updateMenuFromState(menu: Menu) {
menu.setGroupVisible(R.id.allItems, optionsMenuState != null)
optionsMenuState?.run {
menu.findItem(R.id.deck_picker_action_filter).isVisible = searchIcon
@@ -981,6 +990,12 @@ open class DeckPicker :
}
}
+ private fun updateSearchFromState(menu: Menu) {
+ optionsMenuState?.run {
+ menu.findItem(R.id.deck_picker_action_filter).isVisible = searchIcon
+ }
+ }
+
private fun updateUndoLabelFromState(menuItem: MenuItem, undoLabel: String?) {
menuItem.run {
if (undoLabel != null) {
@@ -1639,7 +1654,7 @@ open class DeckPicker :
return previous
}
- private fun undo() {
+ fun undo() {
launchCatchingTask {
undoAndShowSnackbar()
}
@@ -2072,7 +2087,10 @@ open class DeckPicker :
Timber.i("Updating deck list UI")
hideProgressBar()
// Make sure the fragment is visible
- if (fragmented) {
+ if (fragmented && collectionIsEmpty) {
+ studyoptionsFrame!!.visibility = View.GONE
+ invalidateOptionsMenu()
+ } else if (fragmented) {
studyoptionsFrame!!.visibility = View.VISIBLE
}
dueTree = result
@@ -2321,7 +2339,7 @@ open class DeckPicker :
updateDeckList()
}
- private fun handleEmptyCards() {
+ fun handleEmptyCards() {
launchCatchingTask {
val emptyCids = withProgress(R.string.emtpy_cards_finding) {
withCol {
@@ -2522,7 +2540,7 @@ open class DeckPicker :
* @return shownAutomatically `true` if the dialog was shown automatically, `false` if the user
* pressed a button to open the dialog
*/
- private fun showDialogThatOffersToMigrateStorage(shownAutomatically: Boolean) {
+ fun showDialogThatOffersToMigrateStorage(shownAutomatically: Boolean) {
Timber.i("Displaying dialog to migrate storage")
if (mediaMigrationIsInProgress(baseContext)) {
// This should not occur. We should have not called the function in this case.
@@ -2579,7 +2597,7 @@ open class DeckPicker :
dialog.addScopedStorageLearnMoreLinkAndShow(message)
}
- private fun showDialogThatOffersToResumeMigrationAfterError(errorText: String) {
+ fun showDialogThatOffersToResumeMigrationAfterError(errorText: String) {
val helpUrl = getString(R.string.link_migration_failed_dialog_learn_more_en)
val message = getString(R.string.migration__resume_after_failed_dialog__message, errorText, helpUrl)
.parseAsHtml()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
index 0420a859afee..f05ad6a7e60e 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
@@ -35,7 +35,12 @@ import androidx.core.view.MenuItemCompat
import androidx.fragment.app.Fragment
import anki.collection.OpChanges
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.dialogs.DatabaseErrorDialog
+import com.ichi2.anki.dialogs.MediaCheckDialog
import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog
+import com.ichi2.anki.notetype.ManageNotetypes
+import com.ichi2.anki.services.MediaMigrationState
+import com.ichi2.anki.services.getMediaMigrationState
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.ext.description
import com.ichi2.annotations.NeedsTest
@@ -81,6 +86,9 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
private lateinit var textTotal: TextView
private var toolbar: Toolbar? = null
+ @VisibleForTesting
+ var createMenuJob: Job? = null
+
// Flag to indicate if the fragment should load the deck options immediately after it loads
private var loadWithDeckOptions = false
private var fragmented = false
@@ -223,7 +231,7 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.action_undo -> {
+ R.id.action_undo1 -> {
Timber.i("StudyOptionsFragment:: Undo button pressed")
launchCatchingTask {
requireActivity().undoAndShowSnackbar()
@@ -278,6 +286,66 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
(activity as DeckPicker).exportDeck(col!!.decks.selected())
return true
}
+ R.id.action_undo -> {
+ Timber.i("DeckPicker:: Undo button pressed")
+ (activity as DeckPicker).undo()
+ return true
+ }
+ R.id.action_sync -> {
+ Timber.i("DeckPicker:: Sync button pressed")
+ val actionProvider = MenuItemCompat.getActionProvider(item) as? SyncActionProvider
+ if (actionProvider?.isProgressShown == true) {
+ launchCatchingTask {
+ monitorMediaSync(requireContext())
+ }
+ } else {
+ (activity as DeckPicker).sync()
+ }
+ return true
+ }
+ R.id.action_scoped_storage_migrate -> {
+ Timber.i("DeckPicker:: migrate button pressed")
+ val migrationState = (activity as DeckPicker).getMediaMigrationState()
+ if (migrationState is MediaMigrationState.Ongoing.PausedDueToError) {
+ (activity as DeckPicker).showDialogThatOffersToResumeMigrationAfterError(migrationState.errorText)
+ } else {
+ (activity as DeckPicker).showDialogThatOffersToMigrateStorage(shownAutomatically = false)
+ }
+ return true
+ }
+ R.id.action_import -> {
+ Timber.i("DeckPicker:: Import button pressed")
+ (activity as DeckPicker).showImportDialog()
+ return true
+ }
+ R.id.action_check_database -> {
+ Timber.i("DeckPicker:: Check database button pressed")
+ (activity as DeckPicker).showDatabaseErrorDialog(DatabaseErrorDialog.DatabaseErrorDialogType.DIALOG_CONFIRM_DATABASE_CHECK)
+ return true
+ }
+ R.id.action_check_media -> {
+ Timber.i("DeckPicker:: Check media button pressed")
+ (activity as DeckPicker).showMediaCheckDialog(MediaCheckDialog.DIALOG_CONFIRM_MEDIA_CHECK)
+ return true
+ }
+ R.id.action_empty_cards -> {
+ Timber.i("DeckPicker:: Empty cards button pressed")
+ (activity as DeckPicker).handleEmptyCards()
+ return true
+ }
+ R.id.action_model_browser_open -> {
+ Timber.i("DeckPicker:: Model browser button pressed")
+ val manageNoteTypesTarget =
+ ManageNotetypes::class.java
+ val noteTypeBrowser = Intent(requireContext(), manageNoteTypesTarget)
+ startActivity(noteTypeBrowser)
+ return true
+ }
+ R.id.action_restore_backup -> {
+ Timber.i("DeckPicker:: Restore from backup button pressed")
+ (activity as DeckPicker).showDatabaseErrorDialog(DatabaseErrorDialog.DatabaseErrorDialogType.DIALOG_CONFIRM_RESTORE_BACKUP)
+ return true
+ }
else -> return false
}
}
@@ -335,9 +403,17 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
}
// Switch on rename / delete / export if tablet layout
if (fragmented) {
+ menu.setGroupVisible(R.id.allItems, true)
menu.findItem(R.id.action_rename).isVisible = true
menu.findItem(R.id.action_delete).isVisible = true
menu.findItem(R.id.action_export).isVisible = true
+ (activity as DeckPicker).setupMediaSyncMenuItem(menu)
+ (activity as DeckPicker).updateMenuFromState(menu)
+
+ createMenuJob = launchCatchingTask {
+ (activity as DeckPicker).updateMenuState()
+ (activity as DeckPicker).updateMenuFromState(menu)
+ }
} else {
menu.findItem(R.id.action_rename).isVisible = false
menu.findItem(R.id.action_delete).isVisible = false
@@ -347,15 +423,15 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
menu.findItem(R.id.action_unbury).isVisible = col != null && col!!.sched.haveBuried()
// Set the proper click target for the undo button's ActionProvider
val undoActionProvider: RtlCompliantActionProvider? = MenuItemCompat.getActionProvider(
- menu.findItem(R.id.action_undo)
+ menu.findItem(R.id.action_undo1)
) as? RtlCompliantActionProvider
undoActionProvider?.clickHandler = { _, menuItem -> onMenuItemClick(menuItem) }
// Switch on or off undo depending on whether undo is available
if (col == null || !col!!.undoAvailable()) {
- menu.findItem(R.id.action_undo).isVisible = false
+ menu.findItem(R.id.action_undo1).isVisible = false
} else {
- menu.findItem(R.id.action_undo).isVisible = true
- menu.findItem(R.id.action_undo).title = col?.undoLabel()
+ menu.findItem(R.id.action_undo1).isVisible = true
+ menu.findItem(R.id.action_undo1).title = col?.undoLabel()
}
// Set the back button listener
if (!fragmented) {
@@ -363,6 +439,8 @@ class StudyOptionsFragment : Fragment(), ChangeManager.Subscriber, Toolbar.OnMen
icon!!.isAutoMirrored = true
toolbar!!.navigationIcon = icon
toolbar!!.setNavigationOnClickListener { (activity as AnkiActivity).finish() }
+ } else {
+ toolbar!!.navigationIcon = null
}
} catch (e: IllegalStateException) {
if (!CollectionManager.isOpenUnsafe()) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
index dda0027c33b6..b482f8e5e7d4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
@@ -16,6 +16,7 @@
package com.ichi2.anki
+import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.SharedPreferences
@@ -353,13 +354,13 @@ fun DeckPicker.shouldFetchMedia(preferences: SharedPreferences): Boolean {
}
suspend fun monitorMediaSync(
- deckPicker: DeckPicker
+ context: Context
) {
val backend = CollectionManager.getBackend()
val scope = CoroutineScope(Dispatchers.IO)
val dialog = withContext(Dispatchers.Main) {
- AlertDialog.Builder(deckPicker)
+ AlertDialog.Builder(context)
.setTitle(TR.syncMediaLogTitle())
.setMessage("")
.setPositiveButton(R.string.dialog_continue) { _, _ ->
@@ -371,7 +372,7 @@ suspend fun monitorMediaSync(
.show()
}
- fun showMessage(msg: String) = deckPicker.showSnackbar(msg, Snackbar.LENGTH_SHORT)
+ fun showMessage(msg: String) = (context as Activity).showSnackbar(msg, Snackbar.LENGTH_SHORT)
scope.launch {
try {
diff --git a/AnkiDroid/src/main/res/menu/deck_picker.xml b/AnkiDroid/src/main/res/menu/deck_picker.xml
index 2e0aeb2758cb..63903ca4d933 100644
--- a/AnkiDroid/src/main/res/menu/deck_picker.xml
+++ b/AnkiDroid/src/main/res/menu/deck_picker.xml
@@ -2,13 +2,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ankidroid="http://schemas.android.com/apk/res-auto"
tools:context=".DeckPicker">
+
-
-
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+