Skip to content

Commit

Permalink
feat: Display custom flag names
Browse files Browse the repository at this point in the history
Anki's 'Browse' allows a user to rename flags

Support displaying these custom names
not setting them

Fixes 16204
  • Loading branch information
criticalAY authored and lukstbit committed Jun 2, 2024
1 parent a2a109c commit f8784da
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 279 deletions.
127 changes: 39 additions & 88 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,10 @@ open class CardBrowser :
// restore drawer click listener and icon
restoreDrawerIcon()
menuInflater.inflate(R.menu.card_browser, menu)
setFlagTitles(menu)
menu.findItem(R.id.action_search_by_flag).subMenu?.let {
subMenu ->
setupFlags(subMenu, Mode.SINGLE_SELECT)
}
saveSearchItem = menu.findItem(R.id.action_save_search)
saveSearchItem?.isVisible = false // the searchview's query always starts empty.
mySearchesItem = menu.findItem(R.id.action_list_my_searches)
Expand Down Expand Up @@ -777,7 +780,10 @@ open class CardBrowser :
} else {
// multi-select mode
menuInflater.inflate(R.menu.card_browser_multiselect, menu)
setMultiSelectFlagTitles(menu)
menu.findItem(R.id.action_flag).subMenu?.let {
subMenu ->
setupFlags(subMenu, Mode.MULTI_SELECT)
}
showBackIcon()
increaseHorizontalPaddingOfOverflowMenuIcons(menu)
}
Expand All @@ -795,6 +801,28 @@ open class CardBrowser :
return super.onCreateOptionsMenu(menu)
}

/**
* Representing different selection modes.
*/
enum class Mode(val value: Int) {
SINGLE_SELECT(1000),
MULTI_SELECT(1001)
}

private fun setupFlags(subMenu: SubMenu, mode: Mode) {
lifecycleScope.launch {
val groupId = when (mode) {
Mode.SINGLE_SELECT -> mode.value
Mode.MULTI_SELECT -> mode.value
}

for ((flag, displayName) in Flag.queryDisplayNames()) {
subMenu.add(groupId, flag.ordinal, Menu.NONE, displayName)
.setIcon(flag.drawableRes)
}
}
}

override fun onNavigationPressed() {
if (viewModel.isInMultiSelectMode) {
viewModel.endMultiSelectMode()
Expand All @@ -803,28 +831,6 @@ open class CardBrowser :
}
}

private fun setFlagTitles(menu: Menu) {
menu.findItem(R.id.action_select_flag_zero).title = Flag.NONE.displayName()
menu.findItem(R.id.action_select_flag_one).title = Flag.RED.displayName()
menu.findItem(R.id.action_select_flag_two).title = Flag.ORANGE.displayName()
menu.findItem(R.id.action_select_flag_three).title = Flag.GREEN.displayName()
menu.findItem(R.id.action_select_flag_four).title = Flag.BLUE.displayName()
menu.findItem(R.id.action_select_flag_five).title = Flag.PINK.displayName()
menu.findItem(R.id.action_select_flag_six).title = Flag.TURQUOISE.displayName()
menu.findItem(R.id.action_select_flag_seven).title = Flag.PURPLE.displayName()
}

private fun setMultiSelectFlagTitles(menu: Menu) {
menu.findItem(R.id.action_flag_zero).title = Flag.NONE.displayName()
menu.findItem(R.id.action_flag_one).title = Flag.RED.displayName()
menu.findItem(R.id.action_flag_two).title = Flag.ORANGE.displayName()
menu.findItem(R.id.action_flag_three).title = Flag.GREEN.displayName()
menu.findItem(R.id.action_flag_four).title = Flag.BLUE.displayName()
menu.findItem(R.id.action_flag_five).title = Flag.PINK.displayName()
menu.findItem(R.id.action_flag_six).title = Flag.TURQUOISE.displayName()
menu.findItem(R.id.action_flag_seven).title = Flag.PURPLE.displayName()
}

private fun updatePreviewMenuItem() {
previewItem?.isVisible = viewModel.rowCount > 0
}
Expand Down Expand Up @@ -931,6 +937,15 @@ open class CardBrowser :
undoSnackbar != null && undoSnackbar!!.isShown -> undoSnackbar!!.dismiss()
}

Flag.entries.find { it.ordinal == item.itemId }?.let { flag ->
when (item.groupId) {
Mode.SINGLE_SELECT.value -> filterByFlag(flag)
Mode.MULTI_SELECT.value -> updateFlagForSelectedRows(flag)
else -> return@let
}
return true
}

when (item.itemId) {
android.R.id.home -> {
viewModel.endMultiSelectMode()
Expand Down Expand Up @@ -993,70 +1008,6 @@ open class CardBrowser :
showFilterByTagsDialog()
return true
}
R.id.action_flag_zero -> {
updateFlagForSelectedRows(Flag.NONE)
return true
}
R.id.action_flag_one -> {
updateFlagForSelectedRows(Flag.RED)
return true
}
R.id.action_flag_two -> {
updateFlagForSelectedRows(Flag.ORANGE)
return true
}
R.id.action_flag_three -> {
updateFlagForSelectedRows(Flag.GREEN)
return true
}
R.id.action_flag_four -> {
updateFlagForSelectedRows(Flag.BLUE)
return true
}
R.id.action_flag_five -> {
updateFlagForSelectedRows(Flag.PINK)
return true
}
R.id.action_flag_six -> {
updateFlagForSelectedRows(Flag.TURQUOISE)
return true
}
R.id.action_flag_seven -> {
updateFlagForSelectedRows(Flag.PURPLE)
return true
}
R.id.action_select_flag_zero -> {
filterByFlag(Flag.NONE)
return true
}
R.id.action_select_flag_one -> {
filterByFlag(Flag.RED)
return true
}
R.id.action_select_flag_two -> {
filterByFlag(Flag.ORANGE)
return true
}
R.id.action_select_flag_three -> {
filterByFlag(Flag.GREEN)
return true
}
R.id.action_select_flag_four -> {
filterByFlag(Flag.BLUE)
return true
}
R.id.action_select_flag_five -> {
filterByFlag(Flag.PINK)
return true
}
R.id.action_select_flag_six -> {
filterByFlag(Flag.TURQUOISE)
return true
}
R.id.action_select_flag_seven -> {
filterByFlag(Flag.PURPLE)
return true
}
R.id.action_delete_card -> {
deleteSelectedNotes()
return true
Expand Down
45 changes: 44 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/Flag.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ package com.ichi2.anki
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.utils.ext.getStringOrNull
import com.ichi2.libanki.Card
import com.ichi2.libanki.CardId
import com.ichi2.libanki.Collection
import org.json.JSONObject

enum class Flag(val code: Int, @DrawableRes val drawableRes: Int, @ColorRes val browserColorRes: Int?) {
NONE(0, R.drawable.ic_flag_transparent, null),
Expand All @@ -32,7 +35,18 @@ enum class Flag(val code: Int, @DrawableRes val drawableRes: Int, @ColorRes val
TURQUOISE(6, R.drawable.ic_flag_turquoise, R.color.flag_turquoise),
PURPLE(7, R.drawable.ic_flag_purple, R.color.flag_purple);

fun displayName(): String = when (this) {
/**
* Retrieves the name associated with the flag. This may be user-defined
*
* @see queryDisplayNames - more efficient
*/
private fun displayName(labels: FlagLabels): String {
// NONE may not be renamed
if (this == NONE) return defaultDisplayName()
return labels.getLabel(this) ?: defaultDisplayName()
}

private fun defaultDisplayName(): String = when (this) {
NONE -> TR.browsingNoFlag()
RED -> TR.actionsFlagRed()
ORANGE -> TR.actionsFlagOrange()
Expand All @@ -47,7 +61,36 @@ enum class Flag(val code: Int, @DrawableRes val drawableRes: Int, @ColorRes val
fun fromCode(code: Int): Flag {
return entries.first { it.code == code }
}

/**
* @return A mapping from each [Flag] to its display name (optionally user-defined)
*/
suspend fun queryDisplayNames(): Map<Flag, String> {
// load user-defined flag labels from the collection
val labels = FlagLabels.loadFromColConfig()
// either map to user-provided name, or translated name
return Flag.entries.associateWith { it.displayName(labels) }
}
}
}

/**
* User-defined labels for flags. Stored in the collection optionally as `{ "1": "Redd" }`
* [Flag.NONE] does not have a label
*/
@JvmInline
private value class FlagLabels(val value: JSONObject) {
/**
* @return the user-defined label for the provided flag, or null if undefined
* This is not supported for [Flag.NONE] and is validated outside this method
*/
fun getLabel(flag: Flag): String? = value.getStringOrNull(flag.code.toString())

companion object {
suspend fun loadFromColConfig() =
FlagLabels(withCol { config.getObject("flagLabels", JSONObject()) })
}
}

fun Collection.setUserFlag(flag: Flag, cids: List<CardId>) = this.setUserFlag(flag.code, cids)
fun Card.setUserFlag(flag: Flag) = this.setUserFlag(flag.code)
69 changes: 23 additions & 46 deletions AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import anki.frontend.SetSchedulingStatesRequest
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anim.ActivityTransitionAnimation.getInverseTransition
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.Whiteboard.Companion.createInstance
import com.ichi2.anki.Whiteboard.OnPaintColorChangeListener
Expand Down Expand Up @@ -89,6 +89,7 @@ import com.ichi2.utils.HandlerUtils.getDefaultLooper
import com.ichi2.utils.Permissions.canRecordAudio
import com.ichi2.utils.ViewGroupUtils.setRenderWorkaround
import com.ichi2.widget.WidgetStatus.updateInBackground
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File

Expand Down Expand Up @@ -169,6 +170,8 @@ open class Reviewer :
FlashCardViewerResultCallback()
)

private val flagItemIds = mutableSetOf<Int>()

override fun onCreate(savedInstanceState: Bundle?) {
if (showedActivityFailedScreen(savedInstanceState)) {
return
Expand Down Expand Up @@ -353,6 +356,13 @@ open class Reviewer :
if (drawerToggle.onOptionsItemSelected(item)) {
return true
}

Flag.entries.find { it.ordinal == item.itemId }?.let { flag ->
Timber.i("Reviewer:: onOptionItemSelected Flag - ${flag.name} clicked")
onFlag(currentCard, flag)
return true
}

when (item.itemId) {
android.R.id.home -> {
Timber.i("Reviewer:: Home button pressed")
Expand Down Expand Up @@ -452,38 +462,6 @@ open class Reviewer :
Timber.i("Reviewer:: Add note button pressed")
addNote()
}
R.id.action_flag_zero -> {
Timber.i("Reviewer:: No flag")
onFlag(currentCard, Flag.NONE)
}
R.id.action_flag_one -> {
Timber.i("Reviewer:: Flag one")
onFlag(currentCard, Flag.RED)
}
R.id.action_flag_two -> {
Timber.i("Reviewer:: Flag two")
onFlag(currentCard, Flag.ORANGE)
}
R.id.action_flag_three -> {
Timber.i("Reviewer:: Flag three")
onFlag(currentCard, Flag.GREEN)
}
R.id.action_flag_four -> {
Timber.i("Reviewer:: Flag four")
onFlag(currentCard, Flag.BLUE)
}
R.id.action_flag_five -> {
Timber.i("Reviewer:: Flag five")
onFlag(currentCard, Flag.PINK)
}
R.id.action_flag_six -> {
Timber.i("Reviewer:: Flag six")
onFlag(currentCard, Flag.TURQUOISE)
}
R.id.action_flag_seven -> {
Timber.i("Reviewer:: Flag seven")
onFlag(currentCard, Flag.PURPLE)
}
R.id.action_card_info -> {
Timber.i("Card Viewer:: Card Info")
openCardInfo()
Expand Down Expand Up @@ -692,7 +670,7 @@ open class Reviewer :
Timber.d("onCreateOptionsMenu()")
// NOTE: This is called every time a new question is shown via invalidate options menu
menuInflater.inflate(R.menu.reviewer, menu)
setFlagTitles(menu)
menu.findItem(R.id.action_flag).subMenu?.let { subMenu -> setupFlags(subMenu) }
displayIcons(menu)
actionButtons.setCustomButtonsStatus(menu)
val alpha = Themes.ALPHA_ICON_ENABLED_LIGHT
Expand Down Expand Up @@ -844,20 +822,19 @@ open class Reviewer :
onboarding.onCreate()

increaseHorizontalPaddingOfOverflowMenuIcons(menu)
tintOverflowMenuIcons(menu, skipIf = { isFlagResource(it.itemId) })
tintOverflowMenuIcons(menu, skipIf = { isFlagItem(it) })

return super.onCreateOptionsMenu(menu)
}

private fun setFlagTitles(menu: Menu) {
menu.findItem(R.id.action_flag_zero).title = Flag.NONE.displayName()
menu.findItem(R.id.action_flag_one).title = Flag.RED.displayName()
menu.findItem(R.id.action_flag_two).title = Flag.ORANGE.displayName()
menu.findItem(R.id.action_flag_three).title = Flag.GREEN.displayName()
menu.findItem(R.id.action_flag_four).title = Flag.BLUE.displayName()
menu.findItem(R.id.action_flag_five).title = Flag.PINK.displayName()
menu.findItem(R.id.action_flag_six).title = Flag.TURQUOISE.displayName()
menu.findItem(R.id.action_flag_seven).title = Flag.PURPLE.displayName()
private fun setupFlags(subMenu: SubMenu) {
lifecycleScope.launch {
for ((flag, displayName) in Flag.queryDisplayNames()) {
val menuItem = subMenu.add(Menu.NONE, flag.ordinal, Menu.NONE, displayName)
.setIcon(flag.drawableRes)
flagItemIds.add(menuItem.itemId)
}
}
}

@SuppressLint("RestrictedApi")
Expand All @@ -873,8 +850,8 @@ open class Reviewer :
}
}

private fun isFlagResource(itemId: Int): Boolean {
return itemId == R.id.action_flag_seven || itemId == R.id.action_flag_six || itemId == R.id.action_flag_five || itemId == R.id.action_flag_four || itemId == R.id.action_flag_three || itemId == R.id.action_flag_two || itemId == R.id.action_flag_one
private fun isFlagItem(menuItem: MenuItem): Boolean {
return flagItemIds.contains(menuItem.itemId)
}

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
Expand Down
Loading

0 comments on commit f8784da

Please sign in to comment.