Show collections on media fragment

This commit is contained in:
Jarne Demeulemeester 2021-06-14 16:41:54 +02:00
parent b390e80987
commit 3f5a6c5bfa
No known key found for this signature in database
GPG key ID: B61B7B150DB6A6D2
9 changed files with 229 additions and 29 deletions

View file

@ -1,18 +1,19 @@
package dev.jdtech.jellyfin package dev.jdtech.jellyfin
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView import android.widget.ImageView
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.adapters.ServerGridAdapter import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.adapters.ViewListAdapter import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.models.View import dev.jdtech.jellyfin.models.View
import dev.jdtech.jellyfin.models.ViewItem import dev.jdtech.jellyfin.models.ViewItem
import org.jellyfin.sdk.model.api.BaseItemDto
@BindingAdapter("servers") @BindingAdapter("servers")
fun bindServers(recyclerView: RecyclerView, data: List<Server>?) { fun bindServers(recyclerView: RecyclerView, data: List<Server>?) {
@ -40,4 +41,26 @@ fun bindItemImage(imageView: ImageView, item: ViewItem) {
.transition(DrawableTransitionOptions.withCrossFade()) .transition(DrawableTransitionOptions.withCrossFade())
.placeholder(R.color.neutral_800) .placeholder(R.color.neutral_800)
.into(imageView) .into(imageView)
imageView.contentDescription = "${item.name} poster"
}
@BindingAdapter("collections")
fun bindCollections(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
val adapter = recyclerView.adapter as CollectionListAdapter
adapter.submitList(data)
}
@BindingAdapter("collectionImage")
fun bindCollectionImage(imageView: ImageView, item: BaseItemDto) {
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
Glide
.with(imageView.context)
.load(jellyfinApi.api.baseUrl.plus("/items/${item.id}/Images/Primary"))
.transition(DrawableTransitionOptions.withCrossFade())
.placeholder(R.color.neutral_800)
.into(imageView)
imageView.contentDescription = "${item.name} image"
} }

View file

@ -20,7 +20,8 @@ class MainActivity : AppCompatActivity() {
val navView: BottomNavigationView = binding.navView val navView: BottomNavigationView = binding.navView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main) as NavHostFragment val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main) as NavHostFragment
val navController = navHostFragment.navController val navController = navHostFragment.navController
navView.setupWithNavController(navController) navView.setupWithNavController(navController)

View file

@ -0,0 +1,45 @@
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.CollectionItemBinding
import org.jellyfin.sdk.model.api.BaseItemDto
class CollectionListAdapter :
ListAdapter<BaseItemDto, CollectionListAdapter.ViewViewHolder>(DiffCallback) {
class ViewViewHolder(private var binding: CollectionItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(collection: BaseItemDto) {
binding.collection = collection
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): ViewViewHolder {
return ViewViewHolder(
CollectionItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewViewHolder, position: Int) {
val collection = getItem(position)
holder.bind(collection)
}
}

View file

@ -1,13 +1,11 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
import android.util.Log
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.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import dev.jdtech.jellyfin.R
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
@ -18,9 +16,9 @@ class HomeFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val application = requireNotNull(this.activity).application val application = requireNotNull(this.activity).application
val binding = FragmentHomeBinding.inflate(inflater) val binding = FragmentHomeBinding.inflate(inflater, container, false)
val viewModelFactory = HomeViewModelFactory(application) val viewModelFactory = HomeViewModelFactory(application)
val viewModel = ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java) val viewModel = ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java)

View file

@ -5,14 +5,33 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import dev.jdtech.jellyfin.R import androidx.lifecycle.ViewModelProvider
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaBinding
import dev.jdtech.jellyfin.viewmodels.MediaViewModel
import dev.jdtech.jellyfin.viewmodels.MediaViewModelFactory
class MediaFragment : Fragment() { class MediaFragment : Fragment() {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
return inflater.inflate(R.layout.fragment_media, container, false) val application = requireNotNull(this.activity).application
val binding = FragmentMediaBinding.inflate(inflater, container, false)
val viewModelFactory = MediaViewModelFactory(application)
val viewModel = ViewModelProvider(this, viewModelFactory).get(MediaViewModel::class.java)
binding.lifecycleOwner = this
binding.viewModel = viewModel
binding.viewsRecyclerView.adapter = CollectionListAdapter()
viewModel.finishedLoading.observe(viewLifecycleOwner, {
if (it) {
binding.loadingIncicator.visibility = View.GONE
}
})
return binding.root
} }
} }

View file

@ -0,0 +1,41 @@
package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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 java.util.*
class MediaViewModel(
val application: Application
) : ViewModel() {
private val jellyfinApi = JellyfinApi.getInstance(application, "")
private val _collections = MutableLiveData<List<BaseItemDto>>()
val collections : LiveData<List<BaseItemDto>> = _collections
private val _finishedLoading = MutableLiveData<Boolean>()
val finishedLoading: LiveData<Boolean> = _finishedLoading
init {
viewModelScope.launch {
val items = getItems(jellyfinApi.userId!!)
_collections.value = items
_finishedLoading.value = true
}
}
private suspend fun getItems(userId: UUID) : List<BaseItemDto>? {
var items: List<BaseItemDto>?
withContext(Dispatchers.IO) {
items = jellyfinApi.itemsApi.getItems(userId).content.items
}
return items
}
}

View file

@ -0,0 +1,18 @@
package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import java.lang.IllegalArgumentException
class MediaViewModelFactory(
private val application: Application
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MediaViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MediaViewModel(application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View file

@ -0,0 +1,34 @@
<?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="collection"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
app:collectionImage="@{collection}"
app:shapeAppearanceOverlay="@style/roundedImageView" />
<TextView
style="@style/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{collection.name}"
tools:text="Movies" />
</LinearLayout>
</layout>

View file

@ -1,23 +1,44 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.MediaFragment">
<TextView <data>
android:id="@+id/text_dashboard"
<variable
name="viewModel"
type="dev.jdtech.jellyfin.viewmodels.MediaViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="8dp" tools:context=".fragments.MediaFragment">
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp" <com.google.android.material.progressindicator.CircularProgressIndicator
android:textAlignment="center" android:id="@+id/loading_incicator"
android:textSize="20sp" android:layout_width="wrap_content"
android:text="@string/title_media" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:indeterminate="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintStart_toStartOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout> app:layout_constraintTop_toTopOf="parent"
app:trackCornerRadius="10dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/views_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingHorizontal="24dp"
app:collections="@{viewModel.collections}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:itemCount="4"
tools:listitem="@layout/collection_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>