From df2b51aee9c537ea0992d215ba9f97a49d763cd9 Mon Sep 17 00:00:00 2001 From: mohamadmahdi jebeli Date: Tue, 19 Aug 2025 16:17:23 +0330 Subject: [PATCH] added planner page --- android/app/build.gradle.kts | 2 +- android/app/src/main/AndroidManifest.xml | 2 + assets/icons/MDS-Public-TW-Button (1).svg | 8 + assets/icons/arrow-up.svg | 3 + assets/icons/camera2.svg | 5 + assets/icons/flat-color-icons_idea.svg | 8 + assets/icons/flat-color-icons_planner.svg | 8 + assets/icons/gallery-add.svg | 7 + assets/icons/link-2.svg | 4 + assets/icons/microphone-2.svg | 7 + .../pages/otp_verification_page.dart | 8 +- lib/gen/assets.gen.dart | 37 + lib/res/colors.dart | 1 + lib/screens/auth/otpVerifcation.dart | 49 +- lib/screens/mains/discover/discover.dart | 72 +- .../mains/nearby/mainNearby/listScreen.dart | 438 +++--- lib/screens/mains/nearby/mainNearby/map.dart | 2 +- .../mains/nearby/mainNearby/nearby.dart | 64 +- lib/screens/mains/planner/planner.dart | 1277 ++++++++++++++++- lib/screens/product/map_selection_screen.dart | 2 +- lib/screens/product/productdetail.dart | 4 +- lib/screens/product/shop.dart | 4 +- lib/widgets/remainingTime.dart | 16 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 112 ++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 30 files changed, 1866 insertions(+), 290 deletions(-) create mode 100644 assets/icons/MDS-Public-TW-Button (1).svg create mode 100644 assets/icons/arrow-up.svg create mode 100644 assets/icons/camera2.svg create mode 100644 assets/icons/flat-color-icons_idea.svg create mode 100644 assets/icons/flat-color-icons_planner.svg create mode 100644 assets/icons/gallery-add.svg create mode 100644 assets/icons/link-2.svg create mode 100644 assets/icons/microphone-2.svg diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index d6fd36a..5806f8f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "com.example.lba" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 24 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 91953d5..026f001 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ + + + + + + + + + diff --git a/assets/icons/arrow-up.svg b/assets/icons/arrow-up.svg new file mode 100644 index 0000000..2109279 --- /dev/null +++ b/assets/icons/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/camera2.svg b/assets/icons/camera2.svg new file mode 100644 index 0000000..49ba36c --- /dev/null +++ b/assets/icons/camera2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/flat-color-icons_idea.svg b/assets/icons/flat-color-icons_idea.svg new file mode 100644 index 0000000..5584fcb --- /dev/null +++ b/assets/icons/flat-color-icons_idea.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/flat-color-icons_planner.svg b/assets/icons/flat-color-icons_planner.svg new file mode 100644 index 0000000..cb6fa3b --- /dev/null +++ b/assets/icons/flat-color-icons_planner.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/gallery-add.svg b/assets/icons/gallery-add.svg new file mode 100644 index 0000000..83219b2 --- /dev/null +++ b/assets/icons/gallery-add.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/link-2.svg b/assets/icons/link-2.svg new file mode 100644 index 0000000..9b88a9a --- /dev/null +++ b/assets/icons/link-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/microphone-2.svg b/assets/icons/microphone-2.svg new file mode 100644 index 0000000..41030a6 --- /dev/null +++ b/assets/icons/microphone-2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/features/auth/presentation/pages/otp_verification_page.dart b/lib/features/auth/presentation/pages/otp_verification_page.dart index 27b20e4..f3f3eb4 100644 --- a/lib/features/auth/presentation/pages/otp_verification_page.dart +++ b/lib/features/auth/presentation/pages/otp_verification_page.dart @@ -35,7 +35,7 @@ class _OTPVerificationPageState extends State { } void _initializeTimer() { - _otpTimer.initializeFromExpiry(expiryTimeString: widget.timeDue); + _otpTimer.initializeFromExpiry(expiryTime: widget.timeDue); } void _resendOTP() { @@ -80,8 +80,8 @@ class _OTPVerificationPageState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: - const BorderSide(color: Color.fromARGB(255, 14, 63, 102)), + borderSide: const BorderSide( + color: Color.fromARGB(255, 14, 63, 102)), ), ), onChanged: (value) { @@ -102,7 +102,7 @@ class _OTPVerificationPageState extends State { return BlocListener( listener: (context, state) { if (state is AuthSuccess) { - _otpTimer.initializeFromExpiry(expiryTimeString: state.timeDue); + _otpTimer.initializeFromExpiry(expiryTime: state.timeDue); } }, child: Scaffold( diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index be8707e..57ee77c 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -24,6 +24,10 @@ class $AssetsIconsGen { /// File path: assets/icons/Line 4.svg SvgGenImage get line4 => const SvgGenImage('assets/icons/Line 4.svg'); + /// File path: assets/icons/MDS-Public-TW-Button (1).svg + SvgGenImage get mDSPublicTWButton1 => + const SvgGenImage('assets/icons/MDS-Public-TW-Button (1).svg'); + /// File path: assets/icons/MDS-Public-TW-Button.svg SvgGenImage get mDSPublicTWButton => const SvgGenImage('assets/icons/MDS-Public-TW-Button.svg'); @@ -66,6 +70,9 @@ class $AssetsIconsGen { SvgGenImage get arrowRight => const SvgGenImage('assets/icons/arrow-right.svg'); + /// File path: assets/icons/arrow-up.svg + SvgGenImage get arrowUp => const SvgGenImage('assets/icons/arrow-up.svg'); + /// File path: assets/icons/back.svg SvgGenImage get back => const SvgGenImage('assets/icons/back.svg'); @@ -77,6 +84,9 @@ class $AssetsIconsGen { SvgGenImage get calendarTick => const SvgGenImage('assets/icons/calendar-tick.svg'); + /// File path: assets/icons/camera2.svg + SvgGenImage get camera2 => const SvgGenImage('assets/icons/camera2.svg'); + /// File path: assets/icons/card-add.svg SvgGenImage get cardAdd => const SvgGenImage('assets/icons/card-add.svg'); @@ -120,10 +130,22 @@ class $AssetsIconsGen { /// File path: assets/icons/favorite.svg SvgGenImage get favorite => const SvgGenImage('assets/icons/favorite.svg'); + /// File path: assets/icons/flat-color-icons_idea.svg + SvgGenImage get flatColorIconsIdea => + const SvgGenImage('assets/icons/flat-color-icons_idea.svg'); + + /// File path: assets/icons/flat-color-icons_planner.svg + SvgGenImage get flatColorIconsPlanner => + const SvgGenImage('assets/icons/flat-color-icons_planner.svg'); + /// File path: assets/icons/fluent-color_location-ripple-16.svg SvgGenImage get fluentColorLocationRipple16 => const SvgGenImage('assets/icons/fluent-color_location-ripple-16.svg'); + /// File path: assets/icons/gallery-add.svg + SvgGenImage get galleryAdd => + const SvgGenImage('assets/icons/gallery-add.svg'); + /// File path: assets/icons/game 2.svg SvgGenImage get game2 => const SvgGenImage('assets/icons/game 2.svg'); @@ -164,6 +186,9 @@ class $AssetsIconsGen { /// File path: assets/icons/like.svg SvgGenImage get like => const SvgGenImage('assets/icons/like.svg'); + /// File path: assets/icons/link-2.svg + SvgGenImage get link2 => const SvgGenImage('assets/icons/link-2.svg'); + /// File path: assets/icons/list.svg SvgGenImage get list => const SvgGenImage('assets/icons/list.svg'); @@ -205,6 +230,10 @@ class $AssetsIconsGen { SvgGenImage get materialSymbolsLocationWork => const SvgGenImage('assets/icons/material-symbols_location-work.svg'); + /// File path: assets/icons/microphone-2.svg + SvgGenImage get microphone2 => + const SvgGenImage('assets/icons/microphone-2.svg'); + /// File path: assets/icons/nearby.svg SvgGenImage get nearby => const SvgGenImage('assets/icons/nearby.svg'); @@ -318,6 +347,7 @@ class $AssetsIconsGen { lBALogo, line1, line4, + mDSPublicTWButton1, mDSPublicTWButton, mDSPublicTWTag, nextButton, @@ -330,9 +360,11 @@ class $AssetsIconsGen { arrowDown, arrowLeft, arrowRight, + arrowUp, back, calendarTick2, calendarTick, + camera2, cardAdd, cardPos, category2, @@ -346,7 +378,10 @@ class $AssetsIconsGen { down, elementEqual, favorite, + flatColorIconsIdea, + flatColorIconsPlanner, fluentColorLocationRipple16, + galleryAdd, game2, gameIconsWinterGloves, game, @@ -358,6 +393,7 @@ class $AssetsIconsGen { infoPic, ionFastFoodOutline, like, + link2, list, location, mapSelected, @@ -369,6 +405,7 @@ class $AssetsIconsGen { materialSymbolsLocationOn, materialSymbolsLocationOnn, materialSymbolsLocationWork, + microphone2, nearby, nearby2, next, diff --git a/lib/res/colors.dart b/lib/res/colors.dart index c6c8319..5e5f869 100644 --- a/lib/res/colors.dart +++ b/lib/res/colors.dart @@ -24,4 +24,5 @@ class LightAppColors{ static const divider = Color.fromARGB(255, 189, 189, 188); static const textPrice = Color.fromARGB(255, 85, 84, 81); static const deliverySelectedButton = Color.fromARGB(255, 237, 247, 238); + static const loadingBorder = Color.fromARGB(255, 239, 239, 239); } \ No newline at end of file diff --git a/lib/screens/auth/otpVerifcation.dart b/lib/screens/auth/otpVerifcation.dart index b8f8156..c21cb16 100644 --- a/lib/screens/auth/otpVerifcation.dart +++ b/lib/screens/auth/otpVerifcation.dart @@ -17,7 +17,8 @@ class OTPVerification extends StatefulWidget { class _OTPVerificationState extends State { final List _focusNodes = List.generate(5, (_) => FocusNode()); - final List _controllers = List.generate(5, (_) => TextEditingController()); + final List _controllers = + List.generate(5, (_) => TextEditingController()); late RemainingTime _otpTimer; @override @@ -29,7 +30,7 @@ class _OTPVerificationState extends State { void _initializeTimer() { final authCubit = context.read(); - _otpTimer.initializeFromExpiry(expiryTimeString: authCubit.timeDue); + _otpTimer.initializeFromExpiry(expiryTime: authCubit.timeDue); } void _resendOTP() { @@ -74,7 +75,8 @@ class _OTPVerificationState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: const BorderSide(color: Color.fromARGB(255, 14, 63, 102)), + borderSide: const BorderSide( + color: Color.fromARGB(255, 14, 63, 102)), ), ), onChanged: (value) { @@ -109,21 +111,27 @@ class _OTPVerificationState extends State { ], ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + padding: + const EdgeInsets.symmetric(horizontal: 24, vertical: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Padding( padding: const EdgeInsets.only(top: 0), - child: SvgPicture.asset(Assets.images.logo.path, height: height / 5.2), + child: SvgPicture.asset(Assets.images.logo.path, + height: height / 5.2), ), ), SizedBox(height: height / 20), - const Text("OTP Verification", style: TextStyle(fontSize: 33, fontWeight: FontWeight.bold)), + const Text("OTP Verification", + style: TextStyle( + fontSize: 33, fontWeight: FontWeight.bold)), const SizedBox(height: 8), - Text("Enter the verification code we just sent to your device.", - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + const Text( + "Enter the verification code we just sent to your device.", + style: + TextStyle(fontSize: 17, fontWeight: FontWeight.w500), ), const SizedBox(height: 25), _buildOTPFields(), @@ -134,10 +142,14 @@ class _OTPVerificationState extends State { child: Button( text: "Verify", onPressed: () { - final otpCode = _controllers.map((c) => c.text).join(); + final otpCode = + _controllers.map((c) => c.text).join(); if (otpCode.length == 5) { context.read().verifyOTP(otpCode); - Navigator.push(context, MaterialPageRoute(builder: (context) => UserInfo())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const UserInfo())); } }, color: const Color.fromARGB(255, 30, 137, 221), @@ -150,22 +162,27 @@ class _OTPVerificationState extends State { Row( children: [ const Expanded( - child: Divider(thickness: 1, color: Colors.grey), + child: + Divider(thickness: 1, color: Colors.grey), ), ValueListenableBuilder( valueListenable: _otpTimer.remainingSeconds, builder: (context, seconds, _) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric( + horizontal: 8.0), child: Text( "Resend OTP in ${_otpTimer.formatTime()}", - style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 18), + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 18), ), ); }, ), const Expanded( - child: Divider(thickness: 1, color: Colors.grey), + child: + Divider(thickness: 1, color: Colors.grey), ), ], ), @@ -179,7 +196,9 @@ class _OTPVerificationState extends State { "Resend OTP", style: TextStyle( fontSize: 16, - color: canResend ? const Color.fromARGB(255, 0, 0, 0) : Colors.grey, + color: canResend + ? const Color.fromARGB(255, 0, 0, 0) + : Colors.grey, ), ), ); diff --git a/lib/screens/mains/discover/discover.dart b/lib/screens/mains/discover/discover.dart index d8d45a4..3a719a0 100644 --- a/lib/screens/mains/discover/discover.dart +++ b/lib/screens/mains/discover/discover.dart @@ -98,7 +98,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { showGeneralDialog( context: context, barrierDismissible: true, - barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + barrierLabel: + MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.black.withOpacity(0.1), transitionDuration: const Duration(milliseconds: 400), pageBuilder: (context, animation1, animation2) { @@ -108,7 +109,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { alignment: Alignment.topRight, child: Container( width: 250, - margin: const EdgeInsets.only(top: kToolbarHeight + 65, right: 16), + margin: + const EdgeInsets.only(top: kToolbarHeight + 65, right: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), @@ -131,7 +133,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { onTap: () { setDialogState(() { setState(() { - _filters[filterName] = !_filters[filterName]!; + _filters[filterName] = + !_filters[filterName]!; }); }); }, @@ -139,13 +142,15 @@ class _DiscoverState extends State with TickerProviderStateMixin { padding: const EdgeInsets.symmetric( horizontal: 18.0, vertical: 12.0), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( filterName, - style: TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 16), ), - _buildCustomCheckbox(_filters[filterName]!), + _buildCustomCheckbox( + _filters[filterName]!), ], ), ), @@ -167,7 +172,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { ); return ScaleTransition( - scale: Tween(begin: 0.8, end: 1.0).animate(curvedAnimation), + scale: + Tween(begin: 0.8, end: 1.0).animate(curvedAnimation), alignment: Alignment.topRight, child: FadeTransition( opacity: curvedAnimation, @@ -226,7 +232,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { const SizedBox(height: 12), _buildAnimatedSection(_buildFlashSaleSection(), 6), const SizedBox(height: 24), - _buildAnimatedSection(_buildSectionTitle("Special Discount"), 7), + _buildAnimatedSection( + _buildSectionTitle("Special Discount"), 7), const SizedBox(height: 12), _buildAnimatedSection(_buildSpecialDiscountSection(), 1), const SizedBox(height: 24), @@ -237,7 +244,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { const SizedBox(height: 12), _buildAnimatedSection(_buildSeasonalDiscountSection(), 3), const SizedBox(height: 24), - _buildAnimatedSection(_buildSectionTitle("Occasion Specials"), 4), + _buildAnimatedSection( + _buildSectionTitle("Occasion Specials"), 4), const SizedBox(height: 12), _buildAnimatedSection(_buildCraftingSomethingSection(), 5), const SizedBox(height: 24), @@ -291,7 +299,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { borderRadius: BorderRadius.circular(12), ), child: IconButton( - icon: SvgPicture.asset(Assets.icons.sort.path, color: Colors.white), + icon: + SvgPicture.asset(Assets.icons.sort.path, color: Colors.white), onPressed: _showFilterMenu, ), ); @@ -307,7 +316,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { color: isChecked ? LightAppColors.primary : Colors.transparent, border: isChecked ? null - : Border.all(color: Color.fromARGB(255, 89, 93, 98), width: 2), + : Border.all( + color: const Color.fromARGB(255, 89, 93, 98), width: 2), borderRadius: BorderRadius.circular(6), ), child: AnimatedSwitcher( @@ -359,7 +369,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { color: backgroundColor, borderRadius: BorderRadius.circular(12), ), - child: SvgPicture.asset(categoryIcons[index], color: iconColor), + child: + SvgPicture.asset(categoryIcons[index], color: iconColor), ); }, separatorBuilder: (context, index) => const SizedBox(width: 12), @@ -516,7 +527,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { children: [ Text( title, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.normal), + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.normal), ), const SizedBox(width: 8), const Expanded( @@ -576,7 +588,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { SizedBox( width: 250, child: FlashSaleCard( - imagePath: Assets.images.wp1929534FastFoodWallpapers1.path, + imagePath: + Assets.images.wp1929534FastFoodWallpapers1.path, title: "Tulip Luncheon Meat", location: "Fresno (2km away)", originalPrice: "370", @@ -601,7 +614,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), image: DecorationImage( - image: AssetImage(Assets.images.wp1929534FastFoodWallpapers1.path), + image: AssetImage( + Assets.images.wp1929534FastFoodWallpapers1.path), fit: BoxFit.cover, ), ), @@ -746,7 +760,8 @@ class _DiscoverState extends State with TickerProviderStateMixin { category: "Fast Food", discount: "Up to 25% Off", rating: 4.2, - imagePath: Assets.images.wp1929534FastFoodWallpapers1.path, + imagePath: + Assets.images.wp1929534FastFoodWallpapers1.path, ), const SizedBox(width: 16), FirstPurchaseCard( @@ -797,7 +812,7 @@ class FlashSaleCard extends StatelessWidget { @override Widget build(BuildContext context) { final timer = RemainingTime() - ..initializeFromExpiry(expiryTimeString: expiryTimeString); + ..initializeFromExpiry(expiryTime: expiryTimeString); return Container( decoration: BoxDecoration( color: LightAppColors.cardBackground, @@ -902,7 +917,8 @@ class FlashSaleCard extends StatelessWidget { Text( location, style: const TextStyle( - color: LightAppColors.offerCardDetail, fontSize: 12), + color: LightAppColors.offerCardDetail, + fontSize: 12), ), ], ), @@ -960,7 +976,9 @@ class FlashSaleCard extends StatelessWidget { Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), - border: Border.all(color: const Color.fromARGB(255, 76, 175, 80), width: 1.0), + border: Border.all( + color: const Color.fromARGB(255, 76, 175, 80), + width: 1.0), color: Colors.transparent, ), child: Padding( @@ -969,7 +987,8 @@ class FlashSaleCard extends StatelessWidget { children: [ SvgPicture.asset( Assets.icons.cardPos.path, - color: const Color.fromARGB(255, 95, 95, 95), + color: + const Color.fromARGB(255, 95, 95, 95), height: 17, ), const SizedBox(width: 4), @@ -989,7 +1008,9 @@ class FlashSaleCard extends StatelessWidget { Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), - border: Border.all(color: const Color.fromARGB(255, 76, 175, 80), width: 1.0), + border: Border.all( + color: const Color.fromARGB(255, 76, 175, 80), + width: 1.0), color: Colors.transparent, ), child: Padding( @@ -998,7 +1019,8 @@ class FlashSaleCard extends StatelessWidget { children: [ SvgPicture.asset( Assets.icons.shoppingCart.path, - color: const Color.fromARGB(255, 95, 95, 95), + color: + const Color.fromARGB(255, 95, 95, 95), height: 17, ), const SizedBox(width: 4), @@ -1187,7 +1209,8 @@ class FirstPurchaseCard extends StatelessWidget { Text( category, style: const TextStyle( - color: LightAppColors.nearbyPopuphint, fontSize: 14), + color: LightAppColors.nearbyPopuphint, + fontSize: 14), ), const SizedBox(height: 5), Text( @@ -1199,7 +1222,8 @@ class FirstPurchaseCard extends StatelessWidget { const SizedBox(height: 5), Row( children: [ - SvgPicture.asset(Assets.icons.star.path, width: 16), + SvgPicture.asset(Assets.icons.star.path, + width: 16), const SizedBox(width: 4), Text( rating.toString(), diff --git a/lib/screens/mains/nearby/mainNearby/listScreen.dart b/lib/screens/mains/nearby/mainNearby/listScreen.dart index 49af902..0704db8 100644 --- a/lib/screens/mains/nearby/mainNearby/listScreen.dart +++ b/lib/screens/mains/nearby/mainNearby/listScreen.dart @@ -10,16 +10,14 @@ import 'package:lba/widgets/remainingTime.dart'; class ListScreen extends StatefulWidget { final bool delivery; final bool pickup; - int initialTimerStatus; - final String expiryTimeString; + final DateTime expiryTime; final VoidCallback? ontap; - ListScreen({ + const ListScreen({ super.key, required this.delivery, required this.pickup, - this.initialTimerStatus=3, - required this.expiryTimeString, + required this.expiryTime, this.ontap, }); @@ -35,16 +33,17 @@ class _ListScreenState extends State { void initState() { super.initState(); _timer = RemainingTime(); - _timerStatus = ValueNotifier(widget.initialTimerStatus); - _timer.initializeFromExpiry(expiryTimeString: widget.expiryTimeString); + _timerStatus = ValueNotifier(3); + _timer.initializeFromExpiry(expiryTime: widget.expiryTime); _timer.remainingSeconds.addListener(() { - if (_timer.remainingSeconds.value <= 0) { - _timerStatus.value = 3; - } else if (_timer.remainingSeconds.value < 18000) { - _timerStatus.value = 1; - } else if (_timer.remainingSeconds.value > 18000) { - _timerStatus.value = 2; + final secondsLeft = _timer.remainingSeconds.value; + if (secondsLeft <= 0) { + _timerStatus.value = 3; + } else if (secondsLeft < 18000) { + _timerStatus.value = 1; + } else { + _timerStatus.value = 2; } }); } @@ -60,213 +59,228 @@ class _ListScreenState extends State { Widget build(BuildContext context) { final width = context.screenWidth; final height = context.screenHeight; - return - GestureDetector( - onTap: widget.ontap, - child: Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Stack( - children: [ - Container( - margin: const EdgeInsets.only(top: 60), - padding: const EdgeInsets.only(top: 80), - decoration: BoxDecoration( - color: const Color.fromARGB(255, 242, 242, 241), - borderRadius: BorderRadius.circular(10), - ), - child: Padding( - padding: EdgeInsets.fromLTRB(12, 0, width/50, 15), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - children: [ - SvgPicture.asset(Assets.icons.phCheese.path), - const SizedBox(width: 5), - const Text( - "Amul Cheese Slices", - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 10), - Row( - children: [ - SvgPicture.asset( - Assets.icons.location.path, - color: const Color.fromARGB(255, 157, 157, 155), - width: 14, - ), - const SizedBox(width: 5), - const Text("Sharjah (750m away)",style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500 - ),), - ], - ), - const SizedBox(height: 10), - Row( - children: [ - SvgPicture.asset( - Assets.icons.coin.path, - width: 14, - ), - const SizedBox(width: 5), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Text( - "18.15 AED", - style: TextStyle( - color: Color.fromARGB(255, 157, 157, 155), - fontSize: 10, - decoration: TextDecoration.lineThrough, - ), - ), - SizedBox(height: 0), - Row( - children: [ - Text("15.84 AED",style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500 - ),), - SizedBox(width: 4), - Text( - "(13% off)", - style: TextStyle( - color: Color.fromARGB(255, 76, 175, 80), - fontSize: 10, - fontWeight: FontWeight.w500 - ), - ), - ], - ), - ], - ), - ], - ), - ], - ), - ), - Padding( - padding: EdgeInsets.fromLTRB(0, 35, widget.delivery == false || widget.pickup == false ? width/5 : width/60, 0), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.icons.star.path, - width: 14, - ), - const SizedBox(width: 2), - const Text( - "4.8", - style: TextStyle( - color: Color.fromARGB(255, 112, 112, 110), - ), - ), - ], - ), - const SizedBox(height: 10), - Row( - children: [ - if (widget.delivery) - OrderType( - icon: Assets.icons.cardPos.path, - typename: "Delivery", - fill: false, - ), - if (widget.delivery) const SizedBox(width: 5), - if (widget.pickup) - OrderType( - icon: Assets.icons.shoppingCart.path, - typename: "Pickup", - fill: false, - ), - ], - ), - ], - ), - ), - ], - ), - ), + return GestureDetector( + onTap: widget.ontap, + child: Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Stack( + children: [ + Container( + margin: const EdgeInsets.only(top: 60), + padding: const EdgeInsets.only(top: 80), + decoration: BoxDecoration( + color: const Color.fromARGB(255, 242, 242, 241), + borderRadius: BorderRadius.circular(10), ), - ClipRRect( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - topLeft: Radius.circular(10), - ), - child: Image.asset( - Assets.images.media.path, - width: double.infinity, - height: 130, - fit: BoxFit.cover, - ), - ), - Positioned( - top: 10, - left: 10, - child: ValueListenableBuilder( - valueListenable: _timerStatus, - builder: (context, timerStatus, child) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6), - height: 20, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: timerStatus == 1 - ? const Color.fromARGB(255, 255, 193, 7) - : timerStatus == 2 - ? const Color.fromARGB(255, 76, 175, 80) - : timerStatus == 3 - ? const Color.fromARGB(255, 244, 67, 54) - : const Color.fromARGB(255, 244, 67, 54), - ), - child: Row( + child: Padding( + padding: EdgeInsets.fromLTRB(12, 0, width / 50, 15), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - SvgPicture.asset( - timerStatus == 1 - ? Assets.icons.timer.path - : timerStatus == 2 - ? Assets.icons.timerStart.path - : Assets.icons.timerPause.path, - width: 12, - height: 12, + Row( + children: [ + SvgPicture.asset(Assets.icons.phCheese.path), + const SizedBox(width: 5), + const Text( + "Amul Cheese Slices", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], ), - const SizedBox(width: 5), - ValueListenableBuilder( - valueListenable: _timer.remainingSeconds, - builder: (context, seconds, child) { - return Text( - timerStatus==3? "Unavailable":"${_timer.formatTime()} left", - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w400, - ), - ); - }, + const SizedBox(height: 10), + Row( + children: [ + SvgPicture.asset( + Assets.icons.location.path, + color: + const Color.fromARGB(255, 157, 157, 155), + width: 14, + ), + const SizedBox(width: 5), + const Text( + "Sharjah (750m away)", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + SvgPicture.asset( + Assets.icons.coin.path, + width: 14, + ), + const SizedBox(width: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "18.15 AED", + style: TextStyle( + color: Color.fromARGB( + 255, 157, 157, 155), + fontSize: 10, + decoration: + TextDecoration.lineThrough, + ), + ), + SizedBox(height: 0), + Row( + children: [ + Text( + "15.84 AED", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500), + ), + SizedBox(width: 4), + Text( + "(13% off)", + style: TextStyle( + color: Color.fromARGB( + 255, 76, 175, 80), + fontSize: 10, + fontWeight: FontWeight.w500), + ), + ], + ), + ], + ), + ], ), ], ), - ); - }, + ), + Padding( + padding: EdgeInsets.fromLTRB( + 0, + 35, + widget.delivery == false || widget.pickup == false + ? width / 5 + : width / 60, + 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.icons.star.path, + width: 14, + ), + const SizedBox(width: 2), + const Text( + "4.8", + style: TextStyle( + color: + Color.fromARGB(255, 112, 112, 110), + ), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + if (widget.delivery) + OrderType( + icon: Assets.icons.cardPos.path, + typename: "Delivery", + fill: false, + ), + if (widget.delivery) + const SizedBox(width: 5), + if (widget.pickup) + OrderType( + icon: Assets.icons.shoppingCart.path, + typename: "Pickup", + fill: false, + ), + ], + ), + ], + ), + ), + ], ), ), - ], - ), + ), + ClipRRect( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + topLeft: Radius.circular(10), + ), + child: Image.asset( + Assets.images.media.path, + width: double.infinity, + height: 130, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 10, + left: 10, + child: ValueListenableBuilder( + valueListenable: _timerStatus, + builder: (context, timerStatus, child) { + return Container( + padding: + const EdgeInsets.symmetric(horizontal: 6), + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: timerStatus == 1 + ? const Color.fromARGB(255, 255, 193, 7) + : timerStatus == 2 + ? const Color.fromARGB(255, 76, 175, 80) + : const Color.fromARGB(255, 244, 67, 54), + ), + child: Row( + children: [ + SvgPicture.asset( + timerStatus == 1 + ? Assets.icons.timer.path + : timerStatus == 2 + ? Assets.icons.timerStart.path + : Assets.icons.timerPause.path, + width: 12, + height: 12, + ), + const SizedBox(width: 5), + ValueListenableBuilder( + valueListenable: _timer.remainingSeconds, + builder: (context, seconds, child) { + return Text( + timerStatus == 3 + ? "Unavailable" + : "${_timer.formatTime()} left", + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ); + }, + ), + ], + ), + ); + }, + ), + ), + ], ), - ); + ), + ); } } \ No newline at end of file diff --git a/lib/screens/mains/nearby/mainNearby/map.dart b/lib/screens/mains/nearby/mainNearby/map.dart index 20b48c9..d643e48 100644 --- a/lib/screens/mains/nearby/mainNearby/map.dart +++ b/lib/screens/mains/nearby/mainNearby/map.dart @@ -370,7 +370,7 @@ class _CustomMapState extends State TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'com.example.app', + userAgentPackageName: 'com.example.lba', ), MarkerLayer(markers: _markers), ], diff --git a/lib/screens/mains/nearby/mainNearby/nearby.dart b/lib/screens/mains/nearby/mainNearby/nearby.dart index f047164..21c83fd 100644 --- a/lib/screens/mains/nearby/mainNearby/nearby.dart +++ b/lib/screens/mains/nearby/mainNearby/nearby.dart @@ -48,7 +48,7 @@ class _NearbyState extends State with TickerProviderStateMixin { _listAnimationController.dispose(); super.dispose(); } - + void _onToggle(int index) { setState(() { selectedIndex = index; @@ -92,7 +92,8 @@ class _NearbyState extends State with TickerProviderStateMixin { ), ), filled: true, - fillColor: const Color.fromARGB(255, 248, 248, 248), + fillColor: + const Color.fromARGB(255, 248, 248, 248), contentPadding: const EdgeInsets.symmetric( horizontal: 16.0, ), @@ -149,11 +150,14 @@ class _NearbyState extends State with TickerProviderStateMixin { Expanded( child: AnimatedSwitcher( duration: const Duration(milliseconds: 500), - transitionBuilder: (Widget child, Animation animation) { + transitionBuilder: + (Widget child, Animation animation) { final slideAnimation = Tween( - begin: Offset(child.key == const ValueKey('list') ? -1.0 : 1.0, 0.0), + begin: Offset( + child.key == const ValueKey('list') ? -1.0 : 1.0, 0.0), end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: Curves.easeInOutCubic)); + ).animate(CurvedAnimation( + parent: animation, curve: Curves.easeInOutCubic)); return ClipRect( child: SlideTransition( @@ -231,10 +235,12 @@ class _NearbyState extends State with TickerProviderStateMixin { borderRadius: BorderRadius.only( topLeft: const Radius.circular(12), bottomLeft: const Radius.circular(12), - topRight: - selectedIndex == 1 ? const Radius.circular(12) : Radius.zero, - bottomRight: - selectedIndex == 1 ? const Radius.circular(12) : Radius.zero, + topRight: selectedIndex == 1 + ? const Radius.circular(12) + : Radius.zero, + bottomRight: selectedIndex == 1 + ? const Radius.circular(12) + : Radius.zero, ), ), ), @@ -254,10 +260,12 @@ class _NearbyState extends State with TickerProviderStateMixin { borderRadius: BorderRadius.only( topRight: const Radius.circular(12), bottomRight: const Radius.circular(12), - bottomLeft: - selectedIndex == 0 ? const Radius.circular(12) : Radius.zero, - topLeft: - selectedIndex == 0 ? const Radius.circular(12) : Radius.zero, + bottomLeft: selectedIndex == 0 + ? const Radius.circular(12) + : Radius.zero, + topLeft: selectedIndex == 0 + ? const Radius.circular(12) + : Radius.zero, ), ), ), @@ -268,26 +276,29 @@ class _NearbyState extends State with TickerProviderStateMixin { } Widget _buildListContent({Key? key}) { + final now = DateTime.now(); + final listItems = [ ListScreen( delivery: true, pickup: false, - expiryTimeString: "2025-05-19T13:30:49.623619357Z", + expiryTime: now.add(const Duration(hours: 10)), ontap: () { Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const Productdetail()), + MaterialPageRoute( + builder: (context) => const Productdetail()), ); }, ), ListScreen( delivery: false, pickup: true, - expiryTimeString: "2025-05-25T05:20:49.623619357Z", + expiryTime: now.add(const Duration(hours: 2)), ), ListScreen( delivery: true, pickup: true, - expiryTimeString: "2025-05-12T13:20:37.435249520Z", + expiryTime: now.subtract(const Duration(minutes: 10)), ), ]; @@ -308,7 +319,8 @@ class _NearbyState extends State with TickerProviderStateMixin { value: selectedOption, icon: Padding( padding: const EdgeInsets.only(left: 6), - child: SvgPicture.asset(Assets.icons.arrowDown.path), + child: + SvgPicture.asset(Assets.icons.arrowDown.path), ), elevation: 16, dropdownColor: Colors.white, @@ -322,8 +334,8 @@ class _NearbyState extends State with TickerProviderStateMixin { selectedOption = newValue!; }); }, - items: - options.map>((String value) { + items: options + .map>((String value) { return DropdownMenuItem( value: value, child: Text(value), @@ -338,7 +350,8 @@ class _NearbyState extends State with TickerProviderStateMixin { physics: const NeverScrollableScrollPhysics(), itemCount: listItems.length, itemBuilder: (context, index) { - final animation = Tween(begin: 0.0, end: 1.0).animate( + final animation = + Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _listAnimationController, curve: Interval( @@ -413,7 +426,8 @@ class _NearbyState extends State with TickerProviderStateMixin { child: Padding( padding: const EdgeInsets.all(1.0), child: Center( - child: SvgPicture.asset(icon, color: iconColor), + child: + SvgPicture.asset(icon, color: iconColor), ), ), ), @@ -421,7 +435,8 @@ class _NearbyState extends State with TickerProviderStateMixin { Text( text, style: TextStyle( - color: isSelected ? Colors.white : Colors.black87, + color: + isSelected ? Colors.white : Colors.black87, fontWeight: FontWeight.bold, ), ), @@ -433,7 +448,8 @@ class _NearbyState extends State with TickerProviderStateMixin { bottom: 0, left: 0, right: 0, - child: SvgPicture.asset(Assets.icons.shape.path, height: 4), + child: + SvgPicture.asset(Assets.icons.shape.path, height: 4), ), ], ), diff --git a/lib/screens/mains/planner/planner.dart b/lib/screens/mains/planner/planner.dart index c4de6ed..579289e 100644 --- a/lib/screens/mains/planner/planner.dart +++ b/lib/screens/mains/planner/planner.dart @@ -1,10 +1,1283 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/res/colors.dart'; +import 'package:flutter_sound/flutter_sound.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; -class Planner extends StatelessWidget { +class Planner extends StatefulWidget { const Planner({super.key}); + @override + State createState() => _PlannerState(); +} + +class _PlannerState extends State with TickerProviderStateMixin { + final ImagePicker _picker = ImagePicker(); + final GlobalKey _attachIconKey = GlobalKey(); + final TextEditingController _textController = TextEditingController(); + + bool _isLoading = false; + String _sentText = ""; + XFile? _sentImage; + String? _sentAudioPath; + + XFile? _attachedImage; + + FlutterSoundRecorder? _audioRecorder; + final AudioPlayer _audioPlayer = AudioPlayer(); + bool _isRecording = false; + bool _isPlaying = false; + String? _recordedAudioPath; + Duration _recordDuration = Duration.zero; + Duration _playDuration = Duration.zero; + Duration _totalDuration = Duration.zero; + Timer? _recordingTimer; + + late AnimationController _micPulseController; + late Animation _micPulseAnimation; + late AnimationController _pageEnterController; + late Animation _staggeredAnimation; + late Animation _textInputAnimation; + + final List suggestions = [ + 'running shoes', + 'hotel', + 'power tools', + 'food', + 'cafe', + 'fashion', + 'gift', + 'indoor plants', + ]; + + @override + void initState() { + super.initState(); + _initRecorder(); + + _micPulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1000), + )..repeat(reverse: true); + _micPulseAnimation = + Tween(begin: 1.0, end: 1.4).animate(_micPulseController); + + _pageEnterController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1200), + ); + + _staggeredAnimation = CurvedAnimation( + parent: _pageEnterController, + curve: Curves.easeOutCubic, + ); + + _textInputAnimation = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _pageEnterController, + curve: const Interval(0.4, 1.0, curve: Curves.easeOutCubic), + )); + + _pageEnterController.forward(); + } + + Future _initRecorder() async { + try { + _audioRecorder = FlutterSoundRecorder(); + await _audioRecorder!.openRecorder(); + } catch (e) { + debugPrint('Error initializing recorder: $e'); + } + } + + @override + void dispose() { + _textController.dispose(); + _audioRecorder?.closeRecorder(); + _audioPlayer.dispose(); + _recordingTimer?.cancel(); + _micPulseController.dispose(); + _pageEnterController.dispose(); + super.dispose(); + } + + void _showAttachmentOptions() { + final RenderBox renderBox = + _attachIconKey.currentContext!.findRenderObject() as RenderBox; + final position = renderBox.localToGlobal(Offset.zero); + + const double menuWidth = 220.0; + const double menuHeight = 120.0; + + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: 'Attachment Options', + barrierColor: Colors.black.withOpacity(0.1), + transitionDuration: const Duration(milliseconds: 200), + pageBuilder: (context, anim1, anim2) { + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Material( + color: Colors.transparent, + child: Stack( + children: [ + Positioned( + left: position.dx + 30, + top: position.dy - menuHeight + 5, + child: GestureDetector( + onTap: () {}, + child: PopupMenuWithoutTail( + child: SizedBox( + width: menuWidth, + height: menuHeight, + child: _buildAttachmentMenuContent(), + ), + ), + ), + ), + ], + ), + ), + ); + }, + transitionBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition( + opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut), + child: child, + ); + }, + ); + } + + Widget _buildAttachmentMenuContent() { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ListTile( + leading: SvgPicture.asset(Assets.icons.camera2.path, height: 20), + title: const Text('Snap a photo', style: TextStyle(fontSize: 16)), + onTap: () { + Navigator.of(context).pop(); + _pickImage(ImageSource.camera); + }, + dense: true, + ), + const Divider(thickness: 0.5, height: 1, indent: 55, endIndent: 55), + ListTile( + leading: SvgPicture.asset(Assets.icons.galleryAdd.path, height: 19), + title: const Text('Upload a photo', style: TextStyle(fontSize: 16)), + onTap: () { + Navigator.of(context).pop(); + _pickImage(ImageSource.gallery); + }, + dense: true, + ), + ], + ); + } + + Future _pickImage(ImageSource source) async { + try { + final XFile? pickedFile = await _picker.pickImage(source: source); + if (pickedFile != null) { + setState(() { + _attachedImage = pickedFile; + }); + } + } catch (e) { + debugPrint("Error picking image: $e"); + } + } + + void _removeAttachedImage() { + setState(() { + _attachedImage = null; + }); + } + + void _sendMessage() { + final text = _textController.text.trim(); + final imageFile = _attachedImage; + final audioPath = _recordedAudioPath; + + if (text.isNotEmpty || imageFile != null || audioPath != null) { + setState(() { + _isLoading = true; + _sentText = text; + _sentImage = imageFile; + _sentAudioPath = audioPath; + _textController.clear(); + _attachedImage = null; + _recordedAudioPath = null; + _recordDuration = Duration.zero; + }); + + Future.delayed(const Duration(seconds: 3), () { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + }); + } + } + + Future _startRecording() async { + try { + HapticFeedback.mediumImpact(); + + final permission = await Permission.microphone.request(); + + if (permission != PermissionStatus.granted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Microphone permission is required to record audio.')), + ); + return; + } + + if (_audioRecorder == null) { + await _initRecorder(); + } + + final Directory appDocumentsDir = + await getApplicationDocumentsDirectory(); + final String filePath = + '${appDocumentsDir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.aac'; + + await _audioRecorder!.startRecorder( + toFile: filePath, + codec: Codec.aacADTS, + ); + + setState(() { + _isRecording = true; + _recordDuration = Duration.zero; + }); + + _startRecordTimer(); + } catch (e) { + debugPrint('Error starting recording: $e'); + } + } + + Future _stopRecording() async { + try { + HapticFeedback.lightImpact(); + _recordingTimer?.cancel(); + final path = await _audioRecorder!.stopRecorder(); + + setState(() { + _isRecording = false; + _recordedAudioPath = path; + }); + } catch (e) { + debugPrint('Error stopping recording: $e'); + } + } + + void _startRecordTimer() { + _recordingTimer?.cancel(); + _recordingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_isRecording && mounted) { + setState(() { + _recordDuration += const Duration(seconds: 1); + }); + } else { + timer.cancel(); + } + }); + } + + Future _playRecording() async { + if (_recordedAudioPath == null) return; + + try { + if (_isPlaying) { + await _audioPlayer.pause(); + setState(() { + _isPlaying = false; + }); + } else { + await _audioPlayer.play(DeviceFileSource(_recordedAudioPath!)); + setState(() { + _isPlaying = true; + }); + + _audioPlayer.onDurationChanged.listen((duration) { + if (mounted) { + setState(() { + _totalDuration = duration; + }); + } + }); + + _audioPlayer.onPositionChanged.listen((position) { + if (mounted) { + setState(() { + _playDuration = position; + }); + } + }); + + _audioPlayer.onPlayerComplete.listen((_) { + if (mounted) { + setState(() { + _isPlaying = false; + _playDuration = Duration.zero; + }); + } + }); + } + } catch (e) { + debugPrint('Error playing recording: $e'); + } + } + + void _deleteRecording() { + setState(() { + _recordedAudioPath = null; + _recordDuration = Duration.zero; + _playDuration = Duration.zero; + _totalDuration = Duration.zero; + _isPlaying = false; + }); + } + + String _formatDuration(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, "0"); + String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); + String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); + return "$twoDigitMinutes:$twoDigitSeconds"; + } + + Widget _buildAnimatedSection(Widget child, + {required double begin, required double end}) { + return FadeTransition( + opacity: CurvedAnimation( + parent: _staggeredAnimation, + curve: Interval(begin, end), + ), + child: SlideTransition( + position: Tween( + begin: const Offset(0.0, 0.5), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _staggeredAnimation, + curve: Interval(begin, end), + )), + child: child, + ), + ); + } + @override Widget build(BuildContext context) { - return const Placeholder(); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 1, + shadowColor: Colors.grey.withOpacity(0.2), + surfaceTintColor: Colors.transparent, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.black54), + onPressed: () { + if (_isLoading) { + setState(() { + _isLoading = false; + }); + } else { + Navigator.pop(context); + } + }, + ), + title: const Text( + 'Smart Planner', + style: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.normal), + ), + actions: [ + IconButton( + icon: SvgPicture.asset(Assets.icons.mDSPublicTWButton1.path), + onPressed: () {}, + ), + ], + ), + body: Column( + children: [ + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: ScaleTransition( + scale: Tween(begin: 0.9, end: 1.0).animate(animation), + child: child, + ), + ); + }, + child: _isLoading ? _buildLoadingView() : _buildDefaultView(), + ), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + return SizeTransition( + sizeFactor: animation, + child: FadeTransition(opacity: animation, child: child), + ); + }, + child: _attachedImage != null + ? _buildAttachedImagePreview() + : const SizedBox.shrink(), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + return SizeTransition( + sizeFactor: animation, + child: FadeTransition(opacity: animation, child: child), + ); + }, + child: _recordedAudioPath != null + ? _buildRecordedAudioPreview() + : const SizedBox.shrink(), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) { + return SizeTransition( + sizeFactor: animation, + child: FadeTransition(opacity: animation, child: child), + ); + }, + child: _isRecording + ? _buildRecordingIndicator() + : const SizedBox.shrink(), + ), + SlideTransition( + position: _textInputAnimation, + child: _buildTextInputArea(), + ), + ], + ), + ); + } + + Widget _buildDefaultView() { + return ListView( + key: const ValueKey('default'), + padding: const EdgeInsets.all(16.0), + children: [ + _buildAnimatedSection( + _buildChatBubble( + icon: Assets.icons.flatColorIconsPlanner.path, + text: + "\"Hi there! Tell me what you need – I'm here to plan it for you!\" ", + backgroundColor: const Color(0xFFEFF7FE), + iconBackgroundColor: Colors.transparent, + ), + begin: 0.0, + end: 0.5, + ), + const SizedBox(height: 16), + _buildAnimatedSection( + _buildSuggestionArea(suggestions), + begin: 0.2, + end: 1.0, + ), + ], + ); + } + + Widget _buildLoadingView() { + return Padding( + key: const ValueKey('loading'), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Column( + children: [ + _buildUserRequestBubble(), + const Padding( + padding: EdgeInsets.symmetric(vertical: 15.0), + child: Divider(color: Color.fromARGB(255, 224, 224, 224)), + ), + const Text( + "\"Scanning the best deals just for you...\"", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: Color(0xFF37474F), + height: 1.4, + ), + ), + const SizedBox(height: 12), + const SlantedLinesAnimation(isLoading: true), + ], + ), + ); + } + + Widget _buildUserRequestBubble() { + bool hasImage = _sentImage != null; + bool hasText = _sentText.isNotEmpty; + bool hasAudio = _sentAudioPath != null; + + if (hasImage || hasAudio) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (hasImage) + Center( + child: Padding( + padding: const EdgeInsets.only(top: 0.0, right: 12.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: Image.file( + File(_sentImage!.path), + width: 75, + height: 75, + fit: BoxFit.cover, + ), + ), + ), + ), + if (hasAudio && !hasImage) + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFEFF7FE), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: LightAppColors.primary.withOpacity(0.3)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 32, + height: 32, + decoration: const BoxDecoration( + color: LightAppColors.primary, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.volume_up, + color: Colors.white, + size: 16, + ), + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Voice Message', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + Text( + _formatDuration(_recordDuration), + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + if (hasText) + Flexible( + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 6, horizontal: 16), + decoration: BoxDecoration( + color: const Color(0xFFEFF7FE), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey.shade300), + ), + child: Text( + "\"$_sentText\"", + style: const TextStyle( + fontSize: 15, + color: Color(0xFF37474F), + height: 1.4, + ), + ), + ), + ), + ], + ); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: IntrinsicHeight( + child: Container( + decoration: BoxDecoration( + color: const Color(0xFFEFF7FE), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 6, horizontal: 16), + child: Text( + "\"$_sentText\"", + style: const TextStyle( + fontSize: 15, + color: Color(0xFF37474F), + height: 1.4, + ), + ), + ), + ), + Container( + width: 10, + decoration: const BoxDecoration( + color: Color(0xFF189CFF), + borderRadius: BorderRadius.only( + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + } + + Widget _buildAttachedImagePreview() { + return Padding( + key: const ValueKey('image_preview'), + padding: const EdgeInsets.fromLTRB(35, 8, 16, 0), + child: Align( + alignment: Alignment.centerLeft, + child: SizedBox( + height: 120, + width: 120, + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(16.0), + child: Image.file( + File(_attachedImage!.path), + height: 120, + width: 120, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 6, + left: 6, + child: GestureDetector( + onTap: _removeAttachedImage, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.6), + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(4), + child: const Icon(Icons.close, + color: Colors.black, size: 18), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildRecordedAudioPreview() { + return Padding( + key: const ValueKey('audio_preview'), + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF8F9FA), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: LightAppColors.primary.withOpacity(0.3)), + ), + child: Row( + children: [ + GestureDetector( + onTap: _playRecording, + child: Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + color: LightAppColors.primary, + shape: BoxShape.circle, + ), + child: Icon( + _isPlaying ? Icons.pause : Icons.play_arrow, + color: Colors.white, + size: 20, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.mic, + size: 16, color: LightAppColors.primary), + const SizedBox(width: 4), + Text( + 'Voice Message', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: LinearProgressIndicator( + value: _totalDuration.inMilliseconds > 0 + ? _playDuration.inMilliseconds / + _totalDuration.inMilliseconds + : 0.0, + backgroundColor: Colors.grey[300], + valueColor: const AlwaysStoppedAnimation( + LightAppColors.primary), + minHeight: 2, + ), + ), + const SizedBox(width: 8), + Text( + _formatDuration(_playDuration), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + Text( + ' / ${_formatDuration(_recordDuration)}', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: _deleteRecording, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.delete_outline, + color: Colors.red, + size: 18, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildRecordingIndicator() { + return Padding( + key: const ValueKey('recording_indicator'), + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.red.withOpacity(0.3)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: 12, + height: 12, + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.red.withOpacity(0.5), + blurRadius: 8, + spreadRadius: 2, + ), + ], + ), + ), + const SizedBox(width: 12), + const Icon( + Icons.mic, + color: Colors.red, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Recording... ${_formatDuration(_recordDuration)}', + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + const SizedBox(width: 12), + const RecordingPulseAnimation(), + ], + ), + ), + ); + } + + Widget _buildChatBubble({ + required String icon, + required String text, + required Color backgroundColor, + required Color iconBackgroundColor, + }) { + return Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: iconBackgroundColor, + borderRadius: BorderRadius.circular(10), + ), + child: SvgPicture.asset(icon, height: 24), + ), + const SizedBox(width: 12), + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Text( + text, + style: const TextStyle( + fontSize: 15, + color: Color(0xFF37474F), + height: 1.4, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildSuggestionArea(List suggestions) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0), + decoration: BoxDecoration( + color: const Color(0xFFF7F7F7), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: SvgPicture.asset(Assets.icons.flatColorIconsIdea.path), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "\"Need help? Here are some ideas you can ask me about!\"", + style: TextStyle( + fontSize: 15, + color: Color(0xFF37474F), + height: 1.4, + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 10.0, + runSpacing: 10.0, + alignment: WrapAlignment.start, + children: suggestions.asMap().entries.map((entry) { + final index = entry.key; + final suggestion = entry.value; + return FadeTransition( + opacity: CurvedAnimation( + parent: _staggeredAnimation, + curve: Interval(0.4 + (index * 0.05), 1.0), + ), + child: SlideTransition( + position: Tween( + begin: const Offset(0.2, 0.0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _staggeredAnimation, + curve: Interval(0.4 + (index * 0.05), 1.0), + )), + child: _buildSuggestionChip(suggestion), + ), + ); + }).toList(), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildSuggestionChip(String text) { + return GestureDetector( + onTap: () { + _textController.text = text; + _textController.selection = TextSelection.fromPosition( + TextPosition(offset: _textController.text.length)); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all(color: LightAppColors.primary, width: 1), + ), + child: Text( + text, + style: const TextStyle( + color: Color(0xFF546E7A), + fontWeight: FontWeight.w500, + fontSize: 14), + ), + ), + ); + } + + Widget _buildTextInputArea() { + return SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: LightAppColors.nearbyPopup, + borderRadius: BorderRadius.circular(35), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + key: _attachIconKey, + onPressed: _showAttachmentOptions, + icon: SvgPicture.asset(Assets.icons.link2.path), + ), + Expanded( + child: TextField( + controller: _textController, + decoration: const InputDecoration( + hintText: "Type your request here and let's plan it!", + hintStyle: TextStyle(color: Colors.grey, fontSize: 14), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(vertical: 0, horizontal: 3), + ), + onSubmitted: (_) => _sendMessage(), + ), + ), + GestureDetector( + onLongPressStart: (_) => _startRecording(), + onLongPressEnd: (_) => _stopRecording(), + child: AnimatedBuilder( + animation: _micPulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _isRecording ? _micPulseAnimation.value : 1.0, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _isRecording + ? Colors.red + : Colors.transparent, + shape: BoxShape.circle, + boxShadow: _isRecording + ? [ + BoxShadow( + color: Colors.red.withOpacity(0.3), + blurRadius: 8, + spreadRadius: 2, + ), + ] + : null, + ), + child: SvgPicture.asset( + Assets.icons.microphone2.path, + colorFilter: _isRecording + ? const ColorFilter.mode( + Colors.white, BlendMode.srcIn) + : null, + ), + ), + ); + }), + ), + IconButton( + icon: SvgPicture.asset(Assets.icons.arrowUp.path), + onPressed: _sendMessage, + ), + ], + ), + ), + ), + ); + } +} + +class SlantedLinesAnimation extends StatefulWidget { + final bool isLoading; + const SlantedLinesAnimation({super.key, this.isLoading = false}); + + @override + _SlantedLinesAnimationState createState() => _SlantedLinesAnimationState(); +} + +class _SlantedLinesAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return ClipRRect( + borderRadius: BorderRadius.circular(4), + child: CustomPaint( + painter: _SlantedLinesPainter(_controller.value), + child: const SizedBox(height: 8, width: 180), + ), + ); + }, + ); + } +} + +class _SlantedLinesPainter extends CustomPainter { + final double progress; + _SlantedLinesPainter(this.progress); + + @override + void paint(Canvas canvas, Size size) { + const double stripeWidth = 5.0; + const double angle = -0.5; + + final Paint paint = Paint() + ..shader = LinearGradient( + colors: [const Color(0xFF189CFF), LightAppColors.loadingBorder], + stops: const [0.5, 0.5], + tileMode: TileMode.repeated, + transform: GradientRotation(angle), + ).createShader( + Rect.fromLTWH( + -progress * stripeWidth * 4, + 0, + stripeWidth * 4, + size.height, + ), + ); + + canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + } + + @override + bool shouldRepaint(covariant _SlantedLinesPainter oldDelegate) { + return oldDelegate.progress != progress; + } +} + +class PopupMenuWithoutTail extends StatelessWidget { + final Widget child; + const PopupMenuWithoutTail({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _MenuPainter( + color: Colors.white, + shadowColor: Colors.black.withOpacity(0.15), + ), + child: child, + ); + } +} + +class _MenuPainter extends CustomPainter { + final Color color; + final Color shadowColor; + final double radius = 20.0; + + _MenuPainter({required this.color, required this.shadowColor}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + final path = Path() + ..moveTo(radius, 0) + ..lineTo(size.width - radius, 0) + ..arcToPoint(Offset(size.width, radius), + radius: Radius.circular(radius)) + ..lineTo(size.width, size.height - radius) + ..arcToPoint(Offset(size.width - radius, size.height), + radius: Radius.circular(radius)) + ..lineTo(0, size.height) + ..lineTo(0, radius) + ..arcToPoint(Offset(radius, 0), radius: Radius.circular(radius)) + ..close(); + + canvas.drawShadow(path, shadowColor, 10.0, true); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class RecordingPulseAnimation extends StatefulWidget { + const RecordingPulseAnimation({super.key}); + + @override + _RecordingPulseAnimationState createState() => + _RecordingPulseAnimationState(); +} + +class _RecordingPulseAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 1), + vsync: this, + )..repeat(reverse: true); + + _animation = Tween( + begin: 0.5, + end: 1.0, + ).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + )); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Row( + children: List.generate(3, (index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 1), + child: AnimatedContainer( + duration: Duration(milliseconds: 200 + (index * 100)), + height: 16 * _animation.value, + width: 3, + decoration: BoxDecoration( + color: Colors.red.withOpacity(_animation.value), + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ); + }, + ); } } \ No newline at end of file diff --git a/lib/screens/product/map_selection_screen.dart b/lib/screens/product/map_selection_screen.dart index da07e5f..a928711 100644 --- a/lib/screens/product/map_selection_screen.dart +++ b/lib/screens/product/map_selection_screen.dart @@ -116,7 +116,7 @@ class _MapSelectionScreenState extends State { children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'com.example.app', + userAgentPackageName: 'com.example.lba', ), ], ), diff --git a/lib/screens/product/productdetail.dart b/lib/screens/product/productdetail.dart index 7cd190a..72784d9 100644 --- a/lib/screens/product/productdetail.dart +++ b/lib/screens/product/productdetail.dart @@ -15,13 +15,13 @@ class Productdetail extends StatefulWidget { class _ProductdetailState extends State with SingleTickerProviderStateMixin { - String selectedImage = Assets.images.media.path; + String selectedImage = Assets.images.wp1929534FastFoodWallpapers1.path; int selectedIndex = 0; final List imageList = [ + Assets.images.wp1929534FastFoodWallpapers1.path, Assets.images.media.path, Assets.images.topDealsAndStores.path, - Assets.images.wp1929534FastFoodWallpapers1.path, Assets.images.image.path, ]; diff --git a/lib/screens/product/shop.dart b/lib/screens/product/shop.dart index cb28894..7242720 100644 --- a/lib/screens/product/shop.dart +++ b/lib/screens/product/shop.dart @@ -260,11 +260,11 @@ class _ShopState extends State with TickerProviderStateMixin { style: TextStyle(color: isOpen ? Colors.green : Colors.red), ), const SizedBox(width: 5), - Container( + isOpen? Container( color: LightAppColors.productDetailDivider, width: 1, height: 13, - ), + ) : SizedBox(), const SizedBox(width: 5), Text( timeRange, diff --git a/lib/widgets/remainingTime.dart b/lib/widgets/remainingTime.dart index 0d96434..655fde2 100644 --- a/lib/widgets/remainingTime.dart +++ b/lib/widgets/remainingTime.dart @@ -7,9 +7,21 @@ class RemainingTime { DateTime? _expiryTime; Timer? _timer; - void initializeFromExpiry({required String expiryTimeString}) { + void initializeFromExpiry({required dynamic expiryTime}) { try { - _expiryTime = DateTime.fromMillisecondsSinceEpoch(int.parse(expiryTimeString)).toUtc(); + if (expiryTime is DateTime) { + _expiryTime = expiryTime.toUtc(); + } else if (expiryTime is String) { + if (expiryTime.contains('-') && expiryTime.contains(':')) { + _expiryTime = DateTime.parse(expiryTime).toUtc(); + } else { + _expiryTime = + DateTime.fromMillisecondsSinceEpoch(int.parse(expiryTime)) + .toUtc(); + } + } else { + throw ArgumentError("Invalid type for expiryTime"); + } _updateRemainingSeconds(); startTimer(); } catch (e) { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index fe11140..033194e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 633bb62..055ccfc 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux file_selector_linux maps_launcher url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a205f64..fd92eba 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,20 +5,24 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import file_selector_macos import geolocator_apple import location import maps_launcher import mobile_scanner +import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 119ca6b..ba89396 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.12.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: e653f162ddfcec1da2040ba2d8553fff1662b5c2a5c636f4c21a3b11bee497de + url: "https://pub.dev" + source: hosted + version: "6.5.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + url: "https://pub.dev" + source: hosted + version: "4.2.1" bloc: dependency: "direct main" description: @@ -374,6 +430,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.29" + flutter_sound: + dependency: "direct main" + description: + name: flutter_sound + sha256: ef89477f6e8ce2fa395158ebc4a8b11982e3ada440b4021c06fd97a4e771554b + url: "https://pub.dev" + source: hosted + version: "9.28.0" + flutter_sound_platform_interface: + dependency: transitive + description: + name: flutter_sound_platform_interface + sha256: "3394d7e664a09796818014ff85a81db0dec397f4c286cbe52f8783886fa5a497" + url: "https://pub.dev" + source: hosted + version: "9.28.0" + flutter_sound_web: + dependency: transitive + description: + name: flutter_sound_web + sha256: "4e10c94a8574bd93bb8668af59bf76f5312a890bccd3778d73168a7133217dc5" + url: "https://pub.dev" + source: hosted + version: "9.28.0" flutter_svg: dependency: "direct main" description: @@ -776,6 +856,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -1053,6 +1157,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b1482be..d89b3c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,9 @@ dependencies: mobile_scanner: ^5.1.1 image_picker: ^1.1.2 permission_handler: ^11.3.1 + flutter_sound: ^9.8.4 + audioplayers: ^6.1.0 + path_provider: ^2.1.4 # geocoding: ^3.0.0 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 3c8fc89..618d94b 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); GeolocatorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 03bad29..5fd938b 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows file_selector_windows geolocator_windows maps_launcher