From 0290adcb6d2c9f3ddd44a3f398c27aead93e2a36 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:12:43 +0100 Subject: [PATCH] feat: add auto-play toggle close #262 --- lib/i18n/strings.g.dart | 32 +++++++++++++++++-- lib/i18n/strings.i18n.json | 2 ++ lib/i18n/strings_de.i18n.json | 2 ++ lib/i18n/strings_it.i18n.json | 2 ++ lib/i18n/strings_ko.i18n.json | 2 ++ lib/i18n/strings_nl.i18n.json | 2 ++ lib/i18n/strings_sv.i18n.json | 2 ++ lib/i18n/strings_zh.i18n.json | 2 ++ lib/screens/video_player_screen.dart | 21 ++++++++---- lib/services/settings_service.dart | 11 +++++++ .../sheets/video_settings_sheet.dart | 27 ++++++++++++++++ 11 files changed, 97 insertions(+), 8 deletions(-) diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 71f69e5f..0b7cab3e 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 7 -/// Strings: 3750 (535 per locale) +/// Strings: 3764 (537 per locale) /// -/// Built on 2026-01-14 at 23:28 UTC +/// Built on 2026-01-15 at 19:06 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -529,6 +529,8 @@ class _StringsVideoControlsEn { String get timerActive => 'Timer Active'; String playbackWillPauseIn({required Object duration}) => 'Playback will pause in ${duration}'; String get sleepTimerCompleted => 'Sleep timer completed - playback paused'; + String get autoPlayNext => 'Auto-Play Next'; + String get playNext => 'Play Next'; String get playButton => 'Play'; String get pauseButton => 'Pause'; String seekBackwardButton({required Object seconds}) => 'Seek backward ${seconds} seconds'; @@ -1440,6 +1442,8 @@ class _StringsVideoControlsDe implements _StringsVideoControlsEn { @override String get timerActive => 'Schlaftimer aktiv'; @override String playbackWillPauseIn({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert'; @override String get sleepTimerCompleted => 'Schlaftimer abgelaufen – Wiedergabe pausiert'; + @override String get autoPlayNext => 'Nächstes automatisch abspielen'; + @override String get playNext => 'Nächstes abspielen'; @override String get playButton => 'Wiedergeben'; @override String get pauseButton => 'Pause'; @override String seekBackwardButton({required Object seconds}) => '${seconds} Sekunden zurück'; @@ -2352,6 +2356,8 @@ class _StringsVideoControlsIt implements _StringsVideoControlsEn { @override String get timerActive => 'Timer attivo'; @override String playbackWillPauseIn({required Object duration}) => 'La riproduzione si interromperà tra ${duration}'; @override String get sleepTimerCompleted => 'Timer di spegnimento completato - riproduzione in pausa'; + @override String get autoPlayNext => 'Riproduzione automatica successivo'; + @override String get playNext => 'Riproduci successivo'; @override String get playButton => 'Riproduci'; @override String get pauseButton => 'Pausa'; @override String seekBackwardButton({required Object seconds}) => 'Riavvolgi di ${seconds} secondi'; @@ -3264,6 +3270,8 @@ class _StringsVideoControlsKo implements _StringsVideoControlsEn { @override String get timerActive => '타이머 활성화됨'; @override String playbackWillPauseIn({required Object duration}) => '재생이 ${duration} 후에 일시 중지 됩니다'; @override String get sleepTimerCompleted => '수면 타이머 완료됨 - 재생이 일시 중지되었습니다'; + @override String get autoPlayNext => '다음 자동 재생'; + @override String get playNext => '다음 재생'; @override String get playButton => '재생'; @override String get pauseButton => '일시정지'; @override String seekBackwardButton({required Object seconds}) => '${seconds} 초 뒤로'; @@ -4176,6 +4184,8 @@ class _StringsVideoControlsNl implements _StringsVideoControlsEn { @override String get timerActive => 'Timer actief'; @override String playbackWillPauseIn({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}'; @override String get sleepTimerCompleted => 'Slaaptimer voltooid - afspelen gepauzeerd'; + @override String get autoPlayNext => 'Automatisch volgende afspelen'; + @override String get playNext => 'Volgende afspelen'; @override String get playButton => 'Afspelen'; @override String get pauseButton => 'Pauzeren'; @override String seekBackwardButton({required Object seconds}) => 'Terugspoelen ${seconds} seconden'; @@ -5088,6 +5098,8 @@ class _StringsVideoControlsSv implements _StringsVideoControlsEn { @override String get timerActive => 'Timer aktiv'; @override String playbackWillPauseIn({required Object duration}) => 'Uppspelningen pausas om ${duration}'; @override String get sleepTimerCompleted => 'Sovtimer slutförd - uppspelning pausad'; + @override String get autoPlayNext => 'Spela nästa automatiskt'; + @override String get playNext => 'Spela nästa'; @override String get playButton => 'Spela'; @override String get pauseButton => 'Pausa'; @override String seekBackwardButton({required Object seconds}) => 'Spola bakåt ${seconds} sekunder'; @@ -6000,6 +6012,8 @@ class _StringsVideoControlsZh implements _StringsVideoControlsEn { @override String get timerActive => '定时器已激活'; @override String playbackWillPauseIn({required Object duration}) => '播放将在 ${duration} 后暂停'; @override String get sleepTimerCompleted => '睡眠定时器已完成 - 播放已暂停'; + @override String get autoPlayNext => '自动播放下一集'; + @override String get playNext => '播放下一集'; @override String get playButton => '播放'; @override String get pauseButton => '暂停'; @override String seekBackwardButton({required Object seconds}) => '后退 ${seconds} 秒'; @@ -6751,6 +6765,8 @@ extension on Translations { case 'videoControls.timerActive': return 'Timer Active'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Playback will pause in ${duration}'; case 'videoControls.sleepTimerCompleted': return 'Sleep timer completed - playback paused'; + case 'videoControls.autoPlayNext': return 'Auto-Play Next'; + case 'videoControls.playNext': return 'Play Next'; case 'videoControls.playButton': return 'Play'; case 'videoControls.pauseButton': return 'Pause'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Seek backward ${seconds} seconds'; @@ -7294,6 +7310,8 @@ extension on _StringsDe { case 'videoControls.timerActive': return 'Schlaftimer aktiv'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert'; case 'videoControls.sleepTimerCompleted': return 'Schlaftimer abgelaufen – Wiedergabe pausiert'; + case 'videoControls.autoPlayNext': return 'Nächstes automatisch abspielen'; + case 'videoControls.playNext': return 'Nächstes abspielen'; case 'videoControls.playButton': return 'Wiedergeben'; case 'videoControls.pauseButton': return 'Pause'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '${seconds} Sekunden zurück'; @@ -7838,6 +7856,8 @@ extension on _StringsIt { case 'videoControls.timerActive': return 'Timer attivo'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'La riproduzione si interromperà tra ${duration}'; case 'videoControls.sleepTimerCompleted': return 'Timer di spegnimento completato - riproduzione in pausa'; + case 'videoControls.autoPlayNext': return 'Riproduzione automatica successivo'; + case 'videoControls.playNext': return 'Riproduci successivo'; case 'videoControls.playButton': return 'Riproduci'; case 'videoControls.pauseButton': return 'Pausa'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Riavvolgi di ${seconds} secondi'; @@ -8382,6 +8402,8 @@ extension on _StringsKo { case 'videoControls.timerActive': return '타이머 활성화됨'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => '재생이 ${duration} 후에 일시 중지 됩니다'; case 'videoControls.sleepTimerCompleted': return '수면 타이머 완료됨 - 재생이 일시 중지되었습니다'; + case 'videoControls.autoPlayNext': return '다음 자동 재생'; + case 'videoControls.playNext': return '다음 재생'; case 'videoControls.playButton': return '재생'; case 'videoControls.pauseButton': return '일시정지'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '${seconds} 초 뒤로'; @@ -8926,6 +8948,8 @@ extension on _StringsNl { case 'videoControls.timerActive': return 'Timer actief'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}'; case 'videoControls.sleepTimerCompleted': return 'Slaaptimer voltooid - afspelen gepauzeerd'; + case 'videoControls.autoPlayNext': return 'Automatisch volgende afspelen'; + case 'videoControls.playNext': return 'Volgende afspelen'; case 'videoControls.playButton': return 'Afspelen'; case 'videoControls.pauseButton': return 'Pauzeren'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Terugspoelen ${seconds} seconden'; @@ -9470,6 +9494,8 @@ extension on _StringsSv { case 'videoControls.timerActive': return 'Timer aktiv'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Uppspelningen pausas om ${duration}'; case 'videoControls.sleepTimerCompleted': return 'Sovtimer slutförd - uppspelning pausad'; + case 'videoControls.autoPlayNext': return 'Spela nästa automatiskt'; + case 'videoControls.playNext': return 'Spela nästa'; case 'videoControls.playButton': return 'Spela'; case 'videoControls.pauseButton': return 'Pausa'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Spola bakåt ${seconds} sekunder'; @@ -10014,6 +10040,8 @@ extension on _StringsZh { case 'videoControls.timerActive': return '定时器已激活'; case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => '播放将在 ${duration} 后暂停'; case 'videoControls.sleepTimerCompleted': return '睡眠定时器已完成 - 播放已暂停'; + case 'videoControls.autoPlayNext': return '自动播放下一集'; + case 'videoControls.playNext': return '播放下一集'; case 'videoControls.playButton': return '播放'; case 'videoControls.pauseButton': return '暂停'; case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '后退 ${seconds} 秒'; diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index 59e14d21..dc62f498 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -262,6 +262,8 @@ "timerActive": "Timer Active", "playbackWillPauseIn": "Playback will pause in ${duration}", "sleepTimerCompleted": "Sleep timer completed - playback paused", + "autoPlayNext": "Auto-Play Next", + "playNext": "Play Next", "playButton": "Play", "pauseButton": "Pause", "seekBackwardButton": "Seek backward ${seconds} seconds", diff --git a/lib/i18n/strings_de.i18n.json b/lib/i18n/strings_de.i18n.json index 4e49f274..9ef0c9e2 100644 --- a/lib/i18n/strings_de.i18n.json +++ b/lib/i18n/strings_de.i18n.json @@ -263,6 +263,8 @@ "timerActive": "Schlaftimer aktiv", "playbackWillPauseIn": "Wiedergabe wird in ${duration} pausiert", "sleepTimerCompleted": "Schlaftimer abgelaufen – Wiedergabe pausiert", + "autoPlayNext": "Nächstes automatisch abspielen", + "playNext": "Nächstes abspielen", "playButton": "Wiedergeben", "pauseButton": "Pause", "seekBackwardButton": "${seconds} Sekunden zurück", diff --git a/lib/i18n/strings_it.i18n.json b/lib/i18n/strings_it.i18n.json index 4640f9cc..f7dbd287 100644 --- a/lib/i18n/strings_it.i18n.json +++ b/lib/i18n/strings_it.i18n.json @@ -263,6 +263,8 @@ "timerActive": "Timer attivo", "playbackWillPauseIn": "La riproduzione si interromperà tra ${duration}", "sleepTimerCompleted": "Timer di spegnimento completato - riproduzione in pausa", + "autoPlayNext": "Riproduzione automatica successivo", + "playNext": "Riproduci successivo", "playButton": "Riproduci", "pauseButton": "Pausa", "seekBackwardButton": "Riavvolgi di ${seconds} secondi", diff --git a/lib/i18n/strings_ko.i18n.json b/lib/i18n/strings_ko.i18n.json index 70416ab0..f5a6f295 100644 --- a/lib/i18n/strings_ko.i18n.json +++ b/lib/i18n/strings_ko.i18n.json @@ -263,6 +263,8 @@ "timerActive": "타이머 활성화됨", "playbackWillPauseIn": "재생이 ${duration} 후에 일시 중지 됩니다", "sleepTimerCompleted": "수면 타이머 완료됨 - 재생이 일시 중지되었습니다", + "autoPlayNext": "다음 자동 재생", + "playNext": "다음 재생", "playButton": "재생", "pauseButton": "일시정지", "seekBackwardButton": "${seconds} 초 뒤로", diff --git a/lib/i18n/strings_nl.i18n.json b/lib/i18n/strings_nl.i18n.json index 60510580..aefbbe9c 100644 --- a/lib/i18n/strings_nl.i18n.json +++ b/lib/i18n/strings_nl.i18n.json @@ -263,6 +263,8 @@ "timerActive": "Timer actief", "playbackWillPauseIn": "Afspelen wordt gepauzeerd over ${duration}", "sleepTimerCompleted": "Slaaptimer voltooid - afspelen gepauzeerd", + "autoPlayNext": "Automatisch volgende afspelen", + "playNext": "Volgende afspelen", "playButton": "Afspelen", "pauseButton": "Pauzeren", "seekBackwardButton": "Terugspoelen ${seconds} seconden", diff --git a/lib/i18n/strings_sv.i18n.json b/lib/i18n/strings_sv.i18n.json index 99907e0a..0fd3796e 100644 --- a/lib/i18n/strings_sv.i18n.json +++ b/lib/i18n/strings_sv.i18n.json @@ -263,6 +263,8 @@ "timerActive": "Timer aktiv", "playbackWillPauseIn": "Uppspelningen pausas om ${duration}", "sleepTimerCompleted": "Sovtimer slutförd - uppspelning pausad", + "autoPlayNext": "Spela nästa automatiskt", + "playNext": "Spela nästa", "playButton": "Spela", "pauseButton": "Pausa", "seekBackwardButton": "Spola bakåt ${seconds} sekunder", diff --git a/lib/i18n/strings_zh.i18n.json b/lib/i18n/strings_zh.i18n.json index 6c4724bf..3a8a15af 100644 --- a/lib/i18n/strings_zh.i18n.json +++ b/lib/i18n/strings_zh.i18n.json @@ -263,6 +263,8 @@ "timerActive": "定时器已激活", "playbackWillPauseIn": "播放将在 ${duration} 后暂停", "sleepTimerCompleted": "睡眠定时器已完成 - 播放已暂停", + "autoPlayNext": "自动播放下一集", + "playNext": "播放下一集", "playButton": "播放", "pauseButton": "暂停", "seekBackwardButton": "后退 ${seconds} 秒", diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 193a0780..c6a8746d 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -1105,15 +1105,21 @@ class VideoPlayerScreenState extends State with WidgetsBindin } } - void _onVideoCompleted(bool completed) { + void _onVideoCompleted(bool completed) async { if (completed && _nextEpisode != null && !_showPlayNextDialog && !_completionTriggered) { _completionTriggered = true; + final settings = await SettingsService.getInstance(); + final autoPlayEnabled = settings.getAutoPlayNextEpisode(); + setState(() { _showPlayNextDialog = true; - _autoPlayCountdown = 5; + _autoPlayCountdown = autoPlayEnabled ? 5 : -1; }); - _startAutoPlayTimer(); + + if (autoPlayEnabled) { + _startAutoPlayTimer(); + } } } @@ -1720,9 +1726,12 @@ class VideoPlayerScreenState extends State with WidgetsBindin child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('$_autoPlayCountdown'), - const SizedBox(width: 4), - const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 18), + if (_autoPlayCountdown > 0) ...[ + Text('$_autoPlayCountdown'), + const SizedBox(width: 4), + const AppIcon(Symbols.play_arrow_rounded, fill: 1, size: 18), + ] else + Text(t.videoControls.playNext), ], ), ), diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 6f6bc088..9ab6cbce 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -62,6 +62,7 @@ class SettingsService extends BaseSharedPreferencesService { static const String _keyEnableDiscordRPC = 'enable_discord_rpc'; static const String _keyMatchContentFrameRate = 'match_content_frame_rate'; static const String _keyDefaultPlaybackSpeed = 'default_playback_speed'; + static const String _keyAutoPlayNextEpisode = 'auto_play_next_episode'; SettingsService._(); @@ -961,6 +962,15 @@ class SettingsService extends BaseSharedPreferencesService { return prefs.getDouble(_keyDefaultPlaybackSpeed) ?? 1.0; // Default: normal speed } + // Auto-Play Next Episode + Future setAutoPlayNextEpisode(bool enabled) async { + await prefs.setBool(_keyAutoPlayNextEpisode, enabled); + } + + bool getAutoPlayNextEpisode() { + return prefs.getBool(_keyAutoPlayNextEpisode) ?? true; // Default enabled + } + // Reset all settings to defaults Future resetAllSettings() async { await Future.wait([ @@ -1004,6 +1014,7 @@ class SettingsService extends BaseSharedPreferencesService { prefs.remove(_keyEnableDiscordRPC), prefs.remove(_keyMatchContentFrameRate), prefs.remove(_keyDefaultPlaybackSpeed), + prefs.remove(_keyAutoPlayNextEpisode), ]); } diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 4893f0ac..575a1d39 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -112,6 +112,7 @@ class _VideoSettingsSheetState extends State { late int _subtitleSyncOffset; bool _enableHDR = true; bool _showPerformanceOverlay = false; + bool _autoPlayNextEpisode = true; late final FocusNode _initialFocusNode; @override @@ -134,6 +135,7 @@ class _VideoSettingsSheetState extends State { setState(() { _enableHDR = settings.getEnableHDR(); _showPerformanceOverlay = settings.getShowPerformanceOverlay(); + _autoPlayNextEpisode = settings.getAutoPlayNextEpisode(); }); } @@ -157,6 +159,15 @@ class _VideoSettingsSheetState extends State { }); } + Future _toggleAutoPlayNextEpisode() async { + final newValue = !_autoPlayNextEpisode; + final settings = await SettingsService.getInstance(); + await settings.setAutoPlayNextEpisode(newValue); + setState(() { + _autoPlayNextEpisode = newValue; + }); + } + void _navigateTo(_SettingsView view) { setState(() { _currentView = view; @@ -280,6 +291,22 @@ class _VideoSettingsSheetState extends State { onTap: _toggleHDR, ), + // Auto-Play Next Episode Toggle + ListTile( + leading: AppIcon( + Symbols.skip_next_rounded, + fill: 1, + color: _autoPlayNextEpisode ? Colors.amber : Colors.white70, + ), + title: Text(t.videoControls.autoPlayNext, style: const TextStyle(color: Colors.white)), + trailing: Switch( + value: _autoPlayNextEpisode, + onChanged: (_) => _toggleAutoPlayNextEpisode(), + activeThumbColor: Colors.amber, + ), + onTap: _toggleAutoPlayNextEpisode, + ), + // Audio Output Device (Desktop only) if (isDesktop) StreamBuilder(