mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2026-01-29 15:04:16 +01:00
Rewrite filter and preference mutations (#577)
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.FilterChange
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.graphql.types.PreferenceObject
|
||||
import suwayomi.tachidesk.graphql.types.Preference
|
||||
import suwayomi.tachidesk.graphql.types.preferenceOf
|
||||
import suwayomi.tachidesk.graphql.types.updateFilterList
|
||||
import suwayomi.tachidesk.manga.impl.MangaList.insertOrGet
|
||||
import suwayomi.tachidesk.manga.impl.Search
|
||||
import suwayomi.tachidesk.manga.impl.Source
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
@@ -20,10 +27,6 @@ class SourceMutation {
|
||||
POPULAR,
|
||||
LATEST
|
||||
}
|
||||
data class FilterChange(
|
||||
val position: Int,
|
||||
val state: String
|
||||
)
|
||||
data class FetchSourceMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val source: Long,
|
||||
@@ -50,11 +53,7 @@ class SourceMutation {
|
||||
source.fetchSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = Search.buildFilterList(
|
||||
sourceId = sourceId,
|
||||
changes = filters?.map { Search.FilterChange(it.position, it.state) }
|
||||
.orEmpty()
|
||||
)
|
||||
filters = updateFilterList(source, filters)
|
||||
).awaitSingle()
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
@@ -85,7 +84,11 @@ class SourceMutation {
|
||||
|
||||
data class SourcePreferenceChange(
|
||||
val position: Int,
|
||||
val state: String
|
||||
val switchState: Boolean? = null,
|
||||
val checkBoxState: Boolean? = null,
|
||||
val editTextState: String? = null,
|
||||
val listState: String? = null,
|
||||
val multiSelectState: List<String>? = null
|
||||
)
|
||||
data class UpdateSourcePreferenceInput(
|
||||
val clientMutationId: String? = null,
|
||||
@@ -94,7 +97,7 @@ class SourceMutation {
|
||||
)
|
||||
data class UpdateSourcePreferencePayload(
|
||||
val clientMutationId: String?,
|
||||
val preferences: List<PreferenceObject>
|
||||
val preferences: List<Preference>
|
||||
)
|
||||
|
||||
fun updateSourcePreference(
|
||||
@@ -102,11 +105,20 @@ class SourceMutation {
|
||||
): UpdateSourcePreferencePayload {
|
||||
val (clientMutationId, sourceId, change) = input
|
||||
|
||||
Source.setSourcePreference(sourceId, Source.SourcePreferenceChange(change.position, change.state))
|
||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||
when (preference) {
|
||||
is SwitchPreferenceCompat -> change.switchState
|
||||
is CheckBoxPreference -> change.checkBoxState
|
||||
is EditTextPreference -> change.editTextState
|
||||
is ListPreference -> change.listState
|
||||
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
||||
}
|
||||
|
||||
return UpdateSourcePreferencePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
preferences = Source.getSourcePreferences(sourceId).map { PreferenceObject(it.type, it.props) }
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.expediagroup.graphql.generator.SchemaGeneratorConfig
|
||||
import com.expediagroup.graphql.generator.TopLevelObject
|
||||
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
|
||||
import com.expediagroup.graphql.generator.toSchema
|
||||
import graphql.scalars.ExtendedScalars
|
||||
import graphql.schema.GraphQLType
|
||||
import suwayomi.tachidesk.graphql.mutations.CategoryMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.ChapterMutation
|
||||
@@ -36,7 +35,6 @@ class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
|
||||
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
|
||||
Long::class -> GraphQLLongAsString // encode to string for JS
|
||||
Cursor::class -> GraphQLCursor
|
||||
Any::class -> ExtendedScalars.Json
|
||||
else -> super.willGenerateGraphQLType(type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.graphql.types
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.select
|
||||
@@ -18,14 +19,21 @@ import suwayomi.tachidesk.graphql.server.primitives.Edge
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||
import suwayomi.tachidesk.graphql.server.primitives.NodeList
|
||||
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||
import suwayomi.tachidesk.manga.impl.Search
|
||||
import suwayomi.tachidesk.manga.impl.Source
|
||||
import suwayomi.tachidesk.manga.impl.Source.getSourcePreferencesRaw
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import androidx.preference.CheckBoxPreference as SourceCheckBoxPreference
|
||||
import androidx.preference.EditTextPreference as SourceEditTextPreference
|
||||
import androidx.preference.ListPreference as SourceListPreference
|
||||
import androidx.preference.MultiSelectListPreference as SourceMultiSelectListPreference
|
||||
import androidx.preference.Preference as SourcePreference
|
||||
import androidx.preference.SwitchPreferenceCompat as SourceSwitchPreference
|
||||
import eu.kanade.tachiyomi.source.model.Filter as SourceFilter
|
||||
|
||||
class SourceType(
|
||||
val id: Long,
|
||||
@@ -67,12 +75,12 @@ class SourceType(
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Long, ExtensionType>("ExtensionForSourceDataLoader", id)
|
||||
}
|
||||
|
||||
fun preferences(): List<PreferenceObject> {
|
||||
return Source.getSourcePreferences(id).map { PreferenceObject(it.type, it.props) }
|
||||
fun preferences(): List<Preference> {
|
||||
return getSourcePreferencesRaw(id).map { preferenceOf(it) }
|
||||
}
|
||||
|
||||
fun filters(): List<FilterObject> {
|
||||
return Search.getFilterList(id, false).map { FilterObject(it.type, it.filter) }
|
||||
fun filters(): List<Filter> {
|
||||
return getCatalogueSourceOrStub(id).getFilterList().map { filterOf(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +141,255 @@ data class SourceNodeList(
|
||||
}
|
||||
}
|
||||
|
||||
data class PreferenceObject(
|
||||
val type: String,
|
||||
val props: Any
|
||||
sealed interface Filter
|
||||
|
||||
data class HeaderFilter(val name: String) : Filter
|
||||
|
||||
data class SeparatorFilter(val name: String) : Filter
|
||||
|
||||
data class SelectFilter(val name: String, val values: List<String>, val default: Int) : Filter
|
||||
|
||||
data class TextFilter(val name: String, val default: String) : Filter
|
||||
|
||||
data class CheckBoxFilter(val name: String, val default: Boolean) : Filter
|
||||
|
||||
enum class TriState {
|
||||
IGNORE,
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
}
|
||||
|
||||
data class TriStateFilter(val name: String, val default: TriState) : Filter
|
||||
|
||||
data class SortFilter(val name: String, val values: List<String>, val default: SortSelection?) : Filter {
|
||||
data class SortSelection(val index: Int, val ascending: Boolean) {
|
||||
constructor(selection: SourceFilter.Sort.Selection) :
|
||||
this(selection.index, selection.ascending)
|
||||
}
|
||||
}
|
||||
|
||||
data class GroupFilter(val name: String, val filters: List<Filter>) : Filter
|
||||
|
||||
fun filterOf(filter: SourceFilter<*>): Filter {
|
||||
return when (filter) {
|
||||
is SourceFilter.Header -> HeaderFilter(filter.name)
|
||||
is SourceFilter.Separator -> SeparatorFilter(filter.name)
|
||||
is SourceFilter.Select<*> -> SelectFilter(filter.name, filter.displayValues, filter.state)
|
||||
is SourceFilter.Text -> TextFilter(filter.name, filter.state)
|
||||
is SourceFilter.CheckBox -> CheckBoxFilter(filter.name, filter.state)
|
||||
is SourceFilter.TriState -> TriStateFilter(
|
||||
filter.name,
|
||||
when (filter.state) {
|
||||
SourceFilter.TriState.STATE_INCLUDE -> TriState.INCLUDE
|
||||
SourceFilter.TriState.STATE_EXCLUDE -> TriState.EXCLUDE
|
||||
else -> TriState.IGNORE
|
||||
}
|
||||
)
|
||||
is SourceFilter.Group<*> -> GroupFilter(
|
||||
filter.name,
|
||||
filter.state.map { filterOf(it as SourceFilter<*>) }
|
||||
)
|
||||
is SourceFilter.Sort -> SortFilter(filter.name, filter.values.asList(), filter.state?.let(SortFilter::SortSelection))
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
}
|
||||
}
|
||||
|
||||
/*sealed interface FilterChange {
|
||||
val position: Int
|
||||
}
|
||||
|
||||
data class GroupFilterChange(
|
||||
override val position: Int,
|
||||
val filter: FilterChange
|
||||
) : FilterChange
|
||||
|
||||
data class TriStateFilterChange(
|
||||
override val position: Int,
|
||||
val state: TriState
|
||||
) : FilterChange
|
||||
|
||||
data class CheckBoxFilterChange(
|
||||
override val position: Int,
|
||||
val state: Boolean
|
||||
) : FilterChange
|
||||
|
||||
data class SelectFilterChange(
|
||||
override val position: Int,
|
||||
val state: Int
|
||||
) : FilterChange
|
||||
|
||||
data class TextFilterChange(
|
||||
override val position: Int,
|
||||
val state: String
|
||||
) : FilterChange
|
||||
|
||||
data class SortFilterChange(
|
||||
override val position: Int,
|
||||
val state: SortFilter.SortSelection
|
||||
) : FilterChange
|
||||
|
||||
private inline fun <reified T> filterChangeAs(filterChange: FilterChange): T {
|
||||
return filterChange as? T ?: throw Exception("Expected ${T::class.simpleName}, found ${filterChange::class.simpleName}")
|
||||
}*/
|
||||
|
||||
data class FilterChange(
|
||||
val position: Int,
|
||||
val selectState: Int? = null,
|
||||
val textState: String? = null,
|
||||
val checkBoxState: Boolean? = null,
|
||||
val triState: TriState? = null,
|
||||
val sortState: SortFilter.SortSelection? = null,
|
||||
val groupChange: FilterChange? = null
|
||||
)
|
||||
|
||||
data class FilterObject(
|
||||
val type: String,
|
||||
val filter: Any
|
||||
)
|
||||
fun updateFilterList(source: CatalogueSource, changes: List<FilterChange>?): FilterList {
|
||||
val filterList = source.getFilterList()
|
||||
|
||||
changes?.forEach { change ->
|
||||
when (val filter = filterList[1]) {
|
||||
is SourceFilter.Header -> {
|
||||
// NOOP
|
||||
}
|
||||
is SourceFilter.Separator -> {
|
||||
// NOOP
|
||||
}
|
||||
is SourceFilter.Select<*> -> {
|
||||
filter.state = change.selectState ?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Text -> {
|
||||
filter.state = change.textState ?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.CheckBox -> {
|
||||
filter.state = change.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.TriState -> {
|
||||
filter.state = change.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Group<*> -> {
|
||||
val groupChange = change.groupChange ?: throw Exception("Expected group change at position ${change.position}")
|
||||
|
||||
when (val groupFilter = filter.state[1]) {
|
||||
is SourceFilter.CheckBox -> {
|
||||
groupFilter.state = groupChange.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.TriState -> {
|
||||
groupFilter.state = groupChange.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Text -> {
|
||||
groupFilter.state = groupChange.textState ?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Select<*> -> {
|
||||
groupFilter.state = groupChange.selectState ?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
is SourceFilter.Sort -> {
|
||||
filter.state = change.sortState?.run {
|
||||
SourceFilter.Sort.Selection(index, ascending)
|
||||
} ?: throw Exception("Expected sort state change at position ${change.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
return filterList
|
||||
}
|
||||
|
||||
sealed interface Preference
|
||||
|
||||
data class SwitchPreference(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val summary: String?,
|
||||
val currentValue: Boolean?,
|
||||
val default: Boolean
|
||||
) : Preference
|
||||
|
||||
data class CheckBoxPreference(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val summary: String?,
|
||||
val currentValue: Boolean?,
|
||||
val default: Boolean
|
||||
) : Preference
|
||||
|
||||
data class EditTextPreference(
|
||||
val key: String,
|
||||
val title: String?,
|
||||
val summary: String?,
|
||||
val currentValue: String?,
|
||||
val default: String?,
|
||||
val dialogTitle: String?,
|
||||
val dialogMessage: String?,
|
||||
val text: String?
|
||||
) : Preference
|
||||
|
||||
data class ListPreference(
|
||||
val key: String,
|
||||
val title: String?,
|
||||
val summary: String?,
|
||||
val currentValue: String?,
|
||||
val default: String?,
|
||||
val entries: List<String>,
|
||||
val entryValues: List<String>
|
||||
) : Preference
|
||||
|
||||
data class MultiSelectListPreference(
|
||||
val key: String,
|
||||
val title: String?,
|
||||
val summary: String?,
|
||||
val currentValue: List<String>?,
|
||||
val default: List<String>?,
|
||||
val dialogTitle: String?,
|
||||
val dialogMessage: String?,
|
||||
val entries: List<String>,
|
||||
val entryValues: List<String>
|
||||
) : Preference
|
||||
|
||||
fun preferenceOf(preference: SourcePreference): Preference {
|
||||
return when (preference) {
|
||||
is SourceSwitchPreference -> SwitchPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean
|
||||
)
|
||||
is SourceCheckBoxPreference -> CheckBoxPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean
|
||||
)
|
||||
is SourceEditTextPreference -> EditTextPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.text
|
||||
)
|
||||
is SourceListPreference -> ListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() }
|
||||
)
|
||||
is SourceMultiSelectListPreference -> MultiSelectListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
(preference.currentValue as Collection<*>?)?.map { it.toString() },
|
||||
(preference.defaultValue as Collection<*>?)?.map { it.toString() },
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() }
|
||||
)
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ object SourceController {
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
|
||||
ctx.json(Source.setSourcePreference(sourceId, preferenceChange))
|
||||
ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
|
||||
@@ -9,6 +9,7 @@ package suwayomi.tachidesk.manga.impl
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.getPreferenceKey
|
||||
@@ -96,6 +97,12 @@ object Source {
|
||||
* Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap]
|
||||
*/
|
||||
fun getSourcePreferences(sourceId: Long): List<PreferenceObject> {
|
||||
return getSourcePreferencesRaw(sourceId).map {
|
||||
PreferenceObject(it::class.java.simpleName, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSourcePreferencesRaw(sourceId: Long): List<Preference> {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
|
||||
if (source is ConfigurableSource) {
|
||||
@@ -109,9 +116,7 @@ object Source {
|
||||
|
||||
preferenceScreenMap[sourceId] = screen
|
||||
|
||||
return screen.preferences.map {
|
||||
PreferenceObject(it::class.java.simpleName, it)
|
||||
}
|
||||
return screen.preferences
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
@@ -123,18 +128,25 @@ object Source {
|
||||
|
||||
private val jsonMapper by DI.global.instance<JsonMapper>()
|
||||
|
||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||
fun setSourcePreference(sourceId: Long, change: SourcePreferenceChange) {
|
||||
val screen = preferenceScreenMap[sourceId]!!
|
||||
val pref = screen.preferences[change.position]
|
||||
|
||||
println(jsonMapper::class.java.name)
|
||||
val newValue = when (pref.defaultValueType) {
|
||||
"String" -> change.value
|
||||
"Boolean" -> change.value.toBoolean()
|
||||
"Set<String>" -> jsonMapper.fromJsonString(change.value, List::class.java as Class<List<String>>).toSet()
|
||||
else -> throw RuntimeException("Unsupported type conversion")
|
||||
fun setSourcePreference(
|
||||
sourceId: Long,
|
||||
position: Int,
|
||||
value: String,
|
||||
getValue: (Preference) -> Any = { pref ->
|
||||
println(jsonMapper::class.java.name)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (pref.defaultValueType) {
|
||||
"String" -> value
|
||||
"Boolean" -> value.toBoolean()
|
||||
"Set<String>" -> jsonMapper.fromJsonString(value, List::class.java as Class<List<String>>).toSet()
|
||||
else -> throw RuntimeException("Unsupported type conversion")
|
||||
}
|
||||
}
|
||||
) {
|
||||
val screen = preferenceScreenMap[sourceId]!!
|
||||
val pref = screen.preferences[position]
|
||||
|
||||
val newValue = getValue(pref)
|
||||
|
||||
pref.saveNewValue(newValue)
|
||||
pref.callChangeListener(newValue)
|
||||
|
||||
Reference in New Issue
Block a user