Load views and latest items on home screen
Needs a lot of optimization and cleanup
This commit is contained in:
parent
93518f67ee
commit
f276a26d7d
14 changed files with 228 additions and 53 deletions
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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!")
|
||||
|
|
10
app/src/main/java/dev/jdtech/jellyfin/models/View.kt
Normal file
10
app/src/main/java/dev/jdtech/jellyfin/models/View.kt
Normal 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
|
||||
)
|
9
app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt
Normal file
9
app/src/main/java/dev/jdtech/jellyfin/models/ViewItem.kt
Normal 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
|
||||
)
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -2,34 +2,34 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="?attr/actionBarSize">
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment_activity_main"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toTopOf="@id/nav_view"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/main_navigation" />
|
||||
android:paddingTop="?attr/actionBarSize">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment_activity_main"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toTopOf="@id/nav_view"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/main_navigation" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
|
|
36
app/src/main/res/layout/base_item.xml
Normal file
36
app/src/main/res/layout/base_item.xml
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in a new issue