Files
xtremflow/lib/features/iptv/widgets/streaming_settings_tab.dart
MichaelandClaude Opus 5 8b82b101aa fix(ui): remplacer les couleurs Material codées en dur par les jetons Warm Cinema
41 couleurs Material brutes traînaient encore dans l'interface, hors palette :
rouge, vert, orange et surtout un violet omniprésent sur les Season Pass, qui
n'appartient à aucune famille du thème.

Correspondances retenues :
- Colors.red / redAccent -> AppColors.live (#E5484D) pour les badges LIVE et
  les indicateurs d'enregistrement, AppColors.error pour les messages d'erreur
- Colors.green / greenAccent -> AppColors.success
- Colors.orangeAccent -> AppColors.warning
- Colors.amber -> AppColors.ratingGold, pour aligner les étoiles de notation
  du mobile sur celles du bureau
- Colors.purpleAccent -> famille secondary (terre cuite)
- fonds de boutons passés sur les variantes *Container avec leur couleur de
  premier plan, le remplissage clair ne portant pas de texte lisible

Le titre du programme EPG dans la grille Live TV utilisait ratingGold, un jeton
réservé aux notes : il passe sur primary, l'accent ember du thème.

Les fonds de SnackBar vert/rouge deviennent surfaceContainerHigh, le succès et
l'échec restant portés par le pictogramme du message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:16:38 +02:00

307 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../core/theme/app_colors.dart';
import '../providers/settings_provider.dart';
/// Streaming settings tab for Live TV configuration
class StreamingSettingsTab extends ConsumerWidget {
const StreamingSettingsTab({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(iptvSettingsProvider);
final notifier = ref.read(iptvSettingsProvider.notifier);
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Header
Text(
'Paramètres Streaming Live TV',
style: GoogleFonts.karla(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.onSurface,
),
),
const SizedBox(height: 8),
Text(
'Ces paramètres s\'appliquent uniquement aux flux TV en direct.',
style: GoogleFonts.karla(
fontSize: 12,
color: AppColors.onSurfaceVariant,
),
),
const SizedBox(height: 24),
// Note: Player type is now automatically determined:
// - Live TV uses Player Lite (MPEG-TS)
// - Movies/Series use Player Standard
// Quality Section
_buildSectionCard(
context: context,
title: 'Qualité Vidéo',
icon: Icons.high_quality,
children: [
_buildDropdownTile<StreamQuality>(
title: 'Qualité du flux',
subtitle: 'Bitrate: ${settings.bitrateKbps} kbps',
value: settings.streamQuality,
items: StreamQuality.values,
onChanged: (v) => notifier.setStreamQuality(v!),
labelBuilder: (v) => switch (v) {
StreamQuality.low => 'Faible (1.5 Mbps)',
StreamQuality.medium => 'Moyen (3 Mbps)',
StreamQuality.high => 'Élevé (5 Mbps)',
},
),
],
),
const SizedBox(height: 16),
// Buffer Section
_buildSectionCard(
context: context,
title: 'Buffer & Latence',
icon: Icons.speed,
children: [
_buildDropdownTile<BufferSize>(
title: 'Taille du buffer',
subtitle: 'Segments HLS: ${settings.hlsSegmentDuration}s',
value: settings.bufferSize,
items: BufferSize.values,
onChanged: (v) => notifier.setBufferSize(v!),
labelBuilder: (v) => switch (v) {
BufferSize.low => 'Faible (latence min)',
BufferSize.medium => 'Moyen (équilibré)',
BufferSize.high => 'Élevé (stabilité max)',
},
),
],
),
const SizedBox(height: 16),
// Connection Section
_buildSectionCard(
context: context,
title: 'Connexion',
icon: Icons.wifi,
children: [
_buildDropdownTile<ConnectionTimeout>(
title: 'Timeout de connexion',
subtitle: '${settings.timeoutSeconds} secondes',
value: settings.connectionTimeout,
items: ConnectionTimeout.values,
onChanged: (v) => notifier.setConnectionTimeout(v!),
labelBuilder: (v) => switch (v) {
ConnectionTimeout.short => 'Court (15s)',
ConnectionTimeout.medium => 'Moyen (30s)',
ConnectionTimeout.long => 'Long (60s)',
},
),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
SwitchListTile(
title: Text(
'Reconnexion automatique',
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Se reconnecter en cas de coupure',
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.autoReconnect,
activeThumbColor: AppColors.primaryContainer,
onChanged: (v) => notifier.setAutoReconnect(v),
),
],
),
const SizedBox(height: 16),
// Transcoding Section
_buildSectionCard(
context: context,
title: 'Transcodage',
icon: Icons.transform,
children: [
_buildDropdownTile<TranscodingMode>(
title: 'Mode de transcodage',
subtitle: 'Comment le flux est traité',
value: settings.transcodingMode,
items: TranscodingMode.values,
onChanged: (v) => notifier.setTranscodingMode(v!),
labelBuilder: (v) => switch (v) {
TranscodingMode.auto => 'Auto (recommandé)',
TranscodingMode.forced => 'Forcé (toujours transcoder)',
TranscodingMode.disabled => 'Désactivé (direct)',
},
),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
SwitchListTile(
title: Text(
'Préférer lecture directe',
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Utiliser le flux original si compatible',
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.preferDirectPlay,
activeThumbColor: AppColors.primaryContainer,
onChanged: (v) => notifier.setPreferDirectPlay(v),
),
],
),
const SizedBox(height: 16),
// Cache Section
_buildSectionCard(
context: context,
title: 'Cache EPG',
icon: Icons.schedule,
children: [
_buildDropdownTile<EpgCacheDuration>(
title: 'Durée du cache EPG',
subtitle: '${settings.epgCacheMinutes} minutes',
value: settings.epgCacheDuration,
items: EpgCacheDuration.values,
onChanged: (v) => notifier.setEpgCacheDuration(v!),
labelBuilder: (v) => switch (v) {
EpgCacheDuration.short => 'Court (5 min)',
EpgCacheDuration.medium => 'Moyen (15 min)',
EpgCacheDuration.long => 'Long (1 heure)',
},
),
],
),
const SizedBox(height: 16),
// Server/GPU Section
_buildSectionCard(
context: context,
title: 'Serveur / GPU',
icon: Icons.memory,
children: [
SwitchListTile(
title: Text(
'Accélération NVIDIA GPU',
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
'Utiliser NVENC/NVDEC pour le transcodage (requiert GPU NVIDIA)',
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
value: settings.enableNvidiaGpu,
activeThumbColor: AppColors.success,
onChanged: (v) => notifier.setEnableNvidiaGpu(v),
),
],
),
const SizedBox(height: 32),
// Info card
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.primary.withOpacity(0.3)),
),
child: Row(
children: [
const Icon(Icons.info_outline, color: AppColors.primary),
const SizedBox(width: 12),
Expanded(
child: Text(
'Les changements prennent effet immédiatement pour les nouveaux flux.',
style: GoogleFonts.karla(
fontSize: 12,
color: AppColors.primary,
),
),
),
],
),
),
],
);
}
Widget _buildSectionCard({
required BuildContext context,
required String title,
required IconData icon,
required List<Widget> children,
}) {
return Container(
decoration: BoxDecoration(
color: AppColors.onSurface.withOpacity(0.05),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.onSurface.withOpacity(0.1)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(icon, size: 20, color: AppColors.primaryContainer),
const SizedBox(width: 8),
Text(
title,
style: GoogleFonts.karla(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.onSurface,
),
),
],
),
),
Divider(height: 1, color: AppColors.onSurface.withOpacity(0.1)),
...children,
],
),
);
}
Widget _buildDropdownTile<T>({
required String title,
required String subtitle,
required T value,
required List<T> items,
required ValueChanged<T?> onChanged,
required String Function(T) labelBuilder,
}) {
return ListTile(
title: Text(
title,
style: GoogleFonts.karla(fontSize: 14, color: AppColors.onSurface),
),
subtitle: Text(
subtitle,
style: GoogleFonts.karla(fontSize: 12, color: AppColors.onSurfaceVariant),
),
trailing: DropdownButton<T>(
value: value,
underline: const SizedBox(),
dropdownColor: AppColors.surfaceContainer, // Dark background for dropdown
items: items.map((item) {
return DropdownMenuItem<T>(
value: item,
child: Text(
labelBuilder(item),
style: GoogleFonts.karla(fontSize: 13, color: AppColors.onSurface),
),
);
}).toList(),
onChanged: onChanged,
style: GoogleFonts.karla(color: AppColors.onSurface),
iconEnabledColor: AppColors.onSurfaceVariant,
),
);
}
}