feat: enforce player strategy (vod=ultra, live=lite), clean UI and settings

This commit is contained in:
Michael committed 2025-12-16 22:37:47 +01:00
1 parent 5fedee7ec3
commit a2b8d5fb09
5 files changed
+203 -150

No files matched your search

@@ -21,10 +21,12 @@ class MobileSeriesDetailScreen extends ConsumerStatefulWidget {
});
@override
ConsumerState<MobileSeriesDetailScreen> createState() => _MobileSeriesDetailScreenState();
ConsumerState<MobileSeriesDetailScreen> createState() =>
_MobileSeriesDetailScreenState();
}
class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScreen> {
class _MobileSeriesDetailScreenState
extends ConsumerState<MobileSeriesDetailScreen> {
SeriesInfo? _seriesInfo;
bool _isLoading = true;
String? _error;
@@ -52,8 +54,10 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
_error = null;
});
final service = await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final info = await service.getSeriesInfo(widget.series.seriesId.toString());
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final info =
await service.getSeriesInfo(widget.series.seriesId.toString());
if (mounted) {
setState(() {
@@ -85,10 +89,15 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 48, color: AppColors.error),
const Icon(Icons.error_outline,
size: 48, color: AppColors.error),
const SizedBox(height: 16),
Text('Error: $_error', style: const TextStyle(color: AppColors.textSecondary)),
TextButton(onPressed: _loadSeriesInfo, child: const Text('Retry')),
Text('Error: $_error',
style:
const TextStyle(color: AppColors.textSecondary)),
TextButton(
onPressed: _loadSeriesInfo,
child: const Text('Retry')),
],
),
)
@@ -128,7 +137,8 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
CachedNetworkImage(
imageUrl: _seriesInfo!.cover!,
fit: BoxFit.cover,
errorWidget: (_, __, ___) => Container(color: Colors.grey.shade900),
errorWidget: (_, __, ___) =>
Container(color: Colors.grey.shade900),
),
// Gradient Overlay
Container(
@@ -166,17 +176,20 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
Row(
children: [
if (_seriesInfo!.rating != null) ...[
const Icon(Icons.star, size: 16, color: Colors.amber),
const Icon(Icons.star,
size: 16, color: Colors.amber),
const SizedBox(width: 4),
Text(
_formatRating(_seriesInfo!.rating!)!,
style: const TextStyle(color: AppColors.textSecondary, fontSize: 13),
style: const TextStyle(
color: AppColors.textSecondary, fontSize: 13),
),
const SizedBox(width: 16),
],
Text(
'${_seriesInfo!.episodes.keys.length} Seasons',
style: const TextStyle(color: AppColors.textSecondary, fontSize: 13),
style: const TextStyle(
color: AppColors.textSecondary, fontSize: 13),
),
],
),
@@ -195,7 +208,8 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
padding: const EdgeInsets.all(16.0),
child: Text(
_seriesInfo!.plot!,
style: const TextStyle(color: AppColors.textSecondary, height: 1.4),
style: const TextStyle(
color: AppColors.textSecondary, height: 1.4),
maxLines: 4,
overflow: TextOverflow.ellipsis,
),
@@ -215,23 +229,31 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
itemCount: _seriesInfo!.episodes.keys.length,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
final seasonNum = _seriesInfo!.episodes.keys.elementAt(index);
final seasonNum =
_seriesInfo!.episodes.keys.elementAt(index);
final isSelected = seasonNum == _selectedSeason;
return ChoiceChip(
label: Text('Season $seasonNum'),
selected: isSelected,
onSelected: (selected) {
if (selected) setState(() => _selectedSeason = seasonNum);
if (selected)
setState(() => _selectedSeason = seasonNum);
},
backgroundColor: isDark ? const Color(0xFF1C1C1E) : Colors.grey.shade300,
backgroundColor: isDark
? const Color(0xFF1C1C1E)
: Colors.grey.shade300,
selectedColor: isDark ? Colors.white : Colors.blue,
labelStyle: TextStyle(
color: isSelected
? (isDark ? Colors.black : Colors.white)
: (isDark ? Colors.grey.shade400 : Colors.grey.shade700),
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
: (isDark
? Colors.grey.shade400
: Colors.grey.shade700),
fontWeight:
isSelected ? FontWeight.w600 : FontWeight.w400,
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
showCheckmark: false,
side: BorderSide.none,
);
@@ -274,38 +296,24 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final textColor = isDark ? Colors.white : Colors.black;
final secondaryTextColor = isDark ? Colors.grey.shade400 : Colors.grey.shade600;
final surfaceColor = isDark ? const Color(0xFF1C1C1E) : Colors.grey.shade200;
final secondaryTextColor =
isDark ? Colors.grey.shade400 : Colors.grey.shade600;
final surfaceColor =
isDark ? const Color(0xFF1C1C1E) : Colors.grey.shade200;
final primaryColor = isDark ? Colors.white : Colors.blue;
return InkWell(
onTap: () {
// Watch progress is tracked in player at 80% completion
final engine = ref.read(mobileSettingsProvider).playerEngine;
if (engine == 'lite') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LitePlayerScreen(
streamId: episode.streamId,
title: "${widget.series.name} S${episode.seasonNum}E${episode.episodeNum}",
playlist: widget.playlist,
streamType: StreamType.series,
containerExtension: episode.containerExtension ?? 'mp4',
seriesId: widget.series.seriesId,
season: episode.seasonNum,
episodeNum: episode.episodeNum,
),
),
);
} else {
// Enforce Native Player for Series (VOD)
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NativePlayerScreen(
streamId: episode.streamId,
title: "${widget.series.name} S${episode.seasonNum}E${episode.episodeNum}",
title:
"${widget.series.name} S${episode.seasonNum}E${episode.episodeNum}",
playlist: widget.playlist,
streamType: StreamType.series,
containerExtension: episode.containerExtension ?? 'mp4',
@@ -315,12 +323,13 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
),
),
);
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: isDark ? Colors.white10 : Colors.black12)),
border: Border(
bottom:
BorderSide(color: isDark ? Colors.white10 : Colors.black12)),
),
child: Row(
children: [
@@ -331,11 +340,15 @@ class _MobileSeriesDetailScreenState extends ConsumerState<MobileSeriesDetailScr
decoration: BoxDecoration(
color: isWatched ? primaryColor : surfaceColor,
shape: BoxShape.circle,
border: isWatched ? null : Border.all(color: primaryColor, width: 2),
border: isWatched
? null
: Border.all(color: primaryColor, width: 2),
),
child: Icon(
isWatched ? Icons.check : Icons.play_arrow,
color: isWatched ? (isDark ? Colors.black : Colors.white) : primaryColor,
color: isWatched
? (isDark ? Colors.black : Colors.white)
: primaryColor,
size: 20,
),
),
@@ -968,20 +968,6 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// FORCE AUDIO BUTTON (First item)
TVFocusable(
focusNode: _audioFocusNode,
onPressed: _cycleAudioTrack,
onFocus: _resetControlsTimer,
borderRadius: BorderRadius.circular(50),
child: IconButton(
icon: const Icon(Icons.audiotrack,
color: Colors.greenAccent, size: 32),
onPressed: _cycleAudioTrack,
),
),
const SizedBox(width: 16),
// Prev Channel / Replay 10s
if (widget.streamType == StreamType.live &&
widget.channels != null &&
@@ -23,14 +23,15 @@ class MobileLiveTVTab extends ConsumerStatefulWidget {
ConsumerState<MobileLiveTVTab> createState() => _MobileLiveTVTabState();
}
class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with AutomaticKeepAliveClientMixin {
class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
with AutomaticKeepAliveClientMixin {
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
String _searchQuery = '';
bool _showFavoritesOnly = false;
bool _isSearchEditing = false;
bool _justReturnedFromPlayer = false; // Flag to prevent PopScope interception after player
bool _justReturnedFromPlayer =
false; // Flag to prevent PopScope interception after player
@override
void initState() {
@@ -55,7 +56,8 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
@override
Widget build(BuildContext context) {
super.build(context);
final channelsAsync = ref.watch(mobileLiveChannelsProvider(widget.playlist));
final channelsAsync =
ref.watch(mobileLiveChannelsProvider(widget.playlist));
final favorites = ref.watch(mobileFavoritesProvider);
final settings = ref.watch(mobileSettingsProvider);
final uiState = ref.watch(mobileLiveTvUiStateProvider);
@@ -67,7 +69,9 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
backgroundColor: Colors.transparent,
body: PopScope(
// Only allow full pop if we're at category view with no filters AND not just returned from player
canPop: uiState.isCategoryView && _searchQuery.isEmpty && !_showFavoritesOnly,
canPop: uiState.isCategoryView &&
_searchQuery.isEmpty &&
!_showFavoritesOnly,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
@@ -104,7 +108,9 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
// Prepare categories
var categories = groupedChannels.keys.toList();
if (settings.liveTvKeywords.isNotEmpty) {
categories = categories.where((cat) => settings.matchesLiveTvFilter(cat)).toList();
categories = categories
.where((cat) => settings.matchesLiveTvFilter(cat))
.toList();
}
categories.sort();
@@ -112,19 +118,24 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
List<Channel> displayedChannels = [];
// Determine mode based on search/favorites/selection
bool showGrid = uiState.isCategoryView && _searchQuery.isEmpty && !_showFavoritesOnly;
bool showGrid = uiState.isCategoryView &&
_searchQuery.isEmpty &&
!_showFavoritesOnly;
if (!showGrid) {
if (_searchQuery.isNotEmpty) {
displayedChannels = groupedChannels.values.expand((l) => l)
displayedChannels = groupedChannels.values
.expand((l) => l)
.where((c) => c.name.toLowerCase().contains(_searchQuery))
.toList();
} else if (_showFavoritesOnly) {
displayedChannels = groupedChannels.values.expand((l) => l)
displayedChannels = groupedChannels.values
.expand((l) => l)
.where((c) => favorites.contains(c.streamId))
.toList();
} else if (uiState.selectedCategory != null) {
displayedChannels = groupedChannels[uiState.selectedCategory] ?? [];
displayedChannels =
groupedChannels[uiState.selectedCategory] ?? [];
}
}
@@ -134,7 +145,8 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
children: [
// Header (Search + Title/Back)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Column(
children: [
// Search Bar - Wrapped in TVFocusable for remote access
@@ -143,7 +155,8 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
setState(() => _isSearchEditing = true);
WidgetsBinding.instance.addPostFrameCallback((_) {
_searchFocusNode.requestFocus();
SystemChannels.textInput.invokeMethod('TextInput.show');
SystemChannels.textInput
.invokeMethod('TextInput.show');
});
},
borderRadius: BorderRadius.circular(12),
@@ -152,12 +165,16 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
border: _isSearchEditing ? Border.all(color: AppColors.primary) : null,
border: _isSearchEditing
? Border.all(color: AppColors.primary)
: null,
),
padding: const EdgeInsets.symmetric(horizontal: 12),
padding:
const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
const Icon(Icons.search, color: AppColors.textSecondary),
const Icon(Icons.search,
color: AppColors.textSecondary),
const SizedBox(width: 8),
Expanded(
child: ExcludeFocus(
@@ -166,20 +183,23 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
controller: _searchController,
focusNode: _searchFocusNode,
readOnly: !_isSearchEditing,
style: const TextStyle(color: AppColors.textPrimary),
style: const TextStyle(
color: AppColors.textPrimary),
decoration: const InputDecoration(
hintText: 'Rechercher une chaîne...',
border: InputBorder.none,
isDense: true,
),
onSubmitted: (_) => setState(() => _isSearchEditing = false),
onSubmitted: (_) => setState(
() => _isSearchEditing = false),
),
),
),
if (_searchQuery.isNotEmpty)
GestureDetector(
onTap: () => _searchController.clear(),
child: const Icon(Icons.close, color: AppColors.textSecondary),
child: const Icon(Icons.close,
color: AppColors.textSecondary),
),
],
),
@@ -191,16 +211,21 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
// Title / Navigation Row
Row(
children: [
if (!showGrid || _showFavoritesOnly || _searchQuery.isNotEmpty) ...[
if (!showGrid ||
_showFavoritesOnly ||
_searchQuery.isNotEmpty) ...[
IconButton(
icon: const Icon(Icons.arrow_back, color: AppColors.textPrimary),
icon: const Icon(Icons.arrow_back,
color: AppColors.textPrimary),
onPressed: () {
if (_searchQuery.isNotEmpty) {
_searchController.clear();
} else if (_showFavoritesOnly) {
setState(() => _showFavoritesOnly = false);
setState(
() => _showFavoritesOnly = false);
} else {
uiNotifier.state = uiState.copyWith(isCategoryView: true);
uiNotifier.state = uiState.copyWith(
isCategoryView: true);
}
},
padding: EdgeInsets.zero,
@@ -210,10 +235,14 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
],
Expanded(
child: Text(
_searchQuery.isNotEmpty ? 'Résultats de recherche' :
_showFavoritesOnly ? 'Favoris' :
showGrid ? 'Catégories' :
uiState.selectedCategory ?? 'Chaînes',
_searchQuery.isNotEmpty
? 'Résultats de recherche'
: _showFavoritesOnly
? 'Favoris'
: showGrid
? 'Catégories'
: uiState.selectedCategory ??
'Chaînes',
style: const TextStyle(
color: AppColors.textPrimary,
fontWeight: FontWeight.bold,
@@ -224,15 +253,23 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
),
IconButton(
icon: Icon(
_showFavoritesOnly ? Icons.favorite : Icons.favorite_border,
color: _showFavoritesOnly ? AppColors.error : AppColors.textSecondary,
_showFavoritesOnly
? Icons.favorite
: Icons.favorite_border,
color: _showFavoritesOnly
? AppColors.error
: AppColors.textSecondary,
),
onPressed: () => setState(() {
_showFavoritesOnly = !_showFavoritesOnly;
// Reset category view if entering favorites
if (_showFavoritesOnly) uiNotifier.state = uiState.copyWith(isCategoryView: false);
if (_showFavoritesOnly)
uiNotifier.state =
uiState.copyWith(isCategoryView: false);
// If exiting favorites, default depends on logic (here back to category grid if was previously)
if (!_showFavoritesOnly) uiNotifier.state = uiState.copyWith(isCategoryView: true);
if (!_showFavoritesOnly)
uiNotifier.state =
uiState.copyWith(isCategoryView: true);
}),
),
],
@@ -280,7 +317,8 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
final category = categories[index];
return TVFocusable(
onPressed: () {
ref.read(mobileLiveTvUiStateProvider.notifier).state = LiveTvUiState(
ref.read(mobileLiveTvUiStateProvider.notifier).state =
LiveTvUiState(
selectedCategory: category,
isCategoryView: false,
);
@@ -359,16 +397,16 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
return _MobileChannelCard(
channel: channel,
playlist: widget.playlist,
onTap: () => _playChannel(context, channel, channels, widget.playlist, index),
onTap: () =>
_playChannel(context, channel, channels, widget.playlist, index),
);
},
);
}
void _playChannel(BuildContext context, Channel channel, List<Channel> channels, PlaylistConfig playlist, int index) async {
final engine = ref.read(mobileSettingsProvider).playerEngine;
if (engine == 'lite') {
void _playChannel(BuildContext context, Channel channel,
List<Channel> channels, PlaylistConfig playlist, int index) async {
// ENFORCE LITE PLAYER FOR LIVE TV
await Navigator.push(
context,
MaterialPageRoute(
@@ -382,21 +420,6 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
),
),
);
} else {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NativePlayerScreen(
streamId: channel.streamId,
title: channel.name,
playlist: playlist,
streamType: StreamType.live,
channels: channels, // Pass full channel list for zapping
initialIndex: index,
),
),
);
}
// When returning from player, set flag to prevent PopScope from going back to categories
if (mounted) {
@@ -431,7 +454,8 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
Future<void> _loadEpg() async {
try {
final service = await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final epg = await service.getShortEPG(widget.channel.streamId);
if (mounted && epg.nowPlaying != null && epg.nowPlaying!.isNotEmpty) {
setState(() {
@@ -445,7 +469,8 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
@override
Widget build(BuildContext context) {
final iconUrl = widget.channel.streamIcon.isNotEmpty && widget.channel.streamIcon.startsWith('http')
final iconUrl = widget.channel.streamIcon.isNotEmpty &&
widget.channel.streamIcon.startsWith('http')
? widget.channel.streamIcon
: null;
@@ -455,7 +480,9 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
return TVFocusable(
onPressed: widget.onTap,
onLongPress: () {
ref.read(mobileFavoritesProvider.notifier).toggle(widget.channel.streamId);
ref
.read(mobileFavoritesProvider.notifier)
.toggle(widget.channel.streamId);
},
child: Container(
decoration: BoxDecoration(
@@ -489,10 +516,13 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
? CachedNetworkImage(
imageUrl: iconUrl,
fit: BoxFit.contain,
errorWidget: (_, __, ___) => const Icon(Icons.tv, color: Colors.white38, size: 40),
placeholder: (_, __) => const Icon(Icons.tv, color: Colors.white24, size: 40),
errorWidget: (_, __, ___) => const Icon(Icons.tv,
color: Colors.white38, size: 40),
placeholder: (_, __) => const Icon(Icons.tv,
color: Colors.white24, size: 40),
)
: const Icon(Icons.tv, color: Colors.white38, size: 40),
: const Icon(Icons.tv,
color: Colors.white38, size: 40),
),
// Favorite badge
if (isFavorite)
@@ -505,7 +535,8 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.favorite, color: Colors.white, size: 12),
child: const Icon(Icons.favorite,
color: Colors.white, size: 12),
),
),
],
@@ -181,7 +181,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
void _playMovie(BuildContext context, Movie movie) {
if (widget.playlist == null) return;
// DEBUG: FORCE NATIVE PLAYER (Ignoring settings)
// Enforce Native Player for Movies (VOD)
Navigator.push(
context,
MaterialPageRoute(
@@ -311,7 +311,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
style: const TextStyle(
fontSize: 14, color: AppColors.textPrimary),
decoration: const InputDecoration(
hintText: 'Rechercher (DEBUG V3)...',
hintText: 'Rechercher un film...',
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.only(bottom: 11),
@@ -51,7 +51,11 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
}
List<String> _parseKeywords(String text) {
return text.split(',').map((s) => s.trim()).where((s) => s.isNotEmpty).toList();
return text
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
}
@override
@@ -124,7 +128,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
icon: Icons.access_time,
title: 'Afficher l\'heure',
value: settings.showClock ? 'Activé' : 'Désactivé',
onTap: () => ref.read(mobileSettingsProvider.notifier).toggleShowClock(!settings.showClock),
onTap: () => ref
.read(mobileSettingsProvider.notifier)
.toggleShowClock(!settings.showClock),
),
const SizedBox(height: 24),
@@ -132,23 +138,14 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
// === LECTURE VIDEO ===
_buildSectionHeader('Lecture Vidéo'),
_buildSettingItem(
icon: Icons.rocket_launch,
title: 'Moteur Vidéo',
subtitle: settings.playerEngine == 'ultra' ? 'Ultra (MPV) - Puissant' : 'Lite (ExoPlayer) - Léger',
value: settings.playerEngine == 'ultra' ? 'Ultra' : 'Lite',
onTap: () {
final newValue = settings.playerEngine == 'ultra' ? 'lite' : 'ultra';
ref.read(mobileSettingsProvider.notifier).setPlayerEngine(newValue);
},
),
_buildSettingItem(
icon: Icons.bug_report,
title: 'Stats pour Nerds',
subtitle: 'Affiche FPS, Buffer, Bitrate',
value: settings.showDebugStats ? 'Activé' : 'Désactivé',
onTap: () => ref.read(mobileSettingsProvider.notifier).toggleShowDebugStats(!settings.showDebugStats),
onTap: () => ref
.read(mobileSettingsProvider.notifier)
.toggleShowDebugStats(!settings.showDebugStats),
),
_buildSettingItem(
@@ -160,20 +157,28 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
final modes = ['auto', 'mediacodec', 'no'];
final currentIndex = modes.indexOf(settings.decoderMode);
final nextIndex = (currentIndex + 1) % modes.length;
ref.read(mobileSettingsProvider.notifier).setDecoderMode(modes[nextIndex]);
ref
.read(mobileSettingsProvider.notifier)
.setDecoderMode(modes[nextIndex]);
},
),
_buildSettingItem(
icon: Icons.speed,
title: 'Buffer (Cache)',
subtitle: settings.bufferDuration == 0 ? 'Automatique' : '${settings.bufferDuration} secondes',
value: settings.bufferDuration == 0 ? 'Auto' : '${settings.bufferDuration}s',
subtitle: settings.bufferDuration == 0
? 'Automatique'
: '${settings.bufferDuration} secondes',
value: settings.bufferDuration == 0
? 'Auto'
: '${settings.bufferDuration}s',
onTap: () {
final buffers = [0, 15, 30, 60];
final currentIndex = buffers.indexOf(settings.bufferDuration);
final nextIndex = (currentIndex + 1) % buffers.length;
ref.read(mobileSettingsProvider.notifier).setBufferDuration(buffers[nextIndex]);
ref
.read(mobileSettingsProvider.notifier)
.setBufferDuration(buffers[nextIndex]);
},
),
@@ -188,7 +193,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
icon: Icons.live_tv,
controller: _liveTvController,
onChanged: (val) {
ref.read(mobileSettingsProvider.notifier).setLiveTvKeywords(_parseKeywords(val));
ref
.read(mobileSettingsProvider.notifier)
.setLiveTvKeywords(_parseKeywords(val));
},
),
@@ -198,7 +205,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
icon: Icons.movie,
controller: _moviesController,
onChanged: (val) {
ref.read(mobileSettingsProvider.notifier).setMoviesKeywords(_parseKeywords(val));
ref
.read(mobileSettingsProvider.notifier)
.setMoviesKeywords(_parseKeywords(val));
},
),
@@ -208,7 +217,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
icon: Icons.tv,
controller: _seriesController,
onChanged: (val) {
ref.read(mobileSettingsProvider.notifier).setSeriesKeywords(_parseKeywords(val));
ref
.read(mobileSettingsProvider.notifier)
.setSeriesKeywords(_parseKeywords(val));
},
),
@@ -239,7 +250,8 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const MobilePlaylistSelectionScreen(manageMode: true),
builder: (_) =>
const MobilePlaylistSelectionScreen(manageMode: true),
),
);
},
@@ -255,19 +267,27 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
String _getDecoderSubtitle(String mode) {
switch (mode) {
case 'auto': return 'Automatique (Recommandé)';
case 'mediacodec': return 'Matériel (Hardware)';
case 'no': return 'Logiciel (Software)';
default: return 'Automatique';
case 'auto':
return 'Automatique (Recommandé)';
case 'mediacodec':
return 'Matériel (Hardware)';
case 'no':
return 'Logiciel (Software)';
default:
return 'Automatique';
}
}
String _getDecoderValue(String mode) {
switch (mode) {
case 'auto': return 'Auto';
case 'mediacodec': return 'HW';
case 'no': return 'SW';
default: return 'Auto';
case 'auto':
return 'Auto';
case 'mediacodec':
return 'HW';
case 'no':
return 'SW';
default:
return 'Auto';
}
}
@@ -362,7 +382,8 @@ 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),
@@ -442,14 +463,16 @@ 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,