From 5bd5b215430c6d19c1e4314bdaf4264eb43e9d29 Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Mon, 2 May 2022 08:40:35 +0600 Subject: [PATCH] Properly show history state (#7052) * Make `HistoryState` similar to `MigrateState` * Review Changes * Also cache the transformation Co-authored-by: Andreas * Fix States Co-authored-by: Andreas --- .../presentation/history/HistoryScreen.kt | 42 ++++++----- .../ui/recent/history/HistoryPresenter.kt | 75 +++++++++---------- 2 files changed, 61 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index a9ebb4cd2..429e4bbfa 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -37,17 +36,19 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.items import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.MangaCover import eu.kanade.presentation.util.horizontalPadding import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter -import eu.kanade.tachiyomi.ui.recent.history.UiModel +import eu.kanade.tachiyomi.ui.recent.history.HistoryState import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toTimestampString import uy.kohesive.injekt.Injekt @@ -66,37 +67,37 @@ fun HistoryScreen( onClickDelete: (HistoryWithRelations, Boolean) -> Unit, ) { val state by presenter.state.collectAsState() - val history = state.list?.collectAsLazyPagingItems() - when { - history == null -> { - CircularProgressIndicator() - } - history.itemCount == 0 -> { - EmptyScreen( - textResource = R.string.information_no_recent_manga - ) - } - else -> { + when (state) { + is HistoryState.Loading -> LoadingScreen() + is HistoryState.Error -> Text(text = (state as HistoryState.Error).error.message!!) + is HistoryState.Success -> HistoryContent( nestedScroll = nestedScrollInterop, - history = history, + history = (state as HistoryState.Success).uiModels.collectAsLazyPagingItems(), onClickCover = onClickCover, onClickResume = onClickResume, onClickDelete = onClickDelete, ) - } } } @Composable fun HistoryContent( - history: LazyPagingItems, + history: LazyPagingItems, onClickCover: (HistoryWithRelations) -> Unit, onClickResume: (HistoryWithRelations) -> Unit, onClickDelete: (HistoryWithRelations, Boolean) -> Unit, preferences: PreferencesHelper = Injekt.get(), nestedScroll: NestedScrollConnection ) { + if (history.loadState.refresh is LoadState.Loading) { + LoadingScreen() + return + } else if (history.loadState.refresh is LoadState.NotLoading && history.itemCount == 0) { + EmptyScreen(textResource = R.string.information_no_recent_manga) + return + } + val relativeTime: Int = remember { preferences.relativeTime().get() } val dateFormat: DateFormat = remember { preferences.dateFormat() } @@ -111,7 +112,7 @@ fun HistoryContent( ) { items(history) { item -> when (item) { - is UiModel.Header -> { + is HistoryUiModel.Header -> { HistoryHeader( modifier = Modifier .animateItemPlacement(), @@ -120,7 +121,7 @@ fun HistoryContent( dateFormat = dateFormat ) } - is UiModel.Item -> { + is HistoryUiModel.Item -> { val value = item.item HistoryItem( modifier = Modifier.animateItemPlacement(), @@ -282,3 +283,8 @@ private val chapterFormatter = DecimalFormat( "#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' }, ) + +sealed class HistoryUiModel { + data class Header(val date: Date) : HistoryUiModel() + data class Item(val item: HistoryWithRelations) : HistoryUiModel() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt index 7fd31dfa5..a04abbe6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt @@ -11,6 +11,7 @@ import eu.kanade.domain.history.interactor.GetNextChapterForManga import eu.kanade.domain.history.interactor.RemoveHistoryById import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId 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 @@ -20,9 +21,10 @@ import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date @@ -40,40 +42,45 @@ class HistoryPresenter( private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(), ) : BasePresenter() { - private var _query: MutableStateFlow = MutableStateFlow("") - private var _state: MutableStateFlow = MutableStateFlow(HistoryState.EMPTY) - val state: StateFlow = _state + private val _query: MutableStateFlow = MutableStateFlow("") + private val _state: MutableStateFlow = MutableStateFlow(HistoryState.Loading) + val state: StateFlow = _state.asStateFlow() override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) presenterScope.launchIO { - _state.update { state -> - state.copy( - list = _query.flatMapLatest { query -> - getHistory.subscribe(query) - .map { pagingData -> - pagingData - .map { - UiModel.Item(it) - } - .insertSeparators { before, after -> - val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) - val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) - when { - beforeDate.time != afterDate.time && afterDate.time != 0L -> UiModel.Header(afterDate) - // Return null to avoid adding a separator between two items. - else -> null - } - } - } + _query.collectLatest { query -> + getHistory.subscribe(query) + .catch { exception -> + _state.emit(HistoryState.Error(exception)) + } + .map { pagingData -> + pagingData.toHistoryUiModels() + } + .cachedIn(presenterScope) + .let { uiModelsPagingDataFlow -> + _state.emit(HistoryState.Success(uiModelsPagingDataFlow)) } - .cachedIn(presenterScope), - ) } } } + private fun PagingData.toHistoryUiModels(): PagingData { + return this.map { + HistoryUiModel.Item(it) + } + .insertSeparators { before, after -> + val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) + val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) + when { + beforeDate.time != afterDate.time && afterDate.time != 0L -> HistoryUiModel.Header(afterDate) + // Return null to avoid adding a separator between two items. + else -> null + } + } + } + fun search(query: String) { presenterScope.launchIO { _query.emit(query) @@ -112,16 +119,8 @@ class HistoryPresenter( } } -sealed class UiModel { - data class Item(val item: HistoryWithRelations) : UiModel() - data class Header(val date: Date) : UiModel() -} - -data class HistoryState( - val list: Flow>? = null, -) { - - companion object { - val EMPTY = HistoryState(null) - } +sealed class HistoryState { + object Loading : HistoryState() + data class Error(val error: Throwable) : HistoryState() + data class Success(val uiModels: Flow>) : HistoryState() }