fix: 搜索问题
This commit is contained in:
@@ -14,6 +14,7 @@ val Context.localDataStore: DataStore<Preferences> by preferencesDataStore(name
|
|||||||
object LocalPreferencesKeys {
|
object LocalPreferencesKeys {
|
||||||
val SHOW_THEME_REFACTOR_TIP = booleanPreferencesKey("show_theme_refactor_tip")
|
val SHOW_THEME_REFACTOR_TIP = booleanPreferencesKey("show_theme_refactor_tip")
|
||||||
val SEARCH_LAYOUT_MODE = intPreferencesKey("search_layout_mode")
|
val SEARCH_LAYOUT_MODE = intPreferencesKey("search_layout_mode")
|
||||||
|
val SEARCH_SCOPE = stringPreferencesKey("search_scope")
|
||||||
val MATCH_MODE = intPreferencesKey("match_mode")
|
val MATCH_MODE = intPreferencesKey("match_mode")
|
||||||
val EXPLORE_LAYOUT_MODE = intPreferencesKey("explore_layout_mode")
|
val EXPLORE_LAYOUT_MODE = intPreferencesKey("explore_layout_mode")
|
||||||
val EXPLORE_LAYOUT_GRID_PORTRAIT = intPreferencesKey("explore_layout_grid_portrait")
|
val EXPLORE_LAYOUT_GRID_PORTRAIT = intPreferencesKey("explore_layout_grid_portrait")
|
||||||
|
|||||||
@@ -1,40 +1,130 @@
|
|||||||
package io.legado.app.domain.model
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import io.legado.app.utils.fromJsonObject
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
|
||||||
data class BookSearchScope(val raw: String) {
|
data class BookSearchScope(val raw: String) {
|
||||||
|
|
||||||
val items: List<String>
|
private val parsed: ParsedSearchScope by lazy {
|
||||||
get() = raw.splitNotBlank(",").toList()
|
parse(raw)
|
||||||
|
|
||||||
val isAll: Boolean
|
|
||||||
get() = raw.isEmpty()
|
|
||||||
|
|
||||||
val isSource: Boolean
|
|
||||||
get() = items.isNotEmpty() && parseSourceItems().size == items.size
|
|
||||||
|
|
||||||
val groupNames: List<String>
|
|
||||||
get() = if (isSource) emptyList() else items
|
|
||||||
|
|
||||||
val sourceUrls: List<String>
|
|
||||||
get() = parseSourceItems().map { it.url }
|
|
||||||
|
|
||||||
private fun parseSourceItems(): List<ScopeSourceItem> {
|
|
||||||
return items.mapNotNull { item ->
|
|
||||||
val splitIndex = item.indexOf("::")
|
|
||||||
if (splitIndex <= 0 || splitIndex >= item.lastIndex) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
ScopeSourceItem(
|
|
||||||
name = item.substring(0, splitIndex),
|
|
||||||
url = item.substring(splitIndex + 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class ScopeSourceItem(
|
val items: List<String>
|
||||||
|
get() = when {
|
||||||
|
isSource -> sourceUrls
|
||||||
|
else -> groupNames
|
||||||
|
}
|
||||||
|
|
||||||
|
val isAll: Boolean
|
||||||
|
get() = parsed.isAll
|
||||||
|
|
||||||
|
val isSource: Boolean
|
||||||
|
get() = parsed.sources.isNotEmpty()
|
||||||
|
|
||||||
|
val groupNames: List<String>
|
||||||
|
get() = parsed.groups
|
||||||
|
|
||||||
|
val sourceUrls: List<String>
|
||||||
|
get() = parsed.sources.map { it.url }
|
||||||
|
|
||||||
|
val sourceNames: List<String>
|
||||||
|
get() = parsed.sources.map { it.name }
|
||||||
|
|
||||||
|
private data class ParsedSearchScope(
|
||||||
|
val groups: List<String> = emptyList(),
|
||||||
|
val sources: List<ScopeSourceItem> = emptyList(),
|
||||||
|
) {
|
||||||
|
val isAll: Boolean
|
||||||
|
get() = groups.isEmpty() && sources.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ScopeSourceItem(
|
||||||
val name: String,
|
val name: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class SerializedSearchScope(
|
||||||
|
val type: String = "",
|
||||||
|
val groups: List<String> = emptyList(),
|
||||||
|
val sources: List<ScopeSourceItem> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun encodeGroups(groups: List<String>): String {
|
||||||
|
val selected = groups.filter { it.isNotBlank() }
|
||||||
|
return if (selected.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
scopeGson.toJson(SerializedSearchScope(type = TYPE_GROUP, groups = selected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeSources(sources: List<ScopeSourceItem>): String {
|
||||||
|
val selected = sources.filter { it.url.isNotBlank() }
|
||||||
|
return if (selected.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
scopeGson.toJson(SerializedSearchScope(type = TYPE_SOURCE, sources = selected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parse(raw: String): ParsedSearchScope {
|
||||||
|
if (raw.isEmpty()) return ParsedSearchScope()
|
||||||
|
|
||||||
|
parseJson(raw)?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseLegacy(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseJson(raw: String): ParsedSearchScope? {
|
||||||
|
val json = raw.trim()
|
||||||
|
if (!json.startsWith("{") || !json.endsWith("}")) return null
|
||||||
|
|
||||||
|
return scopeGson.fromJsonObject<SerializedSearchScope>(json).getOrNull()?.let { scope ->
|
||||||
|
when (scope.type) {
|
||||||
|
TYPE_SOURCE -> ParsedSearchScope(
|
||||||
|
sources = scope.sources.filter { it.url.isNotBlank() }
|
||||||
|
)
|
||||||
|
|
||||||
|
TYPE_GROUP -> ParsedSearchScope(
|
||||||
|
groups = scope.groups.filter { it.isNotBlank() }
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLegacy(raw: String): ParsedSearchScope {
|
||||||
|
val rawItems = raw.split(",").filter { it.isNotBlank() }
|
||||||
|
val sourceItems = parseLegacySourceItems(rawItems)
|
||||||
|
if (rawItems.isNotEmpty() && sourceItems.size == rawItems.size) {
|
||||||
|
return ParsedSearchScope(sources = sourceItems)
|
||||||
|
}
|
||||||
|
return ParsedSearchScope(groups = raw.splitNotBlank(",").toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLegacySourceItems(items: List<String>): List<ScopeSourceItem> {
|
||||||
|
return items.mapNotNull { item ->
|
||||||
|
val splitIndex = item.indexOf("::")
|
||||||
|
if (splitIndex <= 0 || splitIndex >= item.lastIndex) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
ScopeSourceItem(
|
||||||
|
name = item.substring(0, splitIndex),
|
||||||
|
url = item.substring(splitIndex + 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TYPE_GROUP = "group"
|
||||||
|
private const val TYPE_SOURCE = "source"
|
||||||
|
private val scopeGson = GsonBuilder().disableHtmlEscaping().create()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -634,18 +634,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
val paddingDisplayCutouts
|
val paddingDisplayCutouts
|
||||||
get() = ReadConfig.paddingDisplayCutouts
|
get() = ReadConfig.paddingDisplayCutouts
|
||||||
|
|
||||||
var searchScope: String
|
|
||||||
get() = appCtx.getPrefString("searchScope") ?: ""
|
|
||||||
set(value) {
|
|
||||||
appCtx.putPrefString("searchScope", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchGroup: String
|
|
||||||
get() = appCtx.getPrefString("searchGroup") ?: ""
|
|
||||||
set(value) {
|
|
||||||
appCtx.putPrefString("searchGroup", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pageTouchSlop: Int
|
var pageTouchSlop: Int
|
||||||
get() = ReadConfig.pageTouchSlop
|
get() = ReadConfig.pageTouchSlop
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import io.legado.app.R
|
|||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.data.entities.BookSourcePart
|
import io.legado.app.data.entities.BookSourcePart
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.domain.model.BookSearchScope
|
||||||
import io.legado.app.utils.splitNotBlank
|
import io.legado.app.domain.model.BookSearchScope.ScopeSourceItem
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,14 +15,14 @@ import splitties.init.appCtx
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
data class SearchScope(private var scope: String) {
|
data class SearchScope(private var scope: String) {
|
||||||
|
|
||||||
constructor(groups: List<String>) : this(groups.joinToString(","))
|
constructor(groups: List<String>) : this(BookSearchScope.encodeGroups(groups))
|
||||||
|
|
||||||
constructor(source: BookSource) : this(
|
constructor(source: BookSource) : this(
|
||||||
encodeSourceToken(source.bookSourceName, source.bookSourceUrl)
|
encodeSourceScope(source.bookSourceName, source.bookSourceUrl)
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(source: BookSourcePart) : this(
|
constructor(source: BookSourcePart) : this(
|
||||||
encodeSourceToken(source.bookSourceName, source.bookSourceUrl)
|
encodeSourceScope(source.bookSourceName, source.bookSourceUrl)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@@ -34,43 +34,36 @@ data class SearchScope(private var scope: String) {
|
|||||||
fun update(scope: String, postValue: Boolean = true) {
|
fun update(scope: String, postValue: Boolean = true) {
|
||||||
this.scope = scope
|
this.scope = scope
|
||||||
if (postValue) stateLiveData.postValue(scope)
|
if (postValue) stateLiveData.postValue(scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(groups: List<String>) {
|
fun update(groups: List<String>) {
|
||||||
scope = groups.joinToString(",")
|
scope = BookSearchScope.encodeGroups(groups)
|
||||||
stateLiveData.postValue(scope)
|
stateLiveData.postValue(scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(source: BookSource) {
|
fun update(source: BookSource) {
|
||||||
scope = encodeSourceToken(source.bookSourceName, source.bookSourceUrl)
|
scope = encodeSourceScope(source.bookSourceName, source.bookSourceUrl)
|
||||||
stateLiveData.postValue(scope)
|
stateLiveData.postValue(scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(source: BookSourcePart) {
|
fun update(source: BookSourcePart) {
|
||||||
scope = encodeSourceToken(source.bookSourceName, source.bookSourceUrl)
|
scope = encodeSourceScope(source.bookSourceName, source.bookSourceUrl)
|
||||||
stateLiveData.postValue(scope)
|
stateLiveData.postValue(scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSources(sources: List<BookSourcePart>) {
|
fun updateSources(sources: List<BookSourcePart>) {
|
||||||
scope = encodeSourceScope(sources)
|
scope = encodeSourceScope(sources)
|
||||||
stateLiveData.postValue(scope)
|
stateLiveData.postValue(scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSource(): Boolean {
|
fun isSource(): Boolean {
|
||||||
val items = scopeItems()
|
return parsedScope().isSource
|
||||||
if (items.isEmpty()) return false
|
|
||||||
return parseSourceItems(items).size == items.size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val display: String
|
val display: String
|
||||||
get() {
|
get() {
|
||||||
if (isSource()) {
|
if (isSource()) {
|
||||||
val sourceNames = parseSourceItems().map { it.name }
|
val sourceNames = parsedScope().sourceNames
|
||||||
if (sourceNames.isEmpty()) return appCtx.getString(R.string.all_source)
|
if (sourceNames.isEmpty()) return appCtx.getString(R.string.all_source)
|
||||||
return sourceNames.joinToString(",")
|
return sourceNames.joinToString(",")
|
||||||
}
|
}
|
||||||
@@ -87,40 +80,26 @@ data class SearchScope(private var scope: String) {
|
|||||||
get() {
|
get() {
|
||||||
val list = arrayListOf<String>()
|
val list = arrayListOf<String>()
|
||||||
if (isSource()) {
|
if (isSource()) {
|
||||||
parseSourceItems().forEach {
|
list.addAll(parsedScope().sourceNames)
|
||||||
list.add(it.name)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scopeItems().forEach {
|
list.addAll(parsedScope().groupNames)
|
||||||
list.add(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceUrls: List<String>
|
val sourceUrls: List<String>
|
||||||
get() = parseSourceItems().map { it.url }
|
get() = parsedScope().sourceUrls
|
||||||
|
|
||||||
fun remove(scope: String) {
|
fun remove(scope: String) {
|
||||||
if (isSource()) {
|
if (isSource()) {
|
||||||
val sourceItems = parseSourceItems().filterNot {
|
val sourceItems = sourceItems().filterNot {
|
||||||
it.name == scope || it.url == scope
|
it.name == scope || it.url == scope
|
||||||
}
|
}
|
||||||
this.scope = sourceItems.joinToString(",") { "${it.name}::${it.url}" }
|
this.scope = BookSearchScope.encodeSources(sourceItems)
|
||||||
} else {
|
} else {
|
||||||
val stringBuilder = StringBuilder()
|
this.scope = BookSearchScope.encodeGroups(displayNames.filterNot { it == scope })
|
||||||
scopeItems().forEach {
|
|
||||||
if (it != scope) {
|
|
||||||
if (stringBuilder.isNotEmpty()) {
|
|
||||||
stringBuilder.append(",")
|
|
||||||
}
|
|
||||||
stringBuilder.append(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.scope = stringBuilder.toString()
|
|
||||||
}
|
}
|
||||||
stateLiveData.postValue(this.scope)
|
stateLiveData.postValue(this.scope)
|
||||||
save()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,13 +111,13 @@ data class SearchScope(private var scope: String) {
|
|||||||
list.addAll(appDb.bookSourceDao.allEnabledPart)
|
list.addAll(appDb.bookSourceDao.allEnabledPart)
|
||||||
} else {
|
} else {
|
||||||
if (isSource()) {
|
if (isSource()) {
|
||||||
parseSourceItems().forEach { sourceItem ->
|
sourceItems().forEach { sourceItem ->
|
||||||
appDb.bookSourceDao.getBookSourcePart(sourceItem.url)?.let { source ->
|
appDb.bookSourceDao.getBookSourcePart(sourceItem.url)?.let { source ->
|
||||||
list.add(source)
|
list.add(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val oldScope = scopeItems()
|
val oldScope = parsedScope().groupNames
|
||||||
val newScope = oldScope.filter {
|
val newScope = oldScope.filter {
|
||||||
val bookSources = appDb.bookSourceDao.getEnabledPartByGroup(it)
|
val bookSources = appDb.bookSourceDao.getEnabledPartByGroup(it)
|
||||||
list.addAll(bookSources)
|
list.addAll(bookSources)
|
||||||
@@ -163,54 +142,38 @@ data class SearchScope(private var scope: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isAll(): Boolean {
|
fun isAll(): Boolean {
|
||||||
return scope.isEmpty()
|
return parsedScope().isAll
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
private fun parsedScope(): BookSearchScope = BookSearchScope(scope)
|
||||||
AppConfig.searchScope = scope
|
|
||||||
if (isAll() || isSource() || scope.contains(",")) {
|
private fun sourceItems(): List<ScopeSourceItem> {
|
||||||
AppConfig.searchGroup = ""
|
val parsed = parsedScope()
|
||||||
} else {
|
return parsed.sourceNames.zip(parsed.sourceUrls) { name, url ->
|
||||||
AppConfig.searchGroup = scope
|
ScopeSourceItem(name, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scopeItems(): List<String> = scope.splitNotBlank(",").toList()
|
|
||||||
|
|
||||||
private fun parseSourceItems(
|
|
||||||
items: List<String> = scopeItems()
|
|
||||||
): List<ScopeSourceItem> {
|
|
||||||
return items.mapNotNull { item ->
|
|
||||||
val splitIndex = item.indexOf("::")
|
|
||||||
if (splitIndex <= 0 || splitIndex >= item.lastIndex) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
ScopeSourceItem(
|
|
||||||
name = item.substring(0, splitIndex),
|
|
||||||
url = item.substring(splitIndex + 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ScopeSourceItem(
|
|
||||||
val name: String,
|
|
||||||
val url: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun sanitizeSourceName(name: String): String {
|
private fun sanitizeSourceName(name: String): String {
|
||||||
return name.replace(":", "").replace(",", "")
|
return name.replace(":", "").replace(",", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encodeSourceToken(name: String, url: String): String {
|
private fun encodeSourceScope(name: String, url: String): String {
|
||||||
return "${sanitizeSourceName(name)}::${url}"
|
return BookSearchScope.encodeSources(
|
||||||
|
listOf(ScopeSourceItem(sanitizeSourceName(name), url))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encodeSourceScope(sources: List<BookSourcePart>): String {
|
private fun encodeSourceScope(sources: List<BookSourcePart>): String {
|
||||||
return sources.joinToString(",") { source ->
|
return BookSearchScope.encodeSources(
|
||||||
encodeSourceToken(source.bookSourceName, source.bookSourceUrl)
|
sources.map { source ->
|
||||||
}
|
ScopeSourceItem(
|
||||||
|
name = sanitizeSourceName(source.bookSourceName),
|
||||||
|
url = source.bookSourceUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import io.legado.app.domain.usecase.ExploreBooksUseCase
|
|||||||
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
||||||
import io.legado.app.domain.usecase.SearchBooksUseCase
|
import io.legado.app.domain.usecase.SearchBooksUseCase
|
||||||
import io.legado.app.domain.usecase.SearchRunEvent
|
import io.legado.app.domain.usecase.SearchRunEvent
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.ui.config.otherConfig.OtherConfig
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
@@ -69,10 +68,10 @@ class SearchViewModel(
|
|||||||
|
|
||||||
private val _uiState = MutableStateFlow(
|
private val _uiState = MutableStateFlow(
|
||||||
SearchUiState(
|
SearchUiState(
|
||||||
scopeDisplay = SearchScope(AppConfig.searchScope).display,
|
scopeDisplay = SearchScope("").display,
|
||||||
scopeDisplayNames = SearchScope(AppConfig.searchScope).displayNames.toImmutableList(),
|
scopeDisplayNames = SearchScope("").displayNames.toImmutableList(),
|
||||||
isAllScope = SearchScope(AppConfig.searchScope).isAll(),
|
isAllScope = SearchScope("").isAll(),
|
||||||
isSourceScope = SearchScope(AppConfig.searchScope).isSource(),
|
isSourceScope = SearchScope("").isSource(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
@@ -82,7 +81,9 @@ class SearchViewModel(
|
|||||||
|
|
||||||
private val queryFlow = MutableStateFlow("")
|
private val queryFlow = MutableStateFlow("")
|
||||||
private val bookshelfKeys = MutableStateFlow<Set<BookShelfKey>>(emptySet())
|
private val bookshelfKeys = MutableStateFlow<Set<BookShelfKey>>(emptySet())
|
||||||
private val searchScope = SearchScope(AppConfig.searchScope)
|
private var persistedSearchScopeRaw = ""
|
||||||
|
private var hasTemporaryScope = false
|
||||||
|
private val searchScope = SearchScope("")
|
||||||
private val searchControl = BookSearchControl()
|
private val searchControl = BookSearchControl()
|
||||||
private val searchResultBooks = LinkedHashMap<SearchResultKey, SearchBook>()
|
private val searchResultBooks = LinkedHashMap<SearchResultKey, SearchBook>()
|
||||||
|
|
||||||
@@ -93,6 +94,7 @@ class SearchViewModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
syncScopeState()
|
syncScopeState()
|
||||||
|
observeSearchScope()
|
||||||
observeEnabledGroups()
|
observeEnabledGroups()
|
||||||
observeEnabledSources()
|
observeEnabledSources()
|
||||||
observeBookshelf()
|
observeBookshelf()
|
||||||
@@ -205,6 +207,7 @@ class SearchViewModel(
|
|||||||
SearchIntent.SelectAllScope -> {
|
SearchIntent.SelectAllScope -> {
|
||||||
val oldScope = searchScope.toString()
|
val oldScope = searchScope.toString()
|
||||||
searchScope.update("")
|
searchScope.update("")
|
||||||
|
persistSearchScope()
|
||||||
syncScopeState(restartSearch = true, oldScope = oldScope)
|
syncScopeState(restartSearch = true, oldScope = oldScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +216,7 @@ class SearchViewModel(
|
|||||||
is SearchIntent.RemoveScopeItem -> {
|
is SearchIntent.RemoveScopeItem -> {
|
||||||
val oldScope = searchScope.toString()
|
val oldScope = searchScope.toString()
|
||||||
searchScope.remove(intent.scopeName)
|
searchScope.remove(intent.scopeName)
|
||||||
|
persistSearchScope()
|
||||||
syncScopeState(restartSearch = true, oldScope = oldScope)
|
syncScopeState(restartSearch = true, oldScope = oldScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,10 +317,15 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initialize(key: String?, scopeRaw: String?) {
|
private fun initialize(key: String?, scopeRaw: String?) {
|
||||||
scopeRaw?.let {
|
if (scopeRaw != null) {
|
||||||
searchScope.update(it, postValue = false)
|
hasTemporaryScope = true
|
||||||
|
searchScope.update(scopeRaw, postValue = false)
|
||||||
|
syncScopeState()
|
||||||
|
} else if (hasTemporaryScope) {
|
||||||
|
hasTemporaryScope = false
|
||||||
|
searchScope.update(persistedSearchScopeRaw, postValue = false)
|
||||||
|
syncScopeState()
|
||||||
}
|
}
|
||||||
syncScopeState()
|
|
||||||
|
|
||||||
// When the ViewModel already holds a non-empty committed query,
|
// When the ViewModel already holds a non-empty committed query,
|
||||||
// it means a search session is in progress or completed.
|
// it means a search session is in progress or completed.
|
||||||
@@ -613,6 +622,7 @@ class SearchViewModel(
|
|||||||
selected.add(groupName)
|
selected.add(groupName)
|
||||||
}
|
}
|
||||||
searchScope.update(selected.toList())
|
searchScope.update(selected.toList())
|
||||||
|
persistSearchScope()
|
||||||
syncScopeState(restartSearch = true, oldScope = oldScope)
|
syncScopeState(restartSearch = true, oldScope = oldScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,6 +648,7 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
searchScope.updateSources(selectedSources)
|
searchScope.updateSources(selectedSources)
|
||||||
}
|
}
|
||||||
|
persistSearchScope()
|
||||||
syncScopeState(restartSearch = true, oldScope = oldScope)
|
syncScopeState(restartSearch = true, oldScope = oldScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +664,7 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
searchScope.update("")
|
searchScope.update("")
|
||||||
|
persistSearchScope()
|
||||||
syncScopeState()
|
syncScopeState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +697,32 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeSearchScope() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
localPreferencesRepository
|
||||||
|
.getPreference(LocalPreferencesKeys.SEARCH_SCOPE, "")
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { scopeRaw ->
|
||||||
|
persistedSearchScopeRaw = scopeRaw
|
||||||
|
if (!hasTemporaryScope && scopeRaw != searchScope.toString()) {
|
||||||
|
searchScope.update(scopeRaw, postValue = false)
|
||||||
|
syncScopeState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun persistSearchScope() {
|
||||||
|
hasTemporaryScope = false
|
||||||
|
persistedSearchScopeRaw = searchScope.toString()
|
||||||
|
viewModelScope.launch {
|
||||||
|
localPreferencesRepository.updatePreference(
|
||||||
|
LocalPreferencesKeys.SEARCH_SCOPE,
|
||||||
|
searchScope.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<SearchBook>.toSearchResultItems(
|
private fun List<SearchBook>.toSearchResultItems(
|
||||||
shelf: Set<BookShelfKey>
|
shelf: Set<BookShelfKey>
|
||||||
): List<SearchResultItemUi> {
|
): List<SearchResultItemUi> {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import io.legado.app.domain.usecase.BookSearchControl
|
|||||||
import io.legado.app.domain.usecase.BookSearchRequest
|
import io.legado.app.domain.usecase.BookSearchRequest
|
||||||
import io.legado.app.domain.usecase.SearchBooksUseCase
|
import io.legado.app.domain.usecase.SearchBooksUseCase
|
||||||
import io.legado.app.domain.usecase.SearchRunEvent
|
import io.legado.app.domain.usecase.SearchRunEvent
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.ui.config.otherConfig.OtherConfig
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.fromJsonObject
|
import io.legado.app.utils.fromJsonObject
|
||||||
@@ -82,7 +81,11 @@ class BookSearchWebSocket(private val session: DefaultWebSocketServerSession) :
|
|||||||
BookSearchRequest(
|
BookSearchRequest(
|
||||||
keyword = key,
|
keyword = key,
|
||||||
page = 1,
|
page = 1,
|
||||||
scope = BookSearchScope(AppConfig.searchScope),
|
scope = BookSearchScope(
|
||||||
|
localPreferencesRepository
|
||||||
|
.getPreference(LocalPreferencesKeys.SEARCH_SCOPE, "")
|
||||||
|
.first()
|
||||||
|
),
|
||||||
matchMode = MatchMode.of(
|
matchMode = MatchMode.of(
|
||||||
localPreferencesRepository
|
localPreferencesRepository
|
||||||
.getPreference(
|
.getPreference(
|
||||||
|
|||||||
Reference in New Issue
Block a user