diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt index e65a02d54..2c105b2f8 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -41,7 +42,7 @@ class UpcomingScreen : Screen() { val navigator = LocalNavigator.currentOrThrow val viewModel = viewModel() - val state by viewModel.state.collectAsState() + val state by viewModel.state.collectAsStateWithLifecycle() when (state.dialog) { is UpcomingViewModel.Dialog.FilterSheet -> { diff --git a/app/src/main/java/mihon/feature/upcoming/UpcomingViewModel.kt b/app/src/main/java/mihon/feature/upcoming/UpcomingViewModel.kt index bb26301c9..4e1ac7a5c 100644 --- a/app/src/main/java/mihon/feature/upcoming/UpcomingViewModel.kt +++ b/app/src/main/java/mihon/feature/upcoming/UpcomingViewModel.kt @@ -3,23 +3,29 @@ package mihon.feature.upcoming import androidx.compose.runtime.Immutable import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMapIndexedNotNull +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import eu.kanade.core.util.insertSeparatorsReversed +import kotlinx.coroutines.Dispatchers 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.distinctUntilChanged 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.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.yearMonth -import mihon.core.viewmodel.StateViewModel import mihon.domain.upcoming.interactor.GetUpcomingManga import tachiyomi.core.common.preference.getAndSet -import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.model.Category 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.api.get import kotlin.time.Clock +import kotlin.time.Duration.Companion.seconds class UpcomingViewModel( private val getUpcomingManga: GetUpcomingManga = Injekt.get(), val getCategories: GetCategories = Injekt.get(), val upcomingPreferences: UpcomingPreferences = Injekt.get(), -) : StateViewModel(State()) { +) : ViewModel() { val excludedCategories = upcomingPreferences.filterExcludedCategories val includedCategories = upcomingPreferences.filterIncludedCategories - init { - viewModelScope.launchIO { - getUpcomingItemPreferenceFlow() - .distinctUntilChanged() - .flatMapLatest { - getUpcomingManga.subscribe( - excludedCategories = it.filterExcludedCategories, - includedCategories = it.filterIncludedCategories, - ) - .distinctUntilChanged() - } - .collectLatest { - mutableState.update { state -> - val upcomingItems = it.toUpcomingUIModels() - state.copy( - items = upcomingItems, - events = upcomingItems.toEvents(), - headerIndexes = upcomingItems.getHeaderIndexes(), - ) - } - } + private val selectedYearMonth = MutableStateFlow( + value = Clock.System.now() + .toLocalDateTime(TimeZone.currentSystemDefault()) + .date + .yearMonth, + ) + + private val dialog = MutableStateFlow(null) + + private val upcoming = getUpcomingItemPreferenceFlow() + .distinctUntilChanged() + .flatMapLatest { + getUpcomingManga.subscribe( + excludedCategories = it.filterExcludedCategories, + includedCategories = it.filterIncludedCategories, + ) } + .distinctUntilChanged() + .map { it.toUpcomingUIModels() } + .flowOn(Dispatchers.IO) + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), emptyList()) + + val state: StateFlow = 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.toUpcomingUIModels(): List { var mangaCount = 0 @@ -101,7 +125,7 @@ class UpcomingViewModel( } fun setSelectedYearMonth(yearMonth: YearMonth) { - mutableState.update { it.copy(selectedYearMonth = yearMonth) } + selectedYearMonth.update { yearMonth } } private fun getUpcomingItemPreferenceFlow(): Flow { @@ -117,11 +141,11 @@ class UpcomingViewModel( } fun resetDialog() { - mutableState.update { it.copy(dialog = null) } + dialog.update { null } } fun showFilterDialog() { - mutableState.update { it.copy(dialog = Dialog.FilterSheet) } + dialog.update { Dialog.FilterSheet } } fun cycleCategory(category: Category) { @@ -143,10 +167,7 @@ class UpcomingViewModel( ) data class State( - val selectedYearMonth: YearMonth = Clock.System.now() - .toLocalDateTime(TimeZone.currentSystemDefault()) - .date - .yearMonth, + val selectedYearMonth: YearMonth, val items: List = listOf(), val events: Map = mapOf(), val headerIndexes: Map = mapOf(),