Merge pull request 'add dynamic spanCount for library/search fragemnt and MediaFragmentSimilar' (#68) from feature/dynamic_span_count into develop

Reviewed-on: #68
This commit is contained in:
Jannik 2022-12-04 15:24:18 +01:00
commit 81a20e0aa9
16 changed files with 226 additions and 221 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() {
@ -52,6 +52,8 @@ class HomeFragment : Fragment() {
private val model: HomeViewModel by viewModels() private val model: HomeViewModel by viewModels()
private lateinit var binding: FragmentHomeBinding private lateinit var binding: FragmentHomeBinding
private val itemOffset = 21
private val playerResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { private val playerResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
model.updateUpNextItems() model.updateUpNextItems()
} }
@ -64,40 +66,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))
} },
itemOffset
) )
binding.recyclerWatchlist.adapter = MediaItemListAdapter( binding.recyclerWatchlist.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
itemOffset
) )
binding.recyclerRecommendations.adapter = MediaItemListAdapter( binding.recyclerRecommendations.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
itemOffset
) )
binding.recyclerNewTitles.adapter = MediaItemListAdapter( binding.recyclerNewTitles.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
itemOffset
) )
binding.recyclerTopTen.adapter = MediaItemListAdapter( binding.recyclerTopTen.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
} },
itemOffset
) )
binding.textHighlightMyList.setOnClickListener { binding.textHighlightMyList.setOnClickListener {
@ -108,6 +109,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 +177,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 / requireContext().resources.getInteger(R.integer.item_media_columns)) - itemOffset
}
} }
} }

View File

@ -12,14 +12,15 @@ 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.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 LibraryFragment : Fragment() { class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding private lateinit var binding: FragmentLibraryBinding
private lateinit var adapter: MediaItemAdapter private lateinit var adapter: MediaItemListAdapter
private val itemList = arrayListOf<ItemMedia>() private val itemList = arrayListOf<ItemMedia>()
private val pageSize = 30 private val pageSize = 30
@ -33,26 +34,23 @@ class LibraryFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9))
// TODO replace with pagination3
// https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797
binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener())
// init async // init async
lifecycleScope.launch { lifecycleScope.launch {
// create and set the adapter, needs context // create and set the adapter, needs context
context?.let { context?.let {
val initialResults = Crunchyroll.browse(n = pageSize) itemList.addAll(Crunchyroll.browse(n = pageSize).toItemMediaList())
itemList.addAll(initialResults.items.map { item ->
ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source)
})
nextItemIndex += pageSize nextItemIndex += pageSize
adapter = MediaItemAdapter(itemList) adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener {
adapter.onItemClick = { mediaIdStr, _ -> activity?.showFragment(MediaFragment(it.id))
activity?.showFragment(MediaFragment(mediaIdStr)) })
}
binding.recyclerMediaLibrary.adapter = adapter binding.recyclerMediaLibrary.adapter = adapter
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9)) adapter.submitList(itemList)
// TODO replace with pagination3
// https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797
binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener())
} }
} }
@ -72,10 +70,7 @@ class LibraryFragment : Fragment() {
isLoading = true isLoading = true
lifecycleScope.launch { lifecycleScope.launch {
val firstNewItemIndex = itemList.lastIndex + 1 val firstNewItemIndex = itemList.lastIndex + 1
val results = Crunchyroll.browse(start = nextItemIndex, n = pageSize) itemList.addAll(Crunchyroll.browse(start = nextItemIndex, n = pageSize).toItemMediaList())
itemList.addAll(results.items.map { item ->
ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source)
})
nextItemIndex += pageSize nextItemIndex += pageSize
adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize) adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize)

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 / parent.context.resources.getInteger(R.integer.item_media_columns)) - 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

@ -7,19 +7,23 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import org.mosad.teapod.R
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 / parent.context.resources.getInteger(R.integer.item_media_columns)) - itemOffset
}
return MediaViewHolder(binding)
} }
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) { override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
@ -36,7 +40,7 @@ class MediaItemListAdapter(private val onClickListener: OnClickListener) : ListA
fun bind(item: ItemMedia) { fun bind(item: ItemMedia) {
binding.textTitle.text = item.title binding.textTitle.text = item.title
Glide.with(binding.imagePoster) Glide.with(binding.root.context)
.load(item.posterUrl) .load(item.posterUrl)
.into(binding.imagePoster) .into(binding.imagePoster)

View File

@ -120,7 +120,7 @@
<LinearLayout <LinearLayout
android:id="@+id/linear_up_next" android:id="@+id/linear_up_next"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="7dp"> android:paddingBottom="7dp">
@ -139,7 +139,7 @@
<com.facebook.shimmer.ShimmerFrameLayout <com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer_layout_up_next" android:id="@+id/shimmer_layout_up_next"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
tools:visibility="gone"> tools:visibility="gone">
<LinearLayout <LinearLayout
@ -159,7 +159,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_up_next" android:id="@+id/recycler_up_next"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_media" /> tools:listitem="@layout/item_media" />
@ -207,7 +207,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_watchlist" android:id="@+id/recycler_watchlist"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_media" /> tools:listitem="@layout/item_media" />

View File

@ -19,7 +19,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="@integer/item_media_columns"
tools:listitem="@layout/item_media" /> tools:listitem="@layout/item_media" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -16,7 +16,7 @@
android:paddingEnd="3dp" android:paddingEnd="3dp"
android:paddingBottom="3dp" android:paddingBottom="3dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="@integer/item_media_columns"
tools:listitem="@layout/item_media" /> tools:listitem="@layout/item_media" />
</FrameLayout> </FrameLayout>

View File

@ -35,7 +35,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_text" app:layout_constraintTop_toBottomOf="@+id/search_text"
app:spanCount="2" app:spanCount="@integer/item_media_columns"
tools:listitem="@layout/item_media"> tools:listitem="@layout/item_media">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>

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"
app:layout_constraintWidth_max="195dp"> 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_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"
app:layout_constraintWidth="195dp">
<ImageView <FrameLayout
android:id="@+id/image_poster" android:id="@+id/frame_image_progress"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:contentDescription="@string/media_poster_desc" app:layout_constraintBottom_toTopOf="@+id/text_title"
android:scaleType="centerCrop" app:layout_constraintDimensionRatio="H,16:9"
tools:srcCompat="@color/imagePlaceholder" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView <ImageView
android:id="@+id/image_episode_play" android:id="@+id/image_poster"
android:layout_width="wrap_content" 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:layout_gravity="center" android:gravity="center"
android:background="@drawable/bg_circle__black_transparent_24dp" android:lines="2"
android:contentDescription="@string/button_play" android:maxLines="2"
app:srcCompat="@drawable/ic_baseline_play_arrow_24" android:padding="3dp"
app:tint="#FFFFFF" /> 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.progressindicator.LinearProgressIndicator </com.google.android.material.card.MaterialCardView>
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 </androidx.constraintlayout.widget.ConstraintLayout>
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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_media_columns" type="integer">3</item>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_media_columns" type="integer">4</item>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_media_columns" type="integer">5</item>
</resources>

View File

@ -2,4 +2,5 @@
<resources> <resources>
<dimen name="player_styled_progress_layout_height">28dp</dimen> <dimen name="player_styled_progress_layout_height">28dp</dimen>
<dimen name="player_styled_progress_margin_bottom">52dp</dimen> <dimen name="player_styled_progress_margin_bottom">52dp</dimen>
<item name="item_media_columns" type="integer">2</item>
</resources> </resources>