mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-11 17:29:26 +02:00
feat: Implement a mobile-optimized IPTV settings tab including video playback, content filters, and cache management.
This commit is contained in:
1 parent
af95742321
commit
3d88463c65
4 files changed
+84
-184
No files matched your search
@@ -77,8 +77,6 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
final FocusNode _deinterlaceFocusNode = FocusNode();
|
||||
|
||||
Timer? _controlsTimer;
|
||||
Timer? _clockTimer;
|
||||
String _currentTime = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -90,7 +88,6 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
WakelockPlus.enable();
|
||||
|
||||
_currentIndex = widget.initialIndex;
|
||||
_startClock();
|
||||
|
||||
// Lock orientation
|
||||
SystemChrome.setPreferredOrientations([
|
||||
@@ -305,30 +302,34 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
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<LitePlayerScreen>
|
||||
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<LitePlayerScreen>
|
||||
WakelockPlus.disable();
|
||||
|
||||
_controlsTimer?.cancel();
|
||||
_clockTimer?.cancel();
|
||||
_epgTimer?.cancel();
|
||||
_controller?.dispose();
|
||||
_xtreamService?.dispose();
|
||||
@@ -564,13 +548,6 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
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<LitePlayerScreen>
|
||||
),
|
||||
),
|
||||
|
||||
// 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(
|
||||
|
||||
@@ -810,58 +810,58 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
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
|
||||
|
||||
@@ -102,7 +102,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
||||
),
|
||||
),
|
||||
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<MobileSettingsTab> {
|
||||
},
|
||||
),
|
||||
|
||||
_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<MobileSettingsTab> {
|
||||
),
|
||||
),
|
||||
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<MobileSettingsTab> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showKeyboardDialog(String label, TextEditingController controller,
|
||||
ValueChanged<String> onChanged) {
|
||||
void _showKeyboardDialog(
|
||||
String label,
|
||||
TextEditingController controller,
|
||||
ValueChanged<String> 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,
|
||||
|
||||
+1
-1
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user