Introduce klint (#186)
* Add ktlint plugin * Make code ktlint compliant * Make code ktlint compliant
This commit is contained in:
parent
45ccea57af
commit
ad5e722d44
105 changed files with 682 additions and 524 deletions
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,4 +27,4 @@ class BaseApplication : Application() {
|
|||
|
||||
if (appPreferences.dynamicColors) DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,4 +50,4 @@ class CollectionListAdapter(
|
|||
class OnClickListener(val clickListener: (collection: BaseItemDto) -> Unit) {
|
||||
fun onClick(collection: BaseItemDto) = clickListener(collection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,4 +61,4 @@ class DownloadsListAdapter(
|
|||
val collection = getItem(position)
|
||||
holder.bind(collection, onClickListener, onSeriesClickListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,4 +61,4 @@ class FavoritesListAdapter(
|
|||
val collection = getItem(position)
|
||||
holder.bind(collection, onClickListener, onEpisodeClickListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,4 +47,4 @@ class UserListAdapter(
|
|||
holder.itemView.setOnClickListener { clickListener(user) }
|
||||
holder.bind(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,4 +13,4 @@ import dev.jdtech.jellyfin.models.DownloadItem
|
|||
@TypeConverters(Converters::class)
|
||||
abstract class DownloadDatabase : RoomDatabase() {
|
||||
abstract val downloadDatabaseDao: DownloadDatabaseDao
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ data class Server(
|
|||
val userId: String,
|
||||
val userName: String,
|
||||
val accessToken: String,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -6,4 +6,4 @@ import androidx.room.RoomDatabase
|
|||
@Database(entities = [Server::class], version = 1, exportSchema = false)
|
||||
abstract class ServerDatabase : RoomDatabase() {
|
||||
abstract val serverDatabaseDao: ServerDatabaseDao
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,4 +36,4 @@ object ApiModule {
|
|||
|
||||
return jellyfinApi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,4 +17,4 @@ object AppModule {
|
|||
fun provideApplication(@ApplicationContext app: Context): BaseApplication {
|
||||
return app as BaseApplication
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,4 +43,4 @@ object DatabaseModule {
|
|||
.build()
|
||||
.downloadDatabaseDao
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,4 @@ class GlideModule : AppGlideModule() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,4 +19,4 @@ object RepositoryModule {
|
|||
): JellyfinRepository {
|
||||
return JellyfinRepositoryImpl(jellyfinApi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,5 @@ class SpeedSelectionDialogFragment(
|
|||
}
|
||||
builder.create()
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,4 +146,4 @@ internal class PersonDetailFragment : Fragment() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,4 @@ class SettingsAppearanceFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,4 @@ class SettingsCacheFragment : PreferenceFragmentCompat() {
|
|||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,4 @@ class SettingsDeviceFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@ class SettingsDownloadsFragment : PreferenceFragmentCompat() {
|
|||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.fragment_settings_downloads, rootKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@ class SettingsLanguageFragment : PreferenceFragmentCompat() {
|
|||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.fragment_settings_language, rootKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,4 @@ class SettingsPlayerFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@ class TwoPaneSettingsFragment : PreferenceHeaderFragmentCompat() {
|
|||
override fun onCreatePreferenceHeader(): PreferenceFragmentCompat {
|
||||
return SettingsFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,4 +13,4 @@ enum class CollectionType(val type: String) {
|
|||
HomeVideos, Music, Playlists, Books, LiveTv, BoxSets
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,4 @@ data class DiscoveredServer(
|
|||
val id: String,
|
||||
val name: String,
|
||||
val address: String
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,4 +10,4 @@ class ExternalSubtitle(
|
|||
val language: String,
|
||||
val uri: Uri,
|
||||
val mimeType: String,
|
||||
) : Parcelable
|
||||
) : Parcelable
|
||||
|
|
|
@ -6,4 +6,4 @@ data class FavoriteSection(
|
|||
val id: Int,
|
||||
val name: String,
|
||||
var items: List<BaseItemDto>
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,4 +5,4 @@ import java.util.UUID
|
|||
data class User(
|
||||
val id: UUID,
|
||||
val name: String
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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?
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,4 +105,4 @@ class DynamicMediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Pr
|
|||
}
|
||||
|
||||
override fun onUnbindViewHolder(viewHolder: ViewHolder) = Unit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,4 +68,4 @@ class TrackSelectorAdapter(
|
|||
val selected: Boolean,
|
||||
val playerTrack: MPVPlayer.Companion.Track
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,4 +27,4 @@ object Constants {
|
|||
const val FAVORITE_TYPE_MOVIES = 0
|
||||
const val FAVORITE_TYPE_SHOWS = 1
|
||||
const val FAVORITE_TYPE_EPISODES = 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,4 +19,4 @@ enum class SortBy(val SortString: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,4 @@ import androidx.lifecycle.ViewModel
|
|||
class MainViewModel : ViewModel() {
|
||||
var startDestinationChanged = false
|
||||
var startDestinationTvChanged = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue