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:
parent
7e380582a6
commit
222f8a7d7a
@ -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(
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user