import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/contracts/player_gestures.dart'; import 'shared/settings_persistence.dart'; class PlayerSettingsState { final String hwdecMode; final double volume; final int seekForwardSeconds; final int seekBackwardSeconds; final double keyboardHoldLeftSpeed; final double keyboardHoldRightSpeed; final GestureAction mouseLeftClickAction; final GestureAction mouseLeftDoubleClickAction; final GestureAction mouseRightClickAction; final GestureAction mouseRightDoubleClickAction; final bool autoPip; final bool pipShowEpisodeActions; final double? androidBrightness; const PlayerSettingsState({ this.hwdecMode = 'auto-copy', this.volume = 100.0, this.seekForwardSeconds = 15, this.seekBackwardSeconds = 15, this.keyboardHoldLeftSpeed = 0.5, this.keyboardHoldRightSpeed = 3.0, this.mouseLeftClickAction = GestureAction.playPause, this.mouseLeftDoubleClickAction = GestureAction.fullscreen, this.mouseRightClickAction = GestureAction.toggleControls, this.mouseRightDoubleClickAction = GestureAction.none, this.autoPip = true, this.pipShowEpisodeActions = true, this.androidBrightness, }); PlayerSettingsState copyWith({ String? hwdecMode, double? volume, int? seekForwardSeconds, int? seekBackwardSeconds, double? keyboardHoldLeftSpeed, double? keyboardHoldRightSpeed, GestureAction? mouseLeftClickAction, GestureAction? mouseLeftDoubleClickAction, GestureAction? mouseRightClickAction, GestureAction? mouseRightDoubleClickAction, bool? autoPip, bool? pipShowEpisodeActions, double? androidBrightness, }) => PlayerSettingsState( hwdecMode: hwdecMode ?? this.hwdecMode, volume: volume ?? this.volume, seekForwardSeconds: seekForwardSeconds ?? this.seekForwardSeconds, seekBackwardSeconds: seekBackwardSeconds ?? this.seekBackwardSeconds, keyboardHoldLeftSpeed: keyboardHoldLeftSpeed ?? this.keyboardHoldLeftSpeed, keyboardHoldRightSpeed: keyboardHoldRightSpeed ?? this.keyboardHoldRightSpeed, mouseLeftClickAction: mouseLeftClickAction ?? this.mouseLeftClickAction, mouseLeftDoubleClickAction: mouseLeftDoubleClickAction ?? this.mouseLeftDoubleClickAction, mouseRightClickAction: mouseRightClickAction ?? this.mouseRightClickAction, mouseRightDoubleClickAction: mouseRightDoubleClickAction ?? this.mouseRightDoubleClickAction, autoPip: autoPip ?? this.autoPip, pipShowEpisodeActions: pipShowEpisodeActions ?? this.pipShowEpisodeActions, androidBrightness: androidBrightness ?? this.androidBrightness, ); } class PlayerSettingsNotifier extends AsyncNotifier with SettingsPersistence { static const _keyHwdec = 'smplayer.player_hwdec'; static const _legacyKeyEnginePreference = 'smplayer.player_engine_preference'; static const _keyVolume = 'smplayer.player_volume'; static const _keySeekForward = 'smplayer.player_seek_forward'; static const _keySeekBackward = 'smplayer.player_seek_backward'; static const _keyKbdHoldLeft = 'smplayer.gesture_kbd_hold_left_speed'; static const _keyKbdHoldRight = 'smplayer.gesture_kbd_hold_right_speed'; static const _keyMouseLeftClick = 'smplayer.gesture_mouse_left_click'; static const _keyMouseLeftDblClick = 'smplayer.gesture_mouse_left_dbl_click'; static const _keyMouseRightClick = 'smplayer.gesture_mouse_right_click'; static const _keyMouseRightDblClick = 'smplayer.gesture_mouse_right_dbl_click'; static const _keyAutoPip = 'smplayer.player_auto_pip'; static const _keyPipEpisodeActions = 'smplayer.player_pip_episode_actions'; static const _keyAndroidBrightness = 'smplayer.player_android_brightness'; static const _speedAllowed = [ 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 8.0, ]; static double _normalizeSpeed(double v, double fallback) { for (final s in _speedAllowed) { if ((s - v).abs() < 0.001) return s; } return fallback; } @override Future build() async { final prefs = await getPrefs(); if (prefs.containsKey(_keyHwdec)) { await prefs.remove(_keyHwdec); } const hwdec = 'auto-copy'; final volume = prefs.getDouble(_keyVolume) ?? 100.0; final seekFwd = prefs.getInt(_keySeekForward) ?? 15; final seekBwd = prefs.getInt(_keySeekBackward) ?? 15; if (prefs.containsKey(_legacyKeyEnginePreference)) { await prefs.remove(_legacyKeyEnginePreference); } final holdLeft = _normalizeSpeed( prefs.getDouble(_keyKbdHoldLeft) ?? 0.5, 0.5, ); final holdRight = _normalizeSpeed( prefs.getDouble(_keyKbdHoldRight) ?? 3.0, 3.0, ); final autoPip = prefs.getBool(_keyAutoPip) ?? true; final pipEpisodeActions = prefs.getBool(_keyPipEpisodeActions) ?? true; final androidBrightness = prefs .getDouble(_keyAndroidBrightness) ?.clamp(0.0, 1.0); return PlayerSettingsState( hwdecMode: hwdec, volume: volume.clamp(0.0, 200.0), seekForwardSeconds: seekFwd.clamp(5, 120), seekBackwardSeconds: seekBwd.clamp(5, 120), keyboardHoldLeftSpeed: holdLeft, keyboardHoldRightSpeed: holdRight, mouseLeftClickAction: gestureActionFromName( prefs.getString(_keyMouseLeftClick), GestureAction.playPause, ), mouseLeftDoubleClickAction: gestureActionFromName( prefs.getString(_keyMouseLeftDblClick), GestureAction.fullscreen, ), mouseRightClickAction: gestureActionFromName( prefs.getString(_keyMouseRightClick), GestureAction.toggleControls, ), mouseRightDoubleClickAction: gestureActionFromName( prefs.getString(_keyMouseRightDblClick), GestureAction.none, ), autoPip: autoPip, pipShowEpisodeActions: pipEpisodeActions, androidBrightness: androidBrightness, ); } Future setVolume(double volume) async { final clamped = volume.clamp(0.0, 200.0); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(volume: clamped)); await persistField(_keyVolume, clamped); } Future setSeekForwardSeconds(int seconds) async { final clamped = seconds.clamp(5, 120); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(seekForwardSeconds: clamped)); await persistField(_keySeekForward, clamped); } Future setSeekBackwardSeconds(int seconds) async { final clamped = seconds.clamp(5, 120); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(seekBackwardSeconds: clamped)); await persistField(_keySeekBackward, clamped); } Future setKeyboardHoldLeftSpeed(double v) async { final normalized = _normalizeSpeed(v, 0.5); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data( current.copyWith(keyboardHoldLeftSpeed: normalized), ); await persistField(_keyKbdHoldLeft, normalized); } Future setKeyboardHoldRightSpeed(double v) async { final normalized = _normalizeSpeed(v, 3.0); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data( current.copyWith(keyboardHoldRightSpeed: normalized), ); await persistField(_keyKbdHoldRight, normalized); } Future setMouseLeftClickAction(GestureAction a) async { final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(mouseLeftClickAction: a)); await persistField(_keyMouseLeftClick, a.name); } Future setMouseLeftDoubleClickAction(GestureAction a) async { final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(mouseLeftDoubleClickAction: a)); await persistField(_keyMouseLeftDblClick, a.name); } Future setMouseRightClickAction(GestureAction a) async { final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(mouseRightClickAction: a)); await persistField(_keyMouseRightClick, a.name); } Future setMouseRightDoubleClickAction(GestureAction a) async { final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(mouseRightDoubleClickAction: a)); await persistField(_keyMouseRightDblClick, a.name); } Future setAndroidBrightness(double brightness) async { final clamped = brightness.clamp(0.0, 1.0); final current = state.value ?? const PlayerSettingsState(); state = AsyncValue.data(current.copyWith(androidBrightness: clamped)); await persistField(_keyAndroidBrightness, clamped); } } final playerSettingsProvider = AsyncNotifierProvider( PlayerSettingsNotifier.new, );