Add Next Up items to HomeFragment

Can use some cleanup :)
This commit is contained in:
Jarne Demeulemeester 2021-06-30 15:18:39 +02:00
parent 5ff4ec7e42
commit 7605eb4158
No known key found for this signature in database
GPG key ID: B61B7B150DB6A6D2
9 changed files with 268 additions and 37 deletions

View file

@ -8,8 +8,6 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import dev.jdtech.jellyfin.adapters.*
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.models.View
import dev.jdtech.jellyfin.models.ViewItem
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemPerson
import java.util.*
@ -21,7 +19,7 @@ fun bindServers(recyclerView: RecyclerView, data: List<Server>?) {
}
@BindingAdapter("views")
fun bindViews(recyclerView: RecyclerView, data: List<View>?) {
fun bindViews(recyclerView: RecyclerView, data: List<HomeItem>?) {
val adapter = recyclerView.adapter as ViewListAdapter
adapter.submitList(data)
}
@ -65,7 +63,6 @@ fun bindItemBackdropImage(imageView: ImageView, item: BaseItemDto?) {
@BindingAdapter("itemBackdropById")
fun bindItemBackdropById(imageView: ImageView, itemId: UUID) {
if (itemId != null) {
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
Glide
@ -74,7 +71,6 @@ fun bindItemBackdropById(imageView: ImageView, itemId: UUID) {
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView)
}
}
@BindingAdapter("collections")
fun bindCollections(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
@ -122,6 +118,12 @@ fun bindEpisodes(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
adapter.submitList(data)
}
@BindingAdapter("homeEpisodes")
fun bindHomeEpisodes(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
val adapter = recyclerView.adapter as HomeEpisodeListAdapter
adapter.submitList(data)
}
@BindingAdapter("episodeImage")
fun bindEpisodeImage(imageView: ImageView, episode: BaseItemDto) {
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")

View file

@ -0,0 +1,44 @@
package dev.jdtech.jellyfin.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.HomeEpisodeItemBinding
import org.jellyfin.sdk.model.api.BaseItemDto
class HomeEpisodeListAdapter : ListAdapter<BaseItemDto, HomeEpisodeListAdapter.EpisodeViewHolder>(DiffCallback) {
class EpisodeViewHolder(private var binding: HomeEpisodeItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(episode: BaseItemDto) {
binding.episode = episode
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<BaseItemDto>() {
override fun areItemsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeViewHolder {
return EpisodeViewHolder(
HomeEpisodeItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
}

View file

@ -5,19 +5,34 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.NextUpSectionBinding
import dev.jdtech.jellyfin.databinding.ViewItemBinding
import dev.jdtech.jellyfin.models.NextUp
import dev.jdtech.jellyfin.models.View
import org.jellyfin.sdk.model.api.BaseItemDto
import java.lang.ClassCastException
import java.util.*
private const val ITEM_VIEW_TYPE_NEXT_UP = 0
private const val ITEM_VIEW_TYPE_VIEW = 1
class ViewListAdapter(
private val onClickListener: OnClickListener,
private val onItemClickListener: ViewItemListAdapter.OnClickListener
) : ListAdapter<View, ViewListAdapter.ViewViewHolder>(DiffCallback) {
class ViewViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(view: View, onClickListener: OnClickListener, onItemClickListener: ViewItemListAdapter.OnClickListener) {
) : ListAdapter<HomeItem, RecyclerView.ViewHolder>(DiffCallback) {
class ViewViewHolder(private var binding: ViewItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
dataItem: HomeItem.ViewItem,
onClickListener: OnClickListener,
onItemClickListener: ViewItemListAdapter.OnClickListener
) {
val view = dataItem.view
binding.view = view
// TODO: Change to string placeholder
binding.viewName.text = "Latest ${view.name}"
binding.itemsRecyclerView.adapter = ViewItemListAdapter(onItemClickListener, fixedWidth = true)
binding.itemsRecyclerView.adapter =
ViewItemListAdapter(onItemClickListener, fixedWidth = true)
binding.viewAll.setOnClickListener {
onClickListener.onClick(view)
}
@ -25,26 +40,78 @@ class ViewListAdapter(
}
}
companion object DiffCallback : DiffUtil.ItemCallback<View>() {
override fun areItemsTheSame(oldItem: View, newItem: View): Boolean {
class NextUpViewHolder(private var binding: NextUpSectionBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(section: HomeItem.NextUpSection) {
binding.section = section.nextUp
binding.itemsRecyclerView.adapter = HomeEpisodeListAdapter()
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<HomeItem>() {
override fun areItemsTheSame(oldItem: HomeItem, newItem: HomeItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: View, newItem: View): Boolean {
override fun areContentsTheSame(oldItem: HomeItem, newItem: HomeItem): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewViewHolder {
return ViewViewHolder(ViewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_NEXT_UP -> NextUpViewHolder(
NextUpSectionBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
ITEM_VIEW_TYPE_VIEW -> ViewViewHolder(
ViewItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: ViewViewHolder, position: Int) {
val view = getItem(position)
holder.bind(view, onClickListener, onItemClickListener)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
ITEM_VIEW_TYPE_NEXT_UP -> {
val view = getItem(position) as HomeItem.NextUpSection
(holder as NextUpViewHolder).bind(view)
}
ITEM_VIEW_TYPE_VIEW -> {
val view = getItem(position) as HomeItem.ViewItem
(holder as ViewViewHolder).bind(view, onClickListener, onItemClickListener)
}
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is HomeItem.NextUpSection -> ITEM_VIEW_TYPE_NEXT_UP
is HomeItem.ViewItem -> ITEM_VIEW_TYPE_VIEW
}
}
class OnClickListener(val clickListener: (view: View) -> Unit) {
fun onClick(view: View) = clickListener(view)
}
}
sealed class HomeItem {
data class NextUpSection(val nextUp: NextUp) : HomeItem() {
override val id = nextUp.id
}
data class ViewItem(val view: View) : HomeItem() {
override val id = view.id
}
abstract val id: UUID
}

View file

@ -0,0 +1,10 @@
package dev.jdtech.jellyfin.models
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.*
data class NextUp(
val id: UUID,
val name: String?,
var items: List<BaseItemDto>? = null
)

View file

@ -1,9 +0,0 @@
package dev.jdtech.jellyfin.models
import java.util.*
data class ViewItem(
val id: UUID,
val name: String?,
val primaryImageUrl: String
)

View file

@ -2,7 +2,10 @@ package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import androidx.lifecycle.*
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.HomeItem
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.models.NextUp
import dev.jdtech.jellyfin.models.View
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -16,8 +19,10 @@ class HomeViewModel(
) : AndroidViewModel(application) {
private val jellyfinApi = JellyfinApi.getInstance(application, "")
private val _views = MutableLiveData<List<View>>()
val views: LiveData<List<View>> = _views
private val nextUpString = application.resources.getString(R.string.next_up)
private val _views = MutableLiveData<List<HomeItem>>()
val views: LiveData<List<HomeItem>> = _views
private val _items = MutableLiveData<List<BaseItemDto>>()
val items: LiveData<List<BaseItemDto>> = _items
@ -47,7 +52,14 @@ class HomeViewModel(
views.add(v)
}
_views.value = views
val nextUpItems = getNextUp()
val nextUp = NextUp(UUID.randomUUID(), nextUpString, nextUpItems)
_views.value = when (nextUpItems) {
null -> views.map { HomeItem.ViewItem(it) }
else -> listOf(HomeItem.NextUpSection(nextUp)) + views.map { HomeItem.ViewItem(it) }
}
_finishedLoading.value = true
} catch (e: Exception) {
_finishedLoading.value = true
@ -72,6 +84,14 @@ class HomeViewModel(
return items
}
private suspend fun getNextUp(): List<BaseItemDto>? {
val items: List<BaseItemDto>?
withContext(Dispatchers.IO) {
items = jellyfinApi.showsApi.getNextUp(jellyfinApi.userId!!).content.items
}
return items
}
}
private fun BaseItemDto.toView(): View {

View file

@ -0,0 +1,54 @@
<?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="episode"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/episode_image"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="H,16:9"
android:scaleType="centerCrop"
app:episodeImage="@{episode}"
app:shapeAppearanceOverlay="@style/roundedImageView" />
<TextView
android:id="@+id/series_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@{episode.seriesName}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/episode_image"
tools:text="Wonder Egg Priority" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{String.format(@string/episode_name_extended, episode.parentIndexNumber, episode.indexNumber, episode.name)}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/series_name"
tools:text="The Girl Flautist" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,42 @@
<?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="section"
type="dev.jdtech.jellyfin.models.NextUp" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<TextView
android:id="@+id/section_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textSize="18sp"
android:text="@{section.name}"
tools:text="Next Up" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:layoutAnimation="@anim/overview_media_animation"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
app:homeEpisodes="@{section.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/home_episode_item" />
</LinearLayout>
</layout>

View file

@ -33,6 +33,7 @@
<string name="favorite_button_description">Favorite</string>
<string name="episode_watched_indicator">Episode watched indicator</string>
<string name="episode_name">%1$d. %2$s</string>
<string name="episode_name_extended">S%1$dE%2$d - %3$s</string>
<string name="next_up">Next up</string>
<string name="episode_name_extended">S%1$d:E%2$d - %3$s</string>
<string name="next_up">Next Up</string>
<string name="latest_library">Latest %1$s</string>
</resources>