feat(theme): complete redesign to Cyber-Cinematic Glass (Stitch)

Replace the legacy Apple TV-style purple/cyan theme with the full
Google Stitch Cyber-Cinematic Glass design system across the
entire app (desktop + mobile).

- New Material 3 dark ColorScheme: background #121317, primary
  #adc6ff, primaryContainer #4b8eff, surface containers, etc.
- Typography: Space Grotesk (headlines) + Inter (body/labels)
- 3-level glassmorphism via GlassContainer: base, glass, floating
- Primary gradient: #007AFF → #00C6FF with blue glow effects
- Removed old compatibility aliases (focusColor, border, textPrimary,
  textSecondary); all code now uses semantic Stitch tokens
- Systematically replaced all Colors.white/black/red/grey and
  GoogleFonts.roboto/outfit with Stitch equivalents
- All 36 lib/ files updated, zero compilation errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.7 committed 2026-04-27 22:06:29 +02:00
1 parent a9a1127d6b
commit f7eaa724e2
36 files changed
+1651 -986

No files matched your search

+118 -117
View File
@@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import '../../../core/models/iptv_models.dart';
import '../../../core/models/playlist_config.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/glass_container.dart';
import '../providers/xtream_provider.dart';
import '../providers/settings_provider.dart';
@@ -49,24 +50,24 @@ class _RecordingsTabState extends State<RecordingsTab>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
const Icon(Icons.tv, color: Colors.white, size: 28),
const Icon(Icons.tv, color: AppColors.onSurface, size: 28),
const SizedBox(width: 12),
Text('TV & Enregistrements',
style: GoogleFonts.outfit(
style: GoogleFonts.inter(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.white)),
color: AppColors.onSurface)),
]),
const SizedBox(height: 16),
TabBar(
controller: _tabController,
labelColor: Colors.white,
unselectedLabelColor: Colors.white54,
indicatorColor: Colors.redAccent,
labelColor: AppColors.onSurface,
unselectedLabelColor: AppColors.onSurfaceVariant,
indicatorColor: AppColors.live,
indicatorWeight: 3,
labelStyle: GoogleFonts.outfit(
labelStyle: GoogleFonts.inter(
fontWeight: FontWeight.w600, fontSize: 14),
unselectedLabelStyle: GoogleFonts.outfit(fontSize: 14),
unselectedLabelStyle: GoogleFonts.inter(fontSize: 14),
tabs: const [
Tab(icon: Icon(Icons.grid_view, size: 18), text: 'Guide TV'),
Tab(
@@ -218,8 +219,8 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
child: Column(
children: [
Text('GROUPES',
style: GoogleFonts.outfit(
color: Colors.white38,
style: GoogleFonts.inter(
color: AppColors.outline,
fontSize: 10,
fontWeight: FontWeight.bold,
letterSpacing: 1.2)),
@@ -242,16 +243,16 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
margin: const EdgeInsets.only(bottom: 4),
decoration: BoxDecoration(
color: isSelected
? Colors.white.withOpacity(0.1)
? AppColors.outlineVariant
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
cat,
style: GoogleFonts.outfit(
style: GoogleFonts.inter(
color: isSelected
? Colors.white
: Colors.white60,
? AppColors.onSurface
: AppColors.onSurfaceVariant,
fontSize: 12,
fontWeight: isSelected
? FontWeight.bold
@@ -268,7 +269,7 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
),
),
const VerticalDivider(width: 32, color: Colors.white10),
const VerticalDivider(width: 32, color: AppColors.outlineVariant),
// ── Colonne 2 : CHAÎNES ──
SizedBox(
@@ -277,15 +278,15 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
// Barre de recherche
TextField(
controller: _searchCtrl,
style: const TextStyle(color: Colors.white, fontSize: 13),
style: const TextStyle(color: AppColors.onSurface, fontSize: 13),
decoration: InputDecoration(
hintText: 'Rechercher...',
hintStyle:
const TextStyle(color: Colors.white38, fontSize: 13),
const TextStyle(color: AppColors.outline, fontSize: 13),
prefixIcon: const Icon(Icons.search,
color: Colors.white38, size: 18),
color: AppColors.outline, size: 18),
filled: true,
fillColor: Colors.white.withOpacity(0.07),
fillColor: AppColors.surfaceContainer,
contentPadding: const EdgeInsets.symmetric(vertical: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
@@ -299,7 +300,7 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
? Center(
child: Text('Aucune chaîne',
style: TextStyle(
color: Colors.white24, fontSize: 12)))
color: AppColors.outline, fontSize: 12)))
: ListView.builder(
itemCount: visibleChannels.length,
itemBuilder: (ctx, i) {
@@ -315,12 +316,12 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: isSelected
? Colors.redAccent.withOpacity(0.2)
: Colors.white.withOpacity(0.05),
? AppColors.live.withOpacity(0.2)
: AppColors.surfaceContainerLow,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? Colors.redAccent.withOpacity(0.5)
? AppColors.live.withOpacity(0.5)
: Colors.transparent,
),
),
@@ -332,18 +333,18 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
fit: BoxFit.contain,
errorBuilder: (_, __, ___) =>
const Icon(Icons.tv,
color: Colors.white24,
color: AppColors.outline,
size: 16))
: const Icon(Icons.tv,
color: Colors.white24, size: 16),
color: AppColors.outline, size: 16),
const SizedBox(width: 10),
Expanded(
child: Text(
ch.name,
style: GoogleFonts.outfit(
style: GoogleFonts.inter(
color: isSelected
? Colors.white
: Colors.white70,
? AppColors.onSurface
: AppColors.onSurfaceVariant,
fontSize: 12,
),
maxLines: 1,
@@ -359,7 +360,7 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
]),
),
const VerticalDivider(width: 32, color: Colors.white10),
const VerticalDivider(width: 32, color: AppColors.outlineVariant),
// ── Colonne droite : programmes EPG ──
Expanded(
@@ -368,14 +369,14 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.tv_off,
size: 64, color: Color(0x1FFFFFFF)),
Icon(Icons.tv_off,
size: 64, color: AppColors.outline.withOpacity(0.1)),
const SizedBox(height: 16),
Text(
'Sélectionnez une chaîne\npour voir son guide des programmes',
textAlign: TextAlign.center,
style: GoogleFonts.outfit(
color: Colors.white38, fontSize: 15),
style: GoogleFonts.inter(
color: AppColors.outline, fontSize: 15),
),
],
),
@@ -386,8 +387,8 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
Row(children: [
Text(
_selectedChannel!.name,
style: GoogleFonts.outfit(
color: Colors.white,
style: GoogleFonts.inter(
color: AppColors.onSurface,
fontWeight: FontWeight.bold,
fontSize: 16,
),
@@ -395,12 +396,12 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
const Spacer(),
IconButton(
icon: const Icon(Icons.refresh,
color: Colors.white54, size: 18),
color: AppColors.onSurfaceVariant, size: 18),
tooltip: 'Recharger l\'EPG',
onPressed: () => _loadEpg(_selectedChannel!),
),
]),
const Divider(color: Colors.white12),
const Divider(color: AppColors.outlineVariant),
Expanded(
child: _loadingEpg
? const Center(
@@ -410,7 +411,7 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
child: Text(
'Erreur EPG: $_epgError',
style: const TextStyle(
color: Colors.redAccent,
color: AppColors.live,
fontSize: 12),
textAlign: TextAlign.center,
),
@@ -420,8 +421,8 @@ class _EpgGuideViewState extends ConsumerState<_EpgGuideView>
child: Text(
'Aucun programme EPG disponible\npour cette chaîne',
textAlign: TextAlign.center,
style: GoogleFonts.outfit(
color: Colors.white38),
style: GoogleFonts.inter(
color: AppColors.outline),
),
)
: ListView.builder(
@@ -503,28 +504,28 @@ class _ProgrammeCard extends StatelessWidget {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1E1E2E),
backgroundColor: AppColors.surfaceContainerHigh,
title: Row(children: [
const Icon(Icons.fiber_manual_record,
color: Colors.redAccent, size: 20),
color: AppColors.live, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text('Enregistrer',
style: GoogleFonts.outfit(color: Colors.white))),
style: GoogleFonts.inter(color: AppColors.onSurface))),
]),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title.isEmpty ? channel.name : title,
style: GoogleFonts.outfit(
color: Colors.white,
style: GoogleFonts.inter(
color: AppColors.onSurface,
fontWeight: FontWeight.bold,
fontSize: 16)),
const SizedBox(height: 8),
Text(
'${_fmt(_start)} → ${_fmt(_end)}',
style: const TextStyle(color: Colors.white70),
style: const TextStyle(color: AppColors.onSurfaceVariant),
),
if (_description.isNotEmpty) ...[
const SizedBox(height: 8),
@@ -532,7 +533,7 @@ class _ProgrammeCard extends StatelessWidget {
_description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white38, fontSize: 12),
style: const TextStyle(color: AppColors.outline, fontSize: 12),
),
],
],
@@ -542,7 +543,7 @@ class _ProgrammeCard extends StatelessWidget {
onPressed: () => Navigator.pop(ctx),
child: const Text('Annuler')),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.live),
onPressed: () async {
Navigator.pop(ctx);
await _saveRecording(
@@ -575,8 +576,8 @@ class _ProgrammeCard extends StatelessWidget {
? '✅ "$title" planifié !'
: '❌ Erreur: ${response.body}'),
backgroundColor: response.statusCode == 200
? Colors.green.shade800
: Colors.red.shade800,
? AppColors.success
: AppColors.error,
));
}
} catch (e) {
@@ -596,15 +597,15 @@ class _ProgrammeCard extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: isNow
? Colors.red.withOpacity(0.15)
? AppColors.live.withOpacity(0.15)
: isPast
? Colors.white.withOpacity(0.03)
: Colors.white.withOpacity(0.06),
? AppColors.surfaceContainerLowest
: AppColors.surfaceContainerLow,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isNow
? Colors.redAccent.withOpacity(0.4)
: Colors.white.withOpacity(0.07),
? AppColors.live.withOpacity(0.4)
: AppColors.surfaceContainer,
),
),
child: ListTile(
@@ -619,19 +620,19 @@ class _ProgrammeCard extends StatelessWidget {
padding:
const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: Colors.redAccent,
color: AppColors.live,
borderRadius: BorderRadius.circular(4)),
child: Text('LIVE',
style: GoogleFonts.outfit(
color: Colors.white,
style: GoogleFonts.inter(
color: AppColors.onSurface,
fontSize: 8,
fontWeight: FontWeight.bold)),
)
else
Text(
_fmt(_start),
style: GoogleFonts.outfit(
color: isPast ? Colors.white24 : Colors.white70,
style: GoogleFonts.inter(
color: isPast ? AppColors.outline : AppColors.onSurfaceVariant,
fontSize: 13,
fontWeight: FontWeight.w600,
),
@@ -640,15 +641,15 @@ class _ProgrammeCard extends StatelessWidget {
Text(
_fmt(_end),
style:
GoogleFonts.outfit(color: Colors.white24, fontSize: 10),
GoogleFonts.inter(color: AppColors.outline, fontSize: 10),
),
],
),
),
title: Text(
_title.isEmpty ? '—' : _title,
style: GoogleFonts.outfit(
color: isPast ? Colors.white38 : Colors.white,
style: GoogleFonts.inter(
color: isPast ? AppColors.outline : AppColors.onSurface,
fontWeight: isNow ? FontWeight.bold : FontWeight.normal,
),
maxLines: 2,
@@ -659,13 +660,13 @@ class _ProgrammeCard extends StatelessWidget {
_description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white38, fontSize: 11),
style: const TextStyle(color: AppColors.outline, fontSize: 11),
)
: null,
trailing: !isPast
? IconButton(
icon: const Icon(Icons.fiber_manual_record,
color: Colors.redAccent, size: 20),
color: AppColors.live, size: 20),
tooltip: 'Enregistrer ce programme',
onPressed: () => _record(context),
)
@@ -746,16 +747,16 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1E1E2E),
backgroundColor: AppColors.surfaceContainerHigh,
title: Text(title,
style: GoogleFonts.outfit(color: Colors.white, fontSize: 14)),
style: GoogleFonts.inter(color: AppColors.onSurface, fontSize: 14)),
content: SizedBox(
width: 500,
height: 300,
child: SingleChildScrollView(
child: Text(content,
style: const TextStyle(
color: Colors.white70,
color: AppColors.onSurfaceVariant,
fontSize: 11,
fontFamily: 'monospace')),
),
@@ -776,10 +777,10 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
}
Color _statusColor(String status) => switch (status) {
'recording' => Colors.redAccent,
'completed' => Colors.green,
'failed' => Colors.orangeAccent,
_ => Colors.blueAccent,
'recording' => AppColors.live,
'completed' => AppColors.success,
'failed' => AppColors.warning,
_ => AppColors.primaryContainer,
};
String _statusLabel(String status) => switch (status) {
@@ -809,7 +810,7 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
children: [
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
IconButton(
icon: const Icon(Icons.refresh, color: Colors.white70),
icon: const Icon(Icons.refresh, color: AppColors.onSurfaceVariant),
onPressed: _fetchRecordings,
tooltip: 'Rafraîchir',
),
@@ -820,18 +821,18 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
: _error != null
? Center(
child: Text(_error!,
style: const TextStyle(color: Colors.redAccent)))
style: const TextStyle(color: AppColors.live)))
: _recordings.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.videocam_off,
size: 64, color: Color(0x1FFFFFFF)),
Icon(Icons.videocam_off,
size: 64, color: AppColors.outline.withOpacity(0.1)),
const SizedBox(height: 16),
Text('Aucun enregistrement',
style: GoogleFonts.outfit(
color: Colors.white38, fontSize: 16)),
style: GoogleFonts.inter(
color: AppColors.outline, fontSize: 16)),
],
),
)
@@ -845,10 +846,10 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
color: AppColors.surfaceContainerLow,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withOpacity(0.1)),
color: AppColors.outlineVariant),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
@@ -861,8 +862,8 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
size: 28,
),
title: Text(rec['title'] ?? '—',
style: GoogleFonts.outfit(
color: Colors.white,
style: GoogleFonts.inter(
color: AppColors.onSurface,
fontWeight: FontWeight.w600)),
subtitle: Column(
crossAxisAlignment:
@@ -871,13 +872,13 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
Text(
'${_fmtDate(rec['start_time'])} → ${_fmtDate(rec['end_time'])}',
style: const TextStyle(
color: Colors.white54,
color: AppColors.onSurfaceVariant,
fontSize: 12),
),
if (rec['error_reason'] != null)
Text('⚠ ${rec['error_reason']}',
style: const TextStyle(
color: Colors.orangeAccent,
color: AppColors.warning,
fontSize: 11)),
],
),
@@ -904,7 +905,7 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
if (status == 'recording')
IconButton(
icon: const Icon(Icons.stop_circle,
color: Colors.redAccent),
color: AppColors.live),
tooltip: 'Arrêter',
onPressed: () => _stopRecording(
rec['id'], rec['title'] ?? ''),
@@ -912,7 +913,7 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
IconButton(
icon: const Icon(
Icons.description_outlined,
color: Colors.blueAccent,
color: AppColors.primaryContainer,
size: 20),
tooltip: 'Logs',
onPressed: () => _showLogs(
@@ -920,7 +921,7 @@ class _RecordingsListViewState extends State<_RecordingsListView> {
),
IconButton(
icon: const Icon(Icons.delete_outline,
color: Colors.white38, size: 20),
color: AppColors.outline, size: 20),
tooltip: 'Supprimer',
onPressed: () =>
_deleteRecording(rec['id']),
@@ -991,19 +992,19 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF1E1E2E),
backgroundColor: AppColors.surfaceContainerHigh,
title: Row(children: [
const Icon(Icons.repeat, color: Colors.purpleAccent),
const Icon(Icons.repeat, color: AppColors.primary),
const SizedBox(width: 8),
Text('Nouveau Season Pass',
style: GoogleFonts.outfit(color: Colors.white)),
style: GoogleFonts.inter(color: AppColors.onSurface)),
]),
content: SizedBox(
width: 400,
child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(
'Enregistre automatiquement toutes les nouvelles diffusions d\'une émission.',
style: GoogleFonts.outfit(color: Colors.white54, fontSize: 13),
style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 13),
),
const SizedBox(height: 16),
_buildField(
@@ -1021,7 +1022,7 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
child: const Text('Annuler')),
ElevatedButton(
style:
ElevatedButton.styleFrom(backgroundColor: Colors.purpleAccent),
ElevatedButton.styleFrom(backgroundColor: AppColors.primary),
onPressed: () async {
final t = titleCtrl.text.trim();
final c = channelCtrl.text.trim();
@@ -1055,14 +1056,14 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
Widget _buildField(TextEditingController c, String label) {
return TextField(
controller: c,
style: const TextStyle(color: Colors.white),
style: const TextStyle(color: AppColors.onSurface),
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.white54, fontSize: 12),
labelStyle: const TextStyle(color: AppColors.onSurfaceVariant, fontSize: 12),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white24)),
borderSide: BorderSide(color: AppColors.outline)),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.purpleAccent)),
borderSide: BorderSide(color: AppColors.primary)),
),
);
}
@@ -1076,10 +1077,10 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Enregistrements automatiques',
style: GoogleFonts.outfit(color: Colors.white54, fontSize: 13)),
style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 13)),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purpleAccent),
backgroundColor: AppColors.primary),
icon: const Icon(Icons.add, size: 18),
label: const Text('Nouveau'),
onPressed: _showCreate,
@@ -1090,19 +1091,19 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.purpleAccent.withOpacity(0.08),
color: AppColors.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.purpleAccent.withOpacity(0.2)),
border: Border.all(color: AppColors.primary.withOpacity(0.2)),
),
child: Row(children: [
const Icon(Icons.info_outline,
color: Colors.purpleAccent, size: 18),
color: AppColors.primary, size: 18),
const SizedBox(width: 12),
Expanded(
child: Text(
'Scanne l\'EPG toutes les 4h et programme automatiquement les nouvelles diffusions. Seuls les nouveaux épisodes sont enregistrés.',
style: GoogleFonts.outfit(
color: Colors.purpleAccent.shade100, fontSize: 12),
style: GoogleFonts.inter(
color: AppColors.primaryFixedDim, fontSize: 12),
),
),
]),
@@ -1116,17 +1117,17 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.repeat,
size: 64, color: Color(0x1FFFFFFF)),
Icon(Icons.repeat,
size: 64, color: AppColors.outline.withOpacity(0.1)),
const SizedBox(height: 16),
Text('Aucun Season Pass actif',
style: GoogleFonts.outfit(
color: Colors.white38, fontSize: 16)),
style: GoogleFonts.inter(
color: AppColors.outline, fontSize: 16)),
const SizedBox(height: 8),
Text(
'Créez-en un pour enregistrer automatiquement vos émissions préférées',
style: GoogleFonts.outfit(
color: Colors.white24, fontSize: 12),
style: GoogleFonts.inter(
color: AppColors.outline, fontSize: 12),
textAlign: TextAlign.center),
],
),
@@ -1138,32 +1139,32 @@ class _SeasonPassesViewState extends State<_SeasonPassesView> {
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.purpleAccent.withOpacity(0.08),
color: AppColors.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.purpleAccent.withOpacity(0.2)),
color: AppColors.primary.withOpacity(0.2)),
),
child: ListTile(
leading: const Icon(Icons.repeat,
color: Colors.purpleAccent),
color: AppColors.primary),
title: Text(p['show_title'] ?? '—',
style: GoogleFonts.outfit(
color: Colors.white,
style: GoogleFonts.inter(
color: AppColors.onSurface,
fontWeight: FontWeight.w600)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Chaîne : ${p['channel_id']}',
style: const TextStyle(
color: Colors.white54, fontSize: 11)),
color: AppColors.onSurfaceVariant, fontSize: 11)),
Text('Flux : ${p['stream_url']}',
style: const TextStyle(
color: Colors.white38, fontSize: 10)),
color: AppColors.outline, fontSize: 10)),
],
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline,
color: Colors.white38),
color: AppColors.outline),
tooltip: 'Supprimer',
onPressed: () =>
_deletePass(p['id'], p['show_title'] ?? ''),