Dedupe Global/MigrateSearchContent composables

This commit is contained in:
arkon 2023-07-16 16:37:40 -04:00
parent 30f845139d
commit 8b46e8edad
7 changed files with 35 additions and 116 deletions

View file

@ -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
} }

View file

@ -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)
}
}
}
}
}
}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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
} }

View file

@ -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))
} }