|
@@ -1,11 +1,10 @@
|
|
|
package eu.kanade.tachiyomi.ui.history
|
|
|
|
|
|
-import androidx.compose.runtime.Composable
|
|
|
-import androidx.compose.runtime.Stable
|
|
|
+import androidx.compose.runtime.Immutable
|
|
|
import androidx.compose.runtime.getValue
|
|
|
-import androidx.compose.runtime.mutableStateOf
|
|
|
-import androidx.compose.runtime.remember
|
|
|
-import androidx.compose.runtime.setValue
|
|
|
+import cafe.adriel.voyager.core.model.StateScreenModel
|
|
|
+import cafe.adriel.voyager.core.model.coroutineScope
|
|
|
+import eu.kanade.core.prefs.asState
|
|
|
import eu.kanade.core.util.insertSeparators
|
|
|
import eu.kanade.domain.base.BasePreferences
|
|
|
import eu.kanade.domain.chapter.model.Chapter
|
|
@@ -14,51 +13,53 @@ import eu.kanade.domain.history.interactor.GetNextChapters
|
|
|
import eu.kanade.domain.history.interactor.RemoveHistory
|
|
|
import eu.kanade.domain.history.model.HistoryWithRelations
|
|
|
import eu.kanade.presentation.history.HistoryUiModel
|
|
|
-import eu.kanade.tachiyomi.R
|
|
|
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
|
import eu.kanade.tachiyomi.util.lang.toDateKey
|
|
|
-import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
|
import eu.kanade.tachiyomi.util.system.logcat
|
|
|
-import eu.kanade.tachiyomi.util.system.toast
|
|
|
+import kotlinx.coroutines.Dispatchers
|
|
|
import kotlinx.coroutines.channels.Channel
|
|
|
import kotlinx.coroutines.flow.Flow
|
|
|
import kotlinx.coroutines.flow.catch
|
|
|
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.update
|
|
|
+import kotlinx.coroutines.launch
|
|
|
import logcat.LogPriority
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
import uy.kohesive.injekt.api.get
|
|
|
import java.util.Date
|
|
|
|
|
|
-class HistoryPresenter(
|
|
|
- private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
|
|
|
+class HistoryScreenModel(
|
|
|
private val getHistory: GetHistory = Injekt.get(),
|
|
|
private val getNextChapters: GetNextChapters = Injekt.get(),
|
|
|
private val removeHistory: RemoveHistory = Injekt.get(),
|
|
|
preferences: BasePreferences = Injekt.get(),
|
|
|
-) : BasePresenter<HistoryController>(), HistoryState by state {
|
|
|
+) : StateScreenModel<HistoryState>(HistoryState()) {
|
|
|
|
|
|
- private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
|
|
+ private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
|
|
val events: Flow<Event> = _events.receiveAsFlow()
|
|
|
|
|
|
- val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
|
|
- val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
|
|
+ val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
|
|
|
+ val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
|
|
|
|
|
|
- @Composable
|
|
|
- fun getHistory(): Flow<List<HistoryUiModel>> {
|
|
|
- val query = searchQuery ?: ""
|
|
|
- return remember(query) {
|
|
|
- getHistory.subscribe(query)
|
|
|
+ init {
|
|
|
+ coroutineScope.launch {
|
|
|
+ state.map { it.searchQuery }
|
|
|
.distinctUntilChanged()
|
|
|
- .catch { error ->
|
|
|
- logcat(LogPriority.ERROR, error)
|
|
|
- _events.send(Event.InternalError)
|
|
|
- }
|
|
|
- .map { pagingData ->
|
|
|
- pagingData.toHistoryUiModels()
|
|
|
+ .flatMapLatest { query ->
|
|
|
+ getHistory.subscribe(query ?: "")
|
|
|
+ .distinctUntilChanged()
|
|
|
+ .catch { error ->
|
|
|
+ logcat(LogPriority.ERROR, error)
|
|
|
+ _events.send(Event.InternalError)
|
|
|
+ }
|
|
|
+ .map { it.toHistoryUiModels() }
|
|
|
+ .flowOn(Dispatchers.IO)
|
|
|
}
|
|
|
+ .collect { newList -> mutableState.update { it.copy(list = newList) } }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -76,67 +77,59 @@ class HistoryPresenter(
|
|
|
}
|
|
|
|
|
|
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
|
|
- presenterScope.launchIO {
|
|
|
+ coroutineScope.launchIO {
|
|
|
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun resumeLastChapterRead() {
|
|
|
- presenterScope.launchIO {
|
|
|
- sendNextChapterEvent(getNextChapters.await(onlyUnread = false))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private suspend fun sendNextChapterEvent(chapters: List<Chapter>) {
|
|
|
val chapter = chapters.firstOrNull()
|
|
|
- _events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
|
|
|
+ _events.send(Event.OpenChapter(chapter))
|
|
|
}
|
|
|
|
|
|
fun removeFromHistory(history: HistoryWithRelations) {
|
|
|
- presenterScope.launchIO {
|
|
|
+ coroutineScope.launchIO {
|
|
|
removeHistory.await(history)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fun removeAllFromHistory(mangaId: Long) {
|
|
|
- presenterScope.launchIO {
|
|
|
+ coroutineScope.launchIO {
|
|
|
removeHistory.await(mangaId)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fun removeAllHistory() {
|
|
|
- presenterScope.launchIO {
|
|
|
+ coroutineScope.launchIO {
|
|
|
val result = removeHistory.awaitAll()
|
|
|
if (!result) return@launchIO
|
|
|
- withUIContext {
|
|
|
- view?.activity?.toast(R.string.clear_history_completed)
|
|
|
- }
|
|
|
+ _events.send(Event.HistoryCleared)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fun updateSearchQuery(query: String?) {
|
|
|
+ mutableState.update { it.copy(searchQuery = query) }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setDialog(dialog: Dialog?) {
|
|
|
+ mutableState.update { it.copy(dialog = dialog) }
|
|
|
+ }
|
|
|
+
|
|
|
sealed class Dialog {
|
|
|
object DeleteAll : Dialog()
|
|
|
data class Delete(val history: HistoryWithRelations) : Dialog()
|
|
|
}
|
|
|
|
|
|
sealed class Event {
|
|
|
+ data class OpenChapter(val chapter: Chapter?) : Event()
|
|
|
object InternalError : Event()
|
|
|
- object NoNextChapterFound : Event()
|
|
|
- data class OpenChapter(val chapter: Chapter) : Event()
|
|
|
+ object HistoryCleared : Event()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-@Stable
|
|
|
-interface HistoryState {
|
|
|
- var searchQuery: String?
|
|
|
- var dialog: HistoryPresenter.Dialog?
|
|
|
-}
|
|
|
-
|
|
|
-fun HistoryState(): HistoryState {
|
|
|
- return HistoryStateImpl()
|
|
|
-}
|
|
|
-
|
|
|
-class HistoryStateImpl : HistoryState {
|
|
|
- override var searchQuery: String? by mutableStateOf(null)
|
|
|
- override var dialog: HistoryPresenter.Dialog? by mutableStateOf(null)
|
|
|
-}
|
|
|
+@Immutable
|
|
|
+data class HistoryState(
|
|
|
+ val searchQuery: String? = null,
|
|
|
+ val list: List<HistoryUiModel>? = null,
|
|
|
+ val dialog: HistoryScreenModel.Dialog? = null,
|
|
|
+)
|