Change cover placeholder (#6756)

(cherry picked from commit 869424cd160b2c46a71d98e101a82dbbcf97c408)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceCompactGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt
This commit is contained in:
Ivan Iskandar 2022-03-10 05:26:55 +07:00 committed by Jobobby04
parent 01b8256daf
commit 4a627ea359
11 changed files with 54 additions and 222 deletions

View File

@ -3,15 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.latest
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
class LatestCardHolder(view: View, adapter: LatestCardAdapter) : class LatestCardHolder(view: View, adapter: LatestCardAdapter) :
FlexibleViewHolder(view, adapter) { FlexibleViewHolder(view, adapter) {
@ -55,17 +51,8 @@ class LatestCardHolder(view: View, adapter: LatestCardAdapter) :
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.dispose() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.cover.loadAutoPause(manga) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(itemView.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.diskCachePolicy(CachePolicy.DISABLED)
.target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -2,15 +2,12 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
import exh.metadata.metadata.MangaDexSearchMetadata import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -67,16 +64,8 @@ class SourceComfortableGridHolder(
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
binding.thumbnail.dispose() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.thumbnail.loadAutoPause(manga) {
val crossfadeDuration = binding.root.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(binding.root.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -2,15 +2,12 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
import exh.metadata.metadata.MangaDexSearchMetadata import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -67,16 +64,8 @@ class SourceCompactGridHolder(
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
binding.thumbnail.dispose() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.thumbnail.loadAutoPause(manga) {
val crossfadeDuration = binding.root.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(binding.root.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -3,17 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceEnhancedEhentaiListItemBinding import eu.kanade.tachiyomi.databinding.SourceEnhancedEhentaiListItemBinding
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
import exh.metadata.MetadataUtil import exh.metadata.MetadataUtil
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -50,10 +46,11 @@ class SourceEnhancedEHentaiListHolder(view: View, adapter: FlexibleAdapter<*>) :
binding.thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f binding.thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f
// For rounded corners // For rounded corners
binding.badges.clipToOutline = true binding.badges.leftBadges.clipToOutline = true
binding.badges.rightBadges.clipToOutline = true
// Set favorite badge // Set favorite badge
binding.favoriteText.isVisible = manga.favorite binding.badges.favoriteText.isVisible = manga.favorite
setImage(manga) setImage(manga)
} }
@ -103,21 +100,9 @@ class SourceEnhancedEHentaiListHolder(view: View, adapter: FlexibleAdapter<*>) :
} }
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
// For rounded corners
binding.card.clipToOutline = true
binding.thumbnail.dispose() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.thumbnail.loadAutoPause(manga) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(itemView.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.diskCachePolicy(CachePolicy.DISABLED)
.target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -3,14 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) : class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) :
FlexibleViewHolder(view, adapter) { FlexibleViewHolder(view, adapter) {
@ -54,16 +51,8 @@ class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) :
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.dispose() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.cover.loadAutoPause(manga) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(itemView.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -3,15 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.source.index
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.dispose import coil.dispose
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.transition.CrossfadeTransition
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
import eu.kanade.tachiyomi.widget.StateImageViewTarget import eu.kanade.tachiyomi.util.view.loadAutoPause
class IndexCardHolder(view: View, adapter: IndexCardAdapter) : class IndexCardHolder(view: View, adapter: IndexCardAdapter) :
FlexibleViewHolder(view, adapter) { FlexibleViewHolder(view, adapter) {
@ -55,17 +51,8 @@ class IndexCardHolder(view: View, adapter: IndexCardAdapter) :
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.dispose() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { binding.cover.loadAutoPause(manga) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let { setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
if (it is CrossfadeTransition.Factory) it.durationMillis else 0
}
val request = ImageRequest.Builder(itemView.context)
.data(manga)
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
.diskCachePolicy(CachePolicy.DISABLED)
.target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration))
.build()
itemView.context.imageLoader.enqueue(request)
} }
} }
} }

View File

@ -1,11 +1,14 @@
package eu.kanade.tachiyomi.util.view package eu.kanade.tachiyomi.util.view
import android.content.Context import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.ColorUtils
import coil.ImageLoader import coil.ImageLoader
import coil.imageLoader import coil.imageLoader
import coil.load import coil.load
@ -38,20 +41,22 @@ fun ImageView.loadAutoPause(
loader: ImageLoader = context.imageLoader, loader: ImageLoader = context.imageLoader,
builder: ImageRequest.Builder.() -> Unit = {} builder: ImageRequest.Builder.() -> Unit = {}
) { ) {
// Build the original request so we can add on our success listener
load(data, loader) { load(data, loader) {
val placeholderColor = ColorUtils.setAlphaComponent(Color.GRAY, 0x1F) // 12% gray
placeholder(ColorDrawable(placeholderColor))
// Build the original request so we can add on our success listener // Build the original request so we can add on our success listener
val originalBuild = apply(builder).build() val originalListener = apply(builder).build().listener
listener( listener(
onSuccess = { request, metadata -> onSuccess = { request, metadata ->
(request.target as? ImageViewTarget)?.drawable.let { (request.target as? ImageViewTarget)?.drawable.let {
if (it is Animatable && context.animatorDurationScale == 0f) it.stop() if (it is Animatable && context.animatorDurationScale == 0f) it.stop()
} }
originalBuild.listener?.onSuccess(request, metadata) originalListener?.onSuccess(request, metadata)
}, },
onStart = { request -> originalBuild.listener?.onStart(request) }, onStart = { request -> originalListener?.onStart(request) },
onCancel = { request -> originalBuild.listener?.onCancel(request) }, onCancel = { request -> originalListener?.onCancel(request) },
onError = { request, throwable -> originalBuild.listener?.onError(request, throwable) } onError = { request, throwable -> originalListener?.onError(request, throwable) }
) )
} }
} }

View File

@ -1,43 +0,0 @@
package eu.kanade.tachiyomi.widget
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.core.view.isVisible
import coil.drawable.CrossfadeDrawable
import coil.target.ImageViewTarget
/**
* A Coil target to display an image with an optional view to show while loading.
*
* @param target the view where the image will be loaded
* @param progress the view to show when the image is loading.
* @param crossfadeDuration duration in millisecond to crossfade the result drawable
*/
class StateImageViewTarget(
private val target: ImageView,
private val progress: View,
private val crossfadeDuration: Int = 0
) : ImageViewTarget(target) {
override fun onStart(placeholder: Drawable?) {
progress.isVisible = true
}
override fun onSuccess(result: Drawable) {
progress.isVisible = false
if (crossfadeDuration > 0) {
val crossfadeResult = CrossfadeDrawable(target.drawable, result, durationMillis = crossfadeDuration)
target.setImageDrawable(crossfadeResult)
crossfadeResult.start()
} else {
target.setImageDrawable(result)
}
}
override fun onError(error: Drawable?) {
progress.isVisible = false
if (error != null) {
target.setImageDrawable(error)
}
}
}

View File

@ -9,19 +9,6 @@
android:foreground="@drawable/library_item_selector_overlay" android:foreground="@drawable/library_item_selector_overlay"
android:padding="4dp"> android:padding="4dp">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress"
style="@style/Widget.Tachiyomi.CircularProgressIndicator.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/thumbnail"
app:layout_constraintEnd_toEndOf="@+id/thumbnail"
app:layout_constraintStart_toStartOf="@+id/thumbnail"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/thumbnail" android:id="@+id/thumbnail"
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -83,18 +83,4 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:text="Sample name" /> tools:text="Sample name" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress"
style="@style/Widget.Tachiyomi.CircularProgressIndicator.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,58 +8,29 @@
android:foreground="@drawable/library_item_selector_overlay" android:foreground="@drawable/library_item_selector_overlay"
android:padding="4dp"> android:padding="4dp">
<FrameLayout <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/card" android:id="@+id/thumbnail"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="140dp" android:layout_height="140dp"
android:background="@drawable/rounded_rectangle" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="w,2:3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Cover"
<ImageView tools:ignore="ContentDescription"
android:id="@+id/thumbnail" tools:src="@mipmap/ic_launcher" />
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:scaleType="centerCrop"
tools:src="@mipmap/ic_launcher" />
<LinearLayout
android:id="@+id/badges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/rounded_rectangle">
<TextView
android:id="@+id/favorite_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorSecondary"
android:maxLines="1"
android:paddingStart="3dp"
android:paddingTop="1dp"
android:paddingEnd="3dp"
android:paddingBottom="1dp"
android:text="@string/in_library"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSecondary"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress"
style="@style/Widget.Tachiyomi.CircularProgressIndicator.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
<include
android:id="@+id/badges"
layout="@layout/source_grid_item_badges"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="@+id/thumbnail"
app:layout_constraintStart_toStartOf="@+id/thumbnail"
app:layout_constraintTop_toTopOf="@+id/thumbnail" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/title" android:id="@+id/title"
@ -71,7 +42,7 @@
android:maxLines="2" android:maxLines="2"
android:textAppearance="?attr/textAppearanceTitleSmall" android:textAppearance="?attr/textAppearanceTitleSmall"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card" app:layout_constraintStart_toEndOf="@+id/thumbnail"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Manga title for the life of me I cant think yes totally" /> tools:text="Manga title for the life of me I cant think yes totally" />
@ -83,7 +54,7 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card" app:layout_constraintStart_toEndOf="@+id/thumbnail"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Manga title for the life of me I cant think yes totally" /> tools:text="Manga title for the life of me I cant think yes totally" />
@ -95,7 +66,7 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:background="@drawable/rounded_rectangle" android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/card"> app:layout_constraintStart_toEndOf="@+id/thumbnail">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/genre" android:id="@+id/genre"
@ -121,7 +92,7 @@
android:minHeight="20dp" android:minHeight="20dp"
android:numStars="5" android:numStars="5"
app:layout_constraintBottom_toTopOf="@+id/cardView" app:layout_constraintBottom_toTopOf="@+id/cardView"
app:layout_constraintStart_toEndOf="@+id/card" /> app:layout_constraintStart_toEndOf="@+id/thumbnail" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/date_posted" android:id="@+id/date_posted"