mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-27 08:16:36 +03:00
feat: Move player preferences into their own menu (#1819)
This commit is contained in:
parent
37e1834e2a
commit
33c813792b
22 changed files with 1169 additions and 316 deletions
|
@ -17,6 +17,7 @@ import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.QueryStats
|
import androidx.compose.material.icons.outlined.QueryStats
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.Storage
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
|
import androidx.compose.material.icons.outlined.VideoSettings
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -51,6 +52,7 @@ fun MoreScreen(
|
||||||
onClickStats: () -> Unit,
|
onClickStats: () -> Unit,
|
||||||
onClickStorage: () -> Unit,
|
onClickStorage: () -> Unit,
|
||||||
onClickDataAndStorage: () -> Unit,
|
onClickDataAndStorage: () -> Unit,
|
||||||
|
onClickPlayerSettings: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
onClickAbout: () -> Unit,
|
onClickAbout: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -178,6 +180,13 @@ fun MoreScreen(
|
||||||
onPreferenceClick = onClickSettings,
|
onPreferenceClick = onClickSettings,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.label_player_settings),
|
||||||
|
icon = Icons.Outlined.VideoSettings,
|
||||||
|
onPreferenceClick = onClickPlayerSettings,
|
||||||
|
)
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(MR.strings.pref_category_about),
|
title = stringResource(MR.strings.pref_category_about),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
|
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
@ -199,6 +200,20 @@ sealed class Preference {
|
||||||
val canBeBlank: Boolean = true,
|
val canBeBlank: Boolean = true,
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PreferenceItem] that shows a EditText with a subtitle in the dialog.
|
||||||
|
* Unlike [EditTextPreference], empty values can be set and a subtitle in the dialog can be show.
|
||||||
|
*/
|
||||||
|
data class EditTextInfoPreference(
|
||||||
|
val pref: PreferenceData<String>,
|
||||||
|
val dialogSubtitle: String?,
|
||||||
|
override val title: String,
|
||||||
|
override val subtitle: String? = "%s",
|
||||||
|
override val icon: ImageVector? = null,
|
||||||
|
override val enabled: Boolean = true,
|
||||||
|
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
||||||
|
) : PreferenceItem<String>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] for individual tracker.
|
* A [PreferenceItem] for individual tracker.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -185,6 +185,23 @@ internal fun PreferenceItem(
|
||||||
canBeBlank = item.canBeBlank,
|
canBeBlank = item.canBeBlank,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is Preference.PreferenceItem.EditTextInfoPreference -> {
|
||||||
|
val values by item.pref.collectAsState()
|
||||||
|
EditTextPreferenceWidget(
|
||||||
|
title = item.title,
|
||||||
|
subtitle = item.subtitle,
|
||||||
|
dialogSubtitle = item.dialogSubtitle,
|
||||||
|
icon = item.icon,
|
||||||
|
value = values,
|
||||||
|
onConfirm = {
|
||||||
|
val accepted = item.onValueChanged(it)
|
||||||
|
if (accepted) item.pref.set(it)
|
||||||
|
accepted
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
canBeBlank = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
is Preference.PreferenceItem.TrackerPreference -> {
|
is Preference.PreferenceItem.TrackerPreference -> {
|
||||||
val isLoggedIn by item.tracker.let { tracker ->
|
val isLoggedIn by item.tracker.let { tracker ->
|
||||||
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
|
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import androidx.compose.material.icons.outlined.Explore
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.Palette
|
import androidx.compose.material.icons.outlined.Palette
|
||||||
import androidx.compose.material.icons.outlined.PlayCircleOutline
|
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.Storage
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
|
@ -186,12 +185,6 @@ object SettingsMainScreen : Screen() {
|
||||||
icon = Icons.Outlined.CollectionsBookmark,
|
icon = Icons.Outlined.CollectionsBookmark,
|
||||||
screen = SettingsLibraryScreen,
|
screen = SettingsLibraryScreen,
|
||||||
),
|
),
|
||||||
Item(
|
|
||||||
titleRes = MR.strings.pref_category_player,
|
|
||||||
subtitleRes = MR.strings.pref_player_summary,
|
|
||||||
icon = Icons.Outlined.PlayCircleOutline,
|
|
||||||
screen = SettingsPlayerScreen,
|
|
||||||
),
|
|
||||||
Item(
|
Item(
|
||||||
titleRes = MR.strings.pref_category_reader,
|
titleRes = MR.strings.pref_category_reader,
|
||||||
subtitleRes = MR.strings.pref_reader_summary,
|
subtitleRes = MR.strings.pref_reader_summary,
|
||||||
|
|
|
@ -50,6 +50,12 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.UpIcon
|
import eu.kanade.presentation.components.UpIcon
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsAdvancedScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsAudioScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsDecoderScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsGesturesScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsPlayerScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsSubtitleScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
@ -58,7 +64,9 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||||
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
|
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
|
||||||
|
|
||||||
class SettingsSearchScreen : Screen() {
|
class SettingsSearchScreen(
|
||||||
|
val isPlayer: Boolean = false,
|
||||||
|
) : Screen() {
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
@ -115,7 +123,13 @@ class SettingsSearchScreen : Screen() {
|
||||||
decorator = {
|
decorator = {
|
||||||
if (textFieldState.text.isEmpty()) {
|
if (textFieldState.text.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.action_search_settings),
|
text = stringResource(
|
||||||
|
resource = if (isPlayer) {
|
||||||
|
MR.strings.action_search_player_settings
|
||||||
|
} else {
|
||||||
|
MR.strings.action_search_settings
|
||||||
|
},
|
||||||
|
),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
)
|
)
|
||||||
|
@ -142,6 +156,7 @@ class SettingsSearchScreen : Screen() {
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
SearchResult(
|
SearchResult(
|
||||||
searchKey = textFieldState.text.toString(),
|
searchKey = textFieldState.text.toString(),
|
||||||
|
isPlayer = isPlayer,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) { result ->
|
) { result ->
|
||||||
|
@ -155,6 +170,7 @@ class SettingsSearchScreen : Screen() {
|
||||||
@Composable
|
@Composable
|
||||||
private fun SearchResult(
|
private fun SearchResult(
|
||||||
searchKey: String,
|
searchKey: String,
|
||||||
|
isPlayer: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
listState: LazyListState = rememberLazyListState(),
|
listState: LazyListState = rememberLazyListState(),
|
||||||
contentPadding: PaddingValues = PaddingValues(),
|
contentPadding: PaddingValues = PaddingValues(),
|
||||||
|
@ -164,7 +180,7 @@ private fun SearchResult(
|
||||||
|
|
||||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||||
|
|
||||||
val index = getIndex()
|
val index = if (isPlayer) getPlayerIndex() else getIndex()
|
||||||
val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) {
|
val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) {
|
||||||
value = index.asSequence()
|
value = index.asSequence()
|
||||||
.flatMap { settingsData ->
|
.flatMap { settingsData ->
|
||||||
|
@ -271,6 +287,17 @@ private fun getIndex() = settingScreens
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
private fun getPlayerIndex() = playerSettingScreens
|
||||||
|
.map { screen ->
|
||||||
|
SettingsData(
|
||||||
|
title = stringResource(screen.getTitleRes()),
|
||||||
|
route = screen,
|
||||||
|
contents = screen.getPreferences(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String {
|
private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String {
|
||||||
return if (node == null) {
|
return if (node == null) {
|
||||||
path
|
path
|
||||||
|
@ -285,11 +312,19 @@ private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val playerSettingScreens = listOf(
|
||||||
|
PlayerSettingsPlayerScreen,
|
||||||
|
PlayerSettingsGesturesScreen,
|
||||||
|
PlayerSettingsDecoderScreen,
|
||||||
|
PlayerSettingsSubtitleScreen,
|
||||||
|
PlayerSettingsAudioScreen,
|
||||||
|
PlayerSettingsAdvancedScreen,
|
||||||
|
)
|
||||||
|
|
||||||
private val settingScreens = listOf(
|
private val settingScreens = listOf(
|
||||||
SettingsAppearanceScreen,
|
SettingsAppearanceScreen,
|
||||||
SettingsLibraryScreen,
|
SettingsLibraryScreen,
|
||||||
SettingsReaderScreen,
|
SettingsReaderScreen,
|
||||||
SettingsPlayerScreen,
|
|
||||||
SettingsDownloadScreen,
|
SettingsDownloadScreen,
|
||||||
SettingsTrackingScreen,
|
SettingsTrackingScreen,
|
||||||
SettingsBrowseScreen,
|
SettingsBrowseScreen,
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object PlayerSettingsAdvancedScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
override fun getTitleRes() = MR.strings.pref_player_advanced
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val enableScripts = playerPreferences.mpvScripts()
|
||||||
|
val mpvConf = playerPreferences.mpvConf()
|
||||||
|
val mpvInput = playerPreferences.mpvInput()
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
title = stringResource(MR.strings.pref_mpv_scripts),
|
||||||
|
subtitle = stringResource(MR.strings.pref_mpv_scripts_summary),
|
||||||
|
pref = enableScripts,
|
||||||
|
onValueChanged = {
|
||||||
|
// Ask for external storage permission
|
||||||
|
if (it) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
intent.data = Uri.fromParts("package", context.packageName, null)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.MPVConfPreference(
|
||||||
|
pref = mpvConf,
|
||||||
|
title = stringResource(MR.strings.pref_mpv_conf),
|
||||||
|
fileName = "mpv.conf",
|
||||||
|
scope = scope,
|
||||||
|
context = context,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.MPVConfPreference(
|
||||||
|
pref = mpvInput,
|
||||||
|
title = stringResource(MR.strings.pref_mpv_input),
|
||||||
|
fileName = "input.conf",
|
||||||
|
scope = scope,
|
||||||
|
context = context,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import eu.kanade.tachiyomi.ui.player.viewer.AudioChannels
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object PlayerSettingsAudioScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
override fun getTitleRes() = MR.strings.pref_player_audio
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
|
||||||
|
|
||||||
|
val prefLangs = playerPreferences.preferredAudioLanguages()
|
||||||
|
val pitchCorrection = playerPreferences.enablePitchCorrection()
|
||||||
|
val audioChannels = playerPreferences.audioChannels()
|
||||||
|
val boostCapPref = playerPreferences.volumeBoostCap()
|
||||||
|
val boostCap by boostCapPref.collectAsState()
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.EditTextInfoPreference(
|
||||||
|
pref = prefLangs,
|
||||||
|
title = stringResource(MR.strings.pref_player_audio_lang),
|
||||||
|
dialogSubtitle = stringResource(MR.strings.pref_player_audio_lang_info),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = pitchCorrection,
|
||||||
|
title = stringResource(MR.strings.pref_player_audio_pitch_correction),
|
||||||
|
subtitle = stringResource(MR.strings.pref_player_audio_pitch_correction_summary),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = audioChannels,
|
||||||
|
title = stringResource(MR.strings.pref_player_audio_channels),
|
||||||
|
entries = AudioChannels.entries.associateWith {
|
||||||
|
stringResource(it.textRes)
|
||||||
|
}.toImmutableMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SliderPreference(
|
||||||
|
value = boostCap,
|
||||||
|
title = stringResource(MR.strings.pref_player_audio_boost_cap),
|
||||||
|
subtitle = boostCap.toString(),
|
||||||
|
min = 0,
|
||||||
|
max = 200,
|
||||||
|
onValueChanged = {
|
||||||
|
boostCapPref.set(it)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object PlayerSettingsDecoderScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
override fun getTitleRes() = MR.strings.pref_player_decoder
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
|
||||||
|
|
||||||
|
val tryHw = playerPreferences.tryHWDecoding()
|
||||||
|
val useGpuNext = playerPreferences.gpuNext()
|
||||||
|
val debanding = playerPreferences.videoDebanding()
|
||||||
|
val yuv420p = playerPreferences.useYUV420P()
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = tryHw,
|
||||||
|
title = stringResource(MR.strings.pref_try_hw),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = useGpuNext,
|
||||||
|
title = stringResource(MR.strings.pref_gpu_next_title),
|
||||||
|
subtitle = stringResource(MR.strings.pref_gpu_next_subtitle),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = debanding,
|
||||||
|
title = stringResource(MR.strings.pref_debanding_title),
|
||||||
|
entries = VideoDebanding.entries.associateWith {
|
||||||
|
stringResource(it.stringRes)
|
||||||
|
}.toImmutableMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = yuv420p,
|
||||||
|
title = stringResource(MR.strings.pref_use_yuv420p_title),
|
||||||
|
subtitle = stringResource(MR.strings.pref_use_yuv420p_subtitle),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import eu.kanade.tachiyomi.ui.player.viewer.SingleActionGesture
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentMap
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object PlayerSettingsGesturesScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
override fun getTitleRes() = MR.strings.pref_player_gestures
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
getSeekingGroup(playerPreferences = playerPreferences),
|
||||||
|
getDoubleTapGroup(playerPreferences = playerPreferences),
|
||||||
|
getMediaControlsGroup(playerPreferences = playerPreferences),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSeekingGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val enableHorizontalSeekGesture = playerPreferences.gestureHorizontalSeek()
|
||||||
|
val defaultSkipIntroLength by playerPreferences.defaultIntroLength().stateIn(scope).collectAsState()
|
||||||
|
val skipLengthPreference = playerPreferences.skipLengthPreference()
|
||||||
|
val playerSmoothSeek = playerPreferences.playerSmoothSeek()
|
||||||
|
val mediaChapterSeek = playerPreferences.mediaChapterSeek()
|
||||||
|
|
||||||
|
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
if (showDialog) {
|
||||||
|
SkipIntroLengthDialog(
|
||||||
|
initialSkipIntroLength = defaultSkipIntroLength,
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
onValueChanged = { skipIntroLength ->
|
||||||
|
playerPreferences.defaultIntroLength().set(skipIntroLength)
|
||||||
|
showDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aniskip
|
||||||
|
val enableAniSkip = playerPreferences.aniSkipEnabled()
|
||||||
|
val enableAutoAniSkip = playerPreferences.autoSkipAniSkip()
|
||||||
|
val enableNetflixAniSkip = playerPreferences.enableNetflixStyleAniSkip()
|
||||||
|
val waitingTimeAniSkip = playerPreferences.waitingTimeAniSkip()
|
||||||
|
|
||||||
|
val isAniSkipEnabled by enableAniSkip.collectAsState()
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_category_player_seeking),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = enableHorizontalSeekGesture,
|
||||||
|
title = stringResource(MR.strings.enable_horizontal_seek_gesture),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(MR.strings.pref_default_intro_length),
|
||||||
|
subtitle = "${defaultSkipIntroLength}s",
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = skipLengthPreference,
|
||||||
|
title = stringResource(MR.strings.pref_skip_length),
|
||||||
|
entries = persistentMapOf(
|
||||||
|
30 to stringResource(MR.strings.pref_skip_30),
|
||||||
|
20 to stringResource(MR.strings.pref_skip_20),
|
||||||
|
10 to stringResource(MR.strings.pref_skip_10),
|
||||||
|
5 to stringResource(MR.strings.pref_skip_5),
|
||||||
|
3 to stringResource(MR.strings.pref_skip_3),
|
||||||
|
0 to stringResource(MR.strings.pref_skip_disable),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = playerSmoothSeek,
|
||||||
|
title = stringResource(MR.strings.pref_player_smooth_seek),
|
||||||
|
subtitle = stringResource(MR.strings.pref_player_smooth_seek_summary),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = mediaChapterSeek,
|
||||||
|
title = stringResource(MR.strings.pref_media_control_chapter_seeking),
|
||||||
|
subtitle = stringResource(MR.strings.pref_media_control_chapter_seeking_summary),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(
|
||||||
|
title = stringResource(MR.strings.pref_category_player_aniskip_info),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = enableAniSkip,
|
||||||
|
title = stringResource(MR.strings.pref_enable_aniskip),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = enableAutoAniSkip,
|
||||||
|
title = stringResource(MR.strings.pref_enable_auto_skip_ani_skip),
|
||||||
|
enabled = isAniSkipEnabled,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = enableNetflixAniSkip,
|
||||||
|
title = stringResource(MR.strings.pref_enable_netflix_style_aniskip),
|
||||||
|
enabled = isAniSkipEnabled,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = waitingTimeAniSkip,
|
||||||
|
title = stringResource(MR.strings.pref_waiting_time_aniskip),
|
||||||
|
entries = persistentMapOf(
|
||||||
|
5 to stringResource(MR.strings.pref_waiting_time_aniskip_5),
|
||||||
|
6 to stringResource(MR.strings.pref_waiting_time_aniskip_6),
|
||||||
|
7 to stringResource(MR.strings.pref_waiting_time_aniskip_7),
|
||||||
|
8 to stringResource(MR.strings.pref_waiting_time_aniskip_8),
|
||||||
|
9 to stringResource(MR.strings.pref_waiting_time_aniskip_9),
|
||||||
|
10 to stringResource(MR.strings.pref_waiting_time_aniskip_10),
|
||||||
|
),
|
||||||
|
enabled = isAniSkipEnabled,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getDoubleTapGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||||
|
val leftDoubleTap = playerPreferences.leftDoubleTapGesture()
|
||||||
|
val centerDoubleTap = playerPreferences.centerDoubleTapGesture()
|
||||||
|
val rightDoubleTap = playerPreferences.rightDoubleTapGesture()
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_category_double_tap),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = leftDoubleTap,
|
||||||
|
title = stringResource(MR.strings.pref_left_double_tap),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.Seek,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Switch,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = centerDoubleTap,
|
||||||
|
title = stringResource(MR.strings.pref_center_double_tap),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = rightDoubleTap,
|
||||||
|
title = stringResource(MR.strings.pref_right_double_tap),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.Seek,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Switch,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(
|
||||||
|
title = stringResource(MR.strings.pref_double_tap_info),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getMediaControlsGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||||
|
val mediaPrevious = playerPreferences.mediaPreviousGesture()
|
||||||
|
val mediaPlayPause = playerPreferences.mediaPlayPauseGesture()
|
||||||
|
val mediaNext = playerPreferences.mediaNextGesture()
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_category_media_controls),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = mediaPrevious,
|
||||||
|
title = stringResource(MR.strings.pref_media_previous),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.Seek,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Switch,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = mediaPlayPause,
|
||||||
|
title = stringResource(MR.strings.pref_media_playpause),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = mediaNext,
|
||||||
|
title = stringResource(MR.strings.pref_media_next),
|
||||||
|
entries = listOf(
|
||||||
|
SingleActionGesture.None,
|
||||||
|
SingleActionGesture.Seek,
|
||||||
|
SingleActionGesture.PlayPause,
|
||||||
|
SingleActionGesture.Switch,
|
||||||
|
SingleActionGesture.Custom,
|
||||||
|
).associateWith { stringResource(it.stringRes) }.toPersistentMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(
|
||||||
|
title = stringResource(MR.strings.pref_media_info),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SkipIntroLengthDialog(
|
||||||
|
initialSkipIntroLength: Int,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onValueChanged: (skipIntroLength: Int) -> Unit,
|
||||||
|
) {
|
||||||
|
val skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) }
|
||||||
|
var newLength = 0
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(MR.strings.pref_intro_length)) },
|
||||||
|
text = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
content = {
|
||||||
|
WheelTextPicker(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
items = remember { 0..255 }.map {
|
||||||
|
stringResource(
|
||||||
|
MR.strings.seconds_short,
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}.toImmutableList(),
|
||||||
|
onSelectionChanged = {
|
||||||
|
newLength = it
|
||||||
|
},
|
||||||
|
startIndex = skipIntroLengthValue,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onValueChanged(newLength) }) {
|
||||||
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Audiotrack
|
||||||
|
import androidx.compose.material.icons.outlined.Code
|
||||||
|
import androidx.compose.material.icons.outlined.Gesture
|
||||||
|
import androidx.compose.material.icons.outlined.Memory
|
||||||
|
import androidx.compose.material.icons.outlined.PlayCircleOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
|
import androidx.compose.material.icons.outlined.Subtitles
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SettingsSearchScreen
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
|
||||||
|
|
||||||
|
object PlayerSettingsMainScreen : Screen() {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
Content(twoPane = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getPalerSurface(): Color {
|
||||||
|
val surface = MaterialTheme.colorScheme.surface
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
return remember(surface, dark) {
|
||||||
|
val arr = FloatArray(3)
|
||||||
|
ColorUtils.colorToHSL(surface.toArgb(), arr)
|
||||||
|
arr[2] = if (dark) {
|
||||||
|
arr[2] - 0.05f
|
||||||
|
} else {
|
||||||
|
arr[2] + 0.02f
|
||||||
|
}.coerceIn(0f, 1f)
|
||||||
|
Color.hsl(arr[0], arr[1], arr[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content(twoPane: Boolean) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val backPress = LocalBackPress.currentOrThrow
|
||||||
|
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
|
||||||
|
val topBarState = rememberTopAppBarState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState),
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.label_player_settings),
|
||||||
|
navigateUp = backPress::invoke,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_search),
|
||||||
|
icon = Icons.Outlined.Search,
|
||||||
|
onClick = { navigator.navigate(SettingsSearchScreen(true), twoPane) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = containerColor,
|
||||||
|
content = { contentPadding ->
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
val indexSelected = if (twoPane) {
|
||||||
|
items.indexOfFirst { it.screen::class == navigator.items.first()::class }
|
||||||
|
.also {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
state.animateScrollToItem(it)
|
||||||
|
if (it > 0) {
|
||||||
|
// Lift scroll
|
||||||
|
topBarState.contentOffset = topBarState.heightOffsetLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
state = state,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
itemsIndexed(
|
||||||
|
items = items,
|
||||||
|
key = { _, item -> item.hashCode() },
|
||||||
|
) { index, item ->
|
||||||
|
val selected = indexSelected == index
|
||||||
|
var modifier: Modifier = Modifier
|
||||||
|
var contentColor = LocalContentColor.current
|
||||||
|
if (twoPane) {
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(24.dp))
|
||||||
|
.then(
|
||||||
|
if (selected) {
|
||||||
|
Modifier.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (selected) {
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
|
title = stringResource(item.titleRes),
|
||||||
|
subtitle = item.formatSubtitle(),
|
||||||
|
icon = item.icon,
|
||||||
|
onPreferenceClick = { navigator.navigate(item.screen, twoPane) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Navigator.navigate(screen: VoyagerScreen, twoPane: Boolean) {
|
||||||
|
if (twoPane) replaceAll(screen) else push(screen)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Item(
|
||||||
|
val titleRes: StringResource,
|
||||||
|
val subtitleRes: StringResource? = null,
|
||||||
|
val formatSubtitle: @Composable () -> String? = { subtitleRes?.let { stringResource(it) } },
|
||||||
|
val icon: ImageVector,
|
||||||
|
val screen: VoyagerScreen,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val items = listOf(
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_internal,
|
||||||
|
subtitleRes = MR.strings.pref_player_internal_summary,
|
||||||
|
icon = Icons.Outlined.PlayCircleOutline,
|
||||||
|
screen = PlayerSettingsPlayerScreen,
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_gestures,
|
||||||
|
subtitleRes = MR.strings.pref_player_gestures_summary,
|
||||||
|
icon = Icons.Outlined.Gesture,
|
||||||
|
screen = PlayerSettingsGesturesScreen,
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_decoder,
|
||||||
|
subtitleRes = MR.strings.pref_player_decoder_summary,
|
||||||
|
icon = Icons.Outlined.Memory,
|
||||||
|
screen = PlayerSettingsDecoderScreen,
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_subtitle,
|
||||||
|
subtitleRes = MR.strings.pref_player_subtitle_summary,
|
||||||
|
icon = Icons.Outlined.Subtitles,
|
||||||
|
screen = PlayerSettingsSubtitleScreen,
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_audio,
|
||||||
|
subtitleRes = MR.strings.pref_player_audio_summary,
|
||||||
|
icon = Icons.Outlined.Audiotrack,
|
||||||
|
screen = PlayerSettingsAudioScreen,
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = MR.strings.pref_player_advanced,
|
||||||
|
subtitleRes = MR.strings.pref_player_advanced_summary,
|
||||||
|
icon = Icons.Outlined.Code,
|
||||||
|
screen = PlayerSettingsAdvancedScreen,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,27 +1,14 @@
|
||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
import eu.kanade.tachiyomi.ui.player.JUST_PLAYER
|
import eu.kanade.tachiyomi.ui.player.JUST_PLAYER
|
||||||
import eu.kanade.tachiyomi.ui.player.MPV_KT
|
import eu.kanade.tachiyomi.ui.player.MPV_KT
|
||||||
import eu.kanade.tachiyomi.ui.player.MPV_KT_PREVIEW
|
import eu.kanade.tachiyomi.ui.player.MPV_KT_PREVIEW
|
||||||
|
@ -35,23 +22,20 @@ import eu.kanade.tachiyomi.ui.player.VLC_PLAYER
|
||||||
import eu.kanade.tachiyomi.ui.player.WEB_VIDEO_CASTER
|
import eu.kanade.tachiyomi.ui.player.WEB_VIDEO_CASTER
|
||||||
import eu.kanade.tachiyomi.ui.player.X_PLAYER
|
import eu.kanade.tachiyomi.ui.player.X_PLAYER
|
||||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
import eu.kanade.tachiyomi.ui.player.viewer.AudioChannels
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
import kotlinx.collections.immutable.toPersistentMap
|
import kotlinx.collections.immutable.toPersistentMap
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object SettingsPlayerScreen : SearchableSettings {
|
object PlayerSettingsPlayerScreen : SearchableSettings {
|
||||||
|
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
@Composable
|
@Composable
|
||||||
override fun getTitleRes() = MR.strings.pref_category_player
|
override fun getTitleRes() = MR.strings.pref_player_internal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
|
@ -77,10 +61,17 @@ object SettingsPlayerScreen : SearchableSettings {
|
||||||
pref = playerPreferences.preserveWatchingPosition(),
|
pref = playerPreferences.preserveWatchingPosition(),
|
||||||
title = stringResource(MR.strings.pref_preserve_watching_position),
|
title = stringResource(MR.strings.pref_preserve_watching_position),
|
||||||
),
|
),
|
||||||
getInternalPlayerGroup(playerPreferences = playerPreferences),
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = playerPreferences.playerFullscreen(),
|
||||||
|
title = stringResource(MR.strings.pref_player_fullscreen),
|
||||||
|
enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = playerPreferences.hideControls(),
|
||||||
|
title = stringResource(MR.strings.pref_player_hide_controls),
|
||||||
|
),
|
||||||
getVolumeAndBrightnessGroup(playerPreferences = playerPreferences),
|
getVolumeAndBrightnessGroup(playerPreferences = playerPreferences),
|
||||||
getOrientationGroup(playerPreferences = playerPreferences),
|
getOrientationGroup(playerPreferences = playerPreferences),
|
||||||
getSeekingGroup(playerPreferences = playerPreferences),
|
|
||||||
if (deviceSupportsPip) getPipGroup(playerPreferences = playerPreferences) else null,
|
if (deviceSupportsPip) getPipGroup(playerPreferences = playerPreferences) else null,
|
||||||
getExternalPlayerGroup(
|
getExternalPlayerGroup(
|
||||||
playerPreferences = playerPreferences,
|
playerPreferences = playerPreferences,
|
||||||
|
@ -89,45 +80,6 @@ object SettingsPlayerScreen : SearchableSettings {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getInternalPlayerGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
|
||||||
val playerFullscreen = playerPreferences.playerFullscreen()
|
|
||||||
val playerHideControls = playerPreferences.hideControls()
|
|
||||||
val playerAudioChannels = playerPreferences.audioChannels()
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(MR.strings.pref_category_internal_player),
|
|
||||||
preferenceItems = persistentListOf(
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = playerFullscreen,
|
|
||||||
title = stringResource(MR.strings.pref_player_fullscreen),
|
|
||||||
enabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P,
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = playerHideControls,
|
|
||||||
title = stringResource(MR.strings.pref_player_hide_controls),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = playerAudioChannels,
|
|
||||||
title = stringResource(MR.strings.pref_player_audio_channels),
|
|
||||||
entries = persistentMapOf(
|
|
||||||
AudioChannels.AutoSafe to stringResource(AudioChannels.AutoSafe.textRes),
|
|
||||||
AudioChannels.Auto to stringResource(AudioChannels.Auto.textRes),
|
|
||||||
AudioChannels.Mono to stringResource(AudioChannels.Mono.textRes),
|
|
||||||
AudioChannels.Stereo to stringResource(AudioChannels.Stereo.textRes),
|
|
||||||
AudioChannels.ReverseStereo to stringResource(AudioChannels.ReverseStereo.textRes),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(MR.strings.pref_category_player_advanced),
|
|
||||||
subtitle = stringResource(MR.strings.pref_category_player_advanced_subtitle),
|
|
||||||
onClick = { navigator.push(AdvancedPlayerSettingsScreen) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getVolumeAndBrightnessGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
private fun getVolumeAndBrightnessGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||||
val enableVolumeBrightnessGestures = playerPreferences.gestureVolumeBrightness()
|
val enableVolumeBrightnessGestures = playerPreferences.gestureVolumeBrightness()
|
||||||
|
@ -228,103 +180,6 @@ object SettingsPlayerScreen : SearchableSettings {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getSeekingGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val enableHorizontalSeekGesture = playerPreferences.gestureHorizontalSeek()
|
|
||||||
val defaultSkipIntroLength by playerPreferences.defaultIntroLength().stateIn(scope).collectAsState()
|
|
||||||
val skipLengthPreference = playerPreferences.skipLengthPreference()
|
|
||||||
val playerSmoothSeek = playerPreferences.playerSmoothSeek()
|
|
||||||
val mediaChapterSeek = playerPreferences.mediaChapterSeek()
|
|
||||||
|
|
||||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
if (showDialog) {
|
|
||||||
SkipIntroLengthDialog(
|
|
||||||
initialSkipIntroLength = defaultSkipIntroLength,
|
|
||||||
onDismissRequest = { showDialog = false },
|
|
||||||
onValueChanged = { skipIntroLength ->
|
|
||||||
playerPreferences.defaultIntroLength().set(skipIntroLength)
|
|
||||||
showDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aniskip
|
|
||||||
val enableAniSkip = playerPreferences.aniSkipEnabled()
|
|
||||||
val enableAutoAniSkip = playerPreferences.autoSkipAniSkip()
|
|
||||||
val enableNetflixAniSkip = playerPreferences.enableNetflixStyleAniSkip()
|
|
||||||
val waitingTimeAniSkip = playerPreferences.waitingTimeAniSkip()
|
|
||||||
|
|
||||||
val isAniSkipEnabled by enableAniSkip.collectAsState()
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(MR.strings.pref_category_player_seeking),
|
|
||||||
preferenceItems = persistentListOf(
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = enableHorizontalSeekGesture,
|
|
||||||
title = stringResource(MR.strings.enable_horizontal_seek_gesture),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(MR.strings.pref_default_intro_length),
|
|
||||||
subtitle = "${defaultSkipIntroLength}s",
|
|
||||||
onClick = { showDialog = true },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = skipLengthPreference,
|
|
||||||
title = stringResource(MR.strings.pref_skip_length),
|
|
||||||
entries = persistentMapOf(
|
|
||||||
30 to stringResource(MR.strings.pref_skip_30),
|
|
||||||
20 to stringResource(MR.strings.pref_skip_20),
|
|
||||||
10 to stringResource(MR.strings.pref_skip_10),
|
|
||||||
5 to stringResource(MR.strings.pref_skip_5),
|
|
||||||
3 to stringResource(MR.strings.pref_skip_3),
|
|
||||||
0 to stringResource(MR.strings.pref_skip_disable),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = playerSmoothSeek,
|
|
||||||
title = stringResource(MR.strings.pref_player_smooth_seek),
|
|
||||||
subtitle = stringResource(MR.strings.pref_player_smooth_seek_summary),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = mediaChapterSeek,
|
|
||||||
title = stringResource(MR.strings.pref_media_control_chapter_seeking),
|
|
||||||
subtitle = stringResource(MR.strings.pref_media_control_chapter_seeking_summary),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.InfoPreference(
|
|
||||||
title = stringResource(MR.strings.pref_category_player_aniskip_info),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = enableAniSkip,
|
|
||||||
title = stringResource(MR.strings.pref_enable_aniskip),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = enableAutoAniSkip,
|
|
||||||
title = stringResource(MR.strings.pref_enable_auto_skip_ani_skip),
|
|
||||||
enabled = isAniSkipEnabled,
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = enableNetflixAniSkip,
|
|
||||||
title = stringResource(MR.strings.pref_enable_netflix_style_aniskip),
|
|
||||||
enabled = isAniSkipEnabled,
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = waitingTimeAniSkip,
|
|
||||||
title = stringResource(MR.strings.pref_waiting_time_aniskip),
|
|
||||||
entries = persistentMapOf(
|
|
||||||
5 to stringResource(MR.strings.pref_waiting_time_aniskip_5),
|
|
||||||
6 to stringResource(MR.strings.pref_waiting_time_aniskip_6),
|
|
||||||
7 to stringResource(MR.strings.pref_waiting_time_aniskip_7),
|
|
||||||
8 to stringResource(MR.strings.pref_waiting_time_aniskip_8),
|
|
||||||
9 to stringResource(MR.strings.pref_waiting_time_aniskip_9),
|
|
||||||
10 to stringResource(MR.strings.pref_waiting_time_aniskip_10),
|
|
||||||
),
|
|
||||||
enabled = isAniSkipEnabled,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getPipGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
private fun getPipGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||||
val enablePip = playerPreferences.enablePip()
|
val enablePip = playerPreferences.enablePip()
|
||||||
|
@ -395,50 +250,6 @@ object SettingsPlayerScreen : SearchableSettings {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SkipIntroLengthDialog(
|
|
||||||
initialSkipIntroLength: Int,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onValueChanged: (skipIntroLength: Int) -> Unit,
|
|
||||||
) {
|
|
||||||
val skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) }
|
|
||||||
var newLength = 0
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(MR.strings.pref_intro_length)) },
|
|
||||||
text = {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
content = {
|
|
||||||
WheelTextPicker(
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
items = remember { 0..255 }.map {
|
|
||||||
stringResource(
|
|
||||||
MR.strings.seconds_short,
|
|
||||||
it,
|
|
||||||
)
|
|
||||||
}.toImmutableList(),
|
|
||||||
onSelectionChanged = {
|
|
||||||
newLength = it
|
|
||||||
},
|
|
||||||
startIndex = skipIntroLengthValue,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(MR.strings.action_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { onValueChanged(newLength) }) {
|
|
||||||
Text(text = stringResource(MR.strings.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val externalPlayers = listOf(
|
val externalPlayers = listOf(
|
|
@ -0,0 +1,46 @@
|
||||||
|
package eu.kanade.presentation.more.settings.screen.player
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object PlayerSettingsSubtitleScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
override fun getTitleRes() = MR.strings.pref_player_subtitle
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val playerPreferences = remember { Injekt.get<PlayerPreferences>() }
|
||||||
|
|
||||||
|
val langPref = playerPreferences.preferredSubLanguages()
|
||||||
|
val whitelist = playerPreferences.subtitleWhitelist()
|
||||||
|
val blacklist = playerPreferences.subtitleBlacklist()
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.EditTextInfoPreference(
|
||||||
|
pref = langPref,
|
||||||
|
title = stringResource(MR.strings.pref_player_subtitle_lang),
|
||||||
|
dialogSubtitle = stringResource(MR.strings.pref_player_subtitle_lang_info),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.EditTextInfoPreference(
|
||||||
|
pref = whitelist,
|
||||||
|
title = stringResource(MR.strings.pref_player_subtitle_whitelist),
|
||||||
|
dialogSubtitle = stringResource(MR.strings.pref_player_subtitle_whitelist_info),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.EditTextInfoPreference(
|
||||||
|
pref = blacklist,
|
||||||
|
title = stringResource(MR.strings.pref_player_subtitle_blacklist),
|
||||||
|
dialogSubtitle = stringResource(MR.strings.pref_player_subtitle_blacklist_info),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Cancel
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
|
@ -7,6 +8,7 @@ import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
@ -29,6 +31,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||||
fun EditTextPreferenceWidget(
|
fun EditTextPreferenceWidget(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String?,
|
subtitle: String?,
|
||||||
|
dialogSubtitle: String? = null,
|
||||||
icon: ImageVector?,
|
icon: ImageVector?,
|
||||||
value: String,
|
value: String,
|
||||||
onConfirm: suspend (String) -> Boolean,
|
onConfirm: suspend (String) -> Boolean,
|
||||||
|
@ -52,7 +55,14 @@ fun EditTextPreferenceWidget(
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = title) },
|
title = {
|
||||||
|
Column {
|
||||||
|
Text(text = title)
|
||||||
|
if (dialogSubtitle != null) {
|
||||||
|
Text(text = dialogSubtitle, style = MaterialTheme.typography.bodyMedium)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = textFieldValue,
|
value = textFieldValue,
|
||||||
|
|
|
@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoriesTab
|
import eu.kanade.tachiyomi.ui.category.CategoriesTab
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadsTab
|
import eu.kanade.tachiyomi.ui.download.DownloadsTab
|
||||||
|
import eu.kanade.tachiyomi.ui.setting.PlayerSettingsScreen
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
||||||
import eu.kanade.tachiyomi.ui.stats.StatsTab
|
import eu.kanade.tachiyomi.ui.stats.StatsTab
|
||||||
import eu.kanade.tachiyomi.ui.storage.StorageTab
|
import eu.kanade.tachiyomi.ui.storage.StorageTab
|
||||||
|
@ -79,6 +80,7 @@ data object MoreTab : Tab {
|
||||||
onClickStats = { navigator.push(StatsTab) },
|
onClickStats = { navigator.push(StatsTab) },
|
||||||
onClickStorage = { navigator.push(StorageTab) },
|
onClickStorage = { navigator.push(StorageTab) },
|
||||||
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
|
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
|
||||||
|
onClickPlayerSettings = { navigator.push(PlayerSettingsScreen) },
|
||||||
onClickSettings = { navigator.push(SettingsScreen()) },
|
onClickSettings = { navigator.push(SettingsScreen()) },
|
||||||
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
|
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
|
||||||
)
|
)
|
||||||
|
|
|
@ -624,8 +624,7 @@ class PlayerActivity : BaseActivity() {
|
||||||
when (playerPreferences.videoDebanding().get()) {
|
when (playerPreferences.videoDebanding().get()) {
|
||||||
VideoDebanding.CPU -> MPVLib.setOptionString("vf", "gradfun=radius=12")
|
VideoDebanding.CPU -> MPVLib.setOptionString("vf", "gradfun=radius=12")
|
||||||
VideoDebanding.GPU -> MPVLib.setOptionString("deband", "yes")
|
VideoDebanding.GPU -> MPVLib.setOptionString("deband", "yes")
|
||||||
VideoDebanding.YUV420P -> MPVLib.setOptionString("vf", "format=yuv420p")
|
VideoDebanding.NONE -> {}
|
||||||
VideoDebanding.DISABLED -> {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPlayerStatisticsPage = playerPreferences.playerStatisticsPage().get()
|
val currentPlayerStatisticsPage = playerPreferences.playerStatisticsPage().get()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.ui.player.viewer.AspectState
|
||||||
import eu.kanade.tachiyomi.ui.player.viewer.AudioChannels
|
import eu.kanade.tachiyomi.ui.player.viewer.AudioChannels
|
||||||
import eu.kanade.tachiyomi.ui.player.viewer.HwDecState
|
import eu.kanade.tachiyomi.ui.player.viewer.HwDecState
|
||||||
import eu.kanade.tachiyomi.ui.player.viewer.InvertedPlayback
|
import eu.kanade.tachiyomi.ui.player.viewer.InvertedPlayback
|
||||||
|
import eu.kanade.tachiyomi.ui.player.viewer.SingleActionGesture
|
||||||
import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding
|
import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.core.common.preference.getEnum
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
@ -11,33 +12,29 @@ import tachiyomi.core.common.preference.getEnum
|
||||||
class PlayerPreferences(
|
class PlayerPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
// ==== Internal player ====
|
||||||
|
|
||||||
fun preserveWatchingPosition() = preferenceStore.getBoolean(
|
fun preserveWatchingPosition() = preferenceStore.getBoolean(
|
||||||
"pref_preserve_watching_position",
|
"pref_preserve_watching_position",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
fun progressPreference() = preferenceStore.getFloat("pref_progress_preference", 0.85F)
|
||||||
|
|
||||||
fun enablePip() = preferenceStore.getBoolean("pref_enable_pip", true)
|
fun playerFullscreen() = preferenceStore.getBoolean("player_fullscreen", true)
|
||||||
fun pipEpisodeToasts() = preferenceStore.getBoolean("pref_pip_episode_toasts", true)
|
fun hideControls() = preferenceStore.getBoolean("player_hide_controls", false)
|
||||||
fun pipOnExit() = preferenceStore.getBoolean("pref_pip_on_exit", false)
|
|
||||||
fun pipReplaceWithPrevious() = preferenceStore.getBoolean("pip_replace_with_previous", false)
|
|
||||||
|
|
||||||
|
// Internal player - Volume and brightness
|
||||||
|
|
||||||
|
fun gestureVolumeBrightness() = preferenceStore.getBoolean(
|
||||||
|
"pref_gesture_volume_brightness",
|
||||||
|
true,
|
||||||
|
)
|
||||||
fun rememberPlayerBrightness() = preferenceStore.getBoolean("pref_remember_brightness", false)
|
fun rememberPlayerBrightness() = preferenceStore.getBoolean("pref_remember_brightness", false)
|
||||||
fun playerBrightnessValue() = preferenceStore.getFloat("player_brightness_value", -1.0F)
|
fun playerBrightnessValue() = preferenceStore.getFloat("player_brightness_value", -1.0F)
|
||||||
|
|
||||||
fun rememberPlayerVolume() = preferenceStore.getBoolean("pref_remember_volume", false)
|
fun rememberPlayerVolume() = preferenceStore.getBoolean("pref_remember_volume", false)
|
||||||
fun playerVolumeValue() = preferenceStore.getFloat("player_volume_value", -1.0F)
|
fun playerVolumeValue() = preferenceStore.getFloat("player_volume_value", -1.0F)
|
||||||
|
|
||||||
fun audioChannels() = preferenceStore.getEnum("pref_audio_config", AudioChannels.AutoSafe)
|
// Internal player - Orientation
|
||||||
|
|
||||||
fun autoplayEnabled() = preferenceStore.getBoolean("pref_auto_play_enabled", false)
|
|
||||||
|
|
||||||
fun invertedPlayback() = preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE)
|
|
||||||
|
|
||||||
fun mpvConf() = preferenceStore.getString("pref_mpv_conf", "")
|
|
||||||
|
|
||||||
fun mpvInput() = preferenceStore.getString("pref_mpv_input", "")
|
|
||||||
|
|
||||||
fun subSelectConf() = preferenceStore.getString("pref_sub_select_conf", "")
|
|
||||||
|
|
||||||
fun defaultPlayerOrientationType() = preferenceStore.getInt(
|
fun defaultPlayerOrientationType() = preferenceStore.getInt(
|
||||||
"pref_default_player_orientation_type_key",
|
"pref_default_player_orientation_type_key",
|
||||||
|
@ -47,36 +44,23 @@ class PlayerPreferences(
|
||||||
"pref_adjust_orientation_video_dimensions",
|
"pref_adjust_orientation_video_dimensions",
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun defaultPlayerOrientationLandscape() = preferenceStore.getInt(
|
|
||||||
"pref_default_player_orientation_landscape_key",
|
|
||||||
6,
|
|
||||||
)
|
|
||||||
fun defaultPlayerOrientationPortrait() = preferenceStore.getInt(
|
fun defaultPlayerOrientationPortrait() = preferenceStore.getInt(
|
||||||
"pref_default_player_orientation_portrait_key",
|
"pref_default_player_orientation_portrait_key",
|
||||||
7,
|
7,
|
||||||
)
|
)
|
||||||
|
fun defaultPlayerOrientationLandscape() = preferenceStore.getInt(
|
||||||
fun playerSpeed() = preferenceStore.getFloat("pref_player_speed", 1F)
|
"pref_default_player_orientation_landscape_key",
|
||||||
|
6,
|
||||||
fun playerSmoothSeek() = preferenceStore.getBoolean("pref_player_smooth_seek", false)
|
|
||||||
|
|
||||||
fun mediaChapterSeek() = preferenceStore.getBoolean("pref_media_control_chapter_seeking", false)
|
|
||||||
|
|
||||||
fun aspectState() = preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT)
|
|
||||||
|
|
||||||
fun playerFullscreen() = preferenceStore.getBoolean("player_fullscreen", true)
|
|
||||||
|
|
||||||
fun hideControls() = preferenceStore.getBoolean("player_hide_controls", false)
|
|
||||||
|
|
||||||
fun screenshotSubtitles() = preferenceStore.getBoolean("pref_screenshot_subtitles", false)
|
|
||||||
|
|
||||||
fun gestureVolumeBrightness() = preferenceStore.getBoolean(
|
|
||||||
"pref_gesture_volume_brightness",
|
|
||||||
true,
|
|
||||||
)
|
)
|
||||||
fun gestureHorizontalSeek() = preferenceStore.getBoolean("pref_gesture_horizontal_seek", true)
|
|
||||||
fun playerStatisticsPage() = preferenceStore.getInt("pref_player_statistics_page", 0)
|
// Internal player - PiP
|
||||||
|
|
||||||
|
fun enablePip() = preferenceStore.getBoolean("pref_enable_pip", true)
|
||||||
|
fun pipEpisodeToasts() = preferenceStore.getBoolean("pref_pip_episode_toasts", true)
|
||||||
|
fun pipOnExit() = preferenceStore.getBoolean("pref_pip_on_exit", false)
|
||||||
|
fun pipReplaceWithPrevious() = preferenceStore.getBoolean("pip_replace_with_previous", false)
|
||||||
|
|
||||||
|
// Internal player - External player
|
||||||
|
|
||||||
fun alwaysUseExternalPlayer() = preferenceStore.getBoolean(
|
fun alwaysUseExternalPlayer() = preferenceStore.getBoolean(
|
||||||
"pref_always_use_external_player",
|
"pref_always_use_external_player",
|
||||||
|
@ -84,22 +68,78 @@ class PlayerPreferences(
|
||||||
)
|
)
|
||||||
fun externalPlayerPreference() = preferenceStore.getString("external_player_preference", "")
|
fun externalPlayerPreference() = preferenceStore.getString("external_player_preference", "")
|
||||||
|
|
||||||
fun progressPreference() = preferenceStore.getFloat("pref_progress_preference", 0.85F)
|
// ==== Gestures ====
|
||||||
|
// Gestures - Seeking
|
||||||
|
|
||||||
fun defaultIntroLength() = preferenceStore.getInt("pref_default_intro_length", 85)
|
|
||||||
fun skipLengthPreference() = preferenceStore.getInt("pref_skip_length_preference", 10)
|
fun skipLengthPreference() = preferenceStore.getInt("pref_skip_length_preference", 10)
|
||||||
|
fun gestureHorizontalSeek() = preferenceStore.getBoolean("pref_gesture_horizontal_seek", true)
|
||||||
|
fun defaultIntroLength() = preferenceStore.getInt("pref_default_intro_length", 85)
|
||||||
|
fun playerSmoothSeek() = preferenceStore.getBoolean("pref_player_smooth_seek", false)
|
||||||
|
fun mediaChapterSeek() = preferenceStore.getBoolean("pref_media_control_chapter_seeking", false)
|
||||||
|
|
||||||
fun aniSkipEnabled() = preferenceStore.getBoolean("pref_enable_ani_skip", false)
|
fun aniSkipEnabled() = preferenceStore.getBoolean("pref_enable_ani_skip", false)
|
||||||
fun autoSkipAniSkip() = preferenceStore.getBoolean("pref_enable_auto_skip_ani_skip", false)
|
fun autoSkipAniSkip() = preferenceStore.getBoolean("pref_enable_auto_skip_ani_skip", false)
|
||||||
fun waitingTimeAniSkip() = preferenceStore.getInt("pref_waiting_time_aniskip", 5)
|
|
||||||
fun enableNetflixStyleAniSkip() = preferenceStore.getBoolean(
|
fun enableNetflixStyleAniSkip() = preferenceStore.getBoolean(
|
||||||
"pref_enable_netflixStyle_aniskip",
|
"pref_enable_netflixStyle_aniskip",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
fun waitingTimeAniSkip() = preferenceStore.getInt("pref_waiting_time_aniskip", 5)
|
||||||
|
|
||||||
|
// Gestures - Double tap
|
||||||
|
|
||||||
|
fun leftDoubleTapGesture() = preferenceStore.getEnum("pref_left_double_tap", SingleActionGesture.Seek)
|
||||||
|
fun centerDoubleTapGesture() = preferenceStore.getEnum("pref_center_double_tap", SingleActionGesture.PlayPause)
|
||||||
|
fun rightDoubleTapGesture() = preferenceStore.getEnum("pref_right_double_tap", SingleActionGesture.Seek)
|
||||||
|
|
||||||
|
// Gestures - Media controls
|
||||||
|
|
||||||
|
fun mediaPreviousGesture() = preferenceStore.getEnum("pref_media_previous", SingleActionGesture.Switch)
|
||||||
|
fun mediaPlayPauseGesture() = preferenceStore.getEnum("pref_media_playpause", SingleActionGesture.PlayPause)
|
||||||
|
fun mediaNextGesture() = preferenceStore.getEnum("pref_media_next", SingleActionGesture.Switch)
|
||||||
|
|
||||||
|
// ==== Decoder ====
|
||||||
|
|
||||||
|
fun tryHWDecoding() = preferenceStore.getBoolean("pref_try_hwdec", true)
|
||||||
|
fun gpuNext() = preferenceStore.getBoolean("pref_gpu_next", false)
|
||||||
|
fun videoDebanding() = preferenceStore.getEnum("pref_video_debanding", VideoDebanding.NONE)
|
||||||
|
fun useYUV420P() = preferenceStore.getBoolean("use_yuv420p", true)
|
||||||
|
|
||||||
|
// ==== Subtitle ====
|
||||||
|
|
||||||
|
fun preferredSubLanguages() = preferenceStore.getString("pref_subtitle_lang", "")
|
||||||
|
fun subtitleWhitelist() = preferenceStore.getString("pref_subtitle_whitelist", "")
|
||||||
|
fun subtitleBlacklist() = preferenceStore.getString("pref_subtitle_blacklist", "")
|
||||||
|
|
||||||
|
// ==== Audio ====
|
||||||
|
|
||||||
|
fun preferredAudioLanguages() = preferenceStore.getString("pref_audio_lang", "")
|
||||||
|
fun enablePitchCorrection() = preferenceStore.getBoolean("pref_audio_pitch_correction", true)
|
||||||
|
fun audioChannels() = preferenceStore.getEnum("pref_audio_config", AudioChannels.AutoSafe)
|
||||||
|
fun volumeBoostCap() = preferenceStore.getInt("pref_audio_volume_boost_cap", 30)
|
||||||
|
|
||||||
|
// ==== Advanced ====
|
||||||
|
|
||||||
|
fun mpvScripts() = preferenceStore.getBoolean("mpv_scripts", false)
|
||||||
|
fun mpvConf() = preferenceStore.getString("pref_mpv_conf", "")
|
||||||
|
fun mpvInput() = preferenceStore.getString("pref_mpv_input", "")
|
||||||
|
|
||||||
|
// ==== Non-preferences ====
|
||||||
|
|
||||||
|
fun autoplayEnabled() = preferenceStore.getBoolean("pref_auto_play_enabled", false)
|
||||||
|
|
||||||
|
fun invertedPlayback() = preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE)
|
||||||
|
|
||||||
|
fun subSelectConf() = preferenceStore.getString("pref_sub_select_conf", "")
|
||||||
|
|
||||||
|
fun playerSpeed() = preferenceStore.getFloat("pref_player_speed", 1F)
|
||||||
|
|
||||||
|
fun aspectState() = preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT)
|
||||||
|
|
||||||
|
fun screenshotSubtitles() = preferenceStore.getBoolean("pref_screenshot_subtitles", false)
|
||||||
|
|
||||||
|
fun playerStatisticsPage() = preferenceStore.getInt("pref_player_statistics_page", 0)
|
||||||
|
|
||||||
fun hardwareDecoding() = preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec)
|
fun hardwareDecoding() = preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec)
|
||||||
fun videoDebanding() = preferenceStore.getEnum("pref_video_debanding", VideoDebanding.DISABLED)
|
|
||||||
fun gpuNext() = preferenceStore.getBoolean("pref_gpu_next", false)
|
|
||||||
|
|
||||||
fun rememberAudioDelay() = preferenceStore.getBoolean("pref_remember_audio_delay", false)
|
fun rememberAudioDelay() = preferenceStore.getBoolean("pref_remember_audio_delay", false)
|
||||||
fun audioDelay() = preferenceStore.getInt("pref_audio_delay", 0)
|
fun audioDelay() = preferenceStore.getInt("pref_audio_delay", 0)
|
||||||
|
@ -122,8 +162,6 @@ class PlayerPreferences(
|
||||||
fun borderColorSubtitles() = preferenceStore.getInt("pref_border_color_subtitles", -16777216)
|
fun borderColorSubtitles() = preferenceStore.getInt("pref_border_color_subtitles", -16777216)
|
||||||
fun backgroundColorSubtitles() = preferenceStore.getInt("pref_background_color_subtitles", 0)
|
fun backgroundColorSubtitles() = preferenceStore.getInt("pref_background_color_subtitles", 0)
|
||||||
|
|
||||||
fun mpvScripts() = preferenceStore.getBoolean("mpv_scripts", false)
|
|
||||||
|
|
||||||
fun brightnessFilter() = preferenceStore.getInt("pref_player_filter_brightness")
|
fun brightnessFilter() = preferenceStore.getInt("pref_player_filter_brightness")
|
||||||
fun saturationFilter() = preferenceStore.getInt("pref_player_filter_saturation")
|
fun saturationFilter() = preferenceStore.getInt("pref_player_filter_saturation")
|
||||||
fun contrastFilter() = preferenceStore.getInt("pref_player_filter_contrast")
|
fun contrastFilter() = preferenceStore.getInt("pref_player_filter_contrast")
|
||||||
|
|
|
@ -103,10 +103,32 @@ enum class PlayerStatsPage(val stringRes: StringResource) {
|
||||||
* Player's debanding handler
|
* Player's debanding handler
|
||||||
*/
|
*/
|
||||||
enum class VideoDebanding(val stringRes: StringResource) {
|
enum class VideoDebanding(val stringRes: StringResource) {
|
||||||
DISABLED(stringRes = MR.strings.pref_debanding_disabled),
|
NONE(stringRes = MR.strings.pref_debanding_none),
|
||||||
CPU(stringRes = MR.strings.pref_debanding_cpu),
|
CPU(stringRes = MR.strings.pref_debanding_cpu),
|
||||||
GPU(stringRes = MR.strings.pref_debanding_gpu),
|
GPU(stringRes = MR.strings.pref_debanding_gpu),
|
||||||
YUV420P(stringRes = MR.strings.pref_debanding_yuv420p),
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action performed by a button, like double tap or media controls
|
||||||
|
*/
|
||||||
|
enum class SingleActionGesture(val stringRes: StringResource) {
|
||||||
|
None(stringRes = MR.strings.single_action_none),
|
||||||
|
Seek(stringRes = MR.strings.single_action_seek),
|
||||||
|
PlayPause(stringRes = MR.strings.single_action_playpause),
|
||||||
|
Switch(stringRes = MR.strings.single_action_switch),
|
||||||
|
Custom(stringRes = MR.strings.single_action_custom),
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key codes sent through the `Custom` option in gestures
|
||||||
|
*/
|
||||||
|
enum class CustomKeyCodes(val keyCode: String) {
|
||||||
|
DoubleTapLeft("0x10001"),
|
||||||
|
DoubleTapCenter("0x10002"),
|
||||||
|
DoubleTapRight("0x10003"),
|
||||||
|
MediaPrevious("0x10004"),
|
||||||
|
MediaPlay("0x10005"),
|
||||||
|
MediaNext("0x10006"),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class AudioChannels(val propertyName: String, val propertyValue: String, val textRes: StringResource) {
|
enum class AudioChannels(val propertyName: String, val propertyValue: String, val textRes: StringResource) {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.more.settings.screen.player.PlayerSettingsMainScreen
|
||||||
|
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
|
||||||
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
|
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||||
|
|
||||||
|
object PlayerSettingsScreen : Screen() {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val parentNavigator = LocalNavigator.currentOrThrow
|
||||||
|
if (!isTabletUi()) {
|
||||||
|
Navigator(
|
||||||
|
screen = PlayerSettingsMainScreen,
|
||||||
|
content = {
|
||||||
|
val pop: () -> Unit = {
|
||||||
|
if (it.canPop) {
|
||||||
|
it.pop()
|
||||||
|
} else {
|
||||||
|
parentNavigator.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(LocalBackPress provides pop) {
|
||||||
|
DefaultNavigatorScreenTransition(navigator = it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Navigator(
|
||||||
|
screen = PlayerSettingsMainScreen,
|
||||||
|
) {
|
||||||
|
val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
|
||||||
|
TwoPanelBox(
|
||||||
|
modifier = Modifier
|
||||||
|
.windowInsetsPadding(insets)
|
||||||
|
.consumeWindowInsets(insets),
|
||||||
|
startContent = {
|
||||||
|
CompositionLocalProvider(LocalBackPress provides parentNavigator::pop) {
|
||||||
|
PlayerSettingsMainScreen.Content(twoPane = true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endContent = { DefaultNavigatorScreenTransition(navigator = it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ class EnumsMigration : Migration {
|
||||||
|
|
||||||
preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE).set(invertedPlayback)
|
preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE).set(invertedPlayback)
|
||||||
preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec).set(hardwareDecoding)
|
preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec).set(hardwareDecoding)
|
||||||
preferenceStore.getEnum("pref_video_debanding", VideoDebanding.DISABLED).set(videoDebanding)
|
preferenceStore.getEnum("pref_video_debanding", VideoDebanding.NONE).set(videoDebanding)
|
||||||
preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT).set(aspectState)
|
preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT).set(aspectState)
|
||||||
preferenceStore.getBoolean("pref_gpu_next", false).set(gpuNext.get())
|
preferenceStore.getBoolean("pref_gpu_next", false).set(gpuNext.get())
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,4 +44,5 @@ val migrations: List<Migration>
|
||||||
LogOutMALMigration(),
|
LogOutMALMigration(),
|
||||||
EnumsMigration(),
|
EnumsMigration(),
|
||||||
TrustExtensionRepositoryMigration(),
|
TrustExtensionRepositoryMigration(),
|
||||||
|
VideoPlayerPreferenceMigration(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package mihon.core.migration.migrations
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import mihon.core.migration.Migration
|
||||||
|
import mihon.core.migration.MigrationContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class VideoPlayerPreferenceMigration : Migration {
|
||||||
|
override val version = 126f
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
|
||||||
|
val context = migrationContext.get<Application>() ?: return false
|
||||||
|
val playerPreferences = migrationContext.get<PlayerPreferences>() ?: return false
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
val subtitleConf = prefs.getString("pref_sub_select_conf", "")!!
|
||||||
|
val subtitleData = try {
|
||||||
|
json.decodeFromString<SubConfig>(subtitleConf)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.edit {
|
||||||
|
putString(playerPreferences.preferredSubLanguages().key(), subtitleData.lang.joinToString(","))
|
||||||
|
putString(playerPreferences.subtitleWhitelist().key(), subtitleData.whitelist.joinToString(","))
|
||||||
|
putString(playerPreferences.subtitleBlacklist().key(), subtitleData.blacklist.joinToString(","))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SubConfig(
|
||||||
|
val lang: List<String> = emptyList(),
|
||||||
|
val blacklist: List<String> = emptyList(),
|
||||||
|
val whitelist: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -178,6 +178,119 @@
|
||||||
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
|
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
|
||||||
<string name="onboarding_guides_returning_user">Reinstalling %s?</string>
|
<string name="onboarding_guides_returning_user">Reinstalling %s?</string>
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
|
|
||||||
|
<!-- Player settings -->
|
||||||
|
<string name="label_player_settings">Player settings</string>
|
||||||
|
<string name="action_search_player_settings">Search player settings</string>
|
||||||
|
|
||||||
|
<!-- Player settings - internal player -->
|
||||||
|
<string name="pref_player_internal">Internal player</string>
|
||||||
|
<string name="pref_player_internal_summary">Progress, controls, orientation</string>
|
||||||
|
<string name="pref_progress_mark_as_seen">At what point to mark the episode as seen</string>
|
||||||
|
<string name="pref_progress_70" translatable="false">70%</string>
|
||||||
|
<string name="pref_progress_75" translatable="false">75%</string>
|
||||||
|
<string name="pref_progress_80" translatable="false">80%</string>
|
||||||
|
<string name="pref_progress_85" translatable="false">85%</string>
|
||||||
|
<string name="pref_progress_90" translatable="false">90%</string>
|
||||||
|
<string name="pref_progress_95" translatable="false">95%</string>
|
||||||
|
<string name="pref_progress_100" translatable="false">100%</string>
|
||||||
|
<string name="pref_preserve_watching_position">Preserve watch position on seen episodes</string>
|
||||||
|
<string name="pref_player_fullscreen">Show content in display cutout</string>
|
||||||
|
<string name="pref_player_hide_controls">Hide player controls when opening the player</string>
|
||||||
|
|
||||||
|
<string name="pref_category_volume_brightness">Volume and Brightness</string>
|
||||||
|
<string name="enable_volume_brightness_gestures">Enable Volume and Brightness Gestures</string>
|
||||||
|
<string name="pref_remember_brightness">Remember and switch to the last used brightness</string>
|
||||||
|
<string name="pref_remember_volume">Remember and switch to the last used volume</string>
|
||||||
|
|
||||||
|
<string name="pref_category_player_orientation">Orientation</string>
|
||||||
|
<string name="pref_default_player_orientation">Default orientation</string>
|
||||||
|
<string name="pref_adjust_orientation_video_dimensions">Adjust the orientation based on a video\'s dimensions</string>
|
||||||
|
<string name="pref_default_portrait_orientation">Default portrait</string>
|
||||||
|
<string name="pref_default_landscape_orientation">Default landscape</string>
|
||||||
|
<string name="rotation_reverse_landscape">Reverse landscape</string>
|
||||||
|
<string name="rotation_sensor_portrait">Sensor portrait</string>
|
||||||
|
<string name="rotation_sensor_landscape">Sensor landscape</string>
|
||||||
|
|
||||||
|
<string name="pref_category_pip">Picture-in-Picture (PiP)</string>
|
||||||
|
<string name="pref_enable_pip">Enable the use of PiP mode</string>
|
||||||
|
<string name="pref_pip_episode_toasts">Show episode toasts when switching episodes in PiP mode</string>
|
||||||
|
<string name="pref_pip_on_exit">Automatically switch to PiP mode on exiting the player</string>
|
||||||
|
<string name="pref_pip_replace_with_previous">Replaces the "Skip 10 seconds" option with "Previous episode"</string>
|
||||||
|
|
||||||
|
<string name="pref_category_external_player">External player</string>
|
||||||
|
<string name="pref_always_use_external_player">Always use external player</string>
|
||||||
|
<string name="pref_external_player_preference">External player preference</string>
|
||||||
|
|
||||||
|
<!-- Player Settings - Gestures -->
|
||||||
|
<string name="pref_player_gestures">Gestures</string>
|
||||||
|
<string name="pref_player_gestures_summary">Seeking, double tap, media controls</string>
|
||||||
|
|
||||||
|
<string name="pref_category_double_tap">Double tap</string>
|
||||||
|
<string name="pref_left_double_tap">Double tap (left)</string>
|
||||||
|
<string name="pref_center_double_tap">Double tap (center)</string>
|
||||||
|
<string name="pref_right_double_tap">Double tap (right)</string>
|
||||||
|
<string name="pref_double_tap_info">When a tap gesture is set to "Custom", it can be bound through input.conf. The key codes are 0x10001 for left, 0x10002 for center, and 0x10003 for right.</string>
|
||||||
|
<string name="single_action_none">None</string>
|
||||||
|
<string name="single_action_seek">Seek</string>
|
||||||
|
<string name="single_action_playpause">Play/Pause</string>
|
||||||
|
<string name="single_action_switch">Switch episode</string>
|
||||||
|
<string name="single_action_custom">Custom</string>
|
||||||
|
|
||||||
|
<string name="pref_category_media_controls">Media controls</string>
|
||||||
|
<string name="pref_media_previous">Previous</string>
|
||||||
|
<string name="pref_media_playpause">Play/Pause</string>
|
||||||
|
<string name="pref_media_next">Next</string>
|
||||||
|
<string name="pref_media_info">When a media control is set to "Custom", it can be bound through input.conf. The key codes are 0x10004 for previous, 0x10005 for play/pause, and 0x10006 for next.</string>
|
||||||
|
|
||||||
|
<!-- Player Settings - Decoder -->
|
||||||
|
<string name="pref_player_decoder">Decoder</string>
|
||||||
|
<string name="pref_player_decoder_summary">Hardware decoding, pixel format, debanding</string>
|
||||||
|
<string name="pref_try_hw">Try hardware decoding</string>
|
||||||
|
<string name="pref_gpu_next_title">Enable gpu-next</string>
|
||||||
|
<string name="pref_gpu_next_subtitle">A new video rendering backend</string>
|
||||||
|
<string name="pref_debanding_title">Debanding</string>
|
||||||
|
<string name="pref_debanding_none">None</string>
|
||||||
|
<string name="pref_debanding_cpu">CPU</string>
|
||||||
|
<string name="pref_debanding_gpu">GPU</string>
|
||||||
|
<string name="pref_debanding_yuv420p">YUV420P</string>
|
||||||
|
<string name="pref_use_yuv420p_title">Use YUV420P pixel format</string>
|
||||||
|
<string name="pref_use_yuv420p_subtitle">May fix black screens on some video codecs, can also improve performance at the cost of quality</string>
|
||||||
|
|
||||||
|
<!-- Player settings - Subtitles -->
|
||||||
|
<string name="pref_player_subtitle">Subtitles</string>
|
||||||
|
<string name="pref_player_subtitle_summary">Preferred languages, whitelist, blacklist</string>
|
||||||
|
<string name="pref_player_subtitle_lang">Preferred languages</string>
|
||||||
|
<string name="pref_player_subtitle_lang_info">Subtitle language(s) to be selected by default on a video with multiple subtitles, Two- or three-letter languages codes work. Multiple values can be delimited by a comma.</string>
|
||||||
|
<string name="pref_player_subtitle_whitelist">Whitelist</string>
|
||||||
|
<string name="pref_player_subtitle_whitelist_info">Whitelist for subtitles. If a whitelist is defined, the first subtitle that contains a whitelisted word will be used. Multiple values can be delimited by a comma.</string>
|
||||||
|
<string name="pref_player_subtitle_blacklist">Blacklist</string>
|
||||||
|
<string name="pref_player_subtitle_blacklist_info">Blacklist for subtitles. If a blacklist is defined, all subtitles that contains a blacklisted word will be filtered out. Multiple values can be delimited by a comma.</string>
|
||||||
|
|
||||||
|
<!-- Player settings - Audio -->
|
||||||
|
<string name="pref_player_audio">Audio</string>
|
||||||
|
<string name="pref_player_audio_summary">Preferred languages, pitch correction, audio channels</string>
|
||||||
|
<string name="pref_player_audio_lang">Preferred languages</string>
|
||||||
|
<string name="pref_player_audio_lang_info">Audio language(s) to be selected by default on a video with multiple audio streams, Two- or three-letter languages codes work. Multiple values can be delimited by a comma.</string>
|
||||||
|
<string name="pref_player_audio_pitch_correction">Enable audio pitch correction</string>
|
||||||
|
<string name="pref_player_audio_pitch_correction_summary">Prevents the audio from becoming high-pitched at faster speeds and low-pitched at slower speeds</string>
|
||||||
|
<string name="pref_player_audio_channels">Audio channels</string>
|
||||||
|
<string name="pref_player_audio_channels_auto_safe">Auto-safe</string>
|
||||||
|
<string name="pref_player_audio_channels_auto">Auto</string>
|
||||||
|
<string name="pref_player_audio_channels_mono">Mono</string>
|
||||||
|
<string name="pref_player_audio_channels_stereo">Stereo</string>
|
||||||
|
<string name="pref_player_audio_channels_reverse_stereo">Reverse stereo</string>
|
||||||
|
<string name="pref_player_audio_boost_cap">Volume boost cap</string>
|
||||||
|
|
||||||
|
<!-- Player settings - Advanced -->
|
||||||
|
<string name="pref_player_advanced">Advanced</string>
|
||||||
|
<string name="pref_player_advanced_summary">Scripts, mpv.conf, input.conf</string>
|
||||||
|
<string name="pref_mpv_scripts">Enable MPV scripts</string>
|
||||||
|
<string name="pref_mpv_scripts_summary">Needs external storage permission.</string>
|
||||||
|
<string name="pref_mpv_conf">Edit MPV configuration file for further player settings</string>
|
||||||
|
<string name="pref_reset_mpv_conf">Reset MPV configuration file</string>
|
||||||
|
<string name="pref_mpv_input">Edit MPV input file for keyboard mapping configuration</string>
|
||||||
|
|
||||||
<!-- Subsections -->
|
<!-- Subsections -->
|
||||||
<string name="pref_category_general">General</string>
|
<string name="pref_category_general">General</string>
|
||||||
<string name="pref_category_appearance">Appearance</string>
|
<string name="pref_category_appearance">Appearance</string>
|
||||||
|
@ -900,28 +1013,10 @@
|
||||||
<string name="pref_manga_library_update_categories_details">Manga in excluded categories will not be updated even if they are also in included categories.</string>
|
<string name="pref_manga_library_update_categories_details">Manga in excluded categories will not be updated even if they are also in included categories.</string>
|
||||||
<string name="pref_anime_library_update_categories_details">Anime in excluded categories will not be updated even if they are also in included categories.</string>
|
<string name="pref_anime_library_update_categories_details">Anime in excluded categories will not be updated even if they are also in included categories.</string>
|
||||||
<string name="unofficial_extension_message_aniyomi">This extension is not from the official list.</string>
|
<string name="unofficial_extension_message_aniyomi">This extension is not from the official list.</string>
|
||||||
<string name="rotation_reverse_landscape">Reverse landscape</string>
|
|
||||||
<string name="rotation_sensor_portrait">Sensor portrait</string>
|
|
||||||
<string name="rotation_sensor_landscape">Sensor landscape</string>
|
|
||||||
<string name="unofficial_anime_extension_message">This extension is not from the official list.</string>
|
<string name="unofficial_anime_extension_message">This extension is not from the official list.</string>
|
||||||
<string name="pref_category_player">Player</string>
|
<string name="pref_category_player">Player</string>
|
||||||
<string name="pref_category_progress">Progress</string>
|
<string name="pref_category_progress">Progress</string>
|
||||||
<string name="pref_progress_mark_as_seen">At what point to mark the episode as seen</string>
|
|
||||||
<string name="pref_progress_70" translatable="false">70%</string>
|
|
||||||
<string name="pref_progress_75" translatable="false">75%</string>
|
|
||||||
<string name="pref_progress_80" translatable="false">80%</string>
|
|
||||||
<string name="pref_progress_85" translatable="false">85%</string>
|
|
||||||
<string name="pref_progress_90" translatable="false">90%</string>
|
|
||||||
<string name="pref_progress_95" translatable="false">95%</string>
|
|
||||||
<string name="pref_progress_100" translatable="false">100%</string>
|
|
||||||
<string name="pref_preserve_watching_position">Preserve watch position on seen episodes</string>
|
|
||||||
<string name="pref_category_player_orientation">Orientation</string>
|
|
||||||
<string name="pref_default_player_orientation">Default orientation</string>
|
|
||||||
<string name="pref_adjust_orientation_video_dimensions">Adjust the orientation based on a video\'s dimensions</string>
|
|
||||||
<string name="pref_default_portrait_orientation">Default portrait</string>
|
|
||||||
<string name="pref_default_landscape_orientation">Default landscape</string>
|
|
||||||
<string name="pref_category_internal_player">Internal player</string>
|
<string name="pref_category_internal_player">Internal player</string>
|
||||||
<string name="pref_category_volume_brightness">Volume and Brightness</string>
|
|
||||||
<string name="pref_category_player_seeking">Seeking</string>
|
<string name="pref_category_player_seeking">Seeking</string>
|
||||||
<string name="pref_default_intro_length">Default skip intro length</string>
|
<string name="pref_default_intro_length">Default skip intro length</string>
|
||||||
<string name="pref_intro_length">Skip intro length</string>
|
<string name="pref_intro_length">Skip intro length</string>
|
||||||
|
@ -940,28 +1035,7 @@
|
||||||
<string name="mpv_media_title">%1$s - E%2$s - %3$s</string>
|
<string name="mpv_media_title">%1$s - E%2$s - %3$s</string>
|
||||||
<string name="pref_player_smooth_seek">Enable precise seeking</string>
|
<string name="pref_player_smooth_seek">Enable precise seeking</string>
|
||||||
<string name="pref_player_smooth_seek_summary">When enabled, seeking will not focus on keyframes, leading to slower but precise seeking</string>
|
<string name="pref_player_smooth_seek_summary">When enabled, seeking will not focus on keyframes, leading to slower but precise seeking</string>
|
||||||
<string name="pref_player_fullscreen">Show content in display cutout</string>
|
|
||||||
<string name="pref_player_hide_controls">Hide player controls when opening the player</string>
|
|
||||||
<string name="pref_player_audio_channels">Audio channels</string>
|
|
||||||
<string name="pref_player_audio_channels_auto_safe">Auto-safe</string>
|
|
||||||
<string name="pref_player_audio_channels_auto">Auto</string>
|
|
||||||
<string name="pref_player_audio_channels_mono">Mono</string>
|
|
||||||
<string name="pref_player_audio_channels_stereo">Stereo</string>
|
|
||||||
<string name="pref_player_audio_channels_reverse_stereo">Reverse stereo</string>
|
|
||||||
<string name="pref_category_pip">Picture-in-Picture (PiP)</string>
|
|
||||||
<string name="pref_enable_pip">Enable the use of PiP mode</string>
|
|
||||||
<string name="pref_pip_episode_toasts">Show episode toasts when switching episodes in PiP mode</string>
|
|
||||||
<string name="pref_pip_on_exit">Automatically switch to PiP mode on exiting the player</string>
|
|
||||||
<string name="pref_pip_replace_with_previous">Replaces the "Skip 10 seconds" option with "Previous episode"</string>
|
|
||||||
<string name="pref_remember_brightness">Remember and switch to the last used brightness</string>
|
|
||||||
<string name="pref_remember_volume">Remember and switch to the last used volume</string>
|
|
||||||
<string name="pref_mpv_conf">Edit MPV configuration file for further player settings</string>
|
|
||||||
<string name="pref_reset_mpv_conf">Reset MPV configuration file</string>
|
|
||||||
<string name="pref_mpv_input">Edit MPV input file for keyboard mapping configuration</string>
|
|
||||||
<string name="pref_sub_select_conf">Edit advanced subtitle track select configuration</string>
|
<string name="pref_sub_select_conf">Edit advanced subtitle track select configuration</string>
|
||||||
<string name="pref_category_external_player">External player</string>
|
|
||||||
<string name="pref_always_use_external_player">Always use external player</string>
|
|
||||||
<string name="pref_external_player_preference">External player preference</string>
|
|
||||||
<string name="player_title">%1$s - %2$s</string>
|
<string name="player_title">%1$s - %2$s</string>
|
||||||
<string name="episode_download_progress">%1$d%%</string>
|
<string name="episode_download_progress">%1$d%%</string>
|
||||||
<string name="pref_category_delete_chapters">Delete chapters/episodes</string>
|
<string name="pref_category_delete_chapters">Delete chapters/episodes</string>
|
||||||
|
@ -1032,7 +1106,6 @@
|
||||||
<string name="episode_progress_no_total">Progress: %1$s</string>
|
<string name="episode_progress_no_total">Progress: %1$s</string>
|
||||||
<string name="screenshot_header">Take screenshot</string>
|
<string name="screenshot_header">Take screenshot</string>
|
||||||
<string name="screenshot_show_subs">Include Subtitles</string>
|
<string name="screenshot_show_subs">Include Subtitles</string>
|
||||||
<string name="enable_volume_brightness_gestures">Enable Volume and Brightness Gestures</string>
|
|
||||||
<string name="enable_horizontal_seek_gesture">Enable Horizontal Seek Gesture</string>
|
<string name="enable_horizontal_seek_gesture">Enable Horizontal Seek Gesture</string>
|
||||||
<string name="toggle_player_statistics_page">Toggle statistics page</string>
|
<string name="toggle_player_statistics_page">Toggle statistics page</string>
|
||||||
<string name="player_statistics_page_1">Page 1</string>
|
<string name="player_statistics_page_1">Page 1</string>
|
||||||
|
@ -1040,13 +1113,6 @@
|
||||||
<string name="player_statistics_page_3">Page 3</string>
|
<string name="player_statistics_page_3">Page 3</string>
|
||||||
<string name="pref_category_player_advanced">Advanced player settings</string>
|
<string name="pref_category_player_advanced">Advanced player settings</string>
|
||||||
<string name="pref_category_player_advanced_subtitle">Debanding, mpv.conf… etc</string>
|
<string name="pref_category_player_advanced_subtitle">Debanding, mpv.conf… etc</string>
|
||||||
<string name="pref_gpu_next_title">Enable gpu-next</string>
|
|
||||||
<string name="pref_gpu_next_subtitle">A new video rendering backend</string>
|
|
||||||
<string name="pref_debanding_title">Debanding</string>
|
|
||||||
<string name="pref_debanding_disabled">Disabled</string>
|
|
||||||
<string name="pref_debanding_cpu">CPU</string>
|
|
||||||
<string name="pref_debanding_gpu">GPU</string>
|
|
||||||
<string name="pref_debanding_yuv420p">YUV420P</string>
|
|
||||||
<string name="recent_anime_time">Ep. %1$s - %2$s</string>
|
<string name="recent_anime_time">Ep. %1$s - %2$s</string>
|
||||||
<string name="download_insufficient_space">Couldn\'t download due to low storage space</string>
|
<string name="download_insufficient_space">Couldn\'t download due to low storage space</string>
|
||||||
<string name="download_queue_size_warning">Warning: large bulk downloads may lead to sources becoming slower and/or blocking Aniyomi. Tap to learn more.</string>
|
<string name="download_queue_size_warning">Warning: large bulk downloads may lead to sources becoming slower and/or blocking Aniyomi. Tap to learn more.</string>
|
||||||
|
@ -1182,6 +1248,4 @@
|
||||||
<string name="download_threads_number_summary">Number of threads to use for downloading, might get your IP blocked if too high, usually 4 is a good number to avoid heavy load on source servers.</string>
|
<string name="download_threads_number_summary">Number of threads to use for downloading, might get your IP blocked if too high, usually 4 is a good number to avoid heavy load on source servers.</string>
|
||||||
<string name="download_speed_limit">Download speed limit</string>
|
<string name="download_speed_limit">Download speed limit</string>
|
||||||
<string name="download_speed_limit_hint">Set to 0 to disable the speed limit.</string>
|
<string name="download_speed_limit_hint">Set to 0 to disable the speed limit.</string>
|
||||||
<string name="pref_mpv_scripts">Enable MPV scripts</string>
|
|
||||||
<string name="pref_mpv_scripts_summary">Needs external storage permission.</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue