Create media info fragment and set up navigation

This commit is contained in:
Jarne Demeulemeester 2021-06-22 16:15:30 +02:00
parent 927c134bd6
commit ac10b4ba67
No known key found for this signature in database
GPG key ID: 60884A0C1EBA43E5
10 changed files with 282 additions and 8 deletions

View file

@ -10,7 +10,10 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.databinding.BaseItemBinding import dev.jdtech.jellyfin.databinding.BaseItemBinding
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
class ViewItemListAdapter(private val fixedWidth: Boolean = false) : class ViewItemListAdapter(
private val onClickListener: OnClickListener,
private val fixedWidth: Boolean = false,
) :
ListAdapter<BaseItemDto, ViewItemListAdapter.ItemViewHolder>(DiffCallback) { ListAdapter<BaseItemDto, ViewItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) : class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) :
@ -51,6 +54,13 @@ class ViewItemListAdapter(private val fixedWidth: Boolean = false) :
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(item)
}
holder.bind(item, fixedWidth) holder.bind(item, fixedWidth)
} }
class OnClickListener(val clickListener: (item: BaseItemDto) -> Unit) {
fun onClick(item: BaseItemDto) = clickListener(item)
}
} }

View file

@ -9,14 +9,15 @@ import dev.jdtech.jellyfin.databinding.ViewItemBinding
import dev.jdtech.jellyfin.models.View import dev.jdtech.jellyfin.models.View
class ViewListAdapter( class ViewListAdapter(
private val onClickListener: OnClickListener private val onClickListener: OnClickListener,
private val onItemClickListener: ViewItemListAdapter.OnClickListener
) : ListAdapter<View, ViewListAdapter.ViewViewHolder>(DiffCallback) { ) : ListAdapter<View, ViewListAdapter.ViewViewHolder>(DiffCallback) {
class ViewViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) { class ViewViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(view: View, onClickListener: OnClickListener) { fun bind(view: View, onClickListener: OnClickListener, onItemClickListener: ViewItemListAdapter.OnClickListener) {
binding.view = view binding.view = view
// TODO: Change to string placeholder // TODO: Change to string placeholder
binding.viewName.text = "Latest ${view.name}" binding.viewName.text = "Latest ${view.name}"
binding.itemsRecyclerView.adapter = ViewItemListAdapter(fixedWidth = true) binding.itemsRecyclerView.adapter = ViewItemListAdapter(onItemClickListener, fixedWidth = true)
binding.viewAll.setOnClickListener { binding.viewAll.setOnClickListener {
onClickListener.onClick(view) onClickListener.onClick(view)
} }
@ -40,7 +41,7 @@ class ViewListAdapter(
override fun onBindViewHolder(holder: ViewViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewViewHolder, position: Int) {
val view = getItem(position) val view = getItem(position)
holder.bind(view, onClickListener) holder.bind(view, onClickListener, onItemClickListener)
} }
class OnClickListener(val clickListener: (view: View) -> Unit) { class OnClickListener(val clickListener: (view: View) -> Unit) {

View file

@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.adapters.ViewListAdapter import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.databinding.FragmentHomeBinding import dev.jdtech.jellyfin.databinding.FragmentHomeBinding
import dev.jdtech.jellyfin.viewmodels.HomeViewModel import dev.jdtech.jellyfin.viewmodels.HomeViewModel
@ -30,6 +31,13 @@ class HomeFragment : Fragment() {
it.name it.name
) )
) )
}, ViewItemListAdapter.OnClickListener {
findNavController().navigate(
HomeFragmentDirections.actionNavigationHomeToMediaInfoFragment(
it.id,
it.name
)
)
}) })
binding.errorLayout.findViewById<View>(R.id.retry_button).setOnClickListener { binding.errorLayout.findViewById<View>(R.id.retry_button).setOnClickListener {

View file

@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment
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 androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
@ -32,9 +33,18 @@ 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)
val viewModelFactory = LibraryViewModelFactory(requireNotNull(this.activity).application, args.libraryId) val viewModelFactory =
LibraryViewModelFactory(requireNotNull(this.activity).application, args.libraryId)
viewModel = ViewModelProvider(this, viewModelFactory).get(LibraryViewModel::class.java) viewModel = ViewModelProvider(this, viewModelFactory).get(LibraryViewModel::class.java)
binding.viewModel = viewModel binding.viewModel = viewModel
binding.itemsRecyclerView.adapter = ViewItemListAdapter() binding.itemsRecyclerView.adapter =
ViewItemListAdapter(ViewItemListAdapter.OnClickListener {
findNavController().navigate(
LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
it.id,
it.name
)
)
})
} }
} }

View file

@ -0,0 +1,29 @@
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 dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
import dev.jdtech.jellyfin.R
class MediaInfoFragment : Fragment() {
private lateinit var viewModel: MediaInfoViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_media_info, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(MediaInfoViewModel::class.java)
// TODO: Use the ViewModel
}
}

View file

@ -0,0 +1,7 @@
package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.ViewModel
class MediaInfoViewModel : ViewModel() {
// TODO: Implement the ViewModel
}

View file

@ -16,6 +16,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp" android:layout_marginHorizontal="12dp"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
@ -24,10 +25,10 @@
android:layout_height="0dp" android:layout_height="0dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:itemImage="@{item}" app:itemImage="@{item}"
app:layout_constraintDimensionRatio="H,2:3"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="H,2:3"
app:shapeAppearanceOverlay="@style/roundedImageView" /> app:shapeAppearanceOverlay="@style/roundedImageView" />
<TextView <TextView

View file

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="item"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
tools:context=".fragments.MediaInfoFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="@{item.originalTitle}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView
android:id="@+id/year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{item.productionYear}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="2019" />
<TextView
android:id="@+id/playtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{item.runTimeTicks.toString()}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="122 min" />
<TextView
android:id="@+id/official_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{item.officialRating}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="PG-13" />
<TextView
android:id="@+id/community_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{item.communityRating.toString()}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="7.3" />
</LinearLayout>
<LinearLayout
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/genres_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:id="@+id/genres_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/genres"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/genres"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Action, Science Fiction, Adventure" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/director_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:id="@+id/director_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/director"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/director"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Robert Rodriguez" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/writers_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/writers_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/writers"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/writers"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="James Cameron, Laeta Kalogridis, Yukito Kishiro" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@{item.overview}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="An angel falls. A warrior rises. When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past." />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/cast_amp_crew"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</layout>

View file

@ -17,6 +17,9 @@
app:exitAnim="@anim/nav_default_exit_anim" app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" /> app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_navigation_home_to_mediaInfoFragment"
app:destination="@id/mediaInfoFragment" />
</fragment> </fragment>
<fragment <fragment
@ -51,5 +54,22 @@
android:defaultValue="Library" android:defaultValue="Library"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<action
android:id="@+id/action_libraryFragment_to_mediaInfoFragment"
app:destination="@id/mediaInfoFragment" />
</fragment>
<fragment
android:id="@+id/mediaInfoFragment"
android:name="dev.jdtech.jellyfin.fragments.MediaInfoFragment"
android:label="{itemName}"
tools:layout="@layout/fragment_media_info">
<argument
android:name="itemId"
app:argType="java.util.UUID" />
<argument
android:name="itemName"
app:argType="string"
app:nullable="true"
android:defaultValue="Media Info" />
</fragment> </fragment>
</navigation> </navigation>

View file

@ -21,4 +21,8 @@
<string name="view_all">View all</string> <string name="view_all">View all</string>
<string name="error_loading_data">Error loading data</string> <string name="error_loading_data">Error loading data</string>
<string name="retry">Retry</string> <string name="retry">Retry</string>
<string name="genres">Genres</string>
<string name="director">Director</string>
<string name="writers">Writers</string>
<string name="cast_amp_crew"><![CDATA[Cast & Crew]]></string>
</resources> </resources>