feat: Add mobile-specific Riverpod providers for Xtream service, live TV, movies, series, and EPG data, along with UI state management.

This commit is contained in:
Michael committed 2026-03-27 13:34:06 +01:00
1 parent f85f43e42f
commit e1a0802a0c
18 files changed
+1619 -574

No files matched your search

+114 -40
View File
@@ -1,81 +1,155 @@
import 'package:flutter/material.dart';
/// XtremFlow Apple TV Style Palette
/// XtremFlow Apple TV Grey Glossy Palette
///
/// Ultra minimalist, focus-driven palette.
/// - Background: Pure Black (#000000)
/// - Focus/Active: Pure White (#FFFFFF)
/// - Inactive: Grey (#9E9E9E)
/// - Glass: Heavy blur with low opacity
/// Premium Apple TV design language:
/// - Background: Deep dark grey (#1C1C1E) to black (#000000) gradient
/// - Surface cards: Glossy dark grey (#2C2C2E to #3A3A3C) with glass reflections
/// - Primary accent: Bright white (#FFFFFF) and light grey (#EBEBF5)
/// - Secondary accent: Apple Blue (#0A84FF)
/// - Borders: Subtle white 8% opacity for glass effect
/// - Glossy highlight: White 10-15% at top fading to transparent
class AppColors {
AppColors._();
// ============ BACKGROUNDS ============
/// Pure black for that infinite depth look (OLED friendly)
/// Pure black — infinite depth, OLED-friendly
static const Color background = Color(0xFF000000);
/// Very deep grey for surfaces that need to be distinct but subtle (Apple Dark Grey)
/// Apple deep dark grey — primary app background (alias)
static const Color backgroundSecondary = Color(0xFF1C1C1E);
/// Apple deep dark grey — primary surface
static const Color surface = Color(0xFF1C1C1E);
/// Slightly lighter grey for secondary surfaces or hover states
static const Color surfaceVariant = Color(0xFF3A3A3C);
/// Mid grey — elevated cards, sheets
static const Color surfaceVariant = Color(0xFF2C2C2E);
/// Focused element background (often white in tvOS for text, or bright accent)
/// Light grey surface — tertiary elevation
static const Color surfaceElevated = Color(0xFF3A3A3C);
/// Focus/selected element background
static const Color focusColor = Color(0xFFFFFFFF);
// ============ ACCENTS ============
/// Minimal white accent. In Apple TV, color is used sparingly.
/// We keep a subtle blue only for specific indicators if needed.
/// Primary: Pure white — focus rings, active labels
static const Color primary = Color(0xFFFFFFFF);
static const Color accent = Color(0xFFE5E5E5);
/// Primary Gradient (Subtle silver/white glow)
/// Light grey — secondary text / inactive elements
static const Color accent = Color(0xFFEBEBF5);
/// Apple Blue — CTAs, links, active indicators
static const Color appleBlue = Color(0xFF0A84FF);
// ============ GRADIENTS ============
/// Apple TV background: deep dark top → pure black bottom
static const LinearGradient backgroundGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF1C1C1E), // Deep dark grey
Color(0xFF000000), // Pure black
],
stops: [0.0, 1.0],
);
/// Legacy alias for backward compatibility
static const RadialGradient appleTvGradient = RadialGradient(
center: Alignment.center,
radius: 1.5,
colors: [
Color(0xFF1A1A1C), // Slightly lighter than pure black
Color(0xFF000000), // Pure black edge
],
stops: [0.0, 1.0],
);
/// Glossy card gradient: bright highlight at top fading to dark base
static const LinearGradient glossyCardGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF484848), // Lighter top for gloss highlight
Color(0xFF2C2C2E), // Dark mid
Color(0xFF1C1C1E), // Deeper bottom
],
stops: [0.0, 0.45, 1.0],
);
/// Glossy inner highlight (white shimmer layer, overlaid on top of card)
static const LinearGradient glossyHighlight = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x26FFFFFF), // White ~15% at top
Color(0x00FFFFFF), // Transparent at bottom
],
stops: [0.0, 0.5],
);
/// Card glossy gradient (Apple TV spec alias — dark base)
static const LinearGradient cardGlossyGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF48484A), Color(0xFF2C2C2E)],
);
/// Card glossy highlight (Apple TV spec alias — top-left shimmer)
static const LinearGradient cardGlossyHighlight = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0x26FFFFFF), Color(0x00FFFFFF)],
stops: [0.0, 0.5],
);
/// Primary silver gradient (used for buttons/badges)
static const LinearGradient primaryGradient = LinearGradient(
colors: [Color(0xFFFFFFFF), Color(0xFFCCCCCC)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Apple TV Style Background Gradient (Visible gradient)
static const RadialGradient appleTvGradient = RadialGradient(
center: Alignment.center,
radius: 1.5,
colors: [
Color(0xFF151515), // Deep Dark Grey center (barely visible)
Color(0xFF000000), // Pure Black edge
],
stops: [0.0, 1.0],
);
// ============ TEXT ============
/// Pure white — headings, active labels
static const Color textPrimary = Color(0xFFFFFFFF);
static const Color textSecondary = Color(0xFF9E9E9E); // Medium Grey
static const Color textTertiary = Color(0xFF616161); // Darker Grey
/// Light grey 60% — secondary info, captions
static const Color textSecondary = Color(0x99EBEBF5); // #EBEBF5 @ 60%
/// Darker grey — hints, placeholders
static const Color textTertiary = Color(0xFF636366);
// ============ FUNCTIONAL COLORS ============
static const Color success = Color(0xFF4DB6AC); // Teal-ish
static const Color warning = Color(0xFFFFB74D);
static const Color error = Color(0xFFEF5350);
static const Color live = Color(0xFFFF3B30); // Apple Red
static const Color success = Color(0xFF30D158); // Apple Green
static const Color warning = Color(0xFFFF9F0A); // Apple Orange
static const Color error = Color(0xFFFF453A); // Apple Red
static const Color live = Color(0xFFFF453A); // Apple Red (live badge)
// ============ BORDERS ============
// Borders are usually hidden until focused
static const Color border = Color(0xFF1F1F1F);
/// Invisible border — unfocused state
static const Color border = Color(0xFF2C2C2E);
/// Glass border: white at 8% opacity for subtle edge definition
static const Color glassBorderColor = Color(0x14FFFFFF); // 8% white
/// Focus border: pure white ring
static const Color focusBorder = Color(0xFFFFFFFF);
/// Alias: focused border — pure white (Apple TV spec)
static const Color borderFocused = Color(0xFFFFFFFF);
// ============ 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);
static final Color glassBackground = const Color(0xFF1E1E1E).withValues(alpha: 0.75);
static final Color glassBorder = const Color(0xFFFFFFFF).withValues(alpha: 0.08);
// ============ THEME SCHEMES ============
static ColorScheme get darkColorScheme => const ColorScheme.dark(
primary: primary,
primary: appleBlue,
secondary: accent,
surface: surface,
error: error,
onPrimary: Colors
.black, // White text on buttons -> Black text on White buttons
onPrimary: Colors.white,
onSecondary: Colors.black,
onSurface: textPrimary,
onError: Colors.white,
+194
View File
@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
/// Theme-aware decoration helpers.
/// Returns dark (Apple TV glossy) or light (iOS clean white) styles
/// based on current [BuildContext] brightness.
class AppDecorations {
AppDecorations._();
static bool _isDark(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark;
// ─── Backgrounds ────────────────────────────────────────────────────────────
/// Full-screen gradient background for tab content areas.
static BoxDecoration background(BuildContext context) {
if (_isDark(context)) {
return const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF1C1C1E), Color(0xFF000000)],
stops: [0.0, 1.0],
),
);
}
return const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFEAEAF0), Color(0xFFD8D8E0)],
stops: [0.0, 1.0],
),
);
}
// ─── Nav Pill ────────────────────────────────────────────────────────────────
/// Floating pill navigation bar background.
static BoxDecoration navPill(BuildContext context) {
if (_isDark(context)) {
return BoxDecoration(
color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(40),
border: Border.all(color: const Color(0x20FFFFFF), width: 1),
boxShadow: const [
BoxShadow(
color: Color(0x66000000),
blurRadius: 20,
spreadRadius: 0,
offset: Offset(0, 6),
),
],
);
}
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
border: Border.all(color: const Color(0x1A000000), width: 1),
boxShadow: const [
BoxShadow(
color: Color(0x1A000000),
blurRadius: 16,
spreadRadius: 0,
offset: Offset(0, 4),
),
],
);
}
// ─── Cards ───────────────────────────────────────────────────────────────────
/// Glossy category / channel card — primary card style.
static BoxDecoration glossyCard(
BuildContext context, {
double radius = 16,
}) {
if (_isDark(context)) {
return BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF4A4A4E), Color(0xFF1C1C1E)],
stops: [0.0, 1.0],
),
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: const Color(0x28FFFFFF), width: 1),
boxShadow: const [
BoxShadow(
color: Color(0xAA000000),
blurRadius: 20,
spreadRadius: 0,
offset: Offset(0, 8),
),
],
);
}
return BoxDecoration(
color: const Color(0xFFF2F2F7),
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: const Color(0x1A000000), width: 1),
boxShadow: const [
BoxShadow(
color: Color(0x14000000),
blurRadius: 8,
spreadRadius: 0,
offset: Offset(0, 2),
),
],
);
}
/// Gloss shimmer overlay — placed on top-left of a card Stack.
static BoxDecoration glossShimmer(
BuildContext context, {
double radius = 16,
}) {
if (_isDark(context)) {
return BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x40FFFFFF), Color(0x00FFFFFF)],
stops: [0.0, 0.55],
),
borderRadius:
BorderRadius.vertical(top: Radius.circular(radius)),
);
}
return BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x0A000000), Color(0x00000000)],
stops: [0.0, 0.55],
),
borderRadius:
BorderRadius.vertical(top: Radius.circular(radius)),
);
}
/// Smaller channel card base (top area behind the logo).
static BoxDecoration channelCardBase(BuildContext context) {
if (_isDark(context)) {
return const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF3A3A3C), Color(0xFF1C1C1E)],
),
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
);
}
return const BoxDecoration(
color: Color(0xFFE4E4EC),
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
);
}
// ─── Search Bar ──────────────────────────────────────────────────────────────
/// Search / text field container.
static BoxDecoration searchBar(BuildContext context) {
if (_isDark(context)) {
return BoxDecoration(
color: const Color(0xFF2C2C2E),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0x18FFFFFF), width: 1),
);
}
return BoxDecoration(
color: const Color(0xFFEEEEF4),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0x18000000), width: 1),
);
}
// ─── Colours ─────────────────────────────────────────────────────────────────
static Color textPrimary(BuildContext context) =>
_isDark(context) ? Colors.white : const Color(0xFF000000);
static Color textSecondary(BuildContext context) =>
_isDark(context)
? const Color(0x99EBEBF5)
: const Color(0x993C3C43);
static Color iconMuted(BuildContext context) =>
_isDark(context) ? Colors.white38 : Colors.black26;
static Color divider(BuildContext context) =>
_isDark(context)
? const Color(0x1FFFFFFF)
: const Color(0x1A000000);
}
+185 -6
View File
@@ -179,10 +179,189 @@ class AppTheme {
static const double radiusFull = 999.0;
// Light theme stub
static ThemeData get lightTheme =>
ThemeData.light(useMaterial3: true).copyWith(
colorScheme: AppColors.lightColorScheme,
textTheme: GoogleFonts.interTextTheme(ThemeData.light().textTheme),
);
// ============ LIGHT THEME (Apple Clean White) ============
static ThemeData get lightTheme {
final baseTheme = ThemeData.light(useMaterial3: true);
// Apple light palette
const bgColor = Color(0xFFF2F2F7); // Apple system grey6
const surfaceColor = Color(0xFFFFFFFF); // Pure white surfaces
const primaryColor = Color(0xFF007AFF); // Apple Blue (light)
const textPrimaryColor = Color(0xFF000000);
const textSecondaryColor = Color(0x993C3C43); // #3C3C43 @ 60%
const textTertiaryColor = Color(0xFFC7C7CC);
const borderColor = Color(0x1A000000); // #000000 @ 10%
return baseTheme.copyWith(
colorScheme: AppColors.lightColorScheme.copyWith(
primary: primaryColor,
secondary: const Color(0xFF34C759), // Apple green
surface: surfaceColor,
onPrimary: Colors.white,
onSurface: textPrimaryColor,
brightness: Brightness.light,
),
scaffoldBackgroundColor: bgColor,
// Text selection — blue cursor on light
textSelectionTheme: const TextSelectionThemeData(
cursorColor: primaryColor,
selectionColor: Color(0x4D007AFF),
selectionHandleColor: primaryColor,
),
// Typography
textTheme: TextTheme(
displayLarge: GoogleFonts.inter(
fontSize: 56,
fontWeight: FontWeight.w700,
letterSpacing: -1.0,
color: textPrimaryColor,
),
displayMedium: GoogleFonts.inter(
fontSize: 48,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
color: textPrimaryColor,
),
displaySmall: GoogleFonts.inter(
fontSize: 32,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
headlineMedium: GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
titleLarge: GoogleFonts.inter(
fontSize: 20,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
titleMedium: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
bodyLarge: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w400,
color: textSecondaryColor,
),
bodyMedium: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
color: textSecondaryColor,
),
labelLarge: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
),
// AppBar: White with subtle bottom shadow
appBarTheme: AppBarTheme(
backgroundColor: surfaceColor,
foregroundColor: textPrimaryColor,
elevation: 0,
scrolledUnderElevation: 1,
shadowColor: borderColor,
centerTitle: true,
titleTextStyle: GoogleFonts.inter(
fontSize: 17,
fontWeight: FontWeight.w600,
color: textPrimaryColor,
),
),
// Bottom Navigation Bar: white, Apple Blue selected
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: surfaceColor,
selectedItemColor: primaryColor,
unselectedItemColor: textSecondaryColor,
selectedLabelStyle: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w500,
),
type: BottomNavigationBarType.fixed,
elevation: 8,
),
// Inputs: white fill, subtle border
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surfaceColor,
contentPadding: const EdgeInsets.symmetric(
horizontal: spacing24,
vertical: spacing16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(radiusMd),
borderSide: const BorderSide(color: borderColor, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(radiusMd),
borderSide: const BorderSide(color: borderColor, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(radiusMd),
borderSide: const BorderSide(color: primaryColor, width: 2),
),
hintStyle: GoogleFonts.inter(color: textTertiaryColor),
),
// Buttons: Apple Blue pill
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radiusMd),
),
textStyle:
GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16),
),
),
// Cards: white with subtle shadow
cardTheme: const CardThemeData(
color: surfaceColor,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radiusMd)),
side: BorderSide(color: borderColor, width: 1),
),
shadowColor: borderColor,
),
// Dialogs: white, clean
dialogTheme: DialogThemeData(
backgroundColor: surfaceColor,
elevation: 8,
shadowColor: borderColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(radiusLg),
side: const BorderSide(color: borderColor, width: 1),
),
titleTextStyle: GoogleFonts.inter(
fontSize: 18,
fontWeight: FontWeight.bold,
color: textPrimaryColor,
),
contentTextStyle: GoogleFonts.inter(
fontSize: 14,
color: textSecondaryColor,
),
),
);
}
}