Add image caching (#65)
* Add caching to settings with ability to choose cache size * Remove unused parameter from Api * Add glide module for cache setup * Clean up image handling in adapters * Move caching to it's own category Co-authored-by: Jarne Demeulemeester <32322857+jarnedemeulemeester@users.noreply.github.com>
This commit is contained in:
parent
e259c405bb
commit
94b3790560
8 changed files with 155 additions and 106 deletions
|
@ -1,11 +1,22 @@
|
||||||
package dev.jdtech.jellyfin
|
package dev.jdtech.jellyfin
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
import dev.jdtech.jellyfin.adapters.*
|
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.DownloadsListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.EpisodeItem
|
||||||
|
import dev.jdtech.jellyfin.adapters.EpisodeListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.HomeItem
|
||||||
|
import dev.jdtech.jellyfin.adapters.PersonListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
|
||||||
|
import dev.jdtech.jellyfin.adapters.ViewListAdapter
|
||||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||||
import dev.jdtech.jellyfin.database.Server
|
import dev.jdtech.jellyfin.database.Server
|
||||||
import dev.jdtech.jellyfin.models.DownloadSection
|
import dev.jdtech.jellyfin.models.DownloadSection
|
||||||
|
@ -35,44 +46,26 @@ fun bindItems(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
|
||||||
|
|
||||||
@BindingAdapter("itemImage")
|
@BindingAdapter("itemImage")
|
||||||
fun bindItemImage(imageView: ImageView, item: BaseItemDto) {
|
fun bindItemImage(imageView: ImageView, item: BaseItemDto) {
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
|
||||||
|
|
||||||
val itemId =
|
val itemId =
|
||||||
if (item.type == "Episode" || item.type == "Season" && item.imageTags.isNullOrEmpty()) item.seriesId else item.id
|
if (item.type == "Episode" || item.type == "Season" && item.imageTags.isNullOrEmpty()) item.seriesId else item.id
|
||||||
|
|
||||||
Glide
|
imageView
|
||||||
.with(imageView.context)
|
.loadImage("/items/$itemId/Images/${ImageType.PRIMARY}")
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${itemId}/Images/${ImageType.PRIMARY}"))
|
.posterDescription(item.name)
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.placeholder(R.color.neutral_800)
|
|
||||||
.into(imageView)
|
|
||||||
|
|
||||||
imageView.contentDescription = "${item.name} poster"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("itemBackdropImage")
|
@BindingAdapter("itemBackdropImage")
|
||||||
fun bindItemBackdropImage(imageView: ImageView, item: BaseItemDto?) {
|
fun bindItemBackdropImage(imageView: ImageView, item: BaseItemDto?) {
|
||||||
if (item == null) return
|
if (item == null) return
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
|
||||||
|
|
||||||
Glide
|
imageView
|
||||||
.with(imageView.context)
|
.loadImage("/items/${item.id}/Images/${ImageType.BACKDROP}")
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${item.id}/Images/${ImageType.BACKDROP}"))
|
.backdropDescription(item.name)
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(imageView)
|
|
||||||
|
|
||||||
imageView.contentDescription = "${item.name} backdrop"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("itemBackdropById")
|
@BindingAdapter("itemBackdropById")
|
||||||
fun bindItemBackdropById(imageView: ImageView, itemId: UUID) {
|
fun bindItemBackdropById(imageView: ImageView, itemId: UUID) {
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
imageView.loadImage("/items/$itemId/ MediaStore.Images /${ImageType.BACKDROP}")
|
||||||
|
|
||||||
Glide
|
|
||||||
.with(imageView.context)
|
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${itemId}/Images/${ImageType.BACKDROP}"))
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(imageView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("collections")
|
@BindingAdapter("collections")
|
||||||
|
@ -89,17 +82,9 @@ fun bindPeople(recyclerView: RecyclerView, data: List<BaseItemPerson>?) {
|
||||||
|
|
||||||
@BindingAdapter("personImage")
|
@BindingAdapter("personImage")
|
||||||
fun bindPersonImage(imageView: ImageView, person: BaseItemPerson) {
|
fun bindPersonImage(imageView: ImageView, person: BaseItemPerson) {
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
imageView
|
||||||
|
.loadImage("/items/${person.id}/Images/${ImageType.PRIMARY}")
|
||||||
Glide
|
.posterDescription(person.name)
|
||||||
.with(imageView.context)
|
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${person.id}/Images/${ImageType.PRIMARY}"))
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.placeholder(R.color.neutral_800)
|
|
||||||
.error(R.drawable.person_placeholder)
|
|
||||||
.into(imageView)
|
|
||||||
|
|
||||||
imageView.contentDescription = "${person.name} poster"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("episodes")
|
@BindingAdapter("episodes")
|
||||||
|
@ -118,8 +103,6 @@ fun bindHomeEpisodes(recyclerView: RecyclerView, data: List<BaseItemDto>?) {
|
||||||
fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
|
fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
|
||||||
if (episode == null) return
|
if (episode == null) return
|
||||||
|
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
|
||||||
|
|
||||||
var imageItemId = episode.id
|
var imageItemId = episode.id
|
||||||
var imageType = ImageType.PRIMARY
|
var imageType = ImageType.PRIMARY
|
||||||
|
|
||||||
|
@ -143,26 +126,14 @@ fun bindBaseItemImage(imageView: ImageView, episode: BaseItemDto?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Glide
|
imageView
|
||||||
.with(imageView.context)
|
.loadImage("/items/${imageItemId}/Images/$imageType")
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${imageItemId}/Images/$imageType"))
|
.posterDescription(episode.name)
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.placeholder(R.color.neutral_800)
|
|
||||||
.into(imageView)
|
|
||||||
|
|
||||||
imageView.contentDescription = "${episode.name} poster"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("seasonPoster")
|
@BindingAdapter("seasonPoster")
|
||||||
fun bindSeasonPoster(imageView: ImageView, seasonId: UUID) {
|
fun bindSeasonPoster(imageView: ImageView, seasonId: UUID) {
|
||||||
val jellyfinApi = JellyfinApi.getInstance(imageView.context.applicationContext, "")
|
imageView.loadImage("/items/${seasonId}/Images/${ImageType.PRIMARY}")
|
||||||
|
|
||||||
Glide
|
|
||||||
.with(imageView.context)
|
|
||||||
.load(jellyfinApi.api.baseUrl.plus("/items/${seasonId}/Images/${ImageType.PRIMARY}"))
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.placeholder(R.color.neutral_800)
|
|
||||||
.into(imageView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BindingAdapter("favoriteSections")
|
@BindingAdapter("favoriteSections")
|
||||||
|
@ -176,3 +147,24 @@ fun bindDownloadSections(recyclerView: RecyclerView, data: List<DownloadSection>
|
||||||
val adapter = recyclerView.adapter as DownloadsListAdapter
|
val adapter = recyclerView.adapter as DownloadsListAdapter
|
||||||
adapter.submitList(data)
|
adapter.submitList(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ImageView.loadImage(url: String, errorPlaceHolderId: Int? = null): View {
|
||||||
|
val api = JellyfinApi.getInstance(context.applicationContext)
|
||||||
|
|
||||||
|
return Glide
|
||||||
|
.with(context)
|
||||||
|
.load("${api.api.baseUrl}$url")
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
.placeholder(R.color.neutral_800)
|
||||||
|
.also { if (errorPlaceHolderId != null) error(errorPlaceHolderId) }
|
||||||
|
.into(this)
|
||||||
|
.view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.posterDescription(name: String?) {
|
||||||
|
contentDescription = String.format(context.resources.getString(R.string.image_description_poster), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.backdropDescription(name: String?) {
|
||||||
|
contentDescription = String.format(context.resources.getString(R.string.image_description_backdrop), name)
|
||||||
|
}
|
|
@ -24,18 +24,13 @@ import java.util.UUID
|
||||||
* @param baseUrl The url of the server
|
* @param baseUrl The url of the server
|
||||||
* @constructor Creates a new [JellyfinApi] instance
|
* @constructor Creates a new [JellyfinApi] instance
|
||||||
*/
|
*/
|
||||||
class JellyfinApi(androidContext: Context, baseUrl: String) {
|
class JellyfinApi(androidContext: Context) {
|
||||||
|
|
||||||
val jellyfin = createJellyfin {
|
val jellyfin = createJellyfin {
|
||||||
clientInfo = ClientInfo(
|
clientInfo =
|
||||||
name = androidContext.applicationInfo.loadLabel(androidContext.packageManager)
|
ClientInfo(name = androidContext.applicationInfo.loadLabel(androidContext.packageManager).toString(), version = BuildConfig.VERSION_NAME)
|
||||||
.toString(),
|
|
||||||
version = BuildConfig.VERSION_NAME
|
|
||||||
)
|
|
||||||
context = androidContext
|
context = androidContext
|
||||||
}
|
}
|
||||||
|
val api = jellyfin.createApi()
|
||||||
val api = jellyfin.createApi(baseUrl = baseUrl)
|
|
||||||
var userId: UUID? = null
|
var userId: UUID? = null
|
||||||
|
|
||||||
val devicesApi = api.devicesApi
|
val devicesApi = api.devicesApi
|
||||||
|
@ -54,37 +49,15 @@ class JellyfinApi(androidContext: Context, baseUrl: String) {
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: JellyfinApi? = null
|
private var INSTANCE: JellyfinApi? = null
|
||||||
|
|
||||||
/**
|
fun getInstance(context: Context): JellyfinApi {
|
||||||
* Creates or gets a new instance of [JellyfinApi]
|
|
||||||
*
|
|
||||||
* If there already is an instance, it will return that instance and ignore the [baseUrl] parameter
|
|
||||||
*
|
|
||||||
* @param context The context
|
|
||||||
* @param baseUrl The url of the server
|
|
||||||
*/
|
|
||||||
fun getInstance(context: Context, baseUrl: String): JellyfinApi {
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
var instance = INSTANCE
|
var instance = INSTANCE
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = JellyfinApi(context.applicationContext, baseUrl)
|
instance = JellyfinApi(context.applicationContext)
|
||||||
INSTANCE = instance
|
INSTANCE = instance
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [JellyfinApi] instance
|
|
||||||
*
|
|
||||||
* @param context The context
|
|
||||||
* @param baseUrl The url of the server
|
|
||||||
*/
|
|
||||||
fun newInstance(context: Context, baseUrl: String): JellyfinApi {
|
|
||||||
synchronized(this) {
|
|
||||||
val instance = JellyfinApi(context.applicationContext, baseUrl)
|
|
||||||
INSTANCE = instance
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dev.jdtech.jellyfin.api.JellyfinApi
|
import dev.jdtech.jellyfin.api.JellyfinApi
|
||||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -22,7 +22,7 @@ object ApiModule {
|
||||||
sharedPreferences: SharedPreferences,
|
sharedPreferences: SharedPreferences,
|
||||||
serverDatabase: ServerDatabaseDao
|
serverDatabase: ServerDatabaseDao
|
||||||
): JellyfinApi {
|
): JellyfinApi {
|
||||||
val jellyfinApi = JellyfinApi.getInstance(application, "")
|
val jellyfinApi = JellyfinApi.getInstance(application)
|
||||||
|
|
||||||
val serverId = sharedPreferences.getString("selectedServer", null)
|
val serverId = sharedPreferences.getString("selectedServer", null)
|
||||||
if (serverId != null) {
|
if (serverId != null) {
|
||||||
|
|
51
app/src/main/java/dev/jdtech/jellyfin/di/GlideModule.kt
Normal file
51
app/src/main/java/dev/jdtech/jellyfin/di/GlideModule.kt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package dev.jdtech.jellyfin.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.bumptech.glide.GlideBuilder
|
||||||
|
import com.bumptech.glide.annotation.GlideModule
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy.NONE
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy.RESOURCE
|
||||||
|
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||||
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
private const val cacheDefaultSize = 250
|
||||||
|
|
||||||
|
@GlideModule
|
||||||
|
class GlideModule : AppGlideModule() {
|
||||||
|
|
||||||
|
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val use = preferences.getBoolean("use_image_cache", false)
|
||||||
|
|
||||||
|
if (use) {
|
||||||
|
val sizeMb = preferences.getString("image_cache_size", "$cacheDefaultSize")?.toInt()!!
|
||||||
|
val sizeB = 1024L * 1024 * sizeMb
|
||||||
|
Timber.d("Setting image cache to use $sizeMb MB of disk space")
|
||||||
|
|
||||||
|
builder.setDiskCache(InternalCacheDiskCacheFactory(context, sizeB))
|
||||||
|
builder.caching(enabled = true)
|
||||||
|
} else {
|
||||||
|
builder.caching(enabled = false)
|
||||||
|
Timber.d("Image cache disabled. Clearing all persisted data.")
|
||||||
|
|
||||||
|
MainScope().launch {
|
||||||
|
GlideApp.getPhotoCacheDir(context)?.also {
|
||||||
|
if (it.exists()) it.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun GlideBuilder.caching(enabled: Boolean) {
|
||||||
|
setDefaultRequestOptions(
|
||||||
|
RequestOptions().diskCacheStrategy(
|
||||||
|
if (enabled) RESOURCE else NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package dev.jdtech.jellyfin.fragments
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.InputType
|
||||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
@ -53,6 +54,10 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findPreference<EditTextPreference>("image_cache_size")?.setOnBindEditTextListener { editText ->
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
findPreference<EditTextPreference>("deviceName")?.setOnPreferenceChangeListener { _, name ->
|
findPreference<EditTextPreference>("deviceName")?.setOnPreferenceChangeListener { _, name ->
|
||||||
viewModel.updateDeviceName(name.toString())
|
viewModel.updateDeviceName(name.toString())
|
||||||
true
|
true
|
||||||
|
|
|
@ -55,8 +55,12 @@
|
||||||
<string name="settings_category_player">Přehrávač</string>
|
<string name="settings_category_player">Přehrávač</string>
|
||||||
<string name="manage_servers">Spravovat servery</string>
|
<string name="manage_servers">Spravovat servery</string>
|
||||||
<string name="settings_category_appearance">Vzhled</string>
|
<string name="settings_category_appearance">Vzhled</string>
|
||||||
<string name="settings_category_device">Zařízení</string>
|
|
||||||
<string name="device_name">Název zařízení</string>
|
<string name="device_name">Název zařízení</string>
|
||||||
|
<string name="settings_category_device">Zařízení</string>
|
||||||
|
<string name="settings_use_cache_title">Cachovat obrázky</string>
|
||||||
|
<string name="settings_use_cache_summary">Cachování obrázků urychluje načítání obsahu. Projeví se po restartu aplikace.</string>
|
||||||
|
<string name="settings_cache_size">Nastavit velikost cache v MB</string>
|
||||||
|
<string name="settings_cache_size_message">Aplikace použije tuto velikost v MB k uložení obrázků z Jellyfin serveru. Větší hodnota může být prospěšná na pomalejším připojení.</string>
|
||||||
<string name="theme">Téma</string>
|
<string name="theme">Téma</string>
|
||||||
<string name="error_preparing_player_items">Chyba při načítání přehrávání.</string>
|
<string name="error_preparing_player_items">Chyba při načítání přehrávání.</string>
|
||||||
<string name="view_details">Zobrazit detaily</string>
|
<string name="view_details">Zobrazit detaily</string>
|
||||||
|
@ -71,4 +75,6 @@
|
||||||
<string name="mpv_player_summary">Použít experimentální MPV přehrávač k přehrávání videí. MPV má podporu pro více video a audio kodeků a formátů titulků.</string>
|
<string name="mpv_player_summary">Použít experimentální MPV přehrávač k přehrávání videí. MPV má podporu pro více video a audio kodeků a formátů titulků.</string>
|
||||||
<string name="force_software_decoding">Vynutit softwarové dekódování</string>
|
<string name="force_software_decoding">Vynutit softwarové dekódování</string>
|
||||||
<string name="force_software_decoding_summary">Vypnout hardwarové dekódování a vynutit softwarové dekódování. Může být užitečné pokud hardwarové dekódování produkuje artefakty.</string>
|
<string name="force_software_decoding_summary">Vypnout hardwarové dekódování a vynutit softwarové dekódování. Může být užitečné pokud hardwarové dekódování produkuje artefakty.</string>
|
||||||
|
<string name="image_description_poster">%1$s plakát</string>
|
||||||
|
<string name="image_description_backdrop">%1$s pozadí</string>
|
||||||
</resources>
|
</resources>
|
|
@ -59,8 +59,13 @@
|
||||||
<string name="settings_category_player">Player</string>
|
<string name="settings_category_player">Player</string>
|
||||||
<string name="manage_servers">Manage servers</string>
|
<string name="manage_servers">Manage servers</string>
|
||||||
<string name="settings_category_appearance">Appearance</string>
|
<string name="settings_category_appearance">Appearance</string>
|
||||||
<string name="settings_category_device">Device</string>
|
|
||||||
<string name="device_name">Device name</string>
|
<string name="device_name">Device name</string>
|
||||||
|
<string name="settings_category_device">Device</string>
|
||||||
|
<string name="settings_category_cache">Cache</string>
|
||||||
|
<string name="settings_use_cache_title">Cache images</string>
|
||||||
|
<string name="settings_use_cache_summary">Cache images on disk to speed up loading times. Will take effect after app restart.</string>
|
||||||
|
<string name="settings_cache_size">Cache size (MB)</string>
|
||||||
|
<string name="settings_cache_size_message">App will use this amount of MB of your disk space to store images from Jellyfin server. Larger values might be beneficial on slower networks.</string>
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</string>
|
||||||
<string name="error_preparing_player_items">Error preparing player items.</string>
|
<string name="error_preparing_player_items">Error preparing player items.</string>
|
||||||
<string name="view_details">View details</string>
|
<string name="view_details">View details</string>
|
||||||
|
@ -89,6 +94,9 @@
|
||||||
<string name="sort_order">Sort order</string>
|
<string name="sort_order">Sort order</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
|
<string name="image_description_poster">%1$s poster</string>
|
||||||
|
<string name="image_description_backdrop">%1$s backdrop</string>
|
||||||
|
|
||||||
<string-array name="sort_by_options">
|
<string-array name="sort_by_options">
|
||||||
<item>Name</item>
|
<item>Name</item>
|
||||||
<item>IMDB Rating</item>
|
<item>IMDB Rating</item>
|
||||||
|
|
|
@ -7,28 +7,28 @@
|
||||||
app:defaultValue="null"
|
app:defaultValue="null"
|
||||||
app:entries="@array/languages"
|
app:entries="@array/languages"
|
||||||
app:entryValues="@array/languages_values"
|
app:entryValues="@array/languages_values"
|
||||||
|
app:icon="@drawable/ic_speaker"
|
||||||
app:key="audio_language"
|
app:key="audio_language"
|
||||||
app:title="@string/settings_preferred_audio_language"
|
app:title="@string/settings_preferred_audio_language"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true" />
|
||||||
app:icon="@drawable/ic_speaker"/>
|
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
app:defaultValue="null"
|
app:defaultValue="null"
|
||||||
app:entries="@array/languages"
|
app:entries="@array/languages"
|
||||||
app:entryValues="@array/languages_values"
|
app:entryValues="@array/languages_values"
|
||||||
|
app:icon="@drawable/ic_closed_caption"
|
||||||
app:key="subtitle_language"
|
app:key="subtitle_language"
|
||||||
app:title="@string/settings_preferred_subtitle_language"
|
app:title="@string/settings_preferred_subtitle_language"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true" />
|
||||||
app:icon="@drawable/ic_closed_caption"/>
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_category_servers">
|
<PreferenceCategory app:title="@string/settings_category_servers">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
app:icon="@drawable/ic_server"
|
||||||
app:key="switchServer"
|
app:key="switchServer"
|
||||||
app:title="@string/manage_servers"
|
app:title="@string/manage_servers" />
|
||||||
app:icon="@drawable/ic_server"/>
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
@ -37,22 +37,22 @@
|
||||||
app:defaultValue="system"
|
app:defaultValue="system"
|
||||||
app:entries="@array/themes"
|
app:entries="@array/themes"
|
||||||
app:entryValues="@array/themes_value"
|
app:entryValues="@array/themes_value"
|
||||||
|
app:icon="@drawable/ic_palette"
|
||||||
app:key="theme"
|
app:key="theme"
|
||||||
app:title="@string/theme"
|
app:title="@string/theme"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true" />
|
||||||
app:icon="@drawable/ic_palette"/>
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_category_player">
|
<PreferenceCategory app:title="@string/settings_category_player">
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
app:key="mpv_player"
|
app:key="mpv_player"
|
||||||
app:title="@string/mpv_player"
|
app:summary="@string/mpv_player_summary"
|
||||||
app:summary="@string/mpv_player_summary"/>
|
app:title="@string/mpv_player" />
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
app:key="mpv_disable_hwdec"
|
|
||||||
app:dependency="mpv_player"
|
app:dependency="mpv_player"
|
||||||
app:title="@string/force_software_decoding"
|
app:key="mpv_disable_hwdec"
|
||||||
app:summary="@string/force_software_decoding_summary"/>
|
app:summary="@string/force_software_decoding_summary"
|
||||||
|
app:title="@string/force_software_decoding" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/settings_category_device">
|
<PreferenceCategory app:title="@string/settings_category_device">
|
||||||
|
@ -62,6 +62,20 @@
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/settings_category_cache">
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="use_image_cache"
|
||||||
|
app:summary="@string/settings_use_cache_summary"
|
||||||
|
app:title="@string/settings_use_cache_title" />
|
||||||
|
<EditTextPreference
|
||||||
|
app:defaultValue="250"
|
||||||
|
app:dependency="use_image_cache"
|
||||||
|
app:dialogMessage="@string/settings_cache_size_message"
|
||||||
|
app:key="image_cache_size"
|
||||||
|
app:title="@string/settings_cache_size"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/about">
|
<PreferenceCategory app:title="@string/about">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
|
Loading…
Reference in a new issue