From 3d88463c65b769740d688fd2ad0532a28e409b58 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 21 Dec 2025 09:46:55 +0100 Subject: [PATCH] feat: Implement a mobile-optimized IPTV settings tab including video playback, content filters, and cache management. --- .../iptv/screens/lite_player_screen.dart | 139 +++--------------- .../iptv/screens/native_player_screen.dart | 94 ++++++------ .../iptv/widgets/mobile_settings_tab.dart | 33 ++--- pubspec.yaml | 2 +- 4 files changed, 84 insertions(+), 184 deletions(-) diff --git a/lib/mobile/features/iptv/screens/lite_player_screen.dart b/lib/mobile/features/iptv/screens/lite_player_screen.dart index ec436b4..448a9b2 100644 --- a/lib/mobile/features/iptv/screens/lite_player_screen.dart +++ b/lib/mobile/features/iptv/screens/lite_player_screen.dart @@ -77,8 +77,6 @@ class _LitePlayerScreenState extends ConsumerState final FocusNode _deinterlaceFocusNode = FocusNode(); Timer? _controlsTimer; - Timer? _clockTimer; - String _currentTime = ''; @override void initState() { @@ -90,7 +88,6 @@ class _LitePlayerScreenState extends ConsumerState WakelockPlus.enable(); _currentIndex = widget.initialIndex; - _startClock(); // Lock orientation SystemChrome.setPreferredOrientations([ @@ -305,30 +302,34 @@ class _LitePlayerScreenState extends ConsumerState return false; } + // Arrow keys - Show controls or let focus navigate, NO zapping if (key == LogicalKeyboardKey.arrowDown || - key == LogicalKeyboardKey.channelDown) { - if (widget.streamType == StreamType.live) { - // Only switch with arrows if controls are hidden - if (key == LogicalKeyboardKey.channelDown || !_showControls) { - _playNext(); - _onUserInteraction(); - return true; - } + key == LogicalKeyboardKey.arrowUp || + key == LogicalKeyboardKey.arrowLeft || + key == LogicalKeyboardKey.arrowRight) { + if (!_showControls) { + _onUserInteraction(); + return true; } - return false; // Let focus system handle it if controls are visible + return false; // Let Focus system handle navigation } - if (key == LogicalKeyboardKey.arrowUp || - key == LogicalKeyboardKey.channelUp) { + // Channel Down = Previous Channel (inverted as requested) + if (key == LogicalKeyboardKey.channelDown) { if (widget.streamType == StreamType.live) { - // Only switch with arrows if controls are hidden - if (key == LogicalKeyboardKey.channelUp || !_showControls) { - _playPrevious(); - _onUserInteraction(); - return true; - } + _playPrevious(); + _onUserInteraction(); + return true; + } + } + + // Channel Up = Next Channel (inverted as requested) + if (key == LogicalKeyboardKey.channelUp) { + if (widget.streamType == StreamType.live) { + _playNext(); + _onUserInteraction(); + return true; } - return false; // Let focus system handle it if controls are visible } if (widget.streamType != StreamType.live) { @@ -366,22 +367,6 @@ class _LitePlayerScreenState extends ConsumerState return false; } - void _startClock() { - _updateTime(); - _clockTimer = - Timer.periodic(const Duration(seconds: 1), (_) => _updateTime()); - } - - void _updateTime() { - if (mounted) { - final now = DateTime.now(); - setState(() { - _currentTime = - "${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}"; - }); - } - } - @override void dispose() { WidgetsBinding.instance.removeObserver(this); @@ -389,7 +374,6 @@ class _LitePlayerScreenState extends ConsumerState WakelockPlus.disable(); _controlsTimer?.cancel(); - _clockTimer?.cancel(); _epgTimer?.cancel(); _controller?.dispose(); _xtreamService?.dispose(); @@ -564,13 +548,6 @@ class _LitePlayerScreenState extends ConsumerState style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)), const Spacer(), - if (ref.watch(mobileSettingsProvider).showClock) ...[ - const SizedBox(width: 16), - Text( - _currentTime, - style: const TextStyle(color: Colors.white70), - ), - ], ], ), ), @@ -739,78 +716,6 @@ class _LitePlayerScreenState extends ConsumerState ), ), - // Deinterlace Toggle Button (Only for Live) - if (widget.streamType == StreamType.live) - Positioned( - bottom: 20, // Moved down and left to be visible and out of the way - right: 24, - child: TVFocusable( - focusNode: _deinterlaceFocusNode, - onPressed: _togglePerChannelDeinterlace, - onFocus: _resetControlsTimer, - borderRadius: BorderRadius.circular(12), - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.7), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: ref - .watch(deinterlaceSettingsProvider.notifier) - .isEnabled(widget.channels != null && - widget.channels!.isNotEmpty - ? widget.channels![_currentIndex].streamId - : widget.streamId) - ? AppColors.primary - : Colors.white24, - width: 2, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.waves, - color: ref - .watch(deinterlaceSettingsProvider.notifier) - .isEnabled(widget.channels != null && - widget.channels!.isNotEmpty - ? widget.channels![_currentIndex].streamId - : widget.streamId) - ? AppColors.primary - : Colors.white70, - size: 20, - ), - const SizedBox(width: 8), - const Text( - "Désentrelacer", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - const SizedBox(width: 8), - SizedBox( - height: 24, - child: Switch( - value: ref - .watch(deinterlaceSettingsProvider.notifier) - .isEnabled(widget.channels != null && - widget.channels!.isNotEmpty - ? widget.channels![_currentIndex].streamId - : widget.streamId), - onChanged: (val) => _togglePerChannelDeinterlace(), - activeColor: AppColors.primary, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ), - ], - ), - ), - ), - ), - // Progress Bar (VOD) if (widget.streamType != StreamType.live) Positioned( diff --git a/lib/mobile/features/iptv/screens/native_player_screen.dart b/lib/mobile/features/iptv/screens/native_player_screen.dart index 7c40f6e..ad2a95e 100644 --- a/lib/mobile/features/iptv/screens/native_player_screen.dart +++ b/lib/mobile/features/iptv/screens/native_player_screen.dart @@ -810,58 +810,58 @@ class _NativePlayerScreenState extends ConsumerState return true; } - // Left Arrow - Seek back 10 seconds + // Left Arrow - Show controls or navigate, seek in VOD when hidden if (key == LogicalKeyboardKey.arrowLeft) { - if (_showControls) return false; // Let Focus handle navigation - - if (widget.streamType != StreamType.live) { - final newPos = _position - const Duration(seconds: 5); - _player.seek(newPos < Duration.zero ? Duration.zero : newPos); - _onUserInteraction(); - } - return true; - } - - // Right Arrow - Seek forward 10 seconds - if (key == LogicalKeyboardKey.arrowRight) { - if (_showControls) return false; // Let Focus handle navigation - - if (widget.streamType != StreamType.live) { - final newPos = _position + const Duration(seconds: 5); - if (newPos < _duration) { - _player.seek(newPos); + if (!_showControls) { + if (widget.streamType != StreamType.live) { + final newPos = _position - const Duration(seconds: 5); + _player.seek(newPos < Duration.zero ? Duration.zero : newPos); } _onUserInteraction(); - } - return true; - } - - // Up Arrow - Previous channel (Live only) - if (key == LogicalKeyboardKey.arrowUp || - key == LogicalKeyboardKey.channelUp) { - if (widget.streamType == StreamType.live && widget.channels != null) { - if (key == LogicalKeyboardKey.channelUp || !_showControls) { - _playPrevious(); - return true; - } - } - return false; // Let focus handle navigation if controls shown - } - - // Down Arrow - Next channel (Live) or Cycle Audio (VOD) - if (key == LogicalKeyboardKey.arrowDown || - key == LogicalKeyboardKey.channelDown) { - if (widget.streamType == StreamType.live && widget.channels != null) { - if (key == LogicalKeyboardKey.channelDown || !_showControls) { - _playNext(); - return true; - } - } else if (widget.streamType != StreamType.live && - key == LogicalKeyboardKey.arrowDown) { - _cycleAudioTrack(); return true; } - return false; // Let focus handle navigation if controls shown + return false; // Let Focus handle navigation + } + + // Right Arrow - Show controls or navigate, seek in VOD when hidden + if (key == LogicalKeyboardKey.arrowRight) { + if (!_showControls) { + if (widget.streamType != StreamType.live) { + final newPos = _position + const Duration(seconds: 5); + if (newPos < _duration) { + _player.seek(newPos); + } + } + _onUserInteraction(); + return true; + } + return false; // Let Focus handle navigation + } + + // Up/Down Arrow - Show controls or navigate UI, NO zapping + if (key == LogicalKeyboardKey.arrowUp || + key == LogicalKeyboardKey.arrowDown) { + if (!_showControls) { + _onUserInteraction(); + return true; + } + return false; // Let Focus handle navigation + } + + // Channel Down = Previous Channel (inverted as requested) + if (key == LogicalKeyboardKey.channelDown) { + if (widget.streamType == StreamType.live && widget.channels != null) { + _playPrevious(); + return true; + } + } + + // Channel Up = Next Channel (inverted as requested) + if (key == LogicalKeyboardKey.channelUp) { + if (widget.streamType == StreamType.live && widget.channels != null) { + _playNext(); + return true; + } } // Cycle Aspect Ratio - secret key 'A' or digit 1 diff --git a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart index c78f8e5..ad9e6a4 100644 --- a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart @@ -102,7 +102,7 @@ class _MobileSettingsTabState extends ConsumerState { ), ), Text( - 'Version 1.3.4', + 'Version 1.3.9', style: GoogleFonts.inter( fontSize: 13, color: AppColors.textSecondary, @@ -145,18 +145,6 @@ class _MobileSettingsTabState extends ConsumerState { }, ), - _buildSettingItem( - icon: Icons.line_weight, - title: 'Désentrelacement', - subtitle: settings.deinterlace - ? 'Activé (Recommandé pour TV en direct)' - : 'Désactivé', - value: settings.deinterlace ? 'Oui' : 'Non', - onTap: () => ref - .read(mobileSettingsProvider.notifier) - .toggleDeinterlace(!settings.deinterlace), - ), - _buildSettingItem( icon: Icons.speed, title: 'Buffer (Cache)', @@ -377,8 +365,10 @@ class _MobileSettingsTabState extends ConsumerState { ), ), Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.15), borderRadius: BorderRadius.circular(8), @@ -458,16 +448,21 @@ class _MobileSettingsTabState extends ConsumerState { ); } - void _showKeyboardDialog(String label, TextEditingController controller, - ValueChanged onChanged) { + void _showKeyboardDialog( + String label, + TextEditingController controller, + ValueChanged onChanged, + ) { final tempController = TextEditingController(text: controller.text); showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppColors.surface, - title: Text('Filtres $label', - style: const TextStyle(color: AppColors.textPrimary)), + title: Text( + 'Filtres $label', + style: const TextStyle(color: AppColors.textPrimary), + ), content: TextField( controller: tempController, autofocus: true, diff --git a/pubspec.yaml b/pubspec.yaml index 74f4405..c0ed7ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: xtremflow description: High-performance IPTV Web Application with Xtream Codes API publish_to: 'none' -version: 1.0.0+1 +version: 1.3.9+4 environment: sdk: '>=3.0.0 <4.0.0'