Update feed ui
This commit is contained in:
@@ -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<Int, Int>,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ package org.koitharu.kotatsu.core.ui
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
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 {
|
sealed class DateTimeAgo : ListModel {
|
||||||
|
|
||||||
@@ -72,9 +75,33 @@ sealed class DateTimeAgo : ListModel {
|
|||||||
override fun hashCode(): Int = days
|
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() {
|
object LongAgo : DateTimeAgo() {
|
||||||
override fun format(resources: Resources): String {
|
override fun format(resources: Resources): String {
|
||||||
return resources.getString(R.string.long_ago)
|
return resources.getString(R.string.long_ago)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
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.databinding.FragmentFeedBinding
|
||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||||
@@ -57,7 +57,11 @@ class FeedFragment :
|
|||||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||||
paddingHorizontal = spacing
|
paddingHorizontal = spacing
|
||||||
paddingVertical = resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer)
|
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)
|
viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListHeader
|
import org.koitharu.kotatsu.list.ui.model.*
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
|
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
|
||||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
|
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class FeedViewModel(
|
class FeedViewModel(
|
||||||
private val repository: TrackingRepository
|
private val repository: TrackingRepository
|
||||||
@@ -34,8 +35,8 @@ class FeedViewModel(
|
|||||||
hasNextPage
|
hasNextPage
|
||||||
) { list, isHasNextPage ->
|
) { list, isHasNextPage ->
|
||||||
buildList(list.size + 2) {
|
buildList(list.size + 2) {
|
||||||
add(header)
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
|
add(header)
|
||||||
add(
|
add(
|
||||||
EmptyState(
|
EmptyState(
|
||||||
icon = R.drawable.ic_feed,
|
icon = R.drawable.ic_feed,
|
||||||
@@ -45,7 +46,7 @@ class FeedViewModel(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
list.mapTo(this) { it.toFeedItem() }
|
list.mapListTo(this)
|
||||||
if (isHasNextPage) {
|
if (isHasNextPage) {
|
||||||
add(LoadingFooter)
|
add(LoadingFooter)
|
||||||
}
|
}
|
||||||
@@ -85,4 +86,29 @@ class FeedViewModel(
|
|||||||
onFeedCleared.postCall(Unit)
|
onFeedCleared.postCall(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<TrackingLogItem>.mapListTo(destination: MutableList<ListModel>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,11 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
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.adapter.*
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||||
|
import kotlin.jvm.internal.Intrinsics
|
||||||
|
|
||||||
class FeedAdapter(
|
class FeedAdapter(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -24,6 +25,7 @@ class FeedAdapter(
|
|||||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
||||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||||
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD())
|
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD())
|
||||||
|
.addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||||
@@ -32,6 +34,9 @@ class FeedAdapter(
|
|||||||
oldItem is FeedItem && newItem is FeedItem -> {
|
oldItem is FeedItem && newItem is FeedItem -> {
|
||||||
oldItem.id == newItem.id
|
oldItem.id == newItem.id
|
||||||
}
|
}
|
||||||
|
oldItem is DateTimeAgo && newItem is DateTimeAgo -> {
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
else -> oldItem.javaClass == newItem.javaClass
|
else -> oldItem.javaClass == newItem.javaClass
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,5 +54,6 @@ class FeedAdapter(
|
|||||||
const val ITEM_TYPE_ERROR_FOOTER = 4
|
const val ITEM_TYPE_ERROR_FOOTER = 4
|
||||||
const val ITEM_TYPE_EMPTY = 5
|
const val ITEM_TYPE_EMPTY = 5
|
||||||
const val ITEM_TYPE_HEADER = 6
|
const val ITEM_TYPE_HEADER = 6
|
||||||
|
const val ITEM_TYPE_DATE_HEADER = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
|
||||||
|
|
||||||
fun feedItemAD(
|
fun feedItemAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
@@ -38,13 +37,11 @@ fun feedItemAD(
|
|||||||
.lifecycle(lifecycleOwner)
|
.lifecycle(lifecycleOwner)
|
||||||
.enqueueWith(coil)
|
.enqueueWith(coil)
|
||||||
binding.textViewTitle.text = item.title
|
binding.textViewTitle.text = item.title
|
||||||
binding.badge.text = item.subtitle
|
binding.textViewSummary.text = context.resources.getQuantityString(
|
||||||
binding.textViewChapters.text = item.chapters
|
R.plurals.new_chapters,
|
||||||
binding.textViewTruncated.textAndVisible = if (item.truncated > 0) {
|
item.count,
|
||||||
getString(R.string._and_x_more, item.truncated)
|
item.count,
|
||||||
} else {
|
)
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewRecycled {
|
onViewRecycled {
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ data class FeedItem(
|
|||||||
val id: Long,
|
val id: Long,
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val subtitle: String,
|
|
||||||
val chapters: CharSequence,
|
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val truncated: Int,
|
val count: Int,
|
||||||
) : ListModel
|
) : ListModel
|
||||||
@@ -2,26 +2,10 @@ package org.koitharu.kotatsu.tracker.ui.model
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||||
|
|
||||||
fun TrackingLogItem.toFeedItem(): FeedItem {
|
fun TrackingLogItem.toFeedItem() = FeedItem(
|
||||||
val truncate = chapters.size > MAX_CHAPTERS
|
id = id,
|
||||||
val chaptersString = if (truncate) {
|
imageUrl = manga.coverUrl,
|
||||||
chapters.joinToString(
|
title = manga.title,
|
||||||
separator = "\n",
|
count = chapters.size,
|
||||||
limit = MAX_CHAPTERS - 1,
|
manga = manga,
|
||||||
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
|
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
android:paddingBottom="@dimen/grid_spacing_outer"
|
android:paddingBottom="@dimen/grid_spacing_outer"
|
||||||
app:fastScrollEnabled="true"
|
app:fastScrollEnabled="true"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/item_tracklog" />
|
tools:listitem="@layout/item_feed" />
|
||||||
56
app/src/main/res/layout/item_feed.xml
Normal file
56
app/src/main/res/layout/item_feed.xml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/list_selector"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:padding="@dimen/list_spacing">
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
||||||
|
android:id="@+id/imageView_cover"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||||
|
tools:src="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/imageView_cover"
|
||||||
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_summary"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/imageView_cover"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||||
|
tools:text="@string/new_chapters" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:cardCornerRadius="12dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
|
||||||
android:id="@+id/imageView_cover"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="@dimen/manga_list_details_item_height"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
|
||||||
tools:src="@tools:sample/backgrounds/scenic" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
|
||||||
tools:text="@tools:sample/lorem/random" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/badge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="top|end"
|
|
||||||
android:background="@drawable/badge"
|
|
||||||
android:paddingHorizontal="6dp"
|
|
||||||
android:paddingVertical="2dp"
|
|
||||||
android:textColor="?attr/colorOnTertiary"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="54" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView_chapters"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="none"
|
|
||||||
android:lineSpacingExtra="4sp"
|
|
||||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
|
||||||
tools:text="@tools:sample/lorem[10]" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView_truncated"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:textColor="?android:textColorHint"
|
|
||||||
tools:text="@string/_and_x_more" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
Reference in New Issue
Block a user