Add header to EpisodeListAdapter
This commit is contained in:
parent
a70d154eca
commit
4bbf40bc22
7 changed files with 249 additions and 162 deletions
|
@ -113,7 +113,7 @@ fun bindPersonImage(imageView: ImageView, person: BaseItemPerson) {
|
|||
}
|
||||
|
||||
@BindingAdapter("episodes")
|
||||
fun bindEpisodes(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
|
||||
fun bindEpisodes(recyclerView: RecyclerView, data: List<EpisodeItem>?) {
|
||||
val adapter = recyclerView.adapter as EpisodeListAdapter
|
||||
adapter.submitList(data)
|
||||
}
|
||||
|
|
|
@ -8,10 +8,37 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
|
||||
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import java.util.*
|
||||
|
||||
class EpisodeListAdapter(private val onClickListener: OnClickListener) :
|
||||
ListAdapter<BaseItemDto, EpisodeListAdapter.EpisodeViewHolder>(DiffCallback) {
|
||||
private const val ITEM_VIEW_TYPE_HEADER = 0
|
||||
private const val ITEM_VIEW_TYPE_EPISODE = 1
|
||||
|
||||
class EpisodeListAdapter(
|
||||
private val onClickListener: OnClickListener,
|
||||
private val seriesId: UUID,
|
||||
private val seriesName: String?,
|
||||
private val seasonId: UUID,
|
||||
private val seasonName: String?
|
||||
) :
|
||||
ListAdapter<EpisodeItem, RecyclerView.ViewHolder>(DiffCallback) {
|
||||
|
||||
class HeaderViewHolder(private var binding: SeasonHeaderBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(
|
||||
seriesId: UUID,
|
||||
seriesName: String?,
|
||||
seasonId: UUID,
|
||||
seasonName: String?
|
||||
) {
|
||||
binding.seriesId = seriesId
|
||||
binding.seasonId = seasonId
|
||||
binding.seasonName.text = seasonName
|
||||
binding.seriesName.text = seriesName
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
||||
class EpisodeViewHolder(private var binding: EpisodeItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
@ -20,7 +47,9 @@ class EpisodeListAdapter(private val onClickListener: OnClickListener) :
|
|||
if (episode.userData?.playedPercentage != null) {
|
||||
binding.progressBar.layoutParams.width = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
(episode.userData?.playedPercentage?.times(.84))!!.toFloat(), binding.progressBar.context.resources.displayMetrics).toInt()
|
||||
(episode.userData?.playedPercentage?.times(.84))!!.toFloat(),
|
||||
binding.progressBar.context.resources.displayMetrics
|
||||
).toInt()
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
|
@ -29,35 +58,75 @@ class EpisodeListAdapter(private val onClickListener: OnClickListener) :
|
|||
}
|
||||
}
|
||||
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<BaseItemDto>() {
|
||||
override fun areItemsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<EpisodeItem>() {
|
||||
override fun areItemsTheSame(oldItem: EpisodeItem, newItem: EpisodeItem): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
|
||||
override fun areContentsTheSame(oldItem: EpisodeItem, newItem: EpisodeItem): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeViewHolder {
|
||||
return EpisodeViewHolder(
|
||||
EpisodeItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
ITEM_VIEW_TYPE_HEADER -> {
|
||||
HeaderViewHolder(
|
||||
SeasonHeaderBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
ITEM_VIEW_TYPE_EPISODE -> {
|
||||
EpisodeViewHolder(
|
||||
EpisodeItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> throw ClassCastException("Unknown viewType $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
holder.itemView.setOnClickListener {
|
||||
onClickListener.onClick(item)
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder.itemViewType) {
|
||||
ITEM_VIEW_TYPE_HEADER -> {
|
||||
(holder as HeaderViewHolder).bind(seriesId, seriesName, seasonId, seasonName)
|
||||
}
|
||||
ITEM_VIEW_TYPE_EPISODE -> {
|
||||
val item = getItem(position) as EpisodeItem.Episode
|
||||
holder.itemView.setOnClickListener {
|
||||
onClickListener.onClick(item.episode)
|
||||
}
|
||||
(holder as EpisodeViewHolder).bind(item.episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (getItem(position)) {
|
||||
is EpisodeItem.Header -> ITEM_VIEW_TYPE_HEADER
|
||||
is EpisodeItem.Episode -> ITEM_VIEW_TYPE_EPISODE
|
||||
}
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
class OnClickListener(val clickListener: (item: BaseItemDto) -> Unit) {
|
||||
fun onClick(item: BaseItemDto) = clickListener(item)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class EpisodeItem {
|
||||
abstract val id: UUID
|
||||
|
||||
object Header : EpisodeItem() {
|
||||
override val id: UUID = UUID.randomUUID()
|
||||
}
|
||||
|
||||
data class Episode(val episode: BaseItemDto) : EpisodeItem() {
|
||||
override val id = episode.id
|
||||
}
|
||||
}
|
|
@ -37,11 +37,7 @@ class SeasonFragment : Fragment() {
|
|||
binding.episodesRecyclerView.adapter =
|
||||
EpisodeListAdapter(EpisodeListAdapter.OnClickListener { episode ->
|
||||
navigateToEpisodeBottomSheetFragment(episode)
|
||||
})
|
||||
binding.seriesName.text = args.seriesName
|
||||
binding.seasonName.text = args.seasonName
|
||||
binding.seriesId = args.seriesId
|
||||
binding.seasonId = args.seasonId
|
||||
}, args.seriesId, args.seriesName, args.seasonId, args.seasonName)
|
||||
|
||||
viewModel.loadEpisodes(args.seriesId, args.seasonId)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package dev.jdtech.jellyfin.viewmodels
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.jdtech.jellyfin.adapters.EpisodeItem
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.ItemFields
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
@ -14,12 +17,17 @@ class SeasonViewModel
|
|||
@Inject
|
||||
constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
|
||||
|
||||
private val _episodes = MutableLiveData<List<BaseItemDto>>()
|
||||
val episodes: LiveData<List<BaseItemDto>> = _episodes
|
||||
private val _episodes = MutableLiveData<List<EpisodeItem>>()
|
||||
val episodes: LiveData<List<EpisodeItem>> = _episodes
|
||||
|
||||
fun loadEpisodes(seriesId: UUID, seasonId: UUID) {
|
||||
viewModelScope.launch {
|
||||
_episodes.value = jellyfinRepository.getEpisodes(seriesId, seasonId, fields = listOf(ItemFields.OVERVIEW))
|
||||
_episodes.value = getEpisodes(seriesId, seasonId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEpisodes(seriesId: UUID, seasonId: UUID): List<EpisodeItem> {
|
||||
val episodes = jellyfinRepository.getEpisodes(seriesId, seasonId, fields = listOf(ItemFields.OVERVIEW))
|
||||
return listOf(EpisodeItem.Header) + episodes.map { EpisodeItem.Episode(it) }
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:foreground="?android:attr/selectableItemBackground">
|
||||
|
||||
|
|
|
@ -10,154 +10,23 @@
|
|||
name="viewModel"
|
||||
type="dev.jdtech.jellyfin.viewmodels.SeasonViewModel" />
|
||||
|
||||
<variable
|
||||
name="seriesId"
|
||||
type="java.util.UUID" />
|
||||
|
||||
<variable
|
||||
name="seasonId"
|
||||
type="java.util.UUID" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:itemBackdropById="@{seriesId}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/series_poster"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/header_gradient"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/season_poster"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:elevation="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,3:2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:seasonPoster="@{seasonId}"
|
||||
app:shapeAppearanceOverlay="@style/roundedImageView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/series_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@id/season_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/season_poster"
|
||||
tools:text="Attack on Titan" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/season_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/season_poster"
|
||||
tools:text="Season 1" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_setup_background"
|
||||
android:contentDescription="@string/play_button_description"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="12dp"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shuffle_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/shuffle_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_shuffle" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/check_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/check_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_check" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/favorite_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/favorite_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_heart" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/episodes_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingTop="16dp"
|
||||
app:episodes="@{viewModel.episodes}"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/buttons"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/episode_item" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
144
app/src/main/res/layout/season_header.xml
Normal file
144
app/src/main/res/layout/season_header.xml
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layout 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">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="seriesId"
|
||||
type="java.util.UUID" />
|
||||
|
||||
<variable
|
||||
name="seasonId"
|
||||
type="java.util.UUID" />
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:contentDescription="@string/series_poster"
|
||||
android:scaleType="centerCrop"
|
||||
app:itemBackdropById="@{seriesId}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/header_gradient"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/season_poster"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:elevation="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,3:2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:seasonPoster="@{seasonId}"
|
||||
app:shapeAppearanceOverlay="@style/roundedImageView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/series_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@id/season_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/season_poster"
|
||||
tools:text="Attack on Titan" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/season_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/season_poster"
|
||||
tools:text="Season 1" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_setup_background"
|
||||
android:contentDescription="@string/play_button_description"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="12dp"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shuffle_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/shuffle_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_shuffle" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/check_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/check_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_check" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/favorite_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_accent_background"
|
||||
android:contentDescription="@string/favorite_button_description"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_heart" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</layout>
|
Loading…
Reference in a new issue