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:
FourTOne5 2022-05-02 08:40:35 +06:00 committed by GitHub
parent aec980662f
commit 5bd5b21543
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 56 deletions

View file

@ -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()
}

View file

@ -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()
}