Make UpcomingViewModel collect manga only while subscribed (#3725)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-08 20:04:05 +06:00
committed by GitHub
parent 3cb9883d62
commit 62288c08fb
2 changed files with 55 additions and 33 deletions
@@ -17,6 +17,7 @@ import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachIndexed
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@@ -41,7 +42,7 @@ class UpcomingScreen : Screen() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<UpcomingViewModel>() val viewModel = viewModel<UpcomingViewModel>()
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsStateWithLifecycle()
when (state.dialog) { when (state.dialog) {
is UpcomingViewModel.Dialog.FilterSheet -> { is UpcomingViewModel.Dialog.FilterSheet -> {
@@ -3,23 +3,29 @@ package mihon.feature.upcoming
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMapIndexedNotNull import androidx.compose.ui.util.fastMapIndexedNotNull
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import eu.kanade.core.util.insertSeparatorsReversed import eu.kanade.core.util.insertSeparatorsReversed
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.YearMonth import kotlinx.datetime.YearMonth
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlinx.datetime.yearMonth import kotlinx.datetime.yearMonth
import mihon.core.viewmodel.StateViewModel
import mihon.domain.upcoming.interactor.GetUpcomingManga import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@@ -27,39 +33,57 @@ import tachiyomi.domain.upcoming.service.UpcomingPreferences
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Duration.Companion.seconds
class UpcomingViewModel( class UpcomingViewModel(
private val getUpcomingManga: GetUpcomingManga = Injekt.get(), private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
val getCategories: GetCategories = Injekt.get(), val getCategories: GetCategories = Injekt.get(),
val upcomingPreferences: UpcomingPreferences = Injekt.get(), val upcomingPreferences: UpcomingPreferences = Injekt.get(),
) : StateViewModel<UpcomingViewModel.State>(State()) { ) : ViewModel() {
val excludedCategories = upcomingPreferences.filterExcludedCategories val excludedCategories = upcomingPreferences.filterExcludedCategories
val includedCategories = upcomingPreferences.filterIncludedCategories val includedCategories = upcomingPreferences.filterIncludedCategories
init { private val selectedYearMonth = MutableStateFlow(
viewModelScope.launchIO { value = Clock.System.now()
getUpcomingItemPreferenceFlow() .toLocalDateTime(TimeZone.currentSystemDefault())
.distinctUntilChanged() .date
.flatMapLatest { .yearMonth,
getUpcomingManga.subscribe( )
excludedCategories = it.filterExcludedCategories,
includedCategories = it.filterIncludedCategories, private val dialog = MutableStateFlow<Dialog?>(null)
)
.distinctUntilChanged() private val upcoming = getUpcomingItemPreferenceFlow()
} .distinctUntilChanged()
.collectLatest { .flatMapLatest {
mutableState.update { state -> getUpcomingManga.subscribe(
val upcomingItems = it.toUpcomingUIModels() excludedCategories = it.filterExcludedCategories,
state.copy( includedCategories = it.filterIncludedCategories,
items = upcomingItems, )
events = upcomingItems.toEvents(),
headerIndexes = upcomingItems.getHeaderIndexes(),
)
}
}
} }
.distinctUntilChanged()
.map { it.toUpcomingUIModels() }
.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), emptyList())
val state: StateFlow<State> = combine(
upcoming,
selectedYearMonth,
dialog,
) { upcoming, selectedYearMonth, dialog ->
State(
selectedYearMonth = selectedYearMonth,
items = upcoming,
events = upcoming.toEvents(),
headerIndexes = upcoming.getHeaderIndexes(),
dialog = dialog,
)
} }
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5.seconds),
State(selectedYearMonth = selectedYearMonth.value),
)
private fun List<Manga>.toUpcomingUIModels(): List<UpcomingUIModel> { private fun List<Manga>.toUpcomingUIModels(): List<UpcomingUIModel> {
var mangaCount = 0 var mangaCount = 0
@@ -101,7 +125,7 @@ class UpcomingViewModel(
} }
fun setSelectedYearMonth(yearMonth: YearMonth) { fun setSelectedYearMonth(yearMonth: YearMonth) {
mutableState.update { it.copy(selectedYearMonth = yearMonth) } selectedYearMonth.update { yearMonth }
} }
private fun getUpcomingItemPreferenceFlow(): Flow<ItemPreferences> { private fun getUpcomingItemPreferenceFlow(): Flow<ItemPreferences> {
@@ -117,11 +141,11 @@ class UpcomingViewModel(
} }
fun resetDialog() { fun resetDialog() {
mutableState.update { it.copy(dialog = null) } dialog.update { null }
} }
fun showFilterDialog() { fun showFilterDialog() {
mutableState.update { it.copy(dialog = Dialog.FilterSheet) } dialog.update { Dialog.FilterSheet }
} }
fun cycleCategory(category: Category) { fun cycleCategory(category: Category) {
@@ -143,10 +167,7 @@ class UpcomingViewModel(
) )
data class State( data class State(
val selectedYearMonth: YearMonth = Clock.System.now() val selectedYearMonth: YearMonth,
.toLocalDateTime(TimeZone.currentSystemDefault())
.date
.yearMonth,
val items: List<UpcomingUIModel> = listOf(), val items: List<UpcomingUIModel> = listOf(),
val events: Map<LocalDate, Int> = mapOf(), val events: Map<LocalDate, Int> = mapOf(),
val headerIndexes: Map<LocalDate, Int> = mapOf(), val headerIndexes: Map<LocalDate, Int> = mapOf(),