Make SourcesViewModel collect sources only while subscribed (#3721)
Assisted-by: Claude:claude-opus-5
This commit is contained in:
@@ -5,8 +5,8 @@ import androidx.compose.material.icons.outlined.FilterList
|
||||
import androidx.compose.material.icons.outlined.TravelExplore
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
@@ -26,7 +26,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
fun Screen.sourcesTab(): TabContent {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<SourcesViewModel>()
|
||||
val state by viewModel.state.collectAsState()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
return TabContent(
|
||||
titleRes = MR.strings.label_sources,
|
||||
|
||||
@@ -1,79 +1,88 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||
import eu.kanade.presentation.browse.SourceUiModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import logcat.LogPriority
|
||||
import mihon.core.viewmodel.StateViewModel
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.source.model.Pin
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.TreeMap
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class SourcesViewModel(
|
||||
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
||||
private val toggleSource: ToggleSource = Injekt.get(),
|
||||
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
||||
) : StateViewModel<SourcesViewModel.State>(State()) {
|
||||
) : ViewModel() {
|
||||
|
||||
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||
val events = _events.receiveAsFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
getEnabledSources.subscribe()
|
||||
.catch {
|
||||
logcat(LogPriority.ERROR, it)
|
||||
_events.send(Event.FailedFetchingSources)
|
||||
}
|
||||
.collectLatest(::collectLatestSources)
|
||||
private val dialog = MutableStateFlow<Dialog?>(null)
|
||||
|
||||
private val enabledSources = getEnabledSources.subscribe()
|
||||
.catch {
|
||||
logcat(LogPriority.ERROR, it)
|
||||
_events.send(Event.FailedFetchingSources)
|
||||
}
|
||||
.map(::toSourceUiModels)
|
||||
|
||||
val state: StateFlow<State> = combine(
|
||||
enabledSources,
|
||||
dialog,
|
||||
) { items, dialog ->
|
||||
State(dialog = dialog, isLoading = false, items = items)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), State())
|
||||
|
||||
private fun collectLatestSources(sources: List<Source>) {
|
||||
mutableState.update { state ->
|
||||
val map = TreeMap<String, MutableList<Source>> { d1, d2 ->
|
||||
// Sources without a lang defined will be placed at the end
|
||||
when {
|
||||
d1 == LAST_USED_KEY && d2 != LAST_USED_KEY -> -1
|
||||
d2 == LAST_USED_KEY && d1 != LAST_USED_KEY -> 1
|
||||
d1 == PINNED_KEY && d2 != PINNED_KEY -> -1
|
||||
d2 == PINNED_KEY && d1 != PINNED_KEY -> 1
|
||||
d1 == "" && d2 != "" -> 1
|
||||
d2 == "" && d1 != "" -> -1
|
||||
else -> d1.compareTo(d2)
|
||||
}
|
||||
private fun toSourceUiModels(sources: List<Source>): List<SourceUiModel> {
|
||||
val map = TreeMap<String, MutableList<Source>> { d1, d2 ->
|
||||
// Sources without a lang defined will be placed at the end
|
||||
when {
|
||||
d1 == LAST_USED_KEY && d2 != LAST_USED_KEY -> -1
|
||||
d2 == LAST_USED_KEY && d1 != LAST_USED_KEY -> 1
|
||||
d1 == PINNED_KEY && d2 != PINNED_KEY -> -1
|
||||
d2 == PINNED_KEY && d1 != PINNED_KEY -> 1
|
||||
d1 == "" && d2 != "" -> 1
|
||||
d2 == "" && d1 != "" -> -1
|
||||
else -> d1.compareTo(d2)
|
||||
}
|
||||
val byLang = sources.groupByTo(map) {
|
||||
when {
|
||||
it.isUsedLast -> LAST_USED_KEY
|
||||
Pin.Actual in it.pin -> PINNED_KEY
|
||||
else -> it.lang
|
||||
}
|
||||
}
|
||||
val byLang = sources.groupByTo(map) {
|
||||
when {
|
||||
it.isUsedLast -> LAST_USED_KEY
|
||||
Pin.Actual in it.pin -> PINNED_KEY
|
||||
else -> it.lang
|
||||
}
|
||||
}
|
||||
|
||||
state.copy(
|
||||
isLoading = false,
|
||||
items = byLang
|
||||
.flatMap {
|
||||
listOf(
|
||||
SourceUiModel.Header(it.key),
|
||||
*it.value.map { source ->
|
||||
SourceUiModel.Item(source)
|
||||
}.toTypedArray(),
|
||||
)
|
||||
},
|
||||
return byLang.flatMap {
|
||||
listOf(
|
||||
SourceUiModel.Header(it.key),
|
||||
*it.value.map { source ->
|
||||
SourceUiModel.Item(source)
|
||||
}.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -87,11 +96,11 @@ class SourcesViewModel(
|
||||
}
|
||||
|
||||
fun showSourceDialog(source: Source) {
|
||||
mutableState.update { it.copy(dialog = Dialog(source)) }
|
||||
dialog.update { Dialog(source) }
|
||||
}
|
||||
|
||||
fun closeDialog() {
|
||||
mutableState.update { it.copy(dialog = null) }
|
||||
dialog.update { null }
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
|
||||
Reference in New Issue
Block a user