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.R
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.SourcePresenter 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 import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
@ -63,16 +63,15 @@ fun SourceScreen(
) { ) {
val state by presenter.state.collectAsState() val state by presenter.state.collectAsState()
when { when (state) {
state.isLoading -> LoadingScreen() is SourceState.Loading -> LoadingScreen()
state.hasError -> Text(text = state.error!!.message!!) is SourceState.Error -> Text(text = (state as SourceState.Error).error.message!!)
state.isEmpty -> EmptyScreen(message = "") is SourceState.Success -> SourceList(
else -> SourceList(
nestedScrollConnection = nestedScrollInterop, nestedScrollConnection = nestedScrollInterop,
list = state.sources, list = (state as SourceState.Success).uiModels,
categories = state.sourceCategories, categories = (state as SourceState.Success).sourceCategories,
showPin = state.showPin, showPin = (state as SourceState.Success).showPin,
showLatest = state.showLatest, showLatest = (state as SourceState.Success).showLatest,
onClickItem = onClickItem, onClickItem = onClickItem,
onClickDisable = onClickDisable, onClickDisable = onClickDisable,
onClickLatest = onClickLatest, onClickLatest = onClickLatest,
@ -86,7 +85,7 @@ fun SourceScreen(
@Composable @Composable
fun SourceList( fun SourceList(
nestedScrollConnection: NestedScrollConnection, nestedScrollConnection: NestedScrollConnection,
list: List<UiModel>, list: List<SourceUiModel>,
categories: List<String>, categories: List<String>,
showPin: Boolean, showPin: Boolean,
showLatest: Boolean, showLatest: Boolean,
@ -97,6 +96,11 @@ fun SourceList(
onClickSetCategories: (Source, List<String>) -> Unit, onClickSetCategories: (Source, List<String>) -> Unit,
onClickToggleDataSaver: (Source) -> Unit onClickToggleDataSaver: (Source) -> Unit
) { ) {
if (list.isEmpty()) {
EmptyScreen(textResource = R.string.source_empty_screen)
return
}
val (sourceState, setSourceState) = remember { mutableStateOf<Source?>(null) } val (sourceState, setSourceState) = remember { mutableStateOf<Source?>(null) }
// SY --> // SY -->
val (sourceCategoriesState, setSourceCategoriesState) = remember { mutableStateOf<Source?>(null) } val (sourceCategoriesState, setSourceCategoriesState) = remember { mutableStateOf<Source?>(null) }
@ -110,26 +114,26 @@ fun SourceList(
items = list, items = list,
contentType = { contentType = {
when (it) { when (it) {
is UiModel.Header -> "header" is SourceUiModel.Header -> "header"
is UiModel.Item -> "item" is SourceUiModel.Item -> "item"
} }
}, },
key = { key = {
when (it) { when (it) {
is UiModel.Header -> it.hashCode() is SourceUiModel.Header -> it.hashCode()
is UiModel.Item -> it.source.key() is SourceUiModel.Item -> it.source.key()
} }
} }
) { model -> ) { model ->
when (model) { when (model) {
is UiModel.Header -> { is SourceUiModel.Header -> {
SourceHeader( SourceHeader(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
language = model.language, language = model.language,
isCategory = model.isCategory isCategory = model.isCategory
) )
} }
is UiModel.Item -> SourceItem( is SourceUiModel.Item -> SourceItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
source = model.source, source = model.source,
showLatest = showLatest, 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 --> // SY -->
@Composable @Composable
fun SourceCategoriesDialog( 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.interactor.ToggleSourcePin
import eu.kanade.domain.source.model.Pin import eu.kanade.domain.source.model.Pin
import eu.kanade.domain.source.model.Source 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.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.TreeMap import java.util.TreeMap
@ -39,50 +42,28 @@ class SourcePresenter(
// SY <-- // SY <--
) : BasePresenter<SourceController>() { ) : BasePresenter<SourceController>() {
private val _state: MutableStateFlow<SourceState> = MutableStateFlow(SourceState.EMPTY) private val _state: MutableStateFlow<SourceState> = MutableStateFlow(SourceState.Loading)
val state: StateFlow<SourceState> = _state.asStateFlow() val state: StateFlow<SourceState> = _state.asStateFlow()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
presenterScope.launchIO {
getEnabledSources.subscribe()
.catch { exception ->
_state.update { state ->
state.copy(sources = listOf(), error = exception)
}
}
.collectLatest(::collectLatestSources)
}
// SY --> // SY -->
presenterScope.launchIO { combine(
getSourceCategories.subscribe() getEnabledSources.subscribe(),
.catch { exception -> getSourceCategories.subscribe(),
_state.update { state -> getShowLatest.subscribe(controllerMode),
state.copy(sources = listOf(), error = exception) flowOf(controllerMode == SourceController.Mode.CATALOGUE),
} ::collectLatestSources
} )
.collectLatest(::updateCategories) .catch { exception ->
} _state.emit(SourceState.Error(exception))
presenterScope.launchIO {
_state.update { state ->
state.copy(
showPin = controllerMode == SourceController.Mode.CATALOGUE,
)
} }
} .flowOn(Dispatchers.IO)
presenterScope.launchIO { .launchIn(presenterScope)
getShowLatest.subscribe(mode = controllerMode)
.catch { exception ->
_state.update { state ->
state.copy(sources = listOf(), error = exception)
}
}
.collectLatest(::updateShowLatest)
}
// SY <-- // 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 -> val map = TreeMap<String, MutableList<Source>> { d1, d2 ->
// Catalogues without a lang defined will be placed at the end // Catalogues without a lang defined will be placed at the end
when { when {
@ -105,37 +86,24 @@ class SourcePresenter(
else -> it.lang 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 --> val uiModels = byLang.flatMap {
private fun updateCategories(categories: Set<String>) { listOf(
_state.update { state -> SourceUiModel.Header(it.key, it.value.firstOrNull()?.category != null),
state.copy( *it.value.map { source ->
sourceCategories = categories.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it }) SourceUiModel.Item(source)
}.toTypedArray(),
) )
} }
} _state.emit(
private fun updateShowLatest(showLatest: Boolean) { SourceState.Success(
_state.update { state -> uiModels,
state.copy( categories.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it }),
showLatest = showLatest showLatest,
showPin
) )
} )
} }
// SY <--
fun toggleSource(source: Source) { fun toggleSource(source: Source) {
toggleSource.await(source) toggleSource.await(source)
@ -159,29 +127,13 @@ class SourcePresenter(
} }
} }
sealed class UiModel { sealed class SourceState {
data class Item(val source: Source) : UiModel() object Loading : SourceState()
data class Header(val language: String, val isCategory: Boolean) : UiModel() data class Error(val error: Throwable) : SourceState()
} data class Success(
val uiModels: List<SourceUiModel>,
data class SourceState( val sourceCategories: List<String>,
val sources: List<UiModel>, val showLatest: Boolean,
val error: Throwable?, val showPin: Boolean
val sourceCategories: List<String>, ) : SourceState()
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)
}
} }

View File

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