PreferenceModel: Add subtitle provider to ListPreference (#8322)

* PreferenceModel: Add subtitle provider to ListPreference

So that it's possible to avoid value formatting when needed

* cleanups
This commit is contained in:
Ivan Iskandar 2022-10-29 20:44:12 +07:00 committed by GitHub
parent 9fbd3fe33f
commit 3e86cb094b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 44 additions and 60 deletions

View file

@ -82,7 +82,7 @@ internal fun PreferenceItem(
ListPreferenceWidget( ListPreferenceWidget(
value = value, value = value,
title = item.title, title = item.title,
subtitle = item.subtitle, subtitle = item.internalSubtitleProvider(value, item.entries),
icon = item.icon, icon = item.icon,
entries = item.entries, entries = item.entries,
onValueChange = { newValue -> onValueChange = { newValue ->
@ -98,7 +98,7 @@ internal fun PreferenceItem(
ListPreferenceWidget( ListPreferenceWidget(
value = item.value, value = item.value,
title = item.title, title = item.title,
subtitle = item.subtitle, subtitle = item.subtitleProvider(item.value, item.entries),
icon = item.icon, icon = item.icon,
entries = item.entries, entries = item.entries,
onValueChange = { scope.launch { item.onValueChanged(it) } }, onValueChange = { scope.launch { item.onValueChanged(it) } },

View file

@ -1,7 +1,11 @@
package eu.kanade.presentation.more.settings package eu.kanade.presentation.more.settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.core.preference.Preference as PreferenceData import eu.kanade.tachiyomi.core.preference.Preference as PreferenceData
@ -47,6 +51,8 @@ sealed class Preference {
val pref: PreferenceData<T>, val pref: PreferenceData<T>,
override val title: String, override val title: String,
override val subtitle: String? = "%s", override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
{ v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (newValue: T) -> Boolean = { true }, override val onValueChanged: suspend (newValue: T) -> Boolean = { true },
@ -55,6 +61,10 @@ sealed class Preference {
) : PreferenceItem<T>() { ) : PreferenceItem<T>() {
internal fun internalSet(newValue: Any) = pref.set(newValue as T) internal fun internalSet(newValue: Any) = pref.set(newValue as T)
internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T) internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T)
@Composable
internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
subtitleProvider(value as T, entries as Map<T, String>)
} }
/** /**
@ -64,6 +74,8 @@ sealed class Preference {
val value: String, val value: String,
override val title: String, override val title: String,
override val subtitle: String? = "%s", override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
{ v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }, override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
@ -78,7 +90,15 @@ sealed class Preference {
data class MultiSelectListPreference( data class MultiSelectListPreference(
val pref: PreferenceData<Set<String>>, val pref: PreferenceData<Set<String>>,
override val title: String, override val title: String,
override val subtitle: String? = null, override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? = { v, e ->
val combined = remember(v) {
v.map { e[it] }
.takeIf { it.isNotEmpty() }
?.joinToString()
} ?: stringResource(id = R.string.none)
subtitle?.format(combined)
},
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true }, override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true },

View file

@ -72,7 +72,6 @@ class SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = themeModePref, pref = themeModePref,
title = stringResource(R.string.pref_theme_mode), title = stringResource(R.string.pref_theme_mode),
subtitle = "%s",
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf( mapOf(
ThemeMode.SYSTEM to stringResource(R.string.theme_system), ThemeMode.SYSTEM to stringResource(R.string.theme_system),
@ -129,7 +128,6 @@ class SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = uiPreferences.relativeTime(), pref = uiPreferences.relativeTime(),
title = stringResource(R.string.pref_relative_format), title = stringResource(R.string.pref_relative_format),
subtitle = "%s",
entries = mapOf( entries = mapOf(
0 to stringResource(R.string.off), 0 to stringResource(R.string.off),
2 to stringResource(R.string.pref_relative_time_short), 2 to stringResource(R.string.pref_relative_time_short),
@ -139,7 +137,6 @@ class SettingsAppearanceScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = uiPreferences.dateFormat(), pref = uiPreferences.dateFormat(),
title = stringResource(R.string.pref_date_format), title = stringResource(R.string.pref_date_format),
subtitle = "%s",
entries = DateFormats entries = DateFormats
.associateWith { .associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now) val formattedDate = UiPreferences.dateFormat(it).format(now)

View file

@ -10,7 +10,6 @@ import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -27,7 +26,6 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.presentation.util.collectAsState import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -101,9 +99,11 @@ class SettingsDownloadScreen : SearchableSettings {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
pref = currentDirPref, pref = currentDirPref,
title = stringResource(R.string.pref_download_directory), title = stringResource(R.string.pref_download_directory),
subtitle = remember(currentDir) { subtitleProvider = { value, _ ->
UniFile.fromUri(context, currentDir.toUri())?.filePath remember(value) {
} ?: stringResource(R.string.invalid_location, currentDir), UniFile.fromUri(context, value.toUri())?.filePath
} ?: stringResource(R.string.invalid_location, value)
},
entries = mapOf( entries = mapOf(
defaultDirPair, defaultDirPair,
customDirEntryKey to stringResource(R.string.custom_dir), customDirEntryKey to stringResource(R.string.custom_dir),
@ -173,25 +173,10 @@ class SettingsDownloadScreen : SearchableSettings {
downloadPreferences: DownloadPreferences, downloadPreferences: DownloadPreferences,
categories: () -> List<Category>, categories: () -> List<Category>,
): Preference.PreferenceItem.MultiSelectListPreference { ): Preference.PreferenceItem.MultiSelectListPreference {
val none = stringResource(R.string.none)
val pref = downloadPreferences.removeExcludeCategories()
val entries = categories().associate { it.id.toString() to it.visualName }
val subtitle by produceState(initialValue = "") {
pref.changes()
.stateIn(this)
.collect { mutable ->
value = mutable
.mapNotNull { id -> entries[id] }
.sortedBy { entries.values.indexOf(it) }
.joinToString()
.ifEmpty { none }
}
}
return Preference.PreferenceItem.MultiSelectListPreference( return Preference.PreferenceItem.MultiSelectListPreference(
pref = pref, pref = downloadPreferences.removeExcludeCategories(),
title = stringResource(R.string.pref_remove_exclude_categories), title = stringResource(R.string.pref_remove_exclude_categories),
subtitle = subtitle, entries = categories().associate { it.id.toString() to it.visualName },
entries = entries,
) )
} }

View file

@ -72,7 +72,6 @@ class SettingsGeneralScreen : SearchableSettings {
Preference.PreferenceItem.BasicListPreference( Preference.PreferenceItem.BasicListPreference(
value = currentLanguage, value = currentLanguage,
title = stringResource(R.string.pref_app_language), title = stringResource(R.string.pref_app_language),
subtitle = "%s",
entries = langs, entries = langs,
onValueChanged = { newValue -> onValueChanged = { newValue ->
currentLanguage = newValue currentLanguage = newValue

View file

@ -177,28 +177,6 @@ class SettingsLibraryScreen : SearchableSettings {
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState() val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
val deviceRestrictionEntries = mapOf(
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
DEVICE_CHARGING to stringResource(R.string.charging),
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
)
val deviceRestrictions = libraryUpdateDeviceRestrictionPref.collectAsState()
.value
.sorted()
.map { deviceRestrictionEntries.getOrElse(it) { it } }
.let { if (it.isEmpty()) stringResource(R.string.none) else it.joinToString() }
val mangaRestrictionEntries = mapOf(
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
)
val mangaRestrictions = libraryUpdateMangaRestrictionPref.collectAsState()
.value
.map { mangaRestrictionEntries.getOrElse(it) { it } }
.let { if (it.isEmpty()) stringResource(R.string.none) else it.joinToString() }
val included by libraryUpdateCategoriesPref.collectAsState() val included by libraryUpdateCategoriesPref.collectAsState()
val excluded by libraryUpdateCategoriesExcludePref.collectAsState() val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
var showDialog by rememberSaveable { mutableStateOf(false) } var showDialog by rememberSaveable { mutableStateOf(false) }
@ -224,7 +202,6 @@ class SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryUpdateIntervalPref, pref = libraryUpdateIntervalPref,
title = stringResource(R.string.pref_library_update_interval), title = stringResource(R.string.pref_library_update_interval),
subtitle = "%s",
entries = mapOf( entries = mapOf(
0 to stringResource(R.string.update_never), 0 to stringResource(R.string.update_never),
12 to stringResource(R.string.update_12hour), 12 to stringResource(R.string.update_12hour),
@ -242,8 +219,13 @@ class SettingsLibraryScreen : SearchableSettings {
pref = libraryUpdateDeviceRestrictionPref, pref = libraryUpdateDeviceRestrictionPref,
enabled = libraryUpdateInterval > 0, enabled = libraryUpdateInterval > 0,
title = stringResource(R.string.pref_library_update_restriction), title = stringResource(R.string.pref_library_update_restriction),
subtitle = stringResource(R.string.restrictions, deviceRestrictions), subtitle = stringResource(R.string.restrictions),
entries = deviceRestrictionEntries, entries = mapOf(
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
DEVICE_CHARGING to stringResource(R.string.charging),
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
),
onValueChanged = { onValueChanged = {
// Post to event looper to allow the preference to be updated. // Post to event looper to allow the preference to be updated.
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) } ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
@ -253,8 +235,11 @@ class SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryUpdateMangaRestrictionPref, pref = libraryUpdateMangaRestrictionPref,
title = stringResource(R.string.pref_library_update_manga_restriction), title = stringResource(R.string.pref_library_update_manga_restriction),
subtitle = mangaRestrictions, entries = mapOf(
entries = mangaRestrictionEntries, MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
),
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.categories), title = stringResource(R.string.categories),

View file

@ -49,7 +49,6 @@ class SettingsSecurityScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = securityPreferences.lockAppAfter(), pref = securityPreferences.lockAppAfter(),
title = stringResource(R.string.lock_when_idle), title = stringResource(R.string.lock_when_idle),
subtitle = "%s",
enabled = authSupported && useAuth, enabled = authSupported && useAuth,
entries = LockAfterValues entries = LockAfterValues
.associateWith { .associateWith {
@ -72,7 +71,6 @@ class SettingsSecurityScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = securityPreferences.secureScreen(), pref = securityPreferences.secureScreen(),
title = stringResource(R.string.secure_screen), title = stringResource(R.string.secure_screen),
subtitle = "%s",
entries = SecurityPreferences.SecureScreenMode.values() entries = SecurityPreferences.SecureScreenMode.values()
.associateWith { stringResource(it.titleResId) }, .associateWith { stringResource(it.titleResId) },
), ),

View file

@ -39,7 +39,7 @@ fun <T> ListPreferenceWidget(
TextPreferenceWidget( TextPreferenceWidget(
title = title, title = title,
subtitle = subtitle?.format(entries[value]), subtitle = subtitle,
icon = icon, icon = icon,
onPreferenceClick = { showDialog(true) }, onPreferenceClick = { showDialog(true) },
) )

View file

@ -33,7 +33,7 @@ fun MultiSelectListPreferenceWidget(
TextPreferenceWidget( TextPreferenceWidget(
title = preference.title, title = preference.title,
subtitle = preference.subtitle, subtitle = preference.subtitleProvider(values, preference.entries),
icon = preference.icon, icon = preference.icon,
onPreferenceClick = { showDialog(true) }, onPreferenceClick = { showDialog(true) },
) )