Make SourceState similar to MigrateState (#7053)

* make `SourceState` similar to `MigrateState`

* Review Changes

(cherry picked from commit bd45bf7407716bceec5cc226d3680676bb211fea)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/source/SourceScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt
This commit is contained in:
FourTOne5 2022-05-02 08:34:58 +06:00 committed by Jobobby04
parent 7e380582a6
commit 222f8a7d7a
3 changed files with 70 additions and 106 deletions

View File

@ -47,7 +47,7 @@ import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.SourcePresenter
import eu.kanade.tachiyomi.ui.browse.source.UiModel
import eu.kanade.tachiyomi.ui.browse.source.SourceState
import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable
@ -63,16 +63,15 @@ fun SourceScreen(
) {
val state by presenter.state.collectAsState()
when {
state.isLoading -> LoadingScreen()
state.hasError -> Text(text = state.error!!.message!!)
state.isEmpty -> EmptyScreen(message = "")
else -> SourceList(
when (state) {
is SourceState.Loading -> LoadingScreen()
is SourceState.Error -> Text(text = (state as SourceState.Error).error.message!!)
is SourceState.Success -> SourceList(
nestedScrollConnection = nestedScrollInterop,
list = state.sources,
categories = state.sourceCategories,
showPin = state.showPin,
showLatest = state.showLatest,
list = (state as SourceState.Success).uiModels,
categories = (state as SourceState.Success).sourceCategories,
showPin = (state as SourceState.Success).showPin,
showLatest = (state as SourceState.Success).showLatest,
onClickItem = onClickItem,
onClickDisable = onClickDisable,
onClickLatest = onClickLatest,
@ -86,7 +85,7 @@ fun SourceScreen(
@Composable
fun SourceList(
nestedScrollConnection: NestedScrollConnection,
list: List<UiModel>,
list: List<SourceUiModel>,
categories: List<String>,
showPin: Boolean,
showLatest: Boolean,
@ -97,6 +96,11 @@ fun SourceList(
onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit
) {
if (list.isEmpty()) {
EmptyScreen(textResource = R.string.source_empty_screen)
return
}
val (sourceState, setSourceState) = remember { mutableStateOf<Source?>(null) }
// SY -->
val (sourceCategoriesState, setSourceCategoriesState) = remember { mutableStateOf<Source?>(null) }
@ -110,26 +114,26 @@ fun SourceList(
items = list,
contentType = {
when (it) {
is UiModel.Header -> "header"
is UiModel.Item -> "item"
is SourceUiModel.Header -> "header"
is SourceUiModel.Item -> "item"
}
},
key = {
when (it) {
is UiModel.Header -> it.hashCode()
is UiModel.Item -> it.source.key()
is SourceUiModel.Header -> it.hashCode()
is SourceUiModel.Item -> it.source.key()
}
}
) { model ->
when (model) {
is UiModel.Header -> {
is SourceUiModel.Header -> {
SourceHeader(
modifier = Modifier.animateItemPlacement(),
language = model.language,
isCategory = model.isCategory
)
}
is UiModel.Item -> SourceItem(
is SourceUiModel.Item -> SourceItem(
modifier = Modifier.animateItemPlacement(),
source = model.source,
showLatest = showLatest,
@ -337,6 +341,11 @@ fun SourceOptionsDialog(
)
}
sealed class SourceUiModel {
data class Item(val source: Source) : SourceUiModel()
data class Header(val language: String, val isCategory: Boolean) : SourceUiModel()
}
// SY -->
@Composable
fun SourceCategoriesDialog(

View File

@ -10,14 +10,17 @@ import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.source.model.Pin
import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.source.SourceUiModel
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.TreeMap
@ -39,50 +42,28 @@ class SourcePresenter(
// SY <--
) : BasePresenter<SourceController>() {
private val _state: MutableStateFlow<SourceState> = MutableStateFlow(SourceState.EMPTY)
private val _state: MutableStateFlow<SourceState> = MutableStateFlow(SourceState.Loading)
val state: StateFlow<SourceState> = _state.asStateFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
presenterScope.launchIO {
getEnabledSources.subscribe()
.catch { exception ->
_state.update { state ->
state.copy(sources = listOf(), error = exception)
}
}
.collectLatest(::collectLatestSources)
}
// SY -->
presenterScope.launchIO {
getSourceCategories.subscribe()
.catch { exception ->
_state.update { state ->
state.copy(sources = listOf(), error = exception)
}
}
.collectLatest(::updateCategories)
}
presenterScope.launchIO {
_state.update { state ->
state.copy(
showPin = controllerMode == SourceController.Mode.CATALOGUE,
)
combine(
getEnabledSources.subscribe(),
getSourceCategories.subscribe(),
getShowLatest.subscribe(controllerMode),
flowOf(controllerMode == SourceController.Mode.CATALOGUE),
::collectLatestSources
)
.catch { exception ->
_state.emit(SourceState.Error(exception))
}
}
presenterScope.launchIO {
getShowLatest.subscribe(mode = controllerMode)
.catch { exception ->
_state.update { state ->
state.copy(sources = listOf(), error = exception)
}
}
.collectLatest(::updateShowLatest)
}
.flowOn(Dispatchers.IO)
.launchIn(presenterScope)
// SY <--
}
private fun collectLatestSources(sources: List<Source>) {
private suspend fun collectLatestSources(sources: List<Source>, categories: Set<String>, showLatest: Boolean, showPin: Boolean) {
val map = TreeMap<String, MutableList<Source>> { d1, d2 ->
// Catalogues without a lang defined will be placed at the end
when {
@ -105,37 +86,24 @@ class SourcePresenter(
else -> it.lang
}
}
_state.update { state ->
state.copy(
sources = byLang.flatMap {
listOf(
UiModel.Header(it.key, it.value.firstOrNull()?.category != null),
*it.value.map { source ->
UiModel.Item(source)
}.toTypedArray()
)
},
error = null
)
}
}
// SY -->
private fun updateCategories(categories: Set<String>) {
_state.update { state ->
state.copy(
sourceCategories = categories.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it })
val uiModels = byLang.flatMap {
listOf(
SourceUiModel.Header(it.key, it.value.firstOrNull()?.category != null),
*it.value.map { source ->
SourceUiModel.Item(source)
}.toTypedArray(),
)
}
}
private fun updateShowLatest(showLatest: Boolean) {
_state.update { state ->
state.copy(
showLatest = showLatest
_state.emit(
SourceState.Success(
uiModels,
categories.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it }),
showLatest,
showPin
)
}
)
}
// SY <--
fun toggleSource(source: Source) {
toggleSource.await(source)
@ -159,29 +127,13 @@ class SourcePresenter(
}
}
sealed class UiModel {
data class Item(val source: Source) : UiModel()
data class Header(val language: String, val isCategory: Boolean) : UiModel()
}
data class SourceState(
val sources: List<UiModel>,
val error: Throwable?,
val sourceCategories: List<String>,
val showLatest: Boolean,
val showPin: Boolean
) {
val isLoading: Boolean
get() = sources.isEmpty() && error == null
val hasError: Boolean
get() = error != null
val isEmpty: Boolean
get() = sources.isEmpty()
companion object {
val EMPTY = SourceState(listOf(), null, emptyList(), true, true)
}
sealed class SourceState {
object Loading : SourceState()
data class Error(val error: Throwable) : SourceState()
data class Success(
val uiModels: List<SourceUiModel>,
val sourceCategories: List<String>,
val showLatest: Boolean,
val showPin: Boolean
) : SourceState()
}

View File

@ -711,6 +711,9 @@
<string name="clear_history_completed">History deleted</string>
<string name="clear_history_confirmation">Are you sure? All history will be lost.</string>
<!-- Source Screen -->
<string name="source_empty_screen">No source found</string>
<!-- Source Filter Screen -->
<string name="source_filter_empty_screen">No installed source found</string>