make MediaItem width fully dynamic, based on the parents width (50% of parent width) and update SearchFragment to use MediaItemListAdapter and remove now unused MediaItemAdapter

This commit is contained in:
Jannik 2022-12-04 13:51:29 +01:00
parent 21caa8eb1b
commit fffbeaeb49
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
8 changed files with 190 additions and 197 deletions

View File

@ -27,6 +27,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -44,7 +45,6 @@ import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel
import org.mosad.teapod.util.* import org.mosad.teapod.util.*
import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter
import org.mosad.teapod.util.adapter.MediaItemListAdapter import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
@ -64,40 +64,39 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.recyclerUpNext.addItemDecoration(MediaItemDecoration(9))
binding.recyclerWatchlist.addItemDecoration(MediaItemDecoration(9))
binding.recyclerRecommendations.addItemDecoration(MediaItemDecoration(9))
binding.recyclerNewTitles.addItemDecoration(MediaItemDecoration(9))
binding.recyclerTopTen.addItemDecoration(MediaItemDecoration(9))
binding.recyclerUpNext.adapter = MediaEpisodeListAdapter( binding.recyclerUpNext.adapter = MediaEpisodeListAdapter(
MediaEpisodeListAdapter.OnClickListener { MediaEpisodeListAdapter.OnClickListener {
playerResult.launch(playerIntent(it.panel.episodeMetadata.seasonId, it.panel.id)) playerResult.launch(playerIntent(it.panel.episodeMetadata.seasonId, it.panel.id))
} },
21
) )
binding.recyclerWatchlist.adapter = MediaItemListAdapter( binding.recyclerWatchlist.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
21
) )
binding.recyclerRecommendations.adapter = MediaItemListAdapter( binding.recyclerRecommendations.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
21
) )
binding.recyclerNewTitles.adapter = MediaItemListAdapter( binding.recyclerNewTitles.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
21
) )
binding.recyclerTopTen.adapter = MediaItemListAdapter( binding.recyclerTopTen.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
21
) )
binding.textHighlightMyList.setOnClickListener { binding.textHighlightMyList.setOnClickListener {
@ -108,6 +107,13 @@ class HomeFragment : Fragment() {
// TODO since this might take a few seconds show a loading animation for the watchlist button // TODO since this might take a few seconds show a loading animation for the watchlist button
} }
// set the shimmer items size as it's depending on the screen size
setShimmerLayoutItemSize(binding.shimmerLayoutUpNext)
setShimmerLayoutItemSize(binding.shimmerLayoutWatchlist)
setShimmerLayoutItemSize(binding.shimmerLayoutRecommendations)
setShimmerLayoutItemSize(binding.shimmerLayoutNewTitles)
setShimmerLayoutItemSize(binding.shimmerLayoutTopTen)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
@ -169,10 +175,19 @@ class HomeFragment : Fragment() {
private fun bindUiStateLoading() { private fun bindUiStateLoading() {
// hide highlights layout // hide highlights layout
binding.linearHighlight.isVisible = false binding.linearHighlight.isVisible = false
println(binding.root.childCount)
binding.root.children.filter { it is ShimmerFrameLayout }.forEach { binding.shimmerLayoutUpNext.startShimmer()
it as ShimmerFrameLayout binding.shimmerLayoutWatchlist.startShimmer()
it.startShimmer() binding.shimmerLayoutRecommendations.startShimmer()
binding.shimmerLayoutNewTitles.startShimmer()
binding.shimmerLayoutTopTen.startShimmer()
}
private fun setShimmerLayoutItemSize(shimmerLayout: ShimmerFrameLayout) {
(shimmerLayout.children.first() as? LinearLayout)?.children?.forEach { child ->
child.layoutParams.apply {
width = (binding.root.measuredWidth / 2) - 21
}
} }
} }

View File

@ -12,7 +12,6 @@ import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentLibraryBinding import org.mosad.teapod.databinding.FragmentLibraryBinding
import org.mosad.teapod.parser.crunchyroll.Crunchyroll import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.adapter.MediaItemAdapter
import org.mosad.teapod.util.adapter.MediaItemListAdapter import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.decoration.MediaItemDecoration
import org.mosad.teapod.util.showFragment import org.mosad.teapod.util.showFragment

View File

@ -9,18 +9,18 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentSearchBinding import org.mosad.teapod.databinding.FragmentSearchBinding
import org.mosad.teapod.parser.crunchyroll.Crunchyroll import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.util.ItemMedia import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.adapter.MediaItemAdapter import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.decoration.MediaItemDecoration
import org.mosad.teapod.util.showFragment import org.mosad.teapod.util.showFragment
import org.mosad.teapod.util.toItemMediaList
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private lateinit var binding: FragmentSearchBinding private lateinit var binding: FragmentSearchBinding
private lateinit var adapter: MediaItemAdapter private lateinit var adapter: MediaItemListAdapter
private val itemList = arrayListOf<ItemMedia>() private val itemList = arrayListOf<ItemMedia>()
private var searchJob: Job? = null private var searchJob: Job? = null
@ -34,19 +34,13 @@ class SearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch { binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
// create and set the adapter, needs context
context?.let {
adapter = MediaItemAdapter(itemList)
adapter.onItemClick = { mediaIdStr, _ ->
binding.searchText.clearFocus()
activity?.showFragment(MediaFragment(mediaIdStr))
}
binding.recyclerMediaSearch.adapter = adapter adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9)) binding.searchText.clearFocus()
} activity?.showFragment(MediaFragment(it.id))
} })
binding.recyclerMediaSearch.adapter = adapter
initActions() initActions()
} }
@ -65,6 +59,9 @@ class SearchFragment : Fragment() {
}) })
} }
/**
* Search for a query string at Crunchyroll and submit the results to the adapter.
*/
private fun search(query: String) { private fun search(query: String) {
// if the query hasn't changed since the last successful search, return // if the query hasn't changed since the last successful search, return
if (query == oldSearchQuery) return if (query == oldSearchQuery) return
@ -79,9 +76,9 @@ class SearchFragment : Fragment() {
itemList.clear() // TODO needs clean up itemList.clear() // TODO needs clean up
// TODO add top results first heading // TODO add top results first heading
itemList.addAll(results.items[0].items.map { item -> adapter.submitList(
ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source) results.items.firstOrNull()?.items?.toItemMediaList() ?: listOf<ItemMedia>()
}) )
// TODO currently only tv shows are supported, hence only the first items array // TODO currently only tv shows are supported, hence only the first items array
// should be always present // should be always present
@ -107,9 +104,6 @@ class SearchFragment : Fragment() {
// }) // })
// } // }
adapter.notifyDataSetChanged()
//adapter.notifyItemRangeInserted(0, itemList.size)
// after successfully searching the query term, add it as old query, to make sure we // after successfully searching the query term, add it as old query, to make sure we
// don't search again if the query hasn't changed // don't search again if the query hasn't changed
oldSearchQuery = query oldSearchQuery = query

View File

@ -11,16 +11,19 @@ import org.mosad.teapod.R
import org.mosad.teapod.databinding.ItemMediaBinding import org.mosad.teapod.databinding.ItemMediaBinding
import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem
class MediaEpisodeListAdapter(private val onClickListener: OnClickListener) : ListAdapter<ContinueWatchingItem, MediaEpisodeListAdapter.MediaViewHolder>(DiffCallback) { class MediaEpisodeListAdapter(private val onClickListener: OnClickListener, private val itemOffset: Int = 0) : ListAdapter<ContinueWatchingItem, MediaEpisodeListAdapter.MediaViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
return MediaViewHolder( val binding = ItemMediaBinding.inflate(
ItemMediaBinding.inflate( LayoutInflater.from(parent.context),
LayoutInflater.from(parent.context), parent,
parent, false
false
)
) )
binding.root.layoutParams.apply {
width = (parent.measuredWidth / 2) - itemOffset
}
return MediaViewHolder(binding)
} }
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) { override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {

View File

@ -1,44 +0,0 @@
package org.mosad.teapod.util.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import org.mosad.teapod.databinding.ItemMediaBinding
import org.mosad.teapod.util.ItemMedia
@Deprecated("Use MediaItemListAdapter instead")
class MediaItemAdapter(private val items: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.MediaViewHolder>() {
var onItemClick: ((id: String, position: Int) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaItemAdapter.MediaViewHolder {
return MediaViewHolder(ItemMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MediaItemAdapter.MediaViewHolder, position: Int) {
holder.binding.root.apply {
holder.binding.textTitle.text = items[position].title
Glide.with(context).load(items[position].posterUrl).into(holder.binding.imagePoster)
}
}
override fun getItemCount(): Int {
return items.size
}
inner class MediaViewHolder(val binding: ItemMediaBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.imageEpisodePlay.isVisible = false // hide the play button for media items
binding.root.setOnClickListener {
onItemClick?.invoke(
items[bindingAdapterPosition].id,
bindingAdapterPosition
)
}
}
}
}

View File

@ -10,16 +10,19 @@ import com.bumptech.glide.Glide
import org.mosad.teapod.databinding.ItemMediaBinding import org.mosad.teapod.databinding.ItemMediaBinding
import org.mosad.teapod.util.ItemMedia import org.mosad.teapod.util.ItemMedia
class MediaItemListAdapter(private val onClickListener: OnClickListener) : ListAdapter<ItemMedia, MediaItemListAdapter.MediaViewHolder>(DiffCallback) { class MediaItemListAdapter(private val onClickListener: OnClickListener, private val itemOffset: Int = 0) : ListAdapter<ItemMedia, MediaItemListAdapter.MediaViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
return MediaViewHolder( val binding = ItemMediaBinding.inflate(
ItemMediaBinding.inflate( LayoutInflater.from(parent.context),
LayoutInflater.from(parent.context), parent,
parent, false
false
)
) )
binding.root.layoutParams.apply {
width = (parent.measuredWidth / 2) - itemOffset
}
return MediaViewHolder(binding)
} }
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) { override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {

View File

@ -1,71 +1,82 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:backgroundTint="?themeSecondary"
app:cardCornerRadius="7dp"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout <com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:backgroundTint="?themeSecondary"
app:cardCornerRadius="7dp"
app:cardElevation="4dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.96">
<FrameLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/frame_image_progress" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content">
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintBottom_toTopOf="@+id/text_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_max="195dp">
<ImageView <FrameLayout
android:id="@+id/image_poster" android:id="@+id/frame_image_progress"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/text_title"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/image_poster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/media_poster_desc"
android:scaleType="fitCenter"
tools:srcCompat="@drawable/placeholder_image" />
<ImageView
android:id="@+id/image_episode_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_circle__black_transparent_24dp"
android:contentDescription="@string/button_play"
app:srcCompat="@drawable/ic_baseline_play_arrow_24"
app:tint="#FFFFFF" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_playhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:max="100"
app:trackColor="#00FFFFFF"
app:trackThickness="2dp" />
</FrameLayout>
<TextView
android:id="@+id/text_title"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/media_poster_desc" android:gravity="center"
android:scaleType="fitCenter" android:lines="2"
tools:srcCompat="@drawable/placeholder_image"/> android:maxLines="2"
android:padding="3dp"
android:text="@string/text_title_ex"
android:textAlignment="center"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView </com.google.android.material.card.MaterialCardView>
android:id="@+id/image_episode_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_circle__black_transparent_24dp"
android:contentDescription="@string/button_play"
app:srcCompat="@drawable/ic_baseline_play_arrow_24"
app:tint="#FFFFFF" />
<com.google.android.material.progressindicator.LinearProgressIndicator </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/progress_playhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:max="100"
app:trackColor="#00FFFFFF"
app:trackThickness="2dp" />
</FrameLayout>
<TextView
android:id="@+id/text_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:lines="2"
android:maxLines="2"
android:padding="3dp"
android:text="@string/text_title_ex"
android:textAlignment="center"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -1,50 +1,62 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:layout_marginStart="4dp"
android:layout_marginEnd="3dp"
android:backgroundTint="?themeSecondary"
app:cardCornerRadius="7dp"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:layout_constraintWidth_max="195dp"> android:layout_marginStart="4dp"
android:layout_marginEnd="3dp"
android:backgroundTint="?themeSecondary"
app:cardCornerRadius="7dp"
app:cardElevation="4dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.96">
<FrameLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/frame_image_progress" android:layout_width="match_parent"
android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_height="0dp" app:layout_constraintWidth_max="195dp">
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent" <FrameLayout
app:layout_constraintStart_toStartOf="parent" android:id="@+id/frame_image_progress"
app:layout_constraintTop_toTopOf="parent" android:layout_width="0dp"
app:layout_constraintWidth="195dp"> android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/image_poster"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?shapeTextBackground"
tools:ignore="ContentDescription" />
</FrameLayout>
<ImageView <ImageView
android:id="@+id/image_poster" android:id="@+id/image_dummy_text"
android:layout_width="match_parent" android:layout_width="128dp"
android:layout_height="match_parent" android:layout_height="19dp"
android:background="?shapeTextBackground" android:layout_margin="11dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress"
app:srcCompat="@drawable/shape_rounded_corner"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </com.google.android.material.card.MaterialCardView>
<ImageView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/image_dummy_text"
android:layout_width="128dp"
android:layout_height="19dp"
android:layout_margin="11dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress"
app:srcCompat="@drawable/shape_rounded_corner"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>