From ae404622ad9fc78a7efbede8b5e3bbfc7540967f Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 13 Dec 2025 08:42:30 +0100 Subject: [PATCH] feat: implement login and dashboard screens, introducing `GlassContainer` and `TvFocusableCard` widgets. --- lib/core/widgets/glass_container.dart | 58 +++ lib/core/widgets/tv_focusable_card.dart | 115 +++++ lib/features/auth/screens/login_screen.dart | 445 ++++++++++-------- .../iptv/screens/dashboard_screen.dart | 318 ++++++------- 4 files changed, 577 insertions(+), 359 deletions(-) create mode 100644 lib/core/widgets/glass_container.dart create mode 100644 lib/core/widgets/tv_focusable_card.dart diff --git a/lib/core/widgets/glass_container.dart b/lib/core/widgets/glass_container.dart new file mode 100644 index 0000000..331568b --- /dev/null +++ b/lib/core/widgets/glass_container.dart @@ -0,0 +1,58 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import '../theme/app_colors.dart'; +import '../theme/app_theme.dart'; + +class GlassContainer extends StatelessWidget { + final Widget child; + final double? width; + final double? height; + final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? margin; + final double borderRadius; + final double opacity; + final Color? borderColor; + final bool border; + + const GlassContainer({ + super.key, + required this.child, + this.width, + this.height, + this.padding = const EdgeInsets.all(16), + this.margin, + this.borderRadius = 16.0, + this.opacity = 0.6, + this.borderColor, + this.border = true, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: width, + height: height, + margin: margin, + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: padding, + decoration: BoxDecoration( + color: AppColors.surface.withOpacity(opacity), + borderRadius: BorderRadius.circular(borderRadius), + border: border + ? Border.all( + color: borderColor ?? Colors.white.withOpacity(0.1), + width: 1, + ) + : null, + ), + child: child, + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/tv_focusable_card.dart b/lib/core/widgets/tv_focusable_card.dart new file mode 100644 index 0000000..9a23ce5 --- /dev/null +++ b/lib/core/widgets/tv_focusable_card.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import '../theme/app_colors.dart'; + +class TvFocusableCard extends StatefulWidget { + final Widget child; + final VoidCallback? onTap; + final VoidCallback? onFocus; + final double scaleFactor; + final Duration animationDuration; + final Color? focusColor; + final bool autofocus; + final double borderRadius; + + const TvFocusableCard({ + super.key, + required this.child, + this.onTap, + this.onFocus, + this.scaleFactor = 1.05, + this.animationDuration = const Duration(milliseconds: 200), + this.focusColor, + this.autofocus = false, + this.borderRadius = 12.0, + }); + + @override + State createState() => _TvFocusableCardState(); +} + +class _TvFocusableCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + bool _isFocused = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: widget.animationDuration, + ); + _scaleAnimation = Tween(begin: 1.0, end: widget.scaleFactor) + .animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleFocusChange(bool hasFocus) { + if (widget.onFocus != null && hasFocus) { + widget.onFocus!(); + } + setState(() { + _isFocused = hasFocus; + }); + if (hasFocus) { + _controller.forward(); + } else { + _controller.reverse(); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: child, + ); + }, + child: InkWell( + onTap: widget.onTap, + onFocusChange: _handleFocusChange, + autofocus: widget.autofocus, + borderRadius: BorderRadius.circular(widget.borderRadius), + focusColor: Colors.transparent, // We handle visual focus manually + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(widget.borderRadius), + border: _isFocused + ? Border.all( + color: widget.focusColor ?? AppColors.focusBorder, + width: 3, + ) + : Border.all(color: Colors.transparent, width: 3), + boxShadow: _isFocused + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + BoxShadow( + color: (widget.focusColor ?? AppColors.focusBorder).withOpacity(0.2), + blurRadius: 10, + spreadRadius: 1, + ), + ] + : [], + ), + child: widget.child, + ), + ), + ); + } +} diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index 213aeb2..9ec4371 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../providers/auth_provider.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_theme.dart'; -import '../../../core/widgets/components/ui_components.dart'; +import '../../../core/widgets/glass_container.dart'; +import '../../../core/widgets/tv_focusable_card.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key}); @@ -43,240 +45,301 @@ class _LoginScreenState extends ConsumerState { @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); + final size = MediaQuery.of(context).size; return Scaffold( backgroundColor: AppColors.background, body: Stack( + fit: StackFit.expand, children: [ - // Ambient background glow (Subtle) - Positioned( - top: -100, - left: -100, - child: Container( - width: 500, - height: 500, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [ - AppColors.primary.withOpacity(0.15), - Colors.transparent, - ], - radius: 0.6, - ), + // 1. Cinematic Background (Abstract Gradient) + 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 + ], ), ), ), - // Main content + // 2. Ambient Glows (Apple TV Style) + Positioned( + top: -size.height * 0.2, + right: -size.width * 0.1, + child: Container( + width: size.width * 0.6, + height: size.width * 0.6, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + Colors.white.withOpacity(0.05), + Colors.transparent, + ], + radius: 0.5, + ), + ), + ), + ), + Positioned( + bottom: -size.height * 0.2, + left: -size.width * 0.1, + child: Container( + width: size.width * 0.6, + height: size.width * 0.6, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + Colors.blue.withOpacity(0.03), // Subtle blue hint + Colors.transparent, + ], + radius: 0.5, + ), + ), + ), + ), + + // 3. Main Content Center Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), - child: Container( - constraints: const BoxConstraints(maxWidth: 400), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Logo and title - Container( - width: 80, - height: 80, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Logo / App Icon + Hero( + tag: 'app_logo', + child: Container( + width: 120, + height: 120, decoration: BoxDecoration( - color: AppColors.surface, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: AppColors.border), + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF2C2C2E), Color(0xFF000000)], + ), + borderRadius: BorderRadius.circular(32), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.2), + color: Colors.black.withOpacity(0.5), blurRadius: 30, - offset: const Offset(0, 10), + offset: const Offset(0, 15), ), ], ), child: const Icon( - Icons.live_tv, - color: AppColors.primary, - size: 40, + Icons.play_arrow_rounded, + color: Colors.white, + size: 64, ), ), - const SizedBox(height: 24), - const Text( - 'XtremFlow', - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.w800, - color: AppColors.textPrimary, - letterSpacing: -0.5, - ), + ), + const SizedBox(height: 48), + + // Title + Text( + 'XtremFlow', + style: GoogleFonts.inter( + fontSize: 42, + fontWeight: FontWeight.w700, + color: AppColors.textPrimary, + letterSpacing: -1.0, ), - const SizedBox(height: 8), - const Text( - 'Cinematic Experience', - style: TextStyle( - fontSize: 14, - color: AppColors.textSecondary, - letterSpacing: 2, - fontWeight: FontWeight.w500, - ), + ), + const SizedBox(height: 8), + Text( + 'Sign in to sync your playlists', + style: GoogleFonts.inter( + fontSize: 18, + color: AppColors.textSecondary, + fontWeight: FontWeight.w400, ), - const SizedBox(height: 48), - // Login form - GlassCard( - padding: const EdgeInsets.all(32), - borderRadius: 16, - showBorder: true, - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Welcome Back', - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, + ), + const SizedBox(height: 48), + + // Login Form Container + GlassContainer( + width: 500, + padding: const EdgeInsets.all(48), // Spacious padding + borderRadius: 24, + opacity: 0.7, + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Username Input + _buildTvInput( + controller: _usernameController, + label: 'Username', + icon: Icons.person_outline_rounded, + autofocus: true, + nextFocus: true, + ), + const SizedBox(height: 24), + + // Password Input + _buildTvInput( + controller: _passwordController, + label: 'Password', + icon: Icons.lock_outline_rounded, + obscureText: _obscurePassword, + isLast: true, + onSubmitted: _handleLogin, + suffix: IconButton( + icon: Icon( + _obscurePassword + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: AppColors.textSecondary, ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), - TextFormField( - controller: _usernameController, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - labelText: 'Username', - labelStyle: const TextStyle(color: Colors.white70), - prefixIcon: const Icon(Icons.person_outline, color: Colors.white70), - filled: true, - fillColor: Colors.white.withOpacity(0.05), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.primary), - ), - ), - validator: (value) { - if (value == null || value.trim().isEmpty) { - return 'Username required'; - } - return null; + onPressed: () { + setState(() { + _obscurePassword = !_obscurePassword; + }); }, - autofocus: true, - textInputAction: TextInputAction.next, ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - labelText: 'Password', - labelStyle: const TextStyle(color: Colors.white70), - prefixIcon: const Icon(Icons.lock_outline, color: Colors.white70), - filled: true, - fillColor: Colors.white.withOpacity(0.05), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.primary), - ), - suffixIcon: IconButton( - icon: Icon( - _obscurePassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - color: Colors.white70, - ), - onPressed: () { - setState(() { - _obscurePassword = !_obscurePassword; - }); - }, + ), + + // Error Message + if (authState.errorMessage != null) ...[ + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.error.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.error.withOpacity(0.3), ), ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Password required'; - } - return null; - }, - textInputAction: TextInputAction.done, - onFieldSubmitted: (_) => _handleLogin(), - ), - if (authState.errorMessage != null) ...[ - const SizedBox(height: 24), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.error.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: AppColors.error.withOpacity(0.3), + child: Row( + children: [ + const Icon( + Icons.error_outline_rounded, + color: AppColors.error, + size: 20, ), - ), - child: Row( - children: [ - Icon( - Icons.error_outline, - color: AppColors.error, - size: 20, - ), - const SizedBox(height: 8), - Expanded( - child: Text( - authState.errorMessage!, - style: const TextStyle( - color: AppColors.error, - fontSize: 13, - ), + const SizedBox(width: 12), + Expanded( + child: Text( + authState.errorMessage!, + style: const TextStyle( + color: AppColors.error, + fontSize: 14, + fontWeight: FontWeight.w500, ), ), - ], - ), + ), + ], ), - ], - const SizedBox(height: 32), - GradientButton( - label: 'Sign In', - icon: Icons.arrow_forward, - isLoading: authState.isLoading, - isExpanded: true, - onPressed: _handleLogin, ), ], - ), + + const SizedBox(height: 40), + + // Sign In Button + TvFocusableCard( + onTap: _handleLogin, + scaleFactor: 1.03, + focusColor: Colors.white, + borderRadius: 16, + child: Container( + height: 60, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: authState.isLoading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.black), + ) + : Text( + 'Sign In', + style: GoogleFonts.inter( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], ), ), - const SizedBox(height: 24), - const Text( - 'Default: admin / admin', - style: TextStyle( - fontSize: 12, - color: AppColors.textTertiary, - fontStyle: FontStyle.italic, - ), - textAlign: TextAlign.center, + ), + + const SizedBox(height: 32), + Text( + 'Default: admin / admin', + style: GoogleFonts.inter( + fontSize: 14, + color: AppColors.textTertiary, ), - ], - ), + ), + ], ), ), ), ], ), ); + } + Widget _buildTvInput({ + required TextEditingController controller, + required String label, + required IconData icon, + bool obscureText = false, + bool autofocus = false, + bool nextFocus = false, + bool isLast = false, + VoidCallback? onSubmitted, + Widget? suffix, + }) { + return TextFormField( + controller: controller, + obscureText: obscureText, + style: GoogleFonts.inter(fontSize: 18, color: AppColors.textPrimary), + 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), + suffixIcon: suffix, + filled: true, + fillColor: AppColors.surfaceVariant.withOpacity(0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.focusColor, width: 2), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return '$label required'; + } + return null; + }, + ); } } diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index c48e0f7..7e1944f 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -4,16 +4,14 @@ import 'package:google_fonts/google_fonts.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_theme.dart'; +import '../../../core/widgets/glass_container.dart'; +import '../../../core/widgets/tv_focusable_card.dart'; import '../../../core/widgets/digital_clock.dart'; import '../widgets/live_tv_tab.dart'; import '../widgets/movies_tab.dart'; import '../widgets/series_tab.dart'; import '../widgets/settings_tab.dart'; -/// Apple TV Style Dashboard -/// -/// Uses a floating Top Bar for navigation instead of a sidebar. -/// Content flows underneath the top bar. class DashboardScreen extends ConsumerStatefulWidget { final PlaylistConfig playlist; @@ -26,194 +24,178 @@ class DashboardScreen extends ConsumerStatefulWidget { ConsumerState createState() => _DashboardScreenState(); } -class _DashboardScreenState extends ConsumerState with SingleTickerProviderStateMixin { - late TabController _tabController; - - final List _tabs = ['Live TV', 'Films', 'Séries', 'Réglages']; - final List _icons = [Icons.live_tv, Icons.movie, Icons.tv, Icons.settings]; - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 4, vsync: this); - } - - @override - void dispose() { - _tabController.dispose(); - super.dispose(); - } +class _DashboardScreenState extends ConsumerState { + int _selectedIndex = 0; + final List _tabs = ['Live TV', 'Movies', 'Series', 'Settings']; + final List _icons = [ + Icons.live_tv_rounded, + Icons.movie_rounded, + Icons.tv_rounded, + Icons.settings_rounded + ]; @override Widget build(BuildContext context) { - // Glassmorphism Top Bar return Scaffold( - extendBodyBehindAppBar: true, // Content behind app bar - backgroundColor: Colors.transparent, // Transparent to show gradient - appBar: PreferredSize( - preferredSize: const Size.fromHeight(80), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.6), // Subtler top shadow - Colors.transparent, - ], - ), - ), - child: Row( - children: [ - // 1. Logo Area (Fixed Width for centering balance) - SizedBox( - width: 200, - child: Align( - alignment: Alignment.centerLeft, - child: _buildLogo(), - ), - ), - - // 2. Centered Navigation Tabs (Expanded to fill space, then Centered) - Expanded( - child: Center( - child: Container( - height: 50, - constraints: const BoxConstraints(maxWidth: 600), // Prevent taking too much space - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(30), - border: Border.all(color: Colors.white.withOpacity(0.05)), - ), - child: TabBar( - controller: _tabController, - isScrollable: true, - dividerColor: Colors.transparent, - indicator: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(25), - ), - indicatorSize: TabBarIndicatorSize.tab, - indicatorPadding: const EdgeInsets.all(4), - labelColor: Colors.black, - unselectedLabelColor: Colors.white70, // Fixed: Improved readability - labelStyle: GoogleFonts.inter(fontWeight: FontWeight.w700, fontSize: 13), - overlayColor: WidgetStateProperty.all(Colors.transparent), - tabs: List.generate(_tabs.length, (index) { - return Tab( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - Icon(_icons[index], size: 18), - const SizedBox(width: 8), - Text(_tabs[index]), - ], - ), - ), - ); - }), - ), - ), - ), - ), - - // 3. Search / Profile (Fixed Width for centering balance) - SizedBox( - width: 200, - child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.search, color: Colors.white), - onPressed: () { - // Global search trigger - }, - ), - const SizedBox(width: 16), - const DigitalClock(), - const SizedBox(width: 16), - const CircleAvatar( - radius: 16, - backgroundColor: Colors.white24, - child: Icon(Icons.person, size: 18, color: Colors.white), - ), - ], - ), - ), - ), - ], - ), - ), - ), + backgroundColor: AppColors.background, body: Stack( + fit: StackFit.expand, children: [ - // Background Gradient (Apple TV Depth Effect) - Container( - decoration: const BoxDecoration( - gradient: RadialGradient( - center: Alignment(0, -0.2), // Slightly above center - radius: 1.5, - colors: [ - Color(0xFF2C2C2E), // Dark Grey center (Light Source) - Color(0xFF000000), // Pure Black edges - ], - stops: [0.0, 1.0], + // 1. Global Atmospheric Background + Positioned.fill( + child: Container( + decoration: const BoxDecoration( + gradient: RadialGradient( + center: Alignment(0, -0.4), + radius: 1.3, + colors: [ + Color(0xFF252525), // Center light + Color(0xFF000000), // Vignet + ], + stops: [0.0, 1.0], + ), ), ), ), - // Content - TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), // Disable swipe to avoid gesture conflicts - children: [ - // Padding top to account for the transparent AppBar - Padding(padding: const EdgeInsets.only(top: 80), child: LiveTVTab(playlist: widget.playlist)), - Padding(padding: const EdgeInsets.only(top: 80), child: MoviesTab(playlist: widget.playlist)), - Padding(padding: const EdgeInsets.only(top: 80), child: SeriesTab(playlist: widget.playlist)), - const Padding(padding: EdgeInsets.only(top: 80), child: SettingsTab()), - ], + // 2. Main Content Area + Positioned.fill( + top: 0, // Content goes behind header + child: IndexedStack( + index: _selectedIndex, + children: [ + // Add padding top internally in tabs or here + Padding(padding: const EdgeInsets.only(top: 100), child: LiveTVTab(playlist: widget.playlist)), + Padding(padding: const EdgeInsets.only(top: 100), child: MoviesTab(playlist: widget.playlist)), + Padding(padding: const EdgeInsets.only(top: 100), child: SeriesTab(playlist: widget.playlist)), + const Padding(padding: EdgeInsets.only(top: 100), child: SettingsTab()), + ], + ), + ), + + // 3. Floating Glass Header + Positioned( + top: 24, // Floating margin + left: 24, + right: 24, + child: GlassContainer( + height: 72, + borderRadius: 100, // Pill shape + opacity: 0.85, + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + // Logo + _buildLogo(), + + const Spacer(), + + // Navigation Pills + Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(_tabs.length, (index) { + final isSelected = _selectedIndex == index; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: TvFocusableCard( + onTap: () => setState(() => _selectedIndex = index), + scaleFactor: 1.1, + borderRadius: 100, + focusColor: Colors.white, + child: AnimatedContainer( + duration: AppTheme.durationFast, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: isSelected ? Colors.white : Colors.transparent, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + Icon( + _icons[index], + size: 18, + color: isSelected ? Colors.black : AppColors.textSecondary, + ), + const SizedBox(width: 8), + Text( + _tabs[index], + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500, + color: isSelected ? Colors.black : AppColors.textSecondary, + ), + ), + ], + ), + ), + ), + ); + }), + ), + + const Spacer(), + + // Tools + Row( + children: [ + _buildIconButton(Icons.search_rounded, () {}), + const SizedBox(width: 16), + const DigitalClock(), // Kept existing widget + const SizedBox(width: 16), + CircleAvatar( + radius: 18, + backgroundColor: Colors.white.withOpacity(0.1), + child: const Icon(Icons.person_rounded, size: 20, color: Colors.white), + ), + ], + ), + ], + ), + ), ), ], ), ); } + Widget _buildIconButton(IconData icon, VoidCallback onTap) { + return TvFocusableCard( + onTap: onTap, + borderRadius: 50, + scaleFactor: 1.15, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon(icon, color: Colors.white, size: 20), + ), + ); + } + Widget _buildLogo() { return Row( children: [ Container( - width: 36, - height: 36, + width: 32, + height: 32, decoration: BoxDecoration( - gradient: const LinearGradient(colors: [Color(0xFF43cea2), Color(0xFF185a9d)]), // Green to Blue - borderRadius: BorderRadius.circular(10), + gradient: const LinearGradient(colors: [Colors.white, Color(0xFFAAAAAA)]), + borderRadius: BorderRadius.circular(8), ), - child: const Icon(Icons.play_arrow_rounded, color: Colors.white), + child: const Icon(Icons.play_arrow_rounded, color: Colors.black, size: 24), ), const SizedBox(width: 12), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Xtrem', - style: GoogleFonts.inter(fontWeight: FontWeight.w800, fontSize: 16, color: Colors.white), - ), - Text( - 'Flow', - style: GoogleFonts.inter( - fontWeight: FontWeight.w300, - fontSize: 16, - color: Colors.white.withOpacity(0.8), - height: 0.8, - ), - ), - ], + Text( + 'XtremFlow', + style: GoogleFonts.inter( + fontWeight: FontWeight.w700, + fontSize: 18, + color: Colors.white, + letterSpacing: -0.5, + ), ), ], );