fix(theme): remove every hardcoded Apple/Material colour left in the UI

Porting the palette was not enough: the Warm Cinema charter only reached the
widgets that read from AppColors. Nineteen screen and widget files still painted
Apple TV values inline, so the app kept showing blue — most visibly on the
selected navigation pill.

Swept out:
- 0xFF0A84FF (Apple blue) on the selected nav pill, category card icons and the
  "add playlist" button
- 0xFF4FC3F7 and 0xFF1565C0 in the player overlays
- Colors.blueAccent / Colors.blue in dialogs
- Apple greys 1C1C1E / 2C2C2E / 3A3A3C / 8E8E93 -> warm charcoals
- 128 Colors.white* -> the onSurfaceNN cream ladder (a pure white on a warm
  ground reads blue, which is the charter's whole point)
- Colors.red / green / amber / yellow / grey* -> error, success, ratingGold,
  outlineVariant
- pure black ink on light fills -> onPrimaryFixed, a warm near-black
- the white focus glow -> an ember glow

Deliberately left true black: the video surface backgrounds and the scrims over
posters and video, where a warm tint would clash with the picture.

Adds the onPrimaryFixed / primaryFixed / onSurface70 tokens the sweep needed.

Verified: no Material or Apple colour literal remains outside lib/core/theme;
flutter analyze reports no errors; 19/19 tests pass; release APK builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-08-11 20:39:58 +02:00
1 parent 8d74bbb320
commit 694b1257b9
21 files changed
+221 -184

No files matched your search

@@ -16,6 +16,7 @@ import 'package:xtremobile/features/iptv/screens/lite_player_screen.dart';
import 'package:flutter/services.dart';
import 'package:xtremobile/core/services/ip_service.dart';
import 'package:xtremobile/core/models/iptv_models.dart' as model;
import 'package:xtremobile/core/theme/app_colors.dart';
class MobileLiveTVTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -130,14 +131,14 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
final shouldExit = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.grey[900],
backgroundColor: AppColors.surfaceContainerLowest,
title: const Text(
"Quitter l'application",
style: TextStyle(color: Colors.white),
style: TextStyle(color: AppColors.onSurface),
),
content: const Text(
"Voulez-vous vraiment quitter l'application ?",
style: TextStyle(color: Colors.white70),
style: TextStyle(color: AppColors.onSurface70),
),
actions: [
TextButton(
@@ -145,14 +146,14 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
autofocus: true,
child: const Text(
'Annuler',
style: TextStyle(color: Colors.blueAccent),
style: TextStyle(color: AppColors.primary),
),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
'Quitter',
style: TextStyle(color: Colors.redAccent),
style: TextStyle(color: AppColors.error),
),
),
],
@@ -171,7 +172,7 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
padding: const EdgeInsets.all(16),
child: Text(
'Erreur: $e',
style: const TextStyle(color: Colors.red),
style: const TextStyle(color: AppColors.error),
textAlign: TextAlign.center,
),
),
@@ -235,7 +236,7 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
// Search Bar
TVFocusable(
scale: 1.0,
focusColor: Colors.white,
focusColor: AppColors.onSurface,
onPressed: () {
setState(() => _isSearchEditing = true);
WidgetsBinding.instance
@@ -263,7 +264,7 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
child: ExcludeFocus(
excluding: !_isSearchEditing,
child: TextField(
cursorColor: Colors.white,
cursorColor: AppColors.onSurface,
controller: _searchController,
focusNode: _searchFocusNode,
readOnly: !_isSearchEditing,
@@ -357,7 +358,7 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
? Icons.favorite
: Icons.favorite_border,
color: _showFavoritesOnly
? const Color(0xFFFF453A)
? AppColors.live
: AppDecorations.textSecondary(
context),
),
@@ -535,7 +536,7 @@ class _ChannelListView extends ConsumerWidget {
child: Center(
child: Text(
'Aucune catégorie sélectionnée',
style: TextStyle(color: Colors.white70),
style: TextStyle(color: AppColors.onSurface70),
),
),
);
@@ -557,7 +558,7 @@ class _ChannelListView extends ConsumerWidget {
showFavoritesOnly
? 'Sélectionnez une catégorie pour voir vos favoris'
: 'Sélectionnez une catégorie pour rechercher',
style: const TextStyle(color: Colors.white70),
style: const TextStyle(color: AppColors.onSurface70),
textAlign: TextAlign.center,
),
),
@@ -575,7 +576,7 @@ class _ChannelListView extends ConsumerWidget {
padding: const EdgeInsets.all(16),
child: Text(
'Erreur: $e',
style: const TextStyle(color: Colors.red),
style: const TextStyle(color: AppColors.error),
textAlign: TextAlign.center,
),
),
@@ -600,7 +601,7 @@ class _ChannelListView extends ConsumerWidget {
child: Center(
child: Text(
'Aucune chaîne trouvée',
style: TextStyle(color: Colors.white70),
style: TextStyle(color: AppColors.onSurface70),
),
),
);
@@ -754,12 +755,12 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
color: AppColors.error,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.favorite,
color: Colors.white,
color: AppColors.onSurface,
size: 10,
),
),
@@ -779,7 +780,7 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
Text(
widget.channel.name,
style: const TextStyle(
color: Colors.white,
color: AppColors.onSurface,
fontWeight: FontWeight.bold,
fontSize: 10,
),
@@ -792,7 +793,7 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
Text(
_epgTitle!,
style: const TextStyle(
color: Colors.yellow,
color: AppColors.ratingGold,
fontSize: 8,
),
textAlign: TextAlign.center,
@@ -11,6 +11,7 @@ import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
import 'package:xtremobile/core/models/iptv_models.dart' as model;
import 'package:xtremobile/features/iptv/screens/native_player_screen.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
class MobileMoviesTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -285,14 +286,14 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
final shouldExit = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.grey[900],
backgroundColor: AppColors.surfaceContainerLowest,
title: const Text(
"Quitter l'application",
style: TextStyle(color: Colors.white),
style: TextStyle(color: AppColors.onSurface),
),
content: const Text(
"Voulez-vous vraiment quitter l'application ?",
style: TextStyle(color: Colors.white70),
style: TextStyle(color: AppColors.onSurface70),
),
actions: [
TextButton(
@@ -300,14 +301,14 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
autofocus: true,
child: const Text(
'Annuler',
style: TextStyle(color: Colors.blueAccent),
style: TextStyle(color: AppColors.primary),
),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
'Quitter',
style: TextStyle(color: Colors.redAccent),
style: TextStyle(color: AppColors.error),
),
),
],
@@ -346,7 +347,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
// Search Bar
TVFocusable(
scale: 1.0,
focusColor: Colors.white,
focusColor: AppColors.onSurface,
onPressed: () {
setState(() => _isSearchEditing = true);
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -367,7 +368,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
child: ExcludeFocus(
excluding: !_isSearchEditing,
child: TextField(
cursorColor: Colors.white,
cursorColor: AppColors.onSurface,
controller: _searchController,
focusNode: _searchFocusNode,
readOnly: !_isSearchEditing,
@@ -11,6 +11,7 @@ import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
import 'package:xtremobile/core/models/iptv_models.dart' as model;
import 'package:xtremobile/features/iptv/screens/mobile_series_detail_screen.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
class MobileSeriesTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -269,18 +270,18 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
final shouldExit = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.grey[900],
title: const Text("Quitter l'application", style: TextStyle(color: Colors.white)),
content: const Text("Voulez-vous vraiment quitter l'application ?", style: TextStyle(color: Colors.white70)),
backgroundColor: AppColors.surfaceContainerLowest,
title: const Text("Quitter l'application", style: TextStyle(color: AppColors.onSurface)),
content: const Text("Voulez-vous vraiment quitter l'application ?", style: TextStyle(color: AppColors.onSurface70)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
autofocus: true,
child: const Text('Annuler', style: TextStyle(color: Colors.blueAccent)),
child: const Text('Annuler', style: TextStyle(color: AppColors.primary)),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Quitter', style: TextStyle(color: Colors.redAccent)),
child: const Text('Quitter', style: TextStyle(color: AppColors.error)),
),
],
),
@@ -316,7 +317,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
// Search Bar
TVFocusable(
scale: 1.0,
focusColor: Colors.white,
focusColor: AppColors.onSurface,
onPressed: () {
setState(() => _isSearchEditing = true);
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -337,7 +338,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
child: ExcludeFocus(
excluding: !_isSearchEditing,
child: TextField(
cursorColor: Colors.white,
cursorColor: AppColors.onSurface,
controller: _searchController,
focusNode: _searchFocusNode,
readOnly: !_isSearchEditing,
@@ -8,6 +8,7 @@ import 'package:xtremobile/core/theme/app_decorations.dart';
import 'package:xtremobile/core/theme/app_theme.dart';
import 'package:xtremobile/core/providers/theme_provider.dart';
import 'package:xtremobile/features/iptv/screens/mobile_playlist_selection_screen.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
/// Simplified mobile settings tab - optimized for TV remote control
class MobileSettingsTab extends ConsumerStatefulWidget {
@@ -105,7 +106,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
),
),
Text(
'Version 1.5.1',
'Version 1.6.1',
style: GoogleFonts.inter(
fontSize: 13,
color: AppDecorations.textSecondary(context),
@@ -327,7 +328,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cache actualisé ! Relancez l\'app pour recharger.'),
backgroundColor: Colors.green,
backgroundColor: AppColors.success,
duration: Duration(seconds: 3),
),
);
@@ -335,7 +336,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e'), backgroundColor: Colors.red),
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppColors.error),
);
}
} finally {
@@ -415,7 +416,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
icon,
size: 16,
color: selected
? Colors.white
? AppColors.onSurface
: AppDecorations.textSecondary(context),
),
const SizedBox(height: 3),
@@ -427,7 +428,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
? FontWeight.w700
: FontWeight.w500,
color: selected
? Colors.white
? AppColors.onSurface
: AppDecorations.textSecondary(context),
),
),