+184
-2
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 6
|
||||
/// Strings: 2662 (443 per locale)
|
||||
/// Strings: 2704 (450 per locale)
|
||||
///
|
||||
/// Built on 2025-11-21 at 22:35 UTC
|
||||
/// Built on 2025-12-01 at 09:54 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
@@ -435,6 +435,16 @@ class _StringsSettingsEn {
|
||||
'Shortcut already assigned to ${action}';
|
||||
String shortcutUpdated({required Object action}) =>
|
||||
'Shortcut updated for ${action}';
|
||||
String get autoSkip => 'Auto Skip';
|
||||
String get autoSkipIntro => 'Auto Skip Intro';
|
||||
String get autoSkipIntroDescription =>
|
||||
'Automatically skip intro markers after a few seconds';
|
||||
String get autoSkipCredits => 'Auto Skip Credits';
|
||||
String get autoSkipCreditsDescription =>
|
||||
'Automatically skip credits and play next episode';
|
||||
String get autoSkipDelay => 'Auto Skip Delay';
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'Wait ${seconds} seconds before auto-skipping';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -1478,6 +1488,23 @@ class _StringsSettingsDe implements _StringsSettingsEn {
|
||||
@override
|
||||
String shortcutUpdated({required Object action}) =>
|
||||
'Tastenkürzel aktualisiert für ${action}';
|
||||
@override
|
||||
String get autoSkip => 'Automatisches Überspringen';
|
||||
@override
|
||||
String get autoSkipIntro => 'Intro automatisch überspringen';
|
||||
@override
|
||||
String get autoSkipIntroDescription =>
|
||||
'Intro-Marker nach wenigen Sekunden automatisch überspringen';
|
||||
@override
|
||||
String get autoSkipCredits => 'Abspann automatisch überspringen';
|
||||
@override
|
||||
String get autoSkipCreditsDescription =>
|
||||
'Abspann automatisch überspringen und nächste Episode abspielen';
|
||||
@override
|
||||
String get autoSkipDelay => 'Verzögerung für automatisches Überspringen';
|
||||
@override
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'${seconds} Sekunden vor dem automatischen Überspringen warten';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -2883,6 +2910,23 @@ class _StringsSettingsIt implements _StringsSettingsEn {
|
||||
@override
|
||||
String shortcutUpdated({required Object action}) =>
|
||||
'Scorciatoia aggiornata per ${action}';
|
||||
@override
|
||||
String get autoSkip => 'Salto Automatico';
|
||||
@override
|
||||
String get autoSkipIntro => 'Salta Intro Automaticamente';
|
||||
@override
|
||||
String get autoSkipIntroDescription =>
|
||||
'Salta automaticamente i marcatori dell\'intro dopo alcuni secondi';
|
||||
@override
|
||||
String get autoSkipCredits => 'Salta Crediti Automaticamente';
|
||||
@override
|
||||
String get autoSkipCreditsDescription =>
|
||||
'Salta automaticamente i crediti e riproduci l\'episodio successivo';
|
||||
@override
|
||||
String get autoSkipDelay => 'Ritardo Salto Automatico';
|
||||
@override
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'Aspetta ${seconds} secondi prima del salto automatico';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -4287,6 +4331,23 @@ class _StringsSettingsNl implements _StringsSettingsEn {
|
||||
@override
|
||||
String shortcutUpdated({required Object action}) =>
|
||||
'Sneltoets bijgewerkt voor ${action}';
|
||||
@override
|
||||
String get autoSkip => 'Automatisch Overslaan';
|
||||
@override
|
||||
String get autoSkipIntro => 'Intro Automatisch Overslaan';
|
||||
@override
|
||||
String get autoSkipIntroDescription =>
|
||||
'Intro-markeringen na enkele seconden automatisch overslaan';
|
||||
@override
|
||||
String get autoSkipCredits => 'Credits Automatisch Overslaan';
|
||||
@override
|
||||
String get autoSkipCreditsDescription =>
|
||||
'Credits automatisch overslaan en volgende aflevering afspelen';
|
||||
@override
|
||||
String get autoSkipDelay => 'Vertraging Automatisch Overslaan';
|
||||
@override
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'${seconds} seconden wachten voor automatisch overslaan';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -5680,6 +5741,23 @@ class _StringsSettingsSv implements _StringsSettingsEn {
|
||||
@override
|
||||
String shortcutUpdated({required Object action}) =>
|
||||
'Genväg uppdaterad för ${action}';
|
||||
@override
|
||||
String get autoSkip => 'Auto Hoppa Över';
|
||||
@override
|
||||
String get autoSkipIntro => 'Hoppa Över Intro Automatiskt';
|
||||
@override
|
||||
String get autoSkipIntroDescription =>
|
||||
'Hoppa automatiskt över intro-markörer efter några sekunder';
|
||||
@override
|
||||
String get autoSkipCredits => 'Hoppa Över Credits Automatiskt';
|
||||
@override
|
||||
String get autoSkipCreditsDescription =>
|
||||
'Hoppa automatiskt över credits och spela nästa avsnitt';
|
||||
@override
|
||||
String get autoSkipDelay => 'Fördröjning Auto Hoppa Över';
|
||||
@override
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'Vänta ${seconds} sekunder innan automatisk överhoppning';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -7062,6 +7140,21 @@ class _StringsSettingsZh implements _StringsSettingsEn {
|
||||
'快捷键已被分配给 ${action}';
|
||||
@override
|
||||
String shortcutUpdated({required Object action}) => '快捷键已为 ${action} 更新';
|
||||
@override
|
||||
String get autoSkip => '自动跳过';
|
||||
@override
|
||||
String get autoSkipIntro => '自动跳过片头';
|
||||
@override
|
||||
String get autoSkipIntroDescription => '几秒钟后自动跳过片头标记';
|
||||
@override
|
||||
String get autoSkipCredits => '自动跳过片尾';
|
||||
@override
|
||||
String get autoSkipCreditsDescription => '自动跳过片尾并播放下一集';
|
||||
@override
|
||||
String get autoSkipDelay => '自动跳过延迟';
|
||||
@override
|
||||
String autoSkipDelayDescription({required Object seconds}) =>
|
||||
'自动跳过前等待 ${seconds} 秒';
|
||||
}
|
||||
|
||||
// Path: search
|
||||
@@ -8253,6 +8346,21 @@ extension on Translations {
|
||||
'Shortcut already assigned to ${action}';
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) => 'Shortcut updated for ${action}';
|
||||
case 'settings.autoSkip':
|
||||
return 'Auto Skip';
|
||||
case 'settings.autoSkipIntro':
|
||||
return 'Auto Skip Intro';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return 'Automatically skip intro markers after a few seconds';
|
||||
case 'settings.autoSkipCredits':
|
||||
return 'Auto Skip Credits';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return 'Automatically skip credits and play next episode';
|
||||
case 'settings.autoSkipDelay':
|
||||
return 'Auto Skip Delay';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) =>
|
||||
'Wait ${seconds} seconds before auto-skipping';
|
||||
case 'search.hint':
|
||||
return 'Search movies, shows, music...';
|
||||
case 'search.tryDifferentTerm':
|
||||
@@ -9189,6 +9297,21 @@ extension on _StringsDe {
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) =>
|
||||
'Tastenkürzel aktualisiert für ${action}';
|
||||
case 'settings.autoSkip':
|
||||
return 'Automatisches Überspringen';
|
||||
case 'settings.autoSkipIntro':
|
||||
return 'Intro automatisch überspringen';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return 'Intro-Marker nach wenigen Sekunden automatisch überspringen';
|
||||
case 'settings.autoSkipCredits':
|
||||
return 'Abspann automatisch überspringen';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return 'Abspann automatisch überspringen und nächste Episode abspielen';
|
||||
case 'settings.autoSkipDelay':
|
||||
return 'Verzögerung für automatisches Überspringen';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) =>
|
||||
'${seconds} Sekunden vor dem automatischen Überspringen warten';
|
||||
case 'search.hint':
|
||||
return 'Filme, Serien, Musik suchen...';
|
||||
case 'search.tryDifferentTerm':
|
||||
@@ -10130,6 +10253,21 @@ extension on _StringsIt {
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) =>
|
||||
'Scorciatoia aggiornata per ${action}';
|
||||
case 'settings.autoSkip':
|
||||
return 'Salto Automatico';
|
||||
case 'settings.autoSkipIntro':
|
||||
return 'Salta Intro Automaticamente';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return 'Salta automaticamente i marcatori dell\'intro dopo alcuni secondi';
|
||||
case 'settings.autoSkipCredits':
|
||||
return 'Salta Crediti Automaticamente';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return 'Salta automaticamente i crediti e riproduci l\'episodio successivo';
|
||||
case 'settings.autoSkipDelay':
|
||||
return 'Ritardo Salto Automatico';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) =>
|
||||
'Aspetta ${seconds} secondi prima del salto automatico';
|
||||
case 'search.hint':
|
||||
return 'Cerca film. spettacoli, musica...';
|
||||
case 'search.tryDifferentTerm':
|
||||
@@ -11073,6 +11211,21 @@ extension on _StringsNl {
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) =>
|
||||
'Sneltoets bijgewerkt voor ${action}';
|
||||
case 'settings.autoSkip':
|
||||
return 'Automatisch Overslaan';
|
||||
case 'settings.autoSkipIntro':
|
||||
return 'Intro Automatisch Overslaan';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return 'Intro-markeringen na enkele seconden automatisch overslaan';
|
||||
case 'settings.autoSkipCredits':
|
||||
return 'Credits Automatisch Overslaan';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return 'Credits automatisch overslaan en volgende aflevering afspelen';
|
||||
case 'settings.autoSkipDelay':
|
||||
return 'Vertraging Automatisch Overslaan';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) =>
|
||||
'${seconds} seconden wachten voor automatisch overslaan';
|
||||
case 'search.hint':
|
||||
return 'Zoek films, series, muziek...';
|
||||
case 'search.tryDifferentTerm':
|
||||
@@ -12017,6 +12170,21 @@ extension on _StringsSv {
|
||||
return ({required Object action}) => 'Genväg redan tilldelad ${action}';
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) => 'Genväg uppdaterad för ${action}';
|
||||
case 'settings.autoSkip':
|
||||
return 'Auto Hoppa Över';
|
||||
case 'settings.autoSkipIntro':
|
||||
return 'Hoppa Över Intro Automatiskt';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return 'Hoppa automatiskt över intro-markörer efter några sekunder';
|
||||
case 'settings.autoSkipCredits':
|
||||
return 'Hoppa Över Credits Automatiskt';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return 'Hoppa automatiskt över credits och spela nästa avsnitt';
|
||||
case 'settings.autoSkipDelay':
|
||||
return 'Fördröjning Auto Hoppa Över';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) =>
|
||||
'Vänta ${seconds} sekunder innan automatisk överhoppning';
|
||||
case 'search.hint':
|
||||
return 'Sök filmer, serier, musik...';
|
||||
case 'search.tryDifferentTerm':
|
||||
@@ -12959,6 +13127,20 @@ extension on _StringsZh {
|
||||
return ({required Object action}) => '快捷键已被分配给 ${action}';
|
||||
case 'settings.shortcutUpdated':
|
||||
return ({required Object action}) => '快捷键已为 ${action} 更新';
|
||||
case 'settings.autoSkip':
|
||||
return '自动跳过';
|
||||
case 'settings.autoSkipIntro':
|
||||
return '自动跳过片头';
|
||||
case 'settings.autoSkipIntroDescription':
|
||||
return '几秒钟后自动跳过片头标记';
|
||||
case 'settings.autoSkipCredits':
|
||||
return '自动跳过片尾';
|
||||
case 'settings.autoSkipCreditsDescription':
|
||||
return '自动跳过片尾并播放下一集';
|
||||
case 'settings.autoSkipDelay':
|
||||
return '自动跳过延迟';
|
||||
case 'settings.autoSkipDelayDescription':
|
||||
return ({required Object seconds}) => '自动跳过前等待 ${seconds} 秒';
|
||||
case 'search.hint':
|
||||
return '搜索电影、系列、音乐...';
|
||||
case 'search.tryDifferentTerm':
|
||||
|
||||
@@ -132,7 +132,14 @@
|
||||
"validationErrorEnterNumber": "Please enter a valid number",
|
||||
"validationErrorDuration": "Duration must be between ${min} and ${max} ${unit}",
|
||||
"shortcutAlreadyAssigned": "Shortcut already assigned to ${action}",
|
||||
"shortcutUpdated": "Shortcut updated for ${action}"
|
||||
"shortcutUpdated": "Shortcut updated for ${action}",
|
||||
"autoSkip": "Auto Skip",
|
||||
"autoSkipIntro": "Auto Skip Intro",
|
||||
"autoSkipIntroDescription": "Automatically skip intro markers after a few seconds",
|
||||
"autoSkipCredits": "Auto Skip Credits",
|
||||
"autoSkipCreditsDescription": "Automatically skip credits and play next episode",
|
||||
"autoSkipDelay": "Auto Skip Delay",
|
||||
"autoSkipDelayDescription": "Wait ${seconds} seconds before auto-skipping"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Search movies, shows, music...",
|
||||
|
||||
@@ -133,7 +133,14 @@
|
||||
"validationErrorEnterNumber": "Bitte eine gültige Zahl eingeben",
|
||||
"validationErrorDuration": "Dauer muss zwischen ${min} und ${max} ${unit} liegen",
|
||||
"shortcutAlreadyAssigned": "Tastenkürzel bereits zugewiesen an ${action}",
|
||||
"shortcutUpdated": "Tastenkürzel aktualisiert für ${action}"
|
||||
"shortcutUpdated": "Tastenkürzel aktualisiert für ${action}",
|
||||
"autoSkip": "Automatisches Überspringen",
|
||||
"autoSkipIntro": "Intro automatisch überspringen",
|
||||
"autoSkipIntroDescription": "Intro-Marker nach wenigen Sekunden automatisch überspringen",
|
||||
"autoSkipCredits": "Abspann automatisch überspringen",
|
||||
"autoSkipCreditsDescription": "Abspann automatisch überspringen und nächste Episode abspielen",
|
||||
"autoSkipDelay": "Verzögerung für automatisches Überspringen",
|
||||
"autoSkipDelayDescription": "${seconds} Sekunden vor dem automatischen Überspringen warten"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Filme, Serien, Musik suchen...",
|
||||
|
||||
@@ -133,7 +133,14 @@
|
||||
"validationErrorEnterNumber": "Inserisci un numero valido",
|
||||
"validationErrorDuration": "la durata deve essere compresa tra ${min} e ${max} ${unit}",
|
||||
"shortcutAlreadyAssigned": "Scorciatoia già assegnata a ${action}",
|
||||
"shortcutUpdated": "Scorciatoia aggiornata per ${action}"
|
||||
"shortcutUpdated": "Scorciatoia aggiornata per ${action}",
|
||||
"autoSkip": "Salto Automatico",
|
||||
"autoSkipIntro": "Salta Intro Automaticamente",
|
||||
"autoSkipIntroDescription": "Salta automaticamente i marcatori dell'intro dopo alcuni secondi",
|
||||
"autoSkipCredits": "Salta Crediti Automaticamente",
|
||||
"autoSkipCreditsDescription": "Salta automaticamente i crediti e riproduci l'episodio successivo",
|
||||
"autoSkipDelay": "Ritardo Salto Automatico",
|
||||
"autoSkipDelayDescription": "Aspetta ${seconds} secondi prima del salto automatico"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Cerca film. spettacoli, musica...",
|
||||
|
||||
@@ -133,7 +133,14 @@
|
||||
"validationErrorEnterNumber": "Voer een geldig nummer in",
|
||||
"validationErrorDuration": "Duur moet tussen ${min} en ${max} ${unit} zijn",
|
||||
"shortcutAlreadyAssigned": "Sneltoets al toegewezen aan ${action}",
|
||||
"shortcutUpdated": "Sneltoets bijgewerkt voor ${action}"
|
||||
"shortcutUpdated": "Sneltoets bijgewerkt voor ${action}",
|
||||
"autoSkip": "Automatisch Overslaan",
|
||||
"autoSkipIntro": "Intro Automatisch Overslaan",
|
||||
"autoSkipIntroDescription": "Intro-markeringen na enkele seconden automatisch overslaan",
|
||||
"autoSkipCredits": "Credits Automatisch Overslaan",
|
||||
"autoSkipCreditsDescription": "Credits automatisch overslaan en volgende aflevering afspelen",
|
||||
"autoSkipDelay": "Vertraging Automatisch Overslaan",
|
||||
"autoSkipDelayDescription": "${seconds} seconden wachten voor automatisch overslaan"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Zoek films, series, muziek...",
|
||||
|
||||
@@ -133,7 +133,14 @@
|
||||
"validationErrorEnterNumber": "Vänligen ange ett giltigt nummer",
|
||||
"validationErrorDuration": "Tiden måste vara mellan ${min} och ${max} ${unit}",
|
||||
"shortcutAlreadyAssigned": "Genväg redan tilldelad ${action}",
|
||||
"shortcutUpdated": "Genväg uppdaterad för ${action}"
|
||||
"shortcutUpdated": "Genväg uppdaterad för ${action}",
|
||||
"autoSkip": "Auto Hoppa Över",
|
||||
"autoSkipIntro": "Hoppa Över Intro Automatiskt",
|
||||
"autoSkipIntroDescription": "Hoppa automatiskt över intro-markörer efter några sekunder",
|
||||
"autoSkipCredits": "Hoppa Över Credits Automatiskt",
|
||||
"autoSkipCreditsDescription": "Hoppa automatiskt över credits och spela nästa avsnitt",
|
||||
"autoSkipDelay": "Fördröjning Auto Hoppa Över",
|
||||
"autoSkipDelayDescription": "Vänta ${seconds} sekunder innan automatisk överhoppning"
|
||||
},
|
||||
"search": {
|
||||
"hint": "Sök filmer, serier, musik...",
|
||||
|
||||
@@ -133,7 +133,14 @@
|
||||
"validationErrorEnterNumber": "请输入一个有效的数字",
|
||||
"validationErrorDuration": "时长必须介于 ${min} 和 ${max} ${unit} 之间",
|
||||
"shortcutAlreadyAssigned": "快捷键已被分配给 ${action}",
|
||||
"shortcutUpdated": "快捷键已为 ${action} 更新"
|
||||
"shortcutUpdated": "快捷键已为 ${action} 更新",
|
||||
"autoSkip": "自动跳过",
|
||||
"autoSkipIntro": "自动跳过片头",
|
||||
"autoSkipIntroDescription": "几秒钟后自动跳过片头标记",
|
||||
"autoSkipCredits": "自动跳过片尾",
|
||||
"autoSkipCreditsDescription": "自动跳过片尾并播放下一集",
|
||||
"autoSkipDelay": "自动跳过延迟",
|
||||
"autoSkipDelayDescription": "自动跳过前等待 ${seconds} 秒"
|
||||
},
|
||||
"search": {
|
||||
"hint": "搜索电影、系列、音乐...",
|
||||
|
||||
@@ -37,6 +37,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
int _seekTimeLarge = 30;
|
||||
int _sleepTimerDuration = 30;
|
||||
bool _rememberTrackSelections = true;
|
||||
bool _autoSkipIntro = true;
|
||||
bool _autoSkipCredits = true;
|
||||
int _autoSkipDelay = 5;
|
||||
|
||||
// Update checking state
|
||||
bool _isCheckingForUpdate = false;
|
||||
@@ -60,6 +63,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_seekTimeLarge = _settingsService.getSeekTimeLarge();
|
||||
_sleepTimerDuration = _settingsService.getSleepTimerDuration();
|
||||
_rememberTrackSelections = _settingsService.getRememberTrackSelections();
|
||||
_autoSkipIntro = _settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = _settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = _settingsService.getAutoSkipDelay();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -291,6 +297,52 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
await _settingsService.setRememberTrackSelections(value);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
child: Text(
|
||||
t.settings.autoSkip,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.fast_forward),
|
||||
title: Text(t.settings.autoSkipIntro),
|
||||
subtitle: Text(t.settings.autoSkipIntroDescription),
|
||||
value: _autoSkipIntro,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_autoSkipIntro = value;
|
||||
});
|
||||
await _settingsService.setAutoSkipIntro(value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.skip_next),
|
||||
title: Text(t.settings.autoSkipCredits),
|
||||
subtitle: Text(t.settings.autoSkipCreditsDescription),
|
||||
value: _autoSkipCredits,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
_autoSkipCredits = value;
|
||||
});
|
||||
await _settingsService.setAutoSkipCredits(value);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.timer),
|
||||
title: Text(t.settings.autoSkipDelay),
|
||||
subtitle: Text(
|
||||
t.settings.autoSkipDelayDescription(
|
||||
seconds: _autoSkipDelay.toString(),
|
||||
),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _showAutoSkipDelayDialog(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -703,7 +755,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.minutesLabel,
|
||||
hintText: t.settings.durationHint(min: 5, max: 180),
|
||||
hintText: t.settings.durationHint(min: 5, max: 240),
|
||||
errorText: errorText,
|
||||
suffixText: t.settings.minutesShort,
|
||||
),
|
||||
@@ -713,10 +765,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setDialogState(() {
|
||||
if (parsed == null) {
|
||||
errorText = t.settings.validationErrorEnterNumber;
|
||||
} else if (parsed < 5 || parsed > 180) {
|
||||
} else if (parsed < 5 || parsed > 240) {
|
||||
errorText = t.settings.validationErrorDuration(
|
||||
min: 5,
|
||||
max: 180,
|
||||
max: 240,
|
||||
unit: t.settings.minutesLabel.toLowerCase(),
|
||||
);
|
||||
} else {
|
||||
@@ -733,11 +785,77 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final parsed = int.tryParse(controller.text);
|
||||
if (parsed != null && parsed >= 5 && parsed <= 180) {
|
||||
if (parsed != null && parsed >= 5 && parsed <= 240) {
|
||||
setState(() {
|
||||
_sleepTimerDuration = parsed;
|
||||
_settingsService.setSleepTimerDuration(parsed);
|
||||
});
|
||||
await _settingsService.setSleepTimerDuration(parsed);
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showAutoSkipDelayDialog() {
|
||||
final controller = TextEditingController(text: _autoSkipDelay.toString());
|
||||
String? errorText;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setDialogState) {
|
||||
return AlertDialog(
|
||||
title: Text(t.settings.autoSkipDelay),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.secondsLabel,
|
||||
hintText: t.settings.durationHint(min: 1, max: 30),
|
||||
errorText: errorText,
|
||||
suffixText: t.settings.secondsShort,
|
||||
),
|
||||
autofocus: true,
|
||||
onChanged: (value) {
|
||||
final parsed = int.tryParse(value);
|
||||
setDialogState(() {
|
||||
if (parsed == null) {
|
||||
errorText = t.settings.validationErrorEnterNumber;
|
||||
} else if (parsed < 1 || parsed > 30) {
|
||||
errorText = t.settings.validationErrorDuration(
|
||||
min: 1,
|
||||
max: 30,
|
||||
unit: t.settings.secondsLabel.toLowerCase(),
|
||||
);
|
||||
} else {
|
||||
errorText = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final parsed = int.tryParse(controller.text);
|
||||
if (parsed != null && parsed >= 1 && parsed <= 30) {
|
||||
setState(() {
|
||||
_autoSkipDelay = parsed;
|
||||
});
|
||||
await _settingsService.setAutoSkipDelay(parsed);
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.pop(dialogContext);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ class SettingsService {
|
||||
'subtitle_background_opacity';
|
||||
static const String _keyAppLocale = 'app_locale';
|
||||
static const String _keyRememberTrackSelections = 'remember_track_selections';
|
||||
static const String _keyAutoSkipIntro = 'auto_skip_intro';
|
||||
static const String _keyAutoSkipCredits = 'auto_skip_credits';
|
||||
static const String _keyAutoSkipDelay = 'auto_skip_delay';
|
||||
|
||||
static SettingsService? _instance;
|
||||
late SharedPreferences _prefs;
|
||||
@@ -805,12 +808,39 @@ class SettingsService {
|
||||
// Track Selection Settings
|
||||
|
||||
/// Remember Track Selections - Save per-media audio/subtitle language preferences
|
||||
Future<void> setRememberTrackSelections(bool enabled) async {
|
||||
await _prefs.setBool(_keyRememberTrackSelections, enabled);
|
||||
Future<void> setRememberTrackSelections(bool value) async {
|
||||
await _prefs.setBool(_keyRememberTrackSelections, value);
|
||||
}
|
||||
|
||||
bool getRememberTrackSelections() {
|
||||
return _prefs.getBool(_keyRememberTrackSelections) ?? true; // Default: true
|
||||
return _prefs.getBool(_keyRememberTrackSelections) ?? true;
|
||||
}
|
||||
|
||||
// Auto Skip Intro
|
||||
Future<void> setAutoSkipIntro(bool value) async {
|
||||
await _prefs.setBool(_keyAutoSkipIntro, value);
|
||||
}
|
||||
|
||||
bool getAutoSkipIntro() {
|
||||
return _prefs.getBool(_keyAutoSkipIntro) ?? true; // Default: enabled
|
||||
}
|
||||
|
||||
// Auto Skip Credits
|
||||
Future<void> setAutoSkipCredits(bool value) async {
|
||||
await _prefs.setBool(_keyAutoSkipCredits, value);
|
||||
}
|
||||
|
||||
bool getAutoSkipCredits() {
|
||||
return _prefs.getBool(_keyAutoSkipCredits) ?? true; // Default: enabled
|
||||
}
|
||||
|
||||
// Auto Skip Delay (in seconds)
|
||||
Future<void> setAutoSkipDelay(int seconds) async {
|
||||
await _prefs.setInt(_keyAutoSkipDelay, seconds);
|
||||
}
|
||||
|
||||
int getAutoSkipDelay() {
|
||||
return _prefs.getInt(_keyAutoSkipDelay) ?? 5; // Default: 5 seconds
|
||||
}
|
||||
|
||||
// Reset all settings to defaults
|
||||
@@ -875,6 +905,9 @@ class SettingsService {
|
||||
(key, value) => MapEntry(key, _serializeHotKey(value)),
|
||||
),
|
||||
'rememberTrackSelections': getRememberTrackSelections(),
|
||||
'autoSkipIntro': getAutoSkipIntro(),
|
||||
'autoSkipCredits': getAutoSkipCredits(),
|
||||
'autoSkipDelay': getAutoSkipDelay(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,12 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
bool _markersLoaded = false;
|
||||
// Playback state subscription for auto-hide timer
|
||||
StreamSubscription<bool>? _playingSubscription;
|
||||
// Auto-skip state
|
||||
bool _autoSkipIntro = true;
|
||||
bool _autoSkipCredits = true;
|
||||
int _autoSkipDelay = 5;
|
||||
Timer? _autoSkipTimer;
|
||||
double _autoSkipProgress = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -163,6 +169,13 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
setState(() {
|
||||
_currentMarker = foundMarker;
|
||||
});
|
||||
|
||||
// Start auto-skip timer for new marker
|
||||
if (foundMarker != null) {
|
||||
_startAutoSkipTimer(foundMarker);
|
||||
} else {
|
||||
_cancelAutoSkipTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -183,6 +196,75 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
if (_currentMarker != null) {
|
||||
widget.player.seek(_currentMarker!.endTime);
|
||||
}
|
||||
_cancelAutoSkipTimer();
|
||||
}
|
||||
|
||||
void _startAutoSkipTimer(PlexMarker marker) {
|
||||
_cancelAutoSkipTimer();
|
||||
|
||||
final shouldAutoSkip =
|
||||
(marker.isCredits && _autoSkipCredits) ||
|
||||
(!marker.isCredits && _autoSkipIntro);
|
||||
|
||||
if (!shouldAutoSkip || _autoSkipDelay <= 0) return;
|
||||
|
||||
_autoSkipProgress = 0.0;
|
||||
const tickDuration = Duration(milliseconds: 50);
|
||||
final totalTicks = (_autoSkipDelay * 1000) / tickDuration.inMilliseconds;
|
||||
|
||||
if (totalTicks <= 0) return;
|
||||
|
||||
_autoSkipTimer = Timer.periodic(tickDuration, (timer) {
|
||||
if (!mounted || _currentMarker != marker) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_autoSkipProgress = (timer.tick / totalTicks).clamp(0.0, 1.0);
|
||||
});
|
||||
|
||||
if (timer.tick >= totalTicks) {
|
||||
timer.cancel();
|
||||
try {
|
||||
_performAutoSkip();
|
||||
} catch (e) {
|
||||
// Handle any errors during skip gracefully
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _cancelAutoSkipTimer() {
|
||||
_autoSkipTimer?.cancel();
|
||||
_autoSkipTimer = null;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_autoSkipProgress = 0.0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the appropriate skip action based on marker type and next episode availability
|
||||
void _performAutoSkip() {
|
||||
if (_currentMarker == null) return;
|
||||
|
||||
final isCredits = _currentMarker!.isCredits;
|
||||
final hasNextEpisode = widget.onNext != null;
|
||||
final showNextEpisode = isCredits && hasNextEpisode;
|
||||
|
||||
if (showNextEpisode) {
|
||||
widget.onNext?.call();
|
||||
} else {
|
||||
_skipMarker();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if auto-skip should be active for the current marker
|
||||
bool _shouldShowAutoSkip() {
|
||||
if (_currentMarker == null) return false;
|
||||
return (_currentMarker!.isCredits && _autoSkipCredits) ||
|
||||
(!_currentMarker!.isCredits && _autoSkipIntro);
|
||||
}
|
||||
|
||||
Future<void> _loadSeekTimes() async {
|
||||
@@ -193,6 +275,9 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
_audioSyncOffset = settingsService.getAudioSyncOffset();
|
||||
_subtitleSyncOffset = settingsService.getSubtitleSyncOffset();
|
||||
_isRotationLocked = settingsService.getRotationLocked();
|
||||
_autoSkipIntro = settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = settingsService.getAutoSkipDelay();
|
||||
});
|
||||
|
||||
// Apply rotation lock setting
|
||||
@@ -240,6 +325,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
void dispose() {
|
||||
_hideTimer?.cancel();
|
||||
_feedbackTimer?.cancel();
|
||||
_autoSkipTimer?.cancel();
|
||||
_seekThrottle.cancel();
|
||||
_playingSubscription?.cancel();
|
||||
_focusNode.dispose();
|
||||
@@ -329,6 +415,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
});
|
||||
if (_showControls) {
|
||||
_startHideTimer();
|
||||
// Cancel auto-skip when user manually shows controls
|
||||
_cancelAutoSkipTimer();
|
||||
}
|
||||
|
||||
// On macOS, hide/show traffic lights with controls
|
||||
@@ -804,9 +892,24 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
|
||||
// Show "Next Episode" for credits when next episode is available
|
||||
final bool showNextEpisode = isCredits && hasNextEpisode;
|
||||
final String buttonText = showNextEpisode
|
||||
final String baseButtonText = showNextEpisode
|
||||
? 'Next Episode'
|
||||
: (isCredits ? 'Skip Credits' : 'Skip Intro');
|
||||
|
||||
final isAutoSkipActive = _autoSkipTimer?.isActive ?? false;
|
||||
final shouldShowAutoSkip = _shouldShowAutoSkip();
|
||||
|
||||
final int remainingSeconds = isAutoSkipActive && shouldShowAutoSkip
|
||||
? (_autoSkipDelay - (_autoSkipProgress * _autoSkipDelay)).ceil().clamp(
|
||||
0,
|
||||
_autoSkipDelay,
|
||||
)
|
||||
: 0;
|
||||
|
||||
final String buttonText =
|
||||
isAutoSkipActive && shouldShowAutoSkip && remainingSeconds > 0
|
||||
? '$baseButtonText ($remainingSeconds)'
|
||||
: baseButtonText;
|
||||
final IconData buttonIcon = showNextEpisode
|
||||
? Icons.skip_next
|
||||
: Icons.fast_forward;
|
||||
@@ -814,36 +917,74 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: showNextEpisode ? widget.onNext : _skipMarker,
|
||||
onTap: () {
|
||||
if (isAutoSkipActive) {
|
||||
_cancelAutoSkipTimer();
|
||||
}
|
||||
// Always perform the skip action when tapped
|
||||
_performAutoSkip();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(buttonIcon, color: Colors.black, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Progress indicator overlay
|
||||
if (isAutoSkipActive && shouldShowAutoSkip)
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: (_autoSkipProgress * 100).round(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: ((1.0 - _autoSkipProgress) * 100).round(),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(buttonIcon, color: Colors.black, size: 20),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user