Load views and latest items on home screen

Needs a lot of optimization and cleanup
This commit is contained in:
Jarne Demeulemeester 2021-06-13 21:26:28 +02:00
parent 93518f67ee
commit f276a26d7d
No known key found for this signature in database
GPG key ID: B61B7B150DB6A6D2
14 changed files with 228 additions and 53 deletions

View file

@ -66,6 +66,10 @@ dependencies {
// Jellyfin
implementation "org.jellyfin.sdk:jellyfin-platform-android:$jellyfin_version"
// Glide
implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
// Testing
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'

View file

@ -1,10 +1,17 @@
package dev.jdtech.jellyfin
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.models.View
import dev.jdtech.jellyfin.models.ViewItem
import org.jellyfin.sdk.model.api.BaseItemDto
@BindingAdapter("servers")
@ -14,7 +21,22 @@ fun bindServers(recyclerView: RecyclerView, data: List<Server>?) {
}
@BindingAdapter("views")
fun bindViews(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
fun bindViews(recyclerView: RecyclerView, data: List<View>?) {
val adapter = recyclerView.adapter as ViewListAdapter
adapter.submitList(data)
}
@BindingAdapter("items")
fun bindItems(recyclerView: RecyclerView, data: List<ViewItem>?) {
val adapter = recyclerView.adapter as ViewItemListAdapter
adapter.submitList(data)
}
@BindingAdapter("itemImage")
fun bindItemImage(imageView: ImageView, item: ViewItem) {
Glide
.with(imageView.context)
.load(item.primaryImageUrl)
.placeholder(ColorDrawable(Color.GRAY))
.into(imageView)
}

View file

@ -0,0 +1,38 @@
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.BaseItemBinding
import dev.jdtech.jellyfin.models.ViewItem
import org.jellyfin.sdk.model.api.BaseItemDto
class ViewItemListAdapter : ListAdapter<ViewItem, ViewItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(private var binding: BaseItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(view: ViewItem) {
binding.item = view
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<ViewItem>() {
override fun areItemsTheSame(oldItem: ViewItem, newItem: ViewItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ViewItem, newItem: ViewItem): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(BaseItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
}

View file

@ -6,31 +6,32 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.ViewItemBinding
import org.jellyfin.sdk.model.api.BaseItemDto
import dev.jdtech.jellyfin.models.View
class ViewListAdapter : ListAdapter<BaseItemDto, ViewListAdapter.ViewHolder>(DiffCallback) {
class ViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(view: BaseItemDto) {
class ViewListAdapter : ListAdapter<View, ViewListAdapter.ViewViewHolder>(DiffCallback) {
class ViewViewHolder(private var binding: ViewItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(view: View) {
binding.view = view
binding.itemsRecyclerView.adapter = ViewItemListAdapter()
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<BaseItemDto>() {
override fun areItemsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
companion object DiffCallback : DiffUtil.ItemCallback<View>() {
override fun areItemsTheSame(oldItem: View, newItem: View): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
override fun areContentsTheSame(oldItem: View, newItem: View): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ViewItemBinding.inflate(LayoutInflater.from(parent.context)))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewViewHolder {
return ViewViewHolder(ViewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
override fun onBindViewHolder(holder: ViewViewHolder, position: Int) {
val view = getItem(position)
holder.bind(view)
}

View file

@ -5,9 +5,7 @@ import android.util.Log
import dev.jdtech.jellyfin.BuildConfig
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.android
import org.jellyfin.sdk.api.operations.SystemApi
import org.jellyfin.sdk.api.operations.UserApi
import org.jellyfin.sdk.api.operations.UserViewsApi
import org.jellyfin.sdk.api.operations.*
import org.jellyfin.sdk.model.ClientInfo
import java.util.*
@ -31,6 +29,8 @@ class JellyfinApi(context: Context, baseUrl: String) {
val systemApi = SystemApi(api)
val userApi = UserApi(api)
val viewsApi = UserViewsApi(api)
val itemsApi = ItemsApi(api)
val userLibraryApi = UserLibraryApi(api)
init {
Log.i("JellyfinApi", "Constructor called!")

View file

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

View file

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

View file

@ -1,11 +1,11 @@
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 android.util.Log
import androidx.lifecycle.*
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.models.View
import dev.jdtech.jellyfin.models.ViewItem
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
@ -14,14 +14,50 @@ class HomeViewModel(
) : ViewModel() {
private val jellyfinApi = JellyfinApi.getInstance(application, "")
private val _views = MutableLiveData<List<BaseItemDto>>()
val views: LiveData<List<BaseItemDto>> = _views
private val _views = MutableLiveData<List<View>>()
val views: LiveData<List<View>> = _views
private val _items = MutableLiveData<List<BaseItemDto>>()
val items: LiveData<List<BaseItemDto>> = _items
init {
viewModelScope.launch {
val views: MutableList<View> = mutableListOf()
val result by jellyfinApi.viewsApi.getUserViews(jellyfinApi.userId!!)
_views.value = result.items
for (view in result.items!!) {
val items: MutableList<ViewItem> = mutableListOf()
val resultItems by jellyfinApi.userLibraryApi.getLatestMedia(jellyfinApi.userId!!, parentId = view.id)
if (resultItems.isEmpty()) continue
val v = view.toView()
for (item in resultItems) {
val i = jellyfinApi.api.baseUrl?.let { item.toViewItem(it) }
if (i != null) {
items.add(i)
}
}
v.items = items
views.add(v)
}
_views.value = views
}
}
}
private fun BaseItemDto.toViewItem(baseUrl: String) : ViewItem {
return ViewItem(
id = id,
name = name,
primaryImageUrl = baseUrl.plus("/items/${id}/Images/Primary")
)
}
private fun BaseItemDto.toView() : View {
return View(
id = id,
name = name
)
}

View file

@ -37,7 +37,7 @@ class ServerSelectViewModel(
}
fun connectToServer(server: Server) {
JellyfinApi.getInstance(application, server.address).apply {
JellyfinApi.newInstance(application, server.address).apply {
api.accessToken = server.accessToken
userId = UUID.fromString(server.userId)
}

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
@ -23,7 +23,7 @@
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
@ -31,5 +31,5 @@
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,36 @@
<?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="item"
type="dev.jdtech.jellyfin.models.ViewItem" />
</data>
<LinearLayout
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="220dp"
android:layout_marginBottom="8dp"
app:itemImage="@{item}"
app:shapeAppearanceOverlay="@style/roundedImageView" />
<TextView
android:id="@+id/item_name"
style="@style/text_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="@{item.name}"
tools:text="Movie title" />
</LinearLayout>
</layout>

View file

@ -17,7 +17,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/views_recycler_view"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -7,18 +7,20 @@
<variable
name="view"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
type="dev.jdtech.jellyfin.models.View" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginBottom="42dp">
<TextView
android:id="@+id/view_name"
style="@style/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="@{view.name}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@ -29,10 +31,22 @@
style="@style/text_regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:text="View all"
app:layout_constraintBaseline_toBaselineOf="@id/view_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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:orientation="horizontal"
app:items="@{view.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/view_name"
tools:listitem="@layout/base_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -26,4 +26,9 @@
<item name="android:layout_height">48dp</item>
<item name="android:background">@drawable/button_setup_background</item>
</style>
<style name="roundedImageView">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">10dp</item>
</style>
</resources>