Dedupe Global/MigrateSearchContent composables
This commit is contained in:
parent
30f845139d
commit
8b46e8edad
@ -24,6 +24,7 @@ import androidx.compose.runtime.State
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
@ -43,7 +44,6 @@ import tachiyomi.presentation.core.components.material.padding
|
|||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchScreen(
|
fun GlobalSearchScreen(
|
||||||
state: GlobalSearchScreenModel.State,
|
state: GlobalSearchScreenModel.State,
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
@ -129,7 +129,7 @@ fun GlobalSearchScreen(
|
|||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalSearchContent(
|
GlobalSearchContent(
|
||||||
items = items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -140,7 +140,8 @@ fun GlobalSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GlobalSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
|
sourceId: Long? = null,
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
@ -154,7 +155,7 @@ private fun GlobalSearchContent(
|
|||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = source.name,
|
title = sourceId?.let { "▶ ${source.name}".takeIf { source.id == sourceId } } ?: source.name,
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
@ -164,14 +165,7 @@ private fun GlobalSearchContent(
|
|||||||
}
|
}
|
||||||
is SearchItemResult.Success -> {
|
is SearchItemResult.Success -> {
|
||||||
if (result.isEmpty) {
|
if (result.isEmpty) {
|
||||||
Text(
|
GlobalSearchEmptyResultItem()
|
||||||
text = stringResource(R.string.no_results_found),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@GlobalSearchResultItem
|
return@GlobalSearchResultItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,17 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSearchScreen(
|
fun MigrateSearchScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: MigrateSearchState,
|
state: MigrateSearchScreenModel.State,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
@ -41,8 +32,8 @@ fun MigrateSearchScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
MigrateSearchContent(
|
GlobalSearchContent(
|
||||||
sourceId = state.manga?.source ?: -1,
|
sourceId = state.manga?.source,
|
||||||
items = state.items,
|
items = state.items,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
@ -52,50 +43,3 @@ fun MigrateSearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MigrateSearchContent(
|
|
||||||
sourceId: Long,
|
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onLongClickItem: (Manga) -> Unit,
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
items.forEach { (source, result) ->
|
|
||||||
item(key = source.id) {
|
|
||||||
GlobalSearchResultItem(
|
|
||||||
title = if (source.id == sourceId) "▶ ${source.name}" else source.name,
|
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
|
||||||
onClick = { onClickSource(source) },
|
|
||||||
) {
|
|
||||||
when (result) {
|
|
||||||
SearchItemResult.Loading -> {
|
|
||||||
GlobalSearchLoadingResultItem()
|
|
||||||
}
|
|
||||||
is SearchItemResult.Success -> {
|
|
||||||
if (result.isEmpty) {
|
|
||||||
GlobalSearchEmptyResultItem()
|
|
||||||
return@GlobalSearchResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalSearchCardRow(
|
|
||||||
titles = result.result,
|
|
||||||
getManga = getManga,
|
|
||||||
onClick = onClickItem,
|
|
||||||
onLongClick = onLongClickItem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is SearchItemResult.Error -> {
|
|
||||||
GlobalSearchErrorResultItem(message = result.throwable.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,15 +17,13 @@ import uy.kohesive.injekt.api.get
|
|||||||
|
|
||||||
class MigrateSearchScreenModel(
|
class MigrateSearchScreenModel(
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
initialExtensionFilter: String = "",
|
|
||||||
preferences: BasePreferences = Injekt.get(),
|
preferences: BasePreferences = Injekt.get(),
|
||||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
) : SearchScreenModel<MigrateSearchState>(MigrateSearchState()) {
|
) : SearchScreenModel<MigrateSearchScreenModel.State>(State()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
extensionFilter = initialExtensionFilter
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val manga = getManga.await(mangaId)!!
|
val manga = getManga.await(mangaId)!!
|
||||||
|
|
||||||
@ -73,21 +71,19 @@ class MigrateSearchScreenModel(
|
|||||||
it.copy(dialog = dialog)
|
it.copy(dialog = dialog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val manga: Manga? = null,
|
||||||
|
val searchQuery: String? = null,
|
||||||
|
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
||||||
|
val dialog: MigrateSearchDialog? = null,
|
||||||
|
) {
|
||||||
|
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||||
|
val total: Int = items.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class MigrateSearchDialog {
|
sealed class MigrateSearchDialog {
|
||||||
data class Migrate(val manga: Manga) : MigrateSearchDialog()
|
data class Migrate(val manga: Manga) : MigrateSearchDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class MigrateSearchState(
|
|
||||||
val manga: Manga? = null,
|
|
||||||
val searchQuery: String? = null,
|
|
||||||
val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
|
|
||||||
val dialog: MigrateSearchDialog? = null,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
|
||||||
|
|
||||||
val total: Int = items.size
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,7 @@ import tachiyomi.presentation.core.screens.LoadingScreen
|
|||||||
|
|
||||||
class GlobalSearchScreen(
|
class GlobalSearchScreen(
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
private val extensionFilter: String = "",
|
private val extensionFilter: String? = null,
|
||||||
) : Screen() {
|
) : Screen() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -33,9 +33,8 @@ class GlobalSearchScreen(
|
|||||||
}
|
}
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
var showSingleLoadingScreen by remember {
|
var showSingleLoadingScreen by remember {
|
||||||
mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1)
|
mutableStateOf(searchQuery.isNotEmpty() && !extensionFilter.isNullOrEmpty() && state.total == 1)
|
||||||
}
|
}
|
||||||
val filteredSources by screenModel.searchPagerFlow.collectAsState()
|
|
||||||
|
|
||||||
if (showSingleLoadingScreen) {
|
if (showSingleLoadingScreen) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@ -58,7 +57,6 @@ class GlobalSearchScreen(
|
|||||||
} else {
|
} else {
|
||||||
GlobalSearchScreen(
|
GlobalSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
items = filteredSources,
|
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||||
onSearch = screenModel::search,
|
onSearch = screenModel::search,
|
||||||
|
@ -3,12 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -16,7 +11,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
|
|
||||||
class GlobalSearchScreenModel(
|
class GlobalSearchScreenModel(
|
||||||
initialQuery: String = "",
|
initialQuery: String = "",
|
||||||
initialExtensionFilter: String = "",
|
initialExtensionFilter: String? = null,
|
||||||
preferences: BasePreferences = Injekt.get(),
|
preferences: BasePreferences = Injekt.get(),
|
||||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
@ -24,17 +19,9 @@ class GlobalSearchScreenModel(
|
|||||||
|
|
||||||
val incognitoMode = preferences.incognitoMode()
|
val incognitoMode = preferences.incognitoMode()
|
||||||
val lastUsedSourceId = sourcePreferences.lastUsedSource()
|
val lastUsedSourceId = sourcePreferences.lastUsedSource()
|
||||||
|
|
||||||
val searchPagerFlow = state.map { Pair(it.onlyShowHasResults, it.items) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.map { (onlyShowHasResults, items) ->
|
|
||||||
items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
|
||||||
}
|
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.Lazily, state.value.items)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
extensionFilter = initialExtensionFilter
|
extensionFilter = initialExtensionFilter
|
||||||
if (initialQuery.isNotBlank() || initialExtensionFilter.isNotBlank()) {
|
if (initialQuery.isNotBlank() || !initialExtensionFilter.isNullOrBlank()) {
|
||||||
search(initialQuery)
|
search(initialQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,10 +64,6 @@ class GlobalSearchScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
|
||||||
return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val searchQuery: String? = null,
|
val searchQuery: String? = null,
|
||||||
@ -90,5 +73,10 @@ class GlobalSearchScreenModel(
|
|||||||
) {
|
) {
|
||||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||||
val total: Int = items.size
|
val total: Int = items.size
|
||||||
|
val filteredItems = items.filter { (_, result) -> result.isVisible(onlyShowHasResults) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SearchItemResult.isVisible(onlyShowHasResults: Boolean): Boolean {
|
||||||
|
return !onlyShowHasResults || (this is SearchItemResult.Success && !this.isEmpty)
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@ abstract class SearchScreenModel<T>(
|
|||||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||||
|
|
||||||
protected var query: String? = null
|
protected var query: String? = null
|
||||||
protected lateinit var extensionFilter: String
|
protected var extensionFilter: String? = null
|
||||||
|
|
||||||
private val sources by lazy { getSelectedSources() }
|
private val sources by lazy { getSelectedSources() }
|
||||||
private val pinnedSources by lazy { sourcePreferences.pinnedSources().get() }
|
private val pinnedSources by lazy { sourcePreferences.pinnedSources().get() }
|
||||||
@ -63,11 +63,10 @@ abstract class SearchScreenModel<T>(
|
|||||||
abstract fun getEnabledSources(): List<CatalogueSource>
|
abstract fun getEnabledSources(): List<CatalogueSource>
|
||||||
|
|
||||||
private fun getSelectedSources(): List<CatalogueSource> {
|
private fun getSelectedSources(): List<CatalogueSource> {
|
||||||
val filter = extensionFilter
|
|
||||||
|
|
||||||
val enabledSources = getEnabledSources()
|
val enabledSources = getEnabledSources()
|
||||||
|
|
||||||
if (filter.isEmpty()) {
|
val filter = extensionFilter
|
||||||
|
if (filter.isNullOrEmpty()) {
|
||||||
return enabledSources
|
return enabledSources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ class MainActivity : BaseActivity() {
|
|||||||
INTENT_SEARCH -> {
|
INTENT_SEARCH -> {
|
||||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||||
if (!query.isNullOrEmpty()) {
|
if (!query.isNullOrEmpty()) {
|
||||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
||||||
navigator.popUntilRoot()
|
navigator.popUntilRoot()
|
||||||
navigator.push(GlobalSearchScreen(query, filter))
|
navigator.push(GlobalSearchScreen(query, filter))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user