mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-23 05:13:07 +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.Settings
|
||||
import androidx.compose.material.icons.outlined.Storage
|
||||
import androidx.compose.material.icons.outlined.VideoSettings
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -51,6 +52,7 @@ fun MoreScreen(
|
|||
onClickStats: () -> Unit,
|
||||
onClickStorage: () -> Unit,
|
||||
onClickDataAndStorage: () -> Unit,
|
||||
onClickPlayerSettings: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
onClickAbout: () -> Unit,
|
||||
) {
|
||||
|
@ -178,6 +180,13 @@ fun MoreScreen(
|
|||
onPreferenceClick = onClickSettings,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.label_player_settings),
|
||||
icon = Icons.Outlined.VideoSettings,
|
||||
onPreferenceClick = onClickPlayerSettings,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.pref_category_about),
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import eu.kanade.core.preference.asState
|
||||
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
@ -199,6 +200,20 @@ sealed class Preference {
|
|||
val canBeBlank: Boolean = true,
|
||||
) : 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.
|
||||
*/
|
||||
|
|
|
@ -185,6 +185,23 @@ internal fun PreferenceItem(
|
|||
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 -> {
|
||||
val isLoggedIn by item.tracker.let { tracker ->
|
||||
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.Info
|
||||
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.Security
|
||||
import androidx.compose.material.icons.outlined.Storage
|
||||
|
@ -186,12 +185,6 @@ object SettingsMainScreen : Screen() {
|
|||
icon = Icons.Outlined.CollectionsBookmark,
|
||||
screen = SettingsLibraryScreen,
|
||||
),
|
||||
Item(
|
||||
titleRes = MR.strings.pref_category_player,
|
||||
subtitleRes = MR.strings.pref_player_summary,
|
||||
icon = Icons.Outlined.PlayCircleOutline,
|
||||
screen = SettingsPlayerScreen,
|
||||
),
|
||||
Item(
|
||||
titleRes = MR.strings.pref_category_reader,
|
||||
subtitleRes = MR.strings.pref_reader_summary,
|
||||
|
|
|
@ -50,6 +50,12 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.UpIcon
|
||||
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 tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
@ -58,7 +64,9 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
|
||||
|
||||
class SettingsSearchScreen : Screen() {
|
||||
class SettingsSearchScreen(
|
||||
val isPlayer: Boolean = false,
|
||||
) : Screen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
@ -115,7 +123,13 @@ class SettingsSearchScreen : Screen() {
|
|||
decorator = {
|
||||
if (textFieldState.text.isEmpty()) {
|
||||
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,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
|
@ -142,6 +156,7 @@ class SettingsSearchScreen : Screen() {
|
|||
) { contentPadding ->
|
||||
SearchResult(
|
||||
searchKey = textFieldState.text.toString(),
|
||||
isPlayer = isPlayer,
|
||||
listState = listState,
|
||||
contentPadding = contentPadding,
|
||||
) { result ->
|
||||
|
@ -155,6 +170,7 @@ class SettingsSearchScreen : Screen() {
|
|||
@Composable
|
||||
private fun SearchResult(
|
||||
searchKey: String,
|
||||
isPlayer: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
contentPadding: PaddingValues = PaddingValues(),
|
||||
|
@ -164,7 +180,7 @@ private fun SearchResult(
|
|||
|
||||
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) {
|
||||
value = index.asSequence()
|
||||
.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 {
|
||||
return if (node == null) {
|
||||
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(
|
||||
SettingsAppearanceScreen,
|
||||
SettingsLibraryScreen,
|
||||
SettingsReaderScreen,
|
||||
SettingsPlayerScreen,
|
||||
SettingsDownloadScreen,
|
||||
SettingsTrackingScreen,
|
||||
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.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.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 cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
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.MPV_KT
|
||||
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.X_PLAYER
|
||||
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.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 SettingsPlayerScreen : SearchableSettings {
|
||||
object PlayerSettingsPlayerScreen : SearchableSettings {
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
override fun getTitleRes() = MR.strings.pref_category_player
|
||||
override fun getTitleRes() = MR.strings.pref_player_internal
|
||||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
|
@ -77,10 +61,17 @@ object SettingsPlayerScreen : SearchableSettings {
|
|||
pref = playerPreferences.preserveWatchingPosition(),
|
||||
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),
|
||||
getOrientationGroup(playerPreferences = playerPreferences),
|
||||
getSeekingGroup(playerPreferences = playerPreferences),
|
||||
if (deviceSupportsPip) getPipGroup(playerPreferences = playerPreferences) else null,
|
||||
getExternalPlayerGroup(
|
||||
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
|
||||
private fun getVolumeAndBrightnessGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||
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
|
||||
private fun getPipGroup(playerPreferences: PlayerPreferences): Preference.PreferenceGroup {
|
||||
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(
|
|
@ -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
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
|
@ -29,6 +31,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||
fun EditTextPreferenceWidget(
|
||||
title: String,
|
||||
subtitle: String?,
|
||||
dialogSubtitle: String? = null,
|
||||
icon: ImageVector?,
|
||||
value: String,
|
||||
onConfirm: suspend (String) -> Boolean,
|
||||
|
@ -52,7 +55,14 @@ fun EditTextPreferenceWidget(
|
|||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = title) },
|
||||
title = {
|
||||
Column {
|
||||
Text(text = title)
|
||||
if (dialogSubtitle != null) {
|
||||
Text(text = dialogSubtitle, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
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.ui.category.CategoriesTab
|
||||
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.stats.StatsTab
|
||||
import eu.kanade.tachiyomi.ui.storage.StorageTab
|
||||
|
@ -79,6 +80,7 @@ data object MoreTab : Tab {
|
|||
onClickStats = { navigator.push(StatsTab) },
|
||||
onClickStorage = { navigator.push(StorageTab) },
|
||||
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
|
||||
onClickPlayerSettings = { navigator.push(PlayerSettingsScreen) },
|
||||
onClickSettings = { navigator.push(SettingsScreen()) },
|
||||
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
|
||||
)
|
||||
|
|
|
@ -624,8 +624,7 @@ class PlayerActivity : BaseActivity() {
|
|||
when (playerPreferences.videoDebanding().get()) {
|
||||
VideoDebanding.CPU -> MPVLib.setOptionString("vf", "gradfun=radius=12")
|
||||
VideoDebanding.GPU -> MPVLib.setOptionString("deband", "yes")
|
||||
VideoDebanding.YUV420P -> MPVLib.setOptionString("vf", "format=yuv420p")
|
||||
VideoDebanding.DISABLED -> {}
|
||||
VideoDebanding.NONE -> {}
|
||||
}
|
||||
|
||||
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.HwDecState
|
||||
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 tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
|
@ -11,33 +12,29 @@ import tachiyomi.core.common.preference.getEnum
|
|||
class PlayerPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
// ==== Internal player ====
|
||||
|
||||
fun preserveWatchingPosition() = preferenceStore.getBoolean(
|
||||
"pref_preserve_watching_position",
|
||||
false,
|
||||
)
|
||||
fun progressPreference() = preferenceStore.getFloat("pref_progress_preference", 0.85F)
|
||||
|
||||
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)
|
||||
fun playerFullscreen() = preferenceStore.getBoolean("player_fullscreen", true)
|
||||
fun hideControls() = preferenceStore.getBoolean("player_hide_controls", false)
|
||||
|
||||
// Internal player - Volume and brightness
|
||||
|
||||
fun gestureVolumeBrightness() = preferenceStore.getBoolean(
|
||||
"pref_gesture_volume_brightness",
|
||||
true,
|
||||
)
|
||||
fun rememberPlayerBrightness() = preferenceStore.getBoolean("pref_remember_brightness", false)
|
||||
fun playerBrightnessValue() = preferenceStore.getFloat("player_brightness_value", -1.0F)
|
||||
|
||||
fun rememberPlayerVolume() = preferenceStore.getBoolean("pref_remember_volume", false)
|
||||
fun playerVolumeValue() = preferenceStore.getFloat("player_volume_value", -1.0F)
|
||||
|
||||
fun audioChannels() = preferenceStore.getEnum("pref_audio_config", AudioChannels.AutoSafe)
|
||||
|
||||
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", "")
|
||||
// Internal player - Orientation
|
||||
|
||||
fun defaultPlayerOrientationType() = preferenceStore.getInt(
|
||||
"pref_default_player_orientation_type_key",
|
||||
|
@ -47,36 +44,23 @@ class PlayerPreferences(
|
|||
"pref_adjust_orientation_video_dimensions",
|
||||
true,
|
||||
)
|
||||
|
||||
fun defaultPlayerOrientationLandscape() = preferenceStore.getInt(
|
||||
"pref_default_player_orientation_landscape_key",
|
||||
6,
|
||||
)
|
||||
fun defaultPlayerOrientationPortrait() = preferenceStore.getInt(
|
||||
"pref_default_player_orientation_portrait_key",
|
||||
7,
|
||||
)
|
||||
|
||||
fun playerSpeed() = preferenceStore.getFloat("pref_player_speed", 1F)
|
||||
|
||||
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 defaultPlayerOrientationLandscape() = preferenceStore.getInt(
|
||||
"pref_default_player_orientation_landscape_key",
|
||||
6,
|
||||
)
|
||||
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(
|
||||
"pref_always_use_external_player",
|
||||
|
@ -84,22 +68,78 @@ class PlayerPreferences(
|
|||
)
|
||||
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 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 autoSkipAniSkip() = preferenceStore.getBoolean("pref_enable_auto_skip_ani_skip", false)
|
||||
fun waitingTimeAniSkip() = preferenceStore.getInt("pref_waiting_time_aniskip", 5)
|
||||
fun enableNetflixStyleAniSkip() = preferenceStore.getBoolean(
|
||||
"pref_enable_netflixStyle_aniskip",
|
||||
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 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 audioDelay() = preferenceStore.getInt("pref_audio_delay", 0)
|
||||
|
@ -122,8 +162,6 @@ class PlayerPreferences(
|
|||
fun borderColorSubtitles() = preferenceStore.getInt("pref_border_color_subtitles", -16777216)
|
||||
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 saturationFilter() = preferenceStore.getInt("pref_player_filter_saturation")
|
||||
fun contrastFilter() = preferenceStore.getInt("pref_player_filter_contrast")
|
||||
|
|
|
@ -103,10 +103,32 @@ enum class PlayerStatsPage(val stringRes: StringResource) {
|
|||
* Player's debanding handler
|
||||
*/
|
||||
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),
|
||||
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) {
|
||||
|
|
|
@ -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_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.getBoolean("pref_gpu_next", false).set(gpuNext.get())
|
||||
}
|
||||
|
|
|
@ -44,4 +44,5 @@ val migrations: List<Migration>
|
|||
LogOutMALMigration(),
|
||||
EnumsMigration(),
|
||||
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_returning_user">Reinstalling %s?</string>
|
||||
<!-- 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 -->
|
||||
<string name="pref_category_general">General</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_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="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="pref_category_player">Player</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_volume_brightness">Volume and Brightness</string>
|
||||
<string name="pref_category_player_seeking">Seeking</string>
|
||||
<string name="pref_default_intro_length">Default 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="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_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_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="episode_download_progress">%1$d%%</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="screenshot_header">Take screenshot</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="toggle_player_statistics_page">Toggle statistics page</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="pref_category_player_advanced">Advanced player settings</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="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>
|
||||
|
@ -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_speed_limit">Download 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>
|
||||
|
|
Loading…
Reference in a new issue