Modularize the codebase (#230)
* Split app into core and app:phone * Use global versionCode and versionName * Clean up gradle dependencies * Use string formatting inside getString function * Move proguard file to app:phone * Move app_navigation and BasePlayerActivity to app:phone * Add buildTypes to core gradle and remove buildFeatures * Add suffix core to core namespace * Split code into 4 more modules: data, preferences, player:core and player:video * Clean up some gradle files * Clean up data gradle * Remove duplicate Constants.kt * Use AppPreferences in more places * Split off strings * Remove unused animations * Make about_libraries strings non-translatable * Move mpv assets to player:video module * Make AppPreferences a Singleton
This commit is contained in:
parent
25efbb6eab
commit
76121925d7
308 changed files with 795 additions and 501 deletions
1
app/.gitignore
vendored
1
app/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
/build
|
|
|
@ -19,8 +19,12 @@ android {
|
||||||
applicationId = "dev.jdtech.jellyfin"
|
applicationId = "dev.jdtech.jellyfin"
|
||||||
minSdk = 27
|
minSdk = 27
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
versionCode = 14
|
|
||||||
versionName = "0.8.0"
|
val appVersionCode: Int by rootProject.extra
|
||||||
|
val appVersionName: String by rootProject.extra
|
||||||
|
|
||||||
|
versionCode = appVersionCode
|
||||||
|
versionName = appVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -68,6 +72,11 @@ ktlint {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(":core"))
|
||||||
|
implementation(project(":data"))
|
||||||
|
implementation(project(":preferences"))
|
||||||
|
implementation(project(":player:core"))
|
||||||
|
implementation(project(":player:video"))
|
||||||
implementation(libs.aboutlibraries.core)
|
implementation(libs.aboutlibraries.core)
|
||||||
implementation(libs.aboutlibraries)
|
implementation(libs.aboutlibraries)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
|
@ -85,19 +94,13 @@ dependencies {
|
||||||
implementation(libs.androidx.preference)
|
implementation(libs.androidx.preference)
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.recyclerview)
|
||||||
implementation(libs.androidx.recyclerview.selection)
|
implementation(libs.androidx.recyclerview.selection)
|
||||||
implementation(libs.androidx.room.runtime)
|
|
||||||
kapt(libs.androidx.room.compiler)
|
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
implementation(libs.androidx.swiperefreshlayout)
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
implementation(libs.glide)
|
implementation(libs.glide)
|
||||||
kapt(libs.glide.compiler)
|
|
||||||
implementation(libs.hilt.android)
|
implementation(libs.hilt.android)
|
||||||
kapt(libs.hilt.compiler)
|
kapt(libs.hilt.compiler)
|
||||||
implementation(libs.jellyfin.core)
|
implementation(libs.jellyfin.core)
|
||||||
implementation(libs.libmpv)
|
compileOnly(libs.libmpv)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
|
|
||||||
// Media3 FFmpeg decoder
|
|
||||||
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
|
|
||||||
}
|
}
|
|
@ -2,14 +2,16 @@ package dev.jdtech.jellyfin
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import dev.jdtech.jellyfin.utils.AppPreferences
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class BaseApplication : Application() {
|
class BaseApplication : Application() {
|
||||||
|
@Inject
|
||||||
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
@ -17,8 +19,6 @@ class BaseApplication : Application() {
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
}
|
}
|
||||||
|
|
||||||
val appPreferences = AppPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
|
||||||
|
|
||||||
when (appPreferences.theme) {
|
when (appPreferences.theme) {
|
||||||
"system" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
"system" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
"light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
"light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
|
@ -13,7 +13,7 @@ abstract class BasePlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
abstract val viewModel: PlayerActivityViewModel
|
abstract val viewModel: PlayerActivityViewModel
|
||||||
|
|
||||||
lateinit var mediaSession: MediaSession
|
private lateinit var mediaSession: MediaSession
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
|
@ -131,11 +131,9 @@ private fun ImageView.loadImage(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun View.posterDescription(name: String?) {
|
private fun View.posterDescription(name: String?) {
|
||||||
contentDescription =
|
contentDescription = context.resources.getString(R.string.image_description_poster, name)
|
||||||
String.format(context.resources.getString(R.string.image_description_poster), name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun View.backdropDescription(name: String?) {
|
private fun View.backdropDescription(name: String?) {
|
||||||
contentDescription =
|
contentDescription = context.resources.getString(R.string.image_description_backdrop, name)
|
||||||
String.format(context.resources.getString(R.string.image_description_backdrop), name)
|
|
||||||
}
|
}
|
|
@ -15,7 +15,6 @@ import com.google.android.material.navigation.NavigationBarView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||||
import dev.jdtech.jellyfin.databinding.ActivityMainBinding
|
import dev.jdtech.jellyfin.databinding.ActivityMainBinding
|
||||||
import dev.jdtech.jellyfin.utils.AppPreferences
|
|
||||||
import dev.jdtech.jellyfin.utils.loadDownloadLocation
|
import dev.jdtech.jellyfin.utils.loadDownloadLocation
|
||||||
import dev.jdtech.jellyfin.viewmodels.MainViewModel
|
import dev.jdtech.jellyfin.viewmodels.MainViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -33,7 +32,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferences: AppPreferences
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
lateinit var navController: NavController
|
private lateinit var navController: NavController
|
||||||
|
|
||||||
@OptIn(NavigationUiSaveStateControl::class)
|
@OptIn(NavigationUiSaveStateControl::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
@ -18,7 +18,6 @@ import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
|
||||||
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
|
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
|
||||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||||
import dev.jdtech.jellyfin.mpv.TrackType
|
import dev.jdtech.jellyfin.mpv.TrackType
|
||||||
import dev.jdtech.jellyfin.utils.AppPreferences
|
|
||||||
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
||||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
|
@ -9,10 +9,10 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
|
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
|
||||||
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
|
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
|
||||||
|
import dev.jdtech.jellyfin.models.DownloadEpisodeItem
|
||||||
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
|
import dev.jdtech.jellyfin.models.DownloadSeriesMetadata
|
||||||
import dev.jdtech.jellyfin.models.PlayerItem
|
import dev.jdtech.jellyfin.models.PlayerItem
|
||||||
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
|
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
|
||||||
import java.util.UUID
|
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
|
|
||||||
private const val ITEM_VIEW_TYPE_HEADER = 0
|
private const val ITEM_VIEW_TYPE_HEADER = 0
|
||||||
|
@ -113,16 +113,4 @@ class DownloadEpisodeListAdapter(
|
||||||
class OnClickListener(val clickListener: (item: PlayerItem) -> Unit) {
|
class OnClickListener(val clickListener: (item: PlayerItem) -> Unit) {
|
||||||
fun onClick(item: PlayerItem) = clickListener(item)
|
fun onClick(item: PlayerItem) = clickListener(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class DownloadEpisodeItem {
|
|
||||||
abstract val id: UUID
|
|
||||||
|
|
||||||
object Header : DownloadEpisodeItem() {
|
|
||||||
override val id: UUID = UUID.randomUUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Episode(val episode: PlayerItem) : DownloadEpisodeItem() {
|
|
||||||
override val id = episode.itemId
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,7 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
|
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
|
||||||
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
|
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
|
||||||
|
import dev.jdtech.jellyfin.models.EpisodeItem
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
|
|
||||||
|
@ -118,15 +119,3 @@ class EpisodeListAdapter(
|
||||||
fun onClick(item: BaseItemDto) = clickListener(item)
|
fun onClick(item: BaseItemDto) = clickListener(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class EpisodeItem {
|
|
||||||
abstract val id: UUID
|
|
||||||
|
|
||||||
object Header : EpisodeItem() {
|
|
||||||
override val id: UUID = UUID.randomUUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Episode(val episode: BaseItemDto) : EpisodeItem() {
|
|
||||||
override val id = episode.id
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,9 +5,9 @@ import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import dev.jdtech.jellyfin.Constants
|
||||||
import dev.jdtech.jellyfin.databinding.FavoriteSectionBinding
|
import dev.jdtech.jellyfin.databinding.FavoriteSectionBinding
|
||||||
import dev.jdtech.jellyfin.models.FavoriteSection
|
import dev.jdtech.jellyfin.models.FavoriteSection
|
||||||
import dev.jdtech.jellyfin.utils.Constants
|
|
||||||
|
|
||||||
class FavoritesListAdapter(
|
class FavoritesListAdapter(
|
||||||
private val onClickListener: ViewItemListAdapter.OnClickListener,
|
private val onClickListener: ViewItemListAdapter.OnClickListener,
|
||||||
|
@ -30,6 +30,7 @@ class FavoritesListAdapter(
|
||||||
HomeEpisodeListAdapter(onEpisodeClickListener)
|
HomeEpisodeListAdapter(onEpisodeClickListener)
|
||||||
(binding.itemsRecyclerView.adapter as HomeEpisodeListAdapter).submitList(section.items)
|
(binding.itemsRecyclerView.adapter as HomeEpisodeListAdapter).submitList(section.items)
|
||||||
}
|
}
|
||||||
|
binding.sectionName.text = section.name.asString(binding.root.resources)
|
||||||
binding.executePendingBindings()
|
binding.executePendingBindings()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,9 +8,8 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import dev.jdtech.jellyfin.R
|
import dev.jdtech.jellyfin.R
|
||||||
import dev.jdtech.jellyfin.databinding.NextUpSectionBinding
|
import dev.jdtech.jellyfin.databinding.NextUpSectionBinding
|
||||||
import dev.jdtech.jellyfin.databinding.ViewItemBinding
|
import dev.jdtech.jellyfin.databinding.ViewItemBinding
|
||||||
import dev.jdtech.jellyfin.models.HomeSection
|
import dev.jdtech.jellyfin.models.HomeItem
|
||||||
import dev.jdtech.jellyfin.models.View
|
import dev.jdtech.jellyfin.models.View
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
private const val ITEM_VIEW_TYPE_NEXT_UP = 0
|
private const val ITEM_VIEW_TYPE_NEXT_UP = 0
|
||||||
private const val ITEM_VIEW_TYPE_VIEW = 1
|
private const val ITEM_VIEW_TYPE_VIEW = 1
|
||||||
|
@ -30,7 +29,7 @@ class ViewListAdapter(
|
||||||
) {
|
) {
|
||||||
val view = dataItem.view
|
val view = dataItem.view
|
||||||
binding.view = view
|
binding.view = view
|
||||||
binding.viewName.text = String.format(binding.viewName.context.resources.getString(R.string.latest_library), view.name)
|
binding.viewName.text = binding.viewName.context.resources.getString(R.string.latest_library, view.name)
|
||||||
binding.itemsRecyclerView.adapter =
|
binding.itemsRecyclerView.adapter =
|
||||||
ViewItemListAdapter(onItemClickListener, fixedWidth = true)
|
ViewItemListAdapter(onItemClickListener, fixedWidth = true)
|
||||||
binding.viewAll.setOnClickListener {
|
binding.viewAll.setOnClickListener {
|
||||||
|
@ -105,19 +104,3 @@ class ViewListAdapter(
|
||||||
fun onClick(view: View) = clickListener(view)
|
fun onClick(view: View) = clickListener(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class HomeItem {
|
|
||||||
data class Libraries(val section: HomeSection) : HomeItem() {
|
|
||||||
override val id = section.id
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Section(val homeSection: HomeSection) : HomeItem() {
|
|
||||||
override val id = homeSection.id
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ViewItem(val view: View) : HomeItem() {
|
|
||||||
override val id = view.id
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract val id: UUID
|
|
||||||
}
|
|
|
@ -99,7 +99,7 @@ class AddServerFragment : Fragment() {
|
||||||
binding.buttonConnect.isEnabled = true
|
binding.buttonConnect.isEnabled = true
|
||||||
binding.progressCircular.isVisible = false
|
binding.progressCircular.isVisible = false
|
||||||
binding.editTextServerAddressLayout.apply {
|
binding.editTextServerAddressLayout.apply {
|
||||||
error = uiState.message
|
error = uiState.message.joinToString { it.asString(resources) }
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -195,8 +195,8 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.episodeName.text = String.format(
|
binding.episodeName.text = getString(
|
||||||
getString(R.string.episode_name_extended),
|
R.string.episode_name_extended,
|
||||||
episode.parentIndexNumber,
|
episode.parentIndexNumber,
|
||||||
episode.indexNumber,
|
episode.indexNumber,
|
||||||
episode.name
|
episode.name
|
|
@ -1,6 +1,5 @@
|
||||||
package dev.jdtech.jellyfin.fragments
|
package dev.jdtech.jellyfin.fragments
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
|
@ -20,12 +19,13 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.R
|
import dev.jdtech.jellyfin.R
|
||||||
import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter
|
import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter
|
||||||
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
|
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
|
||||||
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
|
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
|
||||||
import dev.jdtech.jellyfin.dialogs.SortDialogFragment
|
import dev.jdtech.jellyfin.dialogs.SortDialogFragment
|
||||||
import dev.jdtech.jellyfin.utils.SortBy
|
import dev.jdtech.jellyfin.models.SortBy
|
||||||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||||
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
|
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
|
@ -44,7 +44,7 @@ class LibraryFragment : Fragment() {
|
||||||
private lateinit var errorDialog: ErrorDialogFragment
|
private lateinit var errorDialog: ErrorDialogFragment
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var sp: SharedPreferences
|
lateinit var preferences: AppPreferences
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -146,9 +146,9 @@ class LibraryFragment : Fragment() {
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
// Sorting options
|
// Sorting options
|
||||||
val sortBy = SortBy.fromString(sp.getString("sortBy", SortBy.defaultValue.name)!!)
|
val sortBy = SortBy.fromString(preferences.sortBy)
|
||||||
val sortOrder = try {
|
val sortOrder = try {
|
||||||
SortOrder.valueOf(sp.getString("sortOrder", SortOrder.ASCENDING.name)!!)
|
SortOrder.valueOf(preferences.sortOrder)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
SortOrder.ASCENDING
|
SortOrder.ASCENDING
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
|
import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
|
||||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||||
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
|
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
|
||||||
import dev.jdtech.jellyfin.utils.AppPreferences
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
|
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -119,7 +119,7 @@ class LoginFragment : Fragment() {
|
||||||
binding.buttonLogin.isEnabled = true
|
binding.buttonLogin.isEnabled = true
|
||||||
binding.progressCircular.isVisible = false
|
binding.progressCircular.isVisible = false
|
||||||
binding.editTextUsernameLayout.apply {
|
binding.editTextUsernameLayout.apply {
|
||||||
error = uiState.message
|
error = uiState.message.asString(resources)
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
}
|
}
|
||||||
binding.editTextPasswordLayout.isEnabled = true
|
binding.editTextPasswordLayout.isEnabled = true
|
|
@ -272,8 +272,8 @@ class MediaInfoFragment : Fragment() {
|
||||||
binding.writers.text = writersString
|
binding.writers.text = writersString
|
||||||
binding.description.text = item.overview
|
binding.description.text = item.overview
|
||||||
binding.nextUpLayout.isVisible = nextUp != null
|
binding.nextUpLayout.isVisible = nextUp != null
|
||||||
binding.nextUpName.text = String.format(
|
binding.nextUpName.text = getString(
|
||||||
getString(R.string.episode_name_extended),
|
R.string.episode_name_extended,
|
||||||
nextUp?.parentIndexNumber,
|
nextUp?.parentIndexNumber,
|
||||||
nextUp?.indexNumber,
|
nextUp?.indexNumber,
|
||||||
nextUp?.name
|
nextUp?.name
|
|
@ -8,7 +8,7 @@ import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.jdtech.jellyfin.R
|
import dev.jdtech.jellyfin.R
|
||||||
import dev.jdtech.jellyfin.utils.AppPreferences
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
|
@ -4,8 +4,8 @@ import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import dev.jdtech.jellyfin.Constants
|
||||||
import dev.jdtech.jellyfin.R
|
import dev.jdtech.jellyfin.R
|
||||||
import dev.jdtech.jellyfin.utils.Constants
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class SettingsNetworkFragment : PreferenceFragmentCompat() {
|
class SettingsNetworkFragment : PreferenceFragmentCompat() {
|
|
@ -0,0 +1,15 @@
|
||||||
|
package dev.jdtech.jellyfin.utils
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import dev.jdtech.jellyfin.AppNavigationDirections
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
fun Fragment.checkIfLoginRequired(error: String?) {
|
||||||
|
if (error != null) {
|
||||||
|
if (error.contains("401")) {
|
||||||
|
Timber.d("Login required!")
|
||||||
|
findNavController().navigate(AppNavigationDirections.actionGlobalLoginFragment(reLogin = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
|
||||||
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
|
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import dev.jdtech.jellyfin.AppPreferences
|
||||||
|
import dev.jdtech.jellyfin.Constants
|
||||||
import dev.jdtech.jellyfin.PlayerActivity
|
import dev.jdtech.jellyfin.PlayerActivity
|
||||||
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
import dev.jdtech.jellyfin.mpv.MPVPlayer
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
|
@ -22,7 +22,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_marginBottom="12dp"
|
||||||
android:text="@{section.name}"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
tools:text="Movies" />
|
tools:text="Movies" />
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:interpolator="@android:anim/decelerate_interpolator"
|
|
||||||
android:shareInterpolator="true"
|
|
||||||
android:duration="@integer/recyclerview_animation_duration">
|
|
||||||
<translate
|
|
||||||
android:fromXDelta="20%"
|
|
||||||
android:toXDelta="0" />
|
|
||||||
<alpha
|
|
||||||
android:fromAlpha="0"
|
|
||||||
android:toAlpha="1" />
|
|
||||||
</set>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:animation="@anim/item_animation_slide_in"
|
|
||||||
android:animationOrder="normal"
|
|
||||||
android:delay="15%" />
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
|
@ -1,154 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="add_server_error_outdated">Versão do servidor desatualizada: %1$s. Por favor atualize seu servidor</string>
|
|
||||||
<string name="add_server_error_version">Versão do servidor não suportada: %1$s. Por favor atualize seu servidor</string>
|
|
||||||
<string name="add_server_error_slow">O servidor é lendo demais para responder: %1$s</string>
|
|
||||||
<string name="add_server_error_empty_address">Endereço de servidor vazio</string>
|
|
||||||
<string name="add_server_error_no_id">Servidor não possui id, algo parece errado como servidor</string>
|
|
||||||
<string name="login">Entrar</string>
|
|
||||||
<string name="login_error_wrong_username_password">Nome de usuário ou senha incorretos</string>
|
|
||||||
<string name="select_server">Selecione o servidor</string>
|
|
||||||
<string name="edit_text_server_address_hint">Endereço do servidor</string>
|
|
||||||
<string name="edit_text_password_hint">Senha</string>
|
|
||||||
<string name="button_connect">Conectar</string>
|
|
||||||
<string name="button_login">Entrar</string>
|
|
||||||
<string name="remove_server">Remover servidor</string>
|
|
||||||
<string name="remove">Remover</string>
|
|
||||||
<string name="cancel">Cancelar</string>
|
|
||||||
<string name="title_home">Início</string>
|
|
||||||
<string name="title_media">Minhas mídia</string>
|
|
||||||
<string name="title_favorite">Favoritos</string>
|
|
||||||
<string name="title_settings">Configurações</string>
|
|
||||||
<string name="title_download">Downloads</string>
|
|
||||||
<string name="view_all">Ver tudo</string>
|
|
||||||
<string name="error_loading_data">Erro carregando dados</string>
|
|
||||||
<string name="retry">Tentar novamente</string>
|
|
||||||
<string name="genres">Gêneros</string>
|
|
||||||
<string name="director">Diretor(a)</string>
|
|
||||||
<string name="writers">Escritores(as)</string>
|
|
||||||
<string name="cast_amp_crew">Elenco e equipe</string>
|
|
||||||
<string name="seasons">Temporadas</string>
|
|
||||||
<string name="play_button_description">Tocar a mídia</string>
|
|
||||||
<string name="trailer_button_description">Assistir ao trailer</string>
|
|
||||||
<string name="favorite_button_description">Favoritar</string>
|
|
||||||
<string name="episode_watched_indicator">Indicador de episódio assistido</string>
|
|
||||||
<string name="episode_name">%1$d. %2$s</string>
|
|
||||||
<string name="episode_name_extended">E%1$d:T%2$d - %3$s</string>
|
|
||||||
<string name="next_up">Próximo</string>
|
|
||||||
<string name="continue_watching">Continue assistindo</string>
|
|
||||||
<string name="series_poster">Poster da série</string>
|
|
||||||
<string name="no_favorites">Você não tem favoritos</string>
|
|
||||||
<string name="search">Buscar</string>
|
|
||||||
<string name="no_search_results">Sem resultados</string>
|
|
||||||
<string name="settings_category_language">Idioma</string>
|
|
||||||
<string name="settings_preferred_audio_language">Idioma de áudio preferido</string>
|
|
||||||
<string name="settings_preferred_subtitle_language">Idioma de legenda preferido</string>
|
|
||||||
<string name="initializing">Inicializando…</string>
|
|
||||||
<string name="settings_category_player">Reprodutor</string>
|
|
||||||
<string name="manage_servers">Gerenciar servidores</string>
|
|
||||||
<string name="settings_category_appearance">Aparência</string>
|
|
||||||
<string name="device_name">Nome do dispositivo</string>
|
|
||||||
<string name="settings_category_device">Dispositivo</string>
|
|
||||||
<string name="settings_category_cache">Cache</string>
|
|
||||||
<string name="settings_use_cache_title">Cache de imagens</string>
|
|
||||||
<string name="settings_cache_size">Tamanho do cache (MB)</string>
|
|
||||||
<string name="theme">Tema</string>
|
|
||||||
<string name="error_preparing_player_items">Erro preparando itens a serem reproduzidos.</string>
|
|
||||||
<string name="view_details">Ver detalhes</string>
|
|
||||||
<string name="view_details_underlined"><u>Ver detalhes</u></string>
|
|
||||||
<string name="about">Sobre</string>
|
|
||||||
<string name="privacy_policy">Política de privacidade</string>
|
|
||||||
<string name="app_info">Informações do aplicativo</string>
|
|
||||||
<string name="unknown_error">Erro desconhecido</string>
|
|
||||||
<string name="select_a_version">Selecione uma versão</string>
|
|
||||||
<string name="select_audio_track">Selecione uma faixa de áudio</string>
|
|
||||||
<string name="select_subtile_track">Selecione uma legenda</string>
|
|
||||||
<string name="select_playback_speed">Selecione a velocidade de reprodução</string>
|
|
||||||
<string name="mpv_player">Reprodutor mpv</string>
|
|
||||||
<string name="download_mobile_data">Baixar usando dados móveis</string>
|
|
||||||
<string name="download_button_description">Baixar</string>
|
|
||||||
<string name="delete_button_description">Deletar</string>
|
|
||||||
<string name="person_detail_title">Detalhes</string>
|
|
||||||
<string name="error_getting_person_id">Detalhes indisponíveis</string>
|
|
||||||
<string name="movies_label">Filmes</string>
|
|
||||||
<string name="shows_label">Series de TV</string>
|
|
||||||
<string name="hide">Esconder</string>
|
|
||||||
<string name="sort_by">Ordenar por</string>
|
|
||||||
<string name="sort_order">Ordem</string>
|
|
||||||
<string name="close">Fechar</string>
|
|
||||||
<string name="share">Compartilhar</string>
|
|
||||||
<string name="image_description_poster">%1$s poster</string>
|
|
||||||
<string name="image_description_backdrop">%1$s imagem de fundo</string>
|
|
||||||
<string name="gestures">Gestos</string>
|
|
||||||
<string name="add_server">Adicionar servidor</string>
|
|
||||||
<string name="add_server_error_not_jellyfin">Não é um servidor do jellyfin: %1$s</string>
|
|
||||||
<string name="add_server_error_not_found">Servidor não encontrado</string>
|
|
||||||
<string name="edit_text_username_hint">Nome de usuário</string>
|
|
||||||
<string name="remove_server_dialog_text">Tem certeza que deseja remover o servidor %1$s</string>
|
|
||||||
<string name="check_button_description">Marcar como assistido ou não assistido</string>
|
|
||||||
<string name="no_downloads">Você não tem nada baixado</string>
|
|
||||||
<string name="settings_category_servers">Servidores</string>
|
|
||||||
<string name="settings_category_download">Downloads</string>
|
|
||||||
<string name="settings_use_cache_summary">Armazena imagens no disco para acelerar o tempo de carregamento. Surte efeito após reiniciar o aplicativo.</string>
|
|
||||||
<string name="search_hint">Busca filmes, shows, episódios…</string>
|
|
||||||
<string name="mpv_player_summary">Usa o reprodutor experimental mpv para reproduzir vídeos. mpv tem suporte a mais codecs de vidro, áudio e legenda.</string>
|
|
||||||
<string name="libraries">Biblioteca</string>
|
|
||||||
<string name="settings_category_network">Rede</string>
|
|
||||||
<string name="sort_by_options_5">Data de Lançamento</string>
|
|
||||||
<string name="track_selection">[%1$s] %2$s (%3$s)</string>
|
|
||||||
<string name="pref_player_mpv_ao">Saída de áudio</string>
|
|
||||||
<string name="subtitles">Legendas</string>
|
|
||||||
<string name="subtitles_summary">Personalize a aparência das legendas</string>
|
|
||||||
<string name="sort_by_options_2">Classificação Etária</string>
|
|
||||||
<string name="external">Externo</string>
|
|
||||||
<string name="theme_system">Usar configurações do sistema</string>
|
|
||||||
<string name="theme_light">Claro</string>
|
|
||||||
<string name="theme_dark">Escuro</string>
|
|
||||||
<string name="app_description">Aplicação Jellyfin nativa de terceiros</string>
|
|
||||||
<string name="latest_library">Mais recentes %1$s</string>
|
|
||||||
<string name="jellyfin_banner">Jellyfin banner</string>
|
|
||||||
<string name="download_roaming">Download quando estiver em roaming</string>
|
|
||||||
<string name="settings_cache_size_message">O aplicativo usará essa quantidade em MB do seu espaço em disco para armazenar imagens do servidor Jellyfin. Valores maiores podem ser benéficos em redes mais lentas.</string>
|
|
||||||
<string name="episodes_label">Episódios</string>
|
|
||||||
<string name="player_gestures">Gestos do player</string>
|
|
||||||
<string name="player_brightness_remember">Lembrar do nível de brilho</string>
|
|
||||||
<string name="display_extended_title_summary">Exibe o título estendido do episódio, incluindo informações sobre a temporada e o episódio (SXX:EXX - NomeEpisódio).</string>
|
|
||||||
<string name="display_extended_title">Exibir título estendido</string>
|
|
||||||
<string name="add_server_empty_error">Endereço de servidor vazio</string>
|
|
||||||
<string name="sort_by_options_0">Título</string>
|
|
||||||
<string name="dynamic_colors_summary">Usar cores dinâmicas do Material You (disponível apenas no Android 12+)</string>
|
|
||||||
<string name="dynamic_colors">Cores dinâmicas</string>
|
|
||||||
<string name="player_gestures_vb">Gestos de volume e brilho</string>
|
|
||||||
<string name="player_gestures_zoom">Gestos de zoom</string>
|
|
||||||
<string name="player_gestures_vb_summary">Deslize para cima e para baixo no lado direito da tela para alterar o volume e no lado esquerdo para alterar o brilho</string>
|
|
||||||
<string name="player_gestures_zoom_summary">Aperte para preencher a tela com o vídeo</string>
|
|
||||||
<string name="player_controls_skip">Pular</string>
|
|
||||||
<string name="runtime_minutes">%1$d minutos</string>
|
|
||||||
<string name="select_video_version_title">Selecione a versão</string>
|
|
||||||
<string name="player_controls_exit">Sair do player</string>
|
|
||||||
<string name="player_controls_rewind">Retroceder</string>
|
|
||||||
<string name="player_controls_fast_forward">Avanço rápido</string>
|
|
||||||
<string name="player_controls_pause">Pausar</string>
|
|
||||||
<string name="users">Usuários</string>
|
|
||||||
<string name="add_user">Adicionar usuário</string>
|
|
||||||
<string name="pref_player_mpv_hwdec">Decodificação de hardware</string>
|
|
||||||
<string name="pref_player_mpv_hwdec_codecs">Codecs de decodificação de hardware</string>
|
|
||||||
<string name="sort_by_options_3">Data de Adição</string>
|
|
||||||
<string name="sort_by_options_4">Data de Reprodução</string>
|
|
||||||
<string name="ascending">Crescente</string>
|
|
||||||
<string name="descending">Decrescente</string>
|
|
||||||
<string name="pref_player_mpv_vo">Saida de vídeo</string>
|
|
||||||
<string name="remove_user">Remover usuário</string>
|
|
||||||
<string name="remove_user_dialog_text">Tem certeza que deseja remover o usuário (%1$s)</string>
|
|
||||||
<string name="sort_by_options_1">Avaliação IMDb</string>
|
|
||||||
<string name="addresses">Endereços</string>
|
|
||||||
<string name="add_address">Adicionar endereço</string>
|
|
||||||
<string name="add_server_address">Adicionar endereço do servidor</string>
|
|
||||||
<string name="add">Adicionar</string>
|
|
||||||
<string name="seeking">Buscando</string>
|
|
||||||
<string name="seek_forward_increment">Incremento para frente (ms)</string>
|
|
||||||
<string name="seek_back_increment">Incremento para trás (ms)</string>
|
|
||||||
<string name="settings_socket_timeout">Tempo limite do soquete (ms)</string>
|
|
||||||
<string name="settings_request_timeout">Tempo limite da requisição (ms)</string>
|
|
||||||
<string name="settings_connect_timeout">Tempo limite da conexão (ms)</string>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="aboutLibraries_description_showIcon">true</string>
|
|
||||||
<string name="aboutLibraries_description_showVersion">true</string>
|
|
||||||
<string name="aboutLibraries_description_name">@string/app_name</string>
|
|
||||||
<string name="aboutLibraries_description_text">@string/app_description</string>
|
|
||||||
<string name="aboutLibraries_showLicense">true</string>
|
|
||||||
</resources>
|
|
|
@ -2,6 +2,7 @@
|
||||||
@Suppress("DSL_SCOPE_VIOLATION") // False positive
|
@Suppress("DSL_SCOPE_VIOLATION") // False positive
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.parcelize) apply false
|
alias(libs.plugins.kotlin.parcelize) apply false
|
||||||
alias(libs.plugins.kotlin.kapt) apply false
|
alias(libs.plugins.kotlin.kapt) apply false
|
||||||
|
@ -20,4 +21,7 @@ allprojects {
|
||||||
|
|
||||||
tasks.create<Delete>("clean") {
|
tasks.create<Delete>("clean") {
|
||||||
delete(rootProject.buildDir)
|
delete(rootProject.buildDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val appVersionCode by extra { 14 }
|
||||||
|
val appVersionName by extra { "0.8.0" }
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue