From 7605eb4158c9d0b7664d6957e058eac5bfe3b7bf Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Wed, 30 Jun 2021 15:18:39 +0200 Subject: [PATCH] Add Next Up items to HomeFragment Can use some cleanup :) --- .../dev/jdtech/jellyfin/BindingAdapters.kt | 24 ++--- .../adapters/HomeEpisodeListAdapter.kt | 44 +++++++++ .../jellyfin/adapters/ViewListAdapter.kt | 91 ++++++++++++++++--- .../java/dev/jdtech/jellyfin/models/NextUp.kt | 10 ++ .../dev/jdtech/jellyfin/models/ViewItem.kt | 9 -- .../jellyfin/viewmodels/HomeViewModel.kt | 26 +++++- app/src/main/res/layout/home_episode_item.xml | 54 +++++++++++ app/src/main/res/layout/next_up_section.xml | 42 +++++++++ app/src/main/res/values/strings.xml | 5 +- 9 files changed, 268 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/dev/jdtech/jellyfin/adapters/HomeEpisodeListAdapter.kt create mode 100644 app/src/main/java/dev/jdtech/jellyfin/models/NextUp.kt delete mode 100644 app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt create mode 100644 app/src/main/res/layout/home_episode_item.xml create mode 100644 app/src/main/res/layout/next_up_section.xml diff --git a/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt b/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt index 027d113f..ab78bade 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt @@ -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?) { } @BindingAdapter("views") -fun bindViews(recyclerView: RecyclerView, data: List?) { +fun bindViews(recyclerView: RecyclerView, data: List?) { val adapter = recyclerView.adapter as ViewListAdapter adapter.submitList(data) } @@ -65,15 +63,13 @@ fun bindItemBackdropImage(imageView: ImageView, item: BaseItemDto?) { @BindingAdapter("itemBackdropById") fun bindItemBackdropById(imageView: ImageView, itemId: UUID) { - if (itemId != null) { - val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "") + val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "") - Glide - .with(imageView.context) - .load(jellyfinApi.api.baseUrl.plus("/items/${itemId}/Images/Backdrop")) - .transition(DrawableTransitionOptions.withCrossFade()) - .into(imageView) - } + Glide + .with(imageView.context) + .load(jellyfinApi.api.baseUrl.plus("/items/${itemId}/Images/Backdrop")) + .transition(DrawableTransitionOptions.withCrossFade()) + .into(imageView) } @BindingAdapter("collections") @@ -122,6 +118,12 @@ fun bindEpisodes(recyclerView: RecyclerView, data: List?) { adapter.submitList(data) } +@BindingAdapter("homeEpisodes") +fun bindHomeEpisodes(recyclerView: RecyclerView, data: List?) { + val adapter = recyclerView.adapter as HomeEpisodeListAdapter + adapter.submitList(data) +} + @BindingAdapter("episodeImage") fun bindEpisodeImage(imageView: ImageView, episode: BaseItemDto) { val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "") diff --git a/app/src/main/java/dev/jdtech/jellyfin/adapters/HomeEpisodeListAdapter.kt b/app/src/main/java/dev/jdtech/jellyfin/adapters/HomeEpisodeListAdapter.kt new file mode 100644 index 00000000..8ca86e0c --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/adapters/HomeEpisodeListAdapter.kt @@ -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(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() { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt b/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt index 159c0d42..aa2ecb46 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt @@ -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(DiffCallback) { - class ViewViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(view: View, onClickListener: OnClickListener, onItemClickListener: ViewItemListAdapter.OnClickListener) { +) : ListAdapter(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() { - 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() { + 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 } \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/models/NextUp.kt b/app/src/main/java/dev/jdtech/jellyfin/models/NextUp.kt new file mode 100644 index 00000000..e836bd20 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/models/NextUp.kt @@ -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? = null +) \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt b/app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt deleted file mode 100644 index f48e3b2d..00000000 --- a/app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.jdtech.jellyfin.models - -import java.util.* - -data class ViewItem( - val id: UUID, - val name: String?, - val primaryImageUrl: String -) \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt index 11bfdf7a..eaa037a2 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt @@ -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>() - val views: LiveData> = _views + private val nextUpString = application.resources.getString(R.string.next_up) + + private val _views = MutableLiveData>() + val views: LiveData> = _views private val _items = MutableLiveData>() val items: LiveData> = _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? { + val items: List? + withContext(Dispatchers.IO) { + items = jellyfinApi.showsApi.getNextUp(jellyfinApi.userId!!).content.items + } + return items + } + } private fun BaseItemDto.toView(): View { diff --git a/app/src/main/res/layout/home_episode_item.xml b/app/src/main/res/layout/home_episode_item.xml new file mode 100644 index 00000000..cc046781 --- /dev/null +++ b/app/src/main/res/layout/home_episode_item.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/next_up_section.xml b/app/src/main/res/layout/next_up_section.xml new file mode 100644 index 00000000..e4adb614 --- /dev/null +++ b/app/src/main/res/layout/next_up_section.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7e055d2..99a823c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ Favorite Episode watched indicator %1$d. %2$s - S%1$dE%2$d - %3$s - Next up + S%1$d:E%2$d - %3$s + Next Up + Latest %1$s \ No newline at end of file