Implement Multi-Select List Preference for Mangadex

This commit is contained in:
Syer10
2021-11-28 16:06:27 -05:00
parent da197f89dc
commit 18138b7299
7 changed files with 161 additions and 14 deletions

View File

@@ -3,9 +3,9 @@ import org.gradle.api.JavaVersion
object Config {
const val tachideskVersion = "v0.5.4"
// Match this to the Tachidesk-Server commit count
const val serverCode = 1043
const val serverCode = 1045
const val preview = true
const val previewCommit = "5e47b7ae6b37931ce3a8eee33cafb9475d7a77bb"
const val previewCommit = "2478aa77cd4a71b0ae7c895fce0358ad7c30614b"
val jvmTarget = JavaVersion.VERSION_15
}

View File

@@ -0,0 +1,28 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.data.models.sourcepreference
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("MultiSelectListPreference")
data class MultiSelectListPreference(override val props: MultiSelectListProps) : SourcePreference() {
@Serializable
data class MultiSelectListProps(
override val key: String,
override val title: String,
override val summary: String?,
override val currentValue: List<String>?,
override val defaultValue: List<String>?,
override val defaultValueType: String,
val dialogTitle: String?,
val dialogMessage: String?,
val entries: List<String>,
val entryValues: List<String>
) : Props<List<String>?>
}

View File

@@ -7,6 +7,16 @@
package ca.gosyer.data.models.sourcepreference
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class SourcePreferenceChange(val position: Int, val value: String)
data class SourcePreferenceChange(val position: Int, val value: String) {
constructor(position: Int, value: Any) : this(
position,
if (value is List<*>) {
@Suppress("UNCHECKED_CAST")
Json.encodeToString(value as List<String>)
} else value.toString()
)
}

View File

@@ -150,6 +150,6 @@ class SourceInteractionHandler @Inject constructor(
suspend fun setSourceSetting(sourceId: Long, position: Int, value: Any) = setSourceSetting(
sourceId,
SourcePreferenceChange(position, value.toString())
SourcePreferenceChange(position, value)
)
}

View File

@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
@@ -40,6 +42,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Checkbox
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
@@ -72,6 +75,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.components.ColorPickerDialog
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
fun PreferenceRow(
@@ -263,6 +267,48 @@ fun <T> ChoiceDialog(
}
}
fun <T> MultiSelectDialog(
items: List<Pair<T, String>>,
selected: List<T>?,
onCloseRequest: () -> Unit = {},
onFinished: (List<T>) -> Unit,
title: String,
) {
val checkedFlow = MutableStateFlow(selected.orEmpty())
WindowDialog(
onCloseRequest = onCloseRequest,
title = title,
onPositiveButton = {
onFinished(checkedFlow.value)
}
) {
val checked by checkedFlow.collectAsState()
LazyColumn(Modifier.fillMaxSize()) {
items(items) { (value, text) ->
Row(
modifier = Modifier.requiredHeight(48.dp).fillMaxWidth().clickable(
onClick = {
if (value in checked) {
checkedFlow.value -= value
} else {
checkedFlow.value += value
}
}
),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = value in checked,
onCheckedChange = null,
)
Text(text = text, modifier = Modifier.padding(start = 24.dp))
}
}
item { Spacer(Modifier.height(80.dp)) }
}
}
}
@Composable
fun ColorPreference(
preference: PreferenceMutableStateFlow<Color>,

View File

@@ -28,12 +28,14 @@ import ca.gosyer.ui.base.components.LocalMenuController
import ca.gosyer.ui.base.components.MenuController
import ca.gosyer.ui.base.components.Toolbar
import ca.gosyer.ui.base.prefs.ChoiceDialog
import ca.gosyer.ui.base.prefs.MultiSelectDialog
import ca.gosyer.ui.base.prefs.PreferenceRow
import ca.gosyer.ui.base.resources.stringResource
import ca.gosyer.ui.base.vm.viewModel
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.CheckBox
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.EditText
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.List
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.MultiSelect
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.Switch
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.TwoState
import ca.gosyer.util.compose.ThemedWindow
@@ -71,6 +73,9 @@ fun SourceSettingsMenu(sourceId: Long, menuController: MenuController? = LocalMe
is EditText -> {
EditTextPreference(it)
}
is MultiSelect -> {
MultiSelectPreference(it)
}
else -> Unit
}
}
@@ -120,9 +125,35 @@ private fun ListPreference(list: List) {
onClick = {
ChoiceDialog(
list.getOptions(),
state.first,
onSelected = list::setValue,
title = "Select choice"
state,
onSelected = list::updateState,
title = title
)
}
)
}
@Composable
private fun MultiSelectPreference(multiSelect: MultiSelect) {
val state by multiSelect.state.collectAsState()
val title = remember(state) { multiSelect.title ?: multiSelect.summary ?: "No title" }
val subtitle = remember(state) {
if (multiSelect.title == null) {
null
} else {
multiSelect.summary
}
}
val dialogTitle = remember(state) { multiSelect.props.dialogTitle ?: multiSelect.title ?: multiSelect.summary ?: "No title" }
PreferenceRow(
title,
subtitle = subtitle,
onClick = {
MultiSelectDialog(
multiSelect.getOptions(),
state,
onFinished = multiSelect::updateState,
title = dialogTitle
)
}
)

View File

@@ -9,6 +9,7 @@ package ca.gosyer.ui.sources.settings.model
import ca.gosyer.data.models.sourcepreference.CheckBoxPreference
import ca.gosyer.data.models.sourcepreference.EditTextPreference
import ca.gosyer.data.models.sourcepreference.ListPreference
import ca.gosyer.data.models.sourcepreference.MultiSelectListPreference
import ca.gosyer.data.models.sourcepreference.SourcePreference
import ca.gosyer.data.models.sourcepreference.SwitchPreference
import ca.gosyer.data.models.sourcepreference.TwoStateProps
@@ -16,6 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.util.Formatter
import kotlin.collections.List as KtList
sealed class SourceSettingsView<T, R : Any?> {
abstract val index: Int
@@ -76,12 +78,12 @@ sealed class SourceSettingsView<T, R : Any?> {
override val title: String?,
override val subtitle: String?,
override val props: ListPreference.ListProps
) : SourceSettingsView<ListPreference.ListProps, Pair<String, String>>() {
) : SourceSettingsView<ListPreference.ListProps, String>() {
private val _state = MutableStateFlow(
(props.currentValue ?: props.defaultValue ?: "0") to props.entries[props.entryValues.indexOf(props.currentValue ?: props.defaultValue ?: "0")]
props.currentValue ?: props.defaultValue ?: "0"
)
override val state: StateFlow<Pair<String, String>> = _state.asStateFlow()
override fun updateState(value: Pair<String, String>) {
override val state: StateFlow<String> = _state.asStateFlow()
override fun updateState(value: String) {
_state.value = value
}
internal constructor(index: Int, preference: ListPreference) : this(
@@ -92,14 +94,43 @@ sealed class SourceSettingsView<T, R : Any?> {
)
override val summary: String?
get() = subtitle?.let { withFormat(it, state.value.second) }
get() = subtitle?.let { withFormat(it, props.entries[props.entryValues.indexOf(state.value)]) }
fun getOptions() = props.entryValues.mapIndexed { index, s ->
s to props.entries[index]
}
}
data class MultiSelect internal constructor(
override val index: Int,
override val title: String?,
override val subtitle: String?,
override val props: MultiSelectListPreference.MultiSelectListProps
) : SourceSettingsView<MultiSelectListPreference.MultiSelectListProps, KtList<String>?>() {
private val _state = MutableStateFlow(
props.currentValue ?: props.defaultValue
)
override val state: StateFlow<KtList<String>?> = _state.asStateFlow()
override fun updateState(value: KtList<String>?) {
_state.value = value
}
internal constructor(index: Int, preference: MultiSelectListPreference) : this(
index,
preference.props.title,
preference.props.summary,
preference.props
)
fun getOptions() = props.entryValues.mapIndexed { index, s ->
s to props.entries[index]
}
fun setValue(value: String) {
updateState(value to props.entries[props.entryValues.indexOf(value)])
fun toggleOption(key: String) {
if (key in state.value.orEmpty()) {
updateState(state.value.orEmpty() - key)
} else {
updateState(state.value.orEmpty() + key)
}
}
}
@@ -138,6 +169,7 @@ fun SourceSettingsView(index: Int, preference: SourcePreference): SourceSettings
is CheckBoxPreference -> SourceSettingsView.CheckBox(index, preference)
is SwitchPreference -> SourceSettingsView.Switch(index, preference)
is ListPreference -> SourceSettingsView.List(index, preference)
is MultiSelectListPreference -> SourceSettingsView.MultiSelect(index, preference)
is EditTextPreference -> SourceSettingsView.EditText(index, preference)
}
}