mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-12 01:36:20 +02:00
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>
307 lines
10 KiB
Dart
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,
|
|
),
|
|
);
|
|
}
|
|
}
|