feat(ui): lazy tag for mark article as read

This commit is contained in:
junkfood 2024-11-10 22:18:02 +08:00
parent d4541dd026
commit 1343978fc9
No known key found for this signature in database
GPG Key ID: 2EA5B648DB112A34
4 changed files with 45 additions and 23 deletions

View File

@ -90,6 +90,7 @@ private const val TAG = "ArticleItem"
fun ArticleItem(
modifier: Modifier = Modifier,
articleWithFeed: ArticleWithFeed,
isUnread: Boolean = articleWithFeed.article.isUnread,
onClick: (ArticleWithFeed) -> Unit = {},
onLongClick: (() -> Unit)? = null
) {
@ -105,7 +106,7 @@ fun ArticleItem(
dateString = article.dateString,
imgData = article.img,
isStarred = article.isStarred,
isUnread = article.isUnread,
isUnread = isUnread,
onClick = { onClick(articleWithFeed) },
onLongClick = onLongClick
)
@ -280,7 +281,7 @@ private const val SwipeActionDelay = 300L
@Composable
fun SwipeableArticleItem(
articleWithFeed: ArticleWithFeed,
isFilterUnread: Boolean = false,
isUnread: Boolean = articleWithFeed.article.isUnread,
articleListTonalElevation: Int = 0,
onClick: (ArticleWithFeed) -> Unit = {},
isSwipeEnabled: () -> Boolean = { false },
@ -311,7 +312,7 @@ fun SwipeableArticleItem(
SwipeActionBox(
articleWithFeed = articleWithFeed,
isRead = !articleWithFeed.article.isUnread,
isRead = !isUnread,
isStarred = articleWithFeed.article.isStarred,
onToggleStarred = onToggleStarred,
onToggleRead = onToggleRead
@ -337,6 +338,7 @@ fun SwipeableArticleItem(
) {
ArticleItem(
articleWithFeed = articleWithFeed,
isUnread = isUnread,
onClick = onClick,
onLongClick = onLongClick
)

View File

@ -16,7 +16,7 @@ import me.ash.reader.domain.model.article.ArticleWithFeed
@OptIn(ExperimentalFoundationApi::class)
fun LazyListScope.ArticleList(
pagingItems: LazyPagingItems<ArticleFlowItem>,
isFilterUnread: Boolean,
diffMap: Map<String, Diff>,
isShowFeedIcon: Boolean,
isShowStickyHeader: Boolean,
articleListTonalElevation: Int,
@ -40,9 +40,10 @@ fun LazyListScope.ArticleList(
) { index ->
when (val item = pagingItems[index]) {
is ArticleFlowItem.Article -> {
val article = item.articleWithFeed.article
SwipeableArticleItem(
articleWithFeed = item.articleWithFeed,
isFilterUnread = isFilterUnread,
isUnread = diffMap[article.id]?.isUnread ?: article.isUnread,
articleListTonalElevation = articleListTonalElevation,
onClick = onClick,
isSwipeEnabled = isSwipeEnabled,
@ -70,9 +71,10 @@ fun LazyListScope.ArticleList(
when (val item = pagingItems.peek(index)) {
is ArticleFlowItem.Article -> {
item(key = key(item), contentType = contentType(item)) {
val article = item.articleWithFeed.article
SwipeableArticleItem(
articleWithFeed = item.articleWithFeed,
isFilterUnread = isFilterUnread,
isUnread = diffMap[article.id]?.isUnread ?: article.isUnread,
articleListTonalElevation = articleListTonalElevation,
onClick = onClick,
isSwipeEnabled = isSwipeEnabled,

View File

@ -32,6 +32,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@ -43,7 +44,6 @@ import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.domain.model.article.ArticleWithFeed
import me.ash.reader.domain.model.general.Filter
import me.ash.reader.domain.model.general.MarkAsReadConditions
import me.ash.reader.infrastructure.preference.LocalFlowArticleListDateStickyHeader
import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedIcon
import me.ash.reader.infrastructure.preference.LocalFlowArticleListTonalElevation
@ -107,6 +107,12 @@ fun FlowPage(
}
}
DisposableEffect(pagingItems) {
onDispose {
flowViewModel.commitDiff()
}
}
DisposableEffect(owner) {
homeViewModel.syncWorkLiveData.observe(owner) { workInfoList ->
workInfoList.let {
@ -127,15 +133,16 @@ fun FlowPage(
val onToggleRead: (ArticleWithFeed) -> Unit = remember {
{ article ->
flowViewModel.updateReadStatus(
groupId = null,
feedId = null,
articleId = article.article.id,
conditions = MarkAsReadConditions.All,
isUnread = !article.article.isUnread,
)
val id = article.article.id
val isUnread = article.article.isUnread
with(flowViewModel.diffMap) {
if (contains(id)) remove(id)
else put(id, Diff(isUnread = !isUnread))
}
}
}
val onMarkAboveAsRead: ((ArticleWithFeed) -> Unit)? = remember {
{
flowViewModel.markAsReadFromListByDate(
@ -326,7 +333,7 @@ fun FlowPage(
}
ArticleList(
pagingItems = pagingItems,
isFilterUnread = filterUiState.filter == Filter.Unread,
diffMap = flowViewModel.diffMap,
isShowFeedIcon = articleListFeedIcon.value,
isShowStickyHeader = articleListDateStickyHeader.value,
articleListTonalElevation = articleListTonalElevation.value,

View File

@ -1,24 +1,22 @@
package me.ash.reader.ui.page.home.flow
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.mutableStateMapOf
import androidx.lifecycle.ViewModel
import androidx.paging.compose.LazyPagingItems
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import me.ash.reader.domain.model.article.ArticleFlowItem
import me.ash.reader.domain.model.article.ArticleWithFeed
import me.ash.reader.domain.model.general.MarkAsReadConditions
import me.ash.reader.domain.service.RssService
import me.ash.reader.infrastructure.di.ApplicationScope
import me.ash.reader.infrastructure.di.IODispatcher
import java.util.Date
import java.util.function.BiPredicate
import javax.inject.Inject
@HiltViewModel
@ -32,6 +30,7 @@ class FlowViewModel @Inject constructor(
private val _flowUiState = MutableStateFlow(FlowUiState())
val flowUiState: StateFlow<FlowUiState> = _flowUiState.asStateFlow()
val diffMap = mutableStateMapOf<String, Diff>()
fun sync() {
applicationScope.launch(ioDispatcher) {
@ -45,10 +44,8 @@ class FlowViewModel @Inject constructor(
articleId: String?,
conditions: MarkAsReadConditions,
isUnread: Boolean,
withDelay: Long = 0,
) {
applicationScope.launch(ioDispatcher) {
delay(withDelay)
rssService.get().markAsRead(
groupId = groupId,
feedId = feedId,
@ -62,11 +59,8 @@ class FlowViewModel @Inject constructor(
fun updateStarredStatus(
articleId: String?,
isStarred: Boolean,
withDelay: Long = 0,
) {
applicationScope.launch(ioDispatcher) {
// FIXME: a dirty hack to ensure the swipe animation doesn't get interrupted when recomposed, remove this after implementing a lazy tag!
delay(withDelay)
if (articleId != null) {
rssService.get().markAsStarred(
articleId = articleId,
@ -97,6 +91,21 @@ class FlowViewModel @Inject constructor(
rssService.get().batchMarkAsRead(articleIds = articleIdSet, isUnread = false)
}
}
fun commitDiff() {
applicationScope.launch(ioDispatcher) {
val markAsReadArticles =
diffMap.filter { !it.value.isUnread }.map { it.key }.toSet()
val markAsUnreadArticles =
diffMap.filter { it.value.isUnread }.map { it.key }.toSet()
rssService.get()
.batchMarkAsRead(articleIds = markAsReadArticles, isUnread = false)
rssService.get()
.batchMarkAsRead(articleIds = markAsUnreadArticles, isUnread = true)
}.invokeOnCompletion { diffMap.clear() }
}
}
data class FlowUiState(
@ -105,3 +114,5 @@ data class FlowUiState(
val isBack: Boolean = false,
val syncWorkInfo: String = "",
)
data class Diff(val isUnread: Boolean)