diff --git a/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt b/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt index 9ffefa4a..41dbeede 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/BindingAdapters.kt @@ -85,4 +85,24 @@ fun bindPersonImage(imageView: ImageView, person: BaseItemPerson) { .into(imageView) imageView.contentDescription = "${person.name} poster" +} + +@BindingAdapter("episodes") +fun bindEpisodes(recyclerView: RecyclerView, data: List?) { + val adapter = recyclerView.adapter as EpisodeListAdapter + adapter.submitList(data) +} + +@BindingAdapter("episodeImage") +fun bindEpisodeImage(imageView: ImageView, episode: BaseItemDto) { + val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "") + + Glide + .with(imageView.context) + .load(jellyfinApi.api.baseUrl.plus("/items/${episode.id}/Images/Primary")) + .transition(DrawableTransitionOptions.withCrossFade()) + .placeholder(R.color.neutral_800) + .into(imageView) + + imageView.contentDescription = "${episode.name} poster" } \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/adapters/EpisodeListAdapter.kt b/app/src/main/java/dev/jdtech/jellyfin/adapters/EpisodeListAdapter.kt new file mode 100644 index 00000000..35a56afe --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/adapters/EpisodeListAdapter.kt @@ -0,0 +1,46 @@ +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.EpisodeItemBinding +import org.jellyfin.sdk.model.api.BaseItemDto + +class EpisodeListAdapter : + ListAdapter(DiffCallback) { + + class EpisodeViewHolder(private var binding: EpisodeItemBinding) : + 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( + EpisodeItemBinding.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/fragments/MediaInfoFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt index f95f65d3..972c8209 100644 --- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt @@ -1,16 +1,17 @@ package dev.jdtech.jellyfin.fragments -import androidx.lifecycle.ViewModelProvider import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import dev.jdtech.jellyfin.adapters.PersonListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter -import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding +import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModelFactory class MediaInfoFragment : Fragment() { @@ -34,7 +35,8 @@ class MediaInfoFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val viewModelFactory = MediaInfoViewModelFactory(requireNotNull(this.activity).application, args.itemId) + val viewModelFactory = + MediaInfoViewModelFactory(requireNotNull(this.activity).application, args.itemId) viewModel = ViewModelProvider(this, viewModelFactory).get(MediaInfoViewModel::class.java) binding.viewModel = viewModel @@ -46,7 +48,16 @@ class MediaInfoFragment : Fragment() { } }) - binding.seasonsRecyclerView.adapter = ViewItemListAdapter(ViewItemListAdapter.OnClickListener {}, fixedWidth = true) + binding.seasonsRecyclerView.adapter = + ViewItemListAdapter(ViewItemListAdapter.OnClickListener { + findNavController().navigate( + MediaInfoFragmentDirections.actionMediaInfoFragmentToSeasonFragment( + it.seriesId!!, + it.id, + it.name + ) + ) + }, fixedWidth = true) binding.peopleRecyclerView.adapter = PersonListAdapter() } diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt new file mode 100644 index 00000000..f05cae83 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt @@ -0,0 +1,43 @@ +package dev.jdtech.jellyfin.fragments + +import androidx.lifecycle.ViewModelProvider +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.navArgs +import dev.jdtech.jellyfin.adapters.EpisodeListAdapter +import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding +import dev.jdtech.jellyfin.viewmodels.SeasonViewModel +import dev.jdtech.jellyfin.viewmodels.SeasonViewModelFactory + +class SeasonFragment : Fragment() { + + private lateinit var viewModel: SeasonViewModel + private lateinit var binding: FragmentSeasonBinding + + private val args: SeasonFragmentArgs by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentSeasonBinding.inflate(inflater, container, false) + binding.lifecycleOwner = this + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val viewModelFactory = SeasonViewModelFactory( + requireNotNull(this.activity).application, + args.seriesId, + args.seasonId + ) + viewModel = ViewModelProvider(this, viewModelFactory).get(SeasonViewModel::class.java) + binding.viewModel = viewModel + binding.episodesRecyclerView.adapter = EpisodeListAdapter() + } + +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt new file mode 100644 index 00000000..1ba8ed7b --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt @@ -0,0 +1,40 @@ +package dev.jdtech.jellyfin.viewmodels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import dev.jdtech.jellyfin.api.JellyfinApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jellyfin.sdk.model.api.BaseItemDto +import org.jellyfin.sdk.model.api.ItemFields +import java.util.* + +class SeasonViewModel(application: Application, seriesId: UUID, seasonId: UUID) : + AndroidViewModel(application) { + private val jellyfinApi = JellyfinApi.getInstance(application, "") + + private val _episodes = MutableLiveData>() + val episodes: LiveData> = _episodes + + init { + viewModelScope.launch { + _episodes.value = getEpisodes(seriesId, seasonId) + } + } + + private suspend fun getEpisodes(seriesId: UUID, seasonId: UUID): List? { + val episodes: List? + withContext(Dispatchers.IO) { + episodes = jellyfinApi.showsApi.getEpisodes( + seriesId, jellyfinApi.userId!!, seasonId = seasonId, fields = listOf( + ItemFields.OVERVIEW + ) + ).content.items + } + return episodes + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModelFactory.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModelFactory.kt new file mode 100644 index 00000000..184174b0 --- /dev/null +++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModelFactory.kt @@ -0,0 +1,21 @@ +package dev.jdtech.jellyfin.viewmodels + +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import java.lang.IllegalArgumentException +import java.util.* + +class SeasonViewModelFactory( + private val application: Application, + private val seriesId: UUID, + private val seasonId: UUID +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(SeasonViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return SeasonViewModel(application, seriesId, seasonId) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/episode_item.xml b/app/src/main/res/layout/episode_item.xml index 508cbd06..2934d4ce 100644 --- a/app/src/main/res/layout/episode_item.xml +++ b/app/src/main/res/layout/episode_item.xml @@ -4,7 +4,8 @@ xmlns:tools="http://schemas.android.com/tools"> - + + + android:layout_height="100dp" + android:layout_marginBottom="24dp"> + app:layout_constraintTop_toTopOf="@id/episode_image" /> + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 35331039..6974bbf0 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -9,7 +9,7 @@ android:id="@+id/navigation_home" android:name="dev.jdtech.jellyfin.fragments.HomeFragment" android:label="@string/title_home" - tools:layout="@layout/fragment_home" > + tools:layout="@layout/fragment_home"> + app:nullable="true" /> + + + + + + \ No newline at end of file