From 3145af9e8dedc5af337396ccda4624bb3d1e43a5 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 20 Dec 2025 11:39:37 +0100 Subject: [PATCH] feat: Add mobile IPTV tabs for movies, series, and live TV. --- lib/core/theme/app_theme.dart | 93 ++++++++++++++----- .../iptv/widgets/mobile_live_tv_tab.dart | 11 ++- .../iptv/widgets/mobile_movies_tab.dart | 9 +- .../iptv/widgets/mobile_series_tab.dart | 9 +- 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 2de9198..c00066d 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -3,7 +3,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'app_colors.dart'; /// XtremFlow Apple TV Theme -/// +/// /// Focus-driven, immersive, minimal. class AppTheme { AppTheme._(); @@ -27,27 +27,64 @@ class AppTheme { // ============ 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; // Apple-like snappy curve // ============ DARK THEME (TV Main) ============ static ThemeData get darkTheme { final baseTheme = ThemeData.dark(useMaterial3: true); - + return baseTheme.copyWith( colorScheme: AppColors.darkColorScheme, scaffoldBackgroundColor: AppColors.background, - + + // Global Text Selection Theme (Fixes black cursor issues) + textSelectionTheme: const TextSelectionThemeData( + cursorColor: Colors.white, + selectionColor: Color(0x4DFFFFFF), // 30% White + selectionHandleColor: Colors.white, + ), + // Typography: San Francisco style (using Inter as proxy) textTheme: TextTheme( - displayLarge: GoogleFonts.inter(fontSize: 56, fontWeight: FontWeight.w700, letterSpacing: -1.0, color: AppColors.textPrimary), - displayMedium: GoogleFonts.inter(fontSize: 48, fontWeight: FontWeight.w700, letterSpacing: -0.5, color: AppColors.textPrimary), - displaySmall: GoogleFonts.inter(fontSize: 32, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - headlineMedium: GoogleFonts.inter(fontSize: 24, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - titleLarge: GoogleFonts.inter(fontSize: 20, fontWeight: FontWeight.w600, color: AppColors.textPrimary), - titleMedium: GoogleFonts.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(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary), + displayLarge: GoogleFonts.inter( + fontSize: 56, + fontWeight: FontWeight.w700, + letterSpacing: -1.0, + color: AppColors.textPrimary), + displayMedium: GoogleFonts.inter( + fontSize: 48, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + color: AppColors.textPrimary), + displaySmall: GoogleFonts.inter( + fontSize: 32, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary), + headlineMedium: GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary), + titleLarge: GoogleFonts.inter( + fontSize: 20, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary), + titleMedium: GoogleFonts.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( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.textPrimary), ), // AppBar: Transparent / Glass @@ -63,12 +100,13 @@ class AppTheme { color: AppColors.textPrimary, ), ), - + // Inputs: Dark, minimal, no borders unless focused inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColors.surface, - contentPadding: const EdgeInsets.symmetric(horizontal: spacing24, vertical: spacing16), + contentPadding: const EdgeInsets.symmetric( + horizontal: spacing24, vertical: spacing16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide.none, @@ -83,7 +121,7 @@ class AppTheme { ), hintStyle: GoogleFonts.inter(color: AppColors.textTertiary), ), - + // Buttons: White pill or minimal text filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( @@ -91,17 +129,21 @@ class AppTheme { foregroundColor: Colors.black, // Black text elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(radiusMd)), - textStyle: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radiusMd)), + textStyle: + GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16), ).copyWith( overlayColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.hovered)) return Colors.black.withOpacity(0.1); - if (states.contains(WidgetState.pressed)) return Colors.black.withOpacity(0.2); + if (states.contains(WidgetState.hovered)) + return Colors.black.withOpacity(0.1); + if (states.contains(WidgetState.pressed)) + return Colors.black.withOpacity(0.2); return null; }), ), ), - + // Cards: Transparent by default (content defines look) // cardTheme: const CardTheme( // color: AppColors.surface, @@ -124,8 +166,9 @@ 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), - ); + static ThemeData get lightTheme => + ThemeData.light(useMaterial3: true).copyWith( + colorScheme: AppColors.lightColorScheme, + textTheme: GoogleFonts.interTextTheme(ThemeData.light().textTheme), + ); } 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 7b93ab3..8358c4c 100644 --- a/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_live_tv_tab.dart @@ -171,6 +171,8 @@ class _MobileLiveTVTabState extends ConsumerState // Search Bar - Wrapped in TVFocusable for remote access TVFocusable( scale: 1.0, // Disable scaling to prevent overflow + focusColor: + Colors.white, // Solid white selection frame onPressed: () { setState(() => _isSearchEditing = true); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -185,9 +187,7 @@ class _MobileLiveTVTabState extends ConsumerState decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), - border: _isSearchEditing - ? Border.all(color: AppColors.primary) - : null, + // Removed internal editing border here to prevent conflict and "inside frame" look ), padding: const EdgeInsets.symmetric(horizontal: 12), @@ -200,6 +200,8 @@ class _MobileLiveTVTabState extends ConsumerState child: ExcludeFocus( excluding: !_isSearchEditing, child: TextField( + cursorColor: + Colors.white, // Extra safety controller: _searchController, focusNode: _searchFocusNode, readOnly: !_isSearchEditing, @@ -208,6 +210,9 @@ class _MobileLiveTVTabState extends ConsumerState decoration: const InputDecoration( hintText: 'Rechercher une chaîne...', border: InputBorder.none, + focusedBorder: InputBorder + .none, // Explicitly remove focus border + enabledBorder: InputBorder.none, isDense: true, ), onSubmitted: (_) => setState( diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index 75ff67f..576749f 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -280,6 +280,7 @@ class _MobileMoviesTabState extends ConsumerState const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: TVFocusable( scale: 1.0, // Disable scaling to prevent overflow + focusColor: Colors.white, // Solid white selection frame onPressed: () { setState(() => _isSearchEditing = true); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -292,9 +293,7 @@ class _MobileMoviesTabState extends ConsumerState decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), - border: _isSearchEditing - ? Border.all(color: AppColors.primary) - : Border.all(color: AppColors.border), + // Removed internal editing border here to prevent conflict and "inside frame" look ), padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( @@ -306,6 +305,7 @@ class _MobileMoviesTabState extends ConsumerState child: ExcludeFocus( excluding: !_isSearchEditing, child: TextField( + cursorColor: Colors.white, // Extra safety controller: _searchController, focusNode: _searchFocusNode, readOnly: !_isSearchEditing, @@ -314,6 +314,9 @@ class _MobileMoviesTabState extends ConsumerState decoration: const InputDecoration( hintText: 'Rechercher un film...', border: InputBorder.none, + focusedBorder: InputBorder + .none, // Explicitly remove focus border + enabledBorder: InputBorder.none, isDense: true, contentPadding: EdgeInsets.only(bottom: 11), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index a62ca19..f94ff0a 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -259,6 +259,7 @@ class _MobileSeriesTabState extends ConsumerState const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: TVFocusable( scale: 1.0, // Disable scaling to prevent overflow + focusColor: Colors.white, // Solid white selection frame onPressed: () { setState(() => _isSearchEditing = true); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -271,9 +272,7 @@ class _MobileSeriesTabState extends ConsumerState decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), - border: _isSearchEditing - ? Border.all(color: AppColors.primary) - : Border.all(color: AppColors.border), + // Removed internal editing border here to prevent conflict and "inside frame" look ), padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( @@ -285,6 +284,7 @@ class _MobileSeriesTabState extends ConsumerState child: ExcludeFocus( excluding: !_isSearchEditing, child: TextField( + cursorColor: Colors.white, // Extra safety controller: _searchController, focusNode: _searchFocusNode, readOnly: !_isSearchEditing, @@ -293,6 +293,9 @@ class _MobileSeriesTabState extends ConsumerState decoration: const InputDecoration( hintText: 'Rechercher une série...', border: InputBorder.none, + focusedBorder: InputBorder + .none, // Explicitly remove focus border + enabledBorder: InputBorder.none, isDense: true, contentPadding: EdgeInsets.only(bottom: 11), ),