From 2fd7df9a5261569050af87383f6dce898c19676c Mon Sep 17 00:00:00 2001 From: mariam Date: Wed, 18 Feb 2026 00:51:07 +0200 Subject: [PATCH 01/10] feat(SCRUM-87): implement initial order deatils ui page with static data --- assets/images/flower_logo.png | Bin 0 -> 1491 bytes assets/images/whatsapp.png | Bin 0 -> 1546 bytes assets/translations/ar.json | 10 +- assets/translations/en.json | 13 +- lib/app/core/router/app_router.dart | 6 + lib/app/core/router/route_names.dart | 1 + lib/app/core/ui_helper/color/colors.dart | 1 + lib/app/core/values/paths.dart | 2 + .../pages/drivers_orders_details_page.dart | 133 ++++++++++++++++++ .../presentation/widgets/address_card.dart | 78 ++++++++++ .../widgets/bottom_row_section.dart | 41 ++++++ .../presentation/widgets/order_item.dart | 61 ++++++++ .../presentation/widgets/section_title.dart | 20 +++ 13 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 assets/images/flower_logo.png create mode 100644 assets/images/whatsapp.png create mode 100644 lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart create mode 100644 lib/features/driver_orders_details/presentation/widgets/address_card.dart create mode 100644 lib/features/driver_orders_details/presentation/widgets/bottom_row_section.dart create mode 100644 lib/features/driver_orders_details/presentation/widgets/order_item.dart create mode 100644 lib/features/driver_orders_details/presentation/widgets/section_title.dart diff --git a/assets/images/flower_logo.png b/assets/images/flower_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..52e92c56ae5e873b2e44e6f8b11418ffcebdbaca GIT binary patch literal 1491 zcmV;^1uXiBP)RSSW1i`Afi&jpz{b|{btVi!Xcy8fI|cvFmSl3K??mk zgvbw;>sapv>k{01uqmi}^_yhT z25bQ&B9cy8=R>3I(L!~#LKOyTE??10wBD@}%ShlP-=nYQ($zwBZ7obQJ{Yl(hNF#9ZtZN&~iLSCyMzOh2f3c+eN-~r}jZrvIT zGiil#XAe(qsaB$Q(sQ{ITN!#8fn)ojKm7;){XLI~XdEsG=w5M?j%P9%*=eo6x~Wk> ztz51suMME|KpDJ#9pwj0;2I$s)Pl9d@;ofG>u`v-yad%Bzk$Hf{g@k>$>z^br%|@g zi|V&4P+IPRV-{D}#Z&%ST5>z#kT*ZCBhkJ^kz;XXVLXgtZajrum0p#fN|<=~%LJTa zTGp9RtUbJBVeuJUp!t@q$6`g@?-0jEvtk)`5jW^UC=bwjgI5D&=W}qu)E{F=O)sbf zZxZ5A45D@%@THN8TPlq|O28FDoaF7SaO(~kG@p)_-mg-#1pIRlG1`cY?@~g?{))p) zS#XV%=RJ}}p`%UlDr!GHmi6Q0zaHqLWzfkZDv$WpfQxoJ-m_Y?8V4Zq-8m3E&!0c}qytm%1J zGYNRA_NontR_@1S$6XW?A%&qyfyZ{0X73DOZ{umidj=7|H>mQGu?czzKZ=c%vd<%= z4Aatl#jaxd$pmx~XiX=OxHqJ%%;yaEfKEMNQV(#wK;s86$nxbE-ofzk^449|E?+l$=Yg$QhU0Qt zDB5a}@`!E4sRYvG22;0tFxRu_7VN{E%$dcTu$%D1u-mY8+28%AUPp?=uw`d|et_}s zep3HAaa$}f-aUx@UwlO619lQtVlR;v@*;}7^4W+`r z3P!udYa97q=O~8KOVJ`-qj;Qj6yh?-q7hS?Egam#25}YiQE@qQp@D)ioa!x#aR~l| toAc>bC4mTizI|-*-kY|wyhDUT{s*F*G_F&Uv;qJC002ovPDHLkV1h03yubhe literal 0 HcmV?d00001 diff --git a/assets/images/whatsapp.png b/assets/images/whatsapp.png new file mode 100644 index 0000000000000000000000000000000000000000..a45d019ff67e02b835d7d7f10315989fe995eed8 GIT binary patch literal 1546 zcmV+l2KD)gP)v=#%W(Ti~y@C>lCfFiQpKLh3gj{$cA)AcJq z2{Zwl3;bVb&jAjlmtj2cOM=ip>$yG;*e3<8D&Q1g2GCZ3=Ed{^^wBRF67p+xeAQ+0 z5W_7C(BB0-qB8s^*c*5$AXo=54cN=9uK{iao(EciA9Zb~e$f?rgsx54;-i600e%eu zUYVmBcr74|obN&2x;=p@z{(yty`{kUhHpPLpk4oJgq>Cro(l-^Ca|x!=2Boogb1)q zeS(jv0cSgx3gM=Jkc;(e3l0Qcj2HsD^_bkD4$@(Y`T>94jae%{NQLp4zUL`_IIt?R z^8Q$8&ik~0p-#$5`98o0hT%rwU{Cpt87iN2-WQqq_svd;hDnBzY`>8@CujfD3MlV} zn&PLi}J=m&He;zM8t$iI&4BrZy-We;Z~l`M5uYV7CW+z?uvUFv8=p zJhlP8Gt8viMa$D-f_0khDL6hrv)m!a%b_*)5ok5cPS)QqN-3P=4Mzlw@I}IUabj0% zxb+=cq2WG?^;xppqKCmtyz)2g7h3804nM=3XB?w+c_x}8a|`f!0ovb~_5R%JF${3G zAu7Fmhn5nI@S!PQ9M0A7xE-i1Acw8^Y+{C1p3xJbk%p+GvJSVEcvj^&9hJbRZxK1H za&AbfPC{go|CEisCK7b&_7w~;1xT8)Mg2#qm>Hn8$qThrCTWXS3<(w#C_tu`XaG0n zdr4I>^iPN!e$8Pk(E<`R0YD8 zpXv@Qa;ceSe5DPONt)SRuD!odD3oJ(s>gq*nO~K~mDxDMFbVq9kaD} zFh;Y>5DrSXmS0k-2NKmI-aRsGl;3$p!h)>kApBFxIt<6GE{tE8T!5I~;0 z975?BtT#WK6Y2@S2&xE186gHF?2;i`!!OY6&YkP!y6vyg%&kUgNIh_Z(y!I>)^?)Z z*r7+pUCCDzwVogn^3Znlc-@MUfc%+JG}x~9ky#wE9^DXajr9Jf*jTNZUaxpJ+>lb} z9omaqq>0mL?ITXmV&`2gR({YvLziB1uGP-a6Z%xnUBCAlGzXOJ(kgMjz5yJbqO-Hx wq78UUkHSQKj8u{2BS(%LIdbI4k%J8I5AV_*;m@mCumAu607*qoM6N<$f?HnSuK)l5 literal 0 HcmV?d00001 diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 177de8b..69909e4 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -238,6 +238,12 @@ "vehicle_license": "رخصة المركبة", "editDriverProfile": "تعديل الملف الشخصي", "editVehicle": "تعديل المركبة", - "cannotBeSame": "كلمة المرور الجديدة لا يجب أن تطابق الحالية" - + "cannotBeSame": "كلمة المرور الجديدة لا يجب أن تطابق الحالية", + "orderDetails": "بيانات الطلب", + "status": "الحالة", + "orderId": "رقم الطلب : ", + "pickupAddress": "عنوان الاستلام", + "floweryStore": "متجر فلوري", + "userAddress": "عنوان المستخدم", + "arrivedAtPickupPoint": "وصلت الى نقطة الالتقاء" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index 0081696..cf0d5bf 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -113,7 +113,7 @@ "no_products_found": "No products found", "change_language": "Change Language", "arabic": "Arabic", - "initialSearchMsg" : "Search For Any Product You Want", + "initialSearchMsg": "Search For Any Product You Want", "welcomeMessage": "Welcome to Flowery Shop", "home": "Home", "profile": "Profile", @@ -198,7 +198,7 @@ "notification_deleted_successfully": "Notification deleted successfully", "clear_all": "Clear all", "no_notifications_yet": "No Notifications Yet", - "orders" : "Orders", + "orders": "Orders", "onboardingTitle": "Welcome to ", "onboardingDescription": "Flowery rider app ", "applyNow": "Apply Now", @@ -241,5 +241,12 @@ "vehicle_license": "Vehicle License", "editDriverProfile": "Edit Driver Profile", "editVehicle": "Edit Vehicle", - "cannotBeSame": "New password cann't be same" + "cannotBeSame": "New password cann't be same", + "orderDetails": "Order details", + "status": "Status : ", + "orderId": "Order ID : # ", + "pickupAddress": "Pickup address", + "floweryStore": "Flowery Store", + "userAddress": "User address", + "arrivedAtPickupPoint": "Arrived at pickup point" } \ No newline at end of file diff --git a/lib/app/core/router/app_router.dart b/lib/app/core/router/app_router.dart index 5f7cd58..e9a3698 100644 --- a/lib/app/core/router/app_router.dart +++ b/lib/app/core/router/app_router.dart @@ -5,6 +5,7 @@ import 'package:tracking_app/app/config/di/di.dart'; import 'package:tracking_app/app/core/router/route_names.dart'; import 'package:tracking_app/features/Onboarding/presentation/pages/onboardingScreen.dart'; import 'package:tracking_app/features/app_sections/presentation/pages/app_sections.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart'; import 'package:tracking_app/features/profile/data/models/driver_model.dart'; import 'package:tracking_app/features/profile/presentation/pages/edit_driver_profile_page.dart'; import 'package:tracking_app/features/profile/presentation/pages/edit_vehicle_page.dart'; @@ -90,6 +91,11 @@ final GoRouter appRouter = GoRouter( return EditVehiclePage(driver: driver); }, ), + + GoRoute( + path: RouteNames.driverOrdersDetailsPage, + builder: (context, state) => DriversOrdersDetailsPage(), + ), ], redirect: (context, state) async { final token = await getIt().getToken(); diff --git a/lib/app/core/router/route_names.dart b/lib/app/core/router/route_names.dart index 93702d0..abaec7d 100644 --- a/lib/app/core/router/route_names.dart +++ b/lib/app/core/router/route_names.dart @@ -13,4 +13,5 @@ abstract class RouteNames { static const editDriverProfile = "/editDriverProfile"; static const editVehicle = "/editVehicle"; static const getProfle = "/profile-data"; + static const driverOrdersDetailsPage = "/driversOrdersDetails"; } diff --git a/lib/app/core/ui_helper/color/colors.dart b/lib/app/core/ui_helper/color/colors.dart index 394c8a3..bcdc243 100644 --- a/lib/app/core/ui_helper/color/colors.dart +++ b/lib/app/core/ui_helper/color/colors.dart @@ -17,4 +17,5 @@ abstract final class AppColors { static const Color white = Color(0xFFFFFFFF); static const Color purple = Color(0xFF441AB0); static const Color white70 = Color(0xFFA6A6A6); + static const Color lightPink = Color(0xFFF9ECF0); } diff --git a/lib/app/core/values/paths.dart b/lib/app/core/values/paths.dart index d1ceac7..8ef55d1 100644 --- a/lib/app/core/values/paths.dart +++ b/lib/app/core/values/paths.dart @@ -4,4 +4,6 @@ class AppPaths { static const String aboutUs = 'about_app'; static const String terms = 'terms_and_conditions'; static const String onboardingImage = 'assets/images/Clip path group.png'; + static const String whatsappImage = 'assets/images/whatsapp.png'; + static const String flowerLogo = 'assets/images/flower_logo.png'; } diff --git a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart new file mode 100644 index 0000000..2030a2b --- /dev/null +++ b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart @@ -0,0 +1,133 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; +import 'package:tracking_app/app/core/values/paths.dart'; +import 'package:tracking_app/app/core/widgets/custom_button.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/address_card.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/bottom_row_section.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/order_item.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/section_title.dart'; +import 'package:tracking_app/generated/locale_keys.g.dart'; + +class DriversOrdersDetailsPage extends StatelessWidget { + const DriversOrdersDetailsPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: AppColors.blackColor), + onPressed: () => context.pop(), + ), + title: Text( + LocaleKeys.orderDetails.tr(), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 20, + color: AppColors.blackColor, + ), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: List.generate( + 5, + (index) => Expanded( + child: Container( + height: 4, + margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: index == 0 ? AppColors.green : AppColors.lightGrey, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ), + ), + const SizedBox(height: 20), + + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.lightPink, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${LocaleKeys.status.tr()}Accepted', + style: TextStyle( + color: AppColors.green, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + '${LocaleKeys.orderId.tr()}123456', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const SizedBox(height: 4), + Text( + 'Wed, 03 Sep 2024, 11:00 AM', + style: TextStyle(color: AppColors.grey, fontSize: 14), + ), + ], + ), + ), + const SizedBox(height: 24), + + SectionTitle(title: LocaleKeys.pickupAddress.tr()), + AddressCard( + title: LocaleKeys.floweryStore.tr(), + address: '20th st, Sheikh Zayed, Giza', + imagePath: AppPaths.flowerLogo, + ), + const SizedBox(height: 16), + SectionTitle(title: LocaleKeys.userAddress.tr()), + AddressCard( + title: 'Nour mohamed', + address: '20th st, Sheikh Zayed, Giza', + imagePath: AppPaths.flowerLogo, + ), + const SizedBox(height: 24), + + SectionTitle(title: LocaleKeys.orderDetails.tr()), + OrderItem(), + OrderItem(), + const SizedBox(height: 16), + + BottomRowSection( + label: LocaleKeys.total.tr(), + value: '${LocaleKeys.egp.tr()} 3000', + ), + BottomRowSection( + label: LocaleKeys.payment_method.tr(), + value: LocaleKeys.cash_on_delivery.tr(), + ), + + const SizedBox(height: 32), + + SizedBox( + width: double.infinity, + height: 55, + child: CustomButton( + isEnabled: true, + onPressed: () {}, + isLoading: false, + text: LocaleKeys.arrivedAtPickupPoint.tr(), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/driver_orders_details/presentation/widgets/address_card.dart b/lib/features/driver_orders_details/presentation/widgets/address_card.dart new file mode 100644 index 0000000..6b211c2 --- /dev/null +++ b/lib/features/driver_orders_details/presentation/widgets/address_card.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; +import 'package:tracking_app/app/core/values/paths.dart'; + +class AddressCard extends StatelessWidget { + final String title; + final String address; + final String imagePath; + + const AddressCard({ + super.key, + required this.title, + required this.address, + required this.imagePath, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: AppColors.lightGrey), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + CircleAvatar(backgroundImage: AssetImage(imagePath), radius: 25), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of( + context, + ).textTheme.labelSmall!.copyWith(fontWeight: FontWeight.w400), + ), + Row( + children: [ + Icon( + Icons.location_on_outlined, + size: 16, + color: AppColors.blackColor, + ), + Flexible( + child: Text( + address, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w400, + color: AppColors.blackColor, + ), + ), + ), + ], + ), + ], + ), + ), + IconButton( + onPressed: () {}, + icon: Icon(Icons.phone_outlined, color: AppColors.pink, size: 20), + ), + + IconButton( + onPressed: () {}, + icon: ImageIcon( + AssetImage(AppPaths.whatsappImage), + color: AppColors.pink, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/driver_orders_details/presentation/widgets/bottom_row_section.dart b/lib/features/driver_orders_details/presentation/widgets/bottom_row_section.dart new file mode 100644 index 0000000..481983d --- /dev/null +++ b/lib/features/driver_orders_details/presentation/widgets/bottom_row_section.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; + +class BottomRowSection extends StatelessWidget { + final String label; + final String value; + const BottomRowSection({super.key, required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: AppColors.lightGrey), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: Theme.of(context).textTheme.labelMedium!.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16, + ), + ), + Text( + value, + style: Theme.of( + context, + ).textTheme.labelSmall!.copyWith(fontWeight: FontWeight.w500), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/driver_orders_details/presentation/widgets/order_item.dart b/lib/features/driver_orders_details/presentation/widgets/order_item.dart new file mode 100644 index 0000000..1fbefbc --- /dev/null +++ b/lib/features/driver_orders_details/presentation/widgets/order_item.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; +import 'package:tracking_app/app/core/values/paths.dart'; + +class OrderItem extends StatelessWidget { + const OrderItem({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: AppColors.lightGrey), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + AppPaths.flowerLogo, + width: 50, + height: 50, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Red roses, 15 Pink Rose Bouquet', + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.labelSmall!.copyWith(fontWeight: FontWeight.w400), + ), + Text( + 'EGP 600', + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.blackColor, + ), + ), + ], + ), + ), + Text( + 'X1', + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.pink, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/driver_orders_details/presentation/widgets/section_title.dart b/lib/features/driver_orders_details/presentation/widgets/section_title.dart new file mode 100644 index 0000000..8055f29 --- /dev/null +++ b/lib/features/driver_orders_details/presentation/widgets/section_title.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; + +class SectionTitle extends StatelessWidget { + final String title; + const SectionTitle({super.key, required this.title}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text( + title, + style: Theme.of( + context, + ).textTheme.bodyMedium!.copyWith(color: AppColors.blackColor), + ), + ); + } +} From c1b4c06d14351d20a302a518992fdc94c6b3f4f0 Mon Sep 17 00:00:00 2001 From: mariam Date: Wed, 18 Feb 2026 23:28:39 +0200 Subject: [PATCH 02/10] feat(SCRUM-87): adding data&domain layers to get data from firebase collection --- lib/app/config/network/firebase_module.dart | 8 + lib/app/core/router/app_router.dart | 2 +- lib/app/core/router/route_names.dart | 2 +- .../order_details_remote_datasource_impl.dart | 14 ++ .../order_details_remote_datasource.dart | 5 + .../data/mapper/order_dto_mapper.dart | 21 ++ .../data/models/orders_dto.dart | 61 +++++ .../data/repos/order_details_repo_impl.dart | 27 ++ .../domain/models/orders_model.dart | 24 ++ .../domain/repos/order_details_repo.dart | 5 + .../usecases/get_order_details_usecase.dart | 11 + .../manager/order_details_cubit.dart | 30 +++ .../manager/order_details_states.dart | 11 + .../pages/drivers_orders_details_page.dart | 236 +++++++++++------- pubspec.lock | 24 ++ pubspec.yaml | 1 + 16 files changed, 391 insertions(+), 91 deletions(-) create mode 100644 lib/app/config/network/firebase_module.dart create mode 100644 lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart create mode 100644 lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart create mode 100644 lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart create mode 100644 lib/features/driver_orders_details/data/models/orders_dto.dart create mode 100644 lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart create mode 100644 lib/features/driver_orders_details/domain/models/orders_model.dart create mode 100644 lib/features/driver_orders_details/domain/repos/order_details_repo.dart create mode 100644 lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart create mode 100644 lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart create mode 100644 lib/features/driver_orders_details/presentation/manager/order_details_states.dart diff --git a/lib/app/config/network/firebase_module.dart b/lib/app/config/network/firebase_module.dart new file mode 100644 index 0000000..e7ebf30 --- /dev/null +++ b/lib/app/config/network/firebase_module.dart @@ -0,0 +1,8 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:injectable/injectable.dart'; + +@module +abstract class FirebaseModule { + @lazySingleton + FirebaseFirestore get firestore => FirebaseFirestore.instance; +} diff --git a/lib/app/core/router/app_router.dart b/lib/app/core/router/app_router.dart index e9a3698..14342bc 100644 --- a/lib/app/core/router/app_router.dart +++ b/lib/app/core/router/app_router.dart @@ -93,7 +93,7 @@ final GoRouter appRouter = GoRouter( ), GoRoute( - path: RouteNames.driverOrdersDetailsPage, + path: RouteNames.ordersDetailsPage, builder: (context, state) => DriversOrdersDetailsPage(), ), ], diff --git a/lib/app/core/router/route_names.dart b/lib/app/core/router/route_names.dart index abaec7d..67bbd3a 100644 --- a/lib/app/core/router/route_names.dart +++ b/lib/app/core/router/route_names.dart @@ -13,5 +13,5 @@ abstract class RouteNames { static const editDriverProfile = "/editDriverProfile"; static const editVehicle = "/editVehicle"; static const getProfle = "/profile-data"; - static const driverOrdersDetailsPage = "/driversOrdersDetails"; + static const ordersDetailsPage = "/ordersDetails"; } diff --git a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart new file mode 100644 index 0000000..8490a51 --- /dev/null +++ b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart @@ -0,0 +1,14 @@ +import 'package:injectable/injectable.dart'; +import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +@Injectable(as: OrderDetailsRemoteDatasource) +class OrderDetailsRemoteDatasourceImpl implements OrderDetailsRemoteDatasource { + final FirebaseFirestore firestore; + OrderDetailsRemoteDatasourceImpl({required this.firestore}); + + @override + Stream getOrderStream(String orderId) { + return firestore.collection('u8sj29sk2sff').doc(orderId).snapshots(); + } +} diff --git a/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart b/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart new file mode 100644 index 0000000..90bbf26 --- /dev/null +++ b/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart @@ -0,0 +1,5 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +abstract class OrderDetailsRemoteDatasource { + Stream getOrderStream(String orderId); +} diff --git a/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart b/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart new file mode 100644 index 0000000..40ea288 --- /dev/null +++ b/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart @@ -0,0 +1,21 @@ +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; + +extension OrderDtoMapper on OrderDto { + OrderModel toOrderModel() { + return OrderModel( + driverId: driverId, + id: id, + status: status, + totalPrice: totalPrice, + userAddress: userAddress.toUserAddressModel(), + userId: userId, + ); + } +} + +extension UserAddressDtoMapper on UserAddressDto { + UserAddressModel toUserAddressModel() { + return UserAddressModel(address: address, name: name); + } +} diff --git a/lib/features/driver_orders_details/data/models/orders_dto.dart b/lib/features/driver_orders_details/data/models/orders_dto.dart new file mode 100644 index 0000000..ea6d37f --- /dev/null +++ b/lib/features/driver_orders_details/data/models/orders_dto.dart @@ -0,0 +1,61 @@ +class UserAddressDto { + final String address; + final String name; + + UserAddressDto({required this.address, required this.name}); + + factory UserAddressDto.fromJson(Map json) { + return UserAddressDto( + address: json['address'].toString(), + name: json['name'].toString(), + ); + } + + Map toJson() { + return {'address': address, 'name': name}; + } +} + +class OrderDto { + final String driverId; + final String id; + final String status; + final String totalPrice; + final UserAddressDto userAddress; + final String userId; + + OrderDto({ + required this.driverId, + required this.id, + required this.status, + required this.totalPrice, + required this.userAddress, + required this.userId, + }); + + factory OrderDto.fromJson(Map json) { + return OrderDto( + driverId: json['driverId'].toString(), + id: json['id'].toString(), + status: json['status'].toString(), + totalPrice: json['totalPrice'].toString(), + userAddress: json['userAddress '] != null + ? UserAddressDto.fromJson( + Map.from(json['userAddress ']), + ) + : UserAddressDto(address: 'No Address', name: 'No Name'), + userId: json['userId'].toString(), + ); + } + + Map toJson() { + return { + 'driverId': driverId, + 'id': id, + 'status': status, + 'totalPrice': totalPrice, + 'userAddress': userAddress.toJson(), + 'userId': userId, + }; + } +} diff --git a/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart new file mode 100644 index 0000000..0d9288c --- /dev/null +++ b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart @@ -0,0 +1,27 @@ +import 'package:injectable/injectable.dart'; +import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; +import 'package:tracking_app/features/driver_orders_details/data/mapper/order_dto_mapper.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/repos/order_details_repo.dart'; + +@Injectable(as: OrderDetailsRepo) +class OrderDetailsRepoImpl implements OrderDetailsRepo { + final OrderDetailsRemoteDatasource remoteDataSource; + OrderDetailsRepoImpl(this.remoteDataSource); + + @override + Stream getOrderDetails(String orderId) { + return remoteDataSource.getOrderStream(orderId).map((snapshot) { + if (!snapshot.exists || snapshot.data() == null) { + throw Exception("Document does not exist in Firestore!"); + } + final Map data = Map.from( + snapshot.data() as Map, + ); + + final orderDto = OrderDto.fromJson(data); + return orderDto.toOrderModel(); + }); + } +} diff --git a/lib/features/driver_orders_details/domain/models/orders_model.dart b/lib/features/driver_orders_details/domain/models/orders_model.dart new file mode 100644 index 0000000..dc894bb --- /dev/null +++ b/lib/features/driver_orders_details/domain/models/orders_model.dart @@ -0,0 +1,24 @@ +class UserAddressModel { + final String address; + final String name; + + UserAddressModel({required this.address, required this.name}); +} + +class OrderModel { + final String driverId; + final String id; + final String status; + final String totalPrice; + final UserAddressModel userAddress; + final String userId; + + OrderModel({ + required this.driverId, + required this.id, + required this.status, + required this.totalPrice, + required this.userAddress, + required this.userId, + }); +} diff --git a/lib/features/driver_orders_details/domain/repos/order_details_repo.dart b/lib/features/driver_orders_details/domain/repos/order_details_repo.dart new file mode 100644 index 0000000..52f7600 --- /dev/null +++ b/lib/features/driver_orders_details/domain/repos/order_details_repo.dart @@ -0,0 +1,5 @@ +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; + +abstract class OrderDetailsRepo { + Stream getOrderDetails(String orderId); +} diff --git a/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart new file mode 100644 index 0000000..d4ba987 --- /dev/null +++ b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart @@ -0,0 +1,11 @@ +import 'package:injectable/injectable.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/repos/order_details_repo.dart'; + +@injectable +class GetOrderDetailsUsecase { + OrderDetailsRepo repo; + GetOrderDetailsUsecase({required this.repo}); + + Stream call(String orderId) => repo.getOrderDetails(orderId); +} diff --git a/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart new file mode 100644 index 0000000..e18366b --- /dev/null +++ b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart @@ -0,0 +1,30 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:injectable/injectable.dart'; +import 'package:tracking_app/app/config/base_state/base_state.dart'; +import '../../domain/usecases/get_order_details_usecase.dart'; +import 'order_details_states.dart'; + +@injectable +class OrderDetailsCubit extends Cubit { + final GetOrderDetailsUsecase getOrderDetailsUsecase; + StreamSubscription? _subscription; + + OrderDetailsCubit(this.getOrderDetailsUsecase) : super(OrderDetailsStates()); + + void getOrderDetails(String orderId) async { + emit(state.copyWith(data: Resource.loading())); + _subscription?.cancel(); + + _subscription = getOrderDetailsUsecase + .call(orderId) + .listen( + (order) { + emit(state.copyWith(data: Resource.success(order))); + }, + onError: (error) { + emit(state.copyWith(data: Resource.error(error.toString()))); + }, + ); + } +} diff --git a/lib/features/driver_orders_details/presentation/manager/order_details_states.dart b/lib/features/driver_orders_details/presentation/manager/order_details_states.dart new file mode 100644 index 0000000..267a1ca --- /dev/null +++ b/lib/features/driver_orders_details/presentation/manager/order_details_states.dart @@ -0,0 +1,11 @@ +import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; + +class OrderDetailsStates { + final Resource? data; + const OrderDetailsStates({this.data}); + + OrderDetailsStates copyWith({Resource? data}) { + return OrderDetailsStates(data: data ?? this.data); + } +} diff --git a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart index 2030a2b..bc676e5 100644 --- a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart +++ b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart @@ -1,9 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/app/config/di/di.dart'; import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; import 'package:tracking_app/app/core/values/paths.dart'; import 'package:tracking_app/app/core/widgets/custom_button.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_states.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/address_card.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/bottom_row_section.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/order_item.dart'; @@ -15,6 +20,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { + var cubit = getIt(); return Scaffold( appBar: AppBar( leading: IconButton( @@ -29,105 +35,157 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), ), ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: List.generate( - 5, - (index) => Expanded( - child: Container( - height: 4, - margin: const EdgeInsets.symmetric(horizontal: 2), - decoration: BoxDecoration( - color: index == 0 ? AppColors.green : AppColors.lightGrey, - borderRadius: BorderRadius.circular(2), + body: BlocProvider( + create: (context) => cubit..getOrderDetails('pxkMaEmWYVuvV5jkW0JK'), + child: BlocBuilder( + builder: (context, state) { + if (state.data?.status == Status.loading) { + return const Center(child: CircularProgressIndicator()); + } else if (state.data?.status == Status.error) { + return Center(child: Text(state.data!.error.toString())); + } else if (state.data?.status == Status.success) { + final order = state.data!.data; + return SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: List.generate(5, (index) { + int currentStep = _getStepCount(order!.status); + return Expanded( + child: Container( + height: 4, + margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: index < currentStep + ? AppColors.green + : AppColors.lightGrey, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), ), - ), - ), - ), - ), - const SizedBox(height: 20), + const SizedBox(height: 20), + + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.lightPink, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${LocaleKeys.status.tr()}${order!.status}', + style: TextStyle( + color: AppColors.green, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + '${LocaleKeys.orderId.tr()}${order.id}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + 'Wed, 03 Sep 2024, 11:00 AM', + style: TextStyle( + color: AppColors.grey, + fontSize: 14, + ), + ), + ], + ), + ), + const SizedBox(height: 24), - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.lightPink, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${LocaleKeys.status.tr()}Accepted', - style: TextStyle( - color: AppColors.green, - fontWeight: FontWeight.bold, - fontSize: 16, + SectionTitle(title: LocaleKeys.pickupAddress.tr()), + AddressCard( + title: LocaleKeys.floweryStore.tr(), + address: order.userAddress.address, + imagePath: AppPaths.flowerLogo, ), - ), - const SizedBox(height: 4), - Text( - '${LocaleKeys.orderId.tr()}123456', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - const SizedBox(height: 4), - Text( - 'Wed, 03 Sep 2024, 11:00 AM', - style: TextStyle(color: AppColors.grey, fontSize: 14), - ), - ], - ), - ), - const SizedBox(height: 24), + const SizedBox(height: 16), + SectionTitle(title: LocaleKeys.userAddress.tr()), - SectionTitle(title: LocaleKeys.pickupAddress.tr()), - AddressCard( - title: LocaleKeys.floweryStore.tr(), - address: '20th st, Sheikh Zayed, Giza', - imagePath: AppPaths.flowerLogo, - ), - const SizedBox(height: 16), - SectionTitle(title: LocaleKeys.userAddress.tr()), - AddressCard( - title: 'Nour mohamed', - address: '20th st, Sheikh Zayed, Giza', - imagePath: AppPaths.flowerLogo, - ), - const SizedBox(height: 24), + AddressCard( + title: order.userAddress.name, + address: order.userAddress.address, + imagePath: AppPaths.flowerLogo, + ), + const SizedBox(height: 24), - SectionTitle(title: LocaleKeys.orderDetails.tr()), - OrderItem(), - OrderItem(), - const SizedBox(height: 16), + SectionTitle(title: LocaleKeys.orderDetails.tr()), + OrderItem(), + OrderItem(), + const SizedBox(height: 16), - BottomRowSection( - label: LocaleKeys.total.tr(), - value: '${LocaleKeys.egp.tr()} 3000', - ), - BottomRowSection( - label: LocaleKeys.payment_method.tr(), - value: LocaleKeys.cash_on_delivery.tr(), - ), + BottomRowSection( + label: LocaleKeys.total.tr(), + value: '${LocaleKeys.egp.tr()} ${order.totalPrice}', + ), + BottomRowSection( + label: LocaleKeys.payment_method.tr(), + value: LocaleKeys.cash_on_delivery.tr(), + ), - const SizedBox(height: 32), + const SizedBox(height: 32), - SizedBox( - width: double.infinity, - height: 55, - child: CustomButton( - isEnabled: true, - onPressed: () {}, - isLoading: false, - text: LocaleKeys.arrivedAtPickupPoint.tr(), - ), - ), - ], + SizedBox( + width: double.infinity, + height: 55, + child: CustomButton( + isEnabled: true, + onPressed: () {}, + isLoading: false, + text: _getButtonText(order.status), + ), + ), + ], + ), + ); + } + return const SizedBox.shrink(); + }, ), ), ); } + + int _getStepCount(String status) { + switch (status.toLowerCase()) { + case 'accepted': + return 1; + case 'pickup': + return 2; + case 'out_for_delivery': + return 3; + case 'arrived': + return 4; + case 'delivered': + return 5; + default: + return 1; + } + } + + String _getButtonText(String status) { + switch (status.toLowerCase()) { + case 'accepted': + return LocaleKeys.arrivedAtPickupPoint.tr(); + case 'pickup': + return 'Start deliver'; + default: + return LocaleKeys.arrivedAtPickupPoint.tr(); + } + } } diff --git a/pubspec.lock b/pubspec.lock index 8f59585..b19836b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "54484b2fc49f41b46f35b60a54b12351181eeaad22c0e3def276a81e17ae7c9b" + url: "https://pub.dev" + source: hosted + version: "6.1.2" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: dfaa8b2c0d0a824af289d4159816a5c78417feec264c2194081d645687195158 + url: "https://pub.dev" + source: hosted + version: "7.0.6" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "35d01f502b3b701d700470d32a8f82704dac8341a66e86c074900cde5bab343d" + url: "https://pub.dev" + source: hosted + version: "5.1.2" code_builder: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bb7cff1..2f2da4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: firebase_messaging: ^16.1.1 flutter_local_notifications: ^20.0.0 firebase_crashlytics: ^5.0.7 + cloud_firestore: ^6.1.2 dev_dependencies: bloc_test: ^10.0.0 From 54ab985da4b5059cc3c4419e2614848d22ff30a3 Mon Sep 17 00:00:00 2001 From: mariam Date: Thu, 19 Feb 2026 01:33:03 +0200 Subject: [PATCH 03/10] feat(SCRUM-87): Unit test and wigdet test for order deatils feature --- ...r_details_remote_datasource_impl_test.dart | 47 +++++++ .../data/mapper/order_dto_mapper_test.dart | 41 ++++++ .../data/models/orders_dto_test.dart | 81 ++++++++++++ .../repos/order_details_repo_impl_test.dart | 85 +++++++++++++ .../domain/models/orders_model_test.dart | 42 ++++++ .../get_order_details_usecase_test.dart | 54 ++++++++ .../manager/order_details_cubit_test.dart | 81 ++++++++++++ .../drivers_orders_details_page_test.dart | 120 ++++++++++++++++++ 8 files changed, 551 insertions(+) create mode 100644 test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart create mode 100644 test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart create mode 100644 test/features/driver_orders_details/data/models/orders_dto_test.dart create mode 100644 test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart create mode 100644 test/features/driver_orders_details/domain/models/orders_model_test.dart create mode 100644 test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart create mode 100644 test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart create mode 100644 test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart diff --git a/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart b/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart new file mode 100644 index 0000000..812e1d1 --- /dev/null +++ b/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:tracking_app/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart'; +import 'order_details_remote_datasource_impl_test.mocks.dart'; + +@GenerateMocks([ + FirebaseFirestore, + CollectionReference, + DocumentReference, + DocumentSnapshot, +]) +void main() { + late OrderDetailsRemoteDatasourceImpl dataSource; + late MockFirebaseFirestore mockFirestore; + late MockCollectionReference> mockCollection; + late MockDocumentReference> mockDocument; + late MockDocumentSnapshot> mockSnapshot; + + const String tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; + const String tCollectionName = 'u8sj29sk2sff'; + + setUp(() { + mockFirestore = MockFirebaseFirestore(); + mockCollection = MockCollectionReference(); + mockDocument = MockDocumentReference(); + mockSnapshot = MockDocumentSnapshot(); + + dataSource = OrderDetailsRemoteDatasourceImpl(firestore: mockFirestore); + }); + + test('return stream from documentSnapshot when call getOrderStream', () { + when(mockFirestore.collection(tCollectionName)).thenReturn(mockCollection); + when(mockCollection.doc(tOrderId)).thenReturn(mockDocument); + when( + mockDocument.snapshots(), + ).thenAnswer((_) => Stream.value(mockSnapshot)); + + final result = dataSource.getOrderStream(tOrderId); + + expect(result, emits(mockSnapshot)); + + verify(mockFirestore.collection(tCollectionName)).called(1); + verify(mockCollection.doc(tOrderId)).called(1); + }); +} diff --git a/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart b/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart new file mode 100644 index 0000000..d7683b0 --- /dev/null +++ b/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tracking_app/features/driver_orders_details/data/mapper/order_dto_mapper.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; + +void main() { + group('OrderDtoMapper', () { + test('Convert OrderDto to OrderModel correctly', () { + final tUserAddressDto = UserAddressDto(address: 'Alex', name: 'Mariam'); + + final tOrderDto = OrderDto( + driverId: 'D123', + id: 'O456', + status: 'accepted', + totalPrice: '150.0', + userAddress: tUserAddressDto, + userId: 'U789', + ); + + final result = tOrderDto.toOrderModel(); + + expect(result, isA()); + expect(result.id, tOrderDto.id); + expect(result.status, tOrderDto.status); + expect(result.totalPrice, tOrderDto.totalPrice); + expect(result.userAddress.name, tOrderDto.userAddress.name); + expect(result.userAddress.address, tOrderDto.userAddress.address); + }); + }); + + group('UserAddressDtoMapper', () { + test('Convert UserAddressDto to UserAddressModel correctly', () { + final tDto = UserAddressDto(address: 'Alex', name: 'Mariam'); + + final result = tDto.toUserAddressModel(); + + expect(result.name, tDto.name); + expect(result.address, tDto.address); + }); + }); +} diff --git a/test/features/driver_orders_details/data/models/orders_dto_test.dart b/test/features/driver_orders_details/data/models/orders_dto_test.dart new file mode 100644 index 0000000..8bfa40e --- /dev/null +++ b/test/features/driver_orders_details/data/models/orders_dto_test.dart @@ -0,0 +1,81 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; + +void main() { + group('UserAddressDto Tests', () { + test('should return a valid UserAddressDto from JSON', () { + final Map json = {'address': 'Alex', 'name': 'Mariam'}; + + final result = UserAddressDto.fromJson(json); + + expect(result.address, 'Alex'); + expect(result.name, 'Mariam'); + }); + + test('should return a valid JSON map from UserAddressDto', () { + final dto = UserAddressDto(address: 'Alex', name: 'Mariam'); + + final result = dto.toJson(); + + expect(result['address'], 'Alex'); + expect(result['name'], 'Mariam'); + }); + }); + + group('OrderDto Tests', () { + final Map tOrderJsonWithSpace = { + 'driverId': 'D123', + 'id': 'O456', + 'status': 'accepted', + 'totalPrice': '150.0', + 'userAddress ': {'address': 'Alex', 'name': 'Mariam'}, + 'userId': 'U789', + }; + + test( + 'should correctly parse userAddress when key has a trailing space', + () { + final result = OrderDto.fromJson(tOrderJsonWithSpace); + + expect(result.userAddress.name, 'Mariam'); + expect(result.userAddress.address, 'Alex'); + expect(result.id, 'O456'); + }, + ); + + test( + 'should return default address values when userAddress key is missing or null', + () { + final Map jsonMissingAddress = { + 'driverId': 'D123', + 'id': 'O456', + 'status': 'accepted', + 'totalPrice': '150.0', + 'userId': 'U789', + }; + + final result = OrderDto.fromJson(jsonMissingAddress); + + expect(result.userAddress.name, 'No Name'); + expect(result.userAddress.address, 'No Address'); + }, + ); + + test('should return a valid JSON map from OrderDto', () { + final dto = OrderDto( + driverId: 'D1', + id: 'O1', + status: 'pickup', + totalPrice: '100', + userAddress: UserAddressDto(address: 'Alex', name: 'Mariam'), + userId: 'U1', + ); + + final result = dto.toJson(); + + expect(result['driverId'], 'D1'); + expect(result['userAddress']['name'], 'Mariam'); + expect(result.containsKey('userAddress'), true); + }); + }); +} diff --git a/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart b/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart new file mode 100644 index 0000000..79b3234 --- /dev/null +++ b/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart @@ -0,0 +1,85 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; +import 'package:tracking_app/features/driver_orders_details/data/repos/order_details_repo_impl.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'order_details_repo_impl_test.mocks.dart'; + +@GenerateMocks([OrderDetailsRemoteDatasource, DocumentSnapshot]) +void main() { + late OrderDetailsRepoImpl repository; + late MockOrderDetailsRemoteDatasource mockRemoteDataSource; + late MockDocumentSnapshot mockSnapshot; + + setUp(() { + mockRemoteDataSource = MockOrderDetailsRemoteDatasource(); + mockSnapshot = MockDocumentSnapshot(); + repository = OrderDetailsRepoImpl(mockRemoteDataSource); + }); + + const tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; + + final tOrderData = { + 'driverId': 'D123', + 'id': 'O456', + 'status': 'accepted', + 'totalPrice': '150.0', + 'userAddress ': {'address': 'Alex', 'name': 'Mariam'}, + 'userId': 'U789', + }; + + group('getOrderDetails', () { + test( + 'should emit OrderModel when the remote data source returns a valid DocumentSnapshot', + () async { + when(mockSnapshot.exists).thenReturn(true); + when(mockSnapshot.data()).thenReturn(tOrderData); + when( + mockRemoteDataSource.getOrderStream(tOrderId), + ).thenAnswer((_) => Stream.value(mockSnapshot)); + + final result = repository.getOrderDetails(tOrderId); + + expect( + result, + emits( + isA() + .having((o) => o.id, 'order id', 'O456') + .having((o) => o.userAddress.name, 'user name', 'Mariam'), + ), + ); + }, + ); + + test( + 'should throw an Exception when the document does not exist', + () async { + when(mockSnapshot.exists).thenReturn(false); + when( + mockRemoteDataSource.getOrderStream(tOrderId), + ).thenAnswer((_) => Stream.value(mockSnapshot)); + + final result = repository.getOrderDetails(tOrderId); + + expect(result, emitsError(isA())); + }, + ); + + test( + 'should throw an Exception when data is null even if snapshot exists', + () async { + when(mockSnapshot.exists).thenReturn(true); + when(mockSnapshot.data()).thenReturn(null); + when( + mockRemoteDataSource.getOrderStream(tOrderId), + ).thenAnswer((_) => Stream.value(mockSnapshot)); + + final result = repository.getOrderDetails(tOrderId); + + expect(result, emitsError(isA())); + }, + ); + }); +} diff --git a/test/features/driver_orders_details/domain/models/orders_model_test.dart b/test/features/driver_orders_details/domain/models/orders_model_test.dart new file mode 100644 index 0000000..083c50c --- /dev/null +++ b/test/features/driver_orders_details/domain/models/orders_model_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; + +void main() { + group('OrderModel & UserAddressModel Tests', () { + test('should correctly initialize UserAddressModel with given values', () { + final tAddress = UserAddressModel(address: 'Cairo', name: 'Mohamed'); + + expect(tAddress.address, 'Cairo'); + expect(tAddress.name, 'Mohamed'); + }); + + test('should correctly initialize OrderModel with given values', () { + final tUserAddress = UserAddressModel(address: 'Cairo', name: 'Mohamed'); + + final tOrder = OrderModel( + driverId: 'DRV-101', + id: 'ORD-999', + status: 'picked_up', + totalPrice: '250.50', + userAddress: tUserAddress, + userId: 'USR-555', + ); + + expect(tOrder.driverId, 'DRV-101'); + expect(tOrder.id, 'ORD-999'); + expect(tOrder.status, 'picked_up'); + expect(tOrder.totalPrice, '250.50'); + expect(tOrder.userId, 'USR-555'); + + expect(tOrder.userAddress, isA()); + expect(tOrder.userAddress.name, 'Mohamed'); + }); + + test('should support equality check if needed (Optional)', () { + final address1 = UserAddressModel(address: 'A', name: 'B'); + final address2 = UserAddressModel(address: 'A', name: 'B'); + + expect(address1 == address2, isFalse); + }); + }); +} diff --git a/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart b/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart new file mode 100644 index 0000000..b4a718f --- /dev/null +++ b/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/repos/order_details_repo.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; + +import 'get_order_details_usecase_test.mocks.dart'; + +@GenerateMocks([OrderDetailsRepo]) +void main() { + late GetOrderDetailsUsecase usecase; + late MockOrderDetailsRepo mockRepo; + + setUp(() { + mockRepo = MockOrderDetailsRepo(); + usecase = GetOrderDetailsUsecase(repo: mockRepo); + }); + + const tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; + + final tOrderModel = OrderModel( + driverId: 'D1', + id: tOrderId, + status: 'accepted', + totalPrice: '100', + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userId: 'U1', + ); + + group('GetOrderDetailsUsecase test', () { + test('should get order details from the repository when called', () async { + when( + mockRepo.getOrderDetails(any), + ).thenAnswer((_) => Stream.value(tOrderModel)); + + final result = usecase.call(tOrderId); + + expect(result, emits(tOrderModel)); + verify(mockRepo.getOrderDetails(tOrderId)).called(1); + verifyNoMoreInteractions(mockRepo); + }); + + test('should forward the error stream if the repository fails', () async { + when( + mockRepo.getOrderDetails(any), + ).thenAnswer((_) => Stream.error('Error from Repository')); + + final result = usecase.call(tOrderId); + + expect(result, emitsError('Error from Repository')); + }); + }); +} diff --git a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart new file mode 100644 index 0000000..ab034c9 --- /dev/null +++ b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart @@ -0,0 +1,81 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_states.dart'; + +import 'order_details_cubit_test.mocks.dart'; + +@GenerateMocks([GetOrderDetailsUsecase]) +void main() { + late OrderDetailsCubit cubit; + late MockGetOrderDetailsUsecase mockUsecase; + + setUp(() { + mockUsecase = MockGetOrderDetailsUsecase(); + cubit = OrderDetailsCubit(mockUsecase); + }); + + tearDown(() { + cubit.close(); + }); + + const tOrderId = 'order_123'; + final tOrderModel = OrderModel( + driverId: 'D1', + id: tOrderId, + status: 'accepted', + totalPrice: '100', + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userId: 'U1', + ); + + group('OrderDetailsCubit Tests', () { + blocTest( + 'should emit [Loading, Success] when data is fetched successfully', + build: () { + when( + mockUsecase.call(any), + ).thenAnswer((_) => Stream.value(tOrderModel)); + return cubit; + }, + act: (cubit) => cubit.getOrderDetails(tOrderId), + expect: () => [ + predicate( + (state) => state.data?.status == Status.loading, + ), + predicate((state) { + return state.data?.status == Status.success && + state.data?.data == tOrderModel; + }), + ], + verify: (_) { + verify(mockUsecase.call(tOrderId)).called(1); + }, + ); + + blocTest( + 'should emit [Loading, Error] when fetching data fails', + build: () { + when( + mockUsecase.call(any), + ).thenAnswer((_) => Stream.error('Server Error')); + return cubit; + }, + act: (cubit) => cubit.getOrderDetails(tOrderId), + expect: () => [ + predicate( + (state) => state.data?.status == Status.loading, + ), + predicate((state) { + return state.data?.status == Status.error && + state.data?.error == 'Server Error'; + }), + ], + ); + }); +} diff --git a/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart b/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart new file mode 100644 index 0000000..eef4ee9 --- /dev/null +++ b/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart @@ -0,0 +1,120 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/app/config/di/di.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_states.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/address_card.dart'; +import 'drivers_orders_details_page_test.mocks.dart'; + +@GenerateMocks([OrderDetailsCubit]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late MockOrderDetailsCubit mockCubit; + + setUp(() async { + await getIt.reset(); + mockCubit = MockOrderDetailsCubit(); + getIt.registerFactory(() => mockCubit); + when(mockCubit.state).thenReturn(OrderDetailsStates()); + when(mockCubit.stream).thenAnswer((_) => const Stream.empty()); + }); + + Widget buildTestableWidget() { + return EasyLocalization( + supportedLocales: const [Locale('en')], + path: 'assets/translations', + fallbackLocale: const Locale('en'), + startLocale: const Locale('en'), + saveLocale: false, + child: Builder( + builder: (context) { + return MaterialApp( + home: BlocProvider.value( + value: mockCubit, + child: const DriversOrdersDetailsPage(), + ), + ); + }, + ), + ); + } + + final tOrderModel = OrderModel( + driverId: 'D1', + id: 'ORD-123', + status: 'accepted', + totalPrice: '500', + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userId: 'U1', + ); + + group('DriversOrdersDetailsPage Widget Tests', () { + testWidgets('should show CircularProgressIndicator when state is loading', ( + tester, + ) async { + when( + mockCubit.state, + ).thenReturn(OrderDetailsStates(data: Resource.loading())); + when(mockCubit.stream).thenAnswer( + (_) => Stream.value(OrderDetailsStates(data: Resource.loading())), + ); + + await tester.pumpWidget(buildTestableWidget()); + await tester.pump(); + + expect(find.byType(CircularProgressIndicator), findsOneWidget); + }); + + testWidgets( + 'should display order details correctly when state is success', + (tester) async { + when( + mockCubit.state, + ).thenReturn(OrderDetailsStates(data: Resource.success(tOrderModel))); + when(mockCubit.stream).thenAnswer( + (_) => Stream.value( + OrderDetailsStates(data: Resource.success(tOrderModel)), + ), + ); + + await tester.pumpWidget(buildTestableWidget()); + await tester.pump(); + + expect(find.textContaining('ORD-123'), findsOneWidget); + + expect(find.text('Ali'), findsOneWidget); + expect(find.text('Shebin'), findsAtLeastNWidgets(1)); + + expect(find.textContaining('500'), findsOneWidget); + + expect(find.byType(AddressCard), findsAtLeastNWidgets(2)); + }, + ); + + testWidgets('should display error message when state is error', ( + tester, + ) async { + const errorMessage = 'Failed to load order'; + when( + mockCubit.state, + ).thenReturn(OrderDetailsStates(data: Resource.error(errorMessage))); + when(mockCubit.stream).thenAnswer( + (_) => Stream.value( + OrderDetailsStates(data: Resource.error(errorMessage)), + ), + ); + + await tester.pumpWidget(buildTestableWidget()); + await tester.pump(); + + expect(find.text(errorMessage), findsOneWidget); + }); + }); +} From efc019e3790c0f0fbb886bd454b2accc79d4310c Mon Sep 17 00:00:00 2001 From: mariam Date: Thu, 19 Feb 2026 01:51:04 +0200 Subject: [PATCH 04/10] fix(SCRUM-87): update versions --- lib/generated/locale_keys.g.dart | 7 + pubspec.lock | 216 +++++++++++++++++++++---------- pubspec.yaml | 8 +- 3 files changed, 159 insertions(+), 72 deletions(-) diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 47bfd51..53ddf2f 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -246,4 +246,11 @@ abstract class LocaleKeys { static const editDriverProfile = 'editDriverProfile'; static const editVehicle = 'editVehicle'; static const cannotBeSame = 'cannotBeSame'; + static const orderDetails = 'orderDetails'; + static const status = 'status'; + static const orderId = 'orderId'; + static const pickupAddress = 'pickupAddress'; + static const floweryStore = 'floweryStore'; + static const userAddress = 'userAddress'; + static const arrivedAtPickupPoint = 'arrivedAtPickupPoint'; } diff --git a/pubspec.lock b/pubspec.lock index b19836b..ef0fc6d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,18 +21,26 @@ packages: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.7.1" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" archive: dependency: transitive description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: @@ -77,18 +85,18 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "3.1.0" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: @@ -101,26 +109,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "9.3.1" built_collection: dependency: transitive description: @@ -261,10 +269,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.1.1" dbus: dependency: transitive description: @@ -333,10 +341,10 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" file: dependency: transitive description: @@ -474,10 +482,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "76cd20bcfa72fabe50ea27eeaf165527f446f55d3033021462084b87805b4cac" + sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad" url: "https://pub.dev" source: hosted - version: "20.0.0" + version: "20.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -498,10 +506,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_windows - sha256: "7ddd964fa85b6a23e96956c5b63ef55cdb9e5947b71b95712204db42ad46da61" + sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" flutter_localizations: dependency: transitive description: flutter @@ -549,22 +557,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + geoclue: + dependency: transitive + description: + name: geoclue + sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f + url: "https://pub.dev" + source: hosted + version: "0.1.1" geolocator: dependency: "direct main" description: name: geolocator - sha256: f4efb8d3c4cdcad2e226af9661eb1a0dd38c71a9494b22526f9da80ab79520e5 + sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" url: "https://pub.dev" source: hosted - version: "10.1.1" + version: "14.0.2" geolocator_android: dependency: transitive description: name: geolocator_android - sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" url: "https://pub.dev" source: hosted - version: "4.6.2" + version: "5.0.2" geolocator_apple: dependency: transitive description: @@ -573,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.13" + geolocator_linux: + dependency: transitive + description: + name: geolocator_linux + sha256: d64112a205931926f4363bb6bd48f14cb38e7326833041d170615586cd143797 + url: "https://pub.dev" + source: hosted + version: "0.2.4" geolocator_platform_interface: dependency: transitive description: @@ -585,10 +609,10 @@ packages: dependency: transitive description: name: geolocator_web - sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "4.1.3" geolocator_windows: dependency: transitive description: @@ -617,10 +641,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836 + sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" url: "https://pub.dev" source: hosted - version: "13.2.5" + version: "17.1.0" google_maps: dependency: transitive description: @@ -633,10 +657,10 @@ packages: dependency: "direct main" description: name: google_maps_flutter - sha256: "819985697596a42e1054b5feb2f407ba1ac92262e02844a40168e742b9f36dca" + sha256: "9b0d6dab3de6955837575dc371dd772fcb5d0a90f6a4954e8c066472f9938550" url: "https://pub.dev" source: hosted - version: "2.14.0" + version: "2.14.2" google_maps_flutter_android: dependency: transitive description: @@ -649,10 +673,10 @@ packages: dependency: transitive description: name: google_maps_flutter_ios - sha256: "0504508a024410979936bd22bc2dc10a0df5cb1d15a21618d6cfbd973832464f" + sha256: a2e3c7ad2392ea65d6775704716d0aa3c3d226cb984fd0a688bca40f6be1a451 url: "https://pub.dev" source: hosted - version: "2.17.1" + version: "2.17.4" google_maps_flutter_platform_interface: dependency: transitive description: @@ -677,6 +701,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + gsettings: + dependency: transitive + description: + name: gsettings + sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" + url: "https://pub.dev" + source: hosted + version: "0.2.8" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" html: dependency: transitive description: @@ -777,18 +817,18 @@ packages: dependency: "direct main" description: name: injectable - sha256: "32e9bac6fe9c84339c5add60478d27a01e363ce1ad5c22ca7e525c6b28a7559c" + sha256: "32b36a9d87f18662bee0b1951b81f47a01f2bf28cd6ea94f60bc5453c7bf598c" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.1+4" injectable_generator: dependency: "direct dev" description: name: injectable_generator - sha256: af403d76c7b18b4217335e0075e950cd0579fd7f8d7bd47ee7c85ada31680ba1 + sha256: "09c55dba52b53d17411b90134a6751270b8930abd2529e2637d700fc99b0d5b5" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.8.1" intl: dependency: "direct main" description: @@ -825,10 +865,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.2" leak_tracker: dependency: transitive description: @@ -853,6 +893,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9 + url: "https://pub.dev" + source: hosted + version: "0.1.2" lints: dependency: transitive description: @@ -913,10 +961,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99" url: "https://pub.dev" source: hosted - version: "5.4.4" + version: "5.5.0" mocktail: dependency: "direct dev" description: @@ -957,6 +1005,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -1001,10 +1065,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" platform: dependency: transitive description: @@ -1045,6 +1109,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902" + url: "https://pub.dev" + source: hosted + version: "5.1.0" provider: dependency: "direct main" description: @@ -1081,18 +1153,18 @@ packages: dependency: "direct main" description: name: retrofit - sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe" + sha256: "0f629ed26b2c48c66fe54bd548313c6fdf7955be18bff37e08a46dd3f97f8eaf" url: "https://pub.dev" source: hosted - version: "4.9.1" + version: "4.9.2" retrofit_generator: dependency: "direct dev" description: name: retrofit_generator - sha256: "9499eb46b3657a62192ddbc208ff7e6c6b768b19e83c1ee6f6b119c864b99690" + sha256: "7ec323f3329ad2ca0bcdc96fe02ec7f2486ecfac6cd2d035b03c398ef6f42308" url: "https://pub.dev" source: hosted - version: "7.0.8" + version: "10.2.0" sanitize_html: dependency: transitive description: @@ -1185,10 +1257,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" shimmer: dependency: "direct main" description: @@ -1201,10 +1273,10 @@ packages: dependency: "direct main" description: name: skeletonizer - sha256: "83157d8e2e41f0252079cfec496281c16e4c63660052dab8d4cd72a206bb7109" + sha256: "9f38f9b47ec3cf2235a6a4f154a88a95432bc55ba98b3e2eb6ced5c1974bc122" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" sky_engine: dependency: transitive description: flutter @@ -1214,18 +1286,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "3.1.0" source_helper: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.8" source_map_stack_trace: dependency: transitive description: @@ -1330,14 +1402,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - tuple: - dependency: transitive - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" typed_data: dependency: transitive description: @@ -1438,10 +1502,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" + sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" url: "https://pub.dev" source: hosted - version: "1.1.20" + version: "1.2.0" vector_math: dependency: transitive description: @@ -1498,6 +1562,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: @@ -1514,6 +1586,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.dev" + source: hosted + version: "1.2.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2f2da4c..ee39d01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,8 +18,8 @@ dependencies: flutter_otp_text_field: ^1.5.1+1 flutter_svg: ^2.2.3 get_it: ^9.2.0 - go_router: ^13.2.0 - injectable: 2.7.0 + go_router: ^17.1.0 + injectable: ^2.7.1+4 intl: ^0.20.2 json_annotation: ^4.9.0 pretty_dio_logger: ^1.4.0 @@ -30,7 +30,7 @@ dependencies: skeletonizer: ^2.1.2 image_picker: ^1.2.1 google_maps_flutter: ^2.14.0 - geolocator: ^10.1.0 + geolocator: ^14.0.2 firebase_core: ^4.4.0 lottie: ^3.3.2 url_launcher: ^6.1.10 @@ -46,7 +46,7 @@ dev_dependencies: injectable_generator: ^2.4.1 json_serializable: ^6.8.0 mockito: ^5.4.4 - retrofit_generator: 7.0.8 + retrofit_generator: ^10.2.0 network_image_mock: ^2.1.1 mocktail: ^1.0.3 From 991e34444f0dcd51d4bd4ad895d9e37174aabadd Mon Sep 17 00:00:00 2001 From: mariam Date: Thu, 19 Feb 2026 01:56:09 +0200 Subject: [PATCH 05/10] fix(SCRUM-87): update ver --- pubspec.lock | 4 ++-- pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ef0fc6d..51b83f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1153,10 +1153,10 @@ packages: dependency: "direct main" description: name: retrofit - sha256: "0f629ed26b2c48c66fe54bd548313c6fdf7955be18bff37e08a46dd3f97f8eaf" + sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe" url: "https://pub.dev" source: hosted - version: "4.9.2" + version: "4.9.1" retrofit_generator: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index ee39d01..26b9146 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: json_annotation: ^4.9.0 pretty_dio_logger: ^1.4.0 provider: ^6.1.5+1 - retrofit: ^4.4.1 + retrofit: 4.9.1 shared_preferences: ^2.2.2 shimmer: ^3.0.0 skeletonizer: ^2.1.2 @@ -46,7 +46,7 @@ dev_dependencies: injectable_generator: ^2.4.1 json_serializable: ^6.8.0 mockito: ^5.4.4 - retrofit_generator: ^10.2.0 + retrofit_generator: 10.2.0 network_image_mock: ^2.1.1 mocktail: ^1.0.3 From 1c72f13bce377c8fc60b2d0dd6476f20e31a7ede Mon Sep 17 00:00:00 2001 From: mariam Date: Thu, 19 Feb 2026 05:48:28 +0200 Subject: [PATCH 06/10] refactor(SCRUM-87): refactor functions by using api result in return type --- .../order_details_remote_datasource_impl.dart | 20 +++++++++++++++-- .../order_details_remote_datasource.dart | 5 +++-- .../data/repos/order_details_repo_impl.dart | 22 +++++++++---------- .../domain/repos/order_details_repo.dart | 3 ++- .../usecases/get_order_details_usecase.dart | 4 +++- .../manager/order_details_cubit.dart | 18 ++++++++++++--- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart index 8490a51..7f68f45 100644 --- a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart +++ b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart @@ -1,6 +1,8 @@ import 'package:injectable/injectable.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; @Injectable(as: OrderDetailsRemoteDatasource) class OrderDetailsRemoteDatasourceImpl implements OrderDetailsRemoteDatasource { @@ -8,7 +10,21 @@ class OrderDetailsRemoteDatasourceImpl implements OrderDetailsRemoteDatasource { OrderDetailsRemoteDatasourceImpl({required this.firestore}); @override - Stream getOrderStream(String orderId) { - return firestore.collection('u8sj29sk2sff').doc(orderId).snapshots(); + ApiResult> getOrderStream(String orderId) { + try { + final stream = firestore + .collection('u8sj29sk2sff') + .doc(orderId) + .snapshots() + .map((snapshot) { + if (!snapshot.exists || snapshot.data() == null) { + throw Exception("Document does not exist!"); + } + return OrderDto.fromJson(snapshot.data()!); + }); + return SuccessApiResult>(data: stream); + } catch (e) { + return ErrorApiResult>(error: e.toString()); + } } } diff --git a/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart b/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart index 90bbf26..49bbd41 100644 --- a/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart +++ b/lib/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart @@ -1,5 +1,6 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; abstract class OrderDetailsRemoteDatasource { - Stream getOrderStream(String orderId); + ApiResult> getOrderStream(String orderId); } diff --git a/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart index 0d9288c..e5e0f3d 100644 --- a/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart +++ b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart @@ -1,4 +1,5 @@ import 'package:injectable/injectable.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; import 'package:tracking_app/features/driver_orders_details/data/mapper/order_dto_mapper.dart'; import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; @@ -11,17 +12,16 @@ class OrderDetailsRepoImpl implements OrderDetailsRepo { OrderDetailsRepoImpl(this.remoteDataSource); @override - Stream getOrderDetails(String orderId) { - return remoteDataSource.getOrderStream(orderId).map((snapshot) { - if (!snapshot.exists || snapshot.data() == null) { - throw Exception("Document does not exist in Firestore!"); - } - final Map data = Map.from( - snapshot.data() as Map, - ); + ApiResult> getOrderDetails(String orderId) { + final result = remoteDataSource.getOrderStream(orderId); - final orderDto = OrderDto.fromJson(data); - return orderDto.toOrderModel(); - }); + switch (result) { + case SuccessApiResult>(): + return SuccessApiResult>( + data: result.data.map((dto) => dto.toOrderModel()), + ); + case ErrorApiResult>(): + return ErrorApiResult>(error: result.error); + } } } diff --git a/lib/features/driver_orders_details/domain/repos/order_details_repo.dart b/lib/features/driver_orders_details/domain/repos/order_details_repo.dart index 52f7600..942beaa 100644 --- a/lib/features/driver_orders_details/domain/repos/order_details_repo.dart +++ b/lib/features/driver_orders_details/domain/repos/order_details_repo.dart @@ -1,5 +1,6 @@ +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; abstract class OrderDetailsRepo { - Stream getOrderDetails(String orderId); + ApiResult> getOrderDetails(String orderId); } diff --git a/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart index d4ba987..14168b1 100644 --- a/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart +++ b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart @@ -1,4 +1,5 @@ import 'package:injectable/injectable.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import 'package:tracking_app/features/driver_orders_details/domain/repos/order_details_repo.dart'; @@ -7,5 +8,6 @@ class GetOrderDetailsUsecase { OrderDetailsRepo repo; GetOrderDetailsUsecase({required this.repo}); - Stream call(String orderId) => repo.getOrderDetails(orderId); + ApiResult> call(String orderId) => + repo.getOrderDetails(orderId); } diff --git a/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart index e18366b..33eb919 100644 --- a/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart +++ b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; +import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import '../../domain/usecases/get_order_details_usecase.dart'; import 'order_details_states.dart'; @@ -15,10 +17,11 @@ class OrderDetailsCubit extends Cubit { void getOrderDetails(String orderId) async { emit(state.copyWith(data: Resource.loading())); _subscription?.cancel(); + final result = getOrderDetailsUsecase.call(orderId); - _subscription = getOrderDetailsUsecase - .call(orderId) - .listen( + switch (result) { + case SuccessApiResult>(): + _subscription = result.data.listen( (order) { emit(state.copyWith(data: Resource.success(order))); }, @@ -26,5 +29,14 @@ class OrderDetailsCubit extends Cubit { emit(state.copyWith(data: Resource.error(error.toString()))); }, ); + case ErrorApiResult>(error: final errorMessage): + emit(state.copyWith(data: Resource.error(errorMessage))); + } + } + + @override + Future close() { + _subscription?.cancel(); + return super.close(); } } From c0f790a54ad49371b6f0661b197717b11d46b4f1 Mon Sep 17 00:00:00 2001 From: mariam Date: Sun, 22 Feb 2026 00:04:59 +0200 Subject: [PATCH 07/10] feat(SCRUM-87): update models and mapper, refactor unit tests --- lib/app/config/di/di.config.dart | 33 +++ lib/app/core/values/paths.dart | 1 + .../order_details_remote_datasource_impl.dart | 7 +- .../data/mapper/order_dto_mapper.dart | 38 ++- .../data/models/orders_dto.dart | 163 ++++++++++--- .../domain/models/orders_model.dart | 89 ++++++- .../pages/drivers_orders_details_page.dart | 22 +- .../presentation/widgets/order_item.dart | 118 ++++++---- pubspec.lock | 120 ++++++++++ pubspec.yaml | 2 + ...r_details_remote_datasource_impl_test.dart | 64 ++++- .../data/mapper/order_dto_mapper_test.dart | 90 ++++++- .../data/models/orders_dto_test.dart | 222 ++++++++++++++---- .../repos/order_details_repo_impl_test.dart | 77 +++--- .../domain/models/orders_model_test.dart | 46 +++- .../get_order_details_usecase_test.dart | 45 ++-- .../manager/order_details_cubit_test.dart | 24 +- .../drivers_orders_details_page_test.dart | 19 +- 18 files changed, 929 insertions(+), 251 deletions(-) diff --git a/lib/app/config/di/di.config.dart b/lib/app/config/di/di.config.dart index 987afbb..b92ce85 100644 --- a/lib/app/config/di/di.config.dart +++ b/lib/app/config/di/di.config.dart @@ -1,3 +1,4 @@ +// dart format width=80 // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** @@ -8,6 +9,7 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:cloud_firestore/cloud_firestore.dart' as _i974; import 'package:dio/dio.dart' as _i361; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; @@ -48,6 +50,18 @@ import '../../../features/auth/presentation/reset_password/manager/reset_passwor as _i378; import '../../../features/auth/presentation/verify_reset/manger/cubit/verify_reset_cubit.dart' as _i466; +import '../../../features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart' + as _i860; +import '../../../features/driver_orders_details/data/datasource/order_details_remote_datasource.dart' + as _i114; +import '../../../features/driver_orders_details/data/repos/order_details_repo_impl.dart' + as _i55; +import '../../../features/driver_orders_details/domain/repos/order_details_repo.dart' + as _i313; +import '../../../features/driver_orders_details/domain/usecases/get_order_details_usecase.dart' + as _i1045; +import '../../../features/driver_orders_details/presentation/manager/order_details_cubit.dart' + as _i375; import '../../../features/profile/api/profile_lacal_datasource_imp.dart' as _i495; import '../../../features/profile/api/profile_remote_datasource_imp.dart' @@ -68,6 +82,7 @@ import '../../../features/profile/presentation/managers/profile_cubit.dart' as _i603; import '../../core/api_manger/api_client.dart' as _i890; import '../auth_storage/auth_storage.dart' as _i603; +import '../network/firebase_module.dart' as _i383; import '../network/network_module.dart' as _i200; extension GetItInjectableX on _i174.GetIt { @@ -77,9 +92,11 @@ extension GetItInjectableX on _i174.GetIt { _i526.EnvironmentFilter? environmentFilter, }) { final gh = _i526.GetItHelper(this, environment, environmentFilter); + final firebaseModule = _$FirebaseModule(); final networkModule = _$NetworkModule(); gh.factory<_i959.AppSectionCubit>(() => _i959.AppSectionCubit()); gh.lazySingleton<_i603.AuthStorage>(() => _i603.AuthStorage()); + gh.lazySingleton<_i974.FirebaseFirestore>(() => firebaseModule.firestore); gh.lazySingleton<_i783.CountryLocalDataSource>( () => _i783.CountryLocalDataSourceImpl(), ); @@ -89,9 +106,20 @@ extension GetItInjectableX on _i174.GetIt { gh.lazySingleton<_i697.ProfileLocalDataSource>( () => _i495.ProfileLocalDataSourceImpl(gh<_i603.AuthStorage>()), ); + gh.factory<_i114.OrderDetailsRemoteDatasource>( + () => _i860.OrderDetailsRemoteDatasourceImpl( + firestore: gh<_i974.FirebaseFirestore>(), + ), + ); gh.lazySingleton<_i890.ApiClient>( () => networkModule.authApiClient(gh<_i361.Dio>()), ); + gh.factory<_i313.OrderDetailsRepo>( + () => _i55.OrderDetailsRepoImpl(gh<_i114.OrderDetailsRemoteDatasource>()), + ); + gh.factory<_i1045.GetOrderDetailsUsecase>( + () => _i1045.GetOrderDetailsUsecase(repo: gh<_i313.OrderDetailsRepo>()), + ); gh.factory<_i943.ProfileRemoteDatasource>( () => _i899.ProfileRemoteDatasourceImp(gh<_i890.ApiClient>()), ); @@ -142,6 +170,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i75.LoginUseCase>( () => _i75.LoginUseCase(gh<_i712.AuthRepo>()), ); + gh.factory<_i375.OrderDetailsCubit>( + () => _i375.OrderDetailsCubit(gh<_i1045.GetOrderDetailsUsecase>()), + ); gh.factory<_i14.ChangePasswordCubit>( () => _i14.ChangePasswordCubit( gh<_i991.ChangePasswordUsecase>(), @@ -185,4 +216,6 @@ extension GetItInjectableX on _i174.GetIt { } } +class _$FirebaseModule extends _i383.FirebaseModule {} + class _$NetworkModule extends _i200.NetworkModule {} diff --git a/lib/app/core/values/paths.dart b/lib/app/core/values/paths.dart index 8ef55d1..26ff989 100644 --- a/lib/app/core/values/paths.dart +++ b/lib/app/core/values/paths.dart @@ -6,4 +6,5 @@ class AppPaths { static const String onboardingImage = 'assets/images/Clip path group.png'; static const String whatsappImage = 'assets/images/whatsapp.png'; static const String flowerLogo = 'assets/images/flower_logo.png'; + static const String mediaUrl = 'https://flower.elevateegy.com/uploads/'; } diff --git a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart index 7f68f45..63f2c7f 100644 --- a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart +++ b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart @@ -13,14 +13,17 @@ class OrderDetailsRemoteDatasourceImpl implements OrderDetailsRemoteDatasource { ApiResult> getOrderStream(String orderId) { try { final stream = firestore - .collection('u8sj29sk2sff') + .collection('orders') .doc(orderId) .snapshots() .map((snapshot) { if (!snapshot.exists || snapshot.data() == null) { throw Exception("Document does not exist!"); } - return OrderDto.fromJson(snapshot.data()!); + return OrderDto.fromJson( + snapshot.data() as Map, + snapshot.id, + ); }); return SuccessApiResult>(data: stream); } catch (e) { diff --git a/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart b/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart index 40ea288..ab50afe 100644 --- a/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart +++ b/lib/features/driver_orders_details/data/mapper/order_dto_mapper.dart @@ -5,17 +5,47 @@ extension OrderDtoMapper on OrderDto { OrderModel toOrderModel() { return OrderModel( driverId: driverId, - id: id, - status: status, - totalPrice: totalPrice, + orderId: orderId, userAddress: userAddress.toUserAddressModel(), userId: userId, + orderDetails: orderDetails.toOrderDetailsModel(), + ); + } +} + +extension OrderDetailsDtoMapper on OrderDetailsDto { + OrderDetailsModel toOrderDetailsModel() { + return OrderDetailsModel( + items: items.map((i) => i.toOrderItemModel()).toList(), + status: status, + totalPrice: totalPrice, + pickupAddress: pickupAddress.toPickedAddressModel(), + orderId: orderId, + userAddress: userAddress, ); } } +extension OrderItemDtoMapper on OrderItemDto { + OrderItemModel toOrderItemModel() { + return OrderItemModel( + productId: productId, + title: title, + image: image, + quantity: quantity, + price: price, + ); + } +} + +extension PickedAddressDtoMapper on PickedAddressDto { + PickedAddressModel toPickedAddressModel() { + return PickedAddressModel(name: name, address: address); + } +} + extension UserAddressDtoMapper on UserAddressDto { UserAddressModel toUserAddressModel() { - return UserAddressModel(address: address, name: name); + return UserAddressModel(name: name, address: address, userId: userId); } } diff --git a/lib/features/driver_orders_details/data/models/orders_dto.dart b/lib/features/driver_orders_details/data/models/orders_dto.dart index ea6d37f..0b14faf 100644 --- a/lib/features/driver_orders_details/data/models/orders_dto.dart +++ b/lib/features/driver_orders_details/data/models/orders_dto.dart @@ -1,61 +1,154 @@ -class UserAddressDto { - final String address; - final String name; +class OrderDto { + final String orderId; + final String driverId; + final String userId; + final OrderDetailsDto orderDetails; + final UserAddressDto userAddress; - UserAddressDto({required this.address, required this.name}); + OrderDto({ + required this.orderId, + required this.driverId, + required this.userId, + required this.orderDetails, + required this.userAddress, + }); - factory UserAddressDto.fromJson(Map json) { - return UserAddressDto( - address: json['address'].toString(), - name: json['name'].toString(), + factory OrderDto.fromJson(Map json, String id) { + return OrderDto( + orderId: id, + driverId: json['driver_id'] ?? '', + userId: json['user_id'] ?? '', + orderDetails: OrderDetailsDto.fromJson(json['oder_dt'] ?? {}), + userAddress: UserAddressDto.fromJson(json['userAddress'] ?? {}), ); } Map toJson() { - return {'address': address, 'name': name}; + return { + 'driver_id': driverId, + 'user_id': userId, + 'oder_dt': (orderDetails).toJson(), + 'userAddress': (userAddress).toJson(), + }; } } -class OrderDto { - final String driverId; - final String id; +class OrderDetailsDto { + final List items; final String status; - final String totalPrice; - final UserAddressDto userAddress; - final String userId; + final double totalPrice; + final PickedAddressDto pickupAddress; + final String orderId; + final String userAddress; - OrderDto({ - required this.driverId, - required this.id, + OrderDetailsDto({ + required this.items, required this.status, required this.totalPrice, + required this.pickupAddress, + required this.orderId, required this.userAddress, - required this.userId, }); - factory OrderDto.fromJson(Map json) { - return OrderDto( - driverId: json['driverId'].toString(), - id: json['id'].toString(), - status: json['status'].toString(), - totalPrice: json['totalPrice'].toString(), - userAddress: json['userAddress '] != null - ? UserAddressDto.fromJson( - Map.from(json['userAddress ']), - ) - : UserAddressDto(address: 'No Address', name: 'No Name'), - userId: json['userId'].toString(), + factory OrderDetailsDto.fromJson(Map json) { + return OrderDetailsDto( + status: json['status'] ?? '', + totalPrice: (json['totalPrice'] ?? 0).toDouble(), + pickupAddress: PickedAddressDto.fromJson(json['pickupAddress'] ?? {}), + items: (json['items'] as List? ?? []) + .map((i) => OrderItemDto.fromJson(i)) + .toList(), + orderId: json['orderId'] ?? '', + userAddress: json['userAddress'] ?? '', ); } Map toJson() { return { - 'driverId': driverId, - 'id': id, 'status': status, 'totalPrice': totalPrice, - 'userAddress': userAddress.toJson(), - 'userId': userId, + 'pickupAddress': (pickupAddress).toJson(), + 'items': items.map((i) => (i).toJson()).toList(), + 'orderId': orderId, + 'userAddress': userAddress, }; } } + +class OrderItemDto { + final String productId; + final String title; + final String image; + final int quantity; + final double price; + + OrderItemDto({ + required this.productId, + required this.title, + required this.image, + required this.quantity, + required this.price, + }); + + factory OrderItemDto.fromJson(Map json) { + return OrderItemDto( + productId: json['productId'] ?? '', + title: json['title'] ?? '', + image: json['image'] ?? '', + quantity: json['quantity'] ?? 0, + price: (json['price'] ?? 0).toDouble(), + ); + } + + Map toJson() { + return { + 'productId': productId, + 'title': title, + 'image': image, + 'quantity': quantity, + 'price': price, + }; + } +} + +class PickedAddressDto { + final String name; + final String address; + + PickedAddressDto({required this.name, required this.address}); + + factory PickedAddressDto.fromJson(Map json) { + return PickedAddressDto( + name: json['name'] ?? '', + address: json['address'] ?? '', + ); + } + + Map toJson() { + return {'name': name, 'address': address}; + } +} + +class UserAddressDto { + final String name; + final String address; + final String userId; + + UserAddressDto({ + required this.name, + required this.address, + required this.userId, + }); + + factory UserAddressDto.fromJson(Map json) { + return UserAddressDto( + name: json['name'] ?? '', + address: json['adress'] ?? '', + userId: json['user_id'] ?? '', + ); + } + + Map toJson() { + return {'name': name, 'adress': address, 'user_id': userId}; + } +} diff --git a/lib/features/driver_orders_details/domain/models/orders_model.dart b/lib/features/driver_orders_details/domain/models/orders_model.dart index dc894bb..add398f 100644 --- a/lib/features/driver_orders_details/domain/models/orders_model.dart +++ b/lib/features/driver_orders_details/domain/models/orders_model.dart @@ -1,24 +1,93 @@ -class UserAddressModel { - final String address; - final String name; +// class UserAddressModel { +// final String address; +// final String name; - UserAddressModel({required this.address, required this.name}); -} +// UserAddressModel({required this.address, required this.name}); +// } + +// class OrderModel { +// final String driverId; +// final String id; +// final String status; +// final String totalPrice; +// final UserAddressModel userAddress; +// final String userId; + +// OrderModel({ +// required this.driverId, +// required this.id, +// required this.status, +// required this.totalPrice, +// required this.userAddress, +// required this.userId, +// }); +// } class OrderModel { + final String orderId; final String driverId; - final String id; - final String status; - final String totalPrice; - final UserAddressModel userAddress; final String userId; + final OrderDetailsModel orderDetails; + final UserAddressModel userAddress; OrderModel({ + required this.orderId, required this.driverId, - required this.id, + required this.userId, + required this.orderDetails, + required this.userAddress, + }); +} + +class OrderDetailsModel { + final List items; + final String status; + final double totalPrice; + final PickedAddressModel pickupAddress; + final String orderId; + final String userAddress; + + OrderDetailsModel({ + required this.items, required this.status, required this.totalPrice, + required this.pickupAddress, + required this.orderId, required this.userAddress, + }); +} + +class OrderItemModel { + final String productId; + final String title; + final String image; + final int quantity; + final double price; + + OrderItemModel({ + required this.productId, + required this.title, + required this.image, + required this.quantity, + required this.price, + }); +} + +class PickedAddressModel { + final String name; + final String address; + + PickedAddressModel({required this.name, required this.address}); +} + +class UserAddressModel { + final String userId; + final String name; + final String address; + + UserAddressModel({ + required this.name, + required this.address, required this.userId, }); } diff --git a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart index bc676e5..210bc2e 100644 --- a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart +++ b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart @@ -36,7 +36,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), ), body: BlocProvider( - create: (context) => cubit..getOrderDetails('pxkMaEmWYVuvV5jkW0JK'), + create: (context) => cubit..getOrderDetails('696ae30ce364ef61404760df'), child: BlocBuilder( builder: (context, state) { if (state.data?.status == Status.loading) { @@ -52,7 +52,9 @@ class DriversOrdersDetailsPage extends StatelessWidget { children: [ Row( children: List.generate(5, (index) { - int currentStep = _getStepCount(order!.status); + int currentStep = _getStepCount( + order!.orderDetails.status, + ); return Expanded( child: Container( height: 4, @@ -80,7 +82,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${LocaleKeys.status.tr()}${order!.status}', + '${LocaleKeys.status.tr()}${order!.orderDetails.status}', style: TextStyle( color: AppColors.green, fontWeight: FontWeight.bold, @@ -89,7 +91,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), const SizedBox(height: 4), Text( - '${LocaleKeys.orderId.tr()}${order.id}', + '${LocaleKeys.orderId.tr()}${order.orderId}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, @@ -110,8 +112,8 @@ class DriversOrdersDetailsPage extends StatelessWidget { SectionTitle(title: LocaleKeys.pickupAddress.tr()), AddressCard( - title: LocaleKeys.floweryStore.tr(), - address: order.userAddress.address, + title: order.orderDetails.pickupAddress.name, + address: order.orderDetails.pickupAddress.address, imagePath: AppPaths.flowerLogo, ), const SizedBox(height: 16), @@ -125,13 +127,13 @@ class DriversOrdersDetailsPage extends StatelessWidget { const SizedBox(height: 24), SectionTitle(title: LocaleKeys.orderDetails.tr()), - OrderItem(), - OrderItem(), + OrderItems(), const SizedBox(height: 16), BottomRowSection( label: LocaleKeys.total.tr(), - value: '${LocaleKeys.egp.tr()} ${order.totalPrice}', + value: + '${LocaleKeys.egp.tr()} ${order.orderDetails.totalPrice.toStringAsFixed(2)}', ), BottomRowSection( label: LocaleKeys.payment_method.tr(), @@ -147,7 +149,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { isEnabled: true, onPressed: () {}, isLoading: false, - text: _getButtonText(order.status), + text: _getButtonText(order.orderDetails.status), ), ), ], diff --git a/lib/features/driver_orders_details/presentation/widgets/order_item.dart b/lib/features/driver_orders_details/presentation/widgets/order_item.dart index 1fbefbc..1d6cebc 100644 --- a/lib/features/driver_orders_details/presentation/widgets/order_item.dart +++ b/lib/features/driver_orders_details/presentation/widgets/order_item.dart @@ -1,61 +1,85 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shimmer/shimmer.dart'; import 'package:tracking_app/app/core/ui_helper/color/colors.dart'; import 'package:tracking_app/app/core/values/paths.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; -class OrderItem extends StatelessWidget { - const OrderItem({super.key}); +class OrderItems extends StatelessWidget { + const OrderItems({super.key}); @override Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: Border.all(color: AppColors.lightGrey), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.asset( - AppPaths.flowerLogo, - width: 50, - height: 50, - fit: BoxFit.cover, - ), + final order = BlocProvider.of(context).state.data!.data; + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: order?.orderDetails.items.length, + itemBuilder: (context, index) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: AppColors.lightGrey), + borderRadius: BorderRadius.circular(12), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Red roses, 15 Pink Rose Bouquet', - overflow: TextOverflow.ellipsis, - style: Theme.of( - context, - ).textTheme.labelSmall!.copyWith(fontWeight: FontWeight.w400), - ), - Text( - 'EGP 600', - style: Theme.of(context).textTheme.labelSmall!.copyWith( - fontWeight: FontWeight.w500, - color: AppColors.blackColor, + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(50), + child: CachedNetworkImage( + imageUrl: + "${AppPaths.mediaUrl}${order!.orderDetails.items[index].image}", + placeholder: (context, url) => Shimmer( + gradient: LinearGradient( + colors: [ + AppColors.lightGrey, + AppColors.white, + AppColors.lightGrey, + ], + ), + child: const CircularProgressIndicator(), ), + errorWidget: (context, url, error) => Icon(Icons.error), + width: 55, + height: 55, + fit: BoxFit.cover, ), - ], - ), - ), - Text( - 'X1', - style: Theme.of(context).textTheme.labelSmall!.copyWith( - fontWeight: FontWeight.w500, - color: AppColors.pink, - ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order.orderDetails.items[index].title, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w400, + ), + ), + Text( + 'EGP ${order.orderDetails.items[index].price.toStringAsFixed(2)}', + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.blackColor, + ), + ), + ], + ), + ), + Text( + 'X${order.orderDetails.items[index].quantity}', + style: Theme.of(context).textTheme.labelSmall!.copyWith( + fontWeight: FontWeight.w500, + color: AppColors.pink, + ), + ), + ], ), - ], - ), + ); + }, ); } } diff --git a/pubspec.lock b/pubspec.lock index 51b83f8..72c0e85 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,6 +145,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.12.3" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -470,6 +494,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_lints: dependency: "direct dev" description: @@ -997,6 +1029,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -1037,6 +1077,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider: + dependency: transitive + 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: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -1165,6 +1229,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.2.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" sanitize_html: dependency: transitive description: @@ -1322,6 +1394,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -1354,6 +1466,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 26b9146..4841c67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: flutter_local_notifications: ^20.0.0 firebase_crashlytics: ^5.0.7 cloud_firestore: ^6.1.2 + cached_network_image: ^3.3.1 + dev_dependencies: bloc_test: ^10.0.0 diff --git a/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart b/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart index 812e1d1..266aaab 100644 --- a/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart +++ b/test/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl_test.dart @@ -2,7 +2,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; import 'order_details_remote_datasource_impl_test.mocks.dart'; @GenerateMocks([ @@ -19,7 +21,6 @@ void main() { late MockDocumentSnapshot> mockSnapshot; const String tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; - const String tCollectionName = 'u8sj29sk2sff'; setUp(() { mockFirestore = MockFirebaseFirestore(); @@ -29,19 +30,60 @@ void main() { dataSource = OrderDetailsRemoteDatasourceImpl(firestore: mockFirestore); }); + group('getOrderStream', () { + final tOrderJson = { + 'driver_id': '1', + 'user_id': 'U11', + 'userAddress': {'name': 'mariam', 'address': 'alex', 'userId': 'U11'}, + 'oder_dt': { + 'items': [], + 'status': 'accepted', + 'totalPrice': 500.0, + 'orderId': tOrderId, + 'userAddress': 'alex', + 'pickupAddress': {'name': 'mariam', 'address': 'alex'}, + }, + }; - test('return stream from documentSnapshot when call getOrderStream', () { - when(mockFirestore.collection(tCollectionName)).thenReturn(mockCollection); - when(mockCollection.doc(tOrderId)).thenReturn(mockDocument); - when( - mockDocument.snapshots(), - ).thenAnswer((_) => Stream.value(mockSnapshot)); + test('should return SuccessApiResult with Stream of OrderDto', () async { + when(mockFirestore.collection('orders')).thenReturn(mockCollection); + when(mockCollection.doc(tOrderId)).thenReturn(mockDocument); - final result = dataSource.getOrderStream(tOrderId); + when(mockSnapshot.exists).thenReturn(true); + when(mockSnapshot.data()).thenReturn(tOrderJson); + when(mockSnapshot.id).thenReturn(tOrderId); - expect(result, emits(mockSnapshot)); + when( + mockDocument.snapshots(), + ).thenAnswer((_) => Stream.value(mockSnapshot)); - verify(mockFirestore.collection(tCollectionName)).called(1); - verify(mockCollection.doc(tOrderId)).called(1); + final result = dataSource.getOrderStream(tOrderId); + + expect(result, isA>>()); + final stream = (result as SuccessApiResult>).data; + await expectLater( + stream, + emits( + isA() + .having((o) => o.orderId, 'orderId', tOrderId) + .having((o) => o.orderDetails.status, 'status', 'accepted'), + ), + ); + }); + + test('should return ErrorApiResult when document does not exist', () async { + when(mockFirestore.collection(any)).thenReturn(mockCollection); + when(mockCollection.doc(any)).thenReturn(mockDocument); + when(mockSnapshot.exists).thenReturn(false); + when( + mockDocument.snapshots(), + ).thenAnswer((_) => Stream.value(mockSnapshot)); + + final result = dataSource.getOrderStream(tOrderId); + + expect(result, isA>>()); + final stream = (result as SuccessApiResult>).data; + await expectLater(stream, emitsError(isA())); + }); }); } diff --git a/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart b/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart index d7683b0..d11f68f 100644 --- a/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart +++ b/test/features/driver_orders_details/data/mapper/order_dto_mapper_test.dart @@ -6,31 +6,105 @@ import 'package:tracking_app/features/driver_orders_details/domain/models/orders void main() { group('OrderDtoMapper', () { test('Convert OrderDto to OrderModel correctly', () { - final tUserAddressDto = UserAddressDto(address: 'Alex', name: 'Mariam'); + final tUserAddressDto = UserAddressDto( + address: 'Alex', + name: 'Mariam', + userId: 'U123', + ); final tOrderDto = OrderDto( driverId: 'D123', - id: 'O456', - status: 'accepted', - totalPrice: '150.0', userAddress: tUserAddressDto, userId: 'U789', + orderId: '22', + orderDetails: OrderDetailsDto( + items: [], + status: 'pending', + totalPrice: 500, + pickupAddress: PickedAddressDto( + name: 'Store', + address: '123 Main St', + ), + orderId: '22', + userAddress: 'alex', + ), ); final result = tOrderDto.toOrderModel(); expect(result, isA()); - expect(result.id, tOrderDto.id); - expect(result.status, tOrderDto.status); - expect(result.totalPrice, tOrderDto.totalPrice); + expect(result.driverId, tOrderDto.driverId); expect(result.userAddress.name, tOrderDto.userAddress.name); expect(result.userAddress.address, tOrderDto.userAddress.address); + expect(result.userAddress.userId, tOrderDto.userAddress.userId); + expect(result.userId, tOrderDto.userId); + }); + }); + + group('OrderDetailsDtoMapper', () { + test('Convert OrderDetailsDto to OrderDetailsModel correctly', () { + final tpickupAddressDto = PickedAddressDto( + name: 'Store', + address: '123 Main St', + ); + final tDto = OrderDetailsDto( + items: [], + status: 'pending', + totalPrice: 500, + pickupAddress: tpickupAddressDto, + orderId: '1', + userAddress: 'alex', + ); + + final result = tDto.toOrderDetailsModel(); + + expect(result, isA()); + expect(result.items, tDto.items); + expect(result.status, tDto.status); + expect(result.totalPrice, tDto.totalPrice); + expect(result.pickupAddress.name, tDto.pickupAddress.name); + expect(result.orderId, tDto.orderId); + }); + }); + + group('OrderItemDtoMapper', () { + test('Convert OrderItemDto to OrderItemModel correctly', () { + final tDto = OrderItemDto( + productId: '1', + title: 'Item 1', + price: 100, + quantity: 2, + image: 'image_url', + ); + + final result = tDto.toOrderItemModel(); + + expect(result.productId, tDto.productId); + expect(result.title, tDto.title); + expect(result.price, tDto.price); + expect(result.quantity, tDto.quantity); + expect(result.image, tDto.image); + }); + }); + + group('PickedAddressDtoMapper', () { + test('Convert PickedAddressDto to PickedAddressModel correctly', () { + final tDto = PickedAddressDto(name: 'Store', address: '123 Main St'); + + final result = tDto.toPickedAddressModel(); + + expect(result.name, tDto.name); + expect(result.address, tDto.address); }); }); group('UserAddressDtoMapper', () { test('Convert UserAddressDto to UserAddressModel correctly', () { - final tDto = UserAddressDto(address: 'Alex', name: 'Mariam'); + final tDto = UserAddressDto( + name: 'Store', + address: '123 Main St', + userId: 'U123', + ); final result = tDto.toUserAddressModel(); diff --git a/test/features/driver_orders_details/data/models/orders_dto_test.dart b/test/features/driver_orders_details/data/models/orders_dto_test.dart index 8bfa40e..6206376 100644 --- a/test/features/driver_orders_details/data/models/orders_dto_test.dart +++ b/test/features/driver_orders_details/data/models/orders_dto_test.dart @@ -4,16 +4,46 @@ import 'package:tracking_app/features/driver_orders_details/data/models/orders_d void main() { group('UserAddressDto Tests', () { test('should return a valid UserAddressDto from JSON', () { - final Map json = {'address': 'Alex', 'name': 'Mariam'}; + final Map json = { + 'adress': 'Alex', + 'name': 'Mariam', + 'user_id': 'U123', + }; final result = UserAddressDto.fromJson(json); expect(result.address, 'Alex'); expect(result.name, 'Mariam'); + expect(result.userId, 'U123'); }); test('should return a valid JSON map from UserAddressDto', () { - final dto = UserAddressDto(address: 'Alex', name: 'Mariam'); + final dto = UserAddressDto( + address: 'Alex', + name: 'Mariam', + userId: 'U123', + ); + + final result = dto.toJson(); + + expect(result['adress'], 'Alex'); + expect(result['name'], 'Mariam'); + expect(result['user_id'], 'U123'); + }); + }); + + group('PickedAddressDto Tests', () { + test('should return a valid PickedAddressDto from JSON', () { + final Map json = {'address': 'Alex', 'name': 'Mariam'}; + + final result = PickedAddressDto.fromJson(json); + + expect(result.address, 'Alex'); + expect(result.name, 'Mariam'); + }); + + test('should return a valid JSON map from PickedAddressDto', () { + final dto = PickedAddressDto(address: 'Alex', name: 'Mariam'); final result = dto.toJson(); @@ -22,60 +52,162 @@ void main() { }); }); - group('OrderDto Tests', () { - final Map tOrderJsonWithSpace = { - 'driverId': 'D123', - 'id': 'O456', - 'status': 'accepted', - 'totalPrice': '150.0', - 'userAddress ': {'address': 'Alex', 'name': 'Mariam'}, - 'userId': 'U789', - }; + group('OrderItemDto Tests', () { + test('should return a valid OrderItemDto from JSON', () { + final Map json = { + 'productId': '1', + 'title': 'red flower', + 'image': 'url', + 'quantity': 1, + 'price': 100, + }; + + final result = OrderItemDto.fromJson(json); + + expect(result.image, 'url'); + expect(result.title, 'red flower'); + expect(result.quantity, 1); + expect(result.price, 100); + }); + + test('should return a valid JSON map from OrderItemDto', () { + final dto = OrderItemDto( + image: 'Alex', + productId: '1', + title: 'red flower', + quantity: 1, + price: 100, + ); - test( - 'should correctly parse userAddress when key has a trailing space', - () { - final result = OrderDto.fromJson(tOrderJsonWithSpace); + final result = dto.toJson(); + + expect(result['image'], 'Alex'); + expect(result['title'], 'red flower'); + expect(result['quantity'], 1); + expect(result['price'], 100); + }); + }); + + group('OrderDetailsDto Tests', () { + test('should return a valid OrderDetailsDto from JSON', () { + final Map json = { + 'items': [], + 'status': 'accepted', + 'totalPrice': 100.0, + 'pickupAddress': {'name': 'Mariam', 'address': 'Alex'}, + 'orderId': 'O456', + 'userAddress': 'alex', + }; + + final result = OrderDetailsDto.fromJson(json); + + expect(result.status, 'accepted'); + expect(result.totalPrice, 100.0); + expect(result.orderId, 'O456'); + }); + + test('should return a valid JSON map from OrderDetailsDto', () { + final dto = OrderDetailsDto( + items: [ + OrderItemDto( + image: 'url', + productId: '1', + title: 'red flower', + quantity: 1, + price: 100, + ), + ], + status: 'accepted', + totalPrice: 100.0, + pickupAddress: PickedAddressDto(address: 'Alex', name: 'Mariam'), + orderId: 'O456', + userAddress: 'alex', + ); + + final result = dto.toJson(); - expect(result.userAddress.name, 'Mariam'); - expect(result.userAddress.address, 'Alex'); - expect(result.id, 'O456'); + expect(result['status'], 'accepted'); + expect(result['totalPrice'], 100.0); + final firstItem = result['items'][0]; + expect(firstItem['image'], 'url'); + expect(firstItem['title'], 'red flower'); + expect(firstItem['price'], 100.0); + expect(result['pickupAddress']['name'], 'Mariam'); + }); + }); + + group('OrderDto Tests', () { + final Map tOrderJson = { + 'driver_id': 'D123', + 'user_id': 'U789', + 'userAddress': { + 'name': 'Home', + 'address': 'Cairo, Egypt', + 'userId': 'U789', }, - ); - - test( - 'should return default address values when userAddress key is missing or null', - () { - final Map jsonMissingAddress = { - 'driverId': 'D123', - 'id': 'O456', - 'status': 'accepted', - 'totalPrice': '150.0', - 'userId': 'U789', - }; - - final result = OrderDto.fromJson(jsonMissingAddress); - - expect(result.userAddress.name, 'No Name'); - expect(result.userAddress.address, 'No Address'); + 'oder_dt': { + 'status': 'processing', + 'totalPrice': 250.0, + 'orderId': 'O100', + 'userAddress': 'Cairo, Egypt', + 'pickupAddress': {'name': 'Pharmacy', 'address': 'Downtown'}, + 'items': [ + { + 'productId': 'p1', + 'title': 'Panadol', + 'image': 'panadol.png', + 'quantity': 2, + 'price': 125.0, + }, + ], }, - ); + }; + + const String tOrderId = 'O100'; + + test('should return a valid OrderDto from JSON and ID', () { + final result = OrderDto.fromJson(tOrderJson, tOrderId); + + expect(result.orderId, tOrderId); + expect(result.driverId, 'D123'); + expect(result.userId, 'U789'); + expect(result.userAddress, isA()); + expect(result.userAddress.name, 'Home'); + + expect(result.orderDetails, isA()); + expect(result.orderDetails.status, 'processing'); + expect(result.orderDetails.items.length, 1); + expect(result.orderDetails.items[0].title, 'Panadol'); + }); test('should return a valid JSON map from OrderDto', () { final dto = OrderDto( - driverId: 'D1', - id: 'O1', - status: 'pickup', - totalPrice: '100', - userAddress: UserAddressDto(address: 'Alex', name: 'Mariam'), - userId: 'U1', + orderId: tOrderId, + driverId: 'D123', + userId: 'U789', + userAddress: UserAddressDto( + name: 'Home', + address: 'Cairo', + userId: 'U789', + ), + orderDetails: OrderDetailsDto( + items: [], + status: 'pending', + totalPrice: 0.0, + pickupAddress: PickedAddressDto(name: 'Store', address: 'Street'), + orderId: tOrderId, + userAddress: 'Cairo', + ), ); final result = dto.toJson(); - expect(result['driverId'], 'D1'); - expect(result['userAddress']['name'], 'Mariam'); - expect(result.containsKey('userAddress'), true); + expect(result['driver_id'], 'D123'); + expect(result['user_id'], 'U789'); + + expect(result['userAddress'], isA>()); + expect(result['oder_dt'], isA>()); + expect(result['oder_dt']['status'], 'pending'); }); }); } diff --git a/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart b/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart index 79b3234..b10ab3e 100644 --- a/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart +++ b/test/features/driver_orders_details/data/repos/order_details_repo_impl_test.dart @@ -2,7 +2,9 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/data/datasource/order_details_remote_datasource.dart'; +import 'package:tracking_app/features/driver_orders_details/data/models/orders_dto.dart'; import 'package:tracking_app/features/driver_orders_details/data/repos/order_details_repo_impl.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import 'order_details_repo_impl_test.mocks.dart'; @@ -11,43 +13,60 @@ import 'order_details_repo_impl_test.mocks.dart'; void main() { late OrderDetailsRepoImpl repository; late MockOrderDetailsRemoteDatasource mockRemoteDataSource; - late MockDocumentSnapshot mockSnapshot; setUp(() { mockRemoteDataSource = MockOrderDetailsRemoteDatasource(); - mockSnapshot = MockDocumentSnapshot(); repository = OrderDetailsRepoImpl(mockRemoteDataSource); + provideDummy>>( + ErrorApiResult(error: 'dummy_error'), + ); }); const tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; - final tOrderData = { - 'driverId': 'D123', - 'id': 'O456', - 'status': 'accepted', - 'totalPrice': '150.0', - 'userAddress ': {'address': 'Alex', 'name': 'Mariam'}, - 'userId': 'U789', - }; + final tOrderDto = OrderDto( + driverId: 'D123', + userAddress: UserAddressDto( + address: 'Alex', + name: 'Mariam', + userId: 'U123', + ), + userId: 'U789', + orderId: tOrderId, + orderDetails: OrderDetailsDto( + items: [], + status: 'accepted', + totalPrice: 150.0, + pickupAddress: PickedAddressDto(name: 'Pharmacy', address: 'Downtown'), + orderId: tOrderId, + userAddress: 'Alex', + ), + ); group('getOrderDetails', () { test( - 'should emit OrderModel when the remote data source returns a valid DocumentSnapshot', + 'should emit OrderModel when the remote data source returns SuccessApiResult with Stream', () async { - when(mockSnapshot.exists).thenReturn(true); - when(mockSnapshot.data()).thenReturn(tOrderData); when( mockRemoteDataSource.getOrderStream(tOrderId), - ).thenAnswer((_) => Stream.value(mockSnapshot)); + ).thenReturn(SuccessApiResult(data: Stream.value(tOrderDto))); final result = repository.getOrderDetails(tOrderId); - expect( - result, + expect(result, isA>>()); + final stream = (result as SuccessApiResult>).data; + await expectLater( + stream, emits( isA() - .having((o) => o.id, 'order id', 'O456') - .having((o) => o.userAddress.name, 'user name', 'Mariam'), + .having((o) => o.orderId, 'order id', tOrderId) + .having((o) => o.userAddress.name, 'user name', 'Mariam') + .having( + (o) => o.orderDetails.status, + 'order status', + 'accepted', + ) + .having((o) => o.orderDetails.totalPrice, 'total price', 150.0), ), ); }, @@ -56,29 +75,15 @@ void main() { test( 'should throw an Exception when the document does not exist', () async { - when(mockSnapshot.exists).thenReturn(false); - when( - mockRemoteDataSource.getOrderStream(tOrderId), - ).thenAnswer((_) => Stream.value(mockSnapshot)); - - final result = repository.getOrderDetails(tOrderId); - - expect(result, emitsError(isA())); - }, - ); - - test( - 'should throw an Exception when data is null even if snapshot exists', - () async { - when(mockSnapshot.exists).thenReturn(true); - when(mockSnapshot.data()).thenReturn(null); + const errorMessage = "Network Error"; when( mockRemoteDataSource.getOrderStream(tOrderId), - ).thenAnswer((_) => Stream.value(mockSnapshot)); + ).thenReturn(ErrorApiResult(error: errorMessage)); final result = repository.getOrderDetails(tOrderId); - expect(result, emitsError(isA())); + expect(result, isA>>()); + expect((result as ErrorApiResult).error, errorMessage); }, ); }); diff --git a/test/features/driver_orders_details/domain/models/orders_model_test.dart b/test/features/driver_orders_details/domain/models/orders_model_test.dart index 083c50c..b4f986d 100644 --- a/test/features/driver_orders_details/domain/models/orders_model_test.dart +++ b/test/features/driver_orders_details/domain/models/orders_model_test.dart @@ -4,28 +4,46 @@ import 'package:tracking_app/features/driver_orders_details/domain/models/orders void main() { group('OrderModel & UserAddressModel Tests', () { test('should correctly initialize UserAddressModel with given values', () { - final tAddress = UserAddressModel(address: 'Cairo', name: 'Mohamed'); + final tAddress = UserAddressModel( + address: 'Cairo', + name: 'Mohamed', + userId: '1', + ); expect(tAddress.address, 'Cairo'); expect(tAddress.name, 'Mohamed'); + expect(tAddress.userId, '1'); }); test('should correctly initialize OrderModel with given values', () { - final tUserAddress = UserAddressModel(address: 'Cairo', name: 'Mohamed'); + final tUserAddress = UserAddressModel( + address: 'Cairo', + name: 'Mohamed', + userId: 'USR-555', + ); final tOrder = OrderModel( driverId: 'DRV-101', - id: 'ORD-999', - status: 'picked_up', - totalPrice: '250.50', userAddress: tUserAddress, userId: 'USR-555', + orderId: 'ORD-999', + orderDetails: OrderDetailsModel( + items: [], + status: 'picked_up', + totalPrice: 250, + pickupAddress: PickedAddressModel( + name: 'Pharmacy', + address: 'Downtown', + ), + orderId: 'ORD-999', + userAddress: 'Cairo', + ), ); expect(tOrder.driverId, 'DRV-101'); - expect(tOrder.id, 'ORD-999'); - expect(tOrder.status, 'picked_up'); - expect(tOrder.totalPrice, '250.50'); + expect(tOrder.orderId, 'ORD-999'); + expect(tOrder.orderDetails.status, 'picked_up'); + expect(tOrder.orderDetails.totalPrice, 250); expect(tOrder.userId, 'USR-555'); expect(tOrder.userAddress, isA()); @@ -33,8 +51,16 @@ void main() { }); test('should support equality check if needed (Optional)', () { - final address1 = UserAddressModel(address: 'A', name: 'B'); - final address2 = UserAddressModel(address: 'A', name: 'B'); + final address1 = UserAddressModel( + address: 'A', + name: 'B', + userId: 'USR-123', + ); + final address2 = UserAddressModel( + address: 'A', + name: 'B', + userId: 'USR-456', + ); expect(address1 == address2, isFalse); }); diff --git a/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart b/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart index b4a718f..d27570b 100644 --- a/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart +++ b/test/features/driver_orders_details/domain/usecases/get_order_details_usecase_test.dart @@ -1,6 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import 'package:tracking_app/features/driver_orders_details/domain/repos/order_details_repo.dart'; import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; @@ -15,40 +16,52 @@ void main() { setUp(() { mockRepo = MockOrderDetailsRepo(); usecase = GetOrderDetailsUsecase(repo: mockRepo); + provideDummy>>(ErrorApiResult(error: 'dummy')); }); const tOrderId = 'pxkMaEmWYVuvV5jkW0JK'; final tOrderModel = OrderModel( driverId: 'D1', - id: tOrderId, - status: 'accepted', - totalPrice: '100', - userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali', userId: 'U1'), userId: 'U1', + orderId: tOrderId, + orderDetails: OrderDetailsModel( + items: [], + status: 'accepted', + totalPrice: 500, + pickupAddress: PickedAddressModel(name: 'Pharmacy', address: 'Downtown'), + orderId: tOrderId, + userAddress: 'Shebin', + ), ); group('GetOrderDetailsUsecase test', () { - test('should get order details from the repository when called', () async { - when( - mockRepo.getOrderDetails(any), - ).thenAnswer((_) => Stream.value(tOrderModel)); + test( + 'should return SuccessApiResult containing the Stream from the repository', + () async { + when( + mockRepo.getOrderDetails(any), + ).thenReturn(SuccessApiResult(data: Stream.value(tOrderModel))); - final result = usecase.call(tOrderId); + final result = usecase.call(tOrderId); - expect(result, emits(tOrderModel)); - verify(mockRepo.getOrderDetails(tOrderId)).called(1); - verifyNoMoreInteractions(mockRepo); - }); + expect(result, isA>>()); + final stream = (result as SuccessApiResult>).data; + await expectLater(stream, emits(tOrderModel)); + verify(mockRepo.getOrderDetails(tOrderId)).called(1); + }, + ); - test('should forward the error stream if the repository fails', () async { + test('should return ErrorApiResult when the repository fails', () async { when( mockRepo.getOrderDetails(any), - ).thenAnswer((_) => Stream.error('Error from Repository')); + ).thenReturn(ErrorApiResult(error: 'Error from Repository')); final result = usecase.call(tOrderId); - expect(result, emitsError('Error from Repository')); + expect(result, isA>>()); + expect((result as ErrorApiResult).error, 'Error from Repository'); }); }); } diff --git a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart index ab034c9..81e4153 100644 --- a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart +++ b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; @@ -18,6 +19,7 @@ void main() { setUp(() { mockUsecase = MockGetOrderDetailsUsecase(); cubit = OrderDetailsCubit(mockUsecase); + provideDummy>>(ErrorApiResult(error: 'dummy')); }); tearDown(() { @@ -27,11 +29,17 @@ void main() { const tOrderId = 'order_123'; final tOrderModel = OrderModel( driverId: 'D1', - id: tOrderId, - status: 'accepted', - totalPrice: '100', - userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali', userId: 'U1'), userId: 'U1', + orderId: tOrderId, + orderDetails: OrderDetailsModel( + items: [], + status: 'accepted', + totalPrice: 500, + pickupAddress: PickedAddressModel(name: 'Pharmacy', address: 'Downtown'), + orderId: tOrderId, + userAddress: 'Shebin', + ), ); group('OrderDetailsCubit Tests', () { @@ -40,9 +48,10 @@ void main() { build: () { when( mockUsecase.call(any), - ).thenAnswer((_) => Stream.value(tOrderModel)); + ).thenReturn(SuccessApiResult(data: Stream.value(tOrderModel))); return cubit; }, + act: (cubit) => cubit.getOrderDetails(tOrderId), expect: () => [ predicate( @@ -53,9 +62,6 @@ void main() { state.data?.data == tOrderModel; }), ], - verify: (_) { - verify(mockUsecase.call(tOrderId)).called(1); - }, ); blocTest( @@ -63,7 +69,7 @@ void main() { build: () { when( mockUsecase.call(any), - ).thenAnswer((_) => Stream.error('Server Error')); + ).thenReturn(ErrorApiResult(error: 'Server Error')); return cubit; }, act: (cubit) => cubit.getOrderDetails(tOrderId), diff --git a/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart b/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart index eef4ee9..18f5d20 100644 --- a/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart +++ b/test/features/driver_orders_details/presentation/pages/drivers_orders_details_page_test.dart @@ -48,11 +48,17 @@ void main() { final tOrderModel = OrderModel( driverId: 'D1', - id: 'ORD-123', - status: 'accepted', - totalPrice: '500', - userAddress: UserAddressModel(address: 'Shebin', name: 'Ali'), + userAddress: UserAddressModel(address: 'Shebin', name: 'Ali', userId: 'U1'), userId: 'U1', + orderId: 'N123', + orderDetails: OrderDetailsModel( + items: [], + status: 'accepted', + totalPrice: 500, + pickupAddress: PickedAddressModel(name: 'Pharmacy', address: 'Downtown'), + orderId: 'N123', + userAddress: 'Shebin', + ), ); group('DriversOrdersDetailsPage Widget Tests', () { @@ -87,13 +93,10 @@ void main() { await tester.pumpWidget(buildTestableWidget()); await tester.pump(); - expect(find.textContaining('ORD-123'), findsOneWidget); - + expect(find.textContaining('N123'), findsOneWidget); expect(find.text('Ali'), findsOneWidget); expect(find.text('Shebin'), findsAtLeastNWidgets(1)); - expect(find.textContaining('500'), findsOneWidget); - expect(find.byType(AddressCard), findsAtLeastNWidgets(2)); }, ); From ccd6fb88d7e44a3d6630b930cc6a4468f04734ba Mon Sep 17 00:00:00 2001 From: mariam Date: Sat, 28 Feb 2026 01:33:12 +0200 Subject: [PATCH 08/10] feat(SCRUM-87): enhance order details with new status translations and refactor related components --- assets/translations/ar.json | 11 ++- assets/translations/en.json | 11 ++- .../order_details_remote_datasource_impl.dart | 7 +- .../data/repos/order_details_repo_impl.dart | 6 +- .../domain/models/orders_model.dart | 25 ------ .../usecases/get_order_details_usecase.dart | 6 +- .../manager/order_details_cubit.dart | 26 +++++- .../pages/drivers_orders_details_page.dart | 54 +++--------- .../presentation/widgets/order_status.dart | 83 +++++++++++++++++++ 9 files changed, 149 insertions(+), 80 deletions(-) create mode 100644 lib/features/driver_orders_details/presentation/widgets/order_status.dart diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 69909e4..1142135 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -245,5 +245,14 @@ "pickupAddress": "عنوان الاستلام", "floweryStore": "متجر فلوري", "userAddress": "عنوان المستخدم", - "arrivedAtPickupPoint": "وصلت الى نقطة الالتقاء" + "arrivedAtPickupPoint": "وصلت الى نقطة الالتقاء", + "startDelivery": "بدء التوصيل", + "arriverAtDestination": "وصلت إلى نقطة التسليم", + "confirmDelivery": "تأكيد التسليم", + "deliveryConfirmed": "تم تأكيد التسليم", + "orderCompleted": "تم إكمال الطلب", + "accepted": "مقبول", + "pickedUp": "تم الاستلام", + "outForDelivery": "في الطريق للتسليم", + "arrived": "وصلت" } \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index cf0d5bf..2c5ccd0 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -248,5 +248,14 @@ "pickupAddress": "Pickup address", "floweryStore": "Flowery Store", "userAddress": "User address", - "arrivedAtPickupPoint": "Arrived at pickup point" + "arrivedAtPickupPoint": "Arrived at pickup point", + "startDelivery": "Start delivery", + "arriverAtDestination": "Arrived at destination", + "confirmDelivery": "Confirm delivery", + "deliveryConfirmed": "Delivery confirmed", + "orderCompleted": "Order completed", + "accepted": "Accepted", + "pickedUp": "Picked up", + "outForDelivery": "Out for delivery", + "arrived": "Arrived" } \ No newline at end of file diff --git a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart index 63f2c7f..86362e1 100644 --- a/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart +++ b/lib/features/driver_orders_details/api/datasource/order_details_remote_datasource_impl.dart @@ -6,13 +6,14 @@ import 'package:tracking_app/features/driver_orders_details/data/models/orders_d @Injectable(as: OrderDetailsRemoteDatasource) class OrderDetailsRemoteDatasourceImpl implements OrderDetailsRemoteDatasource { - final FirebaseFirestore firestore; - OrderDetailsRemoteDatasourceImpl({required this.firestore}); + final FirebaseFirestore _firestore; + OrderDetailsRemoteDatasourceImpl({required FirebaseFirestore firestore}) + : _firestore = firestore; @override ApiResult> getOrderStream(String orderId) { try { - final stream = firestore + final stream = _firestore .collection('orders') .doc(orderId) .snapshots() diff --git a/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart index e5e0f3d..37251f2 100644 --- a/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart +++ b/lib/features/driver_orders_details/data/repos/order_details_repo_impl.dart @@ -8,12 +8,12 @@ import 'package:tracking_app/features/driver_orders_details/domain/repos/order_d @Injectable(as: OrderDetailsRepo) class OrderDetailsRepoImpl implements OrderDetailsRepo { - final OrderDetailsRemoteDatasource remoteDataSource; - OrderDetailsRepoImpl(this.remoteDataSource); + final OrderDetailsRemoteDatasource _remoteDataSource; + OrderDetailsRepoImpl(this._remoteDataSource); @override ApiResult> getOrderDetails(String orderId) { - final result = remoteDataSource.getOrderStream(orderId); + final result = _remoteDataSource.getOrderStream(orderId); switch (result) { case SuccessApiResult>(): diff --git a/lib/features/driver_orders_details/domain/models/orders_model.dart b/lib/features/driver_orders_details/domain/models/orders_model.dart index add398f..9e96435 100644 --- a/lib/features/driver_orders_details/domain/models/orders_model.dart +++ b/lib/features/driver_orders_details/domain/models/orders_model.dart @@ -1,28 +1,3 @@ -// class UserAddressModel { -// final String address; -// final String name; - -// UserAddressModel({required this.address, required this.name}); -// } - -// class OrderModel { -// final String driverId; -// final String id; -// final String status; -// final String totalPrice; -// final UserAddressModel userAddress; -// final String userId; - -// OrderModel({ -// required this.driverId, -// required this.id, -// required this.status, -// required this.totalPrice, -// required this.userAddress, -// required this.userId, -// }); -// } - class OrderModel { final String orderId; final String driverId; diff --git a/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart index 14168b1..e3253c1 100644 --- a/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart +++ b/lib/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart @@ -5,9 +5,9 @@ import 'package:tracking_app/features/driver_orders_details/domain/repos/order_d @injectable class GetOrderDetailsUsecase { - OrderDetailsRepo repo; - GetOrderDetailsUsecase({required this.repo}); + OrderDetailsRepo _repo; + GetOrderDetailsUsecase({required OrderDetailsRepo repo}) : _repo = repo; ApiResult> call(String orderId) => - repo.getOrderDetails(orderId); + _repo.getOrderDetails(orderId); } diff --git a/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart index 33eb919..2c9e53d 100644 --- a/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart +++ b/lib/features/driver_orders_details/presentation/manager/order_details_cubit.dart @@ -1,7 +1,11 @@ import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; +import 'package:tracking_app/app/config/auth_storage/auth_storage.dart'; import 'package:tracking_app/app/config/base_state/base_state.dart'; +import 'package:tracking_app/app/config/di/di.dart'; import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import '../../domain/usecases/get_order_details_usecase.dart'; @@ -9,15 +13,31 @@ import 'order_details_states.dart'; @injectable class OrderDetailsCubit extends Cubit { - final GetOrderDetailsUsecase getOrderDetailsUsecase; + final GetOrderDetailsUsecase _getOrderDetailsUsecase; StreamSubscription? _subscription; + final _authStorage = getIt(); - OrderDetailsCubit(this.getOrderDetailsUsecase) : super(OrderDetailsStates()); + OrderDetailsCubit(this._getOrderDetailsUsecase) : super(OrderDetailsStates()); + + Future loadUserData() async { + final userJson = await _authStorage.getUserJson(); + + if (userJson != null) { + final userMap = jsonDecode(userJson); + // final orderId = userMap['orderDetails']?['orderId'] as String?; + final orderId = '696ae30ce364ef61404760df'; + if (orderId != null && orderId.isNotEmpty) { + getOrderDetails(orderId); + } else { + debugPrint('Order ID not found in user data'); + } + } + } void getOrderDetails(String orderId) async { emit(state.copyWith(data: Resource.loading())); _subscription?.cancel(); - final result = getOrderDetailsUsecase.call(orderId); + final result = _getOrderDetailsUsecase.call(orderId); switch (result) { case SuccessApiResult>(): diff --git a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart index 210bc2e..50b70cf 100644 --- a/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart +++ b/lib/features/driver_orders_details/presentation/pages/drivers_orders_details_page.dart @@ -12,6 +12,7 @@ import 'package:tracking_app/features/driver_orders_details/presentation/manager import 'package:tracking_app/features/driver_orders_details/presentation/widgets/address_card.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/bottom_row_section.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/order_item.dart'; +import 'package:tracking_app/features/driver_orders_details/presentation/widgets/order_status.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/widgets/section_title.dart'; import 'package:tracking_app/generated/locale_keys.g.dart'; @@ -20,7 +21,6 @@ class DriversOrdersDetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { - var cubit = getIt(); return Scaffold( appBar: AppBar( leading: IconButton( @@ -36,7 +36,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), ), body: BlocProvider( - create: (context) => cubit..getOrderDetails('696ae30ce364ef61404760df'), + create: (context) => getIt()..loadUserData(), child: BlocBuilder( builder: (context, state) { if (state.data?.status == Status.loading) { @@ -45,6 +45,9 @@ class DriversOrdersDetailsPage extends StatelessWidget { return Center(child: Text(state.data!.error.toString())); } else if (state.data?.status == Status.success) { final order = state.data!.data; + final status = OrderStatus.fromString(order?.orderDetails.status); + + int currentStep = status.step; return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( @@ -52,9 +55,6 @@ class DriversOrdersDetailsPage extends StatelessWidget { children: [ Row( children: List.generate(5, (index) { - int currentStep = _getStepCount( - order!.orderDetails.status, - ); return Expanded( child: Container( height: 4, @@ -82,7 +82,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${LocaleKeys.status.tr()}${order!.orderDetails.status}', + '${LocaleKeys.status.tr()}${order?.orderDetails.status}', style: TextStyle( color: AppColors.green, fontWeight: FontWeight.bold, @@ -91,7 +91,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), const SizedBox(height: 4), Text( - '${LocaleKeys.orderId.tr()}${order.orderId}', + '${LocaleKeys.orderId.tr()}${order?.orderId}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, @@ -112,16 +112,16 @@ class DriversOrdersDetailsPage extends StatelessWidget { SectionTitle(title: LocaleKeys.pickupAddress.tr()), AddressCard( - title: order.orderDetails.pickupAddress.name, - address: order.orderDetails.pickupAddress.address, + title: order?.orderDetails.pickupAddress.name ?? '', + address: order?.orderDetails.pickupAddress.address ?? '', imagePath: AppPaths.flowerLogo, ), const SizedBox(height: 16), SectionTitle(title: LocaleKeys.userAddress.tr()), AddressCard( - title: order.userAddress.name, - address: order.userAddress.address, + title: order?.userAddress.name ?? '', + address: order?.userAddress.address ?? '', imagePath: AppPaths.flowerLogo, ), const SizedBox(height: 24), @@ -133,7 +133,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { BottomRowSection( label: LocaleKeys.total.tr(), value: - '${LocaleKeys.egp.tr()} ${order.orderDetails.totalPrice.toStringAsFixed(2)}', + '${LocaleKeys.egp.tr()} ${order?.orderDetails.totalPrice.toStringAsFixed(2)}', ), BottomRowSection( label: LocaleKeys.payment_method.tr(), @@ -149,7 +149,7 @@ class DriversOrdersDetailsPage extends StatelessWidget { isEnabled: true, onPressed: () {}, isLoading: false, - text: _getButtonText(order.orderDetails.status), + text: status.buttonTextKey.tr(), ), ), ], @@ -162,32 +162,4 @@ class DriversOrdersDetailsPage extends StatelessWidget { ), ); } - - int _getStepCount(String status) { - switch (status.toLowerCase()) { - case 'accepted': - return 1; - case 'pickup': - return 2; - case 'out_for_delivery': - return 3; - case 'arrived': - return 4; - case 'delivered': - return 5; - default: - return 1; - } - } - - String _getButtonText(String status) { - switch (status.toLowerCase()) { - case 'accepted': - return LocaleKeys.arrivedAtPickupPoint.tr(); - case 'pickup': - return 'Start deliver'; - default: - return LocaleKeys.arrivedAtPickupPoint.tr(); - } - } } diff --git a/lib/features/driver_orders_details/presentation/widgets/order_status.dart b/lib/features/driver_orders_details/presentation/widgets/order_status.dart new file mode 100644 index 0000000..4c88788 --- /dev/null +++ b/lib/features/driver_orders_details/presentation/widgets/order_status.dart @@ -0,0 +1,83 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:tracking_app/generated/locale_keys.g.dart'; + +enum OrderStatus { + accepted, + pickup, + outForDelivery, + arrived, + delivered, + unknown; + + static OrderStatus fromString(String? status) { + switch (status?.toLowerCase()) { + case 'accepted': + return OrderStatus.accepted; + case 'pickup': + return OrderStatus.pickup; + case 'out_for_delivery': + return OrderStatus.outForDelivery; + case 'arrived': + return OrderStatus.arrived; + case 'delivered': + return OrderStatus.delivered; + default: + debugPrint('Unknown order status: $status'); + return OrderStatus.unknown; + } + } +} + +extension OrderStatusX on OrderStatus { + int get step { + switch (this) { + case OrderStatus.accepted: + return 1; + case OrderStatus.pickup: + return 2; + case OrderStatus.outForDelivery: + return 3; + case OrderStatus.arrived: + return 4; + case OrderStatus.delivered: + return 5; + case OrderStatus.unknown: + return 1; + } + } + + String get buttonTextKey { + switch (this) { + case OrderStatus.accepted: + return LocaleKeys.arrivedAtPickupPoint.tr(); + case OrderStatus.pickup: + return LocaleKeys.startDelivery.tr(); + case OrderStatus.outForDelivery: + return LocaleKeys.arriverAtDestination.tr(); + case OrderStatus.arrived: + return LocaleKeys.confirmDelivery.tr(); + case OrderStatus.delivered: + return LocaleKeys.orderCompleted.tr(); + case OrderStatus.unknown: + return LocaleKeys.arrivedAtPickupPoint; + } + } + + String get statusTextKey { + switch (this) { + case OrderStatus.accepted: + return LocaleKeys.accepted.tr(); + case OrderStatus.pickup: + return LocaleKeys.pickedUp.tr(); + case OrderStatus.outForDelivery: + return LocaleKeys.outForDelivery.tr(); + case OrderStatus.arrived: + return LocaleKeys.arrived.tr(); + case OrderStatus.delivered: + return LocaleKeys.delivered.tr(); + case OrderStatus.unknown: + return ''; + } + } +} From bfcc5a470a5d1bedc4962c33e79b7caa9af4efe0 Mon Sep 17 00:00:00 2001 From: mariam Date: Sat, 28 Feb 2026 01:48:28 +0200 Subject: [PATCH 09/10] test(SCRUM-87): enhance OrderDetailsCubit tests with additional mock setup and assertions --- .../manager/order_details_cubit_test.dart | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart index 81e4153..4f77708 100644 --- a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart +++ b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart @@ -1,29 +1,35 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:tracking_app/app/config/auth_storage/auth_storage.dart'; import 'package:tracking_app/app/config/base_state/base_state.dart'; import 'package:tracking_app/app/core/network/api_result.dart'; import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_states.dart'; - import 'order_details_cubit_test.mocks.dart'; -@GenerateMocks([GetOrderDetailsUsecase]) +@GenerateMocks([GetOrderDetailsUsecase, AuthStorage]) void main() { late OrderDetailsCubit cubit; late MockGetOrderDetailsUsecase mockUsecase; + late MockAuthStorage mockAuthStorage; setUp(() { mockUsecase = MockGetOrderDetailsUsecase(); + mockAuthStorage = MockAuthStorage(); + final sl = GetIt.instance; + sl.registerSingleton(mockAuthStorage); cubit = OrderDetailsCubit(mockUsecase); provideDummy>>(ErrorApiResult(error: 'dummy')); }); tearDown(() { cubit.close(); + GetIt.instance.reset(); }); const tOrderId = 'order_123'; @@ -41,46 +47,46 @@ void main() { userAddress: 'Shebin', ), ); - group('OrderDetailsCubit Tests', () { blocTest( - 'should emit [Loading, Success] when data is fetched successfully', + 'emits [Loading, Success] when data is fetched successfully', build: () { when( mockUsecase.call(any), - ).thenReturn(SuccessApiResult(data: Stream.value(tOrderModel))); + ).thenAnswer((_) => SuccessApiResult(data: Stream.value(tOrderModel))); return cubit; }, - act: (cubit) => cubit.getOrderDetails(tOrderId), expect: () => [ - predicate( - (state) => state.data?.status == Status.loading, + isA().having( + (s) => s.data?.status, + 'status', + Status.loading, ), - predicate((state) { - return state.data?.status == Status.success && - state.data?.data == tOrderModel; - }), + isA() + .having((s) => s.data?.status, 'status', Status.success) + .having((s) => s.data?.data, 'data', tOrderModel), ], ); blocTest( - 'should emit [Loading, Error] when fetching data fails', + 'emits [Loading, Error] when fetching data fails', build: () { when( mockUsecase.call(any), - ).thenReturn(ErrorApiResult(error: 'Server Error')); + ).thenAnswer((_) => ErrorApiResult(error: 'Server Error')); return cubit; }, act: (cubit) => cubit.getOrderDetails(tOrderId), expect: () => [ - predicate( - (state) => state.data?.status == Status.loading, + isA().having( + (s) => s.data?.status, + 'status', + Status.loading, ), - predicate((state) { - return state.data?.status == Status.error && - state.data?.error == 'Server Error'; - }), + isA() + .having((s) => s.data?.status, 'status', Status.error) + .having((s) => s.data?.error, 'error', 'Server Error'), ], ); }); From f8ca78365ee9ccbf4314e9c6b1f96b295fac526d Mon Sep 17 00:00:00 2001 From: mariam Date: Sat, 28 Feb 2026 04:00:50 +0200 Subject: [PATCH 10/10] refactor(SCRUM-87): remove OrderDetailsCubit test file as part of cleanup --- .../manager/order_details_cubit_test.dart | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart diff --git a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart b/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart deleted file mode 100644 index 4f77708..0000000 --- a/test/features/driver_orders_details/presentation/manager/order_details_cubit_test.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:get_it/get_it.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:tracking_app/app/config/auth_storage/auth_storage.dart'; -import 'package:tracking_app/app/config/base_state/base_state.dart'; -import 'package:tracking_app/app/core/network/api_result.dart'; -import 'package:tracking_app/features/driver_orders_details/domain/models/orders_model.dart'; -import 'package:tracking_app/features/driver_orders_details/domain/usecases/get_order_details_usecase.dart'; -import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_cubit.dart'; -import 'package:tracking_app/features/driver_orders_details/presentation/manager/order_details_states.dart'; -import 'order_details_cubit_test.mocks.dart'; - -@GenerateMocks([GetOrderDetailsUsecase, AuthStorage]) -void main() { - late OrderDetailsCubit cubit; - late MockGetOrderDetailsUsecase mockUsecase; - late MockAuthStorage mockAuthStorage; - - setUp(() { - mockUsecase = MockGetOrderDetailsUsecase(); - mockAuthStorage = MockAuthStorage(); - final sl = GetIt.instance; - sl.registerSingleton(mockAuthStorage); - cubit = OrderDetailsCubit(mockUsecase); - provideDummy>>(ErrorApiResult(error: 'dummy')); - }); - - tearDown(() { - cubit.close(); - GetIt.instance.reset(); - }); - - const tOrderId = 'order_123'; - final tOrderModel = OrderModel( - driverId: 'D1', - userAddress: UserAddressModel(address: 'Shebin', name: 'Ali', userId: 'U1'), - userId: 'U1', - orderId: tOrderId, - orderDetails: OrderDetailsModel( - items: [], - status: 'accepted', - totalPrice: 500, - pickupAddress: PickedAddressModel(name: 'Pharmacy', address: 'Downtown'), - orderId: tOrderId, - userAddress: 'Shebin', - ), - ); - group('OrderDetailsCubit Tests', () { - blocTest( - 'emits [Loading, Success] when data is fetched successfully', - build: () { - when( - mockUsecase.call(any), - ).thenAnswer((_) => SuccessApiResult(data: Stream.value(tOrderModel))); - return cubit; - }, - act: (cubit) => cubit.getOrderDetails(tOrderId), - expect: () => [ - isA().having( - (s) => s.data?.status, - 'status', - Status.loading, - ), - isA() - .having((s) => s.data?.status, 'status', Status.success) - .having((s) => s.data?.data, 'data', tOrderModel), - ], - ); - - blocTest( - 'emits [Loading, Error] when fetching data fails', - build: () { - when( - mockUsecase.call(any), - ).thenAnswer((_) => ErrorApiResult(error: 'Server Error')); - return cubit; - }, - act: (cubit) => cubit.getOrderDetails(tOrderId), - expect: () => [ - isA().having( - (s) => s.data?.status, - 'status', - Status.loading, - ), - isA() - .having((s) => s.data?.status, 'status', Status.error) - .having((s) => s.data?.error, 'error', 'Server Error'), - ], - ); - }); -}