Skip to content

Commit

Permalink
refactor(reviewer): move preferences to Prefs
Browse files Browse the repository at this point in the history
+ fix(tests)

fix(tests): Prefs
Sometimes the order wasn't right, and the delegated methods weren't being parsed

refactor: add autoFocusTypeAnswer to Prefs
refactor: add ignoreDisplayCutout to Prefs
refactor: add HideSystemBars to Prefs
  • Loading branch information
BrayanDSO authored and david-allison committed Jan 16, 2025
1 parent e130858 commit e815af1
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ object UsageAnalytics {
"useInputTag", // Type answer into the card
"disableExtendedTextUi", // Disable Single-Field Edit Mode
"noteEditorNewlineReplace", // Replace newlines with HTML
"autoFocusTypeInAnswer", // Focus ‘type in answer’
PrefKey.AUTO_FOCUS_TYPE_ANSWER, // Focus ‘type in answer’
"mediaImportAllowAllFiles", // Allow all files in media imports
"providerEnabled", // Enable AnkiDroid API
// ******************************** App bar buttons ****************************************
Expand Down
3 changes: 3 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/settings/PrefKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ object PrefKey {

// **************************************** Reviewer **************************************** //
const val FRAME_STYLE = "reviewerFrameStyle"
const val HIDE_SYSTEM_BARS = "hideSystemBars"
const val IGNORE_DISPLAY_CUTOUT = "ignoreDisplayCutout"
const val AUTO_FOCUS_TYPE_ANSWER = "autoFocusTypeInAnswer"

// ************************************** Accessibility ************************************* //
const val ANSWER_BUTTON_SIZE = "answerButtonSize"
Expand Down
19 changes: 13 additions & 6 deletions AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,49 @@ import androidx.core.content.edit
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.BuildConfig
import com.ichi2.anki.settings.enums.FrameStyle
import com.ichi2.anki.settings.enums.HideSystemBars
import com.ichi2.anki.settings.enums.PrefEnum
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

// TODO move this to `com.ichi2.anki.preferences`
// after the UI classes of that package are moved to `com.ichi2.anki.ui.preferences`
object Prefs {
private val prefs by lazy { AnkiDroidApp.sharedPrefs() }
private val sharedPrefs get() = AnkiDroidApp.sharedPrefs()

@VisibleForTesting
fun getBoolean(
key: String,
defValue: Boolean,
): Boolean = prefs.getBoolean(key, defValue)
): Boolean = sharedPrefs.getBoolean(key, defValue)

@VisibleForTesting
fun putBoolean(
key: String,
value: Boolean,
) {
prefs.edit { putBoolean(key, value) }
sharedPrefs.edit { putBoolean(key, value) }
}

@VisibleForTesting
fun getString(
key: String,
defValue: String?,
): String? = prefs.getString(key, defValue)
): String? = sharedPrefs.getString(key, defValue)

@VisibleForTesting
fun putString(
key: String,
value: String?,
) {
prefs.edit { putString(key, value) }
sharedPrefs.edit { putString(key, value) }
}

@VisibleForTesting
fun getInt(
key: String,
defValue: Int,
): Int = prefs.getInt(key, defValue)
): Int = sharedPrefs.getInt(key, defValue)

@VisibleForTesting
fun <E> getEnum(
Expand Down Expand Up @@ -129,9 +130,15 @@ object Prefs {

// **************************************** Reviewer **************************************** //

val ignoreDisplayCutout by booleanPref(PrefKey.IGNORE_DISPLAY_CUTOUT, false)
val autoFocusTypeAnswer by booleanPref(PrefKey.AUTO_FOCUS_TYPE_ANSWER, true)

val frameStyle: FrameStyle
get() = getEnum(PrefKey.FRAME_STYLE, FrameStyle.CARD)

val hideSystemBars: HideSystemBars
get() = getEnum(PrefKey.HIDE_SYSTEM_BARS, HideSystemBars.NONE)

// ************************************** Accessibility ************************************* //

val answerButtonsSize: Int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024 Brayan Oliveira <[email protected]>
*
* 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.settings.enums

enum class HideSystemBars(
override val entryValue: String,
) : PrefEnum {
NONE("0"),
STATUS_BAR("1"),
NAVIGATION_BAR("2"),
ALL("3"),
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import com.ichi2.anki.previewer.CardViewerActivity
import com.ichi2.anki.previewer.CardViewerFragment
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.settings.enums.FrameStyle
import com.ichi2.anki.settings.enums.HideSystemBars
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
import com.ichi2.anki.snackbar.SnackbarBuilder
import com.ichi2.anki.snackbar.showSnackbar
Expand Down Expand Up @@ -237,7 +238,7 @@ class ReviewerFragment :
}
}
}
val autoFocusTypeAnswer = sharedPrefs().getBoolean(getString(R.string.type_in_answer_focus_key), true)
val autoFocusTypeAnswer = Prefs.autoFocusTypeAnswer
viewModel.typeAnswerFlow.collectIn(lifecycleScope) { typeInAnswer ->
typeAnswerEditText.text = null
if (typeInAnswer == null) {
Expand Down Expand Up @@ -452,9 +453,8 @@ class ReviewerFragment :
}

private fun setupImmersiveMode(view: View) {
val hideSystemBarsSetting = HideSystemBars.from(requireContext())
val barsToHide =
when (hideSystemBarsSetting) {
when (Prefs.hideSystemBars) {
HideSystemBars.NONE -> return
HideSystemBars.STATUS_BAR -> WindowInsetsCompat.Type.statusBars()
HideSystemBars.NAVIGATION_BAR -> WindowInsetsCompat.Type.navigationBars()
Expand All @@ -467,7 +467,7 @@ class ReviewerFragment :
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}

val ignoreDisplayCutout = sharedPrefs().getBoolean(getString(R.string.ignore_display_cutout_key), false)
val ignoreDisplayCutout = Prefs.ignoreDisplayCutout
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val defaultTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()
val typeMask =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.analytics.UsageAnalytics.preferencesWhoseChangesShouldBeReported
import com.ichi2.anki.preferences.PreferenceTestUtils
import com.ichi2.anki.preferences.SettingsFragment
import com.ichi2.anki.settings.PrefKey
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.junit.Test
Expand Down Expand Up @@ -84,8 +85,8 @@ class PreferencesAnalyticsTest : RobolectricTest() {
"hideAnswerButtons",
"hideHardAndEasy",
"reviewerFrameStyle",
"hideSystemBars",
"ignoreDisplayCutout",
PrefKey.HIDE_SYSTEM_BARS,
PrefKey.IGNORE_DISPLAY_CUTOUT,
)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@ import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.preferences.PreferenceTestUtils
import com.ichi2.anki.preferences.PreferenceTestUtils.getAttrsFromXml
import com.ichi2.anki.preferences.SettingsFragment
import com.ichi2.anki.settings.enums.PrefEnum
import com.ichi2.testutils.EmptyApplication
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.whenever
import org.robolectric.annotation.Config
import kotlin.reflect.KVisibility
Expand All @@ -42,36 +41,24 @@ import kotlin.reflect.full.memberProperties
@RunWith(AndroidJUnit4::class)
@Config(application = EmptyApplication::class)
class PrefsRobolectricTest : RobolectricTest() {
@Before
fun setup() {
AnkiDroidApp.sharedPreferencesTestingOverride =
SPMockBuilder().createSharedPreferences()
}

private fun getKeysAndDefaultValues(): MutableMap<String, Any?> {
val settingsSpy = spy(Prefs)
val spy = spy(SPMockBuilder().createSharedPreferences())
AnkiDroidApp.sharedPreferencesTestingOverride = spy
val keysAndDefaultValues: MutableMap<String, Any?> = mutableMapOf()
var key = ""

doAnswer { invocation ->
key = invocation.arguments[0] as String
keysAndDefaultValues[key] = null
val key = invocation.arguments[0] as String
keysAndDefaultValues[key] = invocation.arguments[1]
invocation.callRealMethod()
}.run {
whenever(settingsSpy).getBoolean(anyString(), anyBoolean())
whenever(settingsSpy).getString(anyString(), anyString())
whenever(settingsSpy).getInt(anyString(), anyInt())
whenever(spy).getBoolean(anyString(), anyBoolean())
whenever(spy).getString(anyString(), anyOrNull())
whenever(spy).getInt(anyString(), anyInt())
}

for (property in Prefs::class.memberProperties) {
if (property.visibility != KVisibility.PUBLIC) continue
val defaultValue = property.getter.call(settingsSpy)
keysAndDefaultValues[key] =
if (defaultValue is PrefEnum) {
defaultValue.entryValue
} else {
defaultValue
}
property.getter.call(Prefs)
}
return keysAndDefaultValues
}
Expand Down
48 changes: 27 additions & 21 deletions AnkiDroid/src/test/java/com/ichi2/anki/settings/PrefsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,37 @@
*/
package com.ichi2.anki.settings

import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import com.github.ivanshafran.sharedpreferencesmock.SPMockBuilder
import com.ichi2.anki.AnkiDroidApp
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.whenever
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KVisibility
import kotlin.reflect.full.memberProperties

class PrefsTest {
@BeforeEach
fun setup() {
AnkiDroidApp.sharedPreferencesTestingOverride =
SPMockBuilder().createSharedPreferences()
class PrefsTest : OnSharedPreferenceChangeListener {
private var lastSetKey: String? = null

override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences?,
key: String?,
) {
lastSetKey = key
}

@Test
fun `booleanSetting getter and setter work`() {
AnkiDroidApp.sharedPreferencesTestingOverride = SPMockBuilder().createSharedPreferences()
var setting by Prefs.booleanPref("boolKey", false)
assertThat(setting, equalTo(false))

Expand All @@ -49,6 +55,7 @@ class PrefsTest {

@Test
fun `stringSetting getter and setter work`() {
AnkiDroidApp.sharedPreferencesTestingOverride = SPMockBuilder().createSharedPreferences()
var setting by Prefs.stringPref("stringKey", "defaultValue")
assertThat(setting, equalTo("defaultValue"))

Expand All @@ -58,34 +65,33 @@ class PrefsTest {

@Test
fun `getters and setters use the same key`() {
val settingsSpy = spy(Prefs)
var key = ""
val sharedPrefsSpy = spy(SPMockBuilder().createSharedPreferences())
AnkiDroidApp.sharedPreferencesTestingOverride = sharedPrefsSpy

var getterKey = ""
doAnswer { invocation ->
key = invocation.arguments[0] as String
getterKey = invocation.arguments[0] as String
invocation.callRealMethod()
}.run {
whenever(settingsSpy).getBoolean(anyString(), anyBoolean())
whenever(settingsSpy).putBoolean(anyString(), anyBoolean())
whenever(settingsSpy).getString(anyString(), anyString())
whenever(settingsSpy).putString(anyString(), anyString())
whenever(settingsSpy).getInt(anyString(), anyInt())
whenever(sharedPrefsSpy).getBoolean(anyString(), anyBoolean())
whenever(sharedPrefsSpy).getString(anyString(), anyOrNull())
whenever(sharedPrefsSpy).getInt(anyString(), anyInt())
}

sharedPrefsSpy.registerOnSharedPreferenceChangeListener(this)
for (property in Prefs::class.memberProperties) {
if (property.visibility != KVisibility.PUBLIC || property !is KMutableProperty<*>) continue

property.getter.call(settingsSpy)
val getterKey = key
property.getter.call(Prefs)

when (property.returnType.classifier) {
Boolean::class -> property.setter.call(settingsSpy, false)
String::class -> property.setter.call(settingsSpy, "foo")
Boolean::class -> property.setter.call(Prefs, false)
String::class -> property.setter.call(Prefs, "foo")
else -> continue
}
val setterKey = key

assertThat("The getter and setter of '${property.name}' use the same key", getterKey, equalTo(setterKey))
assertThat("The getter and setter of '${property.name}' use the same key", getterKey, equalTo(lastSetKey))
}
sharedPrefsSpy.unregisterOnSharedPreferenceChangeListener(this)
}
}

0 comments on commit e815af1

Please sign in to comment.