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.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme 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.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items import androidx.paging.compose.items
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.MangaCover import eu.kanade.presentation.components.MangaCover
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter 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.toRelativeString
import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.lang.toTimestampString
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -66,37 +67,37 @@ fun HistoryScreen(
onClickDelete: (HistoryWithRelations, Boolean) -> Unit, onClickDelete: (HistoryWithRelations, Boolean) -> Unit,
) { ) {
val state by presenter.state.collectAsState() val state by presenter.state.collectAsState()
val history = state.list?.collectAsLazyPagingItems() when (state) {
when { is HistoryState.Loading -> LoadingScreen()
history == null -> { is HistoryState.Error -> Text(text = (state as HistoryState.Error).error.message!!)
CircularProgressIndicator() is HistoryState.Success ->
}
history.itemCount == 0 -> {
EmptyScreen(
textResource = R.string.information_no_recent_manga
)
}
else -> {
HistoryContent( HistoryContent(
nestedScroll = nestedScrollInterop, nestedScroll = nestedScrollInterop,
history = history, history = (state as HistoryState.Success).uiModels.collectAsLazyPagingItems(),
onClickCover = onClickCover, onClickCover = onClickCover,
onClickResume = onClickResume, onClickResume = onClickResume,
onClickDelete = onClickDelete, onClickDelete = onClickDelete,
) )
}
} }
} }
@Composable @Composable
fun HistoryContent( fun HistoryContent(
history: LazyPagingItems<UiModel>, history: LazyPagingItems<HistoryUiModel>,
onClickCover: (HistoryWithRelations) -> Unit, onClickCover: (HistoryWithRelations) -> Unit,
onClickResume: (HistoryWithRelations) -> Unit, onClickResume: (HistoryWithRelations) -> Unit,
onClickDelete: (HistoryWithRelations, Boolean) -> Unit, onClickDelete: (HistoryWithRelations, Boolean) -> Unit,
preferences: PreferencesHelper = Injekt.get(), preferences: PreferencesHelper = Injekt.get(),
nestedScroll: NestedScrollConnection 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 relativeTime: Int = remember { preferences.relativeTime().get() }
val dateFormat: DateFormat = remember { preferences.dateFormat() } val dateFormat: DateFormat = remember { preferences.dateFormat() }
@ -111,7 +112,7 @@ fun HistoryContent(
) { ) {
items(history) { item -> items(history) { item ->
when (item) { when (item) {
is UiModel.Header -> { is HistoryUiModel.Header -> {
HistoryHeader( HistoryHeader(
modifier = Modifier modifier = Modifier
.animateItemPlacement(), .animateItemPlacement(),
@ -120,7 +121,7 @@ fun HistoryContent(
dateFormat = dateFormat dateFormat = dateFormat
) )
} }
is UiModel.Item -> { is HistoryUiModel.Item -> {
val value = item.item val value = item.item
HistoryItem( HistoryItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
@ -282,3 +283,8 @@ private val chapterFormatter = DecimalFormat(
"#.###", "#.###",
DecimalFormatSymbols().apply { decimalSeparator = '.' }, 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.RemoveHistoryById
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.history.HistoryUiModel
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO 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.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow 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.map
import kotlinx.coroutines.flow.update
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
@ -40,40 +42,45 @@ class HistoryPresenter(
private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(), private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
) : BasePresenter<HistoryController>() { ) : BasePresenter<HistoryController>() {
private var _query: MutableStateFlow<String> = MutableStateFlow("") private val _query: MutableStateFlow<String> = MutableStateFlow("")
private var _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.EMPTY) private val _state: MutableStateFlow<HistoryState> = MutableStateFlow(HistoryState.Loading)
val state: StateFlow<HistoryState> = _state val state: StateFlow<HistoryState> = _state.asStateFlow()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
presenterScope.launchIO { presenterScope.launchIO {
_state.update { state -> _query.collectLatest { query ->
state.copy( getHistory.subscribe(query)
list = _query.flatMapLatest { query -> .catch { exception ->
getHistory.subscribe(query) _state.emit(HistoryState.Error(exception))
.map { pagingData -> }
pagingData .map { pagingData ->
.map { pagingData.toHistoryUiModels()
UiModel.Item(it) }
} .cachedIn(presenterScope)
.insertSeparators { before, after -> .let { uiModelsPagingDataFlow ->
val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) _state.emit(HistoryState.Success(uiModelsPagingDataFlow))
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
}
}
}
} }
.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) { fun search(query: String) {
presenterScope.launchIO { presenterScope.launchIO {
_query.emit(query) _query.emit(query)
@ -112,16 +119,8 @@ class HistoryPresenter(
} }
} }
sealed class UiModel { sealed class HistoryState {
data class Item(val item: HistoryWithRelations) : UiModel() object Loading : HistoryState()
data class Header(val date: Date) : UiModel() data class Error(val error: Throwable) : HistoryState()
} data class Success(val uiModels: Flow<PagingData<HistoryUiModel>>) : HistoryState()
data class HistoryState(
val list: Flow<PagingData<UiModel>>? = null,
) {
companion object {
val EMPTY = HistoryState(null)
}
} }