diff --git a/assets/icons/AR.svg b/assets/icons/AR.svg new file mode 100644 index 0000000..6377ff7 --- /dev/null +++ b/assets/icons/AR.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/flash-slash.svg b/assets/icons/flash-slash.svg new file mode 100644 index 0000000..d9dead8 --- /dev/null +++ b/assets/icons/flash-slash.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/flash.svg b/assets/icons/flash.svg new file mode 100644 index 0000000..5b8d304 --- /dev/null +++ b/assets/icons/flash.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/gallery.svg b/assets/icons/gallery.svg new file mode 100644 index 0000000..a7b41a0 --- /dev/null +++ b/assets/icons/gallery.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/keyboard.svg b/assets/icons/keyboard.svg new file mode 100644 index 0000000..140512f --- /dev/null +++ b/assets/icons/keyboard.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index a6563cc..4b1d5bf 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -17,6 +17,9 @@ import 'package:vector_graphics/vector_graphics.dart' as _vg; class $AssetsIconsGen { const $AssetsIconsGen(); + /// File path: assets/icons/AR.svg + SvgGenImage get ar => const SvgGenImage('assets/icons/AR.svg'); + /// File path: assets/icons/Iran 1.svg SvgGenImage get iran1 => const SvgGenImage('assets/icons/Iran 1.svg'); @@ -174,6 +177,13 @@ class $AssetsIconsGen { /// File path: assets/icons/favorite.svg SvgGenImage get favorite => const SvgGenImage('assets/icons/favorite.svg'); + /// File path: assets/icons/flash-slash.svg + SvgGenImage get flashSlash => + const SvgGenImage('assets/icons/flash-slash.svg'); + + /// File path: assets/icons/flash.svg + SvgGenImage get flash => const SvgGenImage('assets/icons/flash.svg'); + /// File path: assets/icons/flat-color-icons_idea.svg SvgGenImage get flatColorIconsIdea => const SvgGenImage('assets/icons/flat-color-icons_idea.svg'); @@ -190,6 +200,9 @@ class $AssetsIconsGen { SvgGenImage get galleryAdd => const SvgGenImage('assets/icons/gallery-add.svg'); + /// File path: assets/icons/gallery.svg + SvgGenImage get gallery => const SvgGenImage('assets/icons/gallery.svg'); + /// File path: assets/icons/game 2.svg SvgGenImage get game2 => const SvgGenImage('assets/icons/game 2.svg'); @@ -252,6 +265,9 @@ class $AssetsIconsGen { SvgGenImage get iranCircle => const SvgGenImage('assets/icons/iran circle.svg'); + /// File path: assets/icons/keyboard.svg + SvgGenImage get keyboard => const SvgGenImage('assets/icons/keyboard.svg'); + /// File path: assets/icons/language-square.svg SvgGenImage get languageSquare => const SvgGenImage('assets/icons/language-square.svg'); @@ -470,6 +486,7 @@ class $AssetsIconsGen { /// List of all assets List get values => [ + ar, iran1, iran, lBALogo, @@ -516,10 +533,13 @@ class $AssetsIconsGen { elementEqual, emptyWallet, favorite, + flashSlash, + flash, flatColorIconsIdea, flatColorIconsPlanner, fluentColorLocationRipple16, galleryAdd, + gallery, game2, gameIconsWinterGloves, game, @@ -537,6 +557,7 @@ class $AssetsIconsGen { infoPic, ionFastFoodOutline, iranCircle, + keyboard, languageSquare, like, link2, diff --git a/lib/main.dart b/lib/main.dart index a866c19..98c4a5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,7 +37,7 @@ class MyApp extends StatelessWidget { themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light, themeAnimationDuration: const Duration(milliseconds: 300), themeAnimationCurve: Curves.easeInOutCubic, - home: CoolSplashScreen( + home: SplashScreen( nextScreen: const SimpleAuthGate(), duration: const Duration(seconds: 3), ), diff --git a/lib/screens/mains/hunt/providers/hunt_provider.dart b/lib/screens/mains/hunt/providers/hunt_provider.dart index 85a4f19..f7d3124 100644 --- a/lib/screens/mains/hunt/providers/hunt_provider.dart +++ b/lib/screens/mains/hunt/providers/hunt_provider.dart @@ -128,10 +128,10 @@ class HuntState extends ChangeNotifier { question: 'In the heart of a grand shopping center, where fountains dance and skiers gather in warmth. Look for a café where the scent of coffee and the aroma of books intertwine.', answer: 'Kino Café', description: 'A cozy book café in Isfahan City Center', - targetLatitude: 32.62501010252744, - targetLongitude: 51.72622026956878, - hintLatitude: 32.62498106569981, - hintLongitude: 51.725834603182015, + targetLatitude: 32.625042058762496, + targetLongitude: 51.7264112781341, + hintLatitude: 32.625042058762496, + hintLongitude: 51.7264112781341, hintDescription: 'Our store is located on Chahar Bagh Abbasi Street', ), HuntCard( @@ -142,10 +142,10 @@ class HuntState extends ChangeNotifier { question: 'Where spices tell stories of ancient Persia, and every dish carries the warmth of tradition. Seek the place where saffron meets hospitality.', answer: 'Shahrzad Restaurant', description: 'Traditional Persian cuisine restaurant', - targetLatitude: 32.625, - targetLongitude: 51.726, - hintLatitude: 32.625, - hintLongitude: 51.726, + targetLatitude: 32.625042058762496, + targetLongitude: 51.7264112781341, + hintLatitude: 32.625042058762496, + hintLongitude: 51.7264112781341, hintDescription: 'Find the AR clue near the traditional architecture', ), HuntCard( @@ -156,10 +156,10 @@ class HuntState extends ChangeNotifier { question: 'Where fashion meets elegance, and every thread tells a story of style. Find the boutique that dresses dreams.', answer: 'Zara Store', description: 'International fashion retailer', - targetLatitude: 32.624, - targetLongitude: 51.725, - hintLatitude: 32.624, - hintLongitude: 51.725, + targetLatitude: 32.625042058762496, + targetLongitude: 51.7264112781341, + hintLatitude: 32.625042058762496, + hintLongitude: 51.7264112781341, hintDescription: 'Check the AR marker at the fashion district entrance', ), HuntCard( @@ -170,10 +170,10 @@ class HuntState extends ChangeNotifier { question: 'In the digital realm where innovation never sleeps, discover the place where technology meets tomorrow.', answer: 'TechnoCity', description: 'Electronics and gadgets store', - targetLatitude: 32.623, - targetLongitude: 51.724, - hintLatitude: 32.623, - hintLongitude: 51.724, + targetLatitude: 32.625042058762496, + targetLongitude: 51.7264112781341, + hintLatitude: 32.625042058762496, + hintLongitude: 51.7264112781341, hintDescription: 'Look for the AR guide near the tech display', ), HuntCard( @@ -184,10 +184,10 @@ class HuntState extends ChangeNotifier { question: 'Where words dance on pages and knowledge finds its home. Seek the sanctuary of stories and wisdom.', answer: 'Ketabsara Bookstore', description: 'Literary paradise with rare collections', - targetLatitude: 32.622, - targetLongitude: 51.723, - hintLatitude: 32.622, - hintLongitude: 51.723, + targetLatitude: 32.625042058762496, + targetLongitude: 51.7264112781341, + hintLatitude: 32.625042058762496, + hintLongitude: 51.7264112781341, hintDescription: 'Find the AR clue in the literature section', ), ]..sort((a, b) => b.points.compareTo(a.points)); diff --git a/lib/screens/mains/hunt/widgets/hint_camera_widget.dart b/lib/screens/mains/hunt/widgets/hint_camera_widget.dart index 651ed02..6897907 100644 --- a/lib/screens/mains/hunt/widgets/hint_camera_widget.dart +++ b/lib/screens/mains/hunt/widgets/hint_camera_widget.dart @@ -50,14 +50,22 @@ class _HintCameraWidgetState extends State double _distanceToTarget = 0.0; Position? _currentPosition; bool _isNearTarget = false; + bool _wasNearTarget = false; bool _isMapVisible = false; Duration _currentRemainingTime = Duration.zero; late AnimationController _rotationController; late AnimationController _scanController; + late AnimationController _entranceController; late Animation _rotationAnimation; late Animation _scanAnimation; + late Animation _entranceOpacityAnimation; + late Animation _entranceScaleAnimation; + late Animation _entranceSlideAnimation; + + late AnimationController _transformController; + late Animation _transformAnimation; final double _cameraHorizontalFov = 60.0; @@ -112,6 +120,11 @@ class _HintCameraWidgetState extends State duration: const Duration(milliseconds: 3500), vsync: this, )..repeat(); + + _entranceController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); _rotationAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _rotationController, curve: Curves.linear), @@ -119,6 +132,44 @@ class _HintCameraWidgetState extends State _scanAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _scanController, curve: Curves.easeInOutSine), ); + + _entranceOpacityAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _entranceController, + curve: const Interval(0.0, 0.8, curve: Curves.easeOut), + ), + ); + + _entranceScaleAnimation = Tween(begin: 0.3, end: 1.0).animate( + CurvedAnimation( + parent: _entranceController, + curve: const Interval(0.0, 0.8, curve: Curves.elasticOut), + ), + ); + + _entranceSlideAnimation = Tween( + begin: const Offset(0.0, 0.5), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _entranceController, + curve: const Interval(0.2, 1.0, curve: Curves.elasticOut), + ), + ); + + _transformController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + + _transformAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _transformController, + curve: Curves.elasticOut, + ), + ); + + _entranceController.forward(); } void _startCompassListening() { @@ -151,7 +202,19 @@ class _HintCameraWidgetState extends State _currentPosition = position; _bearingToTarget = bearing; _distanceToTarget = distance; - _isNearTarget = isNear; + + if (_isNearTarget != isNear) { + _wasNearTarget = _isNearTarget; + _isNearTarget = isNear; + + if (isNear && !_wasNearTarget) { + _transformController.forward(); + } else if (!isNear && _wasNearTarget) { + _transformController.reverse(); + } + } else { + _isNearTarget = isNear; + } }); if (_isMapVisible) { _updateMiniMapCamera(); @@ -215,10 +278,25 @@ class _HintCameraWidgetState extends State _controller?.dispose(); _rotationController.dispose(); _scanController.dispose(); + _entranceController.dispose(); + _transformController.dispose(); _miniMapController.dispose(); super.dispose(); } + void _showARHintInfoDialog() { + showDialog( + context: context, + barrierDismissible: true, + barrierColor: Colors.black.withOpacity(0.7), + builder: (BuildContext context) { + return const _ARHintInfoDialog(); + }, + ); + } + + + Widget _buildMiniMap() { if (_currentPosition == null) return const SizedBox.shrink(); final userLocation = @@ -285,159 +363,198 @@ class _HintCameraWidgetState extends State @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - extendBodyBehindAppBar: true, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight + 10), - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.8), - Colors.black.withOpacity(0.4), - Colors.transparent, - ], - ), - ), - child: ClipRRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), - child: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - toolbarHeight: kToolbarHeight + 10, - leading: Container( - margin: const EdgeInsets.all(8), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Colors.white.withOpacity(0.15), - Colors.white.withOpacity(0.05), - ], - ), - shape: BoxShape.circle, - border: Border.all( - color: Colors.white.withOpacity(0.3), - width: 1.5, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 8, - spreadRadius: 1, - ), - ], - ), - child: IconButton( - onPressed: widget.onClose, - icon: SvgPicture.asset(Assets.icons.back.path, - colorFilter: - const ColorFilter.mode(Colors.white, BlendMode.srcIn)), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), - ), - title: Container( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.primary.withOpacity(0.3), - AppColors.primary.withOpacity(0.1), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(25), - border: Border.all( - color: AppColors.primary.withOpacity(0.4), - width: 1.5, - ), - boxShadow: [ - BoxShadow( - color: AppColors.primary.withOpacity(0.2), - blurRadius: 15, - spreadRadius: 2, - ), - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), - child: const Text( - 'AR Hint', - style: TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.w700, - letterSpacing: 1.2, - fontFamily: 'monospace'), - ), - ), - centerTitle: true, - actions: [ - Container( - margin: const EdgeInsets.all(10), + return AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return FadeTransition( + opacity: _entranceOpacityAnimation, + child: SlideTransition( + position: _entranceSlideAnimation, + child: ScaleTransition( + scale: _entranceScaleAnimation, + child: Scaffold( + backgroundColor: Colors.black, + extendBodyBehindAppBar: true, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight + 10), + child: Container( decoration: BoxDecoration( gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, colors: [ - Colors.white.withOpacity(0.15), - Colors.white.withOpacity(0.05), + Colors.black.withOpacity(0.8), + Colors.black.withOpacity(0.4), + Colors.transparent, ], ), - shape: BoxShape.circle, - border: Border.all( - color: Colors.white.withOpacity(0.3), - width: 1.5, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 8, - spreadRadius: 1, - ), - ], ), - child: IconButton( - onPressed: () {}, - icon: SvgPicture.asset(Assets.icons.infoCircle.path, - colorFilter: const ColorFilter.mode( - Colors.white, BlendMode.srcIn)), + child: ClipRRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), + child: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + toolbarHeight: kToolbarHeight + 10, + leading: Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.05), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + spreadRadius: 1, + ), + ], + ), + child: IconButton( + onPressed: widget.onClose, + icon: SvgPicture.asset(Assets.icons.back.path, + colorFilter: + const ColorFilter.mode(Colors.white, BlendMode.srcIn)), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ), + title: AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return Transform.scale( + scale: 0.8 + (0.2 * _entranceScaleAnimation.value), + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.3 * _entranceOpacityAnimation.value), + AppColors.primary.withOpacity(0.1 * _entranceOpacityAnimation.value), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(25), + border: Border.all( + color: AppColors.primary.withOpacity(0.4 * _entranceOpacityAnimation.value), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.2 * _entranceOpacityAnimation.value), + blurRadius: 15, + spreadRadius: 2, + ), + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + 'AR Hint', + style: TextStyle( + color: Colors.white.withOpacity(_entranceOpacityAnimation.value), + fontSize: 15, + fontWeight: FontWeight.w700, + letterSpacing: 1.2, + fontFamily: 'monospace'), + ), + ), + ); + }, + ), + centerTitle: true, + actions: [ + Container( + margin: const EdgeInsets.all(10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.white.withOpacity(0.10), + Colors.white.withOpacity(0.05), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + spreadRadius: 1, + ), + ], + ), + child: IconButton( + onPressed: _showARHintInfoDialog, + icon: SvgPicture.asset(Assets.icons.infoCircle.path, + colorFilter: const ColorFilter.mode( + Colors.white, BlendMode.srcIn)), + ), + ), + ], + ), + ), ), ), - ], + ), + body: Stack( + children: [ + if (_controller != null) + MobileScanner(controller: _controller!) + else + Container( + color: Colors.black, + child: Center( + child: CircularProgressIndicator(color: AppColors.primary), + ), + ), + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 0.5, sigmaY: 0.5), + child: Container(color: Colors.black.withOpacity(0.1)), + ), + if (widget.remainingTime != null) _buildTimerWidget(), + _buildAROverlay(), + _buildMiniMap(), + ], + ), + floatingActionButton: AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return Transform.translate( + offset: Offset(0, 100 * (1 - _entranceScaleAnimation.value)), + child: Opacity( + opacity: _entranceOpacityAnimation.value, + child: Transform.scale( + scale: _entranceScaleAnimation.value, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: _buildMapFAB(), + ), + ), + ), + ); + }, + ), ), ), ), - ), - ), - body: Stack( - children: [ - if (_controller != null) - MobileScanner(controller: _controller!) - else - Container( - color: Colors.black, - child: Center( - child: CircularProgressIndicator(color: AppColors.primary), - ), - ), - BackdropFilter( - filter: ImageFilter.blur(sigmaX: 0.5, sigmaY: 0.5), - child: Container(color: Colors.black.withOpacity(0.1)), - ), - if (widget.remainingTime != null) _buildTimerWidget(), - _buildAROverlay(), - _buildMiniMap(), - ], - ), - floatingActionButton: _buildMapFAB(), + ); + }, ); } @@ -456,44 +573,56 @@ class _HintCameraWidgetState extends State final isExpired = _currentRemainingTime.inSeconds <= 0 && widget.remainingTime!.inSeconds > 0; - return Positioned( - top: kToolbarHeight + 60, - right: 16, - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 25, sigmaY: 25), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.white.withOpacity(0.2), - width: 0.5, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - isExpired - ? 'Expired' - : hours > 0 - ? '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}' - : '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}', - style: TextStyle( - color: isExpired ? const Color(0xFFFF3B30) : Colors.white, - fontSize: 15, - fontWeight: FontWeight.w600, - letterSpacing: 0.5, + return AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return Positioned( + top: kToolbarHeight + 60, + right: 16 - (100 * (1 - _entranceOpacityAnimation.value)), + child: Opacity( + opacity: _entranceOpacityAnimation.value, + child: Transform.scale( + scale: _entranceScaleAnimation.value, + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.2 * _entranceOpacityAnimation.value), + width: 0.5, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + isExpired + ? 'Expired' + : hours > 0 + ? '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}' + : '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}', + style: TextStyle( + color: (isExpired ? const Color(0xFFFF3B30) : Colors.white) + .withOpacity(_entranceOpacityAnimation.value), + fontSize: 15, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ], + ), ), ), - ], + ), ), ), - ), - ), + ); + }, ); } @@ -535,16 +664,33 @@ class _HintCameraWidgetState extends State Widget _buildAROverlay() { if (_currentPosition == null) { - return const Center( - child: Text( - 'ACQUIRING GPS SIGNAL...', - style: TextStyle( - color: Colors.white70, - fontSize: 16, - fontWeight: FontWeight.bold, - letterSpacing: 1.5, - ), - ), + return AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return Center( + child: FadeTransition( + opacity: _entranceOpacityAnimation, + child: SlideTransition( + position: Tween( + begin: const Offset(0.0, -0.3), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _entranceController, + curve: const Interval(0.4, 1.0, curve: Curves.elasticOut), + )), + child: const Text( + 'ACQUIRING GPS SIGNAL...', + style: TextStyle( + color: Colors.white70, + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: 1.5, + ), + ), + ), + ), + ); + }, ); } @@ -558,22 +704,70 @@ class _HintCameraWidgetState extends State bool isVisible = angleDifference.abs() < (_cameraHorizontalFov / 2); - return Stack( - children: [ - CustomPaint( - painter: HudPainter(animationValue: 1.0), - size: Size.infinite, + return AnimatedBuilder( + animation: _entranceController, + builder: (context, child) { + return Opacity( + opacity: _entranceOpacityAnimation.value, + child: Stack( + children: [ + Transform.scale( + scale: _entranceScaleAnimation.value, + child: CustomPaint( + painter: HudPainter(animationValue: _entranceOpacityAnimation.value), + size: Size.infinite, + ), + ), + if (!isVisible) + _buildDirectionalOverlay(angleDifference), + if (!isVisible) + _buildOffScreenIndicator(angleDifference) + else + _buildOnScreenHint(angleDifference), + ], + ), + ); + }, + ); + } + + + + Widget _buildDirectionalOverlay(double angleDifference) { + final isLeft = angleDifference < 0; + final overlayColor = _isNearTarget + ? const Color(0xFF00E676) + : AppColors.primary; + + return Align( + alignment: isLeft ? Alignment.centerLeft : Alignment.centerRight, + child: Container( + width: MediaQuery.of(context).size.width * 0.13, + height: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: isLeft ? Alignment.centerLeft : Alignment.centerRight, + end: isLeft ? Alignment.centerRight : Alignment.centerLeft, + stops: const [0.0, 0.2, 0.4, 0.7, 1.0], + colors: [ + overlayColor.withOpacity(0.45), + overlayColor.withOpacity(0.30), + overlayColor.withOpacity(0.15), + overlayColor.withOpacity(0.05), + Colors.transparent, + ], + ), ), - if (!isVisible) - _buildOffScreenIndicator(angleDifference) - else - _buildOnScreenHint(angleDifference), - ], + ), ); } Widget _buildOffScreenIndicator(double angleDifference) { final isLeft = angleDifference < 0; + final arrowColor = _isNearTarget + ? const Color(0xFF00E676) + : AppColors.primary; + return Align( alignment: isLeft ? Alignment.centerLeft : Alignment.centerRight, child: Padding( @@ -582,10 +776,10 @@ class _HintCameraWidgetState extends State isLeft ? Icons.arrow_back_ios_new_rounded : Icons.arrow_forward_ios_rounded, - color: AppColors.primary.withOpacity(0.8), + color: arrowColor.withOpacity(0.8), size: 48, shadows: [ - Shadow(color: AppColors.primary.withOpacity(0.5), blurRadius: 15), + Shadow(color: arrowColor.withOpacity(0.5), blurRadius: 15), ], ), ), @@ -603,7 +797,7 @@ class _HintCameraWidgetState extends State bottom: 0, left: (1 + screenX) * (MediaQuery.of(context).size.width / 2) - 100, child: AnimatedBuilder( - animation: Listenable.merge([_rotationController, _scanAnimation]), + animation: Listenable.merge([_rotationAnimation, _scanAnimation, _transformAnimation]), builder: (context, child) { return SizedBox( width: 200, @@ -613,8 +807,9 @@ class _HintCameraWidgetState extends State isNear: _isNearTarget, distance: distance, pulseValue: 1.0, - rotationValue: _rotationController.value, + rotationValue: _rotationAnimation.value, scanValue: _scanAnimation.value, + transformValue: _transformAnimation.value, ), child: Center( child: ClipRRect( @@ -624,7 +819,7 @@ class _HintCameraWidgetState extends State child: Container( width: 140, height: 140, - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.05), shape: BoxShape.circle, @@ -639,16 +834,26 @@ class _HintCameraWidgetState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - _isNearTarget ? "TARGET ACQUIRED" : "SCANNING", - style: TextStyle( - color: _isNearTarget - ? const Color(0xFF00E676) - : const Color(0xFF2196F3), - fontWeight: FontWeight.w600, - fontSize: 12, - letterSpacing: 1.5, - fontFamily: 'monospace'), + Flexible( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + _isNearTarget ? "TARGET\nACQUIRED" : "SCANNING", + textAlign: TextAlign.center, + style: TextStyle( + color: _isNearTarget + ? const Color(0xFF00E676) + : const Color(0xFF2196F3), + fontWeight: FontWeight.w600, + fontSize: _isNearTarget ? 9 : 12, + letterSpacing: _isNearTarget ? 0.5 : 1.5, + height: _isNearTarget ? 1.1 : 1.0, + fontFamily: 'monospace'), + ), + ), + ), ), const SizedBox(height: 12), Text( @@ -780,6 +985,7 @@ class AdvancedTargetPainter extends CustomPainter { final double pulseValue; final double rotationValue; final double scanValue; + final double transformValue; AdvancedTargetPainter({ required this.isNear, @@ -787,14 +993,18 @@ class AdvancedTargetPainter extends CustomPainter { required this.pulseValue, required this.rotationValue, required this.scanValue, + required this.transformValue, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final paint = Paint()..style = PaintingStyle.stroke; - final primaryColor = - isNear ? const Color(0xFF00E676) : const Color(0xFF2196F3); + + final scannerColor = const Color(0xFF2196F3); + final targetColor = const Color(0xFF00E676); + final primaryColor = Color.lerp(scannerColor, targetColor, transformValue)!; + final baseRadius = math.max(60.0, math.min(85.0, 100 - (distance / 2))); paint @@ -818,10 +1028,11 @@ class AdvancedTargetPainter extends CustomPainter { ..strokeWidth = 1.5; canvas.drawCircle(center, baseRadius - 15, paint); - if (!isNear) { + if (!isNear || transformValue < 1.0) { final sweepAngle = scanValue * 2 * math.pi; + final scannerOpacity = isNear ? (1.0 - transformValue) : 1.0; paint - ..color = primaryColor + ..color = scannerColor.withOpacity(scannerOpacity) ..strokeWidth = 4.0; canvas.drawArc( Rect.fromCircle(center: center, radius: baseRadius - 8), @@ -832,15 +1043,197 @@ class AdvancedTargetPainter extends CustomPainter { ); } - if (isNear) { + if (isNear || transformValue > 0.0) { + final targetOpacity = isNear ? transformValue : (1.0 - transformValue); + paint - ..color = primaryColor.withOpacity(1.0 - pulseValue) + ..color = targetColor.withOpacity((1.0 - pulseValue) * targetOpacity) ..strokeWidth = 4.0 ..style = PaintingStyle.stroke; canvas.drawCircle(center, baseRadius + (pulseValue * 15), paint); + + final crosshairScale = transformValue; + final crosshairSize = 20.0 * crosshairScale; + paint + ..color = targetColor.withOpacity(targetOpacity) + ..strokeWidth = 3.0; + + canvas.drawLine( + Offset(center.dx - crosshairSize, center.dy), + Offset(center.dx + crosshairSize, center.dy), + paint, + ); + + canvas.drawLine( + Offset(center.dx, center.dy - crosshairSize), + Offset(center.dx, center.dy + crosshairSize), + paint, + ); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class _ARHintInfoDialog extends StatefulWidget { + const _ARHintInfoDialog({Key? key}) : super(key: key); + + @override + State<_ARHintInfoDialog> createState() => _ARHintInfoDialogState(); +} + +class _ARHintInfoDialogState extends State<_ARHintInfoDialog> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + late Animation _fadeAnimation; + late Animation _iconScaleAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 600), + ); + + _scaleAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.elasticOut, + )); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _controller, + curve: const Interval(0.0, 0.8, curve: Curves.easeOut), + )); + + _iconScaleAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _controller, + curve: const Interval(0.2, 1.0, curve: Curves.bounceOut), + )); + + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: ScaleTransition( + scale: _scaleAnimation, + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + elevation: 10, + backgroundColor: AppColors.surface, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.topCenter, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 50, + left: 20, + right: 20, + bottom: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text( + "AR Hint Guide", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 15), + Text( + "• Point camera around to locate target\n• Blue circle shows target direction\n• Arrows guide you when target is off-screen\n• Circle turns green when target found", + style: TextStyle( + color: AppColors.popupText, + fontSize: 15, + height: 1.5, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 25), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: AppColors.textPrimary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + onPressed: () async { + await _controller.reverse(); + if (mounted) Navigator.of(context).pop(); + }, + child: const Text("Got it"), + ), + ), + ], + ), + ), + Positioned( + top: -40, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.surface, + boxShadow: [ + BoxShadow( + color: AppColors.shadowColor, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: CircleAvatar( + backgroundColor: AppColors.surface, + radius: 40, + child: ScaleTransition( + scale: _iconScaleAnimation, + child: SvgPicture.asset( + Assets.icons.ar.path, + colorFilter: ColorFilter.mode( + AppColors.primary, + BlendMode.srcIn, + ), + width: 35, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } } \ No newline at end of file diff --git a/lib/screens/mains/hunt/widgets/hunt_card_widget.dart b/lib/screens/mains/hunt/widgets/hunt_card_widget.dart index fc946ca..411536f 100644 --- a/lib/screens/mains/hunt/widgets/hunt_card_widget.dart +++ b/lib/screens/mains/hunt/widgets/hunt_card_widget.dart @@ -731,7 +731,7 @@ class _HuntCardWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( - Assets.icons.infoCircle.path, + Assets.icons.ar.path, color: AppColors.isDarkMode ? Colors.yellow.shade600 : const Color(0xFFE65100), diff --git a/lib/screens/mains/profile/profile.dart b/lib/screens/mains/profile/profile.dart index fc13975..6e0dace 100644 --- a/lib/screens/mains/profile/profile.dart +++ b/lib/screens/mains/profile/profile.dart @@ -26,7 +26,7 @@ class _ProfileState extends State with TickerProviderStateMixin { late AnimationController _animationController; late List> _animations; - String _currentLanguage = '🇺🇲 English'; + String _currentLanguage = '🇺🇲 English'; String _currentFlag = 'assets/icons/usa circle.svg'; final GlobalKey _languageTileKey = GlobalKey(); diff --git a/lib/screens/qr_scanner/qr_scanner_page.dart b/lib/screens/qr_scanner/qr_scanner_page.dart index 32b2374..b336ebb 100644 --- a/lib/screens/qr_scanner/qr_scanner_page.dart +++ b/lib/screens/qr_scanner/qr_scanner_page.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:lba/gen/assets.gen.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -179,11 +181,7 @@ class _QRScannerPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( - icon: Icon( - Icons.arrow_back, - color: AppColors.textPrimary, - size: 28, - ), + icon: SvgPicture.asset(Assets.icons.back.path,color: AppColors.textPrimary, width: 28, height: 28), onPressed: () => Navigator.pop(context), ), Text( @@ -195,10 +193,9 @@ class _QRScannerPageState extends State { ), ), IconButton( - icon: Icon( - _isFlashOn ? Icons.flash_on : Icons.flash_off, + icon: SvgPicture.asset( + _isFlashOn ? Assets.icons.flashSlash.path : Assets.icons.flash.path, color: AppColors.textPrimary, - size: 28, ), onPressed: _toggleFlash, ), @@ -223,62 +220,62 @@ class _QRScannerPageState extends State { ), ), - Positioned( - top: -2, - left: -2, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: AppColors.primary, width: 4), - left: BorderSide(color: AppColors.primary, width: 4), - ), - ), - ), - ), - Positioned( - top: -2, - right: -2, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: AppColors.primary, width: 4), - right: BorderSide(color: AppColors.primary, width: 4), - ), - ), - ), - ), - Positioned( - bottom: -2, - left: -2, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: AppColors.primary, width: 4), - left: BorderSide(color: AppColors.primary, width: 4), - ), - ), - ), - ), - Positioned( - bottom: -2, - right: -2, - child: Container( - width: 30, - height: 30, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: AppColors.primary, width: 4), - right: BorderSide(color: AppColors.primary, width: 4), - ), - ), - ), - ), + // Positioned( + // top: -2, + // left: -2, + // child: Container( + // width: 30, + // height: 30, + // decoration: BoxDecoration( + // border: Border( + // top: BorderSide(color: AppColors.primary, width: 4), + // left: BorderSide(color: AppColors.primary, width: 4), + // ), + // ), + // ), + // ), + // Positioned( + // top: -2, + // right: -2, + // child: Container( + // width: 30, + // height: 30, + // decoration: BoxDecoration( + // border: Border( + // top: BorderSide(color: AppColors.primary, width: 4), + // right: BorderSide(color: AppColors.primary, width: 4), + // ), + // ), + // ), + // ), + // Positioned( + // bottom: -2, + // left: -2, + // child: Container( + // width: 30, + // height: 30, + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide(color: AppColors.primary, width: 4), + // left: BorderSide(color: AppColors.primary, width: 4), + // ), + // ), + // ), + // ), + // Positioned( + // bottom: -2, + // right: -2, + // child: Container( + // width: 30, + // height: 30, + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide(color: AppColors.primary, width: 4), + // right: BorderSide(color: AppColors.primary, width: 4), + // ), + // ), + // ), + // ), ], ), ), @@ -317,10 +314,9 @@ class _QRScannerPageState extends State { borderRadius: BorderRadius.circular(30), ), child: IconButton( - icon: Icon( - Icons.photo_library, + icon: SvgPicture.asset( + Assets.icons.gallery.path, color: AppColors.surface, - size: 28, ), onPressed: _pickImageFromGallery, ), @@ -334,10 +330,9 @@ class _QRScannerPageState extends State { borderRadius: BorderRadius.circular(30), ), child: IconButton( - icon: Icon( - Icons.keyboard, + icon: SvgPicture.asset( + Assets.icons.keyboard.path, color: AppColors.surface, - size: 28, ), onPressed: () { _showManualInputDialog(); @@ -355,32 +350,227 @@ class _QRScannerPageState extends State { void _showManualInputDialog() { showDialog( context: context, + barrierDismissible: true, builder: (context) { final TextEditingController controller = TextEditingController(); - return AlertDialog( - title: const Text('Enter Code Manually'), - content: TextField( - controller: controller, - decoration: const InputDecoration( - hintText: 'Enter QR code or barcode', - border: OutlineInputBorder(), + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header with icon and title + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: SvgPicture.asset( + Assets.icons.keyboard.path, + color: AppColors.primary, + width: 24, + height: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Manual Entry', + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + 'Enter your code manually', + style: TextStyle( + color: AppColors.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 24), + + // Input field with enhanced styling + Container( + decoration: BoxDecoration( + color: AppColors.cardBackground, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.textSecondary.withOpacity(0.2), + width: 1, + ), + ), + child: TextField( + controller: controller, + style: TextStyle( + color: AppColors.textPrimary, + fontSize: 16, + ), + maxLines: 3, + minLines: 1, + decoration: InputDecoration( + hintText: 'Enter QR code, barcode, or any text...', + hintStyle: TextStyle( + color: AppColors.textSecondary.withOpacity(0.7), + fontSize: 14, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.all(16), + prefixIcon: Padding( + padding: const EdgeInsets.all(16), + child: Icon( + Icons.qr_code_2, + color: AppColors.textSecondary.withOpacity(0.5), + size: 20, + ), + ), + ), + autofocus: true, + textInputAction: TextInputAction.done, + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + Navigator.pop(context); + _handleScanResult(value.trim()); + } + }, + ), + ), + + const SizedBox(height: 8), + + // Helper text + Text( + 'Tip: You can paste codes from clipboard or type them manually', + style: TextStyle( + color: AppColors.textSecondary.withOpacity(0.8), + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ), + + const SizedBox(height: 24), + + // Action buttons with enhanced styling + Row( + children: [ + Expanded( + child: Container( + height: 48, + decoration: BoxDecoration( + border: Border.all( + color: AppColors.textSecondary.withOpacity(0.3), + width: 1, + ), + borderRadius: BorderRadius.circular(12), + ), + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + foregroundColor: AppColors.textSecondary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: const Text( + 'Cancel', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Container( + height: 48, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: ElevatedButton( + onPressed: () { + if (controller.text.trim().isNotEmpty) { + Navigator.pop(context); + _handleScanResult(controller.text.trim()); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Submit', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 8), + const Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 16, + ), + ], + ), + ), + ), + ), + ], + ), + ], ), ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - if (controller.text.isNotEmpty) { - Navigator.pop(context); - _handleScanResult(controller.text); - } - }, - child: const Text('Submit'), - ), - ], ); }, ); diff --git a/lib/widgets/animated_splash_screen.dart b/lib/widgets/animated_splash_screen.dart index 909867f..864b6a6 100644 --- a/lib/widgets/animated_splash_screen.dart +++ b/lib/widgets/animated_splash_screen.dart @@ -4,21 +4,21 @@ import 'package:lba/res/colors.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../gen/assets.gen.dart'; -class CoolSplashScreen extends StatefulWidget { +class SplashScreen extends StatefulWidget { final Widget nextScreen; final Duration duration; - const CoolSplashScreen({ + const SplashScreen({ super.key, required this.nextScreen, this.duration = const Duration(seconds: 6), }); @override - State createState() => _CoolSplashScreenState(); + State createState() => _SplashScreenState(); } -class _CoolSplashScreenState extends State +class _SplashScreenState extends State with TickerProviderStateMixin { late AnimationController _mainController; late Animation _backgroundAnimation; diff --git a/lib/widgets/language_selection_dialog.dart b/lib/widgets/language_selection_dialog.dart index 6b02ffe..327252f 100644 --- a/lib/widgets/language_selection_dialog.dart +++ b/lib/widgets/language_selection_dialog.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:lba/res/colors.dart'; class LanguageSelectionOverlay extends StatefulWidget { @@ -27,19 +26,29 @@ class _LanguageSelectionOverlayState extends State late String _selectedLanguage; final List> languages = [ - {'name': '🇺🇲 English', 'code': 'en', 'flag': 'assets/icons/usa circle.svg'}, { - 'name': '🇸🇦 العربية', + 'name': '🇺🇲 English', + 'code': 'en', + 'flag': 'assets/icons/usa circle.svg', + }, + { + 'name': '🇦🇪 العربية', 'code': 'ar', 'flag': 'assets/icons/arab circle.svg', }, - {'name': '🇮🇷 فارسی', 'code': 'fa', 'flag': 'assets/icons/iran circle.svg'}, + { + 'name': '🇮🇷 فارسی', + 'code': 'fa', + 'flag': 'assets/icons/iran circle.svg', + }, ]; @override void initState() { super.initState(); - _selectedLanguage = widget.currentLanguage; + _selectedLanguage = widget.currentLanguage.isNotEmpty + ? widget.currentLanguage + : '🇺🇲 English'; _animationController = AnimationController( vsync: this, @@ -75,7 +84,6 @@ class _LanguageSelectionOverlayState extends State _selectedLanguage = languageName; }); - // Wait a bit for visual feedback then close Future.delayed(const Duration(milliseconds: 150), () { widget.onLanguageSelected(languageName, flag); _closeOverlay(); @@ -111,7 +119,7 @@ class _LanguageSelectionOverlayState extends State child: Stack( children: [ Positioned( - right: targetPosition.dx+10, + right: targetPosition.dx + 10, top: targetPosition.dy + 40, child: Transform.scale( scale: _scaleAnimation.value, @@ -179,8 +187,6 @@ class _LanguageSelectionOverlayState extends State padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ - - // Language Name Expanded( child: Text( name, @@ -197,15 +203,17 @@ class _LanguageSelectionOverlayState extends State ), ), - // Selection Indicator if (isSelected) - Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColors.primary, - shape: BoxShape.circle, + Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 0, 0), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: AppColors.primary, + shape: BoxShape.circle, + ), + child: Icon(Icons.check, color: AppColors.surface, size: 12), ), - child: Icon(Icons.check, color: AppColors.surface, size: 12), ), ], ),