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")
@ -131,4 +137,4 @@ dependencies {
val pagingVersion = "3.1.1"
implementation("androidx.paging:paging-runtime-ktx:$pagingVersion")
}
}

View file

@ -27,4 +27,4 @@ class BaseApplication : Application() {
if (appPreferences.dynamicColors) DynamicColors.applyToActivitiesIfAvailable(this)
}
}
}

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
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)
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
)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@ -66,4 +68,4 @@ abstract class BasePlayerActivity: AppCompatActivity() {
}
}
}
}
}

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
@ -134,4 +138,4 @@ private fun View.posterDescription(name: String?) {
private fun View.backdropDescription(name: String?) {
contentDescription =
String.format(context.resources.getString(R.string.image_description_backdrop), name)
}
}

View file

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

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

@ -50,4 +50,4 @@ class CollectionListAdapter(
class OnClickListener(val clickListener: (collection: BaseItemDto) -> Unit) {
fun onClick(collection: BaseItemDto) = clickListener(collection)
}
}
}

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
) {
@ -55,4 +56,4 @@ class DiscoveredServerListAdapter(
holder.itemView.setOnClickListener { clickListener(server) }
holder.bind(server)
}
}
}

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
@ -125,4 +125,4 @@ sealed class DownloadEpisodeItem {
data class Episode(val episode: PlayerItem) : DownloadEpisodeItem() {
override val id = episode.itemId
}
}
}

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
)
}
@ -62,4 +68,4 @@ class DownloadSeriesListAdapter(
class OnClickListener(val clickListener: (item: DownloadSeriesMetadata) -> Unit) {
fun onClick(item: DownloadSeriesMetadata) = clickListener(item)
}
}
}

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
)
}
@ -63,4 +63,4 @@ class DownloadViewItemListAdapter(
class OnClickListener(val clickListener: (item: PlayerItem) -> Unit) {
fun onClick(item: PlayerItem) = clickListener(item)
}
}
}

View file

@ -61,4 +61,4 @@ class DownloadsListAdapter(
val collection = getItem(position)
holder.bind(collection, onClickListener, onSeriesClickListener)
}
}
}

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
@ -129,4 +129,4 @@ sealed class EpisodeItem {
data class Episode(val episode: BaseItemDto) : EpisodeItem() {
override val id = episode.id
}
}
}

View file

@ -61,4 +61,4 @@ class FavoritesListAdapter(
val collection = getItem(position)
holder.bind(collection, onClickListener, onEpisodeClickListener)
}
}
}

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
}
@ -64,4 +66,4 @@ class HomeEpisodeListAdapter(private val onClickListener: OnClickListener) : Lis
class OnClickListener(val clickListener: (item: BaseItemDto) -> Unit) {
fun onClick(item: BaseItemDto) = clickListener(item)
}
}
}

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) {
@ -43,4 +43,4 @@ class PersonListAdapter(private val clickListener: (item: BaseItemPerson) -> Uni
holder.bind(item)
holder.itemView.setOnClickListener { clickListener(item) }
}
}
}

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) {
@ -55,4 +55,4 @@ class ServerGridAdapter(
class OnLongClickListener(val clickListener: (server: Server) -> Boolean) {
fun onLongClick(server: Server) = clickListener(server)
}
}
}

View file

@ -47,4 +47,4 @@ class UserListAdapter(
holder.itemView.setOnClickListener { clickListener(user) }
holder.bind(user)
}
}
}

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
)
}
@ -64,4 +64,4 @@ class ViewItemListAdapter(
class OnClickListener(val clickListener: (item: BaseItemDto) -> Unit) {
fun onClick(item: BaseItemDto) = clickListener(item)
}
}
}

View file

@ -49,7 +49,8 @@ class ViewItemPagingAdapter(
LayoutInflater.from(parent.context),
parent,
false
), parent
),
parent
)
}
@ -66,4 +67,4 @@ class ViewItemPagingAdapter(
class OnClickListener(val clickListener: (item: BaseItemDto) -> Unit) {
fun onClick(item: BaseItemDto) = clickListener(item)
}
}
}

View file

@ -65,7 +65,8 @@ class ViewListAdapter(
NextUpSectionBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
),
parent, false
)
)
ITEM_VIEW_TYPE_VIEW -> ViewViewHolder(
@ -119,4 +120,4 @@ sealed class HomeItem {
}
abstract val id: UUID
}
}

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
@ -59,4 +59,4 @@ class JellyfinApi(androidContext: Context) {
}
}
}
}
}

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
@ -13,4 +13,4 @@ class Converters {
fun fromUUIDToString(value: UUID?): String? {
return value?.toString()
}
}
}

View file

@ -13,4 +13,4 @@ import dev.jdtech.jellyfin.models.DownloadItem
@TypeConverters(Converters::class)
abstract class DownloadDatabase : RoomDatabase() {
abstract val downloadDatabaseDao: DownloadDatabaseDao
}
}

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

@ -12,4 +12,4 @@ data class Server(
val userId: String,
val userName: String,
val accessToken: String,
)
)

View file

@ -6,4 +6,4 @@ import androidx.room.RoomDatabase
@Database(entities = [Server::class], version = 1, exportSchema = false)
abstract class ServerDatabase : RoomDatabase() {
abstract val serverDatabaseDao: ServerDatabaseDao
}
}

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 {
@ -28,4 +32,4 @@ interface ServerDatabaseDao {
@Query("delete from servers where id = :id")
fun delete(id: String)
}
}

View file

@ -36,4 +36,4 @@ object ApiModule {
return jellyfinApi
}
}
}

View file

@ -17,4 +17,4 @@ object AppModule {
fun provideApplication(@ApplicationContext app: Context): BaseApplication {
return app as BaseApplication
}
}
}

View file

@ -43,4 +43,4 @@ object DatabaseModule {
.build()
.downloadDatabaseDao
}
}
}

View file

@ -46,4 +46,4 @@ class GlideModule : AppGlideModule() {
)
)
}
}
}

View file

@ -19,4 +19,4 @@ object RepositoryModule {
): JellyfinRepository {
return JellyfinRepositoryImpl(jellyfinApi)
}
}
}

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,9 +19,8 @@ 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,9 +28,8 @@ 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,
@ -84,4 +84,4 @@ class SortDialogFragment(
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
}

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]
@ -63,4 +65,4 @@ class TrackSelectionDialogFragment(
nameParts.joinToString(separator = " - ")
}.toTypedArray()
}
}
}

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(
@ -24,4 +24,4 @@ class VideoVersionDialogFragment(
}.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
}

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
@ -154,4 +157,4 @@ class AddServerFragment : Fragment() {
private fun navigateToLoginFragment() {
findNavController().navigate(AddServerFragmentDirections.actionAddServerFragmentToLoginFragment())
}
}
}

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,18 +34,21 @@ 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(
DownloadViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item)
}, DownloadSeriesListAdapter.OnClickListener { item ->
navigateToDownloadSeriesFragment(item)
}
)
binding.downloadsRecyclerView.adapter =
DownloadsListAdapter(
DownloadViewItemListAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item)
},
DownloadSeriesListAdapter.OnClickListener { item ->
navigateToDownloadSeriesFragment(item)
}
)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -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,9 +110,8 @@ 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 ->
navigateToEpisodeBottomSheetFragment(episode)
}, args.seriesMetadata)
DownloadEpisodeListAdapter(
DownloadEpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode)
},
args.seriesMetadata
)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -94,4 +98,4 @@ class DownloadSeriesFragment : Fragment() {
)
)
}
}
}

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,
@ -275,4 +274,4 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
)
)
}
}
}

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) {
@ -110,4 +113,4 @@ class FavoriteFragment : Fragment() {
)
)
}
}
}

View file

@ -57,21 +57,24 @@ class HomeFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.home_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_settings -> {
navigateToSettingsFragment()
true
}
else -> false
menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.home_menu, menu)
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_settings -> {
navigateToSettingsFragment()
true
}
else -> false
}
}
},
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()
@ -190,4 +194,4 @@ class HomeFragment : Fragment() {
HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
)
}
}
}

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 ->
navigateToMediaInfoFragment(item)
})
ViewItemPagingAdapter(
ViewItemPagingAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item)
}
)
(binding.itemsRecyclerView.adapter as ViewItemPagingAdapter).addLoadStateListener {
when (it.refresh) {
@ -214,4 +223,4 @@ class LibraryFragment : Fragment() {
)
}
}
}
}

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)
@ -165,4 +166,4 @@ class LoginFragment : Fragment() {
findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragment())
}
}
}
}

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 ->
navigateToLibraryFragment(library)
})
CollectionListAdapter(
CollectionListAdapter.OnClickListener { library ->
navigateToLibraryFragment(library)
}
)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -73,32 +81,35 @@ class MediaFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.media_menu, menu)
menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.media_menu, menu)
val search = menu.findItem(R.id.action_search)
val searchView = search.actionView as SearchView
searchView.queryHint = getString(R.string.search_hint)
val search = menu.findItem(R.id.action_search)
val searchView = search.actionView as SearchView
searchView.queryHint = getString(R.string.search_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(p0: String?): Boolean {
if (p0 != null) {
navigateToSearchResultFragment(p0)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(p0: String?): Boolean {
if (p0 != null) {
navigateToSearchResultFragment(p0)
}
return true
}
return true
}
override fun onQueryTextChange(p0: String?): Boolean {
return false
}
})
}
override fun onQueryTextChange(p0: String?): Boolean {
return false
}
})
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return true
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return true
}
},
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) {
@ -151,4 +161,4 @@ class MediaFragment : Fragment() {
MediaFragmentDirections.actionNavigationMediaToSearchResultFragment(query)
)
}
}
}

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 ->
navigateToSeasonFragment(season)
}, fixedWidth = true)
ViewItemListAdapter(
ViewItemListAdapter.OnClickListener { season ->
navigateToSeasonFragment(season)
},
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()) {
@ -363,4 +364,4 @@ class MediaInfoFragment : Fragment() {
MediaInfoFragmentDirections.actionMediaInfoFragmentToPersonDetailFragment(personId)
)
}
}
}

View file

@ -146,4 +146,4 @@ internal class PersonDetailFragment : Fragment() {
)
)
}
}
}

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
}
@ -119,4 +121,4 @@ class SearchResultFragment : Fragment() {
)
)
}
}
}

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 ->
navigateToEpisodeBottomSheetFragment(episode)
}, args.seriesId, args.seriesName, args.seasonId, args.seasonName)
EpisodeListAdapter(
EpisodeListAdapter.OnClickListener { episode ->
navigateToEpisodeBottomSheetFragment(episode)
},
args.seriesId, args.seriesName, args.seasonId, args.seasonName
)
}
private fun bindUiStateNormal(uiState: SeasonViewModel.UiState.Normal) {
@ -106,4 +109,4 @@ class SeasonFragment : Fragment() {
)
)
}
}
}

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 ->
viewModel.connectToServer(server)
}, ServerGridAdapter.OnLongClickListener { server ->
DeleteServerDialogFragment(viewModel, server).show(
parentFragmentManager,
"deleteServer"
)
true
})
ServerGridAdapter(
ServerGridAdapter.OnClickListener { server ->
viewModel.connectToServer(server)
},
ServerGridAdapter.OnLongClickListener { server ->
DeleteServerDialogFragment(viewModel, server).show(
parentFragmentManager,
"deleteServer"
)
true
}
)
binding.buttonAddServer.setOnClickListener {
navigateToAddServerFragment()
@ -70,4 +74,4 @@ class ServerSelectFragment : Fragment() {
private fun navigateToMainActivity() {
findNavController().navigate(ServerSelectFragmentDirections.actionServerSelectFragmentToHomeFragment())
}
}
}

View file

@ -20,4 +20,4 @@ class SettingsAppearanceFragment : PreferenceFragmentCompat() {
true
}
}
}
}

View file

@ -15,4 +15,4 @@ class SettingsCacheFragment : PreferenceFragmentCompat() {
editText.inputType = InputType.TYPE_CLASS_NUMBER
}
}
}
}

View file

@ -22,4 +22,4 @@ class SettingsDeviceFragment : PreferenceFragmentCompat() {
true
}
}
}
}

View file

@ -9,4 +9,4 @@ class SettingsDownloadsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.fragment_settings_downloads, rootKey)
}
}
}

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)
@ -31,4 +31,4 @@ class SettingsFragment: PreferenceFragmentCompat() {
true
}
}
}
}

View file

@ -9,4 +9,4 @@ class SettingsLanguageFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.fragment_settings_language, rootKey)
}
}
}

View file

@ -24,4 +24,4 @@ class SettingsPlayerFragment : PreferenceFragmentCompat() {
true
}
}
}
}

View file

@ -7,4 +7,4 @@ class TwoPaneSettingsFragment : PreferenceHeaderFragmentCompat() {
override fun onCreatePreferenceHeader(): PreferenceFragmentCompat {
return SettingsFragment()
}
}
}

View file

@ -13,4 +13,4 @@ enum class CollectionType(val type: String) {
HomeVideos, Music, Playlists, Books, LiveTv, BoxSets
)
}
}
}

View file

@ -4,4 +4,4 @@ data class DiscoveredServer(
val id: String,
val name: String,
val address: String
)
)

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")
@ -23,4 +23,4 @@ data class DownloadItem(
val playbackPosition: Long? = null,
val playedPercentage: Double? = null,
val downloadId: Long? = null,
) : Parcelable
) : Parcelable

View file

@ -1,10 +1,10 @@
package dev.jdtech.jellyfin.models
import java.util.*
import java.util.UUID
data class DownloadSection(
val id: UUID,
val name: String,
val items: List<PlayerItem>? = null,
val series: List<DownloadSeriesMetadata>? = null
)
)

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.models
import android.os.Parcelable
import java.util.UUID
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
data class DownloadSeriesMetadata(
val itemId: UUID,
val name: String? = null,
val episodes: List<PlayerItem>
) : Parcelable
) : Parcelable

View file

@ -10,4 +10,4 @@ class ExternalSubtitle(
val language: String,
val uri: Uri,
val mimeType: String,
) : Parcelable
) : Parcelable

View file

@ -6,4 +6,4 @@ data class FavoriteSection(
val id: Int,
val name: String,
var items: List<BaseItemDto>
)
)

View file

@ -1,10 +1,10 @@
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,
val name: String,
var items: List<BaseItemDto>
)
)

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(
@ -15,4 +15,4 @@ data class PlayerItem(
val indexNumber: Int? = null,
val item: DownloadItem? = null,
val externalSubtitles: List<ExternalSubtitle> = emptyList()
) : Parcelable
) : Parcelable

View file

@ -5,4 +5,4 @@ import java.util.UUID
data class User(
val id: UUID,
val name: String
)
)

View file

@ -1,11 +1,11 @@
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,
val name: String?,
var items: List<BaseItemDto>? = null,
val type: String?
)
)

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,
@ -25,14 +25,14 @@ class ItemsPagingSource(
return try {
val response = jellyfinApi.itemsApi.getItems(
jellyfinApi.userId!!,
parentId = parentId,
includeItemTypes = includeTypes,
recursive = recursive,
sortBy = listOf(sortBy.SortString),
sortOrder = listOf(sortOrder),
startIndex = position,
limit = params.loadSize
jellyfinApi.userId!!,
parentId = parentId,
includeItemTypes = includeTypes,
recursive = recursive,
sortBy = listOf(sortBy.SortString),
sortOrder = listOf(sortOrder),
startIndex = position,
limit = params.loadSize
).content.items.orEmpty()
LoadResult.Page(
data = response,
@ -47,4 +47,4 @@ class ItemsPagingSource(
override fun getRefreshKey(state: PagingState<Int, BaseItemDto>): Int {
return 0
}
}
}

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>
@ -78,4 +81,4 @@ interface JellyfinRepository {
fun getBaseUrl(): String
suspend fun updateDeviceName(name: String)
}
}

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)
)
}
}
@ -155,4 +157,4 @@ internal class TvPlayerActivity : BasePlayerActivity() {
return popup
}
}
}

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 ->
navigateToLibraryFragment(item)
}).apply { setItems(items, diffCallback) }
is HomeItem.Section -> ArrayObjectAdapter(DynamicMediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(MediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}).apply { setItems(items, diffCallback) }
is HomeItem.Libraries -> ArrayObjectAdapter(
LibaryItemPresenter { item ->
navigateToLibraryFragment(item)
}
).apply { setItems(items, diffCallback) }
is HomeItem.Section -> ArrayObjectAdapter(
DynamicMediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}
).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(
MediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}
).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
@ -254,4 +255,4 @@ internal class MediaDetailFragment : Fragment() {
)
)
}
}
}

View file

@ -105,4 +105,4 @@ class DynamicMediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Pr
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) = Unit
}
}

View file

@ -68,4 +68,4 @@ class TrackSelectorAdapter(
val selected: Boolean,
val playerTrack: MPVPlayer.Companion.Track
)
}
}

View file

@ -27,4 +27,4 @@ object Constants {
const val FAVORITE_TYPE_MOVIES = 0
const val FAVORITE_TYPE_SHOWS = 1
const val FAVORITE_TYPE_EPISODES = 2
}
}

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,15 +14,15 @@ 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,
private val activity: PlayerActivity,
private val playerView: StyledPlayerView,
private val audioManager: AudioManager
) {
) {
/**
* Tracks whether video content should fill the screen, cutting off unwanted content on the sides.
* Useful on wide-screen phones to remove black bars from some movies.
@ -45,133 +45,143 @@ class PlayerGestureHelper(
private var lastScaleEvent: Long = 0
private val tapGestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
}
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewCenterX = playerView.measuredWidth / 2
val currentPos = playerView.player?.currentPosition ?: 0
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
}
else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
}
return true
}
})
private val gestureDetector = GestureDetector(playerView.context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
}
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewCenterX = playerView.measuredWidth / 2
val currentPos = playerView.player?.currentPosition ?: 0
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
}
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 {
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
return false
// Check whether swipe was oriented vertically
if (abs(distanceY / distanceX) < 2) {
return if ((abs(currentEvent.x - firstEvent.x) > 50 || swipeGestureProgressOpen) &&
!swipeGestureBrightnessOpen &&
!swipeGestureVolumeOpen &&
(SystemClock.elapsedRealtime() - lastScaleEvent) > 200) {
val currentPos = playerView.player?.currentPosition ?: 0
val vidDuration = (playerView.player?.duration ?: 0).coerceAtLeast(0)
val difference = ((currentEvent.x - firstEvent.x) * 90).toLong()
val newPos = (currentPos + difference).coerceIn(0, vidDuration)
activity.binding.progressScrubberLayout.visibility = View.VISIBLE
activity.binding.progressScrubberText.text = "${longToTimestamp(difference)} [${longToTimestamp(newPos, true)}]"
swipeGestureValueTrackerProgress = newPos
swipeGestureProgressOpen = true
true
} else false
}
if (swipeGestureValueTrackerProgress > -1 || swipeGestureProgressOpen)
return false
val viewCenterX = playerView.measuredWidth / 2
// Distance to swipe to go from min to max
val distanceFull = playerView.measuredHeight * Constants.FULL_SWIPE_RANGE_SCREEN_RATIO
val ratioChange = distanceY / distanceFull
if (firstEvent.x.toInt() > viewCenterX) {
// Swiping on the right, change volume
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (swipeGestureValueTrackerVolume == -1f) swipeGestureValueTrackerVolume = currentVolume.toFloat()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val change = ratioChange * maxVolume
swipeGestureValueTrackerVolume += change
val toSet = swipeGestureValueTrackerVolume.toInt().coerceIn(0, maxVolume)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, toSet, 0)
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()}%"
swipeGestureVolumeOpen = true
} else {
// Swiping on the left, change brightness
val window = activity.window
val brightnessRange = BRIGHTNESS_OVERRIDE_OFF..BRIGHTNESS_OVERRIDE_FULL
// Initialize on first swipe
if (swipeGestureValueTrackerBrightness == -1f) {
val brightness = window.attributes.screenBrightness
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
}
private val tapGestureDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
}
swipeGestureValueTrackerBrightness = (swipeGestureValueTrackerBrightness + ratioChange).coerceIn(brightnessRange)
val lp = window.attributes
lp.screenBrightness = swipeGestureValueTrackerBrightness
window.attributes = lp
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()}%"
swipeGestureBrightnessOpen = true
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewCenterX = playerView.measuredWidth / 2
val currentPos = playerView.player?.currentPosition ?: 0
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
} else {
playerView.player?.seekTo((currentPos - appPreferences.playerSeekBackIncrement).coerceAtLeast(0))
}
return true
}
return true
}
})
)
private val gestureDetector = GestureDetector(
playerView.context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
playerView.apply {
if (!isControllerFullyVisible) showController() else hideController()
}
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val viewCenterX = playerView.measuredWidth / 2
val currentPos = playerView.player?.currentPosition ?: 0
if (e.x.toInt() > viewCenterX) {
playerView.player?.seekTo(currentPos + appPreferences.playerSeekForwardIncrement)
} 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 {
if (firstEvent.y < playerView.resources.dip(Constants.GESTURE_EXCLUSION_AREA_TOP))
return false
// Check whether swipe was oriented vertically
if (abs(distanceY / distanceX) < 2) {
return if ((abs(currentEvent.x - firstEvent.x) > 50 || swipeGestureProgressOpen) &&
!swipeGestureBrightnessOpen &&
!swipeGestureVolumeOpen &&
(SystemClock.elapsedRealtime() - lastScaleEvent) > 200
) {
val currentPos = playerView.player?.currentPosition ?: 0
val vidDuration = (playerView.player?.duration ?: 0).coerceAtLeast(0)
val difference = ((currentEvent.x - firstEvent.x) * 90).toLong()
val newPos = (currentPos + difference).coerceIn(0, vidDuration)
activity.binding.progressScrubberLayout.visibility = View.VISIBLE
activity.binding.progressScrubberText.text = "${longToTimestamp(difference)} [${longToTimestamp(newPos, true)}]"
swipeGestureValueTrackerProgress = newPos
swipeGestureProgressOpen = true
true
} else false
}
if (swipeGestureValueTrackerProgress > -1 || swipeGestureProgressOpen)
return false
val viewCenterX = playerView.measuredWidth / 2
// Distance to swipe to go from min to max
val distanceFull = playerView.measuredHeight * Constants.FULL_SWIPE_RANGE_SCREEN_RATIO
val ratioChange = distanceY / distanceFull
if (firstEvent.x.toInt() > viewCenterX) {
// Swiping on the right, change volume
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
if (swipeGestureValueTrackerVolume == -1f) swipeGestureValueTrackerVolume = currentVolume.toFloat()
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val change = ratioChange * maxVolume
swipeGestureValueTrackerVolume += change
val toSet = swipeGestureValueTrackerVolume.toInt().coerceIn(0, maxVolume)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, toSet, 0)
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()}%"
swipeGestureVolumeOpen = true
} else {
// Swiping on the left, change brightness
val window = activity.window
val brightnessRange = BRIGHTNESS_OVERRIDE_OFF..BRIGHTNESS_OVERRIDE_FULL
// Initialize on first swipe
if (swipeGestureValueTrackerBrightness == -1f) {
val brightness = window.attributes.screenBrightness
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
}
}
swipeGestureValueTrackerBrightness = (swipeGestureValueTrackerBrightness + ratioChange).coerceIn(brightnessRange)
val lp = window.attributes
lp.screenBrightness = swipeGestureValueTrackerBrightness
window.attributes = lp
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()}%"
swipeGestureBrightnessOpen = true
}
return true
}
}
)
private val hideGestureVolumeIndicatorOverlayAction = Runnable {
activity.binding.gestureVolumeLayout.visibility = View.GONE
@ -191,27 +201,29 @@ class PlayerGestureHelper(
/**
* Handles scale/zoom gesture
*/
private val zoomGestureDetector = ScaleGestureDetector(playerView.context, object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
private val zoomGestureDetector = ScaleGestureDetector(
playerView.context,
object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
override fun onScale(detector: ScaleGestureDetector): Boolean {
lastScaleEvent = SystemClock.elapsedRealtime()
val scaleFactor = detector.scaleFactor
if (abs(scaleFactor - Constants.ZOOM_SCALE_BASE) > Constants.ZOOM_SCALE_THRESHOLD) {
isZoomEnabled = scaleFactor > 1
updateZoomMode(isZoomEnabled)
override fun onScale(detector: ScaleGestureDetector): Boolean {
lastScaleEvent = SystemClock.elapsedRealtime()
val scaleFactor = detector.scaleFactor
if (abs(scaleFactor - Constants.ZOOM_SCALE_BASE) > Constants.ZOOM_SCALE_THRESHOLD) {
isZoomEnabled = scaleFactor > 1
updateZoomMode(isZoomEnabled)
}
return true
}
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
}).apply { isQuickScaleEnabled = false }
override fun onScaleEnd(detector: ScaleGestureDetector) = Unit
}
).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

@ -19,4 +19,4 @@ enum class SortBy(val SortString: String) {
}
}
}
}
}

View file

@ -23,7 +23,7 @@ fun BaseItemDto.toView(): View {
fun Fragment.checkIfLoginRequired(error: String?) {
if (error != null) {
if (error.contains("401")) {
if (error.contains("401")) {
Timber.d("Login required!")
findNavController().navigate(AppNavigationDirections.actionGlobalLoginFragment())
}
@ -50,4 +50,4 @@ fun ImageButton.setTintColorAttribute(@AttrRes attributeId: Int, theme: Resource
theme
)
)
}
}

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(
serverDiscoveryInfo.id,
serverDiscoveryInfo.name,
serverDiscoveryInfo.address
))
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(
@ -215,4 +224,4 @@ constructor(
if (server != null) return true
return false
}
}
}

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(
{ it.item!!.parentIndexNumber },
{ it.item!!.indexNumber })).map { DownloadEpisodeItem.Episode(it) }
return listOf(DownloadEpisodeItem.Header) + episodes.sortedWith(
compareBy(
{ it.item!!.parentIndexNumber },
{ 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
@ -78,4 +80,4 @@ constructor(
}
}
}
}
}

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
@ -178,4 +184,4 @@ constructor(
val date = Date.from(instant)
return DateFormat.getDateInstance(DateFormat.SHORT).format(date)
}
}
}

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
)
@ -85,4 +88,4 @@ constructor(
}
}
}
}
}

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
@ -60,4 +61,4 @@ constructor(
}
}
}
}
}

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
@ -129,4 +130,4 @@ constructor(
database.insert(server)
}
}
}
}

View file

@ -5,4 +5,4 @@ import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
var startDestinationChanged = false
var startDestinationTvChanged = false
}
}

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
@ -267,4 +272,4 @@ constructor(
fun deleteItem() {
deleteDownloadedEpisode(downloadDatabase, playerItem.itemId)
}
}
}

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
@ -45,4 +46,4 @@ constructor(
}
}
}
}
}

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(
@ -66,4 +66,4 @@ internal class PersonDetailViewModel @Inject internal constructor(
val movies: List<BaseItemDto>,
val shows: List<BaseItemDto>
)
}
}

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(
@ -265,4 +269,4 @@ constructor(
player.setPlaybackSpeed(speed)
playbackSpeed = speed
}
}
}

Some files were not shown because too many files have changed in this diff Show more