From f0f25a4d48c28d7fa316db2e67a9c27e29b88cd7 Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Sun, 31 Mar 2024 13:21:42 +0300 Subject: [PATCH] Adjust feed list --- .../core/components/effects/ListAnimation.kt | 6 +- .../core/tracker/model/TrackingLogItem.kt | 9 +- .../xtimms/tokusho/sections/feed/FeedView.kt | 112 ++++++++++-------- .../tokusho/sections/feed/FeedViewItem.kt | 4 +- .../tokusho/sections/feed/FeedViewModel.kt | 14 --- .../tokusho/sections/history/HistoryView.kt | 17 +-- .../org/xtimms/tokusho/utils/lang/Date.kt | 5 +- 7 files changed, 85 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/org/xtimms/tokusho/core/components/effects/ListAnimation.kt b/app/src/main/java/org/xtimms/tokusho/core/components/effects/ListAnimation.kt index 88293f3..efd1520 100644 --- a/app/src/main/java/org/xtimms/tokusho/core/components/effects/ListAnimation.kt +++ b/app/src/main/java/org/xtimms/tokusho/core/components/effects/ListAnimation.kt @@ -22,7 +22,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.xtimms.tokusho.sections.history.HistoryItemModel +import org.xtimms.tokusho.core.model.ListModel import java.time.Instant enum class RowEntityType { Header, Item } @@ -32,7 +32,7 @@ data class RowEntity( val key: String, var contentHash: String? = null, val day: Instant, - var historyItemModel: HistoryItemModel?, + var itemModel: ListModel?, ) @SuppressLint("ComposableNaming", "UnusedTransitionTargetStateParameter") @@ -126,7 +126,7 @@ fun updateAnimatedItemsState( override fun onChanged(position: Int, count: Int, payload: Any?) { for (i in 0 until count) { - compositeList[position + i].item.historyItemModel = (payload as RowEntity).historyItemModel + compositeList[position + i].item.itemModel = (payload as RowEntity).itemModel compositeList[position + i].item.contentHash = payload.contentHash } } diff --git a/app/src/main/java/org/xtimms/tokusho/core/tracker/model/TrackingLogItem.kt b/app/src/main/java/org/xtimms/tokusho/core/tracker/model/TrackingLogItem.kt index 8234c76..4c4d764 100644 --- a/app/src/main/java/org/xtimms/tokusho/core/tracker/model/TrackingLogItem.kt +++ b/app/src/main/java/org/xtimms/tokusho/core/tracker/model/TrackingLogItem.kt @@ -1,6 +1,7 @@ package org.xtimms.tokusho.core.tracker.model import org.koitharu.kotatsu.parsers.model.Manga +import org.xtimms.tokusho.core.model.ListModel import java.time.Instant data class TrackingLogItem( @@ -9,4 +10,10 @@ data class TrackingLogItem( val chapters: List, val createdAt: Instant, val isNew: Boolean, -) \ No newline at end of file +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is TrackingLogItem && other.manga.id == manga.id + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedView.kt b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedView.kt index 10a97a7..2d25c80 100644 --- a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedView.kt +++ b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedView.kt @@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ClearAll import androidx.compose.material.icons.outlined.Refresh @@ -39,10 +37,15 @@ import org.xtimms.tokusho.core.components.DismissButton import org.xtimms.tokusho.core.components.ListGroupHeader import org.xtimms.tokusho.core.components.ScaffoldWithClassicTopAppBar import org.xtimms.tokusho.core.components.TokushoDialog +import org.xtimms.tokusho.core.components.effects.RowEntity +import org.xtimms.tokusho.core.components.effects.RowEntityType +import org.xtimms.tokusho.core.components.effects.animatedItemsIndexed +import org.xtimms.tokusho.core.components.effects.updateAnimatedItemsState import org.xtimms.tokusho.core.screens.EmptyScreen import org.xtimms.tokusho.core.tracker.model.TrackingLogItem import org.xtimms.tokusho.sections.feed.model.toFeedItem import org.xtimms.tokusho.utils.lang.calculateTimeAgo +import org.xtimms.tokusho.utils.lang.isSameDay import java.time.Instant const val FEED_DESTINATION = "feed" @@ -60,6 +63,39 @@ fun FeedView( val feed by viewModel.content.collectAsStateWithLifecycle(emptyList()) + val animatedList = run { + val list = emptyList().toMutableList() + var createdAt: Instant? = null + feed.forEach { item -> + + if (createdAt === null || !isSameDay( + item.createdAt.toEpochMilli(), + createdAt!!.toEpochMilli() + ) + ) { + createdAt = item.createdAt + + list.add( + RowEntity( + type = RowEntityType.Header, + key = "header-${createdAt}", + itemModel = null, + day = createdAt!!, + ) + ) + } + list.add( + RowEntity( + type = RowEntityType.Item, + key = "item-${item.manga.id}-${item.createdAt}", + day = createdAt!!, + itemModel = item + ) + ) + } + updateAnimatedItemsState(newList = list.toList().map { it }) + } + ScaffoldWithClassicTopAppBar( title = stringResource(R.string.feed), navigateBack = navigateBack, @@ -93,7 +129,29 @@ fun FeedView( verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = padding ) { - feedUiItems(coil, viewModel.getUiModel()) + + + animatedItemsIndexed( + state = animatedList.value, + key = { rowItem -> rowItem.key }, + ) { _, item -> + when (item.type) { + RowEntityType.Header -> ListGroupHeader( + calculateTimeAgo(item.day).format( + LocalContext.current.resources + ) + ) + + RowEntityType.Item -> FeedViewItem( + modifier = Modifier.animateItemPlacement(), + coil = coil, + selected = false, + feed = (item.itemModel as TrackingLogItem).toFeedItem(), + onClick = { /*TODO*/ }, + onLongClick = { /*TODO*/ } + ) + } + } } } if (feed.isEmpty()) { @@ -179,52 +237,4 @@ private fun ClearFeedDialogPreview() { onDismissRequest = {}, isClearInfoAboutNewChaptersSelected = false ) -} - -sealed interface FeedUiModel { - data class Header(val date: Instant) : FeedUiModel - data class Item(val item: TrackingLogItem) : FeedUiModel -} - -@OptIn(ExperimentalFoundationApi::class) -internal fun LazyListScope.feedUiItems( - coil: ImageLoader, - uiModels: List -) { - items( - items = uiModels, - contentType = { - when (it) { - is FeedUiModel.Header -> "header" - is FeedUiModel.Item -> "item" - } - }, - key = { - when (it) { - is FeedUiModel.Header -> "feedHeader-${it.hashCode()}" - is FeedUiModel.Item -> "feed-${it.item.manga.id}" - } - }, - ) { item -> - when (item) { - is FeedUiModel.Header -> { - ListGroupHeader( - modifier = Modifier.animateItemPlacement(), - text = calculateTimeAgo(item.date).format( - LocalContext.current.resources - ) - ) - } - is FeedUiModel.Item -> { - val track = item.item - FeedViewItem( - modifier = Modifier.animateItemPlacement(), - coil = coil, - selected = false, - feed = track.toFeedItem(), - onClick = { /*TODO*/ }, - onLongClick = { /*TODO*/ }) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewItem.kt b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewItem.kt index a482ccc..c7eeea0 100644 --- a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewItem.kt +++ b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewItem.kt @@ -46,7 +46,7 @@ fun FeedViewItem( ) { val haptic = LocalHapticFeedback.current - val textAlpha = if (!feed.isNew) 1f else ReadItemAlpha + val textAlpha = if (feed.isNew) 1f else ReadItemAlpha Row( modifier = modifier @@ -85,7 +85,7 @@ fun FeedViewItem( Row(verticalAlignment = Alignment.CenterVertically) { var textHeight by remember { mutableIntStateOf(0) } - if (!feed.isNew) { + if (feed.isNew) { Icon( imageVector = Icons.Filled.Circle, contentDescription = stringResource(R.string.unread), diff --git a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewModel.kt b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewModel.kt index 5bf9398..e1e27fa 100644 --- a/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewModel.kt +++ b/app/src/main/java/org/xtimms/tokusho/sections/feed/FeedViewModel.kt @@ -54,18 +54,4 @@ class FeedViewModel @Inject constructor( fun updateFeed() { trackScheduler.startNow() } - - fun getUiModel(): List { - return content.value - .map { FeedUiModel.Item(it) } - .insertSeparators { before, after -> - val beforeDate = before?.item?.createdAt - val afterDate = after?.item?.createdAt - when { - beforeDate != afterDate && afterDate != null -> FeedUiModel.Header(afterDate) - // Return null to avoid adding a separator between two items. - else -> null - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/xtimms/tokusho/sections/history/HistoryView.kt b/app/src/main/java/org/xtimms/tokusho/sections/history/HistoryView.kt index a27d12f..dedabf6 100644 --- a/app/src/main/java/org/xtimms/tokusho/sections/history/HistoryView.kt +++ b/app/src/main/java/org/xtimms/tokusho/sections/history/HistoryView.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -97,7 +98,7 @@ fun HistoryView( RowEntity( type = RowEntityType.Header, key = "header-${readDate}", - historyItemModel = null, + itemModel = null, day = readDate!!, ) ) @@ -107,7 +108,7 @@ fun HistoryView( type = RowEntityType.Item, key = "item-${item.manga.id}", day = readDate!!, - historyItemModel = item + itemModel = item ) ) } @@ -115,7 +116,9 @@ fun HistoryView( } Box( - Modifier.fillMaxSize() + modifier = Modifier + .clipToBounds() + .fillMaxSize(), ) { history.let { if (it == null) { @@ -140,7 +143,7 @@ fun HistoryView( animatedItemsIndexed( state = animatedList.value, key = { rowItem -> rowItem.key }, - ) { index, item -> + ) { _, item -> when (item.type) { RowEntityType.Header -> ListGroupHeader( calculateTimeAgo(item.day).format( @@ -157,7 +160,7 @@ fun HistoryView( icon = Icons.Outlined.DeleteForever, stayDismissed = true, onDismiss = { - viewModel.removeFromHistory(item.historyItemModel!!) + viewModel.removeFromHistory(item.itemModel!! as HistoryItemModel) } ), endActionsConfig = SwipeActionsConfig( @@ -233,8 +236,8 @@ fun HistoryView( ) { HistoryItem( coil = coil, - history = item.historyItemModel!!, - onClick = { navigateToDetails(item.historyItemModel!!.manga.id) }, + history = (item.itemModel!! as HistoryItemModel), + onClick = { navigateToDetails((item.itemModel!! as HistoryItemModel).manga.id) }, ) } } diff --git a/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt b/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt index 4fe6628..70ab736 100644 --- a/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt +++ b/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt @@ -29,10 +29,7 @@ fun calculateTimeAgo(instant: Instant, showMonths: Boolean = false): DateTimeAgo val diffDays = localDate.until(now, ChronoUnit.DAYS) return when { - diffDays == 0L -> { - if (instant.until(Instant.now(), ChronoUnit.MINUTES) < 3) DateTimeAgo.JustNow - else DateTimeAgo.Today - } + diffDays == 0L -> DateTimeAgo.Today diffDays == 1L -> DateTimeAgo.Yesterday diffDays < 6 -> DateTimeAgo.DaysAgo(diffDays.toInt()) else -> {