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 {
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user