diff --git a/lib/screens/mains/hunt/hunt.dart b/lib/screens/mains/hunt/hunt.dart index 3c7db3b..7873be1 100644 --- a/lib/screens/mains/hunt/hunt.dart +++ b/lib/screens/mains/hunt/hunt.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:vibration/vibration.dart'; @@ -9,7 +11,6 @@ import 'services/game_sound_service.dart'; import 'widgets/hunt_card_widget.dart'; import 'widgets/leaderboard_widget.dart'; import 'widgets/hint_camera_widget.dart'; -import 'widgets/hunt_timer_widget.dart'; import 'models/hunt_card.dart'; class Hunt extends StatelessWidget { @@ -35,9 +36,11 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi late AnimationController _mainAnimationController; late AnimationController _cardAnimationController; late AnimationController _confettiController; + late AnimationController _backgroundGradientController; late Animation _fadeAnimation; late Animation _slideAnimation; - + late Animation _gradientAnimation; + Timer? _locationTimer; bool _showLeaderboard = false; @@ -61,6 +64,10 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi duration: const Duration(milliseconds: 2000), vsync: this, ); + _backgroundGradientController = AnimationController( + duration: const Duration(seconds: 8), + vsync: this, + )..repeat(); _fadeAnimation = Tween( begin: 0.0, @@ -78,11 +85,19 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi curve: Curves.easeOutCubic, )); + _gradientAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _backgroundGradientController, + curve: Curves.easeInOut, + )); + _mainAnimationController.forward(); } void _initializeGame() { - // The initialization is now handled in the provider creation + // Initialization is handled in provider creation } @override @@ -90,6 +105,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi _mainAnimationController.dispose(); _cardAnimationController.dispose(); _confettiController.dispose(); + _backgroundGradientController.dispose(); _locationTimer?.cancel(); GameSoundService.dispose(); super.dispose(); @@ -118,18 +134,15 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi void _onCardSelected(HuntCard card) async { final huntProvider = Provider.of(context, listen: false); - - // Gaming feedback: sound + vibration + await GameSoundService.playCardFlipSound(); Vibration.hasVibrator().then((hasVibrator) { if (hasVibrator == true) { Vibration.vibrate(duration: 100); } }); - + huntProvider.selectCard(card); - - // No dialog, just flip the card to show the riddle } void _showPermissionDialog(String message) { @@ -174,7 +187,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi void _openHintCamera() async { final huntProvider = Provider.of(context, listen: false); final selectedCard = huntProvider.selectedCard; - + if (selectedCard == null) return; final hasCameraPermission = await LocationService.checkCameraPermission(); @@ -197,7 +210,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi }, onClose: () { Navigator.of(context).pop(); - huntProvider.startHunt(); // Go back to hunting mode + huntProvider.startHunt(); }, ), ), @@ -206,21 +219,21 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi void _onHuntCompleted() async { final huntProvider = Provider.of(context, listen: false); - + await GameSoundService.playSuccessSound(); await Future.delayed(const Duration(milliseconds: 500)); await GameSoundService.playPointsEarnedSound(); - + huntProvider.completeHunt(); _confettiController.forward(); - + _showCompletionDialog(); } void _showCompletionDialog() { final huntProvider = Provider.of(context, listen: false); final selectedCard = huntProvider.selectedCard; - + if (selectedCard == null) return; showDialog( @@ -333,41 +346,74 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppColors.scaffoldBackground, - body: Consumer( - builder: (context, huntProvider, child) { - return Stack( - children: [ - SafeArea( - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: _buildMainContent(huntProvider), - ), - ), + body: AnimatedBuilder( + animation: _gradientAnimation, + builder: (context, child) { + final t = _gradientAnimation.value; + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: AppColors.isDarkMode + ? [ + Color.lerp(const Color(0xFF0F172A), + const Color(0xFF1E293B), (sin(t * pi * 2) + 1) / 2)!, + Color.lerp(const Color(0xFF1E293B), + const Color(0xFF334155), (cos(t * pi * 2 + pi / 3) + 1) / 2)!, + Color.lerp(const Color(0xFF334155), + const Color(0xFF475569), (sin(t * pi * 2 + pi / 2) + 1) / 2)!, + Color.lerp(const Color(0xFF475569), + const Color(0xFF0F172A), (cos(t * pi * 2 + pi) + 1) / 2)!, + ] + : [ + Color.lerp(const Color(0xFF84CEEB), + const Color(0xFF5680E9), (sin(t * pi * 2) + 1) / 2)!, + Color.lerp(const Color(0xFF5680E9), + const Color(0xFF8860D0), (cos(t * pi * 2 + pi / 3) + 1) / 2)!, + Color.lerp(const Color(0xFF8860D0), + const Color(0xFF5AB9EA), (sin(t * pi * 2 + pi / 2) + 1) / 2)!, + Color.lerp(const Color(0xFF5AB9EA), + const Color(0xFF84CEEB), (cos(t * pi * 2 + pi) + 1) / 2)!, + ], + stops: const [0.0, 0.3, 0.7, 1.0], ), - - // Leaderboard overlay - if (_showLeaderboard) - Positioned.fill( - child: Container( - color: Colors.black.withOpacity(0.5), - child: Align( - alignment: Alignment.bottomCenter, - child: LeaderboardWidget( - entries: huntProvider.leaderboard, - userPoints: huntProvider.userPoints, - onClose: () { - setState(() { - _showLeaderboard = false; - }); - }, + ), + child: Consumer( + builder: (context, huntProvider, child) { + return Stack( + children: [ + SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: _buildMainContent(huntProvider), + ), ), ), - ), - ), - ], + if (_showLeaderboard) + Positioned.fill( + child: Container( + color: Colors.black.withOpacity(0.5), + child: Align( + alignment: Alignment.bottomCenter, + child: LeaderboardWidget( + entries: huntProvider.leaderboard, + userPoints: huntProvider.userPoints, + onClose: () { + setState(() { + _showLeaderboard = false; + }); + }, + ), + ), + ), + ), + ], + ); + }, + ), ); }, ), @@ -375,7 +421,6 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi } Widget _buildMainContent(HuntState huntProvider) { - // Always show card selection page, don't switch to other views return _buildCardSelection(huntProvider); } @@ -383,191 +428,201 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi return Column( children: [ _buildHeader(), - const SizedBox(height: 20), - _buildPointsDisplay(huntProvider), - const SizedBox(height: 24), - _buildInstructions(), - const SizedBox(height: 20), Expanded( - child: _buildCardGrid(huntProvider), + child: Scrollbar( + thumbVisibility: false, + trackVisibility: false, + radius: const Radius.circular(6), + thickness: 4, + child: CustomScrollView( + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + slivers: [ + SliverToBoxAdapter( + child: Column( + children: [ + const SizedBox(height: 16), + _buildPointsDisplay(huntProvider), + const SizedBox(height: 18), + _buildInstructions(), + const SizedBox(height: 16), + ], + ), + ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final card = huntProvider.cards[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: HuntCardWidget( + card: card, + onTap: () => _onCardSelected(card), + isSelected: huntProvider.selectedCard?.id == card.id, + isFlipped: huntProvider.selectedCard?.id == card.id, + ), + ); + }, + childCount: huntProvider.cards.length, + ), + ), + ), + const SliverToBoxAdapter( + child: SizedBox(height: 20), + ), + ], + ), + ), ), ], ); } - - Widget _buildHeader() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppColors.primary.withOpacity(0.1), - AppColors.primary.withOpacity(0.05), - ], + return ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: AppColors.isDarkMode ? 0 : 10.0, + sigmaY: AppColors.isDarkMode ? 0 : 10.0, ), - borderRadius: BorderRadius.circular(20), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.white.withOpacity(0.4), // Liquid Glass effect + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.white.withOpacity(0.2), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: AppColors.isDarkMode + ? Colors.black.withOpacity(0.3) + : Colors.black.withOpacity(0.04), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.1) + : const Color(0xFF1976D2).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.search_rounded, + color: AppColors.isDarkMode + ? Colors.white + : const Color(0xFF1976D2), + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Hunt', + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 22, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + ), + ), + Text( + 'Discover & Collect', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () { + setState(() { + _showLeaderboard = true; + }); + }, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.1) + : const Color(0xFF1976D2).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.leaderboard_rounded, + color: AppColors.isDarkMode + ? Colors.white + : const Color(0xFF1976D2), + size: 16, + ), + const SizedBox(width: 6), + Text( + 'Ranks', + style: TextStyle( + color: AppColors.isDarkMode + ? Colors.white + : const Color(0xFF1976D2), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildPointsDisplay(HuntState huntProvider) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.10) + : Colors.white.withOpacity(0.6), // More transparent + borderRadius: BorderRadius.circular(16), border: Border.all( - color: AppColors.primary.withOpacity(0.2), + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.15) + : Colors.white.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.explore_rounded, - color: AppColors.primary, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - 'Treasure Hunt', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - '🗺️ Discover hidden gems in your city', - style: TextStyle( - color: AppColors.textSecondary, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - const SizedBox(width: 16), - GestureDetector( - onTap: () { - setState(() { - _showLeaderboard = true; - }); - }, - child: Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.primary, - AppColors.primary.withOpacity(0.8), - ], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.primary.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.leaderboard_rounded, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 6), - Text( - 'Ranks', - style: TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildHuntHeader(HuntState huntProvider) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Row( - children: [ - IconButton( - onPressed: () => huntProvider.resetGame(), - icon: Icon( - Icons.arrow_back_rounded, - color: AppColors.textPrimary, - ), - ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Active Hunt', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - if (huntProvider.hasTimeLeft) - HuntTimerWidget( - timeRemaining: huntProvider.timeRemaining, - isActive: huntProvider.gameState == HuntGameState.huntingActive, - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildPointsDisplay(HuntState huntProvider) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.cardBackground, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.shadowColor, - blurRadius: 10, - offset: const Offset(0, 4), + color: Colors.black.withOpacity(0.05), + blurRadius: 20, + offset: const Offset(0, 10), ), ], ), @@ -578,18 +633,32 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi icon: Icons.stars_rounded, label: 'Total Points', value: '${huntProvider.userPoints}', - color: AppColors.confirmButton, + color: AppColors.isDarkMode + ? const Color(0xFF64B5F6) + : const Color(0xFF1976D2), ), Container( - height: 40, + height: 32, width: 1, - color: AppColors.divider, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.divider.withOpacity(0), + AppColors.divider.withOpacity(0.6), + AppColors.divider.withOpacity(0), + ], + ), + ), ), _buildStatItem( icon: Icons.emoji_events, label: 'Rank', value: '#${huntProvider.currentUserRank}', - color: AppColors.primary, + color: AppColors.isDarkMode + ? const Color(0xFF81C784) + : const Color(0xFF388E3C), ), ], ), @@ -603,22 +672,32 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi required Color color, }) { return Column( + mainAxisSize: MainAxisSize.min, children: [ - Icon(icon, color: color, size: 28), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: color, size: 24), + ), const SizedBox(height: 8), Text( value, style: TextStyle( color: AppColors.textPrimary, - fontSize: 20, - fontWeight: FontWeight.bold, + fontSize: 18, + fontWeight: FontWeight.w600, ), ), + const SizedBox(height: 2), Text( label, style: TextStyle( color: AppColors.textSecondary, - fontSize: 12, + fontSize: 11, + fontWeight: FontWeight.w400, ), ), ], @@ -628,44 +707,45 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi Widget _buildInstructions() { return Container( margin: const EdgeInsets.symmetric(horizontal: 20), - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(14), decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.amber.withOpacity(0.15), - Colors.orange.withOpacity(0.1), - ], - ), + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.08) + : Colors.white.withOpacity(0.7), // More transparent borderRadius: BorderRadius.circular(16), border: Border.all( - color: Colors.amber.withOpacity(0.3), + color: AppColors.isDarkMode + ? Colors.white.withOpacity(0.12) + : Colors.white.withOpacity(0.4), width: 1, ), boxShadow: [ BoxShadow( - color: Colors.amber.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), + color: Colors.black.withOpacity(0.03), + blurRadius: 15, + offset: const Offset(0, 8), ), ], ), child: Row( children: [ Container( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.amber.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), + color: AppColors.isDarkMode + ? const Color(0xFF64B5F6).withOpacity(0.2) + : const Color(0xFF1976D2).withOpacity(0.12), + borderRadius: BorderRadius.circular(10), ), child: Icon( Icons.psychology_rounded, - color: Colors.amber.shade700, + color: AppColors.isDarkMode + ? const Color(0xFF64B5F6) + : const Color(0xFF1976D2), size: 18, ), ), - const SizedBox(width: 16), + const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -673,18 +753,18 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi Text( 'Gaming Quest Mode', style: TextStyle( - color: Colors.amber.shade800, - fontSize: 15, - fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, ), ), - const SizedBox(height: 4), + const SizedBox(height: 3), Text( 'Tap any card to reveal your mystery quest! Solve riddles, find locations, and earn points.', style: TextStyle( - color: Colors.amber.shade700, + color: AppColors.textSecondary, fontSize: 11, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w400, height: 1.3, ), ), @@ -695,162 +775,4 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi ), ); } - - Widget _buildCardGrid(HuntState huntProvider) { - return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 12), - itemCount: huntProvider.cards.length, - itemBuilder: (context, index) { - final card = huntProvider.cards[index]; - return HuntCardWidget( - card: card, - onTap: () => _onCardSelected(card), - isSelected: huntProvider.selectedCard?.id == card.id, - isFlipped: huntProvider.selectedCard?.id == card.id, - ); - }, - ); - } - - Widget _buildSelectedCard(HuntCard card) { - return HuntCardWidget( - card: card, - onTap: () {}, - isSelected: true, - isFlipped: true, - customHeight: 220, - ); - } - - Widget _buildHuntActions(HuntState huntProvider) { - return Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: _openHintCamera, - icon: const Icon(Icons.camera_alt_outlined), - label: const Text('Get Hint'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.cardBackground, - foregroundColor: AppColors.textPrimary, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide(color: AppColors.divider), - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - setState(() { - _showLeaderboard = true; - }); - }, - icon: const Icon(Icons.leaderboard), - label: const Text('Leaderboard'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ), - ], - ); - } - - Widget _buildHuntStatus(HuntState huntProvider) { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.cardBackground, - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Hunt Status', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - _buildStatusItem( - icon: Icons.location_on, - title: 'Location Access', - status: huntProvider.isLocationEnabled ? 'Enabled' : 'Disabled', - isActive: huntProvider.isLocationEnabled, - ), - const SizedBox(height: 12), - _buildStatusItem( - icon: Icons.camera_alt, - title: 'Camera Access', - status: huntProvider.isCameraPermissionGranted ? 'Granted' : 'Not Granted', - isActive: huntProvider.isCameraPermissionGranted, - ), - const SizedBox(height: 12), - _buildStatusItem( - icon: Icons.timer, - title: 'Time Remaining', - status: huntProvider.hasTimeLeft ? 'Active' : 'Expired', - isActive: huntProvider.hasTimeLeft, - ), - ], - ), - ); - } - - Widget _buildStatusItem({ - required IconData icon, - required String title, - required String status, - required bool isActive, - }) { - return Row( - children: [ - Icon( - icon, - color: isActive ? AppColors.confirmButton : AppColors.textSecondary, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - title, - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: isActive - ? AppColors.confirmButton.withOpacity(0.1) - : AppColors.textSecondary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - status, - style: TextStyle( - color: isActive ? AppColors.confirmButton : AppColors.textSecondary, - fontSize: 12, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ); - } } \ No newline at end of file diff --git a/lib/screens/mains/hunt/hunt.dart.backup b/lib/screens/mains/hunt/hunt.dart.backup deleted file mode 100644 index 3c7db3b..0000000 --- a/lib/screens/mains/hunt/hunt.dart.backup +++ /dev/null @@ -1,856 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:vibration/vibration.dart'; -import 'package:lba/res/colors.dart'; -import 'providers/hunt_provider.dart'; -import 'services/location_service.dart'; -import 'services/game_sound_service.dart'; -import 'widgets/hunt_card_widget.dart'; -import 'widgets/leaderboard_widget.dart'; -import 'widgets/hint_camera_widget.dart'; -import 'widgets/hunt_timer_widget.dart'; -import 'models/hunt_card.dart'; - -class Hunt extends StatelessWidget { - const Hunt({super.key}); - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (_) => HuntState()..initializeGame(), - child: const _HuntContent(), - ); - } -} - -class _HuntContent extends StatefulWidget { - const _HuntContent(); - - @override - State<_HuntContent> createState() => _HuntContentState(); -} - -class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixin { - late AnimationController _mainAnimationController; - late AnimationController _cardAnimationController; - late AnimationController _confettiController; - late Animation _fadeAnimation; - late Animation _slideAnimation; - - Timer? _locationTimer; - bool _showLeaderboard = false; - - @override - void initState() { - super.initState(); - _setupAnimations(); - _initializeGame(); - } - - void _setupAnimations() { - _mainAnimationController = AnimationController( - duration: const Duration(milliseconds: 1000), - vsync: this, - ); - _cardAnimationController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _confettiController = AnimationController( - duration: const Duration(milliseconds: 2000), - vsync: this, - ); - - _fadeAnimation = Tween( - begin: 0.0, - end: 1.0, - ).animate(CurvedAnimation( - parent: _mainAnimationController, - curve: Curves.easeInOut, - )); - - _slideAnimation = Tween( - begin: const Offset(0, 0.3), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _mainAnimationController, - curve: Curves.easeOutCubic, - )); - - _mainAnimationController.forward(); - } - - void _initializeGame() { - // The initialization is now handled in the provider creation - } - - @override - void dispose() { - _mainAnimationController.dispose(); - _cardAnimationController.dispose(); - _confettiController.dispose(); - _locationTimer?.cancel(); - GameSoundService.dispose(); - super.dispose(); - } - - void _startLocationMonitoring(HuntCard card) { - _locationTimer?.cancel(); - _locationTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { - final position = await LocationService.getCurrentPosition(); - if (position != null) { - final isNearTarget = LocationService.isWithinRange( - position.latitude, - position.longitude, - card.targetLatitude, - card.targetLongitude, - rangeInMeters: 50.0, - ); - - if (isNearTarget && mounted) { - _onHuntCompleted(); - timer.cancel(); - } - } - }); - } - - void _onCardSelected(HuntCard card) async { - final huntProvider = Provider.of(context, listen: false); - - // Gaming feedback: sound + vibration - await GameSoundService.playCardFlipSound(); - Vibration.hasVibrator().then((hasVibrator) { - if (hasVibrator == true) { - Vibration.vibrate(duration: 100); - } - }); - - huntProvider.selectCard(card); - - // No dialog, just flip the card to show the riddle - } - - void _showPermissionDialog(String message) { - showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: AppColors.surface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - title: Text( - 'Permission Required', - style: TextStyle( - color: AppColors.textPrimary, - fontWeight: FontWeight.bold, - ), - ), - content: Text( - message, - style: TextStyle( - color: AppColors.textSecondary, - height: 1.4, - ), - ), - actions: [ - ElevatedButton( - onPressed: () => Navigator.of(context).pop(), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: const Text('OK'), - ), - ], - ), - ); - } - - void _openHintCamera() async { - final huntProvider = Provider.of(context, listen: false); - final selectedCard = huntProvider.selectedCard; - - if (selectedCard == null) return; - - final hasCameraPermission = await LocationService.checkCameraPermission(); - if (!hasCameraPermission) { - _showPermissionDialog('Camera permission is required for the hint feature.'); - return; - } - - huntProvider.setCameraPermissionGranted(true); - huntProvider.activateHintMode(); - - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => HintCameraWidget( - targetLatitude: selectedCard.hintLatitude, - targetLongitude: selectedCard.hintLongitude, - hintDescription: selectedCard.hintDescription, - onHintFound: () async { - await GameSoundService.playHintFoundSound(); - }, - onClose: () { - Navigator.of(context).pop(); - huntProvider.startHunt(); // Go back to hunting mode - }, - ), - ), - ); - } - - void _onHuntCompleted() async { - final huntProvider = Provider.of(context, listen: false); - - await GameSoundService.playSuccessSound(); - await Future.delayed(const Duration(milliseconds: 500)); - await GameSoundService.playPointsEarnedSound(); - - huntProvider.completeHunt(); - _confettiController.forward(); - - _showCompletionDialog(); - } - - void _showCompletionDialog() { - final huntProvider = Provider.of(context, listen: false); - final selectedCard = huntProvider.selectedCard; - - if (selectedCard == null) return; - - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - backgroundColor: AppColors.surface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - title: Column( - children: [ - Icon( - Icons.celebration, - color: AppColors.confirmButton, - size: 48, - ), - const SizedBox(height: 8), - Text( - 'Congratulations!', - style: TextStyle( - color: AppColors.textPrimary, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'You found ${selectedCard.answer}!', - style: TextStyle( - color: AppColors.primary, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.confirmButton.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.stars_rounded, - color: AppColors.confirmButton, - size: 24, - ), - const SizedBox(width: 8), - Text( - '+${selectedCard.points} Points', - style: TextStyle( - color: AppColors.confirmButton, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - actions: [ - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - setState(() { - _showLeaderboard = true; - }); - }, - child: Text( - 'View Leaderboard', - style: TextStyle(color: AppColors.primary), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - huntProvider.resetGame(); - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: const Text('Play Again'), - ), - ), - ], - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.scaffoldBackground, - body: Consumer( - builder: (context, huntProvider, child) { - return Stack( - children: [ - SafeArea( - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: _buildMainContent(huntProvider), - ), - ), - ), - - // Leaderboard overlay - if (_showLeaderboard) - Positioned.fill( - child: Container( - color: Colors.black.withOpacity(0.5), - child: Align( - alignment: Alignment.bottomCenter, - child: LeaderboardWidget( - entries: huntProvider.leaderboard, - userPoints: huntProvider.userPoints, - onClose: () { - setState(() { - _showLeaderboard = false; - }); - }, - ), - ), - ), - ), - ], - ); - }, - ), - ); - } - - Widget _buildMainContent(HuntState huntProvider) { - // Always show card selection page, don't switch to other views - return _buildCardSelection(huntProvider); - } - - Widget _buildCardSelection(HuntState huntProvider) { - return Column( - children: [ - _buildHeader(), - const SizedBox(height: 20), - _buildPointsDisplay(huntProvider), - const SizedBox(height: 24), - _buildInstructions(), - const SizedBox(height: 20), - Expanded( - child: _buildCardGrid(huntProvider), - ), - ], - ); - } - - - - Widget _buildHeader() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppColors.primary.withOpacity(0.1), - AppColors.primary.withOpacity(0.05), - ], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: AppColors.primary.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: AppColors.primary.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.explore_rounded, - color: AppColors.primary, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - 'Treasure Hunt', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - '🗺️ Discover hidden gems in your city', - style: TextStyle( - color: AppColors.textSecondary, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - const SizedBox(width: 16), - GestureDetector( - onTap: () { - setState(() { - _showLeaderboard = true; - }); - }, - child: Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.primary, - AppColors.primary.withOpacity(0.8), - ], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.primary.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.leaderboard_rounded, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 6), - Text( - 'Ranks', - style: TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildHuntHeader(HuntState huntProvider) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Row( - children: [ - IconButton( - onPressed: () => huntProvider.resetGame(), - icon: Icon( - Icons.arrow_back_rounded, - color: AppColors.textPrimary, - ), - ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Active Hunt', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - if (huntProvider.hasTimeLeft) - HuntTimerWidget( - timeRemaining: huntProvider.timeRemaining, - isActive: huntProvider.gameState == HuntGameState.huntingActive, - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildPointsDisplay(HuntState huntProvider) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.cardBackground, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.shadowColor, - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildStatItem( - icon: Icons.stars_rounded, - label: 'Total Points', - value: '${huntProvider.userPoints}', - color: AppColors.confirmButton, - ), - Container( - height: 40, - width: 1, - color: AppColors.divider, - ), - _buildStatItem( - icon: Icons.emoji_events, - label: 'Rank', - value: '#${huntProvider.currentUserRank}', - color: AppColors.primary, - ), - ], - ), - ); - } - - Widget _buildStatItem({ - required IconData icon, - required String label, - required String value, - required Color color, - }) { - return Column( - children: [ - Icon(icon, color: color, size: 28), - const SizedBox(height: 8), - Text( - value, - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - Text( - label, - style: TextStyle( - color: AppColors.textSecondary, - fontSize: 12, - ), - ), - ], - ); - } - - Widget _buildInstructions() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 20), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.amber.withOpacity(0.15), - Colors.orange.withOpacity(0.1), - ], - ), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.amber.withOpacity(0.3), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Colors.amber.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.amber.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.psychology_rounded, - color: Colors.amber.shade700, - size: 18, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Gaming Quest Mode', - style: TextStyle( - color: Colors.amber.shade800, - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - 'Tap any card to reveal your mystery quest! Solve riddles, find locations, and earn points.', - style: TextStyle( - color: Colors.amber.shade700, - fontSize: 11, - fontWeight: FontWeight.w500, - height: 1.3, - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildCardGrid(HuntState huntProvider) { - return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 12), - itemCount: huntProvider.cards.length, - itemBuilder: (context, index) { - final card = huntProvider.cards[index]; - return HuntCardWidget( - card: card, - onTap: () => _onCardSelected(card), - isSelected: huntProvider.selectedCard?.id == card.id, - isFlipped: huntProvider.selectedCard?.id == card.id, - ); - }, - ); - } - - Widget _buildSelectedCard(HuntCard card) { - return HuntCardWidget( - card: card, - onTap: () {}, - isSelected: true, - isFlipped: true, - customHeight: 220, - ); - } - - Widget _buildHuntActions(HuntState huntProvider) { - return Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: _openHintCamera, - icon: const Icon(Icons.camera_alt_outlined), - label: const Text('Get Hint'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.cardBackground, - foregroundColor: AppColors.textPrimary, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide(color: AppColors.divider), - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - setState(() { - _showLeaderboard = true; - }); - }, - icon: const Icon(Icons.leaderboard), - label: const Text('Leaderboard'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ), - ], - ); - } - - Widget _buildHuntStatus(HuntState huntProvider) { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.cardBackground, - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Hunt Status', - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - _buildStatusItem( - icon: Icons.location_on, - title: 'Location Access', - status: huntProvider.isLocationEnabled ? 'Enabled' : 'Disabled', - isActive: huntProvider.isLocationEnabled, - ), - const SizedBox(height: 12), - _buildStatusItem( - icon: Icons.camera_alt, - title: 'Camera Access', - status: huntProvider.isCameraPermissionGranted ? 'Granted' : 'Not Granted', - isActive: huntProvider.isCameraPermissionGranted, - ), - const SizedBox(height: 12), - _buildStatusItem( - icon: Icons.timer, - title: 'Time Remaining', - status: huntProvider.hasTimeLeft ? 'Active' : 'Expired', - isActive: huntProvider.hasTimeLeft, - ), - ], - ), - ); - } - - Widget _buildStatusItem({ - required IconData icon, - required String title, - required String status, - required bool isActive, - }) { - return Row( - children: [ - Icon( - icon, - color: isActive ? AppColors.confirmButton : AppColors.textSecondary, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - title, - style: TextStyle( - color: AppColors.textPrimary, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: isActive - ? AppColors.confirmButton.withOpacity(0.1) - : AppColors.textSecondary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - status, - style: TextStyle( - color: isActive ? AppColors.confirmButton : AppColors.textSecondary, - fontSize: 12, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/screens/mains/hunt/widgets/hunt_card_widget.dart b/lib/screens/mains/hunt/widgets/hunt_card_widget.dart index 8364797..991b597 100644 --- a/lib/screens/mains/hunt/widgets/hunt_card_widget.dart +++ b/lib/screens/mains/hunt/widgets/hunt_card_widget.dart @@ -3,6 +3,7 @@ import 'package:lba/res/colors.dart'; import '../models/hunt_card.dart'; import 'hunt_timer_widget.dart'; import 'hint_camera_widget.dart'; +import 'dart:math'; class HuntCardWidget extends StatefulWidget { final HuntCard card; @@ -30,8 +31,10 @@ class _HuntCardWidgetState extends State with TickerProviderStateMixin { late AnimationController _flipController; late AnimationController _scaleController; + late AnimationController _gradientController; late Animation _flipAnimation; late Animation _scaleAnimation; + late Animation _gradientAnimation; @override void initState() { @@ -44,6 +47,10 @@ class _HuntCardWidgetState extends State duration: const Duration(milliseconds: 300), vsync: this, ); + _gradientController = AnimationController( + duration: const Duration(seconds: 8), + vsync: this, + )..repeat(); _flipAnimation = Tween( begin: 0.0, @@ -60,6 +67,14 @@ class _HuntCardWidgetState extends State parent: _scaleController, curve: Curves.elasticOut, )); + + _gradientAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _gradientController, + curve: Curves.easeInOut, + )); } @override @@ -78,6 +93,7 @@ class _HuntCardWidgetState extends State void dispose() { _flipController.dispose(); _scaleController.dispose(); + _gradientController.dispose(); super.dispose(); } @@ -111,102 +127,124 @@ class _HuntCardWidgetState extends State @override Widget build(BuildContext context) { - return GestureDetector( - onTapDown: (_) => _scaleController.forward(), - onTapUp: (_) => _scaleController.reverse(), - onTapCancel: () => _scaleController.reverse(), - onTap: widget.onTap, - child: ScaleTransition( - scale: _scaleAnimation, - child: Container( - height: widget.customHeight ?? 260, - width: widget.customWidth, - margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), - child: AnimatedBuilder( - animation: _flipAnimation, - builder: (context, child) { - final isShowingFront = _flipAnimation.value < 0.5; - return Transform( - alignment: Alignment.center, - transform: Matrix4.identity() - ..setEntry(3, 2, 0.001) - ..rotateY(_flipAnimation.value * 3.14159), - child: Card( - elevation: widget.isSelected ? 20 : 10, - shadowColor: widget.isSelected - ? AppColors.primary.withOpacity(0.6) - : AppColors.shadowColor.withOpacity(0.3), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - side: widget.isSelected - ? BorderSide( - color: AppColors.primary, - width: 3, - strokeAlign: BorderSide.strokeAlignOutside, - ) - : BorderSide.none, - ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isShowingFront - ? [ - AppColors.cardBackground, - AppColors.cardBackground.withOpacity(0.95), - AppColors.primary.withOpacity(0.05), - ] - : [ - AppColors.primary.withOpacity(0.2), - AppColors.primary.withOpacity(0.1), - AppColors.primary.withOpacity(0.05), - ], + return AnimatedBuilder( + animation: _gradientAnimation, + builder: (context, child) { + return GestureDetector( + onTapDown: (_) => _scaleController.forward(), + onTapUp: (_) => _scaleController.reverse(), + onTapCancel: () => _scaleController.reverse(), + onTap: widget.onTap, + child: ScaleTransition( + scale: _scaleAnimation, + child: Container( + height: widget.customHeight ?? 260, + width: widget.customWidth, + margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + child: AnimatedBuilder( + animation: _flipAnimation, + builder: (context, child) { + final isShowingFront = _flipAnimation.value < 0.5; + return Transform( + alignment: Alignment.center, + transform: Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateY(_flipAnimation.value * 3.14159), + child: Card( + elevation: widget.isSelected ? 20 : 10, + shadowColor: widget.isSelected + ? AppColors.primary.withOpacity(0.6) + : AppColors.shadowColor.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: widget.isSelected + ? BorderSide( + color: AppColors.primary, + width: 3, + strokeAlign: BorderSide.strokeAlignOutside, + ) + : BorderSide.none, ), - border: widget.isSelected && !isShowingFront - ? Border.all( - color: AppColors.primary.withOpacity(0.7), - width: 2, - ) - : null, - boxShadow: widget.isSelected ? [ - BoxShadow( - color: AppColors.primary.withOpacity(0.3), - blurRadius: 15, - spreadRadius: 2, - offset: const Offset(0, 8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: _buildAnimatedGradient(isShowingFront), + border: widget.isSelected && !isShowingFront + ? Border.all( + color: AppColors.primary.withOpacity(0.7), + width: 2, + ) + : null, + boxShadow: widget.isSelected ? [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 15, + spreadRadius: 2, + offset: const Offset(0, 8), + ), + ] : [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], ), - ] : [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], + child: isShowingFront ? _buildFrontSide() : _buildBackSide(), + ), ), - child: isShowingFront ? _buildFrontSide() : _buildBackSide(), - ), - ), - ); - }, + ); + }, + ), + ), ), - ), - ), + ); + }, ); } Widget _buildFrontSide() { + final t = _gradientAnimation.value; + final pi = 3.14159; + return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - AppColors.cardBackground, - AppColors.cardBackground.withOpacity(0.9), - AppColors.primary.withOpacity(0.05), + colors: AppColors.isDarkMode ? [ + Color.lerp( + AppColors.cardBackground, + AppColors.cardBackground.withOpacity(0.95), + (sin(t * pi * 2.4) + 1) / 2, + )!, + Color.lerp( + AppColors.cardBackground.withOpacity(0.9), + AppColors.primary.withOpacity(0.08), + (cos(t * pi * 1.8 + pi / 2.7) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.05), + AppColors.primary.withOpacity(0.12), + (sin(t * pi * 3.2 + pi / 1.6) + 1) / 2, + )!, + ] : [ + Color.lerp( + const Color(0xFFFFFFFF), + const Color(0xFFE3F2FD), + (sin(t * pi * 2.1) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFE3F2FD), + const Color(0xFFBBDEFB), + (cos(t * pi * 1.9 + pi / 3.1) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFBBDEFB), + const Color(0xFFFFFFFF), + (sin(t * pi * 2.6 + pi / 1.5) + 1) / 2, + )!, ], ), boxShadow: [ @@ -229,19 +267,26 @@ class _HuntCardWidgetState extends State padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.primary.withOpacity(0.25), AppColors.primary.withOpacity(0.15), + ] : [ + const Color(0xFF1976D2).withOpacity(0.12), + const Color(0xFF1976D2).withOpacity(0.08), ], ), borderRadius: BorderRadius.circular(20), border: Border.all( - color: AppColors.primary.withOpacity(0.4), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.4) + : const Color(0xFF1976D2).withOpacity(0.25), width: 1.5, ), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.2), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.2) + : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), @@ -258,7 +303,9 @@ class _HuntCardWidgetState extends State Text( widget.card.category, style: TextStyle( - color: AppColors.primary, + color: AppColors.isDarkMode + ? AppColors.primary + : const Color(0xFF1976D2), fontWeight: FontWeight.w700, fontSize: 11, ), @@ -270,19 +317,26 @@ class _HuntCardWidgetState extends State padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.confirmButton.withOpacity(0.25), AppColors.confirmButton.withOpacity(0.15), + ] : [ + const Color(0xFF2E7D32).withOpacity(0.15), + const Color(0xFF2E7D32).withOpacity(0.08), ], ), borderRadius: BorderRadius.circular(15), border: Border.all( - color: AppColors.confirmButton.withOpacity(0.5), + color: AppColors.isDarkMode + ? AppColors.confirmButton.withOpacity(0.5) + : const Color(0xFF2E7D32).withOpacity(0.3), width: 1.5, ), boxShadow: [ BoxShadow( - color: AppColors.confirmButton.withOpacity(0.3), + color: AppColors.isDarkMode + ? AppColors.confirmButton.withOpacity(0.3) + : const Color(0xFF2E7D32).withOpacity(0.15), blurRadius: 8, offset: const Offset(0, 3), ), @@ -293,14 +347,18 @@ class _HuntCardWidgetState extends State children: [ Icon( Icons.stars_rounded, - color: AppColors.confirmButton, + color: AppColors.isDarkMode + ? AppColors.confirmButton + : const Color(0xFF2E7D32), size: 16, ), const SizedBox(width: 4), Text( '${widget.card.points}', style: TextStyle( - color: AppColors.confirmButton, + color: AppColors.isDarkMode + ? AppColors.confirmButton + : const Color(0xFF2E7D32), fontWeight: FontWeight.bold, fontSize: 14, ), @@ -321,26 +379,37 @@ class _HuntCardWidgetState extends State padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: RadialGradient( - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.primary.withOpacity(0.4), AppColors.primary.withOpacity(0.2), AppColors.primary.withOpacity(0.1), AppColors.primary.withOpacity(0.05), + ] : [ + const Color(0xFF1976D2).withOpacity(0.2), + const Color(0xFF1976D2).withOpacity(0.1), + const Color(0xFF1976D2).withOpacity(0.05), + const Color(0xFF1976D2).withOpacity(0.02), ], ), shape: BoxShape.circle, border: Border.all( - color: AppColors.primary.withOpacity(0.5), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.5) + : const Color(0xFF1976D2).withOpacity(0.3), width: 2.5, ), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.4), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.4) + : const Color(0xFF1976D2).withOpacity(0.2), blurRadius: 20, spreadRadius: 4, ), BoxShadow( - color: AppColors.primary.withOpacity(0.2), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.2) + : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 40, spreadRadius: 8, ), @@ -359,20 +428,28 @@ class _HuntCardWidgetState extends State gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.surface.withOpacity(0.8), AppColors.primary.withOpacity(0.1), AppColors.surface.withOpacity(0.8), + ] : [ + const Color(0xFFFFFFFE), + const Color(0xFF1976D2).withOpacity(0.04), + const Color(0xFFFFFFFE), ], ), borderRadius: BorderRadius.circular(25), border: Border.all( - color: AppColors.primary.withOpacity(0.3), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.3) + : const Color(0xFF1976D2).withOpacity(0.2), width: 1.5, ), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.1), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.1) + : const Color(0xFF1976D2).withOpacity(0.06), blurRadius: 8, offset: const Offset(0, 4), ), @@ -385,14 +462,18 @@ class _HuntCardWidgetState extends State children: [ Icon( Icons.touch_app_rounded, - color: AppColors.primary, + color: AppColors.isDarkMode + ? AppColors.primary + : const Color(0xFF1976D2), size: 16, ), const SizedBox(width: 8), Text( 'TAP TO REVEAL', style: TextStyle( - color: AppColors.primary, + color: AppColors.isDarkMode + ? AppColors.primary + : const Color(0xFF1976D2), fontSize: 12, fontWeight: FontWeight.bold, letterSpacing: 1.5, @@ -421,30 +502,7 @@ class _HuntCardWidgetState extends State Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { - final isActive = index < (widget.card.points ~/ 50).clamp(1, 3); return SizedBox(); - // Container( - // margin: const EdgeInsets.symmetric(horizontal: 3), - // width: isActive ? 20 : 14, - // height: 5, - // decoration: BoxDecoration( - // gradient: isActive ? LinearGradient( - // colors: [ - // AppColors.primary, - // AppColors.primary.withOpacity(0.7), - // ], - // ) : null, - // color: isActive ? null : AppColors.textSecondary.withOpacity(0.3), - // borderRadius: BorderRadius.circular(3), - // boxShadow: isActive ? [ - // BoxShadow( - // color: AppColors.primary.withOpacity(0.5), - // blurRadius: 4, - // spreadRadius: 1, - // ), - // ] : null, - // ), - // ); }), ), ], @@ -454,6 +512,9 @@ class _HuntCardWidgetState extends State } Widget _buildBackSide() { + final t = _gradientAnimation.value; + final pi = 3.14159; + return Transform( alignment: Alignment.center, transform: Matrix4.identity()..rotateY(3.14159), @@ -463,10 +524,38 @@ class _HuntCardWidgetState extends State gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - AppColors.primary.withOpacity(0.2), - AppColors.primary.withOpacity(0.1), - AppColors.primary.withOpacity(0.05), + colors: AppColors.isDarkMode ? [ + Color.lerp( + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.15), + (sin(t * pi * 2.3) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.18), + (cos(t * pi * 1.5 + pi / 2.6) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.05), + AppColors.primary.withOpacity(0.12), + (sin(t * pi * 3.1 + pi / 1.4) + 1) / 2, + )!, + ] : [ + Color.lerp( + const Color(0xFFE3F2FD), + const Color(0xFFBBDEFB), + (sin(t * pi * 2.2) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFBBDEFB), + const Color(0xFF90CAF9), + (cos(t * pi * 1.7 + pi / 2.4) + 1) / 2, + )!, + Color.lerp( + const Color(0xFF90CAF9), + const Color(0xFFE3F2FD), + (sin(t * pi * 2.8 + pi / 1.8) + 1) / 2, + )!, ], ), ), @@ -485,15 +574,20 @@ class _HuntCardWidgetState extends State padding: const EdgeInsets.all(6), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.primary.withOpacity(0.3), AppColors.primary.withOpacity(0.2), + ] : [ + const Color(0xFF1976D2).withOpacity(0.15), + const Color(0xFF1976D2).withOpacity(0.1), ], ), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.2), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.2) + : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), @@ -501,7 +595,9 @@ class _HuntCardWidgetState extends State ), child: Icon( Icons.auto_awesome_rounded, - color: AppColors.primary, + color: AppColors.isDarkMode + ? AppColors.primary + : const Color(0xFF1976D2), size: 16, ), ), @@ -509,7 +605,9 @@ class _HuntCardWidgetState extends State Text( 'Quest', style: TextStyle( - color: AppColors.primary, + color: AppColors.isDarkMode + ? AppColors.primary + : const Color(0xFF1976D2), fontSize: 15, fontWeight: FontWeight.bold, ), @@ -537,19 +635,26 @@ class _HuntCardWidgetState extends State gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.surface.withOpacity(0.9), AppColors.surface.withOpacity(0.7), + ] : [ + const Color(0xFFFFFFFE), + const Color(0xFFFDFDFD), ], ), borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppColors.primary.withOpacity(0.3), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.3) + : const Color(0xFF1976D2).withOpacity(0.15), width: 1.5, ), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.1), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.1) + : const Color(0xFF1976D2).withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 3), ), @@ -606,19 +711,26 @@ class _HuntCardWidgetState extends State padding: const EdgeInsets.all(6), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ + colors: AppColors.isDarkMode ? [ Colors.yellow.withOpacity(0.2), Colors.blue.withOpacity(0.15), + ] : [ + const Color(0xFFFFF8E1), + const Color(0xFFE3F2FD), ], ), borderRadius: BorderRadius.circular(10), border: Border.all( - color: Colors.yellow.withOpacity(0.4), + color: AppColors.isDarkMode + ? Colors.yellow.withOpacity(0.4) + : const Color(0xFFFFB74D).withOpacity(0.4), width: 1, ), boxShadow: [ BoxShadow( - color: Colors.yellow.withOpacity(0.2), + color: AppColors.isDarkMode + ? Colors.yellow.withOpacity(0.2) + : const Color(0xFFFFB74D).withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), @@ -629,14 +741,18 @@ class _HuntCardWidgetState extends State children: [ Icon( Icons.help, - color: Colors.yellow.shade600, + color: AppColors.isDarkMode + ? Colors.yellow.shade600 + : const Color(0xFFE65100), size: 14, ), const SizedBox(width: 6), Text( 'AR Smart Hint', style: TextStyle( - color: Colors.yellow.shade700, + color: AppColors.isDarkMode + ? Colors.yellow.shade700 + : const Color(0xFFE65100), fontSize: 10, fontWeight: FontWeight.bold, ), @@ -657,16 +773,22 @@ class _HuntCardWidgetState extends State gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ + colors: AppColors.isDarkMode ? [ AppColors.primary, AppColors.primary.withOpacity(0.8), AppColors.primary.withOpacity(0.9), + ] : [ + const Color(0xFF1976D2), + const Color(0xFF1565C0), + const Color(0xFF0D47A1), ], ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: AppColors.primary.withOpacity(0.4), + color: AppColors.isDarkMode + ? AppColors.primary.withOpacity(0.4) + : const Color(0xFF1976D2).withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 3), ), @@ -698,4 +820,99 @@ class _HuntCardWidgetState extends State ), ); } -} + + LinearGradient _buildAnimatedGradient(bool isShowingFront) { + final t = _gradientAnimation.value; + final pi = 3.14159; + + if (isShowingFront) { + // Front side animated gradient + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: AppColors.isDarkMode ? [ + Color.lerp( + AppColors.cardBackground, + AppColors.cardBackground.withOpacity(0.95), + (sin(t * pi * 2.3) + 1) / 2, + )!, + Color.lerp( + AppColors.cardBackground.withOpacity(0.9), + AppColors.primary.withOpacity(0.08), + (cos(t * pi * 1.7 + pi / 2.5) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.05), + AppColors.primary.withOpacity(0.12), + (sin(t * pi * 3.1 + pi / 1.8) + 1) / 2, + )!, + ] : [ + Color.lerp( + const Color(0xFFFAFAFA), + const Color(0xFFF8FBFF), + (sin(t * pi * 2.1) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFF5F8FA), + const Color(0xFFEBF4FF), + (cos(t * pi * 1.9 + pi / 3.2) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFE3F2FD), + const Color(0xFFF3F4F6), + (sin(t * pi * 2.7 + pi / 1.4) + 1) / 2, + )!, + ], + stops: [ + 0.1 + (sin(t * pi * 2.5) * 0.1), + 0.5 + (cos(t * pi * 1.8) * 0.2), + 0.9 + (sin(t * pi * 3.2) * 0.08), + ], + ); + } else { + // Back side animated gradient + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: AppColors.isDarkMode ? [ + Color.lerp( + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.15), + (sin(t * pi * 2.6) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.18), + (cos(t * pi * 1.4 + pi / 2.8) + 1) / 2, + )!, + Color.lerp( + AppColors.primary.withOpacity(0.05), + AppColors.primary.withOpacity(0.12), + (sin(t * pi * 3.4 + pi / 1.3) + 1) / 2, + )!, + ] : [ + Color.lerp( + const Color(0xFFE8F4F8), + const Color(0xFFF0F9FF), + (sin(t * pi * 2.2) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFDEECF6), + const Color(0xFFE0F2FE), + (cos(t * pi * 1.6 + pi / 2.3) + 1) / 2, + )!, + Color.lerp( + const Color(0xFFBBE5FF), + const Color(0xFFE6F3FF), + (sin(t * pi * 2.9 + pi / 1.7) + 1) / 2, + )!, + ], + stops: [ + 0.05 + (cos(t * pi * 2.1) * 0.1), + 0.45 + (sin(t * pi * 1.7) * 0.25), + 0.95 + (cos(t * pi * 3.3) * 0.05), + ], + ); + } + } +} \ No newline at end of file