Skip to content

Commit

Permalink
simplify deckpicker with unified menu
Browse files Browse the repository at this point in the history
  • Loading branch information
SanjaySargam committed May 18, 2024
1 parent e2bb9c8 commit aa849fa
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 28 deletions.
42 changes: 30 additions & 12 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -800,31 +800,40 @@ 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)
}

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()

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -1639,7 +1654,7 @@ open class DeckPicker :
return previous
}

private fun undo() {
fun undo() {
launchCatchingTask {
undoAndShowSnackbar()
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2321,7 +2339,7 @@ open class DeckPicker :
updateDeckList()
}

private fun handleEmptyCards() {
fun handleEmptyCards() {
launchCatchingTask {
val emptyCids = withProgress(R.string.emtpy_cards_finding) {
withCol {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
88 changes: 83 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
Expand All @@ -347,22 +423,24 @@ 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) {
val icon = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_arrow_back_white)
icon!!.isAutoMirrored = true
toolbar!!.navigationIcon = icon
toolbar!!.setNavigationOnClickListener { (activity as AnkiActivity).finish() }
} else {
toolbar!!.navigationIcon = null
}
} catch (e: IllegalStateException) {
if (!CollectionManager.isOpenUnsafe()) {
Expand Down
7 changes: 4 additions & 3 deletions AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) { _, _ ->
Expand All @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions AnkiDroid/src/main/res/menu/deck_picker.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<item
android:id="@+id/deck_picker_action_filter"
android:icon="@drawable/ic_search_white"
android:title="@string/search_decks"
ankidroid:actionViewClass="androidx.appcompat.widget.SearchView"
ankidroid:showAsAction="always|collapseActionView"/>
<group android:id="@+id/allItems">
<item
android:id="@+id/deck_picker_action_filter"
android:icon="@drawable/ic_search_white"
android:title="@string/search_decks"
ankidroid:actionViewClass="androidx.appcompat.widget.SearchView"
ankidroid:showAsAction="always|collapseActionView"/>
<item
android:id="@+id/action_migration_progress"
android:visible="false"
Expand Down
70 changes: 68 additions & 2 deletions AnkiDroid/src/main/res/menu/study_options_fragment.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ankidroid="http://schemas.android.com/apk/res-auto" >
<item
android:id="@+id/action_undo"
android:id="@+id/action_undo1"
android:enabled="true"
android:icon="@drawable/ic_undo_white"
android:title="@string/undo"
Expand Down Expand Up @@ -46,4 +47,69 @@
android:id="@+id/action_unbury"
android:title="@string/unbury"
android:visibility = "gone"/>
<group android:id="@+id/allItems" android:visible="false">
<item
android:id="@+id/deck_picker_action_filter"
android:icon="@drawable/ic_search_white"
android:title="@string/search_decks"
ankidroid:actionViewClass="androidx.appcompat.widget.SearchView"
ankidroid:showAsAction="always|collapseActionView"/>
<item
android:id="@+id/action_migration_progress"
android:visible="false"
android:title="@string/show_migration_progress"
ankidroid:actionLayout="@layout/migration_progress_menu_layout"
ankidroid:showAsAction="always" />
<!--
We want sync on the right, to be consistent with past version: #7737.
We're OK to do this as we're using "always" so it won't be hidden
-->
<item
android:id="@+id/action_scoped_storage_migrate"
android:icon="@drawable/ic_migrate"
android:title="@string/button_upgrade"
ankidroid:showAsAction="always"/>
<item
android:id="@+id/action_sync"
android:icon="@drawable/ic_sync"
android:title="@string/button_sync"
ankidroid:actionProviderClass="com.ichi2.anki.SyncActionProvider"
ankidroid:showAsAction="always"/>
<item
android:id="@+id/action_undo"
ankidroid:showAsAction="never"
android:visible="false"
tools:title="Undo X"/>
<item
android:id="@+id/checks"
android:title="@string/checks_action"
ankidroid:showAsAction="never">
<menu>
<item
android:id="@+id/action_check_database"
android:menuCategory="secondary"
android:title="@string/check_db"/>
<item
android:id="@+id/action_check_media"
android:menuCategory="secondary"
android:title="@string/check_media"/>
<item
android:id="@+id/action_empty_cards"
android:menuCategory="secondary"
android:title="@string/empty_cards"/>
</menu>
</item>
<item
android:id="@+id/action_restore_backup"
android:menuCategory="secondary"
android:title="@string/backup_restore"/>
<item
android:id="@+id/action_model_browser_open"
android:menuCategory="secondary"
android:title="@string/model_browser_label"/>
<item
android:id="@+id/action_import"
android:menuCategory="secondary"
android:title="@string/menu_import"/>
</group>
</menu>

0 comments on commit aa849fa

Please sign in to comment.