add side shadow for hint

This commit is contained in:
mohamadmahdi jebeli 2025-09-21 10:09:29 +03:30
parent 3f7ad2a3df
commit 9a4c51dafb
14 changed files with 988 additions and 353 deletions

3
assets/icons/AR.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.192 16.129L3.308 13.292C3.052 13.142 2.85333 12.944 2.712 12.698C2.57067 12.452 2.5 12.184 2.5 11.894V6.221C2.5 5.93167 2.57067 5.664 2.712 5.418C2.85333 5.172 3.052 4.97367 3.308 4.823L8.192 1.987C8.44867 1.84167 8.719 1.769 9.003 1.769C9.287 1.769 9.55533 1.84167 9.808 1.987L14.692 4.823C14.948 4.97367 15.1467 5.172 15.288 5.418C15.4293 5.664 15.5 5.93167 15.5 6.221V11.894C15.5 12.184 15.4293 12.452 15.288 12.698C15.1467 12.944 14.948 13.142 14.692 13.292L9.808 16.129C9.55067 16.2737 9.28033 16.346 8.997 16.346C8.71367 16.346 8.44533 16.2737 8.192 16.129ZM8.5 15.156V9.325L3.5 6.481V11.894C3.5 11.9967 3.52567 12.093 3.577 12.183C3.62833 12.273 3.705 12.3497 3.807 12.413L8.5 15.156ZM9.5 15.156L14.192 12.414C14.2947 12.3493 14.3717 12.2723 14.423 12.183C14.4743 12.093 14.5 11.9967 14.5 11.894V6.481L9.5 9.325V15.156ZM0.5 4.5C0.357333 4.5 0.238333 4.452 0.143 4.356C0.0476666 4.26 0 4.14133 0 4V1.616C0 1.17133 0.158333 0.791 0.475 0.475C0.791667 0.159 1.17167 0.000666667 1.615 0H4C4.142 0 4.26067 0.0480001 4.356 0.144C4.45133 0.24 4.49933 0.359 4.5 0.501C4.50067 0.643 4.45267 0.761667 4.356 0.857C4.25933 0.952333 4.14067 1 4 1H1.616C1.436 1 1.28833 1.05767 1.173 1.173C1.05767 1.28833 1 1.436 1 1.616V4C1 4.142 0.952 4.26067 0.856 4.356C0.76 4.45133 0.642 4.49933 0.5 4.5ZM1.615 18C1.171 18 0.791 17.8417 0.475 17.525C0.159 17.2083 0.000666667 16.8287 0 16.386V14C0 13.858 0.0480001 13.7393 0.144 13.644C0.24 13.5487 0.359 13.5007 0.501 13.5C0.643 13.4993 0.761667 13.5473 0.857 13.644C0.952333 13.7407 1 13.8593 1 14V16.385C1 16.5643 1.05767 16.7117 1.173 16.827C1.28833 16.9423 1.436 17 1.616 17H4C4.142 17 4.26067 17.048 4.356 17.144C4.45133 17.24 4.49933 17.359 4.5 17.501C4.50067 17.643 4.45267 17.7617 4.356 17.857C4.25933 17.9523 4.14067 18 4 18H1.615ZM16.385 18H14C13.858 18 13.7393 17.952 13.644 17.856C13.5487 17.76 13.5007 17.641 13.5 17.499C13.4993 17.357 13.5473 17.2383 13.644 17.143C13.7407 17.0477 13.8593 17 14 17H16.385C16.5643 17 16.7117 16.9423 16.827 16.827C16.9423 16.7117 17 16.5643 17 16.385V14C17 13.858 17.048 13.7393 17.144 13.644C17.24 13.5487 17.359 13.5007 17.501 13.5C17.643 13.4993 17.7617 13.5473 17.857 13.644C17.9523 13.7407 18 13.8593 18 14V16.385C18 16.829 17.8417 17.209 17.525 17.525C17.2083 17.841 16.8283 17.9993 16.385 18ZM17 4V1.616C17 1.436 16.9423 1.28833 16.827 1.173C16.7117 1.05767 16.5643 1 16.385 1H14C13.858 1 13.7393 0.952 13.644 0.856C13.5487 0.76 13.5007 0.641 13.5 0.499C13.4993 0.357 13.5473 0.238333 13.644 0.143C13.7407 0.0476666 13.8593 0 14 0H16.385C16.829 0 17.209 0.158333 17.525 0.475C17.841 0.791667 17.9993 1.17167 18 1.615V4C18 4.142 17.952 4.26067 17.856 4.356C17.76 4.45133 17.641 4.49933 17.499 4.5C17.357 4.50067 17.2383 4.45267 17.143 4.356C17.0477 4.25933 17 4.14067 17 4ZM9 8.466L13.989 5.583L9.308 2.885C9.20533 2.821 9.10267 2.789 9 2.789C8.89733 2.789 8.79467 2.821 8.692 2.885L4.012 5.583L9 8.466Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.17993 18.04V20.48C9.17993 22.16 10.0899 22.5 11.1999 21.24L18.7699 12.64C19.6999 11.59 19.3099 10.72 17.8999 10.72H16.9699" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.82 8.8399V3.5199C14.82 1.8399 13.91 1.4999 12.8 2.7599L5.23 11.3599C4.3 12.4099 4.69 13.2799 6.1 13.2799H9.19V14.4599" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 2L2 22" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

3
assets/icons/flash.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.0901 13.2799H9.1801V20.4799C9.1801 22.1599 10.0901 22.4999 11.2001 21.2399L18.7701 12.6399C19.7001 11.5899 19.3101 10.7199 17.9001 10.7199H14.8101V3.5199C14.8101 1.8399 13.9001 1.4999 12.7901 2.7599L5.2201 11.3599C4.3001 12.4199 4.6901 13.2799 6.0901 13.2799Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 484 B

5
assets/icons/gallery.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 10C10.1046 10 11 9.10457 11 8C11 6.89543 10.1046 6 9 6C7.89543 6 7 6.89543 7 8C7 9.10457 7.89543 10 9 10Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.66992 18.9501L7.59992 15.6401C8.38992 15.1101 9.52992 15.1701 10.2399 15.7801L10.5699 16.0701C11.3499 16.7401 12.6099 16.7401 13.3899 16.0701L17.5499 12.5001C18.3299 11.8301 19.5899 11.8301 20.3699 12.5001L21.9999 13.9001" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 799 B

View File

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 4H16.5C17.12 4 17.67 4.02 18.16 4.09C20.79 4.38 21.5 5.62 21.5 9V15C21.5 18.38 20.79 19.62 18.16 19.91C17.67 19.98 17.12 20 16.5 20H7.5C6.88 20 6.33 19.98 5.84 19.91C3.21 19.62 2.5 18.38 2.5 15V9C2.5 5.62 3.21 4.38 5.84 4.09C6.33 4.02 6.88 4 7.5 4Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 10H17" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 15.5H7.02H17" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.0946 10H10.1036" stroke="#292D32" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.0946 10H7.10359" stroke="#292D32" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 895 B

View File

@ -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<SvgGenImage> 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,

View File

@ -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),
),

View File

@ -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));

View File

@ -50,14 +50,22 @@ class _HintCameraWidgetState extends State<HintCameraWidget>
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<double> _rotationAnimation;
late Animation<double> _scanAnimation;
late Animation<double> _entranceOpacityAnimation;
late Animation<double> _entranceScaleAnimation;
late Animation<Offset> _entranceSlideAnimation;
late AnimationController _transformController;
late Animation<double> _transformAnimation;
final double _cameraHorizontalFov = 60.0;
@ -112,6 +120,11 @@ class _HintCameraWidgetState extends State<HintCameraWidget>
duration: const Duration(milliseconds: 3500),
vsync: this,
)..repeat();
_entranceController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_rotationAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _rotationController, curve: Curves.linear),
@ -119,6 +132,44 @@ class _HintCameraWidgetState extends State<HintCameraWidget>
_scanAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _scanController, curve: Curves.easeInOutSine),
);
_entranceOpacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _entranceController,
curve: const Interval(0.0, 0.8, curve: Curves.easeOut),
),
);
_entranceScaleAnimation = Tween<double>(begin: 0.3, end: 1.0).animate(
CurvedAnimation(
parent: _entranceController,
curve: const Interval(0.0, 0.8, curve: Curves.elasticOut),
),
);
_entranceSlideAnimation = Tween<Offset>(
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<double>(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<HintCameraWidget>
_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<HintCameraWidget>
_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<HintCameraWidget>
@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<HintCameraWidget>
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<HintCameraWidget>
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<Offset>(
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<HintCameraWidget>
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<HintCameraWidget>
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<HintCameraWidget>
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<HintCameraWidget>
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<HintCameraWidget>
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<HintCameraWidget>
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<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _iconScaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
_scaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.8, curve: Curves.easeOut),
));
_iconScaleAnimation = Tween<double>(
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,
),
),
),
),
),
],
),
),
),
);
}
}

View File

@ -731,7 +731,7 @@ class _HuntCardWidgetState extends State<HuntCardWidget>
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.icons.infoCircle.path,
Assets.icons.ar.path,
color: AppColors.isDarkMode
? Colors.yellow.shade600
: const Color(0xFFE65100),

View File

@ -26,7 +26,7 @@ class _ProfileState extends State<Profile> with TickerProviderStateMixin {
late AnimationController _animationController;
late List<Animation<double>> _animations;
String _currentLanguage = '🇺🇲 English';
String _currentLanguage = '🇺🇲 English';
String _currentFlag = 'assets/icons/usa circle.svg';
final GlobalKey _languageTileKey = GlobalKey();

View File

@ -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<QRScannerPage> {
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<QRScannerPage> {
),
),
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<QRScannerPage> {
),
),
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<QRScannerPage> {
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<QRScannerPage> {
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<QRScannerPage> {
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'),
),
],
);
},
);

View File

@ -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<CoolSplashScreen> createState() => _CoolSplashScreenState();
State<SplashScreen> createState() => _SplashScreenState();
}
class _CoolSplashScreenState extends State<CoolSplashScreen>
class _SplashScreenState extends State<SplashScreen>
with TickerProviderStateMixin {
late AnimationController _mainController;
late Animation<double> _backgroundAnimation;

View File

@ -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<LanguageSelectionOverlay>
late String _selectedLanguage;
final List<Map<String, String>> 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<LanguageSelectionOverlay>
_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<LanguageSelectionOverlay>
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<LanguageSelectionOverlay>
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<LanguageSelectionOverlay>
),
),
// 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),
),
],
),