From f33dc8f79705b8b6051370525c67ffe88bfcc68e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 20 Apr 2022 09:25:46 +0300 Subject: [PATCH] Update feed ui --- .../list/decor/TypedSpacingItemDecoration.kt | 35 ++++++++ .../koitharu/kotatsu/core/ui/DateTimeAgo.kt | 29 ++++++- .../kotatsu/tracker/ui/FeedFragment.kt | 8 +- .../kotatsu/tracker/ui/FeedViewModel.kt | 38 +++++++-- .../kotatsu/tracker/ui/adapter/FeedAdapter.kt | 8 +- .../kotatsu/tracker/ui/adapter/FeedItemAD.kt | 13 ++- .../kotatsu/tracker/ui/model/FeedItem.kt | 4 +- .../ui/model/ListModelConversionExt.kt | 30 ++----- app/src/main/res/layout/fragment_feed.xml | 2 +- app/src/main/res/layout/item_feed.xml | 56 ++++++++++++ app/src/main/res/layout/item_tracklog.xml | 85 ------------------- 11 files changed, 178 insertions(+), 130 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt create mode 100644 app/src/main/res/layout/item_feed.xml delete mode 100644 app/src/main/res/layout/item_tracklog.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt new file mode 100644 index 000000000..5662f026a --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/TypedSpacingItemDecoration.kt @@ -0,0 +1,35 @@ +package org.koitharu.kotatsu.base.ui.list.decor + +import android.graphics.Rect +import android.util.SparseIntArray +import android.view.View +import androidx.core.util.getOrDefault +import androidx.core.util.set +import androidx.recyclerview.widget.RecyclerView + +class TypedSpacingItemDecoration( + vararg spacingMapping: Pair, + private val fallbackSpacing: Int = 0, +) : RecyclerView.ItemDecoration() { + + private val mapping = SparseIntArray(spacingMapping.size) + + init { + spacingMapping.forEach { (k, v) -> mapping[k] = v } + } + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val itemType = parent.getChildViewHolder(view)?.itemViewType + val spacing = if (itemType == null) { + fallbackSpacing + } else { + mapping.getOrDefault(itemType, fallbackSpacing) + } + outRect.set(spacing, spacing, spacing, spacing) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt index 0d38d95ce..03bafa077 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt @@ -3,6 +3,9 @@ package org.koitharu.kotatsu.core.ui import android.content.res.Resources import org.koitharu.kotatsu.R import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.utils.ext.daysDiff +import org.koitharu.kotatsu.utils.ext.format +import java.util.* sealed class DateTimeAgo : ListModel { @@ -72,9 +75,33 @@ sealed class DateTimeAgo : ListModel { override fun hashCode(): Int = days } + class Absolute(private val date: Date) : DateTimeAgo() { + + private val day = date.daysDiff(0) + + override fun format(resources: Resources): String { + return date.format("d MMMM") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Absolute + + if (day != other.day) return false + + return true + } + + override fun hashCode(): Int { + return day + } + } + object LongAgo : DateTimeAgo() { override fun format(resources: Resources): String { return resources.getString(R.string.long_ago) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index ae8d0ed5f..584e2a4ff 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -11,7 +11,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener -import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration import org.koitharu.kotatsu.databinding.FragmentFeedBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.list.ui.adapter.MangaListListener @@ -57,7 +57,11 @@ class FeedFragment : val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing) paddingHorizontal = spacing paddingVertical = resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer) - addItemDecoration(SpacingItemDecoration(spacing)) + val decoration = TypedSpacingItemDecoration( + FeedAdapter.ITEM_TYPE_FEED to 0, + fallbackSpacing = spacing + ) + addItemDecoration(decoration) } viewModel.content.observe(viewLifecycleOwner, this::onListChanged) diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt index 898db478e..292d2c982 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -10,14 +10,15 @@ import kotlinx.coroutines.flow.filterNotNull import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.list.ui.model.EmptyState -import org.koitharu.kotatsu.list.ui.model.ListHeader -import org.koitharu.kotatsu.list.ui.model.LoadingFooter -import org.koitharu.kotatsu.list.ui.model.LoadingState +import org.koitharu.kotatsu.core.ui.DateTimeAgo +import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.ui.model.toFeedItem import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct +import org.koitharu.kotatsu.utils.ext.daysDiff +import java.util.* +import java.util.concurrent.TimeUnit class FeedViewModel( private val repository: TrackingRepository @@ -34,8 +35,8 @@ class FeedViewModel( hasNextPage ) { list, isHasNextPage -> buildList(list.size + 2) { - add(header) if (list.isEmpty()) { + add(header) add( EmptyState( icon = R.drawable.ic_feed, @@ -45,7 +46,7 @@ class FeedViewModel( ) ) } else { - list.mapTo(this) { it.toFeedItem() } + list.mapListTo(this) if (isHasNextPage) { add(LoadingFooter) } @@ -85,4 +86,29 @@ class FeedViewModel( onFeedCleared.postCall(Unit) } } + + private fun List.mapListTo(destination: MutableList) { + var prevDate: DateTimeAgo? = null + for (item in this) { + val date = timeAgo(item.createdAt) + if (prevDate != date) { + destination += date + } + prevDate = date + destination += item.toFeedItem() + } + } + + private fun timeAgo(date: Date): DateTimeAgo { + val diff = (System.currentTimeMillis() - date.time).coerceAtLeast(0L) + val diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diff).toInt() + val diffDays = -date.daysDiff(System.currentTimeMillis()) + return when { + diffMinutes < 3 -> DateTimeAgo.JustNow + diffDays < 1 -> DateTimeAgo.Today + diffDays == 1 -> DateTimeAgo.Yesterday + diffDays < 6 -> DateTimeAgo.DaysAgo(diffDays) + else -> DateTimeAgo.Absolute(date) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt index 1c2d5f7fd..bebf7baae 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt @@ -4,10 +4,11 @@ import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter -import kotlin.jvm.internal.Intrinsics +import org.koitharu.kotatsu.core.ui.DateTimeAgo import org.koitharu.kotatsu.list.ui.adapter.* import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.tracker.ui.model.FeedItem +import kotlin.jvm.internal.Intrinsics class FeedAdapter( coil: ImageLoader, @@ -24,6 +25,7 @@ class FeedAdapter( .addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener)) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener)) .addDelegate(ITEM_TYPE_HEADER, listHeaderAD()) + .addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD()) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -32,6 +34,9 @@ class FeedAdapter( oldItem is FeedItem && newItem is FeedItem -> { oldItem.id == newItem.id } + oldItem is DateTimeAgo && newItem is DateTimeAgo -> { + oldItem == newItem + } else -> oldItem.javaClass == newItem.javaClass } @@ -49,5 +54,6 @@ class FeedAdapter( const val ITEM_TYPE_ERROR_FOOTER = 4 const val ITEM_TYPE_EMPTY = 5 const val ITEM_TYPE_HEADER = 6 + const val ITEM_TYPE_DATE_HEADER = 7 } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt index 77f465f45..7df8279fd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedItemAD.kt @@ -12,7 +12,6 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.tracker.ui.model.FeedItem import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.newImageRequest -import org.koitharu.kotatsu.utils.ext.textAndVisible fun feedItemAD( coil: ImageLoader, @@ -38,13 +37,11 @@ fun feedItemAD( .lifecycle(lifecycleOwner) .enqueueWith(coil) binding.textViewTitle.text = item.title - binding.badge.text = item.subtitle - binding.textViewChapters.text = item.chapters - binding.textViewTruncated.textAndVisible = if (item.truncated > 0) { - getString(R.string._and_x_more, item.truncated) - } else { - null - } + binding.textViewSummary.text = context.resources.getQuantityString( + R.plurals.new_chapters, + item.count, + item.count, + ) } onViewRecycled { diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt index daf0b5e60..1cdce1869 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/FeedItem.kt @@ -7,8 +7,6 @@ data class FeedItem( val id: Long, val imageUrl: String, val title: String, - val subtitle: String, - val chapters: CharSequence, val manga: Manga, - val truncated: Int, + val count: Int, ) : ListModel \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt index f4552b9a7..fc4bc6080 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/model/ListModelConversionExt.kt @@ -2,26 +2,10 @@ package org.koitharu.kotatsu.tracker.ui.model import org.koitharu.kotatsu.core.model.TrackingLogItem -fun TrackingLogItem.toFeedItem(): FeedItem { - val truncate = chapters.size > MAX_CHAPTERS - val chaptersString = if (truncate) { - chapters.joinToString( - separator = "\n", - limit = MAX_CHAPTERS - 1, - truncated = "", - ).trimEnd() - } else { - chapters.joinToString("\n") - } - return FeedItem( - id = id, - imageUrl = manga.coverUrl, - title = manga.title, - subtitle = chapters.size.toString(), - chapters = chaptersString, - manga = manga, - truncated = chapters.size - MAX_CHAPTERS + 1, - ) -} - -private const val MAX_CHAPTERS = 6 \ No newline at end of file +fun TrackingLogItem.toFeedItem() = FeedItem( + id = id, + imageUrl = manga.coverUrl, + title = manga.title, + count = chapters.size, + manga = manga, +) \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 1b60ef073..f95f84b1a 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -14,4 +14,4 @@ android:paddingBottom="@dimen/grid_spacing_outer" app:fastScrollEnabled="true" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - tools:listitem="@layout/item_tracklog" /> \ No newline at end of file + tools:listitem="@layout/item_feed" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed.xml b/app/src/main/res/layout/item_feed.xml new file mode 100644 index 000000000..7b29d3eee --- /dev/null +++ b/app/src/main/res/layout/item_feed.xml @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/item_tracklog.xml b/app/src/main/res/layout/item_tracklog.xml deleted file mode 100644 index 2d073a0b6..000000000 --- a/app/src/main/res/layout/item_tracklog.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -