diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 9e83a24..4dd1661 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -290,19 +290,16 @@ class _HomeScreenState extends State { children: [ Expanded( flex: 2, - child: Focus( - autofocus: _focusedIndex == 0, - child: _DashboardCard( - title: 'LIVE TV', - icon: Icons.tv, - isLarge: _isLargeScreen, - gradient: const LinearGradient( - colors: [Color(0xFF00c853), Color(0xFF2979ff)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - onTap: _showLiveCategories, + child: _DashboardCard( + title: 'LIVE TV', + icon: Icons.tv, + isLarge: _isLargeScreen, + gradient: const LinearGradient( + colors: [Color(0xFF00c853), Color(0xFF2979ff)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), + onTap: _showLiveCategories, ), ), SizedBox(width: cardSpacing), @@ -310,36 +307,30 @@ class _HomeScreenState extends State { child: Column( children: [ Expanded( - child: Focus( - autofocus: _focusedIndex == 1, - child: _DashboardCard( - title: 'MOVIES', - icon: Icons.play_circle_fill, - isLarge: _isLargeScreen, - gradient: const LinearGradient( - colors: [Color(0xFFff5252), Color(0xFFff9800)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - onTap: _showMovies, + child: _DashboardCard( + title: 'MOVIES', + icon: Icons.play_circle_fill, + isLarge: _isLargeScreen, + gradient: const LinearGradient( + colors: [Color(0xFFff5252), Color(0xFFff9800)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), + onTap: _showMovies, ), ), SizedBox(height: cardSpacing), Expanded( - child: Focus( - autofocus: _focusedIndex == 2, - child: _DashboardCard( - title: 'SERIES', - icon: Icons.movie, - isLarge: _isLargeScreen, - gradient: const LinearGradient( - colors: [Color(0xFF9c27b0), Color(0xFF03a9f4)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - onTap: _showSeries, + child: _DashboardCard( + title: 'SERIES', + icon: Icons.tv, + isLarge: _isLargeScreen, + gradient: const LinearGradient( + colors: [Color(0xFF9c27b0), Color(0xFF03a9f4)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), + onTap: _showSeries, ), ), ], @@ -383,7 +374,7 @@ class _HomeScreenState extends State { } } -class _DashboardCard extends StatelessWidget { +class _DashboardCard extends StatefulWidget { final String title; final IconData icon; final Gradient gradient; @@ -398,70 +389,104 @@ class _DashboardCard extends StatelessWidget { this.isLarge = false, }); + @override + State<_DashboardCard> createState() => _DashboardCardState(); +} + +class _DashboardCardState extends State<_DashboardCard> { + bool _hasFocus = false; + + void _handleTap() { + widget.onTap(); + } + + void _handleKeyEvent(KeyEvent event) { + if (event is KeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.enter || + event.logicalKey == LogicalKeyboardKey.select || + event.logicalKey == LogicalKeyboardKey.space) { + _handleTap(); + } + } + } + @override Widget build(BuildContext context) { - final iconSize = isLarge ? 80.0 : 60.0; - final titleSize = isLarge ? 32.0 : 24.0; - final bgIconSize = isLarge ? 200.0 : 150.0; - return Focus( - canRequestFocus: true, - child: Builder( - builder: (context) { - final hasFocus = Focus.of(context).hasFocus; - return InkWell( - onTap: onTap, + final iconSize = widget.isLarge ? 80.0 : 60.0; + final titleSize = widget.isLarge ? 32.0 : 24.0; + final bgIconSize = widget.isLarge ? 200.0 : 150.0; + + return FocusableActionDetector( + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (intent) { + _handleTap(); + return null; + }, + ), + ButtonActivateIntent: CallbackAction( + onInvoke: (intent) { + _handleTap(); + return null; + }, + ), + }, + onFocusChange: (hasFocus) { + setState(() { + _hasFocus = hasFocus; + }); + }, + child: GestureDetector( + onTap: _handleTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + gradient: widget.gradient, borderRadius: BorderRadius.circular(20), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - gradient: gradient, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: hasFocus ? Colors.white.withValues(alpha: 0.4) : Colors.black.withValues(alpha: 0.3), - blurRadius: hasFocus ? 30 : 15, - spreadRadius: hasFocus ? 4 : 0, - offset: const Offset(0, 8), - ), - ], - border: hasFocus - ? Border.all(color: Colors.white, width: 4) - : Border.all(color: Colors.transparent, width: 4), + boxShadow: [ + BoxShadow( + color: _hasFocus ? Colors.white.withValues(alpha: 0.6) : Colors.black.withValues(alpha: 0.3), + blurRadius: _hasFocus ? 35 : 15, + spreadRadius: _hasFocus ? 6 : 0, + offset: const Offset(0, 8), ), - child: Stack( - children: [ - Positioned( - right: -40, - bottom: -40, - child: Icon( - icon, - size: bgIconSize, - color: Colors.white.withValues(alpha: 0.1), - ), - ), - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: iconSize, color: Colors.white), - const SizedBox(height: 16), - Text( - title, - style: TextStyle( - color: Colors.white, - fontSize: titleSize, - fontWeight: FontWeight.bold, - letterSpacing: 2, - ), - ), - ], - ), - ), - ], + ], + border: _hasFocus + ? Border.all(color: Colors.white, width: 5) + : Border.all(color: Colors.transparent, width: 5), + ), + child: Stack( + children: [ + Positioned( + right: -40, + bottom: -40, + child: Icon( + widget.icon, + size: bgIconSize, + color: Colors.white.withValues(alpha: 0.1), + ), ), - ), - ); - }, + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(widget.icon, size: iconSize, color: Colors.white), + const SizedBox(height: 16), + Text( + widget.title, + style: TextStyle( + color: Colors.white, + fontSize: titleSize, + fontWeight: FontWeight.bold, + letterSpacing: 2, + ), + ), + ], + ), + ), + ], + ), + ), ), ); } @@ -791,7 +816,7 @@ class _ContentListScreenState extends State { } } -class _ChannelCard extends StatelessWidget { +class _ChannelCard extends StatefulWidget { final XtreamStream stream; final bool isSeries; final VoidCallback onTap; @@ -804,46 +829,73 @@ class _ChannelCard extends StatelessWidget { this.isLarge = false, }); + @override + State<_ChannelCard> createState() => _ChannelCardState(); +} + +class _ChannelCardState extends State<_ChannelCard> { + bool _hasFocus = false; + + void _handleTap() { + widget.onTap(); + } + @override Widget build(BuildContext context) { - final textSize = isLarge ? 16.0 : 12.0; - final ratingFontSize = isLarge ? 14.0 : 10.0; - final placeholderIconSize = isLarge ? 56.0 : 40.0; - final padding = isLarge ? 12.0 : 8.0; - final ratingPaddingH = isLarge ? 10.0 : 6.0; - final ratingPaddingV = isLarge ? 4.0 : 2.0; - return Focus( - child: Builder( - builder: (context) { - final hasFocus = Focus.of(context).hasFocus; - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - color: Colors.grey[900], - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: hasFocus ? Colors.red : Colors.red.withValues(alpha: 0.3), - width: hasFocus ? 3 : 1, - ), - boxShadow: hasFocus - ? [ - BoxShadow( - color: Colors.red.withValues(alpha: 0.3), - blurRadius: 15, - spreadRadius: 2, - ), - ] - : null, - ), - child: Stack( + final textSize = widget.isLarge ? 16.0 : 12.0; + final ratingFontSize = widget.isLarge ? 14.0 : 10.0; + final placeholderIconSize = widget.isLarge ? 56.0 : 40.0; + final padding = widget.isLarge ? 12.0 : 8.0; + final ratingPaddingH = widget.isLarge ? 10.0 : 6.0; + final ratingPaddingV = widget.isLarge ? 4.0 : 2.0; + return FocusableActionDetector( + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (intent) { + _handleTap(); + return null; + }, + ), + ButtonActivateIntent: CallbackAction( + onInvoke: (intent) { + _handleTap(); + return null; + }, + ), + }, + onFocusChange: (hasFocus) { + setState(() { + _hasFocus = hasFocus; + }); + }, + child: GestureDetector( + onTap: _handleTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: Colors.grey[900], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: _hasFocus ? Colors.red : Colors.red.withValues(alpha: 0.3), + width: _hasFocus ? 3 : 1, + ), + boxShadow: _hasFocus + ? [ + BoxShadow( + color: Colors.red.withValues(alpha: 0.3), + blurRadius: 15, + spreadRadius: 2, + ), + ] + : null, + ), + child: Stack( children: [ - if (stream.streamIcon != null && stream.streamIcon!.isNotEmpty) + if (widget.stream.streamIcon != null && widget.stream.streamIcon!.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( - stream.streamIcon!, + widget.stream.streamIcon!, width: double.infinity, height: double.infinity, fit: BoxFit.cover, @@ -870,7 +922,7 @@ class _ChannelCard extends StatelessWidget { left: padding, right: padding, child: Text( - stream.name, + widget.stream.name, style: TextStyle( color: Colors.white, fontSize: textSize, @@ -880,7 +932,7 @@ class _ChannelCard extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - if (stream.rating != null) + if (widget.stream.rating != null) Positioned( top: padding, right: padding, @@ -891,7 +943,7 @@ class _ChannelCard extends StatelessWidget { borderRadius: BorderRadius.circular(6), ), child: Text( - stream.rating!, + widget.stream.rating!, style: TextStyle( color: Colors.black, fontSize: ratingFontSize, @@ -903,10 +955,8 @@ class _ChannelCard extends StatelessWidget { ], ), ), - ); - }, - ), - ); + ), + ); } Widget _buildPlaceholder(double iconSize) { @@ -914,7 +964,7 @@ class _ChannelCard extends StatelessWidget { color: Colors.grey[800], child: Center( child: Icon( - isSeries ? Icons.tv : Icons.play_circle_outline, + widget.isSeries ? Icons.tv : Icons.play_circle_outline, color: Colors.red, size: iconSize, ), diff --git a/lib/widgets/simple_countries_sidebar.dart b/lib/widgets/simple_countries_sidebar.dart index de1e2fb..fb58ebb 100644 --- a/lib/widgets/simple_countries_sidebar.dart +++ b/lib/widgets/simple_countries_sidebar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class SimpleCountriesSidebar extends StatelessWidget { final List countries; @@ -101,27 +102,48 @@ class SimpleCountriesSidebar extends StatelessWidget { } Widget _buildCountryItem(String name, bool isSelected, VoidCallback onTap) { - return InkWell( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: isSelected ? Colors.red : Colors.transparent, - border: Border( - left: BorderSide( - color: isSelected ? Colors.white : Colors.transparent, - width: 4, + return FocusableActionDetector( + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (intent) { + onTap(); + return null; + }, + ), + ButtonActivateIntent: CallbackAction( + onInvoke: (intent) { + onTap(); + return null; + }, + ), + }, + child: Builder( + builder: (context) { + final hasFocus = Focus.of(context).hasFocus; + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected ? Colors.red : (hasFocus ? Colors.red.withValues(alpha: 0.5) : Colors.transparent), + border: Border( + left: BorderSide( + color: isSelected ? Colors.white : (hasFocus ? Colors.white : Colors.transparent), + width: 4, + ), + ), + ), + child: Text( + name, + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), ), - ), - ), - child: Text( - name, - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - ), - ), + ); + }, ), ); } @@ -186,39 +208,60 @@ class SimpleCountriesSidebar extends StatelessWidget { Widget _buildFootballItem() { final isSelected = selectedCountry == _footballCategoryName; - return InkWell( - onTap: onFootballSelected, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: isSelected ? Colors.green[700] : Colors.green[900]?.withOpacity(0.3), - border: Border( - left: BorderSide( - color: isSelected ? Colors.white : Colors.green[400]!, - width: 4, - ), - ), + return FocusableActionDetector( + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (intent) { + onFootballSelected?.call(); + return null; + }, ), - child: Row( - children: [ - Icon( - Icons.sports_soccer, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - _footballCategoryName, - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ButtonActivateIntent: CallbackAction( + onInvoke: (intent) { + onFootballSelected?.call(); + return null; + }, + ), + }, + child: Builder( + builder: (context) { + final hasFocus = Focus.of(context).hasFocus; + return GestureDetector( + onTap: onFootballSelected, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected ? Colors.green[700] : (hasFocus ? Colors.green[700]?.withOpacity(0.8) : Colors.green[900]?.withOpacity(0.3)), + border: Border( + left: BorderSide( + color: isSelected ? Colors.white : (hasFocus ? Colors.white : Colors.green[400]!), + width: 4, + ), ), ), + child: Row( + children: [ + Icon( + Icons.sports_soccer, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + _footballCategoryName, + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ], + ), ), - ], - ), + ); + }, ), ); }