mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Properly show history state (#7052)
* Make `HistoryState` similar to `MigrateState` * Review Changes * Also cache the transformation Co-authored-by: Andreas <andreas.everos@gmail.com> * Fix States Co-authored-by: Andreas <andreas.everos@gmail.com>
This commit is contained in:
		| @@ -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<UiModel>, | ||||
|     history: LazyPagingItems<HistoryUiModel>, | ||||
|     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() | ||||
| } | ||||
|   | ||||
| @@ -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<HistoryController>() { | ||||
|  | ||||
|     private var _query: MutableStateFlow<String> = MutableStateFlow("") | ||||
|     private var _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.EMPTY) | ||||
|     val state: StateFlow<HistoryState> = _state | ||||
|     private val _query: MutableStateFlow<String> = MutableStateFlow("") | ||||
|     private val _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.Loading) | ||||
|     val state: StateFlow<HistoryState> = _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<HistoryWithRelations>.toHistoryUiModels(): PagingData<HistoryUiModel> { | ||||
|         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<PagingData<UiModel>>? = 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<PagingData<HistoryUiModel>>) : HistoryState() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user