diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 0d4fdd6c..4009dbaa 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: 3505 (500 per locale) +/// Strings: 3533 (504 per locale) /// -/// Built on 2025-12-21 at 14:43 UTC +/// Built on 2025-12-23 at 06:54 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -398,6 +398,10 @@ class _StringsSettingsEn { String get downloadOnWifiOnlyDescription => 'Prevent downloads when on cellular data'; String get cellularDownloadBlocked => 'Downloads are disabled on cellular data. Connect to WiFi or change the setting.'; + String get maxVolume => 'Maximum Volume'; + String get maxVolumeDescription => 'Allow volume boost above 100% for quiet media'; + String maxVolumePercent({required Object percent}) => '${percent}%'; + String get maxVolumeHint => 'Enter max volume (100-300)'; } // Path: search @@ -1450,6 +1454,14 @@ class _StringsSettingsDe implements _StringsSettingsEn { @override String get cellularDownloadBlocked => 'Downloads sind über mobile Daten deaktiviert. Verbinde dich mit einem WLAN oder ändere die Einstellung.'; + @override + String get maxVolume => 'Maximale Lautstärke'; + @override + String get maxVolumeDescription => 'Lautstärke über 100% für leise Medien erlauben'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => 'Maximale Lautstärke eingeben (100-300)'; } // Path: search @@ -2902,6 +2914,14 @@ class _StringsSettingsIt implements _StringsSettingsEn { @override String get cellularDownloadBlocked => 'I download sono disabilitati sulla rete dati cellulare. Connettiti al WiFi o modifica l\'impostazione.'; + @override + String get maxVolume => 'Volume massimo'; + @override + String get maxVolumeDescription => 'Consenti volume superiore al 100% per contenuti audio bassi'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => 'Inserisci volume massimo (100-300)'; } // Path: search @@ -4347,6 +4367,14 @@ class _StringsSettingsKo implements _StringsSettingsEn { String get downloadOnWifiOnlyDescription => '셀룰러 데이터 사용 시 다운로드 불가'; @override String get cellularDownloadBlocked => '셀룰러 데이터에서 다운로드가 차단 되었습니다. WiFi에 연결하거나 설정을 변경하세요.'; + @override + String get maxVolume => '최대 볼륨'; + @override + String get maxVolumeDescription => '조용한 미디어를 위해 100% 이상의 볼륨 허용'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => '최대 볼륨 입력 (100-300)'; } // Path: search @@ -5795,6 +5823,14 @@ class _StringsSettingsNl implements _StringsSettingsEn { @override String get cellularDownloadBlocked => 'Downloads zijn uitgeschakeld bij mobiele data. Maak verbinding met WiFi of wijzig de instelling.'; + @override + String get maxVolume => 'Maximaal volume'; + @override + String get maxVolumeDescription => 'Volume boven 100% toestaan voor stille media'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => 'Voer maximaal volume in (100-300)'; } // Path: search @@ -7246,6 +7282,14 @@ class _StringsSettingsSv implements _StringsSettingsEn { @override String get cellularDownloadBlocked => 'Nedladdningar är inaktiverade på mobildata. Anslut till WiFi eller ändra inställningen.'; + @override + String get maxVolume => 'Maximal volym'; + @override + String get maxVolumeDescription => 'Tillåt volym över 100% för tyst media'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => 'Ange maximal volym (100-300)'; } // Path: search @@ -8693,6 +8737,14 @@ class _StringsSettingsZh implements _StringsSettingsEn { String get downloadOnWifiOnlyDescription => '使用蜂窝数据时禁止下载'; @override String get cellularDownloadBlocked => '蜂窝数据下已禁用下载。请连接 WiFi 或更改设置。'; + @override + String get maxVolume => '最大音量'; + @override + String get maxVolumeDescription => '允许音量超过 100% 以适应安静的媒体'; + @override + String maxVolumePercent({required Object percent}) => '${percent}%'; + @override + String get maxVolumeHint => '输入最大音量 (100-300)'; } // Path: search @@ -9988,6 +10040,14 @@ extension on Translations { return 'Prevent downloads when on cellular data'; case 'settings.cellularDownloadBlocked': return 'Downloads are disabled on cellular data. Connect to WiFi or change the setting.'; + case 'settings.maxVolume': + return 'Maximum Volume'; + case 'settings.maxVolumeDescription': + return 'Allow volume boost above 100% for quiet media'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return 'Enter max volume (100-300)'; case 'search.hint': return 'Search movies, shows, music...'; case 'search.tryDifferentTerm': @@ -11000,6 +11060,14 @@ extension on _StringsDe { return 'Downloads über mobile Daten verhindern'; case 'settings.cellularDownloadBlocked': return 'Downloads sind über mobile Daten deaktiviert. Verbinde dich mit einem WLAN oder ändere die Einstellung.'; + case 'settings.maxVolume': + return 'Maximale Lautstärke'; + case 'settings.maxVolumeDescription': + return 'Lautstärke über 100% für leise Medien erlauben'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return 'Maximale Lautstärke eingeben (100-300)'; case 'search.hint': return 'Filme, Serien, Musik suchen...'; case 'search.tryDifferentTerm': @@ -12016,6 +12084,14 @@ extension on _StringsIt { return 'Impedisci i download quando si utilizza la rete dati cellulare'; case 'settings.cellularDownloadBlocked': return 'I download sono disabilitati sulla rete dati cellulare. Connettiti al WiFi o modifica l\'impostazione.'; + case 'settings.maxVolume': + return 'Volume massimo'; + case 'settings.maxVolumeDescription': + return 'Consenti volume superiore al 100% per contenuti audio bassi'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return 'Inserisci volume massimo (100-300)'; case 'search.hint': return 'Cerca film. spettacoli, musica...'; case 'search.tryDifferentTerm': @@ -13032,6 +13108,14 @@ extension on _StringsKo { return '셀룰러 데이터 사용 시 다운로드 불가'; case 'settings.cellularDownloadBlocked': return '셀룰러 데이터에서 다운로드가 차단 되었습니다. WiFi에 연결하거나 설정을 변경하세요.'; + case 'settings.maxVolume': + return '최대 볼륨'; + case 'settings.maxVolumeDescription': + return '조용한 미디어를 위해 100% 이상의 볼륨 허용'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return '최대 볼륨 입력 (100-300)'; case 'search.hint': return '영화, 시리즈, 음악 등을 검색하세요...'; case 'search.tryDifferentTerm': @@ -14045,6 +14129,14 @@ extension on _StringsNl { return 'Voorkom downloads bij gebruik van mobiele data'; case 'settings.cellularDownloadBlocked': return 'Downloads zijn uitgeschakeld bij mobiele data. Maak verbinding met WiFi of wijzig de instelling.'; + case 'settings.maxVolume': + return 'Maximaal volume'; + case 'settings.maxVolumeDescription': + return 'Volume boven 100% toestaan voor stille media'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return 'Voer maximaal volume in (100-300)'; case 'search.hint': return 'Zoek films, series, muziek...'; case 'search.tryDifferentTerm': @@ -15061,6 +15153,14 @@ extension on _StringsSv { return 'Förhindra nedladdningar vid användning av mobildata'; case 'settings.cellularDownloadBlocked': return 'Nedladdningar är inaktiverade på mobildata. Anslut till WiFi eller ändra inställningen.'; + case 'settings.maxVolume': + return 'Maximal volym'; + case 'settings.maxVolumeDescription': + return 'Tillåt volym över 100% för tyst media'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return 'Ange maximal volym (100-300)'; case 'search.hint': return 'Sök filmer, serier, musik...'; case 'search.tryDifferentTerm': @@ -16076,6 +16176,14 @@ extension on _StringsZh { return '使用蜂窝数据时禁止下载'; case 'settings.cellularDownloadBlocked': return '蜂窝数据下已禁用下载。请连接 WiFi 或更改设置。'; + case 'settings.maxVolume': + return '最大音量'; + case 'settings.maxVolumeDescription': + return '允许音量超过 100% 以适应安静的媒体'; + case 'settings.maxVolumePercent': + return ({required Object percent}) => '${percent}%'; + case 'settings.maxVolumeHint': + return '输入最大音量 (100-300)'; case 'search.hint': return '搜索电影、系列、音乐...'; case 'search.tryDifferentTerm': diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index 49b3486c..d065cd31 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -142,7 +142,11 @@ "downloadLocationSelectError": "Failed to select folder", "downloadOnWifiOnly": "Download on WiFi only", "downloadOnWifiOnlyDescription": "Prevent downloads when on cellular data", - "cellularDownloadBlocked": "Downloads are disabled on cellular data. Connect to WiFi or change the setting." + "cellularDownloadBlocked": "Downloads are disabled on cellular data. Connect to WiFi or change the setting.", + "maxVolume": "Maximum Volume", + "maxVolumeDescription": "Allow volume boost above 100% for quiet media", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "Enter max volume (100-300)" }, "search": { "hint": "Search movies, shows, music...", diff --git a/lib/i18n/strings_de.i18n.json b/lib/i18n/strings_de.i18n.json index 81152985..09932e0a 100644 --- a/lib/i18n/strings_de.i18n.json +++ b/lib/i18n/strings_de.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "Ordnerauswahl fehlgeschlagen", "downloadOnWifiOnly": "Nur über WLAN herunterladen", "downloadOnWifiOnlyDescription": "Downloads über mobile Daten verhindern", - "cellularDownloadBlocked": "Downloads sind über mobile Daten deaktiviert. Verbinde dich mit einem WLAN oder ändere die Einstellung." + "cellularDownloadBlocked": "Downloads sind über mobile Daten deaktiviert. Verbinde dich mit einem WLAN oder ändere die Einstellung.", + "maxVolume": "Maximale Lautstärke", + "maxVolumeDescription": "Lautstärke über 100% für leise Medien erlauben", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "Maximale Lautstärke eingeben (100-300)" }, "search": { "hint": "Filme, Serien, Musik suchen...", diff --git a/lib/i18n/strings_it.i18n.json b/lib/i18n/strings_it.i18n.json index 7492085e..cc8f5bc0 100644 --- a/lib/i18n/strings_it.i18n.json +++ b/lib/i18n/strings_it.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "Impossibile selezionare la cartella", "downloadOnWifiOnly": "Scarica solo con WiFi", "downloadOnWifiOnlyDescription": "Impedisci i download quando si utilizza la rete dati cellulare", - "cellularDownloadBlocked": "I download sono disabilitati sulla rete dati cellulare. Connettiti al WiFi o modifica l'impostazione." + "cellularDownloadBlocked": "I download sono disabilitati sulla rete dati cellulare. Connettiti al WiFi o modifica l'impostazione.", + "maxVolume": "Volume massimo", + "maxVolumeDescription": "Consenti volume superiore al 100% per contenuti audio bassi", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "Inserisci volume massimo (100-300)" }, "search": { "hint": "Cerca film. spettacoli, musica...", diff --git a/lib/i18n/strings_ko.i18n.json b/lib/i18n/strings_ko.i18n.json index 6ecb9dd0..45fbc9de 100644 --- a/lib/i18n/strings_ko.i18n.json +++ b/lib/i18n/strings_ko.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "폴더 선택 실패", "downloadOnWifiOnly": "WiFi 연결 시에만 다운로드", "downloadOnWifiOnlyDescription": "셀룰러 데이터 사용 시 다운로드 불가", - "cellularDownloadBlocked": "셀룰러 데이터에서 다운로드가 차단 되었습니다. WiFi에 연결하거나 설정을 변경하세요." + "cellularDownloadBlocked": "셀룰러 데이터에서 다운로드가 차단 되었습니다. WiFi에 연결하거나 설정을 변경하세요.", + "maxVolume": "최대 볼륨", + "maxVolumeDescription": "조용한 미디어를 위해 100% 이상의 볼륨 허용", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "최대 볼륨 입력 (100-300)" }, "search": { "hint": "영화, 시리즈, 음악 등을 검색하세요...", diff --git a/lib/i18n/strings_nl.i18n.json b/lib/i18n/strings_nl.i18n.json index 993ae289..b0cbc35f 100644 --- a/lib/i18n/strings_nl.i18n.json +++ b/lib/i18n/strings_nl.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "Kan map niet selecteren", "downloadOnWifiOnly": "Alleen via WiFi downloaden", "downloadOnWifiOnlyDescription": "Voorkom downloads bij gebruik van mobiele data", - "cellularDownloadBlocked": "Downloads zijn uitgeschakeld bij mobiele data. Maak verbinding met WiFi of wijzig de instelling." + "cellularDownloadBlocked": "Downloads zijn uitgeschakeld bij mobiele data. Maak verbinding met WiFi of wijzig de instelling.", + "maxVolume": "Maximaal volume", + "maxVolumeDescription": "Volume boven 100% toestaan voor stille media", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "Voer maximaal volume in (100-300)" }, "search": { "hint": "Zoek films, series, muziek...", diff --git a/lib/i18n/strings_sv.i18n.json b/lib/i18n/strings_sv.i18n.json index 306fe984..28e07a86 100644 --- a/lib/i18n/strings_sv.i18n.json +++ b/lib/i18n/strings_sv.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "Kunde inte välja mapp", "downloadOnWifiOnly": "Ladda ner endast på WiFi", "downloadOnWifiOnlyDescription": "Förhindra nedladdningar vid användning av mobildata", - "cellularDownloadBlocked": "Nedladdningar är inaktiverade på mobildata. Anslut till WiFi eller ändra inställningen." + "cellularDownloadBlocked": "Nedladdningar är inaktiverade på mobildata. Anslut till WiFi eller ändra inställningen.", + "maxVolume": "Maximal volym", + "maxVolumeDescription": "Tillåt volym över 100% för tyst media", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "Ange maximal volym (100-300)" }, "search": { "hint": "Sök filmer, serier, musik...", diff --git a/lib/i18n/strings_zh.i18n.json b/lib/i18n/strings_zh.i18n.json index 6bbbc984..fa0fb596 100644 --- a/lib/i18n/strings_zh.i18n.json +++ b/lib/i18n/strings_zh.i18n.json @@ -143,7 +143,11 @@ "downloadLocationSelectError": "选择文件夹失败", "downloadOnWifiOnly": "仅在 WiFi 时下载", "downloadOnWifiOnlyDescription": "使用蜂窝数据时禁止下载", - "cellularDownloadBlocked": "蜂窝数据下已禁用下载。请连接 WiFi 或更改设置。" + "cellularDownloadBlocked": "蜂窝数据下已禁用下载。请连接 WiFi 或更改设置。", + "maxVolume": "最大音量", + "maxVolumeDescription": "允许音量超过 100% 以适应安静的媒体", + "maxVolumePercent": "${percent}%", + "maxVolumeHint": "输入最大音量 (100-300)" }, "search": { "hint": "搜索电影、系列、音乐...", diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index a8a5b8e0..0eb6954f 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -1064,9 +1064,7 @@ class _DiscoverScreenState extends State Widget _buildHeroSection() { final statusBarHeight = MediaQuery.of(context).padding.top; final useSideNav = PlatformDetector.shouldUseSideNavigation(context); - final heroHeight = useSideNav - ? MediaQuery.of(context).size.height * 0.75 - : 500 + statusBarHeight; + final heroHeight = useSideNav ? MediaQuery.of(context).size.height * 0.75 : 500 + statusBarHeight; return SliverToBoxAdapter( child: Focus( focusNode: _heroFocusNode, diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 13dbeb02..ab96d47f 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -60,6 +60,7 @@ class _SettingsScreenState extends State { int _autoSkipDelay = 5; bool _downloadOnWifiOnly = false; bool _videoPlayerNavigationEnabled = false; + int _maxVolume = 100; // Update checking state bool _isCheckingForUpdate = false; @@ -90,6 +91,7 @@ class _SettingsScreenState extends State { _autoSkipDelay = _settingsService.getAutoSkipDelay(); _downloadOnWifiOnly = _settingsService.getDownloadOnWifiOnly(); _videoPlayerNavigationEnabled = _settingsService.getVideoPlayerNavigationEnabled(); + _maxVolume = _settingsService.getMaxVolume(); _isLoading = false; }); } @@ -283,6 +285,13 @@ class _SettingsScreenState extends State { trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showSleepTimerDurationDialog(), ), + ListTile( + leading: const AppIcon(Symbols.volume_up_rounded, fill: 1), + title: Text(t.settings.maxVolume), + subtitle: Text(t.settings.maxVolumePercent(percent: _maxVolume.toString())), + trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), + onTap: () => _showMaxVolumeDialog(), + ), SwitchListTile( secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1), title: Text(t.settings.rememberTrackSelections), @@ -966,6 +975,21 @@ class _SettingsScreenState extends State { ); } + void _showMaxVolumeDialog() { + _showNumericInputDialog( + title: t.settings.maxVolume, + labelText: t.settings.maxVolumeDescription, + suffixText: '%', + min: 100, + max: 300, + currentValue: _maxVolume, + onSave: (value) async { + setState(() => _maxVolume = value); + await _settingsService.setMaxVolume(value); + }, + ); + } + void _showKeyboardShortcutsDialog() { if (_keyboardService == null) return; diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index b81063c3..d7156dab 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -334,8 +334,12 @@ class VideoPlayerScreenState extends State with WidgetsBindin } } - // Apply saved volume - final savedVolume = settingsService.getVolume(); + // Set max volume limit for volume boost + final maxVolume = settingsService.getMaxVolume(); + await player!.setProperty('volume-max', maxVolume.toString()); + + // Apply saved volume (clamped to max volume) + final savedVolume = settingsService.getVolume().clamp(0.0, maxVolume.toDouble()); player!.setVolume(savedVolume); // Notify that player is ready @@ -1647,7 +1651,10 @@ class VideoPlayerScreenState extends State with WidgetsBindin child: Center( child: Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.5), shape: BoxShape.circle), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.5), + shape: BoxShape.circle, + ), child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 3), ), ), @@ -1661,9 +1668,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin valueListenable: _isExiting, builder: (context, isExiting, child) { if (!isExiting) return const SizedBox.shrink(); - return Positioned.fill( - child: Container(color: Colors.black), - ); + return Positioned.fill(child: Container(color: Colors.black)); }, ), ], diff --git a/lib/services/keyboard_shortcuts_service.dart b/lib/services/keyboard_shortcuts_service.dart index 5bae5058..3102865f 100644 --- a/lib/services/keyboard_shortcuts_service.dart +++ b/lib/services/keyboard_shortcuts_service.dart @@ -14,6 +14,7 @@ class KeyboardShortcutsService { Map _hotkeys = {}; // New HotKey objects int _seekTimeSmall = 10; // Default, loaded from settings int _seekTimeLarge = 30; // Default, loaded from settings + int _maxVolume = 100; // Default, loaded from settings (100-300%) KeyboardShortcutsService._(); @@ -38,6 +39,7 @@ class KeyboardShortcutsService { _hotkeys = await _settingsService.getKeyboardHotkeys(); // Primary method _seekTimeSmall = _settingsService.getSeekTimeSmall(); _seekTimeLarge = _settingsService.getSeekTimeLarge(); + _maxVolume = _settingsService.getMaxVolume(); } Map get shortcuts => Map.from(_shortcuts); @@ -258,12 +260,12 @@ class KeyboardShortcutsService { player.playOrPause(); break; case 'volume_up': - final newVolume = (player.state.volume + 10).clamp(0.0, 100.0); + final newVolume = (player.state.volume + 10).clamp(0.0, _maxVolume.toDouble()); player.setVolume(newVolume); _settingsService.setVolume(newVolume); break; case 'volume_down': - final newVolume = (player.state.volume - 10).clamp(0.0, 100.0); + final newVolume = (player.state.volume - 10).clamp(0.0, _maxVolume.toDouble()); player.setVolume(newVolume); _settingsService.setVolume(newVolume); break; diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 35677488..11bad471 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -53,6 +53,7 @@ class SettingsService extends BaseSharedPreferencesService { static const String _keyShowPerformanceOverlay = 'show_performance_overlay'; static const String _keyMpvConfigEntries = 'mpv_config_entries'; static const String _keyMpvConfigPresets = 'mpv_config_presets'; + static const String _keyMaxVolume = 'max_volume'; SettingsService._(); @@ -222,6 +223,15 @@ class SettingsService extends BaseSharedPreferencesService { return prefs.getDouble(_keyVolume) ?? 100.0; // Default: full volume } + // Max Volume (100-300%, for volume boost) + Future setMaxVolume(int percent) async { + await prefs.setInt(_keyMaxVolume, percent.clamp(100, 300)); + } + + int getMaxVolume() { + return prefs.getInt(_keyMaxVolume) ?? 100; // Default: 100% (no boost) + } + // Rotation Lock (mobile only) Future setRotationLocked(bool locked) async { await prefs.setBool(_keyRotationLocked, locked); @@ -907,6 +917,7 @@ class SettingsService extends BaseSharedPreferencesService { prefs.remove(_keyAudioSyncOffset), prefs.remove(_keySubtitleSyncOffset), prefs.remove(_keyVolume), + prefs.remove(_keyMaxVolume), prefs.remove(_keySubtitleFontSize), prefs.remove(_keySubtitleTextColor), prefs.remove(_keySubtitleBorderSize), diff --git a/lib/widgets/video_controls/widgets/volume_control.dart b/lib/widgets/video_controls/widgets/volume_control.dart index c0e7edd9..b2c280ea 100644 --- a/lib/widgets/video_controls/widgets/volume_control.dart +++ b/lib/widgets/video_controls/widgets/volume_control.dart @@ -52,6 +52,24 @@ class _VolumeControlState extends State { /// Volume step size for keyboard adjustment. static const double _volumeStep = 5.0; + /// Maximum volume from settings (100-300). + int _maxVolume = 100; + + @override + void initState() { + super.initState(); + _loadMaxVolume(); + } + + Future _loadMaxVolume() async { + final settings = await SettingsService.getInstance(); + if (mounted) { + setState(() { + _maxVolume = settings.getMaxVolume(); + }); + } + } + void _enterAdjustMode() { setState(() { _isAdjustMode = true; @@ -66,7 +84,7 @@ class _VolumeControlState extends State { Future _adjustVolume(double delta) async { final currentVolume = widget.player.state.volume; - final newVolume = (currentVolume + delta).clamp(0.0, 100.0); + final newVolume = (currentVolume + delta).clamp(0.0, _maxVolume.toDouble()); widget.player.setVolume(newVolume); final settings = await SettingsService.getInstance(); await settings.setVolume(newVolume); @@ -178,33 +196,58 @@ class _VolumeControlState extends State { Widget _buildVolumeSlider(double volume, bool isKeyboardMode) { // Show visual indicator when in adjust mode with keyboard final showAdjustIndicator = _isAdjustMode && isKeyboardMode; + final maxVolumeDouble = _maxVolume.toDouble(); + + // Calculate 100% marker position as fraction of slider width + // Only show marker if max volume > 100 + final showMarker = _maxVolume > 100; + final markerPosition = showMarker ? (100.0 / maxVolumeDouble) : 0.0; return SizedBox( width: 100, - child: SliderTheme( - data: SliderThemeData( - trackHeight: showAdjustIndicator ? 4 : 3, - thumbShape: RoundSliderThumbShape(enabledThumbRadius: showAdjustIndicator ? 8 : 6), - overlayShape: const RoundSliderOverlayShape(overlayRadius: 12), - ), - child: Semantics( - label: t.videoControls.volumeSlider, - slider: true, - child: Slider( - value: volume, - min: 0.0, - max: 100.0, - onChanged: (value) { - widget.player.setVolume(value); - }, - onChangeEnd: (value) async { - final settings = await SettingsService.getInstance(); - await settings.setVolume(value); - }, - activeColor: Colors.white, - inactiveColor: Colors.white.withValues(alpha: 0.3), + child: Stack( + alignment: Alignment.centerLeft, + children: [ + // 100% marker (only shown if max > 100) + if (showMarker) + Positioned( + left: 100 * markerPosition - 1, // Adjust for marker width + child: Container( + width: 2, + height: 12, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(1), + ), + ), + ), + // Volume slider + SliderTheme( + data: SliderThemeData( + trackHeight: showAdjustIndicator ? 4 : 3, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: showAdjustIndicator ? 8 : 6), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 12), + ), + child: Semantics( + label: t.videoControls.volumeSlider, + slider: true, + child: Slider( + value: volume.clamp(0.0, maxVolumeDouble), + min: 0.0, + max: maxVolumeDouble, + onChanged: (value) { + widget.player.setVolume(value); + }, + onChangeEnd: (value) async { + final settings = await SettingsService.getInstance(); + await settings.setVolume(value); + }, + activeColor: Colors.white, + inactiveColor: Colors.white.withValues(alpha: 0.3), + ), + ), ), - ), + ], ), ); }