import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lba/gen/assets.gen.dart'; import 'package:lba/res/colors.dart'; import 'package:provider/provider.dart'; import '../models/hunt_card.dart'; import '../providers/hunt_provider.dart'; import 'hunt_timer_widget.dart'; import 'hint_camera_widget.dart'; import 'dart:math'; class HuntCardWidget extends StatefulWidget { final HuntCard card; final bool isSelected; final bool isFlipped; final VoidCallback onTap; final double? customHeight; final double? customWidth; const HuntCardWidget({ super.key, required this.card, required this.onTap, this.isSelected = false, this.isFlipped = false, this.customHeight, this.customWidth, }); @override State createState() => _HuntCardWidgetState(); } 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() { super.initState(); _flipController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _scaleController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _gradientController = AnimationController( duration: const Duration(seconds: 8), vsync: this, )..repeat(); _flipAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _flipController, curve: Curves.easeInOutBack, )); _scaleAnimation = Tween( begin: 1.0, end: 1.05, ).animate(CurvedAnimation( parent: _scaleController, curve: Curves.elasticOut, )); _gradientAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _gradientController, curve: Curves.easeInOut, )); } @override void didUpdateWidget(HuntCardWidget oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isFlipped != oldWidget.isFlipped) { if (widget.isFlipped) { _flipController.forward(); } else { _flipController.reverse(); } } } @override void dispose() { _flipController.dispose(); _scaleController.dispose(); _gradientController.dispose(); super.dispose(); } void _openARHint() { final huntState = context.read(); Navigator.push( context, MaterialPageRoute( builder: (context) => HintCameraWidget( targetLatitude: widget.card.hintLatitude, targetLongitude: widget.card.hintLongitude, hintDescription: widget.card.hintDescription, remainingTime: huntState.timeRemaining, cardTitle: widget.card.category, onHintFound: () { // TODO: Handle what happens when the hint is found. // For example, show a success message or update state. }, onClose: () { Navigator.pop(context); }, ), ), ); } @override Widget build(BuildContext context) { 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, ), 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), ), ], ), 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.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: [ BoxShadow( color: AppColors.primary.withOpacity(0.1), blurRadius: 12, offset: const Offset(0, 6), ), ], ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( 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.isDarkMode ? AppColors.primary.withOpacity(0.4) : const Color(0xFF1976D2).withOpacity(0.25), width: 1.5, ), boxShadow: [ BoxShadow( color: AppColors.isDarkMode ? AppColors.primary.withOpacity(0.2) : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( widget.card.categoryIcon, style: const TextStyle(fontSize: 16), ), const SizedBox(width: 6), Text( widget.card.category, style: TextStyle( color: AppColors.isDarkMode ? AppColors.primary : const Color(0xFF1976D2), fontWeight: FontWeight.w700, fontSize: 11, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( 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.isDarkMode ? AppColors.confirmButton.withOpacity(0.5) : const Color(0xFF2E7D32).withOpacity(0.3), width: 1.5, ), boxShadow: [ BoxShadow( color: AppColors.isDarkMode ? AppColors.confirmButton.withOpacity(0.3) : const Color(0xFF2E7D32).withOpacity(0.15), blurRadius: 8, offset: const Offset(0, 3), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.stars_rounded, color: AppColors.isDarkMode ? AppColors.confirmButton : const Color.fromARGB(255, 38, 121, 41), size: 16, ), const SizedBox(width: 4), Text( '${widget.card.points}', style: TextStyle( color: AppColors.isDarkMode ? AppColors.confirmButton : const Color.fromARGB(255, 38, 121, 41), fontWeight: FontWeight.bold, fontSize: 14, ), ), ], ), ), ], ), const SizedBox(height: 5), Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: RadialGradient( 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.isDarkMode ? AppColors.primary.withOpacity(0.5) : const Color(0xFF1976D2).withOpacity(0.3), width: 2.5, ), boxShadow: [ BoxShadow( color: AppColors.isDarkMode ? AppColors.primary.withOpacity(0.4) : const Color(0xFF1976D2).withOpacity(0.2), blurRadius: 20, spreadRadius: 4, ), BoxShadow( color: AppColors.isDarkMode ? AppColors.primary.withOpacity(0.2) : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 40, spreadRadius: 8, ), ], ), child: Text( widget.card.categoryIcon, style: const TextStyle(fontSize: 36), ), ), const SizedBox(height: 15), Container( padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, 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.isDarkMode ? AppColors.primary.withOpacity(0.3) : const Color(0xFF1976D2).withOpacity(0.2), width: 1.5, ), boxShadow: AppColors.isDarkMode ? [ BoxShadow( color: AppColors.primary.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 4), ), ] : null, ), child: Column( children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.touch_app_rounded, color: AppColors.isDarkMode ? AppColors.primary : const Color(0xFF1976D2), size: 16, ), const SizedBox(width: 8), Text( 'TAP TO REVEAL', style: TextStyle( color: AppColors.isDarkMode ? AppColors.primary : const Color(0xFF1976D2), fontSize: 12, fontWeight: FontWeight.bold, letterSpacing: 1.5, ), ), ], ), // const SizedBox(height: 4), // Text( // '✨ MYSTERY QUEST ✨', // style: TextStyle( // color: AppColors.textSecondary, // fontSize: 10, // fontWeight: FontWeight.w600, // letterSpacing: 1.0, // ), // ), ], ), ), ], ), ), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { return SizedBox(); }), ), ], ), ), ); } Widget _buildBackSide() { final t = _gradientAnimation.value; final pi = 3.14159; return Transform( alignment: Alignment.center, transform: Matrix4.identity()..rotateY(3.14159), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: 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.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, )!, ], ), ), child: Padding( padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( gradient: LinearGradient( 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.isDarkMode ? AppColors.primary.withOpacity(0.2) : const Color(0xFF1976D2).withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Icon( Icons.auto_awesome_rounded, color: AppColors.isDarkMode ? AppColors.primary : const Color(0xFF1976D2), size: 16, ), ), const SizedBox(width: 8), Text( 'Quest', style: TextStyle( color: AppColors.isDarkMode ? AppColors.primary : const Color(0xFF1976D2), fontSize: 15, fontWeight: FontWeight.bold, ), ), ], ), HuntTimerWidget( timeRemaining: const Duration(hours: 12), isActive: true, showMinutesSeconds: false, fontSize: 8, ), ], ), const SizedBox(height: 10), Expanded( flex: 4, child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, 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.isDarkMode ? AppColors.primary.withOpacity(0.3) : const Color(0xFF1976D2).withOpacity(0.15), width: 1.5, ), boxShadow: [ BoxShadow( color: AppColors.isDarkMode ? AppColors.primary.withOpacity(0.1) : const Color(0xFF1976D2).withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 3), ), ], ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Icon( Icons.psychology_outlined, color: AppColors.textSecondary, size: 14, ), const SizedBox(width: 6), Text( 'Riddle', style: TextStyle( color: AppColors.textSecondary, fontSize: 11, fontWeight: FontWeight.w700, ), ), ], ), const SizedBox(height: 6), Text( widget.card.question, style: TextStyle( color: AppColors.textPrimary, fontSize: 13, height: 1.2, fontWeight: FontWeight.bold, ), ), ], ), ), ), ), const SizedBox(height: 8), Expanded( flex: 1, child: GestureDetector( onTap: () => _openARHint(), child: Container( width: double.infinity, padding: const EdgeInsets.all(6), decoration: BoxDecoration( gradient: LinearGradient( 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: AppColors.isDarkMode ? Colors.yellow.withOpacity(0.4) : const Color(0xFFFFB74D).withOpacity(0.4), width: 1, ), boxShadow: [ BoxShadow( color: AppColors.isDarkMode ? Colors.yellow.withOpacity(0.2) : const Color(0xFFFFB74D).withOpacity(0.1), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( Assets.icons.infoCircle.path, color: AppColors.isDarkMode ? Colors.yellow.shade600 : const Color(0xFFE65100), ), const SizedBox(width: 3), Text( 'AR Smart Hint', style: TextStyle( color: AppColors.isDarkMode ? Colors.yellow.shade700 : const Color(0xFFE65100), fontSize: 10, fontWeight: FontWeight.bold, ), ), ], ), ), ), ), const SizedBox(height: 6), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 6), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, 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.isDarkMode ? AppColors.primary.withOpacity(0.4) : const Color(0xFF1976D2).withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 3), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( Assets.icons.locationTick.path, color: Colors.white, width: 14, ), const SizedBox(width: 4), Text( 'Find & Earn ${widget.card.points} Points!', style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ], ), ), ], ), ), ), ); } LinearGradient _buildAnimatedGradient(bool isShowingFront) { final t = _gradientAnimation.value; final pi = 3.14159; if (isShowingFront) { 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 { 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), ], ); } } }