diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index c402f00..2547835 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -1,98 +1,185 @@ import 'package:flutter/material.dart'; -/// XtremFlow Apple TV Style Palette +/// Cyber-Cinematic Glass Design System — Material 3 Token Palette /// -/// Ultra minimalist, focus-driven palette. -/// - Background: Pure Black (#000000) -/// - Focus/Active: Pure White (#FFFFFF) -/// - Inactive: Grey (#9E9E9E) -/// - Glass: Heavy blur with low opacity +/// Extracted from the Stitch design system for XtremFlow. +/// Optimized for OLED displays and low-light environments. +/// Primary accent: Neon blue (#4b8eff / #adc6ff) class AppColors { AppColors._(); + // ============ COMPATIBILITY ALIASES (transition) ============ + static Color get focusColor => primaryContainer; + static Color get border => outlineVariant; + + // ============ SURFACE / ON SURFACE ============ + static const Color onSurface = Color(0xFFe3e2e7); + static const Color onSurfaceVariant = Color(0xFFc1c6d7); + + // ============ BASE LEVEL 0 ============ + static const Color baseLevel0 = Color(0xFF0F1014); + // ============ BACKGROUNDS ============ - /// Pure black for that infinite depth look (OLED friendly) - static const Color background = Color(0xFF0F1014); // Deepest Blue-Black + static const Color background = Color(0xFF121317); + static const Color onBackground = Color(0xFFe3e2e7); - /// Very deep grey for surfaces that need to be distinct but subtle (Apple Dark Grey) - static const Color surface = Color(0xFF181920); + static const Color surface = Color(0xFF121317); + static const Color surfaceDim = Color(0xFF121317); + static const Color surfaceBright = Color(0xFF38393d); - /// Slightly lighter grey for secondary surfaces or hover states - static const Color surfaceVariant = Color(0xFF1E2028); + // Surface Containers (Material 3 hierarchy) + static const Color surfaceContainerLowest = Color(0xFF0d0e12); + static const Color surfaceContainerLow = Color(0xFF1a1b20); + static const Color surfaceContainer = Color(0xFF1f1f24); + static const Color surfaceContainerHigh = Color(0xFF292a2e); + static const Color surfaceContainerHighest = Color(0xFF343439); + static const Color surfaceVariant = Color(0xFF343439); - static const Color surfaceGlass = - Color(0x1AFFFFFF); // 10% White for glass effect + // ============ PRIMARY (Neon Blue) ============ + static const Color primary = Color(0xFFadc6ff); + static const Color onPrimary = Color(0xFF002e69); + static const Color primaryContainer = Color(0xFF4b8eff); + static const Color onPrimaryContainer = Color(0xFF00285c); + static const Color inversePrimary = Color(0xFF005bc1); + static const Color primaryFixed = Color(0xFFd8e2ff); + static const Color primaryFixedDim = Color(0xFFadc6ff); + static const Color onPrimaryFixed = Color(0xFF001a41); + static const Color onPrimaryFixedVariant = Color(0xFF004493); - /// Focused element background (often white in tvOS for text, or bright accent) - static const Color focusColor = Color(0xFFFFFFFF); - static const Color border = Color(0xFF1F1F1F); - static const Color focusBorder = Color(0xFFFFFFFF); + // ============ SECONDARY ============ + static const Color secondary = Color(0xFFc6c5cf); + static const Color onSecondary = Color(0xFF2f3037); + static const Color secondaryContainer = Color(0xFF4a4b53); + static const Color onSecondaryContainer = Color(0xFFbcbbc4); + static const Color secondaryFixed = Color(0xFFe3e1eb); + static const Color secondaryFixedDim = Color(0xFFc6c5cf); + static const Color onSecondaryFixed = Color(0xFF1a1b22); + static const Color onSecondaryFixedVariant = Color(0xFF46464e); - // ============ ACCENTS ============ - static const Color primary = Color(0xFF6C63FF); - static const Color secondary = Color(0xFF00B4D8); - static const Color accent = Color(0xFF00B4D8); + // ============ TERTIARY ============ + static const Color tertiary = Color(0xFFc6c6c7); + static const Color onTertiary = Color(0xFF2f3131); + static const Color tertiaryContainer = Color(0xFF909191); + static const Color onTertiaryContainer = Color(0xFF282a2a); + static const Color tertiaryFixed = Color(0xFFe2e2e2); + static const Color tertiaryFixedDim = Color(0xFFc6c6c7); + static const Color onTertiaryFixed = Color(0xFF1a1c1c); + static const Color onTertiaryFixedVariant = Color(0xFF454747); - /// Primary Gradient (Electric Violet to Cyan) + // ============ ERROR ============ + static const Color error = Color(0xFFffb4ab); + static const Color onError = Color(0xFF690005); + static const Color errorContainer = Color(0xFF93000a); + static const Color onErrorContainer = Color(0xFFffdad6); + + // ============ OUTLINE ============ + static const Color outline = Color(0xFF8b90a0); + static const Color outlineVariant = Color(0xFF414755); + + // ============ INVERSE SURFACE ============ + static const Color inverseSurface = Color(0xFFe3e2e7); + static const Color inverseOnSurface = Color(0xFF2f3035); + + // ============ SURFACE TINT ============ + static const Color surfaceTint = Color(0xFFadc6ff); + + // ============ TEXT / CONTENT COLORS ============ + static const Color textPrimary = Color(0xFFe3e2e7); + static const Color textSecondary = Color(0xFFc1c6d7); + static const Color textTertiary = Color(0xFF8b90a0); + + // ============ STATUS COLORS ============ + static const Color success = Color(0xFF00E676); + static const Color warning = Color(0xFFFFAB00); + static const Color info = Color(0xFF2979FF); + + // ============ CATEGORY COLORS (kept for semantic meaning) ============ + static const Color live = Color(0xFFFF5252); + static const Color movies = Color(0xFF448AFF); + static const Color series = Color(0xFF69F0AE); + + // ============ RATING / PREMIUM ============ + static const Color ratingGold = Color(0xFFFFD700); + static const Color premiumGold = Color(0xFFFFA000); + + // ============ GLASSMORPHISM LEVELS ============ + static const Color glassLevel1Bg = Color(0x66121317); // 40% opacity + static const Color glassLevel1Border = Color(0x1AFFFFFF); // 10% white + + static const Color glassLevel2Bg = Color(0x99121317); // 60% opacity + static const Color glassLevel2Border = Color(0x1AFFFFFF); // 10% white + static const Color glassLevel2InnerGlow = Color(0x33adc6ff); // 20% primary top-left + + // ============ GRADIENTS ============ static const LinearGradient primaryGradient = LinearGradient( - colors: [Color(0xFF6C63FF), Color(0xFF00B4D8)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, + colors: [Color(0xFF007AFF), Color(0xFF00C6FF)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, ); static const LinearGradient backgroundGradient = LinearGradient( - colors: [Color(0xFF0F1014), Color(0xFF131418)], + colors: [Color(0xFF0F1014), Color(0xFF121317)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ); - // Status Colors - static const Color success = Color(0xFF00E676); - static const Color warning = Color(0xFFFFAB00); - static const Color error = Color(0xFFFF5252); - static const Color info = Color(0xFF2979FF); + // ============ GLOW / SHADOWS ============ + static Color glowPrimary(double opacity) => + const Color(0xFF007AFF).withOpacity(opacity); + static Color glowPrimaryDim(double opacity) => + const Color(0xFFadc6ff).withOpacity(opacity); - // Content Colors - static const Color textPrimary = Color(0xFFFFFFFF); - static const Color textSecondary = Color(0xFFAAAAAA); - static const Color textTertiary = Color(0xFF666666); - - static const Color ratingGold = Color(0xFFFFD700); - static const Color premiumGold = Color(0xFFFFA000); - - // Category Colors - static const Color live = Color(0xFFFF5252); // Red - static const Color movies = Color(0xFF448AFF); // Blue - static const Color series = Color(0xFF69F0AE); // Green - - // ============ GLASSMORPHISM ============ - // Apple TV uses this heavily for headers/overlays - static final Color glassBackground = const Color(0xFF1E1E1E).withOpacity(0.6); - static final Color glassBorder = const Color(0xFFFFFFFF).withOpacity(0.15); - - // ============ THEME SCHEMES ============ + // ============ COLOR SCHEMES ============ static ColorScheme get darkColorScheme => const ColorScheme.dark( primary: primary, - secondary: accent, - surface: surface, + onPrimary: onPrimary, + primaryContainer: primaryContainer, + onPrimaryContainer: onPrimaryContainer, + secondary: secondary, + onSecondary: onSecondary, + secondaryContainer: secondaryContainer, + onSecondaryContainer: onSecondaryContainer, + tertiary: tertiary, + onTertiary: onTertiary, + tertiaryContainer: tertiaryContainer, + onTertiaryContainer: onTertiaryContainer, error: error, - onPrimary: Colors - .black, // White text on buttons -> Black text on White buttons - onSecondary: Colors.black, - onSurface: textPrimary, - onError: Colors.white, + onError: onError, + errorContainer: errorContainer, + onErrorContainer: onErrorContainer, + surface: surface, + onSurface: onSurface, + surfaceContainerLowest: surfaceContainerLowest, + surfaceContainerLow: surfaceContainerLow, + surfaceContainer: surfaceContainer, + surfaceContainerHigh: surfaceContainerHigh, + surfaceContainerHighest: surfaceContainerHighest, + surfaceTint: surfaceTint, + outline: outline, + outlineVariant: outlineVariant, + inverseSurface: inverseSurface, + inversePrimary: inversePrimary, brightness: Brightness.dark, ); + // Light scheme is minimal — Stitch is dark-only static ColorScheme get lightColorScheme => const ColorScheme.light( - primary: Colors.black, - secondary: Colors.grey, - surface: Color(0xFFF2F2F7), - error: error, + primary: Color(0xFF005bc1), onPrimary: Colors.white, + primaryContainer: Color(0xFFd8e2ff), + onPrimaryContainer: Color(0xFF001a41), + secondary: Color(0xFF5e5c68), onSecondary: Colors.white, - onSurface: Colors.black, + secondaryContainer: Color(0xFFe3e1eb), + onSecondaryContainer: Color(0xFF1a1b22), + error: Color(0xFFba1a1a), onError: Colors.white, + errorContainer: Color(0xFFFFdad6), + onErrorContainer: Color(0xFF410002), + surface: Color(0xFFF9F9FF), + onSurface: Color(0xFF1a1b22), + outline: Color(0xFF757680), + outlineVariant: Color(0xFFc5c6d0), brightness: Brightness.light, ); } diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 56646e8..efa75dd 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -2,35 +2,71 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'app_colors.dart'; -/// XtremFlow Apple TV Theme +/// XtremFlow Cyber-Cinematic Glass Theme /// -/// Focus-driven, immersive, minimal. +/// Design system extracted from Google Stitch. +/// Fonts: Space Grotesk (headlines) + Inter (body/labels) +/// Style: Dark-only glassmorphism with neon blue accents. class AppTheme { AppTheme._(); - // ============ SPACING ============ - static const double spacing4 = 4.0; - static const double spacing8 = 8.0; - static const double spacing12 = 12.0; - static const double spacing16 = 16.0; - static const double spacing24 = 24.0; - static const double spacing32 = 32.0; - static const double spacing48 = 48.0; // Larger spacing for TV feel + // ============ SPACING (8px rhythmic system) ============ + static const double spacingXs = 4.0; + static const double spacingSm = 8.0; + static const double spacingBase = 8.0; + static const double spacingMd = 16.0; + static const double spacingLg = 24.0; + static const double spacingXl = 32.0; + static const double spacing2xl = 48.0; + static const double spacing3xl = 80.0; // ============ RADIUS ============ - // ============ RADIUS ============ + static const double radiusNone = 0.0; static const double radiusSm = 8.0; static const double radiusMd = 12.0; - static const double radiusLg = 20.0; - static const double radiusXl = 32.0; + static const double radiusLg = 16.0; + static const double radiusXl = 24.0; + static const double radiusFull = 9999.0; // ============ ANIMATION ============ static const Duration durationFast = Duration(milliseconds: 200); static const Duration durationNormal = Duration(milliseconds: 300); - static const Curve curveDefault = - Curves.fastOutSlowIn; // Apple-like snappy curve + static const Curve curveDefault = Curves.fastOutSlowIn; - // ============ DARK THEME (TV Main) ============ + // ============ TYPOGRAPHY HELPERS ============ + static TextStyle _spaceGrotesk({ + required double fontSize, + required FontWeight fontWeight, + double? letterSpacing, + double? height, + Color color = AppColors.onSurface, + }) { + return GoogleFonts.spaceGrotesk( + fontSize: fontSize, + fontWeight: fontWeight, + letterSpacing: letterSpacing, + height: height, + color: color, + ); + } + + static TextStyle _inter({ + required double fontSize, + required FontWeight fontWeight, + double? letterSpacing, + double? height, + Color color = AppColors.onSurface, + }) { + return GoogleFonts.inter( + fontSize: fontSize, + fontWeight: fontWeight, + letterSpacing: letterSpacing, + height: height, + color: color, + ); + } + + // ============ DARK THEME (Main) ============ static ThemeData get darkTheme { final baseTheme = ThemeData.dark(useMaterial3: true); @@ -38,78 +74,114 @@ class AppTheme { colorScheme: AppColors.darkColorScheme, scaffoldBackgroundColor: AppColors.background, - // Typography: "Cinematic Modern" (Outfit for Headings, Inter for UI) + // Typography textTheme: TextTheme( - displayLarge: GoogleFonts.outfit( - fontSize: 56, - fontWeight: FontWeight.w700, - letterSpacing: -1.0, - color: AppColors.textPrimary, - ), - displayMedium: GoogleFonts.outfit( + // Display / Headline (Space Grotesk) + displayLarge: _spaceGrotesk( fontSize: 48, fontWeight: FontWeight.w700, - letterSpacing: -0.5, - color: AppColors.textPrimary, + letterSpacing: -0.02, + height: 1.1, ), - displaySmall: GoogleFonts.outfit( + displayMedium: _spaceGrotesk( fontSize: 32, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + letterSpacing: -0.01, + height: 1.2, ), - headlineMedium: GoogleFonts.outfit( + displaySmall: _spaceGrotesk( fontSize: 24, - fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + fontWeight: FontWeight.w500, + height: 1.3, ), - titleLarge: GoogleFonts.outfit( + headlineLarge: _spaceGrotesk( + fontSize: 32, + fontWeight: FontWeight.w600, + letterSpacing: -0.01, + height: 1.2, + ), + headlineMedium: _spaceGrotesk( + fontSize: 24, + fontWeight: FontWeight.w500, + height: 1.3, + ), + headlineSmall: _spaceGrotesk( + fontSize: 20, + fontWeight: FontWeight.w500, + height: 1.3, + ), + titleLarge: _spaceGrotesk( fontSize: 20, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, ), - titleMedium: GoogleFonts.inter( + titleMedium: _inter( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, ), - bodyLarge: GoogleFonts.inter( - fontSize: 16, - fontWeight: FontWeight.w400, - color: AppColors.textSecondary, - ), - bodyMedium: GoogleFonts.inter( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.textSecondary, - ), - labelLarge: GoogleFonts.inter( + titleSmall: _inter( fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + ), + // Body (Inter) + bodyLarge: _inter( + fontSize: 18, + fontWeight: FontWeight.w400, + height: 1.6, + color: AppColors.onSurfaceVariant, + ), + bodyMedium: _inter( + fontSize: 16, + fontWeight: FontWeight.w400, + height: 1.5, + color: AppColors.onSurfaceVariant, + ), + bodySmall: _inter( + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.4, + color: AppColors.onSurfaceVariant, + ), + // Labels (Inter with tracking) + labelLarge: _inter( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.05, + height: 1.2, + ), + labelMedium: _inter( + fontSize: 12, + fontWeight: FontWeight.w500, + letterSpacing: 0.02, + height: 1.2, + ), + labelSmall: _inter( + fontSize: 11, + fontWeight: FontWeight.w500, + letterSpacing: 0.02, + height: 1.2, ), ), - // AppBar: Transparent / Glass + // AppBar: Transparent glass appBarTheme: AppBarTheme( backgroundColor: Colors.transparent, - foregroundColor: AppColors.textPrimary, + foregroundColor: AppColors.onSurface, elevation: 0, centerTitle: true, scrolledUnderElevation: 0, - titleTextStyle: GoogleFonts.outfit( + titleTextStyle: _spaceGrotesk( fontSize: 20, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, ), ), - // Inputs: Dark, minimal, no borders unless focused + // Inputs: Dark filled with blue glow on focus inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: AppColors.surface, + fillColor: AppColors.surfaceContainerLow, contentPadding: const EdgeInsets.symmetric( - horizontal: spacing24, - vertical: spacing16, + horizontal: spacingLg, + vertical: spacingMd, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), @@ -117,65 +189,205 @@ class AppTheme { ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), - borderSide: BorderSide.none, + borderSide: const BorderSide(color: AppColors.outlineVariant, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), - borderSide: const BorderSide(color: AppColors.focusColor, width: 2), + borderSide: const BorderSide(color: AppColors.primaryContainer, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(radiusMd), + borderSide: const BorderSide(color: AppColors.error, width: 1), + ), + hintStyle: _inter( + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.outline, + ), + labelStyle: _inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.onSurfaceVariant, ), - hintStyle: GoogleFonts.inter(color: AppColors.textTertiary), ), - // Buttons: White pill or minimal text + // Buttons: Gradient primary, ghost secondary filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( - backgroundColor: AppColors.focusColor, // White buttons - foregroundColor: Colors.black, // Black text + backgroundColor: AppColors.primaryContainer, + foregroundColor: AppColors.onPrimaryContainer, elevation: 0, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusMd), ), - textStyle: - GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16), + textStyle: _inter( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.05, + ), ).copyWith( overlayColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.hovered)) { - return Colors.black.withOpacity(0.1); + return Colors.white.withOpacity(0.1); } if (states.contains(WidgetState.pressed)) { - return Colors.black.withOpacity(0.2); + return Colors.white.withOpacity(0.2); } return null; }), ), ), - // Cards: Transparent by default (content defines look) - // cardTheme: const CardTheme( - // color: AppColors.surface, - // elevation: 0, - // margin: EdgeInsets.zero, - // shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(radiusMd))), - // ), + // Outlined buttons: ghost style + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.onSurface, + side: const BorderSide(color: AppColors.outlineVariant, width: 1), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd), + ), + textStyle: _inter( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.05, + ), + ), + ), - // Dialogs: Glass/Dark - // dialogTheme: DialogTheme( - // backgroundColor: const Color(0xFF1C1C1E), // Apple Dark Grey - // elevation: 24, - // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusLg)), - // titleTextStyle: GoogleFonts.inter(fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary), - // contentTextStyle: GoogleFonts.inter(fontSize: 16, color: AppColors.textSecondary), - // ), + // Text buttons + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.primary, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + textStyle: _inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + + // Cards: Glass style + cardTheme: CardThemeData( + color: AppColors.glassLevel1Bg, + elevation: 0, + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusLg), + side: const BorderSide(color: AppColors.glassLevel1Border, width: 1), + ), + ), + + // Dialogs: Floating glass + dialogTheme: DialogThemeData( + backgroundColor: AppColors.glassLevel2Bg, + elevation: 24, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusXl), + side: const BorderSide(color: AppColors.glassLevel2Border, width: 1), + ), + titleTextStyle: _spaceGrotesk( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + contentTextStyle: _inter( + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.onSurfaceVariant, + ), + ), + + // Bottom sheets + bottomSheetTheme: const BottomSheetThemeData( + backgroundColor: AppColors.surfaceContainerLow, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(radiusXl)), + ), + ), + + // Snackbars + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.surfaceContainerHigh, + contentTextStyle: _inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.onSurface, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd), + ), + behavior: SnackBarBehavior.floating, + ), + + // Chips: Pill shaped + chipTheme: ChipThemeData( + backgroundColor: AppColors.primary.withOpacity(0.1), + deleteIconColor: AppColors.onSurface, + disabledColor: AppColors.surfaceContainerHighest, + selectedColor: AppColors.primaryContainer, + secondarySelectedColor: AppColors.primaryContainer, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + shape: StadiumBorder( + side: BorderSide(color: AppColors.primary.withOpacity(0.3)), + ), + labelStyle: _inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.onSurface, + ), + secondaryLabelStyle: _inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.onPrimaryContainer, + ), + ), + + // Tabs + tabBarTheme: TabBarThemeData( + labelColor: AppColors.onSurface, + unselectedLabelColor: AppColors.outline, + indicatorColor: AppColors.primaryContainer, + labelStyle: _inter(fontSize: 14, fontWeight: FontWeight.w600), + unselectedLabelStyle: _inter(fontSize: 14, fontWeight: FontWeight.w500), + ), + + // Divider + dividerTheme: const DividerThemeData( + color: AppColors.outlineVariant, + thickness: 1, + ), + + // Scrollbar + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.all(AppColors.outlineVariant), + trackColor: WidgetStateProperty.all(Colors.transparent), + radius: const Radius.circular(4), + thickness: WidgetStateProperty.all(6), + ), + + // Switch + switchTheme: SwitchThemeData( + thumbColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.onPrimary; + } + return AppColors.outline; + }), + trackColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.primaryContainer; + } + return AppColors.surfaceContainerHighest; + }), + ), ); } - static const double radiusFull = 999.0; - - // Light theme stub - static ThemeData get lightTheme => - ThemeData.light(useMaterial3: true).copyWith( + // Light theme is minimal — Stitch is dark-only + static ThemeData get lightTheme => ThemeData.light(useMaterial3: true).copyWith( colorScheme: AppColors.lightColorScheme, + scaffoldBackgroundColor: AppColors.lightColorScheme.surface, textTheme: GoogleFonts.interTextTheme(ThemeData.light().textTheme), ); } diff --git a/lib/core/widgets/components/hero_carousel.dart b/lib/core/widgets/components/hero_carousel.dart index 81c8cc1..4523dea 100644 --- a/lib/core/widgets/components/hero_carousel.dart +++ b/lib/core/widgets/components/hero_carousel.dart @@ -92,7 +92,7 @@ class _HeroCarouselState extends State { boxShadow: [ if (isActive) BoxShadow( - color: Colors.black.withOpacity(0.5), + color: AppColors.onPrimaryContainer.withOpacity(0.5), blurRadius: 20, offset: const Offset(0, 10), ), @@ -107,10 +107,10 @@ class _HeroCarouselState extends State { CachedNetworkImage( imageUrl: item.imageUrl, fit: BoxFit.cover, - placeholder: (context, url) => Container(color: AppColors.surfaceVariant), + placeholder: (context, url) => Container(color: AppColors.surfaceContainerLow), errorWidget: (context, url, error) => Container( - color: AppColors.surfaceVariant, - child: const Icon(Icons.movie, size: 64, color: AppColors.textTertiary), + color: AppColors.surfaceContainerLow, + child: const Icon(Icons.movie, size: 64, color: AppColors.outline), ), ), @@ -122,8 +122,8 @@ class _HeroCarouselState extends State { end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.2), - Colors.black.withOpacity(0.9), + AppColors.onPrimaryContainer.withOpacity(0.2), + AppColors.onPrimaryContainer.withOpacity(0.9), ], stops: const [0.0, 0.5, 1.0], ), @@ -140,10 +140,10 @@ class _HeroCarouselState extends State { children: [ Text( item.title, - style: GoogleFonts.outfit( + style: GoogleFonts.spaceGrotesk( fontSize: 32, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -154,7 +154,7 @@ class _HeroCarouselState extends State { item.subtitle!, style: GoogleFonts.inter( fontSize: 16, - color: Colors.white.withOpacity(0.8), + color: AppColors.onSurface, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -219,24 +219,34 @@ class _HeroButtonState extends State<_HeroButton> { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( + gradient: widget.isPrimary ? AppColors.primaryGradient : null, color: widget.isPrimary - ? (_isHovered ? Colors.white : Colors.white.withOpacity(0.9)) - : (_isHovered ? Colors.white.withOpacity(0.2) : Colors.white.withOpacity(0.1)), + ? null + : (_isHovered ? AppColors.surfaceContainerHigh : AppColors.surfaceContainer), borderRadius: BorderRadius.circular(AppTheme.radiusFull), + boxShadow: widget.isPrimary + ? [ + BoxShadow( + color: AppColors.glowPrimary(0.3), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ] + : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( widget.icon, - color: widget.isPrimary ? Colors.black : Colors.white, + color: AppColors.onSurface, size: 20, ), const SizedBox(width: 8), Text( widget.label, style: GoogleFonts.inter( - color: widget.isPrimary ? Colors.black : Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.w600, ), ), diff --git a/lib/core/widgets/components/ui_components.dart b/lib/core/widgets/components/ui_components.dart index 139d907..a505626 100644 --- a/lib/core/widgets/components/ui_components.dart +++ b/lib/core/widgets/components/ui_components.dart @@ -1,10 +1,12 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../../theme/app_colors.dart'; import '../../theme/app_theme.dart'; +import '../glass_container.dart'; -/// Glassmorphism card widget with blur effect +/// Stitch-style glass card using GlassContainer Level 1. class GlassCard extends StatelessWidget { final Widget child; final EdgeInsetsGeometry padding; @@ -23,25 +25,18 @@ class GlassCard extends StatelessWidget { @override Widget build(BuildContext context) { - return ClipRRect( - borderRadius: BorderRadius.circular(borderRadius), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Material( - color: AppColors.glassBackground, - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(borderRadius), - child: Container( - padding: padding, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(borderRadius), - border: showBorder - ? Border.all(color: AppColors.glassBorder, width: 1) - : null, - ), - child: child, - ), + return GlassContainer.glass( + borderRadius: borderRadius, + border: showBorder, + padding: EdgeInsets.zero, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(borderRadius), + child: Padding( + padding: padding, + child: child, ), ), ), @@ -49,7 +44,7 @@ class GlassCard extends StatelessWidget { } } -/// Animated gradient button +/// Animated gradient primary button with Stitch glow. class GradientButton extends StatefulWidget { final String label; final VoidCallback? onPressed; @@ -106,12 +101,17 @@ class _GradientButtonState extends State child: Container( decoration: BoxDecoration( gradient: AppColors.primaryGradient, - borderRadius: BorderRadius.circular(AppTheme.radiusSm), + borderRadius: BorderRadius.circular(AppTheme.radiusMd), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 4), + color: AppColors.glowPrimary(0.3), + blurRadius: 16, + offset: const Offset(0, 6), + ), + BoxShadow( + color: AppColors.glowPrimary(0.1), + blurRadius: 30, + spreadRadius: 2, ), ], ), @@ -122,7 +122,7 @@ class _GradientButtonState extends State onTapDown: (_) => _controller.forward(), onTapUp: (_) => _controller.reverse(), onTapCancel: () => _controller.reverse(), - borderRadius: BorderRadius.circular(AppTheme.radiusSm), + borderRadius: BorderRadius.circular(AppTheme.radiusMd), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 24, @@ -134,24 +134,26 @@ class _GradientButtonState extends State height: 20, child: CircularProgressIndicator( strokeWidth: 2, - color: Colors.black, + color: AppColors.onSurface, ), ) : Row( - mainAxisSize: - widget.isExpanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisSize: widget.isExpanded + ? MainAxisSize.max + : MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ if (widget.icon != null) ...[ - Icon(widget.icon, color: Colors.black, size: 20), + Icon(widget.icon, color: AppColors.onSurface, size: 20), const SizedBox(width: 8), ], Text( widget.label, - style: const TextStyle( - color: Colors.black, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontWeight: FontWeight.w600, fontSize: 14, + letterSpacing: 0.05, ), ), ], @@ -168,7 +170,7 @@ class _GradientButtonState extends State } } -/// Shimmer loading effect widget +/// Shimmer loading effect using surface container tones. class ShimmerLoading extends StatefulWidget { final double width; final double height; @@ -220,9 +222,9 @@ class _ShimmerLoadingState extends State begin: Alignment(_animation.value - 1, 0), end: Alignment(_animation.value + 1, 0), colors: [ - AppColors.surface.withOpacity(0.5), - AppColors.surface, - AppColors.surface.withOpacity(0.5), + AppColors.surfaceContainerLow.withOpacity(0.5), + AppColors.surfaceContainer, + AppColors.surfaceContainerLow.withOpacity(0.5), ], ), ), @@ -232,29 +234,30 @@ class _ShimmerLoadingState extends State } } -/// Live indicator badge +/// Live indicator badge — Stitch style pill. class LiveBadge extends StatelessWidget { const LiveBadge({super.key}); @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppColors.live, borderRadius: BorderRadius.circular(4), ), - child: const Row( + child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.circle, size: 6, color: Colors.white), - SizedBox(width: 4), + const Icon(Icons.circle, size: 6, color: AppColors.onSurface), + const SizedBox(width: 4), Text( 'LIVE', - style: TextStyle( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 10, fontWeight: FontWeight.w700, + letterSpacing: 0.5, ), ), ], @@ -263,7 +266,7 @@ class LiveBadge extends StatelessWidget { } } -/// Category chip with color +/// Stitch-style category chip — pill shaped with primary accent. class CategoryChip extends StatelessWidget { final String label; final bool isSelected; @@ -280,8 +283,8 @@ class CategoryChip extends StatelessWidget { Widget build(BuildContext context) { return Material( color: isSelected - ? AppColors.primary.withOpacity(0.2) - : AppColors.surface, + ? AppColors.primary.withOpacity(0.1) + : AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(AppTheme.radiusFull), child: InkWell( onTap: onTap, @@ -291,15 +294,18 @@ class CategoryChip extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.radiusFull), border: Border.all( - color: isSelected ? AppColors.primary : AppColors.border, + color: isSelected + ? AppColors.primary.withOpacity(0.3) + : AppColors.outlineVariant, + width: 1, ), ), child: Text( label, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 13, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, - color: isSelected ? AppColors.primary : AppColors.textSecondary, + color: isSelected ? AppColors.primary : AppColors.onSurfaceVariant, ), ), ), @@ -308,7 +314,7 @@ class CategoryChip extends StatelessWidget { } } -/// Apple TV style Media Card (Vertical Poster) +/// Media poster card with Stitch glass hover effect. class MediaCard extends StatefulWidget { final String title; final String? imageUrl; @@ -341,6 +347,9 @@ class _MediaCardState extends State @override Widget build(BuildContext context) { + final focusBorderColor = + _isHovered ? AppColors.primaryContainer.withOpacity(0.5) : Colors.transparent; + return MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), @@ -362,21 +371,32 @@ class _MediaCardState extends State duration: AppTheme.durationFast, decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.radiusMd), - color: AppColors.surface, + color: AppColors.surfaceContainerLow, border: Border.all( - color: _isHovered ? AppColors.focusColor : Colors.transparent, - width: _isHovered ? 3 : 0, + color: focusBorderColor, + width: _isHovered ? 2 : 0, ), boxShadow: _isHovered ? [ BoxShadow( color: Colors.black.withOpacity(0.5), - blurRadius: 20, + blurRadius: 25, offset: const Offset(0, 10), + spreadRadius: 2, + ), + BoxShadow( + color: AppColors.glowPrimary(0.3), + blurRadius: 20, + spreadRadius: 1, + ), + BoxShadow( + color: AppColors.glowPrimary(0.1), + blurRadius: 40, + spreadRadius: 4, ), ] : [ - BoxShadow( + BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 4, offset: const Offset(0, 2), @@ -384,7 +404,8 @@ class _MediaCardState extends State ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(AppTheme.radiusMd - (_isHovered ? 2 : 0)), + borderRadius: BorderRadius.circular( + AppTheme.radiusMd - (_isHovered ? 2 : 0)), child: Stack( fit: StackFit.expand, children: [ @@ -393,33 +414,38 @@ class _MediaCardState extends State imageUrl: widget.imageUrl!, fit: BoxFit.cover, placeholder: (context, url) => Container( - color: AppColors.surface, + color: AppColors.surfaceContainerLow, child: Center( - child: Icon(widget.placeholderIcon, color: AppColors.textTertiary), + child: Icon(widget.placeholderIcon, + color: AppColors.outline), ), ), errorWidget: (context, url, error) => Container( - color: AppColors.surface, + color: AppColors.surfaceContainerLow, child: Center( - child: Icon(widget.placeholderIcon, color: AppColors.textTertiary), + child: Icon(widget.placeholderIcon, + color: AppColors.outline), ), ), ) else Container( - color: AppColors.surface, + color: AppColors.surfaceContainerLow, child: Center( - child: Icon(widget.placeholderIcon, color: AppColors.textTertiary), + child: Icon(widget.placeholderIcon, + color: AppColors.outline), ), ), - + // Rating Badge (Top Left) - if (widget.rating != null && widget.rating!.isNotEmpty) + if (widget.rating != null && + widget.rating!.isNotEmpty) Positioned( top: 8, left: 8, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(4), @@ -427,11 +453,16 @@ class _MediaCardState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.star, size: 10, color: Color(0xFFFFD700)), + const Icon(Icons.star, + size: 10, color: AppColors.primaryContainer), const SizedBox(width: 4), Text( widget.rating!, - style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + style: GoogleFonts.inter( + color: AppColors.onSurface, + fontSize: 10, + fontWeight: FontWeight.w700, + ), ), ], ), @@ -449,7 +480,8 @@ class _MediaCardState extends State color: AppColors.success, shape: BoxShape.circle, ), - child: const Icon(Icons.check, size: 12, color: Colors.white), + child: const Icon(Icons.check, + size: 12, color: AppColors.onSurface), ), ), ], @@ -458,7 +490,7 @@ class _MediaCardState extends State ), ), ), - + // Title & Subtitle (Below) const SizedBox(height: 12), SizedBox( @@ -470,9 +502,12 @@ class _MediaCardState extends State textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle( - color: _isHovered ? AppColors.focusColor : AppColors.textSecondary, - fontWeight: _isHovered ? FontWeight.w600 : FontWeight.w500, + style: GoogleFonts.inter( + color: _isHovered + ? AppColors.primary + : AppColors.onSurface, + fontWeight: + _isHovered ? FontWeight.w600 : FontWeight.w500, fontSize: 13, ), ), @@ -483,8 +518,8 @@ class _MediaCardState extends State textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.textTertiary, + style: GoogleFonts.inter( + color: AppColors.onSurfaceVariant, fontSize: 11, ), ), diff --git a/lib/core/widgets/digital_clock.dart b/lib/core/widgets/digital_clock.dart index 7ceda67..002ad2e 100644 --- a/lib/core/widgets/digital_clock.dart +++ b/lib/core/widgets/digital_clock.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; +import '../../core/theme/app_colors.dart'; class DigitalClock extends StatefulWidget { const DigitalClock({super.key}); @@ -40,7 +41,7 @@ class _DigitalClockState extends State { style: GoogleFonts.inter( fontWeight: FontWeight.w600, fontSize: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), ); diff --git a/lib/core/widgets/glass_container.dart b/lib/core/widgets/glass_container.dart index e4bf1da..294d041 100644 --- a/lib/core/widgets/glass_container.dart +++ b/lib/core/widgets/glass_container.dart @@ -2,6 +2,13 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; +/// Glassmorphism container with 3 depth levels (Stitch Design System) +/// +/// Level 0: Pure base background (#0F1014), no blur. +/// Level 1: Translucent layer with backdrop blur 20px, 1px border 10% white. +/// Level 2: High-blur floating container with subtle inner glow in primary accent. +enum GlassLevel { level0, level1, level2 } + class GlassContainer extends StatelessWidget { final Widget child; final double? width; @@ -9,9 +16,10 @@ class GlassContainer extends StatelessWidget { final EdgeInsetsGeometry padding; final EdgeInsetsGeometry? margin; final double borderRadius; - final double opacity; + final GlassLevel level; final Color? borderColor; final bool border; + final List? boxShadow; const GlassContainer({ super.key, @@ -21,13 +29,87 @@ class GlassContainer extends StatelessWidget { this.padding = const EdgeInsets.all(16), this.margin, this.borderRadius = 16.0, - this.opacity = 0.6, + this.level = GlassLevel.level1, this.borderColor, this.border = true, + this.boxShadow, }); + /// Convenience constructor for Level 0 (pure base) + const GlassContainer.base({ + super.key, + required this.child, + this.width, + this.height, + this.padding = const EdgeInsets.all(16), + this.margin, + this.borderRadius = 16.0, + this.borderColor, + this.border = false, + this.boxShadow, + }) : level = GlassLevel.level0; + + /// Convenience constructor for Level 1 (standard glass) + const GlassContainer.glass({ + super.key, + required this.child, + this.width, + this.height, + this.padding = const EdgeInsets.all(16), + this.margin, + this.borderRadius = 16.0, + this.borderColor, + this.border = true, + this.boxShadow, + }) : level = GlassLevel.level1; + + /// Convenience constructor for Level 2 (floating with inner glow) + const GlassContainer.floating({ + super.key, + required this.child, + this.width, + this.height, + this.padding = const EdgeInsets.all(16), + this.margin, + this.borderRadius = 24.0, + this.borderColor, + this.border = true, + this.boxShadow, + }) : level = GlassLevel.level2; + @override Widget build(BuildContext context) { + switch (level) { + case GlassLevel.level0: + return _buildLevel0(); + case GlassLevel.level1: + return _buildLevel1(); + case GlassLevel.level2: + return _buildLevel2(); + } + } + + Widget _buildLevel0() { + return Container( + width: width, + height: height, + margin: margin, + padding: padding, + decoration: BoxDecoration( + color: AppColors.baseLevel0, + borderRadius: BorderRadius.circular(borderRadius), + border: border + ? Border.all( + color: borderColor ?? Colors.transparent, + width: 1, + ) + : null, + ), + child: child, + ); + } + + Widget _buildLevel1() { return Container( width: width, height: height, @@ -35,32 +117,82 @@ class GlassContainer extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(borderRadius), child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), + filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( padding: padding, decoration: BoxDecoration( borderRadius: BorderRadius.circular(borderRadius), + color: AppColors.glassLevel1Bg, border: border ? Border.all( - color: borderColor ?? Colors.white.withOpacity(0.1), + color: borderColor ?? AppColors.glassLevel1Border, width: 1, ) : null, - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppColors.surface.withOpacity(opacity), - AppColors.surface.withOpacity(opacity * 0.5), - ], - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - blurRadius: 20, - spreadRadius: -5, - ), - ], + boxShadow: boxShadow ?? + [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 20, + spreadRadius: -5, + ), + ], + ), + child: child, + ), + ), + ), + ); + } + + Widget _buildLevel2() { + return Container( + width: width, + height: height, + margin: margin, + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40), + child: Container( + padding: padding, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), + color: AppColors.glassLevel2Bg, + border: border + ? Border( + top: BorderSide( + color: borderColor ?? AppColors.glassLevel2InnerGlow, + width: 1, + ), + left: BorderSide( + color: borderColor ?? AppColors.glassLevel2InnerGlow, + width: 1, + ), + right: BorderSide( + color: borderColor ?? AppColors.glassLevel2Border, + width: 1, + ), + bottom: BorderSide( + color: borderColor ?? AppColors.glassLevel2Border, + width: 1, + ), + ) + : null, + boxShadow: boxShadow ?? + [ + BoxShadow( + color: Colors.black.withOpacity(0.5), + blurRadius: 30, + spreadRadius: -10, + offset: const Offset(0, 10), + ), + BoxShadow( + color: AppColors.primary.withOpacity(0.1), + blurRadius: 40, + spreadRadius: -10, + ), + ], ), child: child, ), diff --git a/lib/core/widgets/hero_carousel.dart b/lib/core/widgets/hero_carousel.dart index cd480a5..22b1e7f 100644 --- a/lib/core/widgets/hero_carousel.dart +++ b/lib/core/widgets/hero_carousel.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../theme/app_colors.dart'; import 'glass_container.dart'; import 'tv_focusable_card.dart'; -import 'package:google_fonts/google_fonts.dart'; class HeroCarouselItem { final String id; @@ -12,6 +12,7 @@ class HeroCarouselItem { final String? imageUrl; final VoidCallback? onTap; final VoidCallback? onPlay; + final List? badges; HeroCarouselItem({ required this.id, @@ -20,6 +21,7 @@ class HeroCarouselItem { this.imageUrl, this.onTap, this.onPlay, + this.badges, }); } @@ -88,10 +90,9 @@ class _HeroCarouselState extends State { if (widget.items.isEmpty) return const SizedBox.shrink(); return SizedBox( - height: 500, // Cinematic height + height: 560, child: Stack( children: [ - // Carousel PageView.builder( controller: _pageController, onPageChanged: (index) { @@ -106,7 +107,7 @@ class _HeroCarouselState extends State { // Indicators Positioned( - bottom: 24, + bottom: 32, right: 48, child: Row( children: List.generate( @@ -118,7 +119,7 @@ class _HeroCarouselState extends State { height: 8, decoration: BoxDecoration( color: _currentIndex == index - ? AppColors.primary + ? AppColors.primaryContainer : Colors.white.withOpacity(0.3), borderRadius: BorderRadius.circular(4), ), @@ -135,15 +136,17 @@ class _HeroCarouselState extends State { return Stack( fit: StackFit.expand, children: [ - // Background Image with Ken Burns Effect (Simulated via Scale in future, static for now) + // Background Image if (item.imageUrl != null && item.imageUrl!.isNotEmpty) Image.network( _getProxiedUrl(item.imageUrl)!, fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container(color: AppColors.surface), - ), + errorBuilder: (_, __, ___) => Container(color: AppColors.surfaceContainerLow), + ) + else + Container(color: AppColors.surfaceContainerLow), - // Gradient Overlays + // Gradient Overlays — Stitch style Container( decoration: BoxDecoration( gradient: LinearGradient( @@ -151,9 +154,9 @@ class _HeroCarouselState extends State { end: Alignment.bottomCenter, colors: [ Colors.transparent, - AppColors.background.withOpacity(0.2), - AppColors.background.withOpacity(0.9), - AppColors.background, + AppColors.baseLevel0.withOpacity(0.4), + AppColors.baseLevel0.withOpacity(0.9), + AppColors.baseLevel0, ], stops: const [0.0, 0.5, 0.8, 1.0], ), @@ -167,7 +170,7 @@ class _HeroCarouselState extends State { end: Alignment.centerLeft, colors: [ Colors.transparent, - AppColors.background.withOpacity(0.8), + AppColors.baseLevel0.withOpacity(0.8), ], ), ), @@ -180,25 +183,60 @@ class _HeroCarouselState extends State { mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Category Tag - Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.primary.withOpacity(0.5)), - ), - child: Text( - 'FEATURED', - style: GoogleFonts.inter( - color: AppColors.primary, - fontWeight: FontWeight.bold, - fontSize: 12, - letterSpacing: 1.5, + // Badges row + if (item.badges != null && item.badges!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Wrap( + spacing: 8, + children: item.badges!.map((badge) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + ), + ), + child: Text( + badge.toUpperCase(), + style: GoogleFonts.inter( + color: AppColors.primary, + fontWeight: FontWeight.w600, + fontSize: 11, + letterSpacing: 0.5, + ), + ), + ); + }).toList(), + ), + ) + else + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + ), + ), + child: Text( + 'FEATURED', + style: GoogleFonts.inter( + color: AppColors.primary, + fontWeight: FontWeight.w700, + fontSize: 12, + letterSpacing: 1.5, + ), ), ), - ), + const SizedBox(height: 16), // Title @@ -206,11 +244,12 @@ class _HeroCarouselState extends State { width: 600, child: Text( item.title, - style: GoogleFonts.outfit( + style: GoogleFonts.spaceGrotesk( fontSize: 48, - fontWeight: FontWeight.bold, - color: Colors.white, + fontWeight: FontWeight.w700, + color: AppColors.onSurface, height: 1.1, + letterSpacing: -0.02, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -221,8 +260,9 @@ class _HeroCarouselState extends State { Text( item.subtitle!, style: GoogleFonts.inter( - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, fontSize: 18, + height: 1.5, ), ), ], @@ -245,7 +285,7 @@ class _HeroCarouselState extends State { borderRadius: BorderRadius.circular(50), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.4), + color: AppColors.primaryContainer.withOpacity(0.4), blurRadius: 20, offset: const Offset(0, 5), ), @@ -256,13 +296,13 @@ class _HeroCarouselState extends State { children: [ const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onSurface, ), const SizedBox(width: 8), Text( 'WATCH NOW', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.w600, fontSize: 16, ), @@ -273,9 +313,9 @@ class _HeroCarouselState extends State { ), const SizedBox(width: 20), TvFocusableCard( - onTap: item.onTap, // Add to favorites or details + onTap: item.onTap, borderRadius: 50, - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 50, padding: const EdgeInsets.symmetric( horizontal: 24, @@ -284,12 +324,15 @@ class _HeroCarouselState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.info_outline, color: Colors.white), + const Icon( + Icons.info_outline, + color: AppColors.onSurface, + ), const SizedBox(width: 8), Text( 'DETAILS', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.w600, fontSize: 16, ), @@ -300,7 +343,7 @@ class _HeroCarouselState extends State { ), ], ), - const SizedBox(height: 48), // Bottom padding + const SizedBox(height: 48), ], ), ), diff --git a/lib/core/widgets/themed_loading_screen.dart b/lib/core/widgets/themed_loading_screen.dart index 8665788..79e3789 100644 --- a/lib/core/widgets/themed_loading_screen.dart +++ b/lib/core/widgets/themed_loading_screen.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../theme/app_colors.dart'; +/// Stitch-themed loading screen with dark gradient and blue accent. class ThemedLoading extends StatelessWidget { const ThemedLoading({super.key}); @@ -12,7 +15,7 @@ class ThemedLoading extends StatelessWidget { end: Alignment.bottomRight, colors: [ Color(0xFF0F1014), // Deep Space Dark - Color(0xFF181920), // Soft Eerie Black + Color(0xFF121317), // Stitch background ], ), ), @@ -21,16 +24,16 @@ class ThemedLoading extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator( - color: Color(0xFF007AFF), // Electric Blue (AppColors.primary) + color: Color(0xFF4b8eff), // AppColors.primaryContainer strokeWidth: 3, ), const SizedBox(height: 16), Text( 'Loading...', - style: TextStyle( - fontFamily: 'Outfit', // Match AppTheme - color: Colors.white.withOpacity(0.7), + style: GoogleFonts.inter( + color: AppColors.onSurfaceVariant, fontSize: 14, + fontWeight: FontWeight.w500, ), ), ], diff --git a/lib/core/widgets/tv_focusable_card.dart b/lib/core/widgets/tv_focusable_card.dart index 5af0447..da85c82 100644 --- a/lib/core/widgets/tv_focusable_card.dart +++ b/lib/core/widgets/tv_focusable_card.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import '../theme/app_colors.dart'; +/// TV focusable card with Stitch glow effect. +/// +/// On focus/hover: blue glow border, subtle scale, diffused shadow. class TvFocusableCard extends StatefulWidget { final Widget child; final VoidCallback? onTap; @@ -16,7 +19,7 @@ class TvFocusableCard extends StatefulWidget { required this.child, this.onTap, this.onFocus, - this.scaleFactor = 1.05, + this.scaleFactor = 1.03, this.animationDuration = const Duration(milliseconds: 200), this.focusColor, this.autofocus = false, @@ -32,6 +35,7 @@ class _TvFocusableCardState extends State late AnimationController _controller; late Animation _scaleAnimation; bool _isFocused = false; + bool _isHovered = false; @override void initState() { @@ -64,8 +68,6 @@ class _TvFocusableCardState extends State } } - bool _isHovered = false; - void _handleHover(bool isHovered) { setState(() { _isHovered = isHovered; @@ -80,6 +82,7 @@ class _TvFocusableCardState extends State @override Widget build(BuildContext context) { final isActive = _isFocused || _isHovered; + final focusColor = widget.focusColor ?? AppColors.primaryContainer; return MouseRegion( onEnter: (_) => _handleHover(true), @@ -109,10 +112,13 @@ class _TvFocusableCardState extends State borderRadius: BorderRadius.circular(widget.borderRadius), border: isActive ? Border.all( - color: widget.focusColor ?? AppColors.focusBorder, - width: 2, // Slightly thinner for cleaner look + color: focusColor.withOpacity(0.5), + width: 2, ) - : Border.all(color: Colors.transparent, width: 2), + : Border.all( + color: AppColors.glassLevel1Border, + width: 1, + ), boxShadow: isActive ? [ BoxShadow( @@ -122,15 +128,22 @@ class _TvFocusableCardState extends State spreadRadius: 2, ), BoxShadow( - color: (widget.focusColor ?? AppColors.focusBorder) - .withOpacity(0.3), - blurRadius: 15, + color: focusColor.withOpacity(0.3), + blurRadius: 20, spreadRadius: 1, ), + BoxShadow( + color: focusColor.withOpacity(0.1), + blurRadius: 40, + spreadRadius: 4, + ), ] : [], ), - child: widget.child, + child: ClipRRect( + borderRadius: BorderRadius.circular(widget.borderRadius), + child: widget.child, + ), ), ), ), diff --git a/lib/features/admin/screens/admin_panel.dart b/lib/features/admin/screens/admin_panel.dart index da7418a..f7e4ced 100644 --- a/lib/features/admin/screens/admin_panel.dart +++ b/lib/features/admin/screens/admin_panel.dart @@ -26,14 +26,13 @@ class AdminPanel extends StatelessWidget { extendBodyBehindAppBar: true, body: Container( decoration: const BoxDecoration( - gradient: RadialGradient( - center: Alignment.center, - radius: 1.5, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, colors: [ - Color(0xFF2C2C2E), // Dark Grey (Apple TV Surface) - Color(0xFF000000), // Pure Black + Color(0xFF0F1014), + Color(0xFF121317), ], - stops: [0.0, 1.0], ), ), child: const AdminContent(), @@ -81,13 +80,13 @@ class _AdminContentState extends ConsumerState padding: const EdgeInsets.all(8), showBorder: false, onTap: () => context.go('/playlists'), - child: const Icon(Icons.arrow_back, color: Colors.white70), + child: const Icon(Icons.arrow_back, color: AppColors.onSurfaceVariant), ), const SizedBox(width: 16), Text( 'Administration', style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.bold, ), ), @@ -140,7 +139,7 @@ class _AdminContentState extends ConsumerState duration: AppTheme.durationFast, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( - color: isSelected ? AppColors.focusColor : Colors.transparent, + color: isSelected ? AppColors.primaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(50), ), child: Row( @@ -148,7 +147,7 @@ class _AdminContentState extends ConsumerState Icon( icon, size: 18, - color: isSelected ? Colors.black : Colors.white70, + color: isSelected ? AppColors.onPrimaryContainer : AppColors.onSurfaceVariant, ), const SizedBox(width: 8), Text( @@ -156,7 +155,7 @@ class _AdminContentState extends ConsumerState style: GoogleFonts.inter( fontWeight: FontWeight.w600, fontSize: 14, - color: isSelected ? Colors.black : Colors.white70, + color: isSelected ? AppColors.onPrimaryContainer : AppColors.onSurfaceVariant, ), ), ], @@ -188,7 +187,7 @@ class _PlaylistsTab extends ConsumerWidget { style: Theme.of(context) .textTheme .bodyLarge - ?.copyWith(color: Colors.white70), + ?.copyWith(color: AppColors.onSurfaceVariant), ), GradientButton( label: 'Add Playlist', @@ -216,7 +215,7 @@ class _PlaylistsTab extends ConsumerWidget { Icon( Icons.playlist_remove, size: 64, - color: AppColors.textSecondary.withOpacity(0.5), + color: AppColors.onSurfaceVariant.withOpacity(0.5), ), const SizedBox(height: 16), Text( @@ -253,7 +252,7 @@ class _PlaylistsTab extends ConsumerWidget { ), child: const Icon( Icons.playlist_play, - color: AppColors.textPrimary, + color: AppColors.onSurface, ), ), const SizedBox(width: 16), @@ -266,7 +265,7 @@ class _PlaylistsTab extends ConsumerWidget { style: Theme.of(context) .textTheme .titleMedium - ?.copyWith(color: Colors.white), + ?.copyWith(color: AppColors.onSurface), ), const SizedBox(height: 4), Text( @@ -274,7 +273,7 @@ class _PlaylistsTab extends ConsumerWidget { style: Theme.of(context) .textTheme .bodyMedium - ?.copyWith(color: Colors.white60), + ?.copyWith(color: AppColors.onSurfaceVariant), ), ], ), @@ -283,7 +282,7 @@ class _PlaylistsTab extends ConsumerWidget { children: [ IconButton( icon: const Icon(Icons.edit_outlined, size: 20), - color: Colors.white70, + color: AppColors.onSurfaceVariant, onPressed: () => _showPlaylistDialog(context, ref, playlist), tooltip: 'Edit', @@ -463,7 +462,7 @@ class _UsersTab extends ConsumerWidget { style: Theme.of(context) .textTheme .bodyLarge - ?.copyWith(color: Colors.white70), + ?.copyWith(color: AppColors.onSurfaceVariant), ), GradientButton( label: 'Create User', @@ -491,7 +490,7 @@ class _UsersTab extends ConsumerWidget { Icon( Icons.people_outline, size: 64, - color: AppColors.textSecondary.withOpacity(0.5), + color: AppColors.onSurfaceVariant.withOpacity(0.5), ), const SizedBox(height: 16), Text( @@ -519,7 +518,7 @@ class _UsersTab extends ConsumerWidget { height: 48, decoration: BoxDecoration( color: user.isAdmin - ? AppColors.focusColor + ? AppColors.primaryContainer : AppColors.surface, shape: BoxShape.circle, ), @@ -528,8 +527,8 @@ class _UsersTab extends ConsumerWidget { ? Icons.admin_panel_settings : Icons.person, color: user.isAdmin - ? Colors.black - : AppColors.textPrimary, + ? AppColors.onPrimaryContainer + : AppColors.onSurface, size: 24, ), ), @@ -545,7 +544,7 @@ class _UsersTab extends ConsumerWidget { style: Theme.of(context) .textTheme .titleMedium - ?.copyWith(color: Colors.white), + ?.copyWith(color: AppColors.onSurface), ), if (user.isAdmin) ...[ const SizedBox(width: 8), @@ -555,16 +554,16 @@ class _UsersTab extends ConsumerWidget { vertical: 2, ), decoration: BoxDecoration( - color: AppColors.focusColor + color: AppColors.primaryContainer .withOpacity(0.2), borderRadius: BorderRadius.circular(50), ), - child: const Text( + child: Text( 'ADMIN', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, - color: AppColors.focusColor, + color: AppColors.primaryContainer, ), ), ), @@ -577,7 +576,7 @@ class _UsersTab extends ConsumerWidget { style: Theme.of(context) .textTheme .bodyMedium - ?.copyWith(color: Colors.white60), + ?.copyWith(color: AppColors.onSurfaceVariant), ), ], ), @@ -585,7 +584,7 @@ class _UsersTab extends ConsumerWidget { PopupMenuButton( icon: const Icon( Icons.more_vert, - color: Colors.white70, + color: AppColors.onSurfaceVariant, ), color: AppColors.surface, shape: RoundedRectangleBorder( diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index 3a8334e..2d44539 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -51,22 +51,22 @@ class _LoginScreenState extends ConsumerState { body: Stack( fit: StackFit.expand, children: [ - // 1. Cinematic Background (Abstract Gradient) + // 1. Cinematic Background Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFF0D0D0D), // Almost Black - Color(0xFF1A1A1A), // Dark Grey - Color(0xFF0F0F0F), // Deep Dark + Color(0xFF0F1014), + Color(0xFF121317), + Color(0xFF0D0E12), ], ), ), ), - // 2. Ambient Glows (Apple TV Style) + // 2. Ambient Glows (Stitch style — blue diffuse) Positioned( top: -size.height * 0.2, right: -size.width * 0.1, @@ -77,7 +77,7 @@ class _LoginScreenState extends ConsumerState { shape: BoxShape.circle, gradient: RadialGradient( colors: [ - Colors.white.withOpacity(0.05), + AppColors.primary.withOpacity(0.06), Colors.transparent, ], radius: 0.5, @@ -95,7 +95,7 @@ class _LoginScreenState extends ConsumerState { shape: BoxShape.circle, gradient: RadialGradient( colors: [ - Colors.blue.withOpacity(0.03), // Subtle blue hint + AppColors.primaryContainer.withOpacity(0.04), Colors.transparent, ], radius: 0.5, @@ -118,15 +118,11 @@ class _LoginScreenState extends ConsumerState { width: 120, height: 120, decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF2C2C2E), Color(0xFF000000)], - ), + gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(32), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.5), + color: AppColors.glowPrimary(0.3), blurRadius: 30, offset: const Offset(0, 15), ), @@ -134,7 +130,7 @@ class _LoginScreenState extends ConsumerState { ), child: const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onSurface, size: 64, ), ), @@ -144,10 +140,10 @@ class _LoginScreenState extends ConsumerState { // Title Text( 'XtremFlow', - style: GoogleFonts.inter( + style: GoogleFonts.spaceGrotesk( fontSize: 42, fontWeight: FontWeight.w700, - color: AppColors.textPrimary, + color: AppColors.onSurface, letterSpacing: -1.0, ), ), @@ -156,18 +152,17 @@ class _LoginScreenState extends ConsumerState { 'Sign in to sync your playlists', style: GoogleFonts.inter( fontSize: 18, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, fontWeight: FontWeight.w400, ), ), const SizedBox(height: 48), // Login Form Container - GlassContainer( + GlassContainer.floating( width: 500, - padding: const EdgeInsets.all(48), // Spacious padding + padding: const EdgeInsets.all(48), borderRadius: 24, - opacity: 0.7, child: Form( key: _formKey, child: Column( @@ -196,7 +191,7 @@ class _LoginScreenState extends ConsumerState { _obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, ), onPressed: () { setState(() { @@ -229,7 +224,7 @@ class _LoginScreenState extends ConsumerState { Expanded( child: Text( authState.errorMessage!, - style: const TextStyle( + style: GoogleFonts.inter( color: AppColors.error, fontSize: 14, fontWeight: FontWeight.w500, @@ -247,14 +242,20 @@ class _LoginScreenState extends ConsumerState { TvFocusableCard( onTap: _handleLogin, scaleFactor: 1.03, - focusColor: Colors.white, borderRadius: 16, child: Container( height: 60, alignment: Alignment.center, decoration: BoxDecoration( - color: Colors.white, + gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.glowPrimary(0.3), + blurRadius: 16, + offset: const Offset(0, 6), + ), + ], ), child: authState.isLoading ? const SizedBox( @@ -262,13 +263,13 @@ class _LoginScreenState extends ConsumerState { height: 24, child: CircularProgressIndicator( strokeWidth: 2.5, - color: Colors.black, + color: AppColors.onSurface, ), ) : Text( 'Sign In', style: GoogleFonts.inter( - color: Colors.black, + color: AppColors.onSurface, fontSize: 18, fontWeight: FontWeight.w600, ), @@ -285,7 +286,7 @@ class _LoginScreenState extends ConsumerState { 'Default: admin / admin', style: GoogleFonts.inter( fontSize: 14, - color: AppColors.textTertiary, + color: AppColors.outline, ), ), ], @@ -311,28 +312,34 @@ class _LoginScreenState extends ConsumerState { return TextFormField( controller: controller, obscureText: obscureText, - style: GoogleFonts.inter(fontSize: 18, color: AppColors.textPrimary), + style: GoogleFonts.inter(fontSize: 18, color: AppColors.onSurface), autofocus: autofocus, textInputAction: nextFocus ? TextInputAction.next : TextInputAction.done, onFieldSubmitted: isLast ? (_) => onSubmitted?.call() : null, decoration: InputDecoration( labelText: label, - labelStyle: GoogleFonts.inter(color: AppColors.textSecondary), - prefixIcon: Icon(icon, color: AppColors.textSecondary), + labelStyle: GoogleFonts.inter(color: AppColors.onSurfaceVariant), + prefixIcon: Icon(icon, color: AppColors.onSurfaceVariant), suffixIcon: suffix, filled: true, - fillColor: AppColors.surfaceVariant.withOpacity(0.5), + fillColor: AppColors.surfaceContainerLow, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + borderSide: const BorderSide( + color: AppColors.outlineVariant, + width: 1, + ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.focusColor, width: 2), + borderSide: const BorderSide( + color: AppColors.primaryContainer, + width: 2, + ), ), contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index f715be8..706ca55 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -49,15 +49,15 @@ class _DashboardScreenState extends ConsumerState { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Color(0xFF0F1014), // Deep Space Blue-Grey - Color(0xFF181920), // Darker shade + Color(0xFF0F1014), + Color(0xFF121317), ], ), ), ), ), - // Ambient Glow (Subtle accent) + // Ambient Glow — primary accent diffuse Positioned( top: -100, left: -100, @@ -68,7 +68,7 @@ class _DashboardScreenState extends ConsumerState { shape: BoxShape.circle, gradient: RadialGradient( colors: [ - AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.06), Colors.transparent, ], ), @@ -91,19 +91,15 @@ class _DashboardScreenState extends ConsumerState { ), ), - // 3. Vertical Glass Sidebar + // 3. Vertical Glass Sidebar (Stitch Level 2) Positioned( left: 24, top: 24, bottom: 24, width: 80, - child: GlassContainer( + child: GlassContainer.floating( borderRadius: 24, - opacity: 0.1, - // blur is hardcoded in GlassContainer (15), so we don't pass it - // border is bool, borderColor allows customization - border: true, - borderColor: Colors.white.withOpacity(0.1), + padding: EdgeInsets.zero, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -118,7 +114,7 @@ class _DashboardScreenState extends ConsumerState { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.4), + color: AppColors.glowPrimary(0.4), blurRadius: 10, offset: const Offset(0, 4), ), @@ -126,7 +122,7 @@ class _DashboardScreenState extends ConsumerState { ), child: const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onSurface, size: 28, ), ), @@ -143,27 +139,37 @@ class _DashboardScreenState extends ConsumerState { onTap: () => setState(() => _selectedIndex = index), scaleFactor: 1.2, borderRadius: 16, - focusColor: AppColors.primary, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 48, height: 48, decoration: BoxDecoration( color: isSelected - ? AppColors.primary.withOpacity(0.2) + ? AppColors.primary.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(16), border: isSelected ? Border.all( - color: AppColors.primary.withOpacity(0.5), + color: AppColors.primaryContainer + .withOpacity(0.5), + width: 2, ) : null, + boxShadow: isSelected + ? [ + BoxShadow( + color: AppColors.glowPrimary(0.2), + blurRadius: 12, + spreadRadius: 1, + ), + ] + : null, ), child: Icon( _icons[index], color: isSelected - ? Colors.white - : AppColors.textSecondary, + ? AppColors.onSurface + : AppColors.onSurfaceVariant, size: 24, ), ), @@ -173,7 +179,7 @@ class _DashboardScreenState extends ConsumerState { const Spacer(), - // Settings / Profile + // Profile Padding( padding: const EdgeInsets.only(bottom: 24), child: TvFocusableCard( @@ -182,10 +188,11 @@ class _DashboardScreenState extends ConsumerState { scaleFactor: 1.1, child: CircleAvatar( radius: 20, - backgroundColor: Colors.white.withOpacity(0.1), - child: const Icon( + backgroundColor: AppColors.surfaceContainerHigh + .withOpacity(0.5), + child: Icon( Icons.person, - color: Colors.white70, + color: AppColors.onSurfaceVariant, size: 20, ), ), diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index e76ba4b..bb11d7d 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -379,7 +379,7 @@ class _PlayerScreenState extends ConsumerState { // LITE PLAYER MODE for Live TV - Simplified UI (no BackdropFilter) if (isLiveTV) { return Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.background, body: Stack( fit: StackFit.expand, children: [ @@ -420,7 +420,7 @@ class _PlayerScreenState extends ConsumerState { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black.withOpacity(0.8), + AppColors.background.withOpacity(0.8), Colors.transparent, ], ), @@ -450,8 +450,8 @@ class _PlayerScreenState extends ConsumerState { 'Unknown') .toString() : widget.title, - style: GoogleFonts.outfit( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 18, fontWeight: FontWeight.w600, ), @@ -494,7 +494,7 @@ class _PlayerScreenState extends ConsumerState { borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: AppColors.background.withOpacity(0.3), blurRadius: 10, spreadRadius: 2, ), @@ -510,7 +510,7 @@ class _PlayerScreenState extends ConsumerState { // EPG Info (top in mobile) _buildInlineEpg(), const SizedBox(height: 12), - const Divider(color: Colors.white10), + const Divider(color: AppColors.outlineVariant), const SizedBox(height: 8), // Control Buttons Row( @@ -554,7 +554,7 @@ class _PlayerScreenState extends ConsumerState { // STANDARD PLAYER MODE (VOD/Series) return Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.background, body: Stack( fit: StackFit.expand, children: [ @@ -585,7 +585,7 @@ class _PlayerScreenState extends ConsumerState { Positioned.fill( child: PointerInterceptor( child: Container( - color: Colors.black.withOpacity(0.4), + color: AppColors.background.withOpacity(0.4), child: Stack( children: [ // Top Bar @@ -593,10 +593,9 @@ class _PlayerScreenState extends ConsumerState { top: 24, left: 24, right: 24, - child: GlassContainer( + child: GlassContainer.glass( height: 72, borderRadius: 24, - opacity: 0.1, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ @@ -608,10 +607,10 @@ class _PlayerScreenState extends ConsumerState { Expanded( child: Text( widget.title, - style: GoogleFonts.outfit( + style: GoogleFonts.inter( fontSize: 20, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -633,11 +632,8 @@ class _PlayerScreenState extends ConsumerState { bottom: 40, left: 40, right: 40, - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 24, - opacity: 0.2, // Slightly more opaque for controls - border: true, - borderColor: Colors.white.withOpacity(0.1), padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, @@ -652,7 +648,7 @@ class _PlayerScreenState extends ConsumerState { ), ), style: GoogleFonts.inter( - color: Colors.white70, + color: AppColors.onSurfaceVariant, ), ), const SizedBox(width: 16), @@ -663,7 +659,7 @@ class _PlayerScreenState extends ConsumerState { activeTrackColor: AppColors.primary, inactiveTrackColor: Colors.white.withOpacity(0.2), - thumbColor: Colors.white, + thumbColor: AppColors.onSurface, thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 8, ), @@ -704,7 +700,7 @@ class _PlayerScreenState extends ConsumerState { ), ), style: GoogleFonts.inter( - color: Colors.white70, + color: AppColors.onSurfaceVariant, ), ), ], @@ -800,7 +796,7 @@ class _PlayerScreenState extends ConsumerState { ? null : Border.all(color: Colors.white.withOpacity(0.1)), ), - child: Icon(icon, color: Colors.white, size: iconSize), + child: Icon(icon, color: AppColors.onSurface, size: iconSize), ), ), ); @@ -891,7 +887,7 @@ class _PlayerScreenState extends ConsumerState { ), child: Icon( icon, - color: highlighted ? AppColors.primary : Colors.white, + color: highlighted ? AppColors.primary : AppColors.onSurface, size: iconSize, ), ), @@ -923,13 +919,13 @@ class _PlayerScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.red, + color: AppColors.error, borderRadius: BorderRadius.circular(4), ), child: const Text( 'LIVE', style: TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 11, fontWeight: FontWeight.bold, ), @@ -941,8 +937,8 @@ class _PlayerScreenState extends ConsumerState { widget.channels != null ? widget.channels![_currentIndex].name : widget.title, - style: GoogleFonts.outfit( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), @@ -971,13 +967,13 @@ class _PlayerScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.red, + color: AppColors.error, borderRadius: BorderRadius.circular(4), ), child: const Text( 'LIVE', style: TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 11, fontWeight: FontWeight.bold, ), @@ -991,8 +987,8 @@ class _PlayerScreenState extends ConsumerState { children: [ Text( currentProgram.title, - style: GoogleFonts.outfit( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 15, fontWeight: FontWeight.w600, ), @@ -1003,7 +999,7 @@ class _PlayerScreenState extends ConsumerState { Text( currentProgram.description, style: GoogleFonts.inter( - color: Colors.white60, + color: AppColors.onSurfaceVariant, fontSize: 12, ), maxLines: 1, @@ -1020,13 +1016,13 @@ class _PlayerScreenState extends ConsumerState { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.red, + color: AppColors.error, borderRadius: BorderRadius.circular(4), ), child: const Text( 'LIVE', style: TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 11, fontWeight: FontWeight.bold, ), @@ -1037,8 +1033,8 @@ class _PlayerScreenState extends ConsumerState { widget.channels != null ? widget.channels![_currentIndex].name : widget.title, - style: GoogleFonts.outfit( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), @@ -1049,8 +1045,8 @@ class _PlayerScreenState extends ConsumerState { widget.channels != null ? widget.channels![_currentIndex].name : widget.title, - style: GoogleFonts.outfit( - color: Colors.white, + style: GoogleFonts.inter( + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), diff --git a/lib/features/iptv/screens/playlist_selection_screen.dart b/lib/features/iptv/screens/playlist_selection_screen.dart index 1921716..ee00a21 100644 --- a/lib/features/iptv/screens/playlist_selection_screen.dart +++ b/lib/features/iptv/screens/playlist_selection_screen.dart @@ -27,9 +27,9 @@ class PlaylistSelectionScreen extends ConsumerWidget { appBar: AppBar( title: Text( 'Select Playlist', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), backgroundColor: Colors.transparent, @@ -38,12 +38,12 @@ class PlaylistSelectionScreen extends ConsumerWidget { if (currentUser?.isAdmin ?? false) IconButton( icon: - const Icon(Icons.admin_panel_settings, color: Colors.white70), + const Icon(Icons.admin_panel_settings, color: AppColors.onSurfaceVariant), onPressed: () => context.go('/admin'), tooltip: 'Admin Panel', ), IconButton( - icon: const Icon(Icons.logout, color: Colors.white70), + icon: const Icon(Icons.logout, color: AppColors.onSurfaceVariant), onPressed: () async { await ref.read(authProvider.notifier).logout(); if (context.mounted) { @@ -78,9 +78,9 @@ class PlaylistSelectionScreen extends ConsumerWidget { const SizedBox(height: 16), Text( 'Error loading playlists', - style: GoogleFonts.outfit( + style: GoogleFonts.inter( fontSize: 18, - color: Colors.white70, + color: AppColors.onSurfaceVariant, ), ), const SizedBox(height: 8), @@ -88,7 +88,7 @@ class PlaylistSelectionScreen extends ConsumerWidget { onPressed: () => ref.refresh(playlistsProvider), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, - foregroundColor: Colors.white, + foregroundColor: AppColors.onSurface, ), child: Text('Retry', style: GoogleFonts.inter()), ), @@ -104,14 +104,14 @@ class PlaylistSelectionScreen extends ConsumerWidget { const Icon( Icons.playlist_remove, size: 64, - color: Colors.white24, + color: AppColors.outlineVariant, ), const SizedBox(height: 16), Text( 'No playlists available', - style: GoogleFonts.outfit( + style: GoogleFonts.inter( fontSize: 18, - color: Colors.white60, + color: AppColors.onSurfaceVariant, ), ), const SizedBox(height: 8), @@ -121,7 +121,7 @@ class PlaylistSelectionScreen extends ConsumerWidget { : 'Contact administrator', style: GoogleFonts.inter( fontSize: 14, - color: Colors.white38, + color: AppColors.outline, ), ), ], @@ -246,16 +246,16 @@ class _PlaylistCardState extends State<_PlaylistCard> { child: Icon( Icons.playlist_play_rounded, size: 32, - color: _isHovered ? Colors.white : Colors.white70, + color: _isHovered ? AppColors.onSurface : AppColors.onSurfaceVariant, ), ), const SizedBox(height: 20), Text( widget.playlist.name, - style: GoogleFonts.outfit( + style: GoogleFonts.inter( fontSize: 22, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, height: 1.2, ), maxLines: 2, @@ -266,7 +266,7 @@ class _PlaylistCardState extends State<_PlaylistCard> { Uri.tryParse(widget.playlist.dns)?.host ?? widget.playlist.dns, style: GoogleFonts.inter( fontSize: 13, - color: Colors.white38, + color: AppColors.outline, letterSpacing: 0.5, ), maxLines: 1, diff --git a/lib/features/iptv/screens/series_detail_screen.dart b/lib/features/iptv/screens/series_detail_screen.dart index 963397c..ad1521c 100644 --- a/lib/features/iptv/screens/series_detail_screen.dart +++ b/lib/features/iptv/screens/series_detail_screen.dart @@ -6,6 +6,7 @@ import '../../../core/models/playlist_config.dart'; import '../models/xtream_models.dart'; import '../providers/xtream_provider.dart'; import '../providers/watch_history_provider.dart'; +import '../../../core/theme/app_colors.dart'; import 'player_screen.dart'; /// Series detail screen showing seasons and episodes @@ -75,13 +76,13 @@ class _SeriesDetailScreenState extends ConsumerState { return Scaffold( appBar: AppBar( title: Text(widget.series.name), - backgroundColor: Colors.black87, + backgroundColor: AppColors.background, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, color: AppColors.onSurface), onPressed: () => Navigator.pop(context), ), ), - backgroundColor: Colors.grey.shade900, + backgroundColor: AppColors.surfaceContainerLow, body: _isLoading ? const Center(child: CircularProgressIndicator()) : _error != null @@ -89,9 +90,9 @@ class _SeriesDetailScreenState extends ConsumerState { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, size: 48, color: Colors.red), + const Icon(Icons.error_outline, size: 48, color: AppColors.error), const SizedBox(height: 16), - Text('Error: $_error', style: const TextStyle(color: Colors.white70)), + Text('Error: $_error', style: const TextStyle(color: AppColors.onSurfaceVariant)), const SizedBox(height: 16), ElevatedButton( onPressed: _loadSeriesInfo, @@ -132,8 +133,8 @@ class _SeriesDetailScreenState extends ConsumerState { errorWidget: (_, __, ___) => Container( width: 248, height: 350, - color: Colors.grey.shade800, - child: const Icon(Icons.tv, size: 64, color: Colors.white54), + color: AppColors.surfaceContainer, + child: const Icon(Icons.tv, size: 64, color: AppColors.onSurfaceVariant), ), ), ), @@ -142,10 +143,10 @@ class _SeriesDetailScreenState extends ConsumerState { // Title Text( _seriesInfo!.name, - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), const SizedBox(height: 8), @@ -158,7 +159,7 @@ class _SeriesDetailScreenState extends ConsumerState { const SizedBox(width: 4), Text( _formatRating(_seriesInfo!.rating!)!, - style: GoogleFonts.roboto(fontSize: 13, color: Colors.white70), + style: GoogleFonts.roboto(fontSize: 13, color: AppColors.onSurfaceVariant), ), ], ), @@ -168,7 +169,7 @@ class _SeriesDetailScreenState extends ConsumerState { if (_seriesInfo!.plot != null && _seriesInfo!.plot!.isNotEmpty) Text( _seriesInfo!.plot!, - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white60), + style: GoogleFonts.roboto(fontSize: 12, color: AppColors.onSurfaceVariant), maxLines: 5, overflow: TextOverflow.ellipsis, ), @@ -177,10 +178,10 @@ class _SeriesDetailScreenState extends ConsumerState { // Season selector Text( 'Seasons', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), const SizedBox(height: 8), @@ -197,10 +198,10 @@ class _SeriesDetailScreenState extends ConsumerState { setState(() => _selectedSeason = seasonNum); } }, - selectedColor: Colors.blue.shade700, - backgroundColor: Colors.grey.shade800, - labelStyle: GoogleFonts.roboto( - color: isSelected ? Colors.white : Colors.white70, + selectedColor: AppColors.primaryContainer, + backgroundColor: AppColors.surfaceContainer, + labelStyle: GoogleFonts.inter( + color: isSelected ? AppColors.onPrimary : AppColors.onSurfaceVariant, fontSize: 12, ), ); @@ -212,7 +213,7 @@ class _SeriesDetailScreenState extends ConsumerState { ), // Divider - Container(width: 1, color: Colors.grey.shade700), + Container(width: 1, color: AppColors.surfaceContainerHigh), // Right panel: Episodes list Expanded( @@ -223,10 +224,10 @@ class _SeriesDetailScreenState extends ConsumerState { padding: const EdgeInsets.all(16), child: Text( 'Season $_selectedSeason - ${currentEpisodes.length} Episodes', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -235,7 +236,7 @@ class _SeriesDetailScreenState extends ConsumerState { ? Center( child: Text( 'No episodes found', - style: GoogleFonts.roboto(color: Colors.white54), + style: GoogleFonts.roboto(color: AppColors.onSurfaceVariant), ), ) : ListView.builder( @@ -264,7 +265,7 @@ class _SeriesDetailScreenState extends ConsumerState { final isWatched = watchHistory.isEpisodeWatched(episodeKey); return Card( - color: Colors.grey.shade800, + color: AppColors.surfaceContainer, margin: const EdgeInsets.only(bottom: 8), child: InkWell( onTap: () async { @@ -314,18 +315,18 @@ class _SeriesDetailScreenState extends ConsumerState { width: 40, height: 40, decoration: BoxDecoration( - color: isWatched ? Colors.green : Colors.blue.shade700, + color: isWatched ? AppColors.success : AppColors.primaryContainer, borderRadius: BorderRadius.circular(8), ), child: Center( child: isWatched - ? const Icon(Icons.check, color: Colors.white, size: 20) + ? const Icon(Icons.check, color: AppColors.onSurface, size: 20) : Text( '${episode.episodeNum}', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -342,7 +343,7 @@ class _SeriesDetailScreenState extends ConsumerState { style: GoogleFonts.roboto( fontSize: 14, fontWeight: FontWeight.w500, - color: isWatched ? Colors.white54 : Colors.white, + color: isWatched ? AppColors.onSurfaceVariant : AppColors.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -352,7 +353,7 @@ class _SeriesDetailScreenState extends ConsumerState { _formatDuration(episode.durationSecs!), style: GoogleFonts.roboto( fontSize: 12, - color: Colors.white54, + color: AppColors.onSurfaceVariant, ), ), ], @@ -362,7 +363,7 @@ class _SeriesDetailScreenState extends ConsumerState { // Play icon Icon( isWatched ? Icons.replay : Icons.play_circle_outline, - color: Colors.white70, + color: AppColors.onSurfaceVariant, size: 28, ), ], diff --git a/lib/features/iptv/widgets/channel_card.dart b/lib/features/iptv/widgets/channel_card.dart index b0d4d8b..9c7d90f 100644 --- a/lib/features/iptv/widgets/channel_card.dart +++ b/lib/features/iptv/widgets/channel_card.dart @@ -134,14 +134,14 @@ class _ChannelCardState extends ConsumerState color: AppColors.surface, // Fallback color // Thick white border on focus (tvOS style) border: Border.all( - color: _isHovered ? AppColors.focusColor : Colors.transparent, + color: _isHovered ? AppColors.primaryContainer : Colors.transparent, width: _isHovered ? 3 : 0, ), // Deep shadow on focus boxShadow: _isHovered ? [ BoxShadow( - color: Colors.black.withOpacity(0.5), + color: AppColors.background.withOpacity(0.5), blurRadius: 30, offset: const Offset(0, 15), spreadRadius: 2, @@ -149,7 +149,7 @@ class _ChannelCardState extends ConsumerState ] : [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: AppColors.background.withOpacity(0.2), blurRadius: 4, offset: const Offset(0, 2), ), @@ -169,7 +169,7 @@ class _ChannelCardState extends ConsumerState child: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [Colors.transparent, Colors.black.withOpacity(0.4)], + colors: [Colors.transparent, AppColors.background.withOpacity(0.4)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), @@ -188,7 +188,7 @@ class _ChannelCardState extends ConsumerState color: AppColors.live, borderRadius: BorderRadius.circular(4), ), - child: const Text('LIVE', style: TextStyle(color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold)), + child: const Text('LIVE', style: TextStyle(color: AppColors.onSurface, fontSize: 9, fontWeight: FontWeight.bold)), ), ), @@ -201,7 +201,7 @@ class _ChannelCardState extends ConsumerState onTap: () => ref.read(favoritesProvider.notifier).toggleFavorite(widget.streamId), child: Icon( isFavorite ? Icons.favorite : Icons.favorite_border, - color: isFavorite ? AppColors.live : Colors.white.withOpacity(0.7), + color: isFavorite ? AppColors.live : AppColors.onSurface.withOpacity(0.7), size: 20, ), ), @@ -227,7 +227,7 @@ class _ChannelCardState extends ConsumerState maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: _isHovered ? AppColors.focusColor : AppColors.textSecondary, + color: _isHovered ? AppColors.primaryContainer : AppColors.onSurfaceVariant, fontWeight: _isHovered ? FontWeight.bold : FontWeight.w500, fontSize: 13, ), diff --git a/lib/features/iptv/widgets/clock_widget.dart b/lib/features/iptv/widgets/clock_widget.dart index e6fb9ed..540d049 100644 --- a/lib/features/iptv/widgets/clock_widget.dart +++ b/lib/features/iptv/widgets/clock_widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../../../core/theme/app_colors.dart'; import 'package:intl/intl.dart'; class ClockWidget extends StatefulWidget { @@ -45,10 +46,10 @@ class _ClockWidgetState extends State { return Text( timeString, style: widget.style ?? const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 16, fontWeight: FontWeight.bold, - shadows: [Shadow(color: Colors.black, blurRadius: 4)], + shadows: [Shadow(color: AppColors.background, blurRadius: 4)], ), ); } diff --git a/lib/features/iptv/widgets/epg_overlay.dart b/lib/features/iptv/widgets/epg_overlay.dart index f4536e5..513ed66 100644 --- a/lib/features/iptv/widgets/epg_overlay.dart +++ b/lib/features/iptv/widgets/epg_overlay.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/models/iptv_models.dart'; import '../providers/xtream_provider.dart'; +import '../../../core/theme/app_colors.dart'; /// Minimal EPG overlay - shows program title and info class EpgOverlay extends ConsumerWidget { @@ -60,13 +61,13 @@ class EpgOverlay extends ConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.red, + color: AppColors.error, borderRadius: BorderRadius.circular(4), ), child: const Text( 'LIVE', style: TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 12, // Larger fontWeight: FontWeight.bold, ), @@ -82,10 +83,10 @@ class EpgOverlay extends ConsumerWidget { Text( currentProgram.title, style: const TextStyle( - color: Colors.white, + color: AppColors.onSurface, fontSize: 18, // Much Larger fontWeight: FontWeight.bold, - shadows: [Shadow(color: Colors.black, blurRadius: 4)], + shadows: [Shadow(color: AppColors.background, blurRadius: 4)], ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -94,7 +95,7 @@ class EpgOverlay extends ConsumerWidget { Text( currentProgram.description, style: const TextStyle( - color: Colors.white70, + color: AppColors.onSurfaceVariant, fontSize: 13, // Larger desc ), maxLines: 1, diff --git a/lib/features/iptv/widgets/epg_widget.dart b/lib/features/iptv/widgets/epg_widget.dart index 7c0071f..ec66757 100644 --- a/lib/features/iptv/widgets/epg_widget.dart +++ b/lib/features/iptv/widgets/epg_widget.dart @@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import '../../../core/models/playlist_config.dart'; import '../providers/xtream_provider.dart'; import '../models/xtream_models.dart'; +import '../../../core/theme/app_colors.dart'; class EPGWidget extends ConsumerStatefulWidget { final String channelId; @@ -76,7 +77,7 @@ class _EPGWidgetState extends ConsumerState { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), + color: AppColors.background.withOpacity(0.6), borderRadius: BorderRadius.circular(4), ), child: Column( @@ -93,15 +94,15 @@ class _EPGWidgetState extends ConsumerState { vertical: 2, ), decoration: BoxDecoration( - color: Colors.red, + color: AppColors.error, borderRadius: BorderRadius.circular(3), ), child: Text( 'LIVE', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 9, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -112,7 +113,7 @@ class _EPGWidgetState extends ConsumerState { style: GoogleFonts.roboto( fontSize: 11, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -128,8 +129,8 @@ class _EPGWidgetState extends ConsumerState { borderRadius: BorderRadius.circular(2), child: LinearProgressIndicator( value: _epg!.progress!.clamp(0.0, 1.0), - backgroundColor: Colors.white24, - valueColor: const AlwaysStoppedAnimation(Colors.red), + backgroundColor: AppColors.outlineVariant, + valueColor: const AlwaysStoppedAnimation(AppColors.error), minHeight: 3, ), ), @@ -144,7 +145,7 @@ class _EPGWidgetState extends ConsumerState { Icon( Icons.upcoming, size: 12, - color: Colors.grey.shade400, + color: AppColors.outline, ), const SizedBox(width: 6), Expanded( @@ -152,7 +153,7 @@ class _EPGWidgetState extends ConsumerState { 'Next: ${_epg!.nextPlaying}', style: GoogleFonts.roboto( fontSize: 10, - color: Colors.grey.shade400, + color: AppColors.outline, ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/features/iptv/widgets/live_tv_tab.dart b/lib/features/iptv/widgets/live_tv_tab.dart index e3872ff..13bc999 100644 --- a/lib/features/iptv/widgets/live_tv_tab.dart +++ b/lib/features/iptv/widgets/live_tv_tab.dart @@ -64,7 +64,10 @@ class _LiveTVTabState extends ConsumerState body: channelsAsync.when( loading: () => const ThemedLoading(), error: (e, s) => Center( - child: Text('Error: $e', style: const TextStyle(color: Colors.white)), + child: Text( + 'Error: $e', + style: GoogleFonts.inter(color: AppColors.onSurface), + ), ), data: (groupedChannels) { var categories = groupedChannels.keys.toList(); @@ -73,7 +76,6 @@ class _LiveTVTabState extends ConsumerState .where((cat) => settings.matchesLiveTvFilter(cat)) .toList(); } - // REMOVED: categories.sort(); - Respect API/Settings order List displayedChannels = []; bool showingCategoryGrid = false; @@ -96,12 +98,11 @@ class _LiveTVTabState extends ConsumerState return Column( children: [ - // HEADER (Glass) + // HEADER (Glass Level 1) Padding( padding: const EdgeInsets.fromLTRB(24, 16, 24, 16), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 16, - opacity: 0.6, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Row( @@ -120,12 +121,13 @@ class _LiveTVTabState extends ConsumerState child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainerHigh + .withOpacity(0.5), shape: BoxShape.circle, ), - child: const Icon( + child: Icon( Icons.arrow_back, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -139,10 +141,10 @@ class _LiveTVTabState extends ConsumerState : _showFavoritesOnly ? 'Favorites' : _selectedCategory ?? 'Live TV', - style: GoogleFonts.inter( + style: GoogleFonts.spaceGrotesk( fontSize: 24, fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -154,17 +156,22 @@ class _LiveTVTabState extends ConsumerState width: 250, height: 44, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.outlineVariant, + width: 1, + ), ), child: TextField( controller: _searchController, - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), decoration: InputDecoration( hintText: 'Search channels...', - hintStyle: GoogleFonts.inter(color: Colors.white54), + hintStyle: + GoogleFonts.inter(color: AppColors.outline), prefixIcon: - const Icon(Icons.search, color: Colors.white54), + Icon(Icons.search, color: AppColors.outline), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 16, @@ -172,9 +179,9 @@ class _LiveTVTabState extends ConsumerState ), suffixIcon: _searchQuery.isNotEmpty ? IconButton( - icon: const Icon( + icon: Icon( Icons.clear, - color: Colors.white, + color: AppColors.onSurface, ), onPressed: _searchController.clear, ) @@ -199,7 +206,8 @@ class _LiveTVTabState extends ConsumerState decoration: BoxDecoration( color: _showFavoritesOnly ? AppColors.live.withOpacity(0.2) - : Colors.white.withOpacity(0.1), + : AppColors.surfaceContainerHigh + .withOpacity(0.5), borderRadius: BorderRadius.circular(12), border: _showFavoritesOnly ? Border.all(color: AppColors.live) @@ -211,7 +219,7 @@ class _LiveTVTabState extends ConsumerState : Icons.favorite_border, color: _showFavoritesOnly ? AppColors.live - : Colors.white, + : AppColors.onSurfaceVariant, ), ), ), @@ -232,20 +240,19 @@ class _LiveTVTabState extends ConsumerState Icon( Icons.tv_off, size: 64, - color: Colors.white.withOpacity(0.3), + color: AppColors.outline, ), const SizedBox(height: 16), Text( 'No channels found', style: GoogleFonts.inter( - color: Colors.white54, + color: AppColors.onSurfaceVariant, fontSize: 18, ), ), ], ), ) - // Use raw grid for now, but wrapped in focusable cards : _buildChannelGrid(displayedChannels), ), ], @@ -288,10 +295,9 @@ class _LiveTVTabState extends ConsumerState }); }, borderRadius: 16, - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 16, - opacity: 0.2, // Darker glass - padding: const EdgeInsets.all(0), + padding: EdgeInsets.zero, child: Stack( children: [ // Background Gradient @@ -301,8 +307,8 @@ class _LiveTVTabState extends ConsumerState begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - Colors.white.withOpacity(0.05), - Colors.blue.withOpacity(0.05), + AppColors.surfaceContainerLow.withOpacity(0.5), + AppColors.primary.withOpacity(0.03), ], ), ), @@ -313,7 +319,7 @@ class _LiveTVTabState extends ConsumerState children: [ Icon( Icons.folder_copy_outlined, - color: Colors.white.withOpacity(0.9), + color: AppColors.onSurfaceVariant, size: 32, ), const SizedBox(height: 12), @@ -325,7 +331,7 @@ class _LiveTVTabState extends ConsumerState style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), maxLines: 2, overflow: TextOverflow.ellipsis, @@ -336,7 +342,7 @@ class _LiveTVTabState extends ConsumerState '$count Channels', style: GoogleFonts.inter( fontSize: 13, - color: Colors.white54, + color: AppColors.outline, ), ), ], @@ -365,7 +371,7 @@ class _LiveTVTabState extends ConsumerState crossAxisCount: columns, mainAxisSpacing: 16, crossAxisSpacing: 16, - childAspectRatio: 1.0, // Square-ish cards + childAspectRatio: 1.0, ), itemCount: channels.length, itemBuilder: (context, index) { @@ -375,7 +381,7 @@ class _LiveTVTabState extends ConsumerState borderRadius: 12, child: Container( decoration: BoxDecoration( - color: AppColors.surfaceVariant, + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(12), ), child: Stack( @@ -388,8 +394,8 @@ class _LiveTVTabState extends ConsumerState Expanded( child: Container( decoration: BoxDecoration( - color: Colors.white.withOpacity( - 0.05), // Subtle transparent background + color: AppColors.surfaceContainer + .withOpacity(0.5), borderRadius: const BorderRadius.vertical( top: Radius.circular(12)), ), @@ -400,16 +406,16 @@ class _LiveTVTabState extends ConsumerState _getProxiedIconUrl(channel.streamIcon)!, errorBuilder: (_, __, ___) => Icon( Icons.tv, - color: Colors.white.withOpacity(0.3), + color: AppColors.outline, size: 40, ), ) : Text( channel.name.characters.first.toUpperCase(), - style: GoogleFonts.outfit( + style: GoogleFonts.spaceGrotesk( fontSize: 40, fontWeight: FontWeight.bold, - color: Colors.white24, + color: AppColors.outlineVariant, ), ), ), @@ -419,14 +425,18 @@ class _LiveTVTabState extends ConsumerState Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8), - color: Colors.black54, + decoration: BoxDecoration( + color: AppColors.surfaceContainerHigh.withOpacity(0.9), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(12)), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( channel.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w500, ), @@ -451,7 +461,7 @@ class _LiveTVTabState extends ConsumerState return Text( 'No Info', style: GoogleFonts.inter( - color: Colors.white54, + color: AppColors.outline, fontSize: 10, ), ); @@ -478,7 +488,7 @@ class _LiveTVTabState extends ConsumerState return Text( currentProgram.title, style: GoogleFonts.inter( - color: const Color(0xFFFFD700), + color: AppColors.ratingGold, fontSize: 10, fontWeight: FontWeight.w600, ), @@ -489,14 +499,14 @@ class _LiveTVTabState extends ConsumerState loading: () => Text( '...', style: GoogleFonts.inter( - color: Colors.white24, + color: AppColors.outlineVariant, fontSize: 10, ), ), error: (_, __) => Text( 'Err', style: GoogleFonts.inter( - color: Colors.red, + color: AppColors.error, fontSize: 10, ), ), @@ -508,7 +518,7 @@ class _LiveTVTabState extends ConsumerState ), ], ), - // Record Button Icon Overlay (Positioned top right) + // Record Button Icon Overlay Positioned( top: 8, right: 8, @@ -521,12 +531,12 @@ class _LiveTVTabState extends ConsumerState child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), + color: AppColors.background.withOpacity(0.5), shape: BoxShape.circle, ), child: const Icon( Icons.fiber_manual_record, - color: Colors.redAccent, + color: AppColors.error, size: 16, ), ), diff --git a/lib/features/iptv/widgets/movies_tab.dart b/lib/features/iptv/widgets/movies_tab.dart index f3fba21..7b8ba1f 100644 --- a/lib/features/iptv/widgets/movies_tab.dart +++ b/lib/features/iptv/widgets/movies_tab.dart @@ -150,12 +150,10 @@ class _MoviesTabState extends ConsumerState { Future _playMovie(Movie movie) async { ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId); - // Fetch actual duration from API if not available in movie data Duration? movieDuration; if (movie.durationSecs != null && movie.durationSecs! > 0) { movieDuration = Duration(seconds: movie.durationSecs!); } else { - // Try to fetch from API final service = ref.read(xtreamServiceProvider(widget.playlist)); final durationSecs = await service.getVodDuration(movie.streamId); if (durationSecs != null && durationSecs > 0) { @@ -213,19 +211,18 @@ class _MoviesTabState extends ConsumerState { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 100, - opacity: 0.6, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( children: [ Text( 'Movies', - style: GoogleFonts.inter( + style: GoogleFonts.spaceGrotesk( fontSize: 24, fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.onSurface, ), ), const Spacer(), @@ -235,16 +232,20 @@ class _MoviesTabState extends ConsumerState { width: 300, height: 40, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(100), + border: Border.all( + color: AppColors.outlineVariant, + width: 1, + ), ), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ - const Icon( + Icon( Icons.search, size: 20, - color: Colors.white54, + color: AppColors.outline, ), const SizedBox(width: 8), Expanded( @@ -252,12 +253,12 @@ class _MoviesTabState extends ConsumerState { controller: _searchController, style: GoogleFonts.inter( fontSize: 14, - color: Colors.white, + color: AppColors.onSurface, ), decoration: InputDecoration( hintText: 'Search movies...', hintStyle: - GoogleFonts.inter(color: Colors.white54), + GoogleFonts.inter(color: AppColors.outline), border: InputBorder.none, isDense: true, contentPadding: @@ -272,7 +273,7 @@ class _MoviesTabState extends ConsumerState { height: 16, child: CircularProgressIndicator( strokeWidth: 2, - color: Colors.white, + color: AppColors.onSurface, ), ), if (_searchQuery.isNotEmpty) @@ -281,10 +282,10 @@ class _MoviesTabState extends ConsumerState { _searchController.clear(); _onSearchChanged(''); }, - child: const Icon( + child: Icon( Icons.close, size: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -352,23 +353,23 @@ class _MoviesTabState extends ConsumerState { _getProxiedImageUrl(movie.streamIcon), fit: BoxFit.cover, errorBuilder: (ctx, err, stack) => Container( - color: AppColors.surfaceVariant, - child: const Center( + color: AppColors.surfaceContainerLow, + child: Center( child: Icon( Icons.movie, size: 48, - color: Colors.white24, + color: AppColors.outline, ), ), ), ) : Container( - color: AppColors.surfaceVariant, - child: const Center( + color: AppColors.surfaceContainerLow, + child: Center( child: Icon( Icons.movie, size: 48, - color: Colors.white24, + color: AppColors.outline, ), ), ), @@ -387,7 +388,7 @@ class _MoviesTabState extends ConsumerState { end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.9), + AppColors.baseLevel0.withOpacity(0.9), ], ), borderRadius: const BorderRadius.vertical( @@ -408,9 +409,10 @@ class _MoviesTabState extends ConsumerState { vertical: 2, ), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.7), + color: AppColors.background.withOpacity(0.7), borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.white24), + border: Border.all( + color: AppColors.outlineVariant), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -418,7 +420,7 @@ class _MoviesTabState extends ConsumerState { const Icon( Icons.star, size: 10, - color: Colors.amber, + color: Color(0xFFFFD700), ), const SizedBox(width: 4), Text( @@ -426,7 +428,7 @@ class _MoviesTabState extends ConsumerState { style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -446,12 +448,12 @@ class _MoviesTabState extends ConsumerState { Text( movie.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w600, shadows: [ const Shadow( - color: Colors.black, + color: AppColors.background, blurRadius: 4, ), ], @@ -489,7 +491,7 @@ class _MoviesTabState extends ConsumerState { child: Padding( padding: EdgeInsets.all(32), child: Center( - child: CircularProgressIndicator(color: Colors.white), + child: CircularProgressIndicator(color: AppColors.onSurface), ), ), ), diff --git a/lib/features/iptv/widgets/recording_modal.dart b/lib/features/iptv/widgets/recording_modal.dart index aa5676f..1596813 100644 --- a/lib/features/iptv/widgets/recording_modal.dart +++ b/lib/features/iptv/widgets/recording_modal.dart @@ -101,7 +101,7 @@ class _RecordingModalState extends State { // Header Row( children: [ - const Icon(Icons.fiber_manual_record, color: Colors.red), + const Icon(Icons.fiber_manual_record, color: AppColors.error), const SizedBox(width: 12), Expanded( child: Text( @@ -109,7 +109,7 @@ class _RecordingModalState extends State { style: GoogleFonts.inter( fontSize: 20, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ), @@ -120,12 +120,12 @@ class _RecordingModalState extends State { // Informations Chaine Text( 'Chaîne ciblée :', - style: GoogleFonts.inter(color: Colors.white54, fontSize: 14), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 8), Text( widget.channel.name, - style: GoogleFonts.inter(color: Colors.white, fontSize: 18), + style: GoogleFonts.inter(color: AppColors.onSurface, fontSize: 18), ), const SizedBox(height: 24), @@ -139,7 +139,7 @@ class _RecordingModalState extends State { children: [ Text( 'Date :', - style: GoogleFonts.inter(color: Colors.white54, fontSize: 14), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 8), TvFocusableCard( @@ -168,12 +168,12 @@ class _RecordingModalState extends State { ), child: Row( children: [ - const Icon(Icons.calendar_today, color: Colors.white, size: 18), + const Icon(Icons.calendar_today, color: AppColors.onSurface, size: 18), const SizedBox(width: 8), Expanded( child: Text( '${_startTime.day.toString().padLeft(2, '0')}/${_startTime.month.toString().padLeft(2, '0')}/${_startTime.year}', - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), overflow: TextOverflow.ellipsis, ), ), @@ -193,7 +193,7 @@ class _RecordingModalState extends State { children: [ Text( 'Heure :', - style: GoogleFonts.inter(color: Colors.white54, fontSize: 14), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 8), TvFocusableCard( @@ -231,12 +231,12 @@ class _RecordingModalState extends State { ), child: Row( children: [ - const Icon(Icons.access_time, color: Colors.white, size: 18), + const Icon(Icons.access_time, color: AppColors.onSurface, size: 18), const SizedBox(width: 8), Expanded( child: Text( '${_startTime.hour.toString().padLeft(2, '0')}:${_startTime.minute.toString().padLeft(2, '0')}', - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), ), ), ], @@ -255,17 +255,16 @@ class _RecordingModalState extends State { children: [ Text( 'Durée (min) :', - style: GoogleFonts.inter(color: Colors.white54, fontSize: 14), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, fontSize: 14), ), const SizedBox(height: 8), - GlassContainer( + GlassContainer.glass( borderRadius: 8, - opacity: 0.1, padding: EdgeInsets.zero, child: Row( children: [ IconButton( - icon: const Icon(Icons.remove, color: Colors.white, size: 18), + icon: const Icon(Icons.remove, color: AppColors.onSurface, size: 18), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 46), onPressed: () { @@ -278,11 +277,11 @@ class _RecordingModalState extends State { child: Text( '$_durationMinutes', textAlign: TextAlign.center, - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), ), ), IconButton( - icon: const Icon(Icons.add, color: Colors.white, size: 18), + icon: const Icon(Icons.add, color: AppColors.onSurface, size: 18), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 46), onPressed: () { @@ -309,7 +308,7 @@ class _RecordingModalState extends State { onPressed: _isLoading ? null : () => Navigator.of(context).pop(), child: Text( 'Annuler', - style: GoogleFonts.inter(color: Colors.white54), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant), ), ), const SizedBox(width: 16), @@ -319,18 +318,18 @@ class _RecordingModalState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - color: Colors.redAccent.withOpacity(0.8), + color: AppColors.error.withOpacity(0.8), borderRadius: BorderRadius.circular(8), ), child: _isLoading ? const SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + child: CircularProgressIndicator(color: AppColors.onSurface, strokeWidth: 2), ) : Text( 'Enregistrer de suite', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.bold, ), ), @@ -349,12 +348,12 @@ class _RecordingModalState extends State { child: _isLoading ? const SizedBox( width: 20, height: 20, - child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + child: CircularProgressIndicator(color: AppColors.onSurface, strokeWidth: 2), ) : Text( 'Programmer', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.bold, ), ), diff --git a/lib/features/iptv/widgets/recordings_tab.dart b/lib/features/iptv/widgets/recordings_tab.dart index 2ac5d4a..de4ec6e 100644 --- a/lib/features/iptv/widgets/recordings_tab.dart +++ b/lib/features/iptv/widgets/recordings_tab.dart @@ -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 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'] ?? ''), diff --git a/lib/features/iptv/widgets/series_tab.dart b/lib/features/iptv/widgets/series_tab.dart index 71ef558..522ffb2 100644 --- a/lib/features/iptv/widgets/series_tab.dart +++ b/lib/features/iptv/widgets/series_tab.dart @@ -191,19 +191,18 @@ class _SeriesTabState extends ConsumerState { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 100, - opacity: 0.6, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( children: [ Text( 'TV Series', - style: GoogleFonts.inter( + style: GoogleFonts.spaceGrotesk( fontSize: 24, fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.onSurface, ), ), const Spacer(), @@ -211,16 +210,20 @@ class _SeriesTabState extends ConsumerState { width: 300, height: 40, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(100), + border: Border.all( + color: AppColors.outlineVariant, + width: 1, + ), ), padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ - const Icon( + Icon( Icons.search, size: 20, - color: Colors.white54, + color: AppColors.outline, ), const SizedBox(width: 8), Expanded( @@ -228,12 +231,12 @@ class _SeriesTabState extends ConsumerState { controller: _searchController, style: GoogleFonts.inter( fontSize: 14, - color: Colors.white, + color: AppColors.onSurface, ), decoration: InputDecoration( hintText: 'Search series...', hintStyle: - GoogleFonts.inter(color: Colors.white54), + GoogleFonts.inter(color: AppColors.outline), border: InputBorder.none, isDense: true, contentPadding: @@ -248,7 +251,7 @@ class _SeriesTabState extends ConsumerState { height: 16, child: CircularProgressIndicator( strokeWidth: 2, - color: Colors.white, + color: AppColors.onSurface, ), ), if (_searchQuery.isNotEmpty) @@ -257,10 +260,10 @@ class _SeriesTabState extends ConsumerState { _searchController.clear(); _onSearchChanged(''); }, - child: const Icon( + child: Icon( Icons.close, size: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -341,23 +344,23 @@ class _SeriesTabState extends ConsumerState { _getProxiedImageUrl(serie.cover), fit: BoxFit.cover, errorBuilder: (ctx, err, stack) => Container( - color: AppColors.surfaceVariant, - child: const Center( + color: AppColors.surfaceContainerLow, + child: Center( child: Icon( Icons.tv, size: 48, - color: Colors.white24, + color: AppColors.outline, ), ), ), ) : Container( - color: AppColors.surfaceVariant, - child: const Center( + color: AppColors.surfaceContainerLow, + child: Center( child: Icon( Icons.tv, size: 48, - color: Colors.white24, + color: AppColors.outline, ), ), ), @@ -376,7 +379,7 @@ class _SeriesTabState extends ConsumerState { end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.9), + AppColors.baseLevel0.withOpacity(0.9), ], ), borderRadius: const BorderRadius.vertical( @@ -394,12 +397,12 @@ class _SeriesTabState extends ConsumerState { child: Text( serie.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w600, shadows: [ const Shadow( - color: Colors.black, + color: AppColors.background, blurRadius: 4, ), ], @@ -420,9 +423,10 @@ class _SeriesTabState extends ConsumerState { vertical: 2, ), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.7), + color: AppColors.background.withOpacity(0.7), borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.white24), + border: Border.all( + color: AppColors.outlineVariant), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -430,7 +434,7 @@ class _SeriesTabState extends ConsumerState { const Icon( Icons.star, size: 10, - color: Colors.amber, + color: Color(0xFFFFD700), ), const SizedBox(width: 4), Text( @@ -438,7 +442,7 @@ class _SeriesTabState extends ConsumerState { style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -460,7 +464,7 @@ class _SeriesTabState extends ConsumerState { child: Padding( padding: EdgeInsets.all(32), child: Center( - child: CircularProgressIndicator(color: Colors.white), + child: CircularProgressIndicator(color: AppColors.onSurface), ), ), ), diff --git a/lib/features/iptv/widgets/settings_tab.dart b/lib/features/iptv/widgets/settings_tab.dart index 54d984b..9c45c1b 100644 --- a/lib/features/iptv/widgets/settings_tab.dart +++ b/lib/features/iptv/widgets/settings_tab.dart @@ -77,17 +77,17 @@ class _SettingsTabState extends ConsumerState Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(50), ), child: TabBar( controller: _tabController, indicator: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.outlineVariant, borderRadius: BorderRadius.circular(50), ), - labelColor: Colors.white, - unselectedLabelColor: Colors.white54, + labelColor: AppColors.onSurface, + unselectedLabelColor: AppColors.onSurfaceVariant, dividerColor: Colors.transparent, overlayColor: WidgetStateProperty.all(Colors.transparent), tabs: [ @@ -138,9 +138,9 @@ class _SettingsTabState extends ConsumerState // Theme selection card Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), padding: const EdgeInsets.all(16), child: Column( @@ -150,7 +150,7 @@ class _SettingsTabState extends ConsumerState children: [ const Icon( Icons.dark_mode, - color: Colors.white, + color: AppColors.onSurface, ), const SizedBox(width: 12), Text( @@ -158,7 +158,7 @@ class _SettingsTabState extends ConsumerState style: GoogleFonts.inter( fontSize: 18, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -201,25 +201,25 @@ class _SettingsTabState extends ConsumerState // Quick toggle card Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), child: ListTile( leading: Icon( isDark ? Icons.dark_mode : Icons.light_mode, - color: Colors.white, + color: AppColors.onSurface, ), title: Text( 'Mode actuel', style: GoogleFonts.inter( fontWeight: FontWeight.w500, - color: Colors.white, + color: AppColors.onSurface, ), ), subtitle: Text( isDark ? 'Thème sombre actif' : 'Thème clair actif', - style: const TextStyle(color: Colors.white70), + style: const TextStyle(color: AppColors.onSurfaceVariant), ), trailing: Switch( value: isDark, @@ -228,7 +228,7 @@ class _SettingsTabState extends ConsumerState trackColor: WidgetStateProperty.resolveWith( (states) => states.contains(WidgetState.selected) ? AppColors.primary.withOpacity(0.5) - : Colors.grey.shade800, + : AppColors.surfaceContainerHigh, ), ), ), @@ -318,34 +318,34 @@ class _SettingsTabState extends ConsumerState // User info card Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), padding: const EdgeInsets.all(4), child: ListTile( - leading: const Icon(Icons.person, color: Colors.white), + leading: const Icon(Icons.person, color: AppColors.onSurface), title: Text( 'Connecté en tant que', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white70), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), subtitle: Text( currentUser?.username ?? 'Unknown', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), trailing: currentUser?.isAdmin ?? false ? Chip( label: Text( 'Admin', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 11, - color: Colors.black, + color: AppColors.onPrimaryContainer, ), ), - backgroundColor: Colors.white, + backgroundColor: AppColors.onSurface, ) : null, ), @@ -355,18 +355,18 @@ class _SettingsTabState extends ConsumerState // Category filters section Text( 'Filtres de Catégories', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontWeight: FontWeight.bold, fontSize: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), const SizedBox(height: 4), Text( 'Seules les catégories contenant un de ces mots-clés seront affichées. Séparez par des virgules.', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 12, - color: Colors.white54, + color: AppColors.onSurfaceVariant, ), ), const SizedBox(height: 12), @@ -422,17 +422,17 @@ class _SettingsTabState extends ConsumerState // Change playlist Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), child: ListTile( - leading: const Icon(Icons.playlist_play, color: Colors.white), + leading: const Icon(Icons.playlist_play, color: AppColors.onSurface), title: Text( 'Changer de Playlist', - style: GoogleFonts.roboto(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), ), - trailing: const Icon(Icons.chevron_right, color: Colors.white54), + trailing: const Icon(Icons.chevron_right, color: AppColors.onSurfaceVariant), onTap: () => context.go('/playlists'), ), ), @@ -441,19 +441,19 @@ class _SettingsTabState extends ConsumerState // About Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), child: ListTile( - leading: const Icon(Icons.info_outline, color: Colors.white), + leading: const Icon(Icons.info_outline, color: AppColors.onSurface), title: Text( 'À propos', - style: GoogleFonts.roboto(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), ), subtitle: Text( 'XtremFlow IPTV v1.0.0', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), ), ), @@ -462,19 +462,19 @@ class _SettingsTabState extends ConsumerState // Maintenance Section Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + border: Border.all(color: AppColors.warning.withOpacity(0.3)), ), child: ListTile( - leading: const Icon(Icons.cleaning_services, color: Colors.orange), + leading: const Icon(Icons.cleaning_services, color: AppColors.warning), title: Text( 'Maintenance', - style: GoogleFonts.roboto(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), ), subtitle: Text( 'En cas de problèmes d\'affichage ou de mise à jour', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), trailing: FilledButton.icon( onPressed: () { @@ -485,12 +485,12 @@ class _SettingsTabState extends ConsumerState backgroundColor: const Color(0xFF1E1E1E), title: const Text( 'Vider le cache ?', - style: TextStyle(color: Colors.white), + style: TextStyle(color: AppColors.onSurface), ), content: const Text( 'Cette action va recharger l\'application et effacer les données temporaires.\n' 'Vos réglages et favoris sont sauvegardés sur le serveur.', - style: TextStyle(color: Colors.white70), + style: TextStyle(color: AppColors.onSurfaceVariant), ), actions: [ TextButton( @@ -499,7 +499,7 @@ class _SettingsTabState extends ConsumerState ), FilledButton( style: FilledButton.styleFrom( - backgroundColor: Colors.orange, + backgroundColor: AppColors.warning, ), onPressed: () { Navigator.pop(context); @@ -524,8 +524,8 @@ class _SettingsTabState extends ConsumerState icon: const Icon(Icons.refresh, size: 16), label: const Text('Vider le Cache'), style: FilledButton.styleFrom( - backgroundColor: Colors.orange.withOpacity(0.2), - foregroundColor: Colors.orange, + backgroundColor: AppColors.warning.withOpacity(0.2), + foregroundColor: AppColors.warning, ), ), ), @@ -541,7 +541,7 @@ class _SettingsTabState extends ConsumerState icon: const Icon(Icons.logout), label: const Text('Déconnexion'), style: FilledButton.styleFrom( - backgroundColor: Colors.red.shade700, + backgroundColor: AppColors.error, padding: const EdgeInsets.symmetric(vertical: 16), ), ), @@ -559,9 +559,9 @@ class _SettingsTabState extends ConsumerState }) { return Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: AppColors.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: AppColors.outlineVariant), ), padding: const EdgeInsets.all(16), child: Column( @@ -569,14 +569,14 @@ class _SettingsTabState extends ConsumerState children: [ Row( children: [ - Icon(icon, size: 18, color: Colors.blueAccent), + Icon(icon, size: 18, color: AppColors.primaryContainer), const SizedBox(width: 8), Text( title, - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontWeight: FontWeight.w600, fontSize: 13, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -587,9 +587,9 @@ class _SettingsTabState extends ConsumerState decoration: InputDecoration( hintText: 'Ex: FR,FRANCE,HD,SPORT', hintStyle: - GoogleFonts.roboto(fontSize: 12, color: Colors.white30), + GoogleFonts.inter(fontSize: 12, color: AppColors.outline), filled: true, - fillColor: Colors.black26, + fillColor: AppColors.surfaceContainerHigh, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, @@ -604,7 +604,7 @@ class _SettingsTabState extends ConsumerState icon: const Icon( Icons.clear, size: 16, - color: Colors.white54, + color: AppColors.onSurfaceVariant, ), onPressed: onClear, padding: EdgeInsets.zero, @@ -612,7 +612,7 @@ class _SettingsTabState extends ConsumerState ) : null, ), - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurface), onChanged: onChanged, ), if (keywords.isNotEmpty) ...[ @@ -625,15 +625,15 @@ class _SettingsTabState extends ConsumerState padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: Colors.blueAccent.withOpacity(0.2), + color: AppColors.primaryContainer.withOpacity(0.2), borderRadius: BorderRadius.circular(50), border: - Border.all(color: Colors.blueAccent.withOpacity(0.5)), + Border.all(color: AppColors.primaryContainer.withOpacity(0.5)), ), child: Text( keyword, style: - GoogleFonts.roboto(fontSize: 11, color: Colors.white), + GoogleFonts.inter(fontSize: 11, color: AppColors.onSurface), ), ); }).toList(), diff --git a/lib/features/iptv/widgets/streaming_settings_tab.dart b/lib/features/iptv/widgets/streaming_settings_tab.dart index 46700b7..4cfa2e9 100644 --- a/lib/features/iptv/widgets/streaming_settings_tab.dart +++ b/lib/features/iptv/widgets/streaming_settings_tab.dart @@ -1,6 +1,7 @@ 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 @@ -18,18 +19,18 @@ class StreamingSettingsTab extends ConsumerWidget { // Header Text( 'Paramètres Streaming Live TV', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), const SizedBox(height: 8), Text( 'Ces paramètres s\'appliquent uniquement aux flux TV en direct.', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 12, - color: Colors.white54, + color: AppColors.onSurfaceVariant, ), ), const SizedBox(height: 24), @@ -104,11 +105,11 @@ class StreamingSettingsTab extends ConsumerWidget { SwitchListTile( title: Text( 'Reconnexion automatique', - style: GoogleFonts.roboto(fontSize: 14, color: Colors.white), + style: GoogleFonts.inter(fontSize: 14, color: AppColors.onSurface), ), subtitle: Text( 'Se reconnecter en cas de coupure', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), value: settings.autoReconnect, activeThumbColor: Colors.blueAccent, @@ -140,11 +141,11 @@ class StreamingSettingsTab extends ConsumerWidget { SwitchListTile( title: Text( 'Préférer lecture directe', - style: GoogleFonts.roboto(fontSize: 14, color: Colors.white), + style: GoogleFonts.inter(fontSize: 14, color: AppColors.onSurface), ), subtitle: Text( 'Utiliser le flux original si compatible', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), value: settings.preferDirectPlay, activeThumbColor: Colors.blueAccent, @@ -185,11 +186,11 @@ class StreamingSettingsTab extends ConsumerWidget { SwitchListTile( title: Text( 'Accélération NVIDIA GPU', - style: GoogleFonts.roboto(fontSize: 14, color: Colors.white), + style: GoogleFonts.inter(fontSize: 14, color: AppColors.onSurface), ), subtitle: Text( 'Utiliser NVENC/NVDEC pour le transcodage (requiert GPU NVIDIA)', - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), value: settings.enableNvidiaGpu, activeThumbColor: Colors.green, @@ -214,7 +215,7 @@ class StreamingSettingsTab extends ConsumerWidget { Expanded( child: Text( 'Les changements prennent effet immédiatement pour les nouveaux flux.', - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 12, color: Colors.blue.shade700, ), @@ -250,10 +251,10 @@ class StreamingSettingsTab extends ConsumerWidget { const SizedBox(width: 8), Text( title, - style: GoogleFonts.roboto( + style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -277,11 +278,11 @@ class StreamingSettingsTab extends ConsumerWidget { return ListTile( title: Text( title, - style: GoogleFonts.roboto(fontSize: 14, color: Colors.white), + style: GoogleFonts.inter(fontSize: 14, color: AppColors.onSurface), ), subtitle: Text( subtitle, - style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54), + style: GoogleFonts.inter(fontSize: 12, color: AppColors.onSurfaceVariant), ), trailing: DropdownButton( value: value, @@ -292,13 +293,13 @@ class StreamingSettingsTab extends ConsumerWidget { value: item, child: Text( labelBuilder(item), - style: GoogleFonts.roboto(fontSize: 13, color: Colors.white), + style: GoogleFonts.inter(fontSize: 13, color: AppColors.onSurface), ), ); }).toList(), onChanged: onChanged, - style: GoogleFonts.roboto(color: Colors.white), - iconEnabledColor: Colors.white70, + style: GoogleFonts.inter(color: AppColors.onSurface), + iconEnabledColor: AppColors.onSurfaceVariant, ), ); } diff --git a/lib/mobile/features/auth/screens/mobile_login_screen.dart b/lib/mobile/features/auth/screens/mobile_login_screen.dart index 904fb24..059281f 100644 --- a/lib/mobile/features/auth/screens/mobile_login_screen.dart +++ b/lib/mobile/features/auth/screens/mobile_login_screen.dart @@ -100,9 +100,8 @@ class _MobileLoginScreenState extends ConsumerState { Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), - child: GlassContainer( + child: GlassContainer.floating( borderRadius: 24, - opacity: 0.7, padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, @@ -112,23 +111,23 @@ class _MobileLoginScreenState extends ConsumerState { width: 80, height: 80, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainer, shape: BoxShape.circle, - border: Border.all(color: Colors.white24), + border: Border.all(color: AppColors.outlineVariant), ), child: const Icon( Icons.live_tv_rounded, - color: Colors.white, + color: AppColors.onSurface, size: 40, ), ), const SizedBox(height: 24), Text( 'XtremFlow', - style: GoogleFonts.inter( + style: GoogleFonts.spaceGrotesk( fontSize: 32, fontWeight: FontWeight.w700, - color: Colors.white, + color: AppColors.onSurface, letterSpacing: -1.0, ), ), @@ -136,7 +135,7 @@ class _MobileLoginScreenState extends ConsumerState { 'Mobile Edition', style: GoogleFonts.inter( fontSize: 14, - color: Colors.white54, + color: AppColors.onSurfaceVariant, letterSpacing: 0.5, ), ), @@ -182,28 +181,39 @@ class _MobileLoginScreenState extends ConsumerState { const SizedBox(height: 32), - SizedBox( + Container( width: double.infinity, height: 50, + decoration: BoxDecoration( + gradient: AppColors.primaryGradient, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.glowPrimary(0.4), + blurRadius: 16, + offset: const Offset(0, 4), + ), + ], + ), child: FilledButton( onPressed: authState.isLoading ? null : _handleLogin, style: FilledButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 0, ).copyWith( - overlayColor: WidgetStateProperty.all(Colors.black12), + overlayColor: WidgetStateProperty.all(AppColors.onSurface.withOpacity(0.1)), ), child: authState.isLoading ? const SizedBox( width: 24, height: 24, - child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black), + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : Text( 'Sign In', - style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16), + style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white), ), ), ), @@ -233,19 +243,19 @@ class _MobileLoginScreenState extends ConsumerState { return TextFormField( controller: controller, obscureText: obscureText, - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), decoration: InputDecoration( labelText: label, - labelStyle: GoogleFonts.inter(color: Colors.white54), - prefixIcon: Icon(icon, color: Colors.white54), - suffixIcon: isPassword + labelStyle: GoogleFonts.inter(color: AppColors.onSurfaceVariant), + prefixIcon: Icon(icon, color: AppColors.onSurfaceVariant), + suffixIcon: isPassword ? IconButton( - icon: Icon(obscureText ? Icons.visibility_outlined : Icons.visibility_off_outlined, color: Colors.white38), + icon: Icon(obscureText ? Icons.visibility_outlined : Icons.visibility_off_outlined, color: AppColors.outline), onPressed: onTogglePassword, ) : null, filled: true, - fillColor: Colors.white.withOpacity(0.1), + fillColor: AppColors.surfaceContainerLow, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -256,7 +266,7 @@ class _MobileLoginScreenState extends ConsumerState { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Colors.white, width: 2), // White Focus + borderSide: const BorderSide(color: AppColors.primaryContainer, width: 2), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), ), diff --git a/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart b/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart index 25f72eb..e9b14ba 100644 --- a/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart +++ b/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart @@ -83,11 +83,11 @@ class MobilePlaylistSelectionScreen extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.playlist_remove, - size: 48, color: AppColors.textSecondary), + size: 48, color: AppColors.onSurfaceVariant), const SizedBox(height: 16), Text('No playlists available', style: - GoogleFonts.inter(color: AppColors.textSecondary)), + GoogleFonts.inter(color: AppColors.onSurfaceVariant)), ], ), ); @@ -133,10 +133,10 @@ class _MobilePlaylistCard extends StatelessWidget { decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColors.border.withOpacity(0.5)), + border: Border.all(color: AppColors.outlineVariant.withOpacity(0.5)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: AppColors.background.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, 4), ), @@ -171,7 +171,7 @@ class _MobilePlaylistCard extends StatelessWidget { style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColors.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -181,7 +181,7 @@ class _MobilePlaylistCard extends StatelessWidget { Uri.tryParse(playlist.dns)?.host ?? playlist.dns, style: GoogleFonts.inter( fontSize: 13, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart b/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart index a94eade..3c5ed3e 100644 --- a/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart +++ b/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart @@ -80,7 +80,7 @@ class _MobileSeriesDetailScreenState @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.black, + backgroundColor: AppColors.background, body: Stack( fit: StackFit.expand, children: [ @@ -90,13 +90,13 @@ class _MobileSeriesDetailScreenState gradient: RadialGradient( center: Alignment.center, radius: 1.5, - colors: [Color(0xFF1C1C1E), Color(0xFF000000)], + colors: [AppColors.surfaceContainerLow, AppColors.background], ), ), ), if (_isLoading) - const Center(child: CircularProgressIndicator(color: Colors.white)) + const Center(child: CircularProgressIndicator(color: AppColors.onSurface)) else if (_error != null) Center( child: Column( @@ -110,7 +110,7 @@ class _MobileSeriesDetailScreenState const SizedBox(height: 16), Text( 'Error: $_error', - style: GoogleFonts.inter(color: Colors.white70), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant), ), TextButton( onPressed: _loadSeriesInfo, @@ -141,11 +141,11 @@ class _MobileSeriesDetailScreenState leading: Container( margin: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), + color: AppColors.background.withOpacity(0.5), shape: BoxShape.circle, ), child: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon(Icons.arrow_back, color: AppColors.onSurface), onPressed: () => Navigator.pop(context), ), ), @@ -158,7 +158,7 @@ class _MobileSeriesDetailScreenState imageUrl: _seriesInfo!.cover!, fit: BoxFit.cover, errorWidget: (_, __, ___) => - Container(color: Colors.grey.shade900), + Container(color: AppColors.surfaceContainerLow), ), // Gradient Overlay Container( @@ -168,8 +168,8 @@ class _MobileSeriesDetailScreenState end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.8), - Colors.black, // Merge into body background + AppColors.background.withOpacity(0.8), + AppColors.background, // Merge into body background ], stops: const [0.5, 0.8, 1.0], ), @@ -189,7 +189,7 @@ class _MobileSeriesDetailScreenState style: GoogleFonts.inter( fontSize: 24, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), const SizedBox(height: 8), @@ -205,7 +205,7 @@ class _MobileSeriesDetailScreenState Text( _formatRating(_seriesInfo!.rating!)!, style: GoogleFonts.inter( - color: Colors.white70, + color: AppColors.onSurfaceVariant, fontSize: 13, ), ), @@ -214,7 +214,7 @@ class _MobileSeriesDetailScreenState Text( '${_seriesInfo!.episodes.keys.length} Seasons', style: GoogleFonts.inter( - color: Colors.white70, + color: AppColors.onSurfaceVariant, fontSize: 13, ), ), @@ -235,7 +235,7 @@ class _MobileSeriesDetailScreenState padding: const EdgeInsets.all(16.0), child: Text( _seriesInfo!.plot!, - style: GoogleFonts.inter(color: Colors.white70, height: 1.4), + style: GoogleFonts.inter(color: AppColors.onSurfaceVariant, height: 1.4), maxLines: 4, overflow: TextOverflow.ellipsis, ), @@ -260,10 +260,10 @@ class _MobileSeriesDetailScreenState onSelected: (selected) { if (selected) setState(() => _selectedSeason = seasonNum); }, - backgroundColor: Colors.white.withOpacity(0.1), + backgroundColor: AppColors.surfaceContainer, selectedColor: AppColors.primary, labelStyle: GoogleFonts.inter( - color: isSelected ? Colors.white : Colors.white70, + color: isSelected ? AppColors.onPrimary : AppColors.onSurfaceVariant, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, ), shape: RoundedRectangleBorder( @@ -308,9 +308,8 @@ class _MobileSeriesDetailScreenState ); final isWatched = watchHistory.isEpisodeWatched(episodeKey); - return GlassContainer( + return GlassContainer.glass( borderRadius: 12, - opacity: 0.1, padding: EdgeInsets.zero, child: InkWell( onTap: () { @@ -342,12 +341,12 @@ class _MobileSeriesDetailScreenState decoration: BoxDecoration( color: isWatched ? AppColors.primary - : Colors.white.withOpacity(0.1), + : AppColors.surfaceContainer, shape: BoxShape.circle, ), child: Icon( isWatched ? Icons.check : Icons.play_arrow_rounded, - color: isWatched ? Colors.white : AppColors.primary, + color: isWatched ? AppColors.onPrimary : AppColors.primary, size: 24, ), ), @@ -359,7 +358,7 @@ class _MobileSeriesDetailScreenState Text( 'E${episode.episodeNum} - ${episode.title}', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.w600, fontSize: 14, ), @@ -371,7 +370,7 @@ class _MobileSeriesDetailScreenState Text( _formatDuration(episode.durationSecs!), style: GoogleFonts.inter( - color: Colors.white54, + color: AppColors.onSurfaceVariant, fontSize: 12, ), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart b/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart index cffcf6b..1b89fb1 100644 --- a/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart @@ -48,7 +48,7 @@ class _MobileLiveTVTabState extends ConsumerState { children: [ const CircularProgressIndicator(), const SizedBox(height: 16), - const Text('Loading channels...', style: TextStyle(color: Colors.white70)), + const Text('Loading channels...', style: TextStyle(color: AppColors.onSurfaceVariant)), const SizedBox(height: 24), FutureBuilder( future: Future.delayed(const Duration(seconds: 8)), @@ -71,11 +71,11 @@ class _MobileLiveTVTabState extends ConsumerState { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 48), + const Icon(Icons.error_outline, color: AppColors.error, size: 48), const SizedBox(height: 16), - Text('Failed to load channels', style: GoogleFonts.inter(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), + Text('Failed to load channels', style: GoogleFonts.inter(color: AppColors.onSurface, fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 8), - Text('$e', style: const TextStyle(color: Colors.white54), textAlign: TextAlign.center), + Text('$e', style: const TextStyle(color: AppColors.onSurfaceVariant), textAlign: TextAlign.center), const SizedBox(height: 24), ElevatedButton( onPressed: () => ref.refresh(liveChannelsByPlaylistProvider(widget.playlist)), @@ -127,9 +127,8 @@ class _MobileLiveTVTabState extends ConsumerState { // Header & Search Padding( padding: const EdgeInsets.all(16), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 16, - opacity: 0.1, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( @@ -137,15 +136,15 @@ class _MobileLiveTVTabState extends ConsumerState { Expanded( child: TextField( controller: _searchController, - style: GoogleFonts.inter(color: Colors.white), + style: GoogleFonts.inter(color: AppColors.onSurface), decoration: InputDecoration( hintText: 'Search channels...', hintStyle: - GoogleFonts.inter(color: Colors.white54), + GoogleFonts.inter(color: AppColors.onSurfaceVariant), border: InputBorder.none, prefixIcon: const Icon( Icons.search, - color: Colors.white54, + color: AppColors.onSurfaceVariant, ), isDense: true, ), @@ -155,7 +154,7 @@ class _MobileLiveTVTabState extends ConsumerState { GestureDetector( onTap: () => _searchController.clear(), child: - const Icon(Icons.close, color: Colors.white54), + const Icon(Icons.close, color: AppColors.onSurfaceVariant), ), ], ), @@ -185,15 +184,15 @@ class _MobileLiveTVTabState extends ConsumerState { ), decoration: BoxDecoration( color: isSelected - ? Colors.white - : Colors.black.withOpacity(0.4), + ? AppColors.primary + : AppColors.surfaceContainer, borderRadius: BorderRadius.circular(20), ), child: Text( category, style: GoogleFonts.inter( color: - isSelected ? Colors.black : Colors.white70, + isSelected ? AppColors.onPrimary : AppColors.onSurfaceVariant, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, @@ -218,7 +217,7 @@ class _MobileLiveTVTabState extends ConsumerState { ? 'Favorites' : _selectedCategory ?? 'Channels', style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.bold, fontSize: 18, ), @@ -233,7 +232,7 @@ class _MobileLiveTVTabState extends ConsumerState { decoration: BoxDecoration( color: _showFavoritesOnly ? AppColors.live.withOpacity(0.2) - : Colors.white.withOpacity(0.1), + : AppColors.surfaceContainer, borderRadius: BorderRadius.circular(8), border: _showFavoritesOnly ? Border.all(color: AppColors.live) @@ -245,7 +244,7 @@ class _MobileLiveTVTabState extends ConsumerState { : Icons.favorite_border, color: _showFavoritesOnly ? AppColors.live - : Colors.white, + : AppColors.onSurface, size: 20, ), ), @@ -263,13 +262,13 @@ class _MobileLiveTVTabState extends ConsumerState { children: [ const Icon( Icons.tv_off, - color: Colors.white24, + color: AppColors.outlineVariant, size: 48, ), const SizedBox(height: 16), Text( 'No channels found', - style: GoogleFonts.inter(color: Colors.white38), + style: GoogleFonts.inter(color: AppColors.outline), ), ], ), @@ -339,9 +338,8 @@ class _MobileChannelTile extends ConsumerWidget { ), ); - return GlassContainer( + return GlassContainer.glass( borderRadius: 12, - opacity: 0.15, padding: EdgeInsets.zero, child: InkWell( onTap: onTap, @@ -354,7 +352,7 @@ class _MobileChannelTile extends ConsumerWidget { width: 48, height: 48, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainer, borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.all(4), @@ -363,9 +361,9 @@ class _MobileChannelTile extends ConsumerWidget { iconUrl, fit: BoxFit.contain, errorBuilder: (_, __, ___) => - const Icon(Icons.tv, color: Colors.white24), + const Icon(Icons.tv, color: AppColors.outlineVariant), ) - : const Icon(Icons.tv, color: Colors.white24), + : const Icon(Icons.tv, color: AppColors.outlineVariant), ), const SizedBox(width: 16), Expanded( @@ -375,7 +373,7 @@ class _MobileChannelTile extends ConsumerWidget { Text( channel.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontWeight: FontWeight.w500, fontSize: 14, ), @@ -390,7 +388,7 @@ class _MobileChannelTile extends ConsumerWidget { return Text( 'No Info', style: GoogleFonts.inter( - color: Colors.white38, + color: AppColors.outline, fontSize: 12, ), ); @@ -411,11 +409,11 @@ class _MobileChannelTile extends ConsumerWidget { }, loading: () => const Text( '...', - style: TextStyle(color: Colors.white38, fontSize: 12), + style: TextStyle(color: AppColors.outline, fontSize: 12), ), error: (err, stack) => const Text( 'Err', - style: TextStyle(color: Colors.red, fontSize: 10), + style: TextStyle(color: AppColors.error, fontSize: 10), ), ), ], @@ -424,12 +422,12 @@ class _MobileChannelTile extends ConsumerWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: AppColors.surfaceContainer, shape: BoxShape.circle, ), child: const Icon( Icons.play_arrow_rounded, - color: Colors.white, + color: AppColors.onSurface, size: 16, ), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index 71b7400..21f03c3 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -195,25 +195,24 @@ class _MobileMoviesTabState extends ConsumerState { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 100, - opacity: 0.1, // Glass padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ - const Icon(Icons.search, size: 20, color: Colors.white54), + const Icon(Icons.search, size: 20, color: AppColors.onSurfaceVariant), const SizedBox(width: 8), Expanded( child: TextField( controller: _searchController, style: GoogleFonts.inter( fontSize: 14, - color: Colors.white, + color: AppColors.onSurface, ), decoration: InputDecoration( hintText: 'Search Movies...', - hintStyle: GoogleFonts.inter(color: Colors.white54), + hintStyle: GoogleFonts.inter(color: AppColors.onSurfaceVariant), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.only(bottom: 11), @@ -227,7 +226,7 @@ class _MobileMoviesTabState extends ConsumerState { height: 12, child: CircularProgressIndicator( strokeWidth: 2, - color: Colors.white, + color: AppColors.onSurface, ), ), if (_searchQuery.isNotEmpty) @@ -239,7 +238,7 @@ class _MobileMoviesTabState extends ConsumerState { child: const Icon( Icons.close, size: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -302,13 +301,13 @@ class _MobileMoviesTabState extends ConsumerState { _getProxiedImageUrl(movie.streamIcon, ref), fit: BoxFit.cover, errorBuilder: (_, __, ___) => - Container(color: Colors.white10), + Container(color: AppColors.surfaceContainerLow), ) : Container( - color: Colors.white10, + color: AppColors.surfaceContainerLow, child: const Icon( Icons.movie, - color: Colors.white24, + color: AppColors.outlineVariant, ), ), ), @@ -326,7 +325,7 @@ class _MobileMoviesTabState extends ConsumerState { end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.9), + AppColors.background.withOpacity(0.9), ], ), borderRadius: const BorderRadius.vertical( @@ -348,7 +347,7 @@ class _MobileMoviesTabState extends ConsumerState { Text( movie.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w600, ), @@ -373,9 +372,8 @@ class _MobileMoviesTabState extends ConsumerState { Positioned( top: 8, right: 8, - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 4, - opacity: 0.6, padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, @@ -394,7 +392,7 @@ class _MobileMoviesTabState extends ConsumerState { style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -415,7 +413,7 @@ class _MobileMoviesTabState extends ConsumerState { child: Padding( padding: EdgeInsets.all(24), child: Center( - child: CircularProgressIndicator(color: Colors.white), + child: CircularProgressIndicator(color: AppColors.onSurface), ), ), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index 98f470b..009c8b7 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../../core/models/playlist_config.dart'; +import '../../../../core/theme/app_colors.dart'; import '../../../../core/widgets/components/hero_carousel.dart'; import '../../../../core/widgets/glass_container.dart'; import '../../../../features/iptv/models/xtream_models.dart'; @@ -185,25 +186,24 @@ class _MobileSeriesTabState extends ConsumerState { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16), - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 100, - opacity: 0.1, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ - const Icon(Icons.search, size: 20, color: Colors.white54), + const Icon(Icons.search, size: 20, color: AppColors.onSurfaceVariant), const SizedBox(width: 8), Expanded( child: TextField( controller: _searchController, style: GoogleFonts.inter( fontSize: 14, - color: Colors.white, + color: AppColors.onSurface, ), decoration: InputDecoration( hintText: 'Search Series...', - hintStyle: GoogleFonts.inter(color: Colors.white54), + hintStyle: GoogleFonts.inter(color: AppColors.onSurfaceVariant), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.only(bottom: 11), @@ -217,7 +217,7 @@ class _MobileSeriesTabState extends ConsumerState { height: 12, child: CircularProgressIndicator( strokeWidth: 2, - color: Colors.white, + color: AppColors.onSurface, ), ), if (_searchQuery.isNotEmpty) @@ -229,7 +229,7 @@ class _MobileSeriesTabState extends ConsumerState { child: const Icon( Icons.close, size: 16, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -291,13 +291,13 @@ class _MobileSeriesTabState extends ConsumerState { _getProxiedImageUrl(series.cover, ref), fit: BoxFit.cover, errorBuilder: (_, __, ___) => - Container(color: Colors.white10), + Container(color: AppColors.surfaceContainerLow), ) : Container( - color: Colors.white10, + color: AppColors.surfaceContainerLow, child: const Icon( Icons.tv, - color: Colors.white24, + color: AppColors.outlineVariant, ), ), ), @@ -315,7 +315,7 @@ class _MobileSeriesTabState extends ConsumerState { end: Alignment.bottomCenter, colors: [ Colors.transparent, - Colors.black.withOpacity(0.9), + AppColors.background.withOpacity(0.9), ], ), borderRadius: const BorderRadius.vertical( @@ -333,7 +333,7 @@ class _MobileSeriesTabState extends ConsumerState { child: Text( series.name, style: GoogleFonts.inter( - color: Colors.white, + color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w600, ), @@ -347,9 +347,8 @@ class _MobileSeriesTabState extends ConsumerState { Positioned( top: 8, right: 8, - child: GlassContainer( + child: GlassContainer.glass( borderRadius: 4, - opacity: 0.6, padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, @@ -368,7 +367,7 @@ class _MobileSeriesTabState extends ConsumerState { style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.bold, - color: Colors.white, + color: AppColors.onSurface, ), ), ], @@ -389,7 +388,7 @@ class _MobileSeriesTabState extends ConsumerState { child: Padding( padding: EdgeInsets.all(24), child: Center( - child: CircularProgressIndicator(color: Colors.white), + child: CircularProgressIndicator(color: AppColors.onSurface), ), ), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart index b5dd22c..5840c78 100644 --- a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart @@ -112,7 +112,7 @@ class _MobileSettingsTabState extends ConsumerState { style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: AppColors.textPrimary, + color: AppColors.onSurface, ), ), if (currentUser?.isAdmin ?? false) @@ -155,7 +155,7 @@ class _MobileSettingsTabState extends ConsumerState { SwitchListTile( title: const Text( 'Dark Mode', - style: TextStyle(color: AppColors.textPrimary), + style: TextStyle(color: AppColors.onSurface), ), secondary: Icon( isDark ? Icons.dark_mode : Icons.light_mode, @@ -169,7 +169,7 @@ class _MobileSettingsTabState extends ConsumerState { SwitchListTile( title: const Text( 'Show Clock', - style: TextStyle(color: AppColors.textPrimary), + style: TextStyle(color: AppColors.onSurface), ), secondary: const Icon(Icons.access_time, color: AppColors.primary), @@ -264,7 +264,7 @@ class _MobileSettingsTabState extends ConsumerState { SwitchListTile( title: const Text( 'Auto Reconnect', - style: TextStyle(color: AppColors.textPrimary), + style: TextStyle(color: AppColors.onSurface), ), secondary: const Icon(Icons.refresh, color: AppColors.primary), @@ -303,7 +303,7 @@ class _MobileSettingsTabState extends ConsumerState { 'Currently using: ${Uri.base.origin}', style: const TextStyle( fontSize: 10, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, fontStyle: FontStyle.italic, ), ), @@ -402,7 +402,7 @@ class _SectionHeader extends StatelessWidget { style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, ), ), ); @@ -426,7 +426,7 @@ class _FilterInput extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - Icon(icon, size: 20, color: AppColors.textSecondary), + Icon(icon, size: 20, color: AppColors.onSurfaceVariant), const SizedBox(width: 12), Expanded( child: Column( @@ -436,13 +436,13 @@ class _FilterInput extends StatelessWidget { label, style: const TextStyle( fontSize: 12, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, ), ), TextField( controller: controller, style: - const TextStyle(fontSize: 14, color: AppColors.textPrimary), + const TextStyle(fontSize: 14, color: AppColors.onSurface), decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 4), @@ -483,17 +483,17 @@ class _SettingsButton extends StatelessWidget { ), child: Row( children: [ - Icon(icon, color: AppColors.textPrimary), + Icon(icon, color: AppColors.onSurface), const SizedBox(width: 16), Text( label, style: const TextStyle( fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColors.onSurface, ), ), const Spacer(), - const Icon(Icons.chevron_right, color: AppColors.textSecondary), + const Icon(Icons.chevron_right, color: AppColors.onSurfaceVariant), ], ), ), @@ -530,7 +530,7 @@ class _DropdownSetting extends StatelessWidget { child: Text( label, style: const TextStyle( - color: AppColors.textPrimary, + color: AppColors.onSurface, fontSize: 16, ), ), @@ -541,9 +541,9 @@ class _DropdownSetting extends StatelessWidget { dropdownColor: AppColors.surfaceVariant, icon: const Icon( Icons.arrow_drop_down, - color: AppColors.textSecondary, + color: AppColors.onSurfaceVariant, ), - style: const TextStyle(color: AppColors.textPrimary), + style: const TextStyle(color: AppColors.onSurface), items: items.map((item) { return DropdownMenuItem( value: item, diff --git a/lib/mobile/theme/mobile_theme.dart b/lib/mobile/theme/mobile_theme.dart index b3c3033..2fb09af 100644 --- a/lib/mobile/theme/mobile_theme.dart +++ b/lib/mobile/theme/mobile_theme.dart @@ -3,98 +3,191 @@ import 'package:google_fonts/google_fonts.dart'; import '../../core/theme/app_colors.dart'; /// XtremFlow Mobile Theme -/// +/// /// Adapted from AppTheme for touch-first screens. +/// Uses the same Cyber-Cinematic Glass design system. class MobileTheme { MobileTheme._(); - // ============ SPACING ============ - static const double spacing4 = 4.0; - static const double spacing8 = 8.0; - static const double spacing12 = 12.0; - static const double spacing16 = 16.0; - static const double spacing24 = 24.0; - + // ============ SPACING (8px rhythmic system) ============ + static const double spacingXs = 4.0; + static const double spacingSm = 8.0; + static const double spacingMd = 16.0; + static const double spacingLg = 24.0; + static const double spacingXl = 32.0; + // ============ RADIUS ============ static const double radiusSm = 8.0; static const double radiusMd = 12.0; static const double radiusLg = 16.0; + static const double radiusXl = 20.0; // ============ DARK THEME (Mobile Main) ============ static ThemeData get darkTheme { final baseTheme = ThemeData.dark(useMaterial3: true); - + return baseTheme.copyWith( colorScheme: AppColors.darkColorScheme, scaffoldBackgroundColor: AppColors.background, - + // Typography: Scaled down for mobile textTheme: TextTheme( - displayLarge: GoogleFonts.inter(fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: -1.0, color: AppColors.textPrimary), - displayMedium: GoogleFonts.inter(fontSize: 28, fontWeight: FontWeight.w700, letterSpacing: -0.5, color: AppColors.textPrimary), - displaySmall: GoogleFonts.inter(fontSize: 24, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - headlineMedium: GoogleFonts.inter(fontSize: 20, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - titleLarge: GoogleFonts.inter(fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - titleMedium: GoogleFonts.inter(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - bodyLarge: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.textSecondary), - bodyMedium: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w400, color: AppColors.textSecondary), - labelLarge: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.textPrimary), + displayLarge: GoogleFonts.spaceGrotesk( + fontSize: 32, + fontWeight: FontWeight.w700, + letterSpacing: -0.02, + height: 1.1, + color: AppColors.onSurface, + ), + displayMedium: GoogleFonts.spaceGrotesk( + fontSize: 28, + fontWeight: FontWeight.w600, + letterSpacing: -0.01, + height: 1.2, + color: AppColors.onSurface, + ), + displaySmall: GoogleFonts.spaceGrotesk( + fontSize: 22, + fontWeight: FontWeight.w500, + height: 1.3, + color: AppColors.onSurface, + ), + headlineMedium: GoogleFonts.spaceGrotesk( + fontSize: 20, + fontWeight: FontWeight.w500, + height: 1.3, + color: AppColors.onSurface, + ), + titleLarge: GoogleFonts.spaceGrotesk( + fontSize: 18, + fontWeight: FontWeight.w600, + color: AppColors.onSurface, + ), + titleMedium: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.onSurface, + ), + bodyLarge: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w400, + height: 1.5, + color: AppColors.onSurfaceVariant, + ), + bodyMedium: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.5, + color: AppColors.onSurfaceVariant, + ), + labelLarge: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + letterSpacing: 0.05, + height: 1.2, + color: AppColors.onSurface, + ), + labelMedium: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + letterSpacing: 0.02, + height: 1.2, + color: AppColors.onSurface, + ), + labelSmall: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + letterSpacing: 0.02, + height: 1.2, + color: AppColors.onSurfaceVariant, + ), ), // AppBar: Transparent / Glass appBarTheme: AppBarTheme( - backgroundColor: Colors.transparent, // Let GlassContainer handle background - foregroundColor: Colors.white, + backgroundColor: Colors.transparent, + foregroundColor: AppColors.onSurface, elevation: 0, centerTitle: true, scrolledUnderElevation: 0, - titleTextStyle: GoogleFonts.inter( + titleTextStyle: GoogleFonts.spaceGrotesk( fontSize: 18, fontWeight: FontWeight.w600, - color: Colors.white, + color: AppColors.onSurface, ), ), - + // Bottom Navigation Bar bottomNavigationBarTheme: BottomNavigationBarThemeData( - backgroundColor: const Color(0xFF1C1C1E).withOpacity(0.9), // Dark glass - selectedItemColor: Colors.white, - unselectedItemColor: Colors.white38, - selectedLabelStyle: GoogleFonts.inter(fontSize: 11, fontWeight: FontWeight.w600), - unselectedLabelStyle: GoogleFonts.inter(fontSize: 11, fontWeight: FontWeight.w500), + backgroundColor: AppColors.surfaceContainerLow.withOpacity(0.9), + selectedItemColor: AppColors.onSurface, + unselectedItemColor: AppColors.outline, + selectedLabelStyle: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + ), type: BottomNavigationBarType.fixed, elevation: 0, ), - + // Inputs inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: AppColors.surface, - contentPadding: const EdgeInsets.symmetric(horizontal: spacing16, vertical: spacing12), + fillColor: AppColors.surfaceContainerLow, + contentPadding: const EdgeInsets.symmetric( + horizontal: spacingMd, + vertical: spacingMd, + ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), - borderSide: BorderSide.none, + borderSide: const BorderSide(color: AppColors.outlineVariant, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), - borderSide: const BorderSide(color: AppColors.focusColor, width: 2), + borderSide: const BorderSide( + color: AppColors.primaryContainer, + width: 2, + ), + ), + hintStyle: GoogleFonts.inter( + color: AppColors.outline, + fontSize: 14, ), - hintStyle: GoogleFonts.inter(color: AppColors.textTertiary, fontSize: 14), ), - + // Buttons filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( - backgroundColor: AppColors.focusColor, - foregroundColor: Colors.black, + backgroundColor: AppColors.primaryContainer, + foregroundColor: AppColors.onPrimaryContainer, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusMd)), - textStyle: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd), + ), + textStyle: GoogleFonts.inter( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + + // Cards + cardTheme: CardThemeData( + color: AppColors.glassLevel1Bg, + elevation: 0, + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusLg), + side: const BorderSide(color: AppColors.glassLevel1Border, width: 1), ), ), ); diff --git a/lib/mobile/widgets/mobile_scaffold.dart b/lib/mobile/widgets/mobile_scaffold.dart index 73b621f..1001094 100644 --- a/lib/mobile/widgets/mobile_scaffold.dart +++ b/lib/mobile/widgets/mobile_scaffold.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:ui'; +import '../../../core/theme/app_colors.dart'; class MobileScaffold extends ConsumerWidget { final Widget child; @@ -19,7 +20,7 @@ class MobileScaffold extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Scaffold( extendBody: true, - backgroundColor: Colors.black, // Base color + backgroundColor: AppColors.background, body: Stack( fit: StackFit.expand, children: [ @@ -30,8 +31,8 @@ class MobileScaffold extends ConsumerWidget { center: Alignment.center, radius: 1.5, colors: [ - Color(0xFF1C1C1E), - Color(0xFF000000), + AppColors.surfaceContainerLow, + AppColors.background, ], ), ), @@ -46,10 +47,10 @@ class MobileScaffold extends ConsumerWidget { filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( decoration: BoxDecoration( - color: const Color(0xFF1C1C1E).withOpacity(0.85), + color: AppColors.surfaceContainerLow.withOpacity(0.85), border: Border( top: BorderSide( - color: Colors.white.withOpacity(0.1), + color: AppColors.outlineVariant.withOpacity(0.5), width: 0.5, ), ), @@ -60,8 +61,8 @@ class MobileScaffold extends ConsumerWidget { backgroundColor: Colors.transparent, type: BottomNavigationBarType.fixed, elevation: 0, - selectedItemColor: Colors.white, - unselectedItemColor: Colors.white38, + selectedItemColor: AppColors.onSurface, + unselectedItemColor: AppColors.onSurfaceVariant, selectedLabelStyle: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 11), unselectedLabelStyle: diff --git a/pubspec.yaml b/pubspec.yaml index e0ba01f..279136b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: google_fonts: ^6.2.1 cached_network_image: ^3.3.1 pointer_interceptor: ^0.10.1 + material_symbols_icons: ^4.2801.1 # Security crypto: ^3.0.3