Make HistoryViewModel collect history only while subscribed (#3728)
Assisted-by: Claude:claude-opus-5
This commit is contained in:
@@ -7,9 +7,9 @@ import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
@@ -63,7 +63,7 @@ data object HistoryTab : Tab {
|
|||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val viewModel = viewModel<HistoryViewModel>()
|
val viewModel = viewModel<HistoryViewModel>()
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
HistoryScreen(
|
HistoryScreen(
|
||||||
state = state,
|
state = state,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.history
|
|||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
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.catch
|
||||||
|
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.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import mihon.core.viewmodel.StateViewModel
|
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
@@ -42,6 +48,7 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount
|
|||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
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.Duration.Companion.seconds
|
||||||
|
|
||||||
class HistoryViewModel(
|
class HistoryViewModel(
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
@@ -56,15 +63,16 @@ class HistoryViewModel(
|
|||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
) : StateViewModel<HistoryViewModel.State>(State()) {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
private val searchQuery = MutableStateFlow<String?>(null)
|
||||||
viewModelScope.launch {
|
|
||||||
state.map { it.searchQuery }
|
private val dialog = MutableStateFlow<Dialog?>(null)
|
||||||
.distinctUntilChanged()
|
|
||||||
|
private val history = searchQuery
|
||||||
.flatMapLatest { query ->
|
.flatMapLatest { query ->
|
||||||
getHistory.subscribe(query ?: "")
|
getHistory.subscribe(query ?: "")
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
@@ -75,9 +83,16 @@ class HistoryViewModel(
|
|||||||
.map { it.toHistoryUiModels() }
|
.map { it.toHistoryUiModels() }
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
.collect { newList -> mutableState.update { it.copy(list = newList) } }
|
.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> {
|
private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> {
|
||||||
return map { HistoryUiModel.Item(it) }
|
return map { HistoryUiModel.Item(it) }
|
||||||
@@ -128,11 +143,11 @@ class HistoryViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateSearchQuery(query: String?) {
|
fun updateSearchQuery(query: String?) {
|
||||||
mutableState.update { it.copy(searchQuery = query) }
|
searchQuery.update { query }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDialog(dialog: Dialog?) {
|
fun setDialog(dialog: Dialog?) {
|
||||||
mutableState.update { it.copy(dialog = dialog) }
|
this.dialog.update { dialog }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,7 +190,7 @@ class HistoryViewModel(
|
|||||||
|
|
||||||
val duplicates = getDuplicateLibraryManga(manga)
|
val duplicates = getDuplicateLibraryManga(manga)
|
||||||
if (duplicates.isNotEmpty()) {
|
if (duplicates.isNotEmpty()) {
|
||||||
mutableState.update { it.copy(dialog = Dialog.DuplicateManga(manga, duplicates)) }
|
dialog.update { Dialog.DuplicateManga(manga, duplicates) }
|
||||||
return@launchIO
|
return@launchIO
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,21 +230,17 @@ class HistoryViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showMigrateDialog(target: Manga, current: Manga) {
|
fun showMigrateDialog(target: Manga, current: Manga) {
|
||||||
mutableState.update { currentState ->
|
dialog.update { Dialog.Migrate(target = target, current = current) }
|
||||||
currentState.copy(dialog = Dialog.Migrate(target = target, current = current))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showChangeCategoryDialog(manga: Manga) {
|
fun showChangeCategoryDialog(manga: Manga) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val selection = getMangaCategoryIds(manga)
|
val selection = getMangaCategoryIds(manga)
|
||||||
mutableState.update { currentState ->
|
dialog.update {
|
||||||
currentState.copy(
|
Dialog.ChangeCategory(
|
||||||
dialog = Dialog.ChangeCategory(
|
|
||||||
manga = manga,
|
manga = manga,
|
||||||
initialSelection = categories.mapAsCheckboxState { it.id in selection },
|
initialSelection = categories.mapAsCheckboxState { it.id in selection },
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user