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:
Jarne Demeulemeester 2023-01-13 23:51:20 +01:00 committed by GitHub
parent 25efbb6eab
commit 76121925d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
308 changed files with 795 additions and 501 deletions

1
app/.gitignore vendored
View file

@ -1 +0,0 @@
/build

View file

@ -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"))
} }

View file

@ -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)

View file

@ -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()

View file

@ -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)
} }

View file

@ -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?) {

View file

@ -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

View file

@ -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
@ -114,15 +114,3 @@ class DownloadEpisodeListAdapter(
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
}
}

View file

@ -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
}
}

View file

@ -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()
} }
} }

View file

@ -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
}

View file

@ -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
} }
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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() {

View file

@ -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))
}
}
}

View file

@ -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

View file

@ -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" />

View file

@ -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>

View file

@ -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%" />

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -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>

View file

@ -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>

View file

@ -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
@ -21,3 +22,6 @@ 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