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, ), ), ), ], ); } }