mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Implement Multi-Select List Preference for Mangadex
This commit is contained in:
@@ -3,9 +3,9 @@ import org.gradle.api.JavaVersion
|
|||||||
object Config {
|
object Config {
|
||||||
const val tachideskVersion = "v0.5.4"
|
const val tachideskVersion = "v0.5.4"
|
||||||
// Match this to the Tachidesk-Server commit count
|
// Match this to the Tachidesk-Server commit count
|
||||||
const val serverCode = 1043
|
const val serverCode = 1045
|
||||||
const val preview = true
|
const val preview = true
|
||||||
const val previewCommit = "5e47b7ae6b37931ce3a8eee33cafb9475d7a77bb"
|
const val previewCommit = "2478aa77cd4a71b0ae7c895fce0358ad7c30614b"
|
||||||
|
|
||||||
val jvmTarget = JavaVersion.VERSION_15
|
val jvmTarget = JavaVersion.VERSION_15
|
||||||
}
|
}
|
||||||
@@ -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>?>
|
||||||
|
}
|
||||||
@@ -7,6 +7,16 @@
|
|||||||
package ca.gosyer.data.models.sourcepreference
|
package ca.gosyer.data.models.sourcepreference
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@Serializable
|
@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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,6 +150,6 @@ class SourceInteractionHandler @Inject constructor(
|
|||||||
|
|
||||||
suspend fun setSourceSetting(sourceId: Long, position: Int, value: Any) = setSourceSetting(
|
suspend fun setSourceSetting(sourceId: Long, position: Int, value: Any) = setSourceSetting(
|
||||||
sourceId,
|
sourceId,
|
||||||
SourcePreferenceChange(position, value.toString())
|
SourcePreferenceChange(position, value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredHeight
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
import androidx.compose.foundation.layout.size
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Checkbox
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.LocalContentColor
|
import androidx.compose.material.LocalContentColor
|
||||||
@@ -72,6 +75,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.ui.base.WindowDialog
|
import ca.gosyer.ui.base.WindowDialog
|
||||||
import ca.gosyer.ui.base.components.ColorPickerDialog
|
import ca.gosyer.ui.base.components.ColorPickerDialog
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceRow(
|
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
|
@Composable
|
||||||
fun ColorPreference(
|
fun ColorPreference(
|
||||||
preference: PreferenceMutableStateFlow<Color>,
|
preference: PreferenceMutableStateFlow<Color>,
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ import ca.gosyer.ui.base.components.LocalMenuController
|
|||||||
import ca.gosyer.ui.base.components.MenuController
|
import ca.gosyer.ui.base.components.MenuController
|
||||||
import ca.gosyer.ui.base.components.Toolbar
|
import ca.gosyer.ui.base.components.Toolbar
|
||||||
import ca.gosyer.ui.base.prefs.ChoiceDialog
|
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.prefs.PreferenceRow
|
||||||
import ca.gosyer.ui.base.resources.stringResource
|
import ca.gosyer.ui.base.resources.stringResource
|
||||||
import ca.gosyer.ui.base.vm.viewModel
|
import ca.gosyer.ui.base.vm.viewModel
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.CheckBox
|
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.EditText
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.List
|
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.Switch
|
||||||
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.TwoState
|
import ca.gosyer.ui.sources.settings.model.SourceSettingsView.TwoState
|
||||||
import ca.gosyer.util.compose.ThemedWindow
|
import ca.gosyer.util.compose.ThemedWindow
|
||||||
@@ -71,6 +73,9 @@ fun SourceSettingsMenu(sourceId: Long, menuController: MenuController? = LocalMe
|
|||||||
is EditText -> {
|
is EditText -> {
|
||||||
EditTextPreference(it)
|
EditTextPreference(it)
|
||||||
}
|
}
|
||||||
|
is MultiSelect -> {
|
||||||
|
MultiSelectPreference(it)
|
||||||
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,9 +125,35 @@ private fun ListPreference(list: List) {
|
|||||||
onClick = {
|
onClick = {
|
||||||
ChoiceDialog(
|
ChoiceDialog(
|
||||||
list.getOptions(),
|
list.getOptions(),
|
||||||
state.first,
|
state,
|
||||||
onSelected = list::setValue,
|
onSelected = list::updateState,
|
||||||
title = "Select choice"
|
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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package ca.gosyer.ui.sources.settings.model
|
|||||||
import ca.gosyer.data.models.sourcepreference.CheckBoxPreference
|
import ca.gosyer.data.models.sourcepreference.CheckBoxPreference
|
||||||
import ca.gosyer.data.models.sourcepreference.EditTextPreference
|
import ca.gosyer.data.models.sourcepreference.EditTextPreference
|
||||||
import ca.gosyer.data.models.sourcepreference.ListPreference
|
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.SourcePreference
|
||||||
import ca.gosyer.data.models.sourcepreference.SwitchPreference
|
import ca.gosyer.data.models.sourcepreference.SwitchPreference
|
||||||
import ca.gosyer.data.models.sourcepreference.TwoStateProps
|
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.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import java.util.Formatter
|
import java.util.Formatter
|
||||||
|
import kotlin.collections.List as KtList
|
||||||
|
|
||||||
sealed class SourceSettingsView<T, R : Any?> {
|
sealed class SourceSettingsView<T, R : Any?> {
|
||||||
abstract val index: Int
|
abstract val index: Int
|
||||||
@@ -76,12 +78,12 @@ sealed class SourceSettingsView<T, R : Any?> {
|
|||||||
override val title: String?,
|
override val title: String?,
|
||||||
override val subtitle: String?,
|
override val subtitle: String?,
|
||||||
override val props: ListPreference.ListProps
|
override val props: ListPreference.ListProps
|
||||||
) : SourceSettingsView<ListPreference.ListProps, Pair<String, String>>() {
|
) : SourceSettingsView<ListPreference.ListProps, String>() {
|
||||||
private val _state = MutableStateFlow(
|
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 val state: StateFlow<String> = _state.asStateFlow()
|
||||||
override fun updateState(value: Pair<String, String>) {
|
override fun updateState(value: String) {
|
||||||
_state.value = value
|
_state.value = value
|
||||||
}
|
}
|
||||||
internal constructor(index: Int, preference: ListPreference) : this(
|
internal constructor(index: Int, preference: ListPreference) : this(
|
||||||
@@ -92,14 +94,43 @@ sealed class SourceSettingsView<T, R : Any?> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
override val summary: String?
|
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 ->
|
fun getOptions() = props.entryValues.mapIndexed { index, s ->
|
||||||
s to props.entries[index]
|
s to props.entries[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setValue(value: String) {
|
fun toggleOption(key: String) {
|
||||||
updateState(value to props.entries[props.entryValues.indexOf(value)])
|
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 CheckBoxPreference -> SourceSettingsView.CheckBox(index, preference)
|
||||||
is SwitchPreference -> SourceSettingsView.Switch(index, preference)
|
is SwitchPreference -> SourceSettingsView.Switch(index, preference)
|
||||||
is ListPreference -> SourceSettingsView.List(index, preference)
|
is ListPreference -> SourceSettingsView.List(index, preference)
|
||||||
|
is MultiSelectListPreference -> SourceSettingsView.MultiSelect(index, preference)
|
||||||
is EditTextPreference -> SourceSettingsView.EditText(index, preference)
|
is EditTextPreference -> SourceSettingsView.EditText(index, preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user