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:
parent
9fbd3fe33f
commit
3e86cb094b
9 changed files with 44 additions and 60 deletions
|
@ -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) } },
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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) },
|
||||||
),
|
),
|
||||||
|
|
|
@ -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) },
|
||||||
)
|
)
|
||||||
|
|
|
@ -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) },
|
||||||
)
|
)
|
||||||
|
|
Reference in a new issue