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