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

View file

@ -8,7 +8,7 @@ import androidx.core.view.updatePadding
import com.google.android.exoplayer2.trackselection.MappingTrackSelector
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
abstract class BasePlayerActivity: AppCompatActivity() {
abstract class BasePlayerActivity : AppCompatActivity() {
abstract val viewModel: PlayerActivityViewModel
@ -27,9 +27,11 @@ abstract class BasePlayerActivity: AppCompatActivity() {
@Suppress("DEPRECATION")
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
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_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)
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.database.Server
import dev.jdtech.jellyfin.models.User
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.BaseItemPerson
import org.jellyfin.sdk.model.api.ImageType
import java.util.UUID
@BindingAdapter("servers")
fun bindServers(recyclerView: RecyclerView, data: List<Server>?) {
@ -75,7 +75,7 @@ fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
var imageItemId = episode.id
var imageType = ImageType.PRIMARY
if (!episode.imageTags.isNullOrEmpty()) { //TODO: Downloadmetadata currently does not store imagetags, so it always uses the backdrop
if (!episode.imageTags.isNullOrEmpty()) { // TODO: Downloadmetadata currently does not store imagetags, so it always uses the backdrop
when (episode.type) {
BaseItemKind.MOVIE -> {
if (!episode.backdropImageTags.isNullOrEmpty()) {
@ -96,13 +96,13 @@ fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
}
imageView
.loadImage("/items/${imageItemId}/Images/$imageType")
.loadImage("/items/$imageItemId/Images/$imageType")
.posterDescription(episode.name)
}
@BindingAdapter("seasonPoster")
fun bindSeasonPoster(imageView: ImageView, seasonId: UUID) {
imageView.loadImage("/items/${seasonId}/Images/${ImageType.PRIMARY}")
imageView.loadImage("/items/$seasonId/Images/${ImageType.PRIMARY}")
}
@BindingAdapter("userImage")
@ -112,7 +112,11 @@ fun bindUserImage(imageView: ImageView, user: User) {
.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)
Glide

View file

@ -59,7 +59,6 @@ class MainActivity : AppCompatActivity() {
}
}
if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION) {
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.PlayerGestureHelper
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import timber.log.Timber
import javax.inject.Inject
import timber.log.Timber
@AndroidEntryPoint
class PlayerActivity : BasePlayerActivity() {
@ -177,4 +177,3 @@ class PlayerActivity : BasePlayerActivity() {
hideSystemUI()
}
}

View file

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

View file

@ -12,8 +12,8 @@ import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
import org.jellyfin.sdk.model.api.BaseItemDto
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_EPISODE = 1

View file

@ -13,8 +13,7 @@ import dev.jdtech.jellyfin.utils.downloadSeriesMetadataToBaseItemDto
class DownloadSeriesListAdapter(
private val onClickListener: OnClickListener,
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) :
RecyclerView.ViewHolder(binding.root) {
@ -32,11 +31,17 @@ class DownloadSeriesListAdapter(
}
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
}
override fun areContentsTheSame(oldItem: DownloadSeriesMetadata, newItem: DownloadSeriesMetadata): Boolean {
override fun areContentsTheSame(
oldItem: DownloadSeriesMetadata,
newItem: DownloadSeriesMetadata
): Boolean {
return oldItem == newItem
}
}
@ -47,7 +52,8 @@ class DownloadSeriesListAdapter(
LayoutInflater.from(parent.context),
parent,
false
), parent
),
parent
)
}

View file

@ -14,8 +14,7 @@ import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
class DownloadViewItemListAdapter(
private val onClickListener: OnClickListener,
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) :
RecyclerView.ViewHolder(binding.root) {
@ -48,7 +47,8 @@ class DownloadViewItemListAdapter(
LayoutInflater.from(parent.context),
parent,
false
), parent
),
parent
)
}

View file

@ -9,8 +9,8 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.*
private const val ITEM_VIEW_TYPE_HEADER = 0
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) {
binding.episode = episode
if (episode.userData?.playedPercentage != null) {
binding.progressBar.layoutParams.width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(episode.userData?.playedPercentage?.times(2.24))!!.toFloat(), binding.progressBar.context.resources.displayMetrics).toInt()
binding.progressBar.layoutParams.width = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
(episode.userData?.playedPercentage?.times(2.24))!!.toFloat(), binding.progressBar.context.resources.displayMetrics
).toInt()
binding.progressBar.visibility = View.VISIBLE
}

View file

@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.PersonItemBinding
import org.jellyfin.sdk.model.api.BaseItemPerson
class PersonListAdapter(private val clickListener: (item: BaseItemPerson) -> Unit) :ListAdapter<BaseItemPerson, PersonListAdapter.PersonViewHolder>(DiffCallback) {
class PersonListAdapter(private val clickListener: (item: BaseItemPerson) -> Unit) : ListAdapter<BaseItemPerson, PersonListAdapter.PersonViewHolder>(DiffCallback) {
class PersonViewHolder(private var binding: PersonItemBinding) :
RecyclerView.ViewHolder(binding.root) {

View file

@ -11,7 +11,7 @@ import dev.jdtech.jellyfin.databinding.ServerItemBinding
class ServerGridAdapter(
private val onClickListener: OnClickListener,
private val onLongClickListener: OnLongClickListener
) : ListAdapter<Server, ServerGridAdapter.ServerViewHolder>(DiffCallback) {
) : ListAdapter<Server, ServerGridAdapter.ServerViewHolder>(DiffCallback) {
class ServerViewHolder(private var binding: ServerItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(server: Server) {

View file

@ -14,8 +14,7 @@ import org.jellyfin.sdk.model.api.BaseItemKind
class ViewItemListAdapter(
private val onClickListener: OnClickListener,
private val fixedWidth: Boolean = false,
) :
ListAdapter<BaseItemDto, ViewItemListAdapter.ItemViewHolder>(DiffCallback) {
) : ListAdapter<BaseItemDto, ViewItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(private var binding: BaseItemBinding, private val parent: ViewGroup) :
RecyclerView.ViewHolder(binding.root) {
@ -49,7 +48,8 @@ class ViewItemListAdapter(
LayoutInflater.from(parent.context),
parent,
false
), parent
),
parent
)
}

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package dev.jdtech.jellyfin.api
import android.content.Context
import dev.jdtech.jellyfin.BuildConfig
import java.util.UUID
import org.jellyfin.sdk.api.client.extensions.devicesApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
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.createJellyfin
import org.jellyfin.sdk.model.ClientInfo
import java.util.UUID
/**
* Jellyfin API class using org.jellyfin.sdk:jellyfin-platform-android

View file

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

View file

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

View file

@ -1,7 +1,11 @@
package dev.jdtech.jellyfin.database
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
interface ServerDatabaseDao {

View file

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

View file

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

View file

@ -10,7 +10,7 @@ import java.lang.IllegalStateException
class ErrorDialogFragment(
private val error: Exception
) : DialogFragment() {
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it, R.style.ErrorDialogStyle)
@ -28,7 +28,6 @@ class ErrorDialogFragment(
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}
builder.create()
} ?: 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.utils.SortBy
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import org.jellyfin.sdk.model.api.SortOrder
import java.lang.IllegalStateException
import java.util.*
import java.util.UUID
import org.jellyfin.sdk.model.api.SortOrder
class SortDialogFragment(
private val parentId: UUID,

View file

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

View file

@ -22,7 +22,8 @@ class TrackSelectionDialogFragment(
builder.setTitle(getString(R.string.select_audio_track))
.setSingleChoiceItems(
getTrackNames(viewModel.currentAudioTracks),
viewModel.currentAudioTracks.indexOfFirst { it.selected }) { dialog, which ->
viewModel.currentAudioTracks.indexOfFirst { it.selected }
) { dialog, which ->
viewModel.switchToTrack(
TrackType.AUDIO,
viewModel.currentAudioTracks[which]
@ -38,7 +39,8 @@ class TrackSelectionDialogFragment(
builder.setTitle(getString(R.string.select_subtile_track))
.setSingleChoiceItems(
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(
TrackType.SUBTITLE,
viewModel.currentSubtitleTracks[which]

View file

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

View file

@ -31,7 +31,8 @@ class AddServerFragment : Fragment() {
private val viewModel: AddServerViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
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
if (servers.isEmpty()) {
binding.serversRecyclerView.isVisible = false

View file

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

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
@ -18,8 +18,8 @@ import dev.jdtech.jellyfin.databinding.FragmentDownloadSeriesBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.DownloadSeriesViewModel
import java.util.UUID
import kotlinx.coroutines.launch
import java.util.*
@AndroidEntryPoint
class DownloadSeriesFragment : Fragment() {
@ -32,7 +32,8 @@ class DownloadSeriesFragment : Fragment() {
private val args: DownloadSeriesFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDownloadSeriesBinding.inflate(inflater, container, false)
@ -45,9 +46,12 @@ class DownloadSeriesFragment : Fragment() {
binding.viewModel = viewModel
binding.episodesRecyclerView.adapter =
DownloadEpisodeListAdapter(DownloadEpisodeListAdapter.OnClickListener { episode ->
DownloadEpisodeListAdapter(
DownloadEpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode)
}, args.seriesMetadata)
},
args.seriesMetadata
)
viewLifecycleOwner.lifecycleScope.launch {
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.viewmodels.EpisodeBottomSheetViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import java.util.UUID
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.LocationType
import timber.log.Timber
import java.util.*
@AndroidEntryPoint
class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
@ -50,7 +50,7 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
binding.playButton.setOnClickListener {
binding.playButton.setImageResource(android.R.color.transparent)
binding.progressCircular.isVisible = true
if (viewModel.canRetry){
if (viewModel.canRetry) {
binding.playButton.isEnabled = false
viewModel.download()
return@setOnClickListener
@ -195,7 +195,6 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
}
}
binding.episodeName.text = String.format(
getString(R.string.episode_name_extended),
episode.parentIndexNumber,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,13 @@
package dev.jdtech.jellyfin.fragments
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.core.view.MenuHost
import androidx.core.view.MenuProvider
@ -41,9 +47,11 @@ class MediaFragment : Fragment() {
binding = FragmentMediaBinding.inflate(inflater, container, false)
binding.viewsRecyclerView.adapter =
CollectionListAdapter(CollectionListAdapter.OnClickListener { library ->
CollectionListAdapter(
CollectionListAdapter.OnClickListener { library ->
navigateToLibraryFragment(library)
})
}
)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -73,7 +81,8 @@ class MediaFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.media_menu, menu)
@ -98,7 +107,9 @@ class MediaFragment : Fragment() {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return true
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
},
viewLifecycleOwner, Lifecycle.State.RESUMED
)
}
override fun onStart() {
@ -133,7 +144,6 @@ class MediaFragment : Fragment() {
binding.viewsRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(uiState.error.message)
}
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.viewmodels.MediaInfoViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import java.util.UUID
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import timber.log.Timber
import java.util.UUID
@AndroidEntryPoint
class MediaInfoFragment : Fragment() {
@ -48,7 +48,8 @@ class MediaInfoFragment : Fragment() {
lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentMediaInfoBinding.inflate(inflater, container, false)
@ -111,9 +112,12 @@ class MediaInfoFragment : Fragment() {
}
binding.seasonsRecyclerView.adapter =
ViewItemListAdapter(ViewItemListAdapter.OnClickListener { season ->
ViewItemListAdapter(
ViewItemListAdapter.OnClickListener { season ->
navigateToSeasonFragment(season)
}, fixedWidth = true)
},
fixedWidth = true
)
binding.peopleRecyclerView.adapter = PersonListAdapter { person ->
navigateToPersonDetail(person.id)
}
@ -121,7 +125,7 @@ class MediaInfoFragment : Fragment() {
binding.playButton.setOnClickListener {
binding.playButton.setImageResource(android.R.color.transparent)
binding.progressCircular.isVisible = true
if (viewModel.canRetry){
if (viewModel.canRetry) {
binding.playButton.isEnabled = false
viewModel.download()
return@setOnClickListener
@ -187,7 +191,6 @@ class MediaInfoFragment : Fragment() {
)
)
}
} else {
binding.favoriteButton.isVisible = false
binding.checkButton.isVisible = false
@ -225,7 +228,6 @@ class MediaInfoFragment : Fragment() {
false -> binding.checkButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme)
}
// Favorite icon
val favoriteDrawable = when (favorite) {
true -> R.drawable.ic_heart_filled
@ -248,7 +250,6 @@ class MediaInfoFragment : Fragment() {
}
}
binding.name.text = item.name
binding.originalTitle.text = item.originalTitle
if (dateString.isEmpty()) {

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.R
class SettingsFragment: PreferenceFragmentCompat() {
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.fragment_settings, rootKey)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
package dev.jdtech.jellyfin.mpv
import `is`.xyz.mpv.MPVLib
import android.app.Application
import android.content.Context
import android.content.res.AssetManager
@ -13,24 +12,39 @@ import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.TextureView
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.Timeline
import com.google.android.exoplayer2.Tracks
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.source.TrackGroup
import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.text.CueGroup
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 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 org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.CopyOnWriteArraySet
@Suppress("SpellCheckingInspection")
class MPVPlayer(
@ -627,16 +641,16 @@ class MPVPlayer(
.addIf(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, hasPreviousMediaItem() && !isPlayingAd)
.addIf(
COMMAND_SEEK_TO_PREVIOUS,
!currentTimeline.isEmpty
&& (hasPreviousMediaItem() || !isCurrentMediaItemLive || isCurrentMediaItemSeekable)
&& !isPlayingAd
!currentTimeline.isEmpty &&
(hasPreviousMediaItem() || !isCurrentMediaItemLive || isCurrentMediaItemSeekable) &&
!isPlayingAd
)
.addIf(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, hasNextMediaItem() && !isPlayingAd)
.addIf(
COMMAND_SEEK_TO_NEXT,
!currentTimeline.isEmpty
&& (hasNextMediaItem() || (isCurrentMediaItemLive && isCurrentMediaItemDynamic))
&& !isPlayingAd
!currentTimeline.isEmpty &&
(hasNextMediaItem() || (isCurrentMediaItemLive && isCurrentMediaItemDynamic)) &&
!isPlayingAd
)
.addIf(COMMAND_SEEK_TO_MEDIA_ITEM, !isPlayingAd)
.addIf(COMMAND_SEEK_BACK, isCurrentMediaItemSeekable && !isPlayingAd)
@ -655,7 +669,7 @@ class MPVPlayer(
tracks = Tracks.EMPTY
playbackParameters = PlaybackParameters.DEFAULT
initialCommands.clear()
//initialSeekTo = 0L
// initialSeekTo = 0L
}
/** Prepares the player. */
@ -1212,8 +1226,7 @@ class MPVPlayer(
MPVLib.setOptionString("panscan", "1")
MPVLib.setOptionString("sub-use-margins", "yes")
MPVLib.setOptionString("sub-ass-force-margins", "yes")
}
else {
} else {
MPVLib.setOptionString("panscan", "0")
MPVLib.setOptionString("sub-use-margins", "no")
MPVLib.setOptionString("sub-ass-force-margins", "no")
@ -1409,7 +1422,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentVideo }
)
})
}
)
}
if (trackListAudio.isNotEmpty()) {
trackGroups.add(
@ -1420,7 +1434,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentAudio }
)
})
}
)
}
if (trackListText.isNotEmpty()) {
trackGroups.add(
@ -1431,7 +1446,8 @@ class MPVPlayer(
IntArray(this.length) { C.FORMAT_HANDLED },
BooleanArray(this.length) { it == indexCurrentText }
)
})
}
)
}
if (trackGroups.isNotEmpty()) {
tracks = Tracks(trackGroups)

View file

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

View file

@ -1,11 +1,14 @@
package dev.jdtech.jellyfin.repository
import androidx.paging.PagingData
import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import kotlinx.coroutines.flow.Flow
import org.jellyfin.sdk.model.api.*
import java.util.*
import org.jellyfin.sdk.model.api.BaseItemDto
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 {
suspend fun getUserViews(): List<BaseItemDto>

View file

@ -5,12 +5,25 @@ import androidx.paging.PagingConfig
import androidx.paging.PagingData
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
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 java.util.*
class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRepository {
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> =
withContext(Dispatchers.IO) {
jellyfinApi.mediaInfoApi.getPostedPlaybackInfo(
itemId, PlaybackInfoDto(
itemId,
PlaybackInfoDto(
userId = jellyfinApi.userId!!,
deviceProfile = DeviceProfile(
name = "Direct play all",
@ -220,7 +234,8 @@ class JellyfinRepositoryImpl(private val jellyfinApi: JellyfinApi) : JellyfinRep
GeneralCommandType.PLAY_STATE,
GeneralCommandType.PLAY_NEXT,
GeneralCommandType.PLAY_MEDIA_SOURCE
), supportsMediaControl = true
),
supportsMediaControl = true
)
}
}

View file

@ -70,13 +70,15 @@ internal class TvPlayerActivity : BasePlayerActivity() {
when {
viewModel.player.isPlaying -> {
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
else -> {
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.fragment.app.viewModels
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.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
@ -146,15 +150,21 @@ internal class HomeFragment : BrowseSupportFragment() {
adapterMap[name]?.setItems(items, diffCallback)
} else {
adapterMap[name] = when (this) {
is HomeItem.Libraries -> ArrayObjectAdapter(LibaryItemPresenter { item ->
is HomeItem.Libraries -> ArrayObjectAdapter(
LibaryItemPresenter { 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)
}).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(MediaItemPresenter { item ->
}
).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(
MediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}).apply { setItems(items, diffCallback) }
}
).apply { setItems(items, diffCallback) }
}
}

View file

@ -76,7 +76,8 @@ internal class MediaDetailFragment : Fragment() {
val seasonsAdapter = ViewItemListAdapter(
fixedWidth = true,
onClickListener = ViewItemListAdapter.OnClickListener {})
onClickListener = ViewItemListAdapter.OnClickListener {}
)
binding.seasonsRow.gridView.adapter = seasonsAdapter
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.PlayerItem
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.BaseItemKind
import org.jellyfin.sdk.model.api.UserItemDataDto
import timber.log.Timber
import java.io.File
import java.util.UUID
var defaultStorage: File? = null
@ -76,7 +76,7 @@ fun loadDownloadLocation(context: Context) {
fun checkDownloadStatus(downloadDatabase: DownloadDatabaseDao, context: Context) {
val items = downloadDatabase.loadItems()
for (item in items) {
try{
try {
val query = DownloadManager.Query()
.setFilterById(item.downloadId!!)
val result = context.getSystemService<DownloadManager>()!!.query(query)
@ -156,7 +156,6 @@ fun deleteDownloadedEpisode(downloadDatabase: DownloadDatabaseDao, itemId: UUID)
} catch (e: Exception) {
Timber.e(e)
}
}
fun postDownloadPlaybackProgress(
@ -277,6 +276,5 @@ suspend fun syncPlaybackProgress(
} catch (e: Exception) {
Timber.e(e)
}
}
}

View file

@ -14,8 +14,8 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.StyledPlayerView
import dev.jdtech.jellyfin.PlayerActivity
import dev.jdtech.jellyfin.mpv.MPVPlayer
import timber.log.Timber
import kotlin.math.abs
import timber.log.Timber
class PlayerGestureHelper(
private val appPreferences: AppPreferences,
@ -45,7 +45,9 @@ class PlayerGestureHelper(
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 {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
@ -60,15 +62,17 @@ class PlayerGestureHelper(
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
}
else {
} else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
}
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 {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
@ -83,15 +87,19 @@ class PlayerGestureHelper(
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
}
else {
} else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
}
return true
}
@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))
return false
@ -100,7 +108,8 @@ class PlayerGestureHelper(
return if ((abs(currentEvent.x - firstEvent.x) > 50 || swipeGestureProgressOpen) &&
!swipeGestureBrightnessOpen &&
!swipeGestureVolumeOpen &&
(SystemClock.elapsedRealtime() - lastScaleEvent) > 200) {
(SystemClock.elapsedRealtime() - lastScaleEvent) > 200
) {
val currentPos = playerView.player?.currentPosition ?: 0
val vidDuration = (playerView.player?.duration ?: 0).coerceAtLeast(0)
@ -140,7 +149,7 @@ class PlayerGestureHelper(
activity.binding.gestureVolumeLayout.visibility = View.VISIBLE
activity.binding.gestureVolumeProgressBar.max = maxVolume
activity.binding.gestureVolumeProgressBar.progress = toSet
activity.binding.gestureVolumeText.text = "${(toSet.toFloat()/maxVolume.toFloat()).times(100).toInt()}%"
activity.binding.gestureVolumeText.text = "${(toSet.toFloat() / maxVolume.toFloat()).times(100).toInt()}%"
swipeGestureVolumeOpen = true
} else {
@ -154,7 +163,7 @@ class PlayerGestureHelper(
Timber.d("Brightness ${Settings.System.getFloat(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS)}")
swipeGestureValueTrackerBrightness = when (brightness) {
in brightnessRange -> brightness
else -> Settings.System.getFloat(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS)/255
else -> Settings.System.getFloat(activity.contentResolver, Settings.System.SCREEN_BRIGHTNESS) / 255
}
}
swipeGestureValueTrackerBrightness = (swipeGestureValueTrackerBrightness + ratioChange).coerceIn(brightnessRange)
@ -165,13 +174,14 @@ class PlayerGestureHelper(
activity.binding.gestureBrightnessLayout.visibility = View.VISIBLE
activity.binding.gestureBrightnessProgressBar.max = BRIGHTNESS_OVERRIDE_FULL.times(100).toInt()
activity.binding.gestureBrightnessProgressBar.progress = lp.screenBrightness.times(100).toInt()
activity.binding.gestureBrightnessText.text = "${(lp.screenBrightness/BRIGHTNESS_OVERRIDE_FULL).times(100).toInt()}%"
activity.binding.gestureBrightnessText.text = "${(lp.screenBrightness / BRIGHTNESS_OVERRIDE_FULL).times(100).toInt()}%"
swipeGestureBrightnessOpen = true
}
return true
}
})
}
)
private val hideGestureVolumeIndicatorOverlayAction = Runnable {
activity.binding.gestureVolumeLayout.visibility = View.GONE
@ -191,7 +201,9 @@ class PlayerGestureHelper(
/**
* 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 onScale(detector: ScaleGestureDetector): Boolean {
@ -205,13 +217,13 @@ class PlayerGestureHelper(
}
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
}).apply { isQuickScaleEnabled = false }
}
).apply { isQuickScaleEnabled = false }
private fun updateZoomMode(enabled: Boolean) {
if (playerView.player is MPVPlayer) {
(playerView.player as MPVPlayer).updateZoomMode(enabled)
}
else {
} else {
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.ServerDatabaseDao
import dev.jdtech.jellyfin.models.DiscoveredServer
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import javax.inject.Inject
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.RecommendedServerInfoScore
import org.jellyfin.sdk.discovery.RecommendedServerIssue
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class AddServerViewModel
@ -54,11 +62,13 @@ constructor(
viewModelScope.launch(Dispatchers.IO) {
val servers = jellyfinApi.jellyfin.discovery.discoverLocalServers()
servers.collect { serverDiscoveryInfo ->
discoveredServers.add(DiscoveredServer(
discoveredServers.add(
DiscoveredServer(
serverDiscoveryInfo.id,
serverDiscoveryInfo.name,
serverDiscoveryInfo.address
))
)
)
_discoveredServersState.emit(
DiscoveredServersState.Servers(ArrayList(discoveredServers))
)
@ -131,7 +141,6 @@ constructor(
}
}
} catch (_: CancellationException) {
} catch (e: Exception) {
_uiState.emit(
UiState.Error(

View file

@ -5,10 +5,10 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.adapters.DownloadEpisodeItem
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class DownloadSeriesViewModel
@ -36,8 +36,11 @@ constructor() : ViewModel() {
private fun getEpisodes(seriesMetadata: DownloadSeriesMetadata): List<DownloadEpisodeItem> {
val episodes = seriesMetadata.episodes
return listOf(DownloadEpisodeItem.Header) + episodes.sortedWith(compareBy(
return listOf(DownloadEpisodeItem.Header) + episodes.sortedWith(
compareBy(
{ 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
import android.app.Application
import androidx.lifecycle.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao
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.utils.checkDownloadStatus
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.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind
import java.util.*
import javax.inject.Inject
@HiltViewModel
class DownloadViewModel

View file

@ -1,12 +1,23 @@
package dev.jdtech.jellyfin.viewmodels
import android.app.Application
import androidx.lifecycle.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.PlayerItem
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.asStateFlow
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.api.BaseItemDto
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
class EpisodeBottomSheetViewModel

View file

@ -9,13 +9,13 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Constants
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind
import javax.inject.Inject
@HiltViewModel
class FavoriteViewModel
@ -56,7 +56,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_MOVIES,
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(
it
)
@ -64,7 +65,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_SHOWS,
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(
it
)
@ -72,7 +74,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_EPISODES,
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(
it
)

View file

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

View file

@ -1,10 +1,13 @@
package dev.jdtech.jellyfin.viewmodels
import androidx.lifecycle.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.SortBy
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
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.SortOrder
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@HiltViewModel
class LibraryViewModel

View file

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

View file

@ -7,7 +7,14 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.database.DownloadDatabaseDao
import dev.jdtech.jellyfin.models.PlayerItem
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.flow.MutableStateFlow
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.BaseItemPerson
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
class MediaInfoViewModel

View file

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

View file

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

View file

@ -8,7 +8,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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 dagger.hilt.android.lifecycle.HiltViewModel
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.utils.AppPreferences
import dev.jdtech.jellyfin.utils.postDownloadPlaybackProgress
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
class PlayerActivityViewModel
@ -133,8 +137,8 @@ constructor(
player.setMediaItems(mediaItems, currentMediaItemIndex, items.getOrNull(currentMediaItemIndex)?.playbackPosition ?: C.TIME_UNSET)
val useMpv = sp.getBoolean("mpv_player", false)
if(!useMpv || !playFromDownloads)
player.prepare() //TODO: This line causes a crash when playing from downloads with MPV
if (!useMpv || !playFromDownloads)
player.prepare() // TODO: This line causes a crash when playing from downloads with MPV
player.play()
pollPosition(player)
}
@ -167,8 +171,8 @@ constructor(
override fun run() {
viewModelScope.launch {
if (player.currentMediaItem != null && player.currentMediaItem!!.mediaId.isNotEmpty()) {
if(playFromDownloads){
postDownloadPlaybackProgress(downloadDatabase, items[0].itemId, player.currentPosition, (player.currentPosition.toDouble()/player.duration.toDouble()).times(100)) //TODO Automatically use the correct item
if (playFromDownloads) {
postDownloadPlaybackProgress(downloadDatabase, items[0].itemId, player.currentPosition, (player.currentPosition.toDouble() / player.duration.toDouble()).times(100)) // TODO Automatically use the correct item
}
try {
jellyfinRepository.postPlaybackProgress(

View file

@ -14,6 +14,7 @@ import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.getDownloadPlayerItem
import dev.jdtech.jellyfin.utils.isItemAvailable
import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
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.MediaStreamType
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
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.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Constants
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind
import javax.inject.Inject
@HiltViewModel
class SearchResultViewModel
@ -52,7 +52,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_MOVIES,
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(
it
)
@ -60,7 +61,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_SHOWS,
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(
it
)
@ -68,7 +70,8 @@ constructor(
FavoriteSection(
Constants.FAVORITE_TYPE_EPISODES,
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(
it
)

View file

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

View file

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

View file

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