From df10031dac7bcdbb9640d51bd9b9040de87ed315 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Fri, 3 Nov 2023 12:03:49 +0000 Subject: [PATCH] feat: Preference - Show audio play buttons Functionality added in Anki Desktop Fixes 14649 --- .../com/ichi2/anki/AbstractFlashcardViewer.kt | 2 +- .../ichi2/anki/analytics/UsageAnalytics.kt | 1 + .../ichi2/anki/cardviewer/HtmlGenerator.kt | 14 +++++- .../preferences/AppearanceSettingsFragment.kt | 11 +++++ AnkiDroid/src/main/res/values/preferences.xml | 1 + .../main/res/xml/preferences_appearance.xml | 6 +++ .../ichi2/anki/AbstractFlashcardViewerTest.kt | 46 +++++++++++++++++++ .../java/com/ichi2/anki/RobolectricTest.kt | 7 +++ 8 files changed, 85 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt index 85ea4e2f8297..786760c405ae 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt @@ -538,7 +538,7 @@ abstract class AbstractFlashcardViewer : registerExternalStorageListener() restoreCollectionPreferences(col) initLayout() - mHtmlGenerator = createInstance(this, typeAnswer!!) + mHtmlGenerator = createInstance(this, col, typeAnswer!!) // Initialize text-to-speech. This is an asynchronous operation. mTTS.initialize(this, ReadTextListener()) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt index 8b6894a2cdde..a55e26f88834 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt @@ -487,6 +487,7 @@ object UsageAnalytics { "showTopbar", // Show top bar "showProgress", // Show remaining "showETA", // Show ETA + "showAudioPlayButtons", // Show play buttons on cards with audio (reversed in collection: HIDE_AUDIO_PLAY_BUTTONS) "card_browser_show_media_filenames", // Display filenames in card browser // Controls "gestures", // Enable gestures diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/HtmlGenerator.kt b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/HtmlGenerator.kt index b512edc6bb5d..9084874d2c15 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/HtmlGenerator.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/HtmlGenerator.kt @@ -19,10 +19,13 @@ package com.ichi2.anki.cardviewer import android.content.Context import android.content.res.Resources import androidx.annotation.CheckResult +import anki.config.ConfigKey import com.ichi2.anki.preferences.sharedPrefs import com.ichi2.anki.reviewer.ReviewerCustomFonts import com.ichi2.libanki.Card +import com.ichi2.libanki.Collection import com.ichi2.libanki.Sound +import com.ichi2.libanki.stripAvRefs import timber.log.Timber import java.io.BufferedReader import java.io.IOException @@ -33,6 +36,7 @@ class HtmlGenerator( private val typeAnswer: TypeAnswer, val cardAppearance: CardAppearance, val cardTemplate: CardTemplate, + private val showAudioPlayButtons: Boolean, val resources: Resources ) { @@ -49,22 +53,28 @@ class HtmlGenerator( } fun expandSounds(content: String): String { - return Sound.expandSounds(content) + return if (showAudioPlayButtons) { + Sound.expandSounds(content) + } else { + stripAvRefs(content) + } } companion object { fun createInstance( context: Context, + col: Collection, typeAnswer: TypeAnswer ): HtmlGenerator { val preferences = context.sharedPrefs() val cardAppearance = CardAppearance.create(ReviewerCustomFonts(), preferences) val cardHtmlTemplate = loadCardTemplate(context) - + val showAudioPlayButtons = !col.config.getBool(ConfigKey.Bool.HIDE_AUDIO_PLAY_BUTTONS) return HtmlGenerator( typeAnswer, cardAppearance, cardHtmlTemplate, + showAudioPlayButtons, context.resources ) } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt index ff91cb74d53c..bfc1f401f228 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt @@ -22,6 +22,7 @@ import androidx.core.app.ActivityCompat import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat +import anki.config.ConfigKey import com.ichi2.anki.* import com.ichi2.anki.CollectionManager.withCol import com.ichi2.anki.snackbar.showSnackbar @@ -141,6 +142,16 @@ class AppearanceSettingsFragment : SettingsFragment() { true } } + + // Show play buttons on cards with audio + // Note: Stored inverted in the collection as HIDE_AUDIO_PLAY_BUTTONS + requirePreference(R.string.show_audio_play_buttons_key).apply { + title = CollectionManager.TR.preferencesShowPlayButtonsOnCardsWith() + launchCatchingTask { isChecked = withCol { !config.getBool(ConfigKey.Bool.HIDE_AUDIO_PLAY_BUTTONS) } } + setOnPreferenceChangeListener { newValue -> + launchCatchingTask { withCol { config.setBool(ConfigKey.Bool.HIDE_AUDIO_PLAY_BUTTONS, !(newValue as Boolean)) } } + } + } } private val mBackgroundImageResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { selectedImage -> diff --git a/AnkiDroid/src/main/res/values/preferences.xml b/AnkiDroid/src/main/res/values/preferences.xml index f1caa1432a26..6b36d4dc4209 100644 --- a/AnkiDroid/src/main/res/values/preferences.xml +++ b/AnkiDroid/src/main/res/values/preferences.xml @@ -44,6 +44,7 @@ showTopbar showProgress showETA + showAudioPlayButtons relativeCardBrowserFontSize appBarButtonsScreen diff --git a/AnkiDroid/src/main/res/xml/preferences_appearance.xml b/AnkiDroid/src/main/res/xml/preferences_appearance.xml index 4c9de6a1f0f2..6ee10d42e774 100644 --- a/AnkiDroid/src/main/res/xml/preferences_appearance.xml +++ b/AnkiDroid/src/main/res/xml/preferences_appearance.xml @@ -24,6 +24,7 @@ @@ -109,6 +110,11 @@ android:defaultValue="true" android:summary="@string/show_eta_summ" android:title="@string/show_eta" /> + + assertThat("show audio preference default value: enabled", content, containsString("playsound:q:0")) + assertThat("show audio preference default value: enabled", content, containsString("SOUND")) + } + setHidePlayAudioButtons(true) + getViewerContent().let { content -> + assertThat("show audio preference disabled", content, not(containsString("playsound:q:0"))) + assertThat("show audio preference disabled", content, containsString("SOUND")) + } + setHidePlayAudioButtons(false) + getViewerContent().let { content -> + assertThat("show audio preference enabled explicitly", content, containsString("playsound:q:0")) + assertThat("show audio preference enabled explicitly", content, containsString("SOUND")) + } + } + + @Test + fun `Show audio play buttons preference handling - tts`() = runTest { + addNoteUsingTextToSpeechNoteType("TTS", "BACK") + getViewerContent().let { content -> + assertThat("show audio preference default value: enabled", content, containsString("playsound:q:0")) + assertThat("show audio preference default value: enabled", content, containsString("TTS")) + } + setHidePlayAudioButtons(true) + getViewerContent().let { content -> + assertThat("show audio preference disabled", content, not(containsString("playsound:q:0"))) + assertThat("show audio preference disabled", content, containsString("TTS")) + } + setHidePlayAudioButtons(false) + getViewerContent().let { content -> + assertThat("show audio preference enabled explicitly", content, containsString("playsound:q:0")) + assertThat("show audio preference enabled explicitly", content, containsString("TTS")) + } + } + + private fun setHidePlayAudioButtons(value: Boolean) = col.config.setBool(ConfigKey.Bool.HIDE_AUDIO_PLAY_BUTTONS, value) + + private fun getViewerContent(): String? { + // PERF: Optimise this to not create a new viewer each time + return getViewer(addCard = false).cardContent + } + private fun showNextCard(viewer: NonAbstractFlashcardViewer) { viewer.executeCommand(ViewerCommand.FLIP_OR_ANSWER_EASE4) viewer.executeCommand(ViewerCommand.FLIP_OR_ANSWER_EASE4) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.kt index 84d5f8b7e144..5c41ccc3b3d1 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.kt @@ -416,6 +416,13 @@ open class RobolectricTest : AndroidTest { return name } + /** Adds a note with Text to Speech functionality */ + @Suppress("SameParameterValue") + protected fun addNoteUsingTextToSpeechNoteType(front: String, back: String) { + addNonClozeModel("TTS", arrayOf("Front", "Back"), "{{Front}}{{tts en_GB:Front}}", "{{tts en_GB:Front}}
{{Back}}") + addNoteUsingModelName("TTS", front, back) + } + private fun addField(notetype: NotetypeJson, name: String) { val models = col.notetypes try {