diff --git a/lib/features/doc_viewer/container/video_viewer.dart b/lib/features/doc_viewer/container/video_viewer.dart index cb22a72..b01cb10 100644 --- a/lib/features/doc_viewer/container/video_viewer.dart +++ b/lib/features/doc_viewer/container/video_viewer.dart @@ -423,11 +423,33 @@ class _VideoViewerState extends State { }); }, ); - + String subtitleStyleName = config.subtitleStyle ?? 'Normal'; + String subtitleStyleColor = config.subtitleColor ?? 'white'; + double subtitleSize = config.subtitleSize ; + Color hexToColor(String hexColor) { + final hexCode = hexColor.replaceAll('#', ''); + return Color(int.parse('0x$hexCode')); + } + FontStyle getFontStyleFromString(String styleName) { + switch (styleName.toLowerCase()) { + case 'italic': + return FontStyle.italic; + case 'normal': // Explicitly handle 'normal' (good practice) + default: // Default case for any other string or null + return FontStyle.normal; + } + } + FontStyle currentFontStyle = getFontStyleFromString(subtitleStyleName); return MaterialVideoControlsTheme( fullscreen: mobile, normal: mobile, child: Video( + subtitleViewConfiguration: SubtitleViewConfiguration( + style: TextStyle(color: hexToColor(subtitleStyleColor), + fontSize: subtitleSize, + fontStyle: currentFontStyle, + fontWeight: FontWeight.bold), + ), fit: isScaled ? BoxFit.fitWidth : BoxFit.fitHeight, pauseUponEnteringBackgroundMode: true, key: key, @@ -598,4 +620,4 @@ int calculateSecondsFromProgress( final clampedProgress = progressPercentage.clamp(0.0, 100.0); final currentSeconds = (duration * (clampedProgress / 100)).round(); return currentSeconds; -} +} \ No newline at end of file diff --git a/lib/features/settings/screen/playback_settings_screen.dart b/lib/features/settings/screen/playback_settings_screen.dart index d29d50e..f98e7df 100644 --- a/lib/features/settings/screen/playback_settings_screen.dart +++ b/lib/features/settings/screen/playback_settings_screen.dart @@ -6,6 +6,8 @@ import 'package:pocketbase/pocketbase.dart'; import '../../../engine/engine.dart'; import '../../../utils/load_language.dart'; +import 'package:flex_color_picker/flex_color_picker.dart'; +import 'package:flutter/services.dart'; class PlaybackSettingsScreen extends StatefulWidget { const PlaybackSettingsScreen({super.key}); @@ -21,13 +23,69 @@ class _PlaybackSettingsScreenState extends State { // Playback settings bool _autoPlay = true; double _playbackSpeed = 1.0; + double _subtitleSize = 10.0; String _defaultAudioTrack = 'eng'; String _defaultSubtitleTrack = 'eng'; bool _enableExternalPlayer = true; String? _defaultPlayerId; bool _disabledSubtitle = false; - Map _availableLanguages = {}; + final List _subtitleStyle = [ + 'Normal', + 'Italic', + ]; + String colorToHex(Color color) { + return '#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}'; + } + + Color hexToColor(String hexColor) { + final hexCode = hexColor.replaceAll('#', ''); + return Color(int.parse('0x$hexCode')); + } + + Color _selectedSubtitleColor = Colors.yellow; + + _showColorPickerDialog(BuildContext context) async { + Color? color = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Pick a Subtitle Color'), + content: SingleChildScrollView( + child: ColorPicker( + color: _selectedSubtitleColor, + onColorChanged: (Color color) { + _selectedSubtitleColor = color; + }, + // Remove pickerType + enableShadesSelection: true, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context) + .pop(_selectedSubtitleColor); // Return the color + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + if (color != null) { + setState(() { + _selectedSubtitleColor = color; + }); + _debouncedSave(); // Debounced save after color change + } + } List> get dropdown => _availableLanguages.entries.map((item) { @@ -64,6 +122,12 @@ class _PlaybackSettingsScreenState extends State { ? playbackConfig.externalPlayerId![currentPlatform] : null; _disabledSubtitle = playbackConfig.disableSubtitle; + final subtitleStyle = playbackConfig.subtitleStyle; // Get saved style + _subtitleStyle.removeWhere((style) => + style == subtitleStyle); // Remove saved style from dropdown options + _subtitleStyle.insert(0, subtitleStyle ?? "Normal"); + _selectedSubtitleColor = hexToColor(playbackConfig.subtitleColor!); + _subtitleSize = playbackConfig.subtitleSize.toDouble(); } @override @@ -103,6 +167,9 @@ class _PlaybackSettingsScreenState extends State { 'externalPlayer': _enableExternalPlayer, 'externalPlayerId': extranalId, 'disableSubtitle': _disabledSubtitle, + 'subtitleStyle': _subtitleStyle[0], + 'subtitleColor': colorToHex(_selectedSubtitleColor), + 'subtitleSize': _subtitleSize, }, }; @@ -134,6 +201,12 @@ class _PlaybackSettingsScreenState extends State { @override Widget build(BuildContext context) { + final dropdownstyle = _subtitleStyle.map((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(); if (_error != null) { return Scaffold( body: Center( @@ -178,6 +251,7 @@ class _PlaybackSettingsScreenState extends State { divisions: 18, label: '${_playbackSpeed.toStringAsFixed(2)}x', onChanged: (value) { + HapticFeedback.mediumImpact(); setState(() => _playbackSpeed = double.parse(value.toStringAsFixed(2))); _debouncedSave(); @@ -209,7 +283,7 @@ class _PlaybackSettingsScreenState extends State { _debouncedSave(); }, ), - if (!_disabledSubtitle) + if (!_disabledSubtitle) ...[ ListTile( title: const Text('Default Subtitle Track'), trailing: DropdownButton( @@ -223,6 +297,87 @@ class _PlaybackSettingsScreenState extends State { }, ), ), + Padding( + padding: const EdgeInsets.all(12.0), + child: Material( // Center the text + + child: ConstrainedBox( // Prevent overflow + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, // 80% of screen width + ), + child: Text( + 'Sample Text', + textAlign: TextAlign.center, // Center text within its box + style: TextStyle( + fontSize: _subtitleSize / 2, + color: _selectedSubtitleColor, + fontStyle: _subtitleStyle[0].toLowerCase() == 'italic' + ? FontStyle.italic + : FontStyle.normal, + + ), + ), + ), + ), + ), + ListTile( + title: const Text('Subtitle Style'), + + trailing: DropdownButton( + value: _subtitleStyle[0], + items: dropdownstyle, + onChanged: (value) { + HapticFeedback.mediumImpact(); + if (value != null) { + setState(() { + // <--- Crucial setState here + _subtitleStyle.remove(value); + _subtitleStyle.insert(0, value); + }); + _debouncedSave(); + } + }, + ), + ), + ListTile( + title: const Text('Subtitle Color'), + trailing: GestureDetector( + // Use GestureDetector to make the color display tappable + onTap: () => _showColorPickerDialog(context), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: _selectedSubtitleColor, + borderRadius: BorderRadius.circular(5), + border: Border.all(color: Colors.grey), + ), + ), + ), + ), + ListTile( + title: const Text('Font Size'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Slider( + value: _subtitleSize, + min: 20.0, + max: 60.0, + divisions: 18, + label: '${_subtitleSize.toStringAsFixed(2)}x', + onChanged: (value) { + HapticFeedback.mediumImpact(); + setState(() => + _subtitleSize = double.parse(value.toStringAsFixed(2))); + _debouncedSave(); + }, + ), + Text('Current: ${_subtitleSize.toStringAsFixed(2)}x'), + ], + ), + ), + ], const Divider(), if (!isWeb) SwitchListTile( diff --git a/lib/utils/load_language.dart b/lib/utils/load_language.dart index e850655..82ee437 100644 --- a/lib/utils/load_language.dart +++ b/lib/utils/load_language.dart @@ -24,6 +24,7 @@ Future> loadLanguages(BuildContext context) async { return availableLanguages; } + PlaybackConfig getPlaybackConfig() { final user = AppEngine.engine.pb.authStore.record; if (user == null) { @@ -52,7 +53,10 @@ class PlaybackConfig { @JsonKey(defaultValue: false) final bool externalPlayer; final Map? externalPlayerId; - + final String? subtitleStyle; + final String? subtitleColor; + @JsonKey(defaultValue: 10.0) + final double subtitleSize; PlaybackConfig({ required this.autoPlay, required this.playbackSpeed, @@ -61,6 +65,9 @@ class PlaybackConfig { required this.externalPlayer, required this.disableSubtitle, this.externalPlayerId, + this.subtitleStyle, + this.subtitleColor, + required this.subtitleSize, }); String? get currentPlayerPackage { diff --git a/pubspec.lock b/pubspec.lock index 33fd291..0462eb6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -542,6 +542,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + flex_color_picker: + dependency: "direct main" + description: + name: flex_color_picker + sha256: c083b79f1c57eaeed9f464368be376951230b3cb1876323b784626152a86e480 + url: "https://pub.dev" + source: hosted + version: "3.7.0" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0 + url: "https://pub.dev" + source: hosted + version: "3.5.0" flutter: dependency: "direct main" description: flutter @@ -1878,4 +1894,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.5.3 <4.0.0" - flutter: ">=3.24.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 69d45cc..0a83a80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: cast: ^2.1.0 permission_handler: ^11.3.1 android_intent_plus: ^5.2.1 + flex_color_picker: ^3.7.0 dependency_overrides: media_kit: