Make HistoryViewModel collect history only while subscribed (#3728)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-08 20:04:41 +06:00
committed by GitHub
parent c68efd0e86
commit a494ebd791
2 changed files with 40 additions and 29 deletions
@@ -7,9 +7,9 @@ import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -63,7 +63,7 @@ data object HistoryTab : Tab {
val navigator = LocalNavigator.currentOrThrow
val context = LocalContext.current
val viewModel = viewModel<HistoryViewModel>()
val state by viewModel.state.collectAsState()
val state by viewModel.state.collectAsStateWithLifecycle()
HistoryScreen(
state = state,
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.history
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.manga.interactor.UpdateManga
@@ -11,16 +12,21 @@ import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
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.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.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.core.viewmodel.StateViewModel
import tachiyomi.core.common.preference.CheckboxState
import tachiyomi.core.common.preference.mapAsCheckboxState
import tachiyomi.core.common.util.lang.launchIO
@@ -42,6 +48,7 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
class HistoryViewModel(
private val addTracks: AddTracks = Injekt.get(),
@@ -56,28 +63,36 @@ class HistoryViewModel(
private val updateManga: UpdateManga = Injekt.get(),
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
private val sourceManager: SourceManager = Injekt.get(),
) : StateViewModel<HistoryViewModel.State>(State()) {
) : ViewModel() {
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
val events: Flow<Event> = _events.receiveAsFlow()
init {
viewModelScope.launch {
state.map { it.searchQuery }
private val searchQuery = MutableStateFlow<String?>(null)
private val dialog = MutableStateFlow<Dialog?>(null)
private val history = searchQuery
.flatMapLatest { query ->
getHistory.subscribe(query ?: "")
.distinctUntilChanged()
.flatMapLatest { query ->
getHistory.subscribe(query ?: "")
.distinctUntilChanged()
.catch { error ->
logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError)
}
.map { it.toHistoryUiModels() }
.flowOn(Dispatchers.IO)
.catch { error ->
logcat(LogPriority.ERROR, error)
_events.send(Event.InternalError)
}
.collect { newList -> mutableState.update { it.copy(list = newList) } }
.map { it.toHistoryUiModels() }
.flowOn(Dispatchers.IO)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), emptyList())
val state: StateFlow<State> = combine(
searchQuery,
history,
dialog,
) { searchQuery, history, dialog ->
State(searchQuery = searchQuery, list = history, dialog = dialog)
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), State())
private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> {
return map { HistoryUiModel.Item(it) }
@@ -128,11 +143,11 @@ class HistoryViewModel(
}
fun updateSearchQuery(query: String?) {
mutableState.update { it.copy(searchQuery = query) }
searchQuery.update { query }
}
fun setDialog(dialog: Dialog?) {
mutableState.update { it.copy(dialog = dialog) }
this.dialog.update { dialog }
}
/**
@@ -175,7 +190,7 @@ class HistoryViewModel(
val duplicates = getDuplicateLibraryManga(manga)
if (duplicates.isNotEmpty()) {
mutableState.update { it.copy(dialog = Dialog.DuplicateManga(manga, duplicates)) }
dialog.update { Dialog.DuplicateManga(manga, duplicates) }
return@launchIO
}
@@ -215,21 +230,17 @@ class HistoryViewModel(
}
fun showMigrateDialog(target: Manga, current: Manga) {
mutableState.update { currentState ->
currentState.copy(dialog = Dialog.Migrate(target = target, current = current))
}
dialog.update { Dialog.Migrate(target = target, current = current) }
}
fun showChangeCategoryDialog(manga: Manga) {
viewModelScope.launch {
val categories = getCategories()
val selection = getMangaCategoryIds(manga)
mutableState.update { currentState ->
currentState.copy(
dialog = Dialog.ChangeCategory(
manga = manga,
initialSelection = categories.mapAsCheckboxState { it.id in selection },
),
dialog.update {
Dialog.ChangeCategory(
manga = manga,
initialSelection = categories.mapAsCheckboxState { it.id in selection },
)
}
}