Introduce klint (#186)

* Add ktlint plugin

* Make code ktlint compliant

* Make code ktlint compliant
This commit is contained in:
Jarne Demeulemeester 2022-10-29 21:17:48 +02:00 committed by GitHub
parent 45ccea57af
commit ad5e722d44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 682 additions and 524 deletions

View file

@ -6,6 +6,7 @@ plugins {
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("dagger.hilt.android.plugin") id("dagger.hilt.android.plugin")
id("com.mikepenz.aboutlibraries.plugin") id("com.mikepenz.aboutlibraries.plugin")
id("org.jlleitschuh.gradle.ktlint") version "11.0.0"
} }
android { android {
@ -59,6 +60,12 @@ android {
} }
} }
ktlint {
android.set(true)
ignoreFailures.set(false)
disabledRules.add("max-line-length")
}
dependencies { dependencies {
implementation("androidx.leanback:leanback:1.2.0-alpha02") implementation("androidx.leanback:leanback:1.2.0-alpha02")
@ -67,7 +74,6 @@ dependencies {
implementation("androidx.activity:activity-ktx:1.6.1") implementation("androidx.activity:activity-ktx:1.6.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
// Material // Material
implementation("com.google.android.material:material:1.7.0") implementation("com.google.android.material:material:1.7.0")

View file

@ -27,9 +27,11 @@ abstract class BasePlayerActivity: AppCompatActivity() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
protected fun hideSystemUI() { protected fun hideSystemUI() {
// These methods are deprecated but we still use them because the new WindowInsetsControllerCompat has a bug which makes the action bar reappear // These methods are deprecated but we still use them because the new WindowInsetsControllerCompat has a bug which makes the action bar reappear
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

View file

@ -13,11 +13,11 @@ import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.models.User import dev.jdtech.jellyfin.models.User
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.BaseItemPerson import org.jellyfin.sdk.model.api.BaseItemPerson
import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.ImageType
import java.util.UUID
@BindingAdapter("servers") @BindingAdapter("servers")
fun bindServers(recyclerView: RecyclerView, data: List<Server>?) { fun bindServers(recyclerView: RecyclerView, data: List<Server>?) {
@ -96,13 +96,13 @@ fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
} }
imageView imageView
.loadImage("/items/${imageItemId}/Images/$imageType") .loadImage("/items/$imageItemId/Images/$imageType")
.posterDescription(episode.name) .posterDescription(episode.name)
} }
@BindingAdapter("seasonPoster") @BindingAdapter("seasonPoster")
fun bindSeasonPoster(imageView: ImageView, seasonId: UUID) { fun bindSeasonPoster(imageView: ImageView, seasonId: UUID) {
imageView.loadImage("/items/${seasonId}/Images/${ImageType.PRIMARY}") imageView.loadImage("/items/$seasonId/Images/${ImageType.PRIMARY}")
} }
@BindingAdapter("userImage") @BindingAdapter("userImage")
@ -112,7 +112,11 @@ fun bindUserImage(imageView: ImageView, user: User) {
.posterDescription(user.name) .posterDescription(user.name)
} }
private fun ImageView.loadImage(url: String, @DrawableRes placeholderId: Int = R.color.neutral_800, @DrawableRes errorPlaceHolderId: Int? = null): View { private fun ImageView.loadImage(
url: String,
@DrawableRes placeholderId: Int = R.color.neutral_800,
@DrawableRes errorPlaceHolderId: Int? = null
): View {
val api = JellyfinApi.getInstance(context.applicationContext) val api = JellyfinApi.getInstance(context.applicationContext)
Glide Glide

View file

@ -59,7 +59,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION) { if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION) {
val navView: NavigationBarView = binding.navView as NavigationBarView val navView: NavigationBarView = binding.navView as NavigationBarView

View file

@ -21,8 +21,8 @@ import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.utils.AppPreferences import dev.jdtech.jellyfin.utils.AppPreferences
import dev.jdtech.jellyfin.utils.PlayerGestureHelper import dev.jdtech.jellyfin.utils.PlayerGestureHelper
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class PlayerActivity : BasePlayerActivity() { class PlayerActivity : BasePlayerActivity() {
@ -177,4 +177,3 @@ class PlayerActivity : BasePlayerActivity() {
hideSystemUI() hideSystemUI()
} }
} }

View file

@ -9,7 +9,8 @@ import dev.jdtech.jellyfin.databinding.DiscoveredServerItemBinding
import dev.jdtech.jellyfin.models.DiscoveredServer import dev.jdtech.jellyfin.models.DiscoveredServer
class DiscoveredServerListAdapter( class DiscoveredServerListAdapter(
private val clickListener: (server: DiscoveredServer) -> Unit) : private val clickListener: (server: DiscoveredServer) -> Unit
) :
ListAdapter<DiscoveredServer, DiscoveredServerListAdapter.DiscoveredServerViewHolder>( ListAdapter<DiscoveredServer, DiscoveredServerListAdapter.DiscoveredServerViewHolder>(
DiffCallback DiffCallback
) { ) {

View file

@ -12,8 +12,8 @@ import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.UUID import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
private const val ITEM_VIEW_TYPE_HEADER = 0 private const val ITEM_VIEW_TYPE_HEADER = 0
private const val ITEM_VIEW_TYPE_EPISODE = 1 private const val ITEM_VIEW_TYPE_EPISODE = 1

View file

@ -13,8 +13,7 @@ import dev.jdtech.jellyfin.utils.downloadSeriesMetadataToBaseItemDto
class DownloadSeriesListAdapter( class DownloadSeriesListAdapter(
private val onClickListener: OnClickListener, private val onClickListener: OnClickListener,
private val fixedWidth: Boolean = false, private val fixedWidth: Boolean = false,
) : ) : ListAdapter<DownloadSeriesMetadata, DownloadSeriesListAdapter.ItemViewHolder>(DiffCallback) {
ListAdapter<DownloadSeriesMetadata, DownloadSeriesListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) : class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
@ -32,11 +31,17 @@ class DownloadSeriesListAdapter(
} }
companion object DiffCallback : DiffUtil.ItemCallback<DownloadSeriesMetadata>() { companion object DiffCallback : DiffUtil.ItemCallback<DownloadSeriesMetadata>() {
override fun areItemsTheSame(oldItem: DownloadSeriesMetadata, newItem: DownloadSeriesMetadata): Boolean { override fun areItemsTheSame(
oldItem: DownloadSeriesMetadata,
newItem: DownloadSeriesMetadata
): Boolean {
return oldItem.itemId == newItem.itemId return oldItem.itemId == newItem.itemId
} }
override fun areContentsTheSame(oldItem: DownloadSeriesMetadata, newItem: DownloadSeriesMetadata): Boolean { override fun areContentsTheSame(
oldItem: DownloadSeriesMetadata,
newItem: DownloadSeriesMetadata
): Boolean {
return oldItem == newItem return oldItem == newItem
} }
} }
@ -47,7 +52,8 @@ class DownloadSeriesListAdapter(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
), parent ),
parent
) )
} }

View file

@ -14,8 +14,7 @@ import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
class DownloadViewItemListAdapter( class DownloadViewItemListAdapter(
private val onClickListener: OnClickListener, private val onClickListener: OnClickListener,
private val fixedWidth: Boolean = false, private val fixedWidth: Boolean = false,
) : ) : ListAdapter<PlayerItem, DownloadViewItemListAdapter.ItemViewHolder>(DiffCallback) {
ListAdapter<PlayerItem, DownloadViewItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) : class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
@ -48,7 +47,8 @@ class DownloadViewItemListAdapter(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
), parent ),
parent
) )
} }

View file

@ -9,8 +9,8 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.*
private const val ITEM_VIEW_TYPE_HEADER = 0 private const val ITEM_VIEW_TYPE_HEADER = 0
private const val ITEM_VIEW_TYPE_EPISODE = 1 private const val ITEM_VIEW_TYPE_EPISODE = 1

View file

@ -17,8 +17,10 @@ class HomeEpisodeListAdapter(private val onClickListener: OnClickListener) : Lis
fun bind(episode: BaseItemDto) { fun bind(episode: BaseItemDto) {
binding.episode = episode binding.episode = episode
if (episode.userData?.playedPercentage != null) { if (episode.userData?.playedPercentage != null) {
binding.progressBar.layoutParams.width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, binding.progressBar.layoutParams.width = TypedValue.applyDimension(
(episode.userData?.playedPercentage?.times(2.24))!!.toFloat(), binding.progressBar.context.resources.displayMetrics).toInt() TypedValue.COMPLEX_UNIT_DIP,
(episode.userData?.playedPercentage?.times(2.24))!!.toFloat(), binding.progressBar.context.resources.displayMetrics
).toInt()
binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
} }

View file

@ -14,8 +14,7 @@ import org.jellyfin.sdk.model.api.BaseItemKind
class ViewItemListAdapter( class ViewItemListAdapter(
private val onClickListener: OnClickListener, private val onClickListener: OnClickListener,
private val fixedWidth: Boolean = false, 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) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
@ -49,7 +48,8 @@ class ViewItemListAdapter(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
), parent ),
parent
) )
} }

View file

@ -49,7 +49,8 @@ class ViewItemPagingAdapter(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
), parent ),
parent
) )
} }

View file

@ -65,7 +65,8 @@ class ViewListAdapter(
NextUpSectionBinding.inflate( NextUpSectionBinding.inflate(
LayoutInflater.from( LayoutInflater.from(
parent.context parent.context
), parent, false ),
parent, false
) )
) )
ITEM_VIEW_TYPE_VIEW -> ViewViewHolder( ITEM_VIEW_TYPE_VIEW -> ViewViewHolder(

View file

@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.api
import android.content.Context import android.content.Context
import dev.jdtech.jellyfin.BuildConfig import dev.jdtech.jellyfin.BuildConfig
import java.util.UUID
import org.jellyfin.sdk.api.client.extensions.devicesApi import org.jellyfin.sdk.api.client.extensions.devicesApi
import org.jellyfin.sdk.api.client.extensions.itemsApi import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
@ -15,7 +16,6 @@ import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.api.client.extensions.videosApi import org.jellyfin.sdk.api.client.extensions.videosApi
import org.jellyfin.sdk.createJellyfin import org.jellyfin.sdk.createJellyfin
import org.jellyfin.sdk.model.ClientInfo import org.jellyfin.sdk.model.ClientInfo
import java.util.UUID
/** /**
* Jellyfin API class using org.jellyfin.sdk:jellyfin-platform-android * Jellyfin API class using org.jellyfin.sdk:jellyfin-platform-android

View file

@ -1,7 +1,7 @@
package dev.jdtech.jellyfin.database package dev.jdtech.jellyfin.database
import androidx.room.TypeConverter import androidx.room.TypeConverter
import java.util.* import java.util.UUID
class Converters { class Converters {
@TypeConverter @TypeConverter

View file

@ -4,7 +4,7 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import dev.jdtech.jellyfin.models.DownloadItem import dev.jdtech.jellyfin.models.DownloadItem
import java.util.* import java.util.UUID
@Dao @Dao
interface DownloadDatabaseDao { interface DownloadDatabaseDao {
@ -28,5 +28,4 @@ interface DownloadDatabaseDao {
@Query("SELECT EXISTS (SELECT 1 FROM downloads WHERE id = :id)") @Query("SELECT EXISTS (SELECT 1 FROM downloads WHERE id = :id)")
fun exists(id: UUID): Boolean fun exists(id: UUID): Boolean
} }

View file

@ -1,7 +1,11 @@
package dev.jdtech.jellyfin.database package dev.jdtech.jellyfin.database
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
@Dao @Dao
interface ServerDatabaseDao { interface ServerDatabaseDao {

View file

@ -10,7 +10,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object SharedPreferencesModule { object SharedPreferencesModule {

View file

@ -19,7 +19,6 @@ class DeleteServerDialogFragment(private val viewModel: ServerSelectViewModel, v
viewModel.deleteServer(server) viewModel.deleteServer(server)
} }
.setNegativeButton(getString(R.string.cancel)) { _, _ -> .setNegativeButton(getString(R.string.cancel)) { _, _ ->
} }
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")

View file

@ -28,7 +28,6 @@ class ErrorDialogFragment(
val shareIntent = Intent.createChooser(sendIntent, null) val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent) startActivity(shareIntent)
} }
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")

View file

@ -8,9 +8,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import org.jellyfin.sdk.model.api.SortOrder
import java.lang.IllegalStateException import java.lang.IllegalStateException
import java.util.* import java.util.UUID
import org.jellyfin.sdk.model.api.SortOrder
class SortDialogFragment( class SortDialogFragment(
private val parentId: UUID, private val parentId: UUID,

View file

@ -29,7 +29,5 @@ class SpeedSelectionDialogFragment(
} }
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
} }

View file

@ -22,7 +22,8 @@ class TrackSelectionDialogFragment(
builder.setTitle(getString(R.string.select_audio_track)) builder.setTitle(getString(R.string.select_audio_track))
.setSingleChoiceItems( .setSingleChoiceItems(
getTrackNames(viewModel.currentAudioTracks), getTrackNames(viewModel.currentAudioTracks),
viewModel.currentAudioTracks.indexOfFirst { it.selected }) { dialog, which -> viewModel.currentAudioTracks.indexOfFirst { it.selected }
) { dialog, which ->
viewModel.switchToTrack( viewModel.switchToTrack(
TrackType.AUDIO, TrackType.AUDIO,
viewModel.currentAudioTracks[which] viewModel.currentAudioTracks[which]
@ -38,7 +39,8 @@ class TrackSelectionDialogFragment(
builder.setTitle(getString(R.string.select_subtile_track)) builder.setTitle(getString(R.string.select_subtile_track))
.setSingleChoiceItems( .setSingleChoiceItems(
getTrackNames(viewModel.currentSubtitleTracks), getTrackNames(viewModel.currentSubtitleTracks),
viewModel.currentSubtitleTracks.indexOfFirst { if (viewModel.disableSubtitle) it.ffIndex == -1 else it.selected }) { dialog, which -> viewModel.currentSubtitleTracks.indexOfFirst { if (viewModel.disableSubtitle) it.ffIndex == -1 else it.selected }
) { dialog, which ->
viewModel.switchToTrack( viewModel.switchToTrack(
TrackType.SUBTITLE, TrackType.SUBTITLE,
viewModel.currentSubtitleTracks[which] viewModel.currentSubtitleTracks[which]

View file

@ -4,9 +4,9 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.IllegalStateException
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import java.lang.IllegalStateException
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
class VideoVersionDialogFragment( class VideoVersionDialogFragment(

View file

@ -31,7 +31,8 @@ class AddServerFragment : Fragment() {
private val viewModel: AddServerViewModel by viewModels() private val viewModel: AddServerViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentAddServerBinding.inflate(inflater) binding = FragmentAddServerBinding.inflate(inflater)
@ -136,7 +137,9 @@ class AddServerFragment : Fragment() {
} }
} }
private fun bindDiscoveredServersStateServers(serversState: AddServerViewModel.DiscoveredServersState.Servers) { private fun bindDiscoveredServersStateServers(
serversState: AddServerViewModel.DiscoveredServersState.Servers
) {
val servers = serversState.servers val servers = serversState.servers
if (servers.isEmpty()) { if (servers.isEmpty()) {
binding.serversRecyclerView.isVisible = false binding.serversRecyclerView.isVisible = false

View file

@ -12,16 +12,18 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.* import dev.jdtech.jellyfin.adapters.DownloadSeriesListAdapter
import dev.jdtech.jellyfin.adapters.DownloadViewItemListAdapter
import dev.jdtech.jellyfin.adapters.DownloadsListAdapter
import dev.jdtech.jellyfin.databinding.FragmentDownloadBinding import dev.jdtech.jellyfin.databinding.FragmentDownloadBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.checkIfLoginRequired import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.DownloadViewModel import dev.jdtech.jellyfin.viewmodels.DownloadViewModel
import java.util.UUID
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class DownloadFragment : Fragment() { class DownloadFragment : Fragment() {
@ -32,15 +34,18 @@ class DownloadFragment : Fragment() {
private lateinit var errorDialog: ErrorDialogFragment private lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentDownloadBinding.inflate(inflater, container, false) binding = FragmentDownloadBinding.inflate(inflater, container, false)
binding.downloadsRecyclerView.adapter = DownloadsListAdapter( binding.downloadsRecyclerView.adapter =
DownloadsListAdapter(
DownloadViewItemListAdapter.OnClickListener { item -> DownloadViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item) navigateToMediaInfoFragment(item)
}, DownloadSeriesListAdapter.OnClickListener { item -> },
DownloadSeriesListAdapter.OnClickListener { item ->
navigateToDownloadSeriesFragment(item) navigateToDownloadSeriesFragment(item)
} }
) )
@ -97,11 +102,7 @@ class DownloadFragment : Fragment() {
private fun navigateToMediaInfoFragment(item: PlayerItem) { private fun navigateToMediaInfoFragment(item: PlayerItem) {
findNavController().navigate( findNavController().navigate(
DownloadFragmentDirections.actionDownloadFragmentToMediaInfoFragment( DownloadFragmentDirections.actionDownloadFragmentToMediaInfoFragment(
UUID.randomUUID(), UUID.randomUUID(), item.name, item.item!!.type, item, isOffline = true
item.name,
item.item!!.type,
item,
isOffline = true
) )
) )
} }
@ -109,8 +110,7 @@ class DownloadFragment : Fragment() {
private fun navigateToDownloadSeriesFragment(series: DownloadSeriesMetadata) { private fun navigateToDownloadSeriesFragment(series: DownloadSeriesMetadata) {
findNavController().navigate( findNavController().navigate(
DownloadFragmentDirections.actionDownloadFragmentToDownloadSeriesFragment( DownloadFragmentDirections.actionDownloadFragmentToDownloadSeriesFragment(
seriesMetadata = series, seriesMetadata = series, seriesName = series.name
seriesName = series.name
) )
) )
} }

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
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.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -18,8 +18,8 @@ import dev.jdtech.jellyfin.databinding.FragmentDownloadSeriesBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.DownloadSeriesViewModel import dev.jdtech.jellyfin.viewmodels.DownloadSeriesViewModel
import java.util.UUID
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class DownloadSeriesFragment : Fragment() { class DownloadSeriesFragment : Fragment() {
@ -32,7 +32,8 @@ class DownloadSeriesFragment : Fragment() {
private val args: DownloadSeriesFragmentArgs by navArgs() private val args: DownloadSeriesFragmentArgs by navArgs()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentDownloadSeriesBinding.inflate(inflater, container, false) binding = FragmentDownloadSeriesBinding.inflate(inflater, container, false)
@ -45,9 +46,12 @@ class DownloadSeriesFragment : Fragment() {
binding.viewModel = viewModel binding.viewModel = viewModel
binding.episodesRecyclerView.adapter = binding.episodesRecyclerView.adapter =
DownloadEpisodeListAdapter(DownloadEpisodeListAdapter.OnClickListener { episode -> DownloadEpisodeListAdapter(
DownloadEpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode) navigateToEpisodeBottomSheetFragment(episode)
}, args.seriesMetadata) },
args.seriesMetadata
)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {

View file

@ -26,11 +26,11 @@ import dev.jdtech.jellyfin.utils.setTintColor
import dev.jdtech.jellyfin.utils.setTintColorAttribute import dev.jdtech.jellyfin.utils.setTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import java.util.UUID
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.LocationType import org.jellyfin.sdk.model.api.LocationType
import timber.log.Timber import timber.log.Timber
import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class EpisodeBottomSheetFragment : BottomSheetDialogFragment() { class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
@ -195,7 +195,6 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
} }
} }
binding.episodeName.text = String.format( binding.episodeName.text = String.format(
getString(R.string.episode_name_extended), getString(R.string.episode_name_extended),
episode.parentIndexNumber, episode.parentIndexNumber,

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
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.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -32,7 +32,8 @@ class FavoriteFragment : Fragment() {
private lateinit var errorDialog: ErrorDialogFragment private lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentFavoriteBinding.inflate(inflater, container, false) binding = FragmentFavoriteBinding.inflate(inflater, container, false)
@ -40,9 +41,11 @@ class FavoriteFragment : Fragment() {
binding.favoritesRecyclerView.adapter = FavoritesListAdapter( binding.favoritesRecyclerView.adapter = FavoritesListAdapter(
ViewItemListAdapter.OnClickListener { item -> ViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item) navigateToMediaInfoFragment(item)
}, HomeEpisodeListAdapter.OnClickListener { item -> },
HomeEpisodeListAdapter.OnClickListener { item ->
navigateToEpisodeBottomSheetFragment(item) navigateToEpisodeBottomSheetFragment(item)
}) }
)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {

View file

@ -57,7 +57,8 @@ class HomeFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity() val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider { menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.home_menu, menu) menuInflater.inflate(R.menu.home_menu, menu)
} }
@ -71,7 +72,9 @@ class HomeFragment : Fragment() {
else -> false else -> false
} }
} }
}, viewLifecycleOwner, Lifecycle.State.RESUMED) },
viewLifecycleOwner, Lifecycle.State.RESUMED
)
} }
override fun onResume() { override fun onResume() {
@ -97,7 +100,8 @@ class HomeFragment : Fragment() {
else -> Toast.makeText(requireContext(), R.string.unknown_error, LENGTH_LONG) else -> Toast.makeText(requireContext(), R.string.unknown_error, LENGTH_LONG)
.show() .show()
} }
}) }
)
binding.errorLayout.errorRetryButton.setOnClickListener { binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadData() viewModel.loadData()

View file

@ -4,7 +4,12 @@ import android.app.UiModeManager
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuHost import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
@ -20,18 +25,18 @@ import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.LinearSnapHelper
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.SortDialogFragment import dev.jdtech.jellyfin.dialogs.SortDialogFragment
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import dev.jdtech.jellyfin.utils.checkIfLoginRequired import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import java.lang.IllegalArgumentException
import javax.inject.Inject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.SortOrder import org.jellyfin.sdk.model.api.SortOrder
import java.lang.IllegalArgumentException
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class LibraryFragment : Fragment() { class LibraryFragment : Fragment() {
@ -47,7 +52,8 @@ class LibraryFragment : Fragment() {
lateinit var sp: SharedPreferences lateinit var sp: SharedPreferences
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentLibraryBinding.inflate(inflater, container, false) binding = FragmentLibraryBinding.inflate(inflater, container, false)
@ -95,7 +101,8 @@ class LibraryFragment : Fragment() {
else -> false else -> false
} }
} }
}, viewLifecycleOwner, Lifecycle.State.RESUMED },
viewLifecycleOwner, Lifecycle.State.RESUMED
) )
binding.title?.text = args.libraryName binding.title?.text = args.libraryName
@ -117,9 +124,11 @@ class LibraryFragment : Fragment() {
} }
binding.itemsRecyclerView.adapter = binding.itemsRecyclerView.adapter =
ViewItemPagingAdapter(ViewItemPagingAdapter.OnClickListener { item -> ViewItemPagingAdapter(
ViewItemPagingAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item) navigateToMediaInfoFragment(item)
}) }
)
(binding.itemsRecyclerView.adapter as ViewItemPagingAdapter).addLoadStateListener { (binding.itemsRecyclerView.adapter as ViewItemPagingAdapter).addLoadStateListener {
when (it.refresh) { when (it.refresh) {

View file

@ -31,7 +31,8 @@ class LoginFragment : Fragment() {
private val viewModel: LoginViewModel by viewModels() private val viewModel: LoginViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentLoginBinding.inflate(inflater) binding = FragmentLoginBinding.inflate(inflater)

View file

@ -1,7 +1,13 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuHost import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
@ -41,9 +47,11 @@ class MediaFragment : Fragment() {
binding = FragmentMediaBinding.inflate(inflater, container, false) binding = FragmentMediaBinding.inflate(inflater, container, false)
binding.viewsRecyclerView.adapter = binding.viewsRecyclerView.adapter =
CollectionListAdapter(CollectionListAdapter.OnClickListener { library -> CollectionListAdapter(
CollectionListAdapter.OnClickListener { library ->
navigateToLibraryFragment(library) navigateToLibraryFragment(library)
}) }
)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -73,7 +81,8 @@ class MediaFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity() val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider { menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.media_menu, menu) menuInflater.inflate(R.menu.media_menu, menu)
@ -98,7 +107,9 @@ class MediaFragment : Fragment() {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return true return true
} }
}, viewLifecycleOwner, Lifecycle.State.RESUMED) },
viewLifecycleOwner, Lifecycle.State.RESUMED
)
} }
override fun onStart() { override fun onStart() {
@ -133,7 +144,6 @@ class MediaFragment : Fragment() {
binding.viewsRecyclerView.isVisible = false binding.viewsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(uiState.error.message) checkIfLoginRequired(uiState.error.message)
} }
private fun navigateToLibraryFragment(library: BaseItemDto) { private fun navigateToLibraryFragment(library: BaseItemDto) {

View file

@ -31,11 +31,11 @@ import dev.jdtech.jellyfin.utils.setTintColor
import dev.jdtech.jellyfin.utils.setTintColorAttribute import dev.jdtech.jellyfin.utils.setTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import java.util.UUID
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import timber.log.Timber import timber.log.Timber
import java.util.UUID
@AndroidEntryPoint @AndroidEntryPoint
class MediaInfoFragment : Fragment() { class MediaInfoFragment : Fragment() {
@ -48,7 +48,8 @@ class MediaInfoFragment : Fragment() {
lateinit var errorDialog: ErrorDialogFragment lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentMediaInfoBinding.inflate(inflater, container, false) binding = FragmentMediaInfoBinding.inflate(inflater, container, false)
@ -111,9 +112,12 @@ class MediaInfoFragment : Fragment() {
} }
binding.seasonsRecyclerView.adapter = binding.seasonsRecyclerView.adapter =
ViewItemListAdapter(ViewItemListAdapter.OnClickListener { season -> ViewItemListAdapter(
ViewItemListAdapter.OnClickListener { season ->
navigateToSeasonFragment(season) navigateToSeasonFragment(season)
}, fixedWidth = true) },
fixedWidth = true
)
binding.peopleRecyclerView.adapter = PersonListAdapter { person -> binding.peopleRecyclerView.adapter = PersonListAdapter { person ->
navigateToPersonDetail(person.id) navigateToPersonDetail(person.id)
} }
@ -187,7 +191,6 @@ class MediaInfoFragment : Fragment() {
) )
) )
} }
} else { } else {
binding.favoriteButton.isVisible = false binding.favoriteButton.isVisible = false
binding.checkButton.isVisible = false binding.checkButton.isVisible = false
@ -225,7 +228,6 @@ class MediaInfoFragment : Fragment() {
false -> binding.checkButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme) false -> binding.checkButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme)
} }
// Favorite icon // Favorite icon
val favoriteDrawable = when (favorite) { val favoriteDrawable = when (favorite) {
true -> R.drawable.ic_heart_filled true -> R.drawable.ic_heart_filled
@ -248,7 +250,6 @@ class MediaInfoFragment : Fragment() {
} }
} }
binding.name.text = item.name binding.name.text = item.name
binding.originalTitle.text = item.originalTitle binding.originalTitle.text = item.originalTitle
if (dateString.isEmpty()) { if (dateString.isEmpty()) {

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
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.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -34,7 +34,8 @@ class SearchResultFragment : Fragment() {
private lateinit var errorDialog: ErrorDialogFragment private lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentSearchResultBinding.inflate(inflater, container, false) binding = FragmentSearchResultBinding.inflate(inflater, container, false)
@ -42,9 +43,11 @@ class SearchResultFragment : Fragment() {
binding.searchResultsRecyclerView.adapter = FavoritesListAdapter( binding.searchResultsRecyclerView.adapter = FavoritesListAdapter(
ViewItemListAdapter.OnClickListener { item -> ViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item) navigateToMediaInfoFragment(item)
}, HomeEpisodeListAdapter.OnClickListener { item -> },
HomeEpisodeListAdapter.OnClickListener { item ->
navigateToEpisodeBottomSheetFragment(item) navigateToEpisodeBottomSheetFragment(item)
}) }
)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -73,7 +76,6 @@ class SearchResultFragment : Fragment() {
errorDialog.show(parentFragmentManager, "errordialog") errorDialog.show(parentFragmentManager, "errordialog")
} }
return binding.root return binding.root
} }

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
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.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -32,7 +32,8 @@ class SeasonFragment : Fragment() {
private lateinit var errorDialog: ErrorDialogFragment private lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentSeasonBinding.inflate(inflater, container, false) binding = FragmentSeasonBinding.inflate(inflater, container, false)
@ -70,10 +71,12 @@ class SeasonFragment : Fragment() {
} }
binding.episodesRecyclerView.adapter = binding.episodesRecyclerView.adapter =
EpisodeListAdapter(EpisodeListAdapter.OnClickListener { episode -> EpisodeListAdapter(
EpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode) navigateToEpisodeBottomSheetFragment(episode)
}, args.seriesId, args.seriesName, args.seasonId, args.seasonName) },
args.seriesId, args.seriesName, args.seasonId, args.seasonName
)
} }
private fun bindUiStateNormal(uiState: SeasonViewModel.UiState.Normal) { private fun bindUiStateNormal(uiState: SeasonViewModel.UiState.Normal) {

View file

@ -1,19 +1,19 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.os.Bundle import android.os.Bundle
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.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding
import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,7 +24,8 @@ class ServerSelectFragment : Fragment() {
private val viewModel: ServerSelectViewModel by viewModels() private val viewModel: ServerSelectViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentServerSelectBinding.inflate(inflater) binding = FragmentServerSelectBinding.inflate(inflater)
@ -34,15 +35,18 @@ class ServerSelectFragment : Fragment() {
binding.viewModel = viewModel binding.viewModel = viewModel
binding.serversRecyclerView.adapter = binding.serversRecyclerView.adapter =
ServerGridAdapter(ServerGridAdapter.OnClickListener { server -> ServerGridAdapter(
ServerGridAdapter.OnClickListener { server ->
viewModel.connectToServer(server) viewModel.connectToServer(server)
}, ServerGridAdapter.OnLongClickListener { server -> },
ServerGridAdapter.OnLongClickListener { server ->
DeleteServerDialogFragment(viewModel, server).show( DeleteServerDialogFragment(viewModel, server).show(
parentFragmentManager, parentFragmentManager,
"deleteServer" "deleteServer"
) )
true true
}) }
)
binding.buttonAddServer.setOnClickListener { binding.buttonAddServer.setOnClickListener {
navigateToAddServerFragment() navigateToAddServerFragment()

View file

@ -3,9 +3,9 @@ package dev.jdtech.jellyfin.models
import android.os.Parcelable import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.util.UUID
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.*
@Parcelize @Parcelize
@Entity(tableName = "downloads") @Entity(tableName = "downloads")

View file

@ -1,6 +1,6 @@
package dev.jdtech.jellyfin.models package dev.jdtech.jellyfin.models
import java.util.* import java.util.UUID
data class DownloadSection( data class DownloadSection(
val id: UUID, val id: UUID,

View file

@ -1,8 +1,8 @@
package dev.jdtech.jellyfin.models package dev.jdtech.jellyfin.models
import android.os.Parcelable import android.os.Parcelable
import java.util.UUID
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize @Parcelize
data class DownloadSeriesMetadata( data class DownloadSeriesMetadata(

View file

@ -1,7 +1,7 @@
package dev.jdtech.jellyfin.models package dev.jdtech.jellyfin.models
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.*
data class HomeSection( data class HomeSection(
val id: UUID, val id: UUID,

View file

@ -1,8 +1,8 @@
package dev.jdtech.jellyfin.models package dev.jdtech.jellyfin.models
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.util.UUID import java.util.UUID
import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class PlayerItem( data class PlayerItem(

View file

@ -1,7 +1,7 @@
package dev.jdtech.jellyfin.models package dev.jdtech.jellyfin.models
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.UUID import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
data class View( data class View(
val id: UUID, val id: UUID,

View file

@ -1,6 +1,5 @@
package dev.jdtech.jellyfin.mpv package dev.jdtech.jellyfin.mpv
import `is`.xyz.mpv.MPVLib
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.AssetManager import android.content.res.AssetManager
@ -13,24 +12,39 @@ import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.TextureView import android.view.TextureView
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.BasePlayer
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DeviceInfo
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.MediaMetadata
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.Commands import com.google.android.exoplayer2.Player.Commands
import com.google.android.exoplayer2.Timeline
import com.google.android.exoplayer2.Tracks
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.source.TrackGroup import com.google.android.exoplayer2.source.TrackGroup
import com.google.android.exoplayer2.text.Cue import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.text.CueGroup import com.google.android.exoplayer2.text.CueGroup
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters import com.google.android.exoplayer2.trackselection.TrackSelectionParameters
import com.google.android.exoplayer2.util.* import com.google.android.exoplayer2.util.Clock
import com.google.android.exoplayer2.util.FlagSet
import com.google.android.exoplayer2.util.ListenerSet
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.exoplayer2.util.Util
import com.google.android.exoplayer2.video.VideoSize import com.google.android.exoplayer2.video.VideoSize
import dev.jdtech.jellyfin.utils.AppPreferences import dev.jdtech.jellyfin.utils.AppPreferences
import `is`.xyz.mpv.MPVLib
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.CopyOnWriteArraySet
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.CopyOnWriteArraySet
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
class MPVPlayer( class MPVPlayer(
@ -627,16 +641,16 @@ class MPVPlayer(
.addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd) .addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd)
.addIf( .addIf(
COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_PREVIOUS,
!currentTimeline.isEmpty !currentTimeline.isEmpty &&
&& (hasPreviousMediaItem() || !isCurrentMediaItemLive || isCurrentMediaItemSeekable) (hasPreviousMediaItem() || !isCurrentMediaItemLive || isCurrentMediaItemSeekable) &&
&& !isPlayingAd !isPlayingAd
) )
.addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd) .addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd)
.addIf( .addIf(
COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_NEXT,
!currentTimeline.isEmpty !currentTimeline.isEmpty &&
&& (hasNextMediaItem() || (isCurrentMediaItemLive && isCurrentMediaItemDynamic)) (hasNextMediaItem() || (isCurrentMediaItemLive && isCurrentMediaItemDynamic)) &&
&& !isPlayingAd !isPlayingAd
) )
.addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd) .addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd)
.addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable && !isPlayingAd) .addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable && !isPlayingAd)
@ -1212,8 +1226,7 @@ class MPVPlayer(
MPVLib.setOptionString("panscan", "1") MPVLib.setOptionString("panscan", "1")
MPVLib.setOptionString("sub-use-margins", "yes") MPVLib.setOptionString("sub-use-margins", "yes")
MPVLib.setOptionString("sub-ass-force-margins", "yes") MPVLib.setOptionString("sub-ass-force-margins", "yes")
} } else {
else {
MPVLib.setOptionString("panscan", "0") MPVLib.setOptionString("panscan", "0")
MPVLib.setOptionString("sub-use-margins", "no") MPVLib.setOptionString("sub-use-margins", "no")
MPVLib.setOptionString("sub-ass-force-margins", "no") MPVLib.setOptionString("sub-ass-force-margins", "no")
@ -1409,7 +1422,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED }, IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentVideo } BooleanArray(this.length) { it == indexCurrentVideo }
) )
}) }
)
} }
if (trackListAudio.isNotEmpty()) { if (trackListAudio.isNotEmpty()) {
trackGroups.add( trackGroups.add(
@ -1420,7 +1434,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED }, IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentAudio } BooleanArray(this.length) { it == indexCurrentAudio }
) )
}) }
)
} }
if (trackListText.isNotEmpty()) { if (trackListText.isNotEmpty()) {
trackGroups.add( trackGroups.add(
@ -1431,7 +1446,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED }, IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentText } BooleanArray(this.length) { it == indexCurrentText }
) )
}) }
)
} }
if (trackGroups.isNotEmpty()) { if (trackGroups.isNotEmpty()) {
tracks = Tracks(trackGroups) tracks = Tracks(trackGroups)

View file

@ -4,11 +4,11 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.SortOrder import org.jellyfin.sdk.model.api.SortOrder
import timber.log.Timber import timber.log.Timber
import java.util.*
class ItemsPagingSource( class ItemsPagingSource(
private val jellyfinApi: JellyfinApi, private val jellyfinApi: JellyfinApi,

View file

@ -1,11 +1,14 @@
package dev.jdtech.jellyfin.repository package dev.jdtech.jellyfin.repository
import androidx.paging.PagingData import androidx.paging.PagingData
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.jellyfin.sdk.model.api.* import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.* import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.SortOrder
interface JellyfinRepository { interface JellyfinRepository {
suspend fun getUserViews(): List<BaseItemDto> suspend fun getUserViews(): List<BaseItemDto>

View file

@ -5,12 +5,25 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.* import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.DeviceOptionsDto
import org.jellyfin.sdk.model.api.DeviceProfile
import org.jellyfin.sdk.model.api.DirectPlayProfile
import org.jellyfin.sdk.model.api.DlnaProfileType
import org.jellyfin.sdk.model.api.GeneralCommandType
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.api.ItemFilter
import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.PlaybackInfoDto
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
import org.jellyfin.sdk.model.api.SubtitleProfile
import timber.log.Timber import timber.log.Timber
import java.util.*
class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRepository { class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRepository {
override suspend fun getUserViews(): List<BaseItemDto> = withContext(Dispatchers.IO) { override suspend fun getUserViews(): List<BaseItemDto> = withContext(Dispatchers.IO) {
@ -150,7 +163,8 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
override suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo> = override suspend fun getMediaSources(itemId: UUID): List<MediaSourceInfo> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
jellyfinApi.mediaInfoApi.getPostedPlaybackInfo( jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
itemId, PlaybackInfoDto( itemId,
PlaybackInfoDto(
userId = jellyfinApi.userId!!, userId = jellyfinApi.userId!!,
deviceProfile = DeviceProfile( deviceProfile = DeviceProfile(
name = "Direct play all", name = "Direct play all",
@ -220,7 +234,8 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
GeneralCommandType.PLAY_STATE, GeneralCommandType.PLAY_STATE,
GeneralCommandType.PLAY_NEXT, GeneralCommandType.PLAY_NEXT,
GeneralCommandType.PLAY_MEDIA_SOURCE GeneralCommandType.PLAY_MEDIA_SOURCE
), supportsMediaControl = true ),
supportsMediaControl = true
) )
} }
} }

View file

@ -70,13 +70,15 @@ internal class TvPlayerActivity : BasePlayerActivity() {
when { when {
viewModel.player.isPlaying -> { viewModel.player.isPlaying -> {
viewModel.player.pause() viewModel.player.pause()
setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme) setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme)
) )
} }
viewModel.player.isLoading -> Unit viewModel.player.isLoading -> Unit
else -> { else -> {
viewModel.player.play() viewModel.player.play()
setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme) setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme)
) )
} }
} }

View file

@ -10,7 +10,11 @@ import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.leanback.app.BrowseSupportFragment import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.* import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.DiffCallback
import androidx.leanback.widget.HeaderItem
import androidx.leanback.widget.ListRow
import androidx.leanback.widget.ListRowPresenter
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@ -146,15 +150,21 @@ internal class HomeFragment : BrowseSupportFragment() {
adapterMap[name]?.setItems(items, diffCallback) adapterMap[name]?.setItems(items, diffCallback)
} else { } else {
adapterMap[name] = when (this) { adapterMap[name] = when (this) {
is HomeItem.Libraries -> ArrayObjectAdapter(LibaryItemPresenter { item -> is HomeItem.Libraries -> ArrayObjectAdapter(
LibaryItemPresenter { item ->
navigateToLibraryFragment(item) navigateToLibraryFragment(item)
}).apply { setItems(items, diffCallback) } }
is HomeItem.Section -> ArrayObjectAdapter(DynamicMediaItemPresenter { item -> ).apply { setItems(items, diffCallback) }
is HomeItem.Section -> ArrayObjectAdapter(
DynamicMediaItemPresenter { item ->
navigateToMediaDetailFragment(item) navigateToMediaDetailFragment(item)
}).apply { setItems(items, diffCallback) } }
is HomeItem.ViewItem -> ArrayObjectAdapter(MediaItemPresenter { item -> ).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(
MediaItemPresenter { item ->
navigateToMediaDetailFragment(item) navigateToMediaDetailFragment(item)
}).apply { setItems(items, diffCallback) } }
).apply { setItems(items, diffCallback) }
} }
} }

View file

@ -76,7 +76,8 @@ internal class MediaDetailFragment : Fragment() {
val seasonsAdapter = ViewItemListAdapter( val seasonsAdapter = ViewItemListAdapter(
fixedWidth = true, fixedWidth = true,
onClickListener = ViewItemListAdapter.OnClickListener {}) onClickListener = ViewItemListAdapter.OnClickListener {}
)
binding.seasonsRow.gridView.adapter = seasonsAdapter binding.seasonsRow.gridView.adapter = seasonsAdapter
binding.seasonsRow.gridView.verticalSpacing = 25 binding.seasonsRow.gridView.verticalSpacing = 25

View file

@ -11,12 +11,12 @@ import dev.jdtech.jellyfin.models.DownloadItem
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import java.io.File
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.UserItemDataDto import org.jellyfin.sdk.model.api.UserItemDataDto
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.util.UUID
var defaultStorage: File? = null var defaultStorage: File? = null
@ -156,7 +156,6 @@ fun deleteDownloadedEpisode(downloadDatabase: DownloadDatabaseDao, itemId: UUID)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
} }
fun postDownloadPlaybackProgress( fun postDownloadPlaybackProgress(
@ -277,6 +276,5 @@ suspend fun syncPlaybackProgress(
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
} }
} }

View file

@ -14,8 +14,8 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.ui.StyledPlayerView
import dev.jdtech.jellyfin.PlayerActivity import dev.jdtech.jellyfin.PlayerActivity
import dev.jdtech.jellyfin.mpv.MPVPlayer import dev.jdtech.jellyfin.mpv.MPVPlayer
import timber.log.Timber
import kotlin.math.abs import kotlin.math.abs
import timber.log.Timber
class PlayerGestureHelper( class PlayerGestureHelper(
private val appPreferences: AppPreferences, private val appPreferences: AppPreferences,
@ -45,7 +45,9 @@ class PlayerGestureHelper(
private var lastScaleEvent: Long = 0 private var lastScaleEvent: Long = 0
private val tapGestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() { private val tapGestureDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean { override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply { playerView.apply {
if (!isControllerFullyVisible) showController() else hideController() if (!isControllerFullyVisible) showController() else hideController()
@ -60,15 +62,17 @@ class PlayerGestureHelper(
if (e.x.toInt() > viewCenterX) { if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement) playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
} } else {
else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0)) playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
} }
return true return true
} }
}) }
)
private val gestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() { private val gestureDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean { override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply { playerView.apply {
if (!isControllerFullyVisible) showController() else hideController() if (!isControllerFullyVisible) showController() else hideController()
@ -83,15 +87,19 @@ class PlayerGestureHelper(
if (e.x.toInt() > viewCenterX) { if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement) playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
} } else {
else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0)) playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
} }
return true return true
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onScroll(firstEvent: MotionEvent, currentEvent: MotionEvent, distanceX: Float, distanceY: Float): Boolean { override fun onScroll(
firstEvent: MotionEvent,
currentEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP)) if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
return false return false
@ -100,7 +108,8 @@ class PlayerGestureHelper(
return if ((abs(currentEvent.x - firstEvent.x) > 50 || swipeGestureProgressOpen) && return if ((abs(currentEvent.x - firstEvent.x) > 50 || swipeGestureProgressOpen) &&
!swipeGestureBrightnessOpen && !swipeGestureBrightnessOpen &&
!swipeGestureVolumeOpen && !swipeGestureVolumeOpen &&
(SystemClock.elapsedRealtime() - lastScaleEvent) > 200) { (SystemClock.elapsedRealtime() - lastScaleEvent) > 200
) {
val currentPos = playerView.player?.currentPosition ?: 0 val currentPos = playerView.player?.currentPosition ?: 0
val vidDuration = (playerView.player?.duration ?: 0).coerceAtLeast(0) val vidDuration = (playerView.player?.duration ?: 0).coerceAtLeast(0)
@ -171,7 +180,8 @@ class PlayerGestureHelper(
} }
return true return true
} }
}) }
)
private val hideGestureVolumeIndicatorOverlayAction = Runnable { private val hideGestureVolumeIndicatorOverlayAction = Runnable {
activity.binding.gestureVolumeLayout.visibility = View.GONE activity.binding.gestureVolumeLayout.visibility = View.GONE
@ -191,7 +201,9 @@ class PlayerGestureHelper(
/** /**
* Handles scale/zoom gesture * Handles scale/zoom gesture
*/ */
private val zoomGestureDetector = ScaleGestureDetector(playerView.context, object : ScaleGestureDetector.OnScaleGestureListener { private val zoomGestureDetector = ScaleGestureDetector(
playerView.context,
object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
override fun onScale(detector: ScaleGestureDetector): Boolean { override fun onScale(detector: ScaleGestureDetector): Boolean {
@ -205,13 +217,13 @@ class PlayerGestureHelper(
} }
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
}).apply { isQuickScaleEnabled = false } }
).apply { isQuickScaleEnabled = false }
private fun updateZoomMode(enabled: Boolean) { private fun updateZoomMode(enabled: Boolean) {
if (playerView.player is MPVPlayer) { if (playerView.player is MPVPlayer) {
(playerView.player as MPVPlayer).updateZoomMode(enabled) (playerView.player as MPVPlayer).updateZoomMode(enabled)
} } else {
else {
playerView.resizeMode = if (enabled) AspectRatioFrameLayout.RESIZE_MODE_ZOOM else AspectRatioFrameLayout.RESIZE_MODE_FIT playerView.resizeMode = if (enabled) AspectRatioFrameLayout.RESIZE_MODE_ZOOM else AspectRatioFrameLayout.RESIZE_MODE_FIT
} }
} }

View file

@ -11,13 +11,21 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.DiscoveredServer import dev.jdtech.jellyfin.models.DiscoveredServer
import kotlinx.coroutines.* import javax.inject.Inject
import kotlinx.coroutines.flow.* import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.discovery.RecommendedServerInfo import org.jellyfin.sdk.discovery.RecommendedServerInfo
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
import org.jellyfin.sdk.discovery.RecommendedServerIssue import org.jellyfin.sdk.discovery.RecommendedServerIssue
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class AddServerViewModel class AddServerViewModel
@ -54,11 +62,13 @@ constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val servers = jellyfinApi.jellyfin.discovery.discoverLocalServers() val servers = jellyfinApi.jellyfin.discovery.discoverLocalServers()
servers.collect { serverDiscoveryInfo -> servers.collect { serverDiscoveryInfo ->
discoveredServers.add(DiscoveredServer( discoveredServers.add(
DiscoveredServer(
serverDiscoveryInfo.id, serverDiscoveryInfo.id,
serverDiscoveryInfo.name, serverDiscoveryInfo.name,
serverDiscoveryInfo.address serverDiscoveryInfo.address
)) )
)
_discoveredServersState.emit( _discoveredServersState.emit(
DiscoveredServersState.Servers(ArrayList(discoveredServers)) DiscoveredServersState.Servers(ArrayList(discoveredServers))
) )
@ -131,7 +141,6 @@ constructor(
} }
} }
} catch (_: CancellationException) { } catch (_: CancellationException) {
} catch (e: Exception) { } catch (e: Exception) {
_uiState.emit( _uiState.emit(
UiState.Error( UiState.Error(

View file

@ -5,10 +5,10 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.adapters.DownloadEpisodeItem import dev.jdtech.jellyfin.adapters.DownloadEpisodeItem
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class DownloadSeriesViewModel class DownloadSeriesViewModel
@ -36,8 +36,11 @@ constructor() : ViewModel() {
private fun getEpisodes(seriesMetadata: DownloadSeriesMetadata): List<DownloadEpisodeItem> { private fun getEpisodes(seriesMetadata: DownloadSeriesMetadata): List<DownloadEpisodeItem> {
val episodes = seriesMetadata.episodes val episodes = seriesMetadata.episodes
return listOf(DownloadEpisodeItem.Header) + episodes.sortedWith(compareBy( return listOf(DownloadEpisodeItem.Header) + episodes.sortedWith(
compareBy(
{ it.item!!.parentIndexNumber }, { it.item!!.parentIndexNumber },
{ it.item!!.indexNumber })).map { DownloadEpisodeItem.Episode(it) } { it.item!!.indexNumber }
)
).map { DownloadEpisodeItem.Episode(it) }
} }
} }

View file

@ -1,7 +1,8 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.DownloadSection import dev.jdtech.jellyfin.models.DownloadSection
@ -9,13 +10,14 @@ import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.checkDownloadStatus import dev.jdtech.jellyfin.utils.checkDownloadStatus
import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes
import kotlinx.coroutines.* import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.*
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class DownloadViewModel class DownloadViewModel

View file

@ -1,12 +1,23 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import android.app.Application import android.app.Application
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.* import dev.jdtech.jellyfin.utils.canRetryDownload
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
import dev.jdtech.jellyfin.utils.isItemAvailable
import dev.jdtech.jellyfin.utils.isItemDownloaded
import dev.jdtech.jellyfin.utils.requestDownload
import java.text.DateFormat
import java.time.ZoneOffset
import java.util.Date
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -14,11 +25,6 @@ import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.model.DateTime import org.jellyfin.sdk.model.DateTime
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat
import java.time.ZoneOffset
import java.util.Date
import java.util.UUID
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class EpisodeBottomSheetViewModel class EpisodeBottomSheetViewModel

View file

@ -9,13 +9,13 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.models.FavoriteSection import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Constants import dev.jdtech.jellyfin.utils.Constants
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class FavoriteViewModel class FavoriteViewModel
@ -56,7 +56,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_MOVIES, Constants.FAVORITE_TYPE_MOVIES,
resources.getString(R.string.movies_label), resources.getString(R.string.movies_label),
items.filter { it.type == BaseItemKind.MOVIE }).let { items.filter { it.type == BaseItemKind.MOVIE }
).let {
if (it.items.isNotEmpty()) favoriteSections.add( if (it.items.isNotEmpty()) favoriteSections.add(
it it
) )
@ -64,7 +65,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_SHOWS, Constants.FAVORITE_TYPE_SHOWS,
resources.getString(R.string.shows_label), resources.getString(R.string.shows_label),
items.filter { it.type == BaseItemKind.SERIES }).let { items.filter { it.type == BaseItemKind.SERIES }
).let {
if (it.items.isNotEmpty()) favoriteSections.add( if (it.items.isNotEmpty()) favoriteSections.add(
it it
) )
@ -72,7 +74,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_EPISODES, Constants.FAVORITE_TYPE_EPISODES,
resources.getString(R.string.episodes_label), resources.getString(R.string.episodes_label),
items.filter { it.type == BaseItemKind.EPISODE }).let { items.filter { it.type == BaseItemKind.EPISODE }
).let {
if (it.items.isNotEmpty()) favoriteSections.add( if (it.items.isNotEmpty()) favoriteSections.add(
it it
) )

View file

@ -14,13 +14,13 @@ import dev.jdtech.jellyfin.models.HomeSection
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.syncPlaybackProgress import dev.jdtech.jellyfin.utils.syncPlaybackProgress
import dev.jdtech.jellyfin.utils.toView import dev.jdtech.jellyfin.utils.toView
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.*
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class HomeViewModel @Inject internal constructor( class HomeViewModel @Inject internal constructor(
@ -117,5 +117,3 @@ class HomeViewModel @Inject internal constructor(
.map { (view, latest) -> view.toView().apply { items = latest } } .map { (view, latest) -> view.toView().apply { items = latest } }
.map { ViewItem(it) } .map { ViewItem(it) }
} }

View file

@ -1,10 +1,13 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.SortBy import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -13,8 +16,6 @@ import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.SortOrder import org.jellyfin.sdk.model.api.SortOrder
import timber.log.Timber import timber.log.Timber
import java.util.*
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class LibraryViewModel class LibraryViewModel

View file

@ -2,7 +2,8 @@ package dev.jdtech.jellyfin.viewmodels
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.BaseApplication import dev.jdtech.jellyfin.BaseApplication
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
@ -10,6 +11,8 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.User import dev.jdtech.jellyfin.models.User
import javax.inject.Inject
import kotlin.Exception
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -18,8 +21,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.AuthenticateUserByName import org.jellyfin.sdk.model.api.AuthenticateUserByName
import javax.inject.Inject
import kotlin.Exception
@HiltViewModel @HiltViewModel
class LoginViewModel class LoginViewModel

View file

@ -7,7 +7,14 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.* import dev.jdtech.jellyfin.utils.canRetryDownload
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
import dev.jdtech.jellyfin.utils.isItemAvailable
import dev.jdtech.jellyfin.utils.isItemDownloaded
import dev.jdtech.jellyfin.utils.requestDownload
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -18,8 +25,6 @@ import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.BaseItemPerson import org.jellyfin.sdk.model.api.BaseItemPerson
import timber.log.Timber import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class MediaInfoViewModel class MediaInfoViewModel

View file

@ -1,14 +1,15 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.CollectionType import dev.jdtech.jellyfin.models.CollectionType
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class MediaViewModel class MediaViewModel

View file

@ -4,13 +4,13 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.UUID
import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class PersonDetailViewModel @Inject internal constructor( internal class PersonDetailViewModel @Inject internal constructor(

View file

@ -8,7 +8,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao import dev.jdtech.jellyfin.database.DownloadDatabaseDao
@ -18,11 +22,11 @@ import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.AppPreferences import dev.jdtech.jellyfin.utils.AppPreferences
import dev.jdtech.jellyfin.utils.postDownloadPlaybackProgress import dev.jdtech.jellyfin.utils.postDownloadPlaybackProgress
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PlayerActivityViewModel class PlayerActivityViewModel

View file

@ -14,6 +14,7 @@ import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.getDownloadPlayerItem import dev.jdtech.jellyfin.utils.getDownloadPlayerItem
import dev.jdtech.jellyfin.utils.isItemAvailable import dev.jdtech.jellyfin.utils.isItemAvailable
import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,7 +25,6 @@ import org.jellyfin.sdk.model.api.LocationType.VIRTUAL
import org.jellyfin.sdk.model.api.MediaProtocol import org.jellyfin.sdk.model.api.MediaProtocol
import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.MediaStreamType
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PlayerViewModel @Inject internal constructor( class PlayerViewModel @Inject internal constructor(

View file

@ -9,13 +9,13 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.models.FavoriteSection import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Constants import dev.jdtech.jellyfin.utils.Constants
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SearchResultViewModel class SearchResultViewModel
@ -52,7 +52,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_MOVIES, Constants.FAVORITE_TYPE_MOVIES,
resources.getString(R.string.movies_label), resources.getString(R.string.movies_label),
items.filter { it.type == BaseItemKind.MOVIE }).let { items.filter { it.type == BaseItemKind.MOVIE }
).let {
if (it.items.isNotEmpty()) sections.add( if (it.items.isNotEmpty()) sections.add(
it it
) )
@ -60,7 +61,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_SHOWS, Constants.FAVORITE_TYPE_SHOWS,
resources.getString(R.string.shows_label), resources.getString(R.string.shows_label),
items.filter { it.type == BaseItemKind.SERIES }).let { items.filter { it.type == BaseItemKind.SERIES }
).let {
if (it.items.isNotEmpty()) sections.add( if (it.items.isNotEmpty()) sections.add(
it it
) )
@ -68,7 +70,8 @@ constructor(
FavoriteSection( FavoriteSection(
Constants.FAVORITE_TYPE_EPISODES, Constants.FAVORITE_TYPE_EPISODES,
resources.getString(R.string.episodes_label), resources.getString(R.string.episodes_label),
items.filter { it.type == BaseItemKind.EPISODE }).let { items.filter { it.type == BaseItemKind.EPISODE }
).let {
if (it.items.isNotEmpty()) sections.add( if (it.items.isNotEmpty()) sections.add(
it it
) )

View file

@ -1,15 +1,16 @@
package dev.jdtech.jellyfin.viewmodels package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.* import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.adapters.EpisodeItem import dev.jdtech.jellyfin.adapters.EpisodeItem
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemFields
import java.util.*
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SeasonViewModel class SeasonViewModel

View file

@ -7,12 +7,12 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.api.JellyfinApi import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ServerSelectViewModel class ServerSelectViewModel

View file

@ -4,9 +4,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import javax.inject.Inject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
@HiltViewModel @HiltViewModel
internal class SettingsDeviceViewModel internal class SettingsDeviceViewModel