improve hunt for light mode

This commit is contained in:
mohamadmahdi jebeli 2025-09-11 11:59:49 +03:30
parent 32557bb5d8
commit e478747724
3 changed files with 690 additions and 1407 deletions

View File

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:vibration/vibration.dart'; import 'package:vibration/vibration.dart';
@ -9,7 +11,6 @@ import 'services/game_sound_service.dart';
import 'widgets/hunt_card_widget.dart'; import 'widgets/hunt_card_widget.dart';
import 'widgets/leaderboard_widget.dart'; import 'widgets/leaderboard_widget.dart';
import 'widgets/hint_camera_widget.dart'; import 'widgets/hint_camera_widget.dart';
import 'widgets/hunt_timer_widget.dart';
import 'models/hunt_card.dart'; import 'models/hunt_card.dart';
class Hunt extends StatelessWidget { class Hunt extends StatelessWidget {
@ -35,8 +36,10 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
late AnimationController _mainAnimationController; late AnimationController _mainAnimationController;
late AnimationController _cardAnimationController; late AnimationController _cardAnimationController;
late AnimationController _confettiController; late AnimationController _confettiController;
late AnimationController _backgroundGradientController;
late Animation<double> _fadeAnimation; late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation; late Animation<Offset> _slideAnimation;
late Animation<double> _gradientAnimation;
Timer? _locationTimer; Timer? _locationTimer;
bool _showLeaderboard = false; bool _showLeaderboard = false;
@ -61,6 +64,10 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
duration: const Duration(milliseconds: 2000), duration: const Duration(milliseconds: 2000),
vsync: this, vsync: this,
); );
_backgroundGradientController = AnimationController(
duration: const Duration(seconds: 8),
vsync: this,
)..repeat();
_fadeAnimation = Tween<double>( _fadeAnimation = Tween<double>(
begin: 0.0, begin: 0.0,
@ -78,11 +85,19 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
curve: Curves.easeOutCubic, curve: Curves.easeOutCubic,
)); ));
_gradientAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _backgroundGradientController,
curve: Curves.easeInOut,
));
_mainAnimationController.forward(); _mainAnimationController.forward();
} }
void _initializeGame() { void _initializeGame() {
// The initialization is now handled in the provider creation // Initialization is handled in provider creation
} }
@override @override
@ -90,6 +105,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
_mainAnimationController.dispose(); _mainAnimationController.dispose();
_cardAnimationController.dispose(); _cardAnimationController.dispose();
_confettiController.dispose(); _confettiController.dispose();
_backgroundGradientController.dispose();
_locationTimer?.cancel(); _locationTimer?.cancel();
GameSoundService.dispose(); GameSoundService.dispose();
super.dispose(); super.dispose();
@ -119,7 +135,6 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
void _onCardSelected(HuntCard card) async { void _onCardSelected(HuntCard card) async {
final huntProvider = Provider.of<HuntState>(context, listen: false); final huntProvider = Provider.of<HuntState>(context, listen: false);
// Gaming feedback: sound + vibration
await GameSoundService.playCardFlipSound(); await GameSoundService.playCardFlipSound();
Vibration.hasVibrator().then((hasVibrator) { Vibration.hasVibrator().then((hasVibrator) {
if (hasVibrator == true) { if (hasVibrator == true) {
@ -128,8 +143,6 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
}); });
huntProvider.selectCard(card); huntProvider.selectCard(card);
// No dialog, just flip the card to show the riddle
} }
void _showPermissionDialog(String message) { void _showPermissionDialog(String message) {
@ -197,7 +210,7 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
}, },
onClose: () { onClose: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
huntProvider.startHunt(); // Go back to hunting mode huntProvider.startHunt();
}, },
), ),
), ),
@ -333,8 +346,40 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.scaffoldBackground, body: AnimatedBuilder(
body: Consumer<HuntState>( 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],
),
),
child: Consumer<HuntState>(
builder: (context, huntProvider, child) { builder: (context, huntProvider, child) {
return Stack( return Stack(
children: [ children: [
@ -347,8 +392,6 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
), ),
), ),
), ),
// Leaderboard overlay
if (_showLeaderboard) if (_showLeaderboard)
Positioned.fill( Positioned.fill(
child: Container( child: Container(
@ -372,10 +415,12 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
}, },
), ),
); );
},
),
);
} }
Widget _buildMainContent(HuntState huntProvider) { Widget _buildMainContent(HuntState huntProvider) {
// Always show card selection page, don't switch to other views
return _buildCardSelection(huntProvider); return _buildCardSelection(huntProvider);
} }
@ -383,91 +428,134 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
return Column( return Column(
children: [ children: [
_buildHeader(), _buildHeader(),
const SizedBox(height: 20),
_buildPointsDisplay(huntProvider),
const SizedBox(height: 24),
_buildInstructions(),
const SizedBox(height: 20),
Expanded( 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() { Widget _buildHeader() {
return Container( return ClipRRect(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), borderRadius: BorderRadius.circular(16),
padding: const EdgeInsets.all(20), child: BackdropFilter(
decoration: BoxDecoration( filter: ImageFilter.blur(
gradient: LinearGradient( sigmaX: AppColors.isDarkMode ? 0 : 10.0,
begin: Alignment.topLeft, sigmaY: AppColors.isDarkMode ? 0 : 10.0,
end: Alignment.bottomRight,
colors: [
AppColors.primary.withOpacity(0.1),
AppColors.primary.withOpacity(0.05),
],
), ),
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( border: Border.all(
color: AppColors.primary.withOpacity(0.2), color: AppColors.isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white.withOpacity(0.2),
width: 1, width: 1,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.1), color: AppColors.isDarkMode
blurRadius: 10, ? Colors.black.withOpacity(0.3)
offset: const Offset(0, 4), : Colors.black.withOpacity(0.04),
blurRadius: 8,
offset: const Offset(0, 2),
), ),
], ],
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.2), color: AppColors.isDarkMode
? Colors.white.withOpacity(0.1)
: const Color(0xFF1976D2).withOpacity(0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Icon( child: Icon(
Icons.explore_rounded, Icons.search_rounded,
color: AppColors.primary, color: AppColors.isDarkMode
? Colors.white
: const Color(0xFF1976D2),
size: 20, size: 20,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text( Text(
'Treasure Hunt', 'Hunt',
style: TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: AppColors.textPrimary,
fontSize: 24, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w700,
letterSpacing: -0.5,
), ),
), ),
],
),
const SizedBox(height: 8),
Text( Text(
'🗺️ Discover hidden gems in your city', 'Discover & Collect',
style: TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: AppColors.textSecondary,
fontSize: 14, fontSize: 13,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: 16),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
@ -475,38 +563,33 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
}); });
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(14), padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: AppColors.isDarkMode
colors: [ ? Colors.white.withOpacity(0.1)
AppColors.primary, : const Color(0xFF1976D2).withOpacity(0.1),
AppColors.primary.withOpacity(0.8), borderRadius: BorderRadius.circular(12),
],
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(
Icons.leaderboard_rounded, Icons.leaderboard_rounded,
color: Colors.white, color: AppColors.isDarkMode
size: 20, ? Colors.white
: const Color(0xFF1976D2),
size: 16,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
'Ranks', 'Ranks',
style: TextStyle( style: TextStyle(
color: Colors.white, color: AppColors.isDarkMode
? Colors.white
: const Color(0xFF1976D2),
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
), ),
), ),
], ],
@ -515,59 +598,31 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
), ),
], ],
), ),
);
}
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) { Widget _buildPointsDisplay(HuntState huntProvider) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 20), margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBackground, color: AppColors.isDarkMode
? Colors.white.withOpacity(0.10)
: Colors.white.withOpacity(0.6), // More transparent
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppColors.isDarkMode
? Colors.white.withOpacity(0.15)
: Colors.white.withOpacity(0.3),
width: 1,
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.shadowColor, color: Colors.black.withOpacity(0.05),
blurRadius: 10, blurRadius: 20,
offset: const Offset(0, 4), offset: const Offset(0, 10),
), ),
], ],
), ),
@ -578,18 +633,32 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
icon: Icons.stars_rounded, icon: Icons.stars_rounded,
label: 'Total Points', label: 'Total Points',
value: '${huntProvider.userPoints}', value: '${huntProvider.userPoints}',
color: AppColors.confirmButton, color: AppColors.isDarkMode
? const Color(0xFF64B5F6)
: const Color(0xFF1976D2),
), ),
Container( Container(
height: 40, height: 32,
width: 1, 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( _buildStatItem(
icon: Icons.emoji_events, icon: Icons.emoji_events,
label: 'Rank', label: 'Rank',
value: '#${huntProvider.currentUserRank}', 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, required Color color,
}) { }) {
return Column( return Column(
mainAxisSize: MainAxisSize.min,
children: [ 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), const SizedBox(height: 8),
Text( Text(
value, value,
style: TextStyle( style: TextStyle(
color: AppColors.textPrimary, color: AppColors.textPrimary,
fontSize: 20, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
), ),
), ),
const SizedBox(height: 2),
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
color: AppColors.textSecondary, color: AppColors.textSecondary,
fontSize: 12, fontSize: 11,
fontWeight: FontWeight.w400,
), ),
), ),
], ],
@ -628,44 +707,45 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
Widget _buildInstructions() { Widget _buildInstructions() {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 20), margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: AppColors.isDarkMode
begin: Alignment.topLeft, ? Colors.white.withOpacity(0.08)
end: Alignment.bottomRight, : Colors.white.withOpacity(0.7), // More transparent
colors: [
Colors.amber.withOpacity(0.15),
Colors.orange.withOpacity(0.1),
],
),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: Colors.amber.withOpacity(0.3), color: AppColors.isDarkMode
? Colors.white.withOpacity(0.12)
: Colors.white.withOpacity(0.4),
width: 1, width: 1,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.amber.withOpacity(0.1), color: Colors.black.withOpacity(0.03),
blurRadius: 8, blurRadius: 15,
offset: const Offset(0, 2), offset: const Offset(0, 8),
), ),
], ],
), ),
child: Row( child: Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.2), color: AppColors.isDarkMode
borderRadius: BorderRadius.circular(12), ? const Color(0xFF64B5F6).withOpacity(0.2)
: const Color(0xFF1976D2).withOpacity(0.12),
borderRadius: BorderRadius.circular(10),
), ),
child: Icon( child: Icon(
Icons.psychology_rounded, Icons.psychology_rounded,
color: Colors.amber.shade700, color: AppColors.isDarkMode
? const Color(0xFF64B5F6)
: const Color(0xFF1976D2),
size: 18, size: 18,
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -673,18 +753,18 @@ class _HuntContentState extends State<_HuntContent> with TickerProviderStateMixi
Text( Text(
'Gaming Quest Mode', 'Gaming Quest Mode',
style: TextStyle( style: TextStyle(
color: Colors.amber.shade800, color: AppColors.textPrimary,
fontSize: 15, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 3),
Text( Text(
'Tap any card to reveal your mystery quest! Solve riddles, find locations, and earn points.', 'Tap any card to reveal your mystery quest! Solve riddles, find locations, and earn points.',
style: TextStyle( style: TextStyle(
color: Colors.amber.shade700, color: AppColors.textSecondary,
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w400,
height: 1.3, 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,
),
),
),
],
);
}
} }

View File

@ -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<double> _fadeAnimation;
late Animation<Offset> _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<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _mainAnimationController,
curve: Curves.easeInOut,
));
_slideAnimation = Tween<Offset>(
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<HuntState>(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<HuntState>(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<HuntState>(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<HuntState>(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<HuntState>(
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,
),
),
),
],
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:lba/res/colors.dart';
import '../models/hunt_card.dart'; import '../models/hunt_card.dart';
import 'hunt_timer_widget.dart'; import 'hunt_timer_widget.dart';
import 'hint_camera_widget.dart'; import 'hint_camera_widget.dart';
import 'dart:math';
class HuntCardWidget extends StatefulWidget { class HuntCardWidget extends StatefulWidget {
final HuntCard card; final HuntCard card;
@ -30,8 +31,10 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late AnimationController _flipController; late AnimationController _flipController;
late AnimationController _scaleController; late AnimationController _scaleController;
late AnimationController _gradientController;
late Animation<double> _flipAnimation; late Animation<double> _flipAnimation;
late Animation<double> _scaleAnimation; late Animation<double> _scaleAnimation;
late Animation<double> _gradientAnimation;
@override @override
void initState() { void initState() {
@ -44,6 +47,10 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
vsync: this, vsync: this,
); );
_gradientController = AnimationController(
duration: const Duration(seconds: 8),
vsync: this,
)..repeat();
_flipAnimation = Tween<double>( _flipAnimation = Tween<double>(
begin: 0.0, begin: 0.0,
@ -60,6 +67,14 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
parent: _scaleController, parent: _scaleController,
curve: Curves.elasticOut, curve: Curves.elasticOut,
)); ));
_gradientAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _gradientController,
curve: Curves.easeInOut,
));
} }
@override @override
@ -78,6 +93,7 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
void dispose() { void dispose() {
_flipController.dispose(); _flipController.dispose();
_scaleController.dispose(); _scaleController.dispose();
_gradientController.dispose();
super.dispose(); super.dispose();
} }
@ -111,6 +127,9 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _gradientAnimation,
builder: (context, child) {
return GestureDetector( return GestureDetector(
onTapDown: (_) => _scaleController.forward(), onTapDown: (_) => _scaleController.forward(),
onTapUp: (_) => _scaleController.reverse(), onTapUp: (_) => _scaleController.reverse(),
@ -149,21 +168,7 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
gradient: LinearGradient( gradient: _buildAnimatedGradient(isShowingFront),
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),
],
),
border: widget.isSelected && !isShowingFront border: widget.isSelected && !isShowingFront
? Border.all( ? Border.all(
color: AppColors.primary.withOpacity(0.7), color: AppColors.primary.withOpacity(0.7),
@ -194,19 +199,52 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
), ),
), ),
); );
},
);
} }
Widget _buildFrontSide() { Widget _buildFrontSide() {
final t = _gradientAnimation.value;
final pi = 3.14159;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: AppColors.isDarkMode ? [
Color.lerp(
AppColors.cardBackground, AppColors.cardBackground,
AppColors.cardBackground.withOpacity(0.95),
(sin(t * pi * 2.4) + 1) / 2,
)!,
Color.lerp(
AppColors.cardBackground.withOpacity(0.9), 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.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: [
@ -229,19 +267,26 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: AppColors.isDarkMode ? [
AppColors.primary.withOpacity(0.25), AppColors.primary.withOpacity(0.25),
AppColors.primary.withOpacity(0.15), AppColors.primary.withOpacity(0.15),
] : [
const Color(0xFF1976D2).withOpacity(0.12),
const Color(0xFF1976D2).withOpacity(0.08),
], ],
), ),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( 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, width: 1.5,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.2), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.2)
: const Color(0xFF1976D2).withOpacity(0.1),
blurRadius: 6, blurRadius: 6,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -258,7 +303,9 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
Text( Text(
widget.card.category, widget.card.category,
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.isDarkMode
? AppColors.primary
: const Color(0xFF1976D2),
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 11, fontSize: 11,
), ),
@ -270,19 +317,26 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: AppColors.isDarkMode ? [
AppColors.confirmButton.withOpacity(0.25), AppColors.confirmButton.withOpacity(0.25),
AppColors.confirmButton.withOpacity(0.15), AppColors.confirmButton.withOpacity(0.15),
] : [
const Color(0xFF2E7D32).withOpacity(0.15),
const Color(0xFF2E7D32).withOpacity(0.08),
], ],
), ),
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
border: Border.all( 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, width: 1.5,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.confirmButton.withOpacity(0.3), color: AppColors.isDarkMode
? AppColors.confirmButton.withOpacity(0.3)
: const Color(0xFF2E7D32).withOpacity(0.15),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
@ -293,14 +347,18 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
children: [ children: [
Icon( Icon(
Icons.stars_rounded, Icons.stars_rounded,
color: AppColors.confirmButton, color: AppColors.isDarkMode
? AppColors.confirmButton
: const Color(0xFF2E7D32),
size: 16, size: 16,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${widget.card.points}', '${widget.card.points}',
style: TextStyle( style: TextStyle(
color: AppColors.confirmButton, color: AppColors.isDarkMode
? AppColors.confirmButton
: const Color(0xFF2E7D32),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, fontSize: 14,
), ),
@ -321,26 +379,37 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: RadialGradient( gradient: RadialGradient(
colors: [ colors: AppColors.isDarkMode ? [
AppColors.primary.withOpacity(0.4), AppColors.primary.withOpacity(0.4),
AppColors.primary.withOpacity(0.2), AppColors.primary.withOpacity(0.2),
AppColors.primary.withOpacity(0.1), AppColors.primary.withOpacity(0.1),
AppColors.primary.withOpacity(0.05), 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, shape: BoxShape.circle,
border: Border.all( 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, width: 2.5,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.4), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.4)
: const Color(0xFF1976D2).withOpacity(0.2),
blurRadius: 20, blurRadius: 20,
spreadRadius: 4, spreadRadius: 4,
), ),
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.2), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.2)
: const Color(0xFF1976D2).withOpacity(0.1),
blurRadius: 40, blurRadius: 40,
spreadRadius: 8, spreadRadius: 8,
), ),
@ -359,20 +428,28 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: AppColors.isDarkMode ? [
AppColors.surface.withOpacity(0.8), AppColors.surface.withOpacity(0.8),
AppColors.primary.withOpacity(0.1), AppColors.primary.withOpacity(0.1),
AppColors.surface.withOpacity(0.8), AppColors.surface.withOpacity(0.8),
] : [
const Color(0xFFFFFFFE),
const Color(0xFF1976D2).withOpacity(0.04),
const Color(0xFFFFFFFE),
], ],
), ),
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
border: Border.all( 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, width: 1.5,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.1), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.1)
: const Color(0xFF1976D2).withOpacity(0.06),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@ -385,14 +462,18 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
children: [ children: [
Icon( Icon(
Icons.touch_app_rounded, Icons.touch_app_rounded,
color: AppColors.primary, color: AppColors.isDarkMode
? AppColors.primary
: const Color(0xFF1976D2),
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'TAP TO REVEAL', 'TAP TO REVEAL',
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.isDarkMode
? AppColors.primary
: const Color(0xFF1976D2),
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.5, letterSpacing: 1.5,
@ -421,30 +502,7 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (index) { children: List.generate(3, (index) {
final isActive = index < (widget.card.points ~/ 50).clamp(1, 3);
return SizedBox(); 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<HuntCardWidget>
} }
Widget _buildBackSide() { Widget _buildBackSide() {
final t = _gradientAnimation.value;
final pi = 3.14159;
return Transform( return Transform(
alignment: Alignment.center, alignment: Alignment.center,
transform: Matrix4.identity()..rotateY(3.14159), transform: Matrix4.identity()..rotateY(3.14159),
@ -463,10 +524,38 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: AppColors.isDarkMode ? [
Color.lerp(
AppColors.primary.withOpacity(0.2), 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.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.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<HuntCardWidget>
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: AppColors.isDarkMode ? [
AppColors.primary.withOpacity(0.3), AppColors.primary.withOpacity(0.3),
AppColors.primary.withOpacity(0.2), AppColors.primary.withOpacity(0.2),
] : [
const Color(0xFF1976D2).withOpacity(0.15),
const Color(0xFF1976D2).withOpacity(0.1),
], ],
), ),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.2), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.2)
: const Color(0xFF1976D2).withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -501,7 +595,9 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
), ),
child: Icon( child: Icon(
Icons.auto_awesome_rounded, Icons.auto_awesome_rounded,
color: AppColors.primary, color: AppColors.isDarkMode
? AppColors.primary
: const Color(0xFF1976D2),
size: 16, size: 16,
), ),
), ),
@ -509,7 +605,9 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
Text( Text(
'Quest', 'Quest',
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.isDarkMode
? AppColors.primary
: const Color(0xFF1976D2),
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -537,19 +635,26 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: AppColors.isDarkMode ? [
AppColors.surface.withOpacity(0.9), AppColors.surface.withOpacity(0.9),
AppColors.surface.withOpacity(0.7), AppColors.surface.withOpacity(0.7),
] : [
const Color(0xFFFFFFFE),
const Color(0xFFFDFDFD),
], ],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( 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, width: 1.5,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.1), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.1)
: const Color(0xFF1976D2).withOpacity(0.05),
blurRadius: 6, blurRadius: 6,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
@ -606,19 +711,26 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: AppColors.isDarkMode ? [
Colors.yellow.withOpacity(0.2), Colors.yellow.withOpacity(0.2),
Colors.blue.withOpacity(0.15), Colors.blue.withOpacity(0.15),
] : [
const Color(0xFFFFF8E1),
const Color(0xFFE3F2FD),
], ],
), ),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( 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, width: 1,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.yellow.withOpacity(0.2), color: AppColors.isDarkMode
? Colors.yellow.withOpacity(0.2)
: const Color(0xFFFFB74D).withOpacity(0.1),
blurRadius: 6, blurRadius: 6,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@ -629,14 +741,18 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
children: [ children: [
Icon( Icon(
Icons.help, Icons.help,
color: Colors.yellow.shade600, color: AppColors.isDarkMode
? Colors.yellow.shade600
: const Color(0xFFE65100),
size: 14, size: 14,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
'AR Smart Hint', 'AR Smart Hint',
style: TextStyle( style: TextStyle(
color: Colors.yellow.shade700, color: AppColors.isDarkMode
? Colors.yellow.shade700
: const Color(0xFFE65100),
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -657,16 +773,22 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: AppColors.isDarkMode ? [
AppColors.primary, AppColors.primary,
AppColors.primary.withOpacity(0.8), AppColors.primary.withOpacity(0.8),
AppColors.primary.withOpacity(0.9), AppColors.primary.withOpacity(0.9),
] : [
const Color(0xFF1976D2),
const Color(0xFF1565C0),
const Color(0xFF0D47A1),
], ],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withOpacity(0.4), color: AppColors.isDarkMode
? AppColors.primary.withOpacity(0.4)
: const Color(0xFF1976D2).withOpacity(0.3),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 3), offset: const Offset(0, 3),
), ),
@ -698,4 +820,99 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
), ),
); );
} }
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),
],
);
}
}
} }