From 886825a4b97bf47b004713fa07b072b5220bda23 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 3 Sep 2025 16:53:36 +0300 Subject: [PATCH 01/11] bottom navigation updated. --- lib/extensions/string_extensions.dart | 124 +++++++++++++++--- lib/presentation/home/landing_page.dart | 3 +- lib/presentation/home/navigation_screen.dart | 43 ++++++ lib/splashPage.dart | 3 +- .../bottom_navigation/bottom_navigation.dart | 118 +++++++++++------ 5 files changed, 232 insertions(+), 59 deletions(-) create mode 100644 lib/presentation/home/navigation_screen.dart diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index b9d4ba8..484ebc1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -11,13 +11,16 @@ extension CapExtension on String { String get allInCaps => this.toUpperCase(); - String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + String get capitalizeFirstofEach => + this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( + Widget toText8( + {Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => + Text( this, maxLines: maxlines, overflow: textOverflow, @@ -30,7 +33,14 @@ extension EmailValidator on String { ), ); - Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( + Widget toText10( + {Color? color, + bool isBold = false, + bool isUnderLine = false, + int? maxlines, + FontStyle? fontStyle, + TextOverflow? textOverflow}) => + Text( this, maxLines: maxlines, overflow: textOverflow, @@ -44,7 +54,15 @@ extension EmailValidator on String { decorationColor: color ?? AppColors.blackColor), ); - Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text( + Widget toText11( + {Color? color, + FontWeight? weight, + bool isUnderLine = false, + bool isCenter = false, + bool isBold = false, + int maxLine = 0, + double letterSpacing = 0.64}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -58,21 +76,33 @@ extension EmailValidator on String { ), ); - Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( + Widget toText12( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + FontWeight? fontWeight, + bool isCenter = false, + double? height, + int maxLine = 0}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( + fontSize: 12.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, + height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, ), ); - Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText( + Widget toText12Auto( + {Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => + AutoSizeText( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -133,7 +163,14 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText14( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + bool isCenter = false, + FontWeight? weight, + int? maxlines}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -145,7 +182,14 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText15( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + bool isCenter = false, + FontWeight? weight, + int? maxlines}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -181,53 +225,93 @@ extension EmailValidator on String { Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + color: color ?? AppColors.blackColor, + fontSize: 17.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, - style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 18.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 19.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText20({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(fontSize: 20.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 20.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle( + color: color ?? AppColors.blackColor, + fontSize: 21.fSize, + letterSpacing: -0.4, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 1, + color: color ?? AppColors.blackColor, + fontSize: 22.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 23 / 24, + color: color ?? AppColors.blackColor, + fontSize: 24.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 32 / 32, + color: color ?? AppColors.blackColor, + fontSize: 32.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 32 / 32, + color: color ?? AppColors.blackColor, + fontSize: 44.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -263,7 +347,9 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); + return RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') + .hasMatch(this); } String toFormattedDate() { diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 7435df9..4ade673 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -17,7 +17,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -30,6 +29,7 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -273,7 +273,6 @@ class _LandingPageState extends State { ), ), ), - bottomNavigationBar: BottomNavigation(), ); } } diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart new file mode 100644 index 0000000..f3e088b --- /dev/null +++ b/lib/presentation/home/navigation_screen.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; + +class LandingNavigation extends StatefulWidget { + const LandingNavigation({super.key}); + + @override + State createState() => _LandingNavigationState(); +} + +class _LandingNavigationState extends State { + int _currentIndex = 0; + final PageController _pageController = PageController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: const [ + LandingPage(), + MedicalFilePage(), + LandingPage(), + LandingPage(), + LandingPage(), + ], + ), + bottomNavigationBar: BottomNavigation( + currentIndex: _currentIndex, + onTap: (index) { + setState(() => _currentIndex = index); + _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut); + }, + ), + ); + } +} diff --git a/lib/splashPage.dart b/lib/splashPage.dart index bc3e542..6136d22 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; // import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -37,7 +38,7 @@ class _SplashScreenState extends State { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( - page: LandingPage(), + page: LandingNavigation(), // page: LoginScreen(), ), ); diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index a052bc6..7f45464 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -1,59 +1,103 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class BottomNavigation extends StatelessWidget { - const BottomNavigation({super.key}); + final int currentIndex; + final ValueChanged onTap; + + const BottomNavigation({ + super.key, + required this.currentIndex, + required this.onTap, + }); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: const BoxDecoration( - color: Colors.white, - border: Border( - top: BorderSide(color: AppColors.bottomNAVBorder, width: 0.5), - ), - + final items = [ + BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr()), + BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.myFiles.tr()), + BottomNavItem( + icon: AppAssets.bookAppoBottom, + label: LocaleKeys.appointment.tr(), + iconSize: 27, + isSpecial: true, ), + BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr()), + BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr()), + ]; + + return Container( + decoration: _containerDecoration, + padding: _containerPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem(AppAssets.homeBottom, LocaleKeys.home.tr()), - _buildNavItem(AppAssets.myFilesBottom, LocaleKeys.myFiles.tr()), - _buildNavItem(AppAssets.bookAppoBottom, LocaleKeys.appointment.tr(), iconSize: 32), - _buildNavItem(AppAssets.toDoBottom, LocaleKeys.todoList.tr()), - _buildNavItem(AppAssets.servicesBottom, LocaleKeys.services2.tr()), - ], + children: List.generate( + items.length, + (index) => _buildNavItem(items[index], index), + ), ), ); } - Widget _buildNavItem(String iconName, String label,{ double iconSize = 24}) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(10), - child: Utils.buildSvgWithAssets( - icon: iconName, - height: iconSize, - width: iconSize - ), - ), - // const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.black87, + Widget _buildNavItem(BottomNavItem item, int index) { + final bool isSelected = currentIndex == index; + + return GestureDetector( + onTap: () => onTap(index), + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Utils.buildSvgWithAssets( + icon: item.icon, + height: item.iconSize.h, + width: item.iconSize.h, + // iconColor: isSelected ? Colors.black87 : Colors.black87, + ), + ), + const SizedBox(height: 10), + item.label.toText12( + fontWeight:FontWeight.w500, + // color: Colors.black87, + // textAlign: TextAlign.center, ), - ), - ], + SizedBox(height: item.isSpecial ? 5:0 ) + ], + + ), ); } } + +class BottomNavItem { + final String icon; + final String label; + final double iconSize; + final bool isSpecial; + + const BottomNavItem({ + required this.icon, + required this.label, + this.iconSize = 21, + this.isSpecial = false, + }); +} + +// Constants +const EdgeInsets _containerPadding = EdgeInsets.all(15); +const BoxDecoration _containerDecoration = BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + color: AppColors.bottomNAVBorder, + width: 0.5, + ), + ), +); From e8f8f6074ca829ae537ff9a4f1082f1d769689b3 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Thu, 4 Sep 2025 08:44:37 +0300 Subject: [PATCH 02/11] prescription details updates --- .../prescription_detail_page.dart | 195 +++++++++--------- 1 file changed, 97 insertions(+), 98 deletions(-) diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 0231c67..d14da43 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -95,7 +95,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -112,7 +112,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -132,7 +132,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -149,7 +149,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -185,116 +185,115 @@ class _PrescriptionDetailPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ Row( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, children: [ - Image.network( - prescriptionVM.prescriptionDetailsList[index].imageSRCUrl!, - width: 60.h, - height: 60.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, ), ], ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + Row( + mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, ), ], ), - SizedBox(height: 8.h), Row( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), - SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), ], - ) + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), ], - ), - ), + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 16.h), + ], ) ], ), From e41ffd583d938bc10e0186d6c883759878f6a4c5 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 4 Sep 2025 11:25:16 +0300 Subject: [PATCH 03/11] changes --- .../authentication_view_model.dart | 17 +- lib/presentation/authentication/login.dart | 12 +- lib/presentation/authentication/register.dart | 244 +++++++++--------- .../authentication/register_step2.dart | 199 ++++++++------ .../bottomsheet/generic_bottom_sheet.dart | 11 +- lib/widgets/input_widget.dart | 49 ++-- 6 files changed, 286 insertions(+), 246 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index baa6f7e..9abd673 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -23,13 +23,28 @@ class AuthenticationViewModel extends ChangeNotifier { final TextEditingController nationalIdController = TextEditingController(); final TextEditingController phoneNumberController = TextEditingController(); + CountryEnum selectedCountry = CountryEnum.saudiArabia; + bool isTermsAccepted = false; void login() { if (ValidationUtils.isValidatePhoneAndId(nationalId: nationalIdController.text, phoneNumber: phoneNumberController.text)) { - } else {} } + void onCountryChange(CountryEnum country) { + selectedCountry = country; + notifyListeners(); + } + + void onPhoneNumberChange(String? phoneNumber) { + phoneNumberController.text = phoneNumber!; + } + + void onTermAccepted() { + isTermsAccepted = !isTermsAccepted; + notifyListeners(); + } + Future selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async { String firebaseToken = "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 2771b36..f171443 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -83,7 +83,7 @@ class _LoginScreen extends State { icon: AppAssets.login1, iconColor: Colors.white, onPressed: () { - showLoginModel(context: context, textController: authVm.phoneNumberController); + showLoginModel(context: context, authVM: authVm); // if (nationIdController.text.isNotEmpty) { // } else { @@ -140,7 +140,7 @@ class _LoginScreen extends State { ); } - void showLoginModel({required BuildContext context, TextEditingController? textController}) { + void showLoginModel({required BuildContext context, required AuthenticationViewModel authVM}) { context.showBottomSheet( isScrollControlled: true, isDismissible: false, @@ -151,12 +151,12 @@ class _LoginScreen extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - countryCode: "966", + countryCode: authVM.selectedCountry.countryCode, initialPhoneNumber: "", - textController: textController, + textController: authVM.phoneNumberController, isEnableCountryDropdown: true, - onCountryChange: (value) {}, - onChange: (String? value) {}, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, buttons: [ Padding( padding: EdgeInsets.only(bottom: 10.h), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 1e8a0c7..85ff402 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; @@ -18,6 +19,7 @@ import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dar import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/otp/otp.dart'; +import 'package:provider/provider.dart'; class RegisterNew extends StatefulWidget { @override @@ -25,8 +27,6 @@ class RegisterNew extends StatefulWidget { } class _RegisterNew extends State { - bool isTermsAccepted = true; - @override void initState() { super.initState(); @@ -40,6 +40,8 @@ class _RegisterNew extends State { @override Widget build(BuildContext context) { AppState appState = getIt.get(); + AuthenticationViewModel authVm = context.read(); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: CustomAppBar( @@ -68,12 +70,7 @@ class _RegisterNew extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.showLottie(context: context, - assetPath: 'assets/animations/lottie/register.json', - width: 200.h, - height: 200.h, - fit: BoxFit.cover, - repeat: true), + Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true), SizedBox(height: 16.h), LocaleKeys.prepareToElevate.tr().toText32(isBold: true), SizedBox(height: 24.h), @@ -125,22 +122,25 @@ class _RegisterNew extends State { ), SizedBox(height: 25.h), GestureDetector( - onTap: () {}, + onTap: authVm.onTermAccepted, child: Row( children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: 24.h, - width: 24.h, - decoration: BoxDecoration( - color: isTermsAccepted ? const Color(0xFFE92227) : Colors.transparent, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: isTermsAccepted ? const Color(0xFFE92227) : Colors.grey, - width: 2.h, - ), - ), - child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, + Selector( + selector: (_, viewModel) => viewModel.isTermsAccepted, + shouldRebuild: (previous, next) => previous != next, + builder: (context, isTermsAccepted, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), + ), + child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, + ); + }, ), SizedBox(width: 12.h), Expanded( @@ -157,7 +157,7 @@ class _RegisterNew extends State { text: "Register", icon: AppAssets.note_edit, onPressed: () { - showRegisterModel(context: context); + showRegisterModel(context: context, authVM: authVm); }, ), SizedBox(height: 14), @@ -200,118 +200,116 @@ class _RegisterNew extends State { )); } - void showRegisterModel({required BuildContext context, TextEditingController? textController}) { + void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) { showModalBottomSheet( context: context, isScrollControlled: true, isDismissible: false, useSafeArea: true, backgroundColor: Colors.transparent, - builder: (bottomSheetContext) => - Padding( - padding: EdgeInsets.only(bottom: MediaQuery - .of(bottomSheetContext) - .viewInsets - .bottom), - child: SingleChildScrollView( - child: GenericBottomSheet( - countryCode: "966", - initialPhoneNumber: "", - textController: TextEditingController(), - isEnableCountryDropdown: true, - onChange: (String? value) {}, - buttons: [ - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567',))); - + builder: (bottomSheetContext) => Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: authVM.selectedCountry.countryCode, + initialPhoneNumber: "", + textController: authVM.phoneNumberController, + isEnableCountryDropdown: true, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, + buttons: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => OTPVerificationPage( + phoneNumber: '12234567', + ))); - // if (mobileNo.isEmpty) { - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: TranslationBase.of(context).pleaseEnterMobile, - // showCancel: false, - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // ), - // ); - // } else if (!Utils.validateMobileNumber(mobileNo)) { - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: TranslationBase.of(context).pleaseEnterValidMobile, - // showCancel: false, - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // ), - // ); - // } else { - // registerUser(1); - // } - Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567'))); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedBorderColor, - textColor: AppColors.whiteColor, - icon: AppAssets.message, - ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.h), - child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), - ), - ], - ), + // if (mobileNo.isEmpty) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else if (!Utils.validateMobileNumber(mobileNo)) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterValidMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else { + // registerUser(1); + // } + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567'))); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: AppColors.whiteColor, + icon: AppAssets.message, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ Padding( - padding: EdgeInsets.only(bottom: 10.h, top: 10.h), - child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), - onPressed: () { - // if (mobileNo.isEmpty) { - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: TranslationBase.of(context).pleaseEnterMobile, - // showCancel: false, - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // ), - // ); - // } else if (!Utils.validateMobileNumber(mobileNo)) { - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: TranslationBase.of(context).pleaseEnterValidMobile, - // showCancel: false, - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // ), - // ); - // } else { - // registerUser(4); - // } - // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context); - // registerUser(val); - }, - backgroundColor: AppColors.whiteColor, - borderColor: AppColors.borderOnlyColor, - textColor: AppColors.textColor, - icon: AppAssets.whatsapp, - ), + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), ), ], ), - ), + Padding( + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), + onPressed: () { + // if (mobileNo.isEmpty) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else if (!Utils.validateMobileNumber(mobileNo)) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterValidMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else { + // registerUser(4); + // } + // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context); + // registerUser(val); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + ), + ), + ], ), + ), + ), ); } } diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 295dd8a..660ba69 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -53,8 +53,11 @@ class _RegisterNew extends State { Widget build(BuildContext context) { return Scaffold( - - appBar: CustomAppBar(onBackPressed: () {}, onLanguageChanged: (lang) {}, hideLogoAndLang: true,), + appBar: CustomAppBar( + onBackPressed: () {}, + onLanguageChanged: (lang) {}, + hideLogoAndLang: true, + ), body: SingleChildScrollView( reverse: false, padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h), @@ -69,30 +72,38 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, - leadingIcon: AppAssets.user_circle).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + isReadOnly: isFromDubai ? false : true, + leadingIcon: AppAssets.user_circle) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - leadingIcon: AppAssets.student_card).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.student_card) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), isFromDubai ? DropdownWidget( labelText: LocaleKeys.gender.tr(), @@ -109,22 +120,25 @@ class _RegisterNew extends State { isAllowRadius: false, padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.user_full, + leadingIcon: AppAssets.user_full, ).withVerticalPadding(8) : TextInputWidget( - labelText: LocaleKeys.gender.tr(), - hintText: (widget.nHICData!.gender ?? ""), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, - leadingIcon: AppAssets.user_full, - - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + labelText: LocaleKeys.gender.tr(), + hintText: (widget.nHICData!.gender ?? ""), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: isFromDubai ? false : true, + leadingIcon: AppAssets.user_full, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), isFromDubai ? DropdownWidget( labelText: LocaleKeys.maritalStatus.tr(), @@ -138,22 +152,26 @@ class _RegisterNew extends State { isAllowRadius: false, padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.smart_phone, + leadingIcon: AppAssets.smart_phone, ).withVerticalPadding(8) : TextInputWidget( - labelText: LocaleKeys.maritalStatus.tr(), - hintText: appState!.isArabic() - ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) - : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - leadingIcon: AppAssets.smart_phone, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + labelText: LocaleKeys.maritalStatus.tr(), + hintText: appState!.isArabic() + ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) + : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.smart_phone, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), isFromDubai ? DropdownWidget( labelText: LocaleKeys.country.tr(), @@ -170,46 +188,55 @@ class _RegisterNew extends State { leadingIcon: AppAssets.globe, ).withVerticalPadding(8) : TextInputWidget( - labelText: LocaleKeys.nationality.tr(), - hintText: appState!.isArabic() - ? (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") - : (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + labelText: LocaleKeys.nationality.tr(), + hintText: appState!.isArabic() + ? (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.globe, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), + TextInputWidget( + labelText: LocaleKeys.mobileNumber.tr(), + hintText: ("widget.payload.mobileNo" ?? ""), + controller: null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, - leadingIcon: AppAssets.globe, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), - TextInputWidget( - labelText: LocaleKeys.mobileNumber.tr(), - hintText: ("widget.payload.mobileNo" ?? ""), - controller: null, - isEnable: true, - prefix: null, - isAllowRadius: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - leadingIcon: AppAssets.call, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), - Divider(height: 1, color: AppColors.greyColor,), + leadingIcon: AppAssets.call, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), TextInputWidget( - labelText: LocaleKeys.dob.tr(), - hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), - controller: null, - isEnable: true, - prefix: null, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - // : SelectionType.calendar, - // selectedValue: widget.payload.dob != null ? Utils.formatDateToDisplay(widget.payload.dob.toString()) : null, - // selectionCustomIcon: AppAssets.calendar, - leadingIcon: AppAssets.birthday_cake, - onChange: (value) {}).paddingSymmetrical(0.h,16.h), + labelText: LocaleKeys.dob.tr(), + hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + controller: null, + isEnable: true, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + // : SelectionType.calendar, + // selectedValue: widget.payload.dob != null ? Utils.formatDateToDisplay(widget.payload.dob.toString()) : null, + // selectionCustomIcon: AppAssets.calendar, + leadingIcon: AppAssets.birthday_cake, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), ], ), ), diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index 1d07099..08bf849 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -46,8 +46,8 @@ class _GenericBottomSheetState extends State { void initState() { super.initState(); - if (!widget.isForEmail) { - widget.textController = TextEditingController(text: widget.initialPhoneNumber); + if (!widget.isForEmail && widget.textController != null) { + widget.textController!.text = widget.initialPhoneNumber!; } } @@ -116,13 +116,16 @@ class _GenericBottomSheetState extends State { padding: EdgeInsets.all(8.h), keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, onChange: (value) { - widget.textController!.text = value!; if (widget.onChange != null) { widget.onChange!(value); } }, + onCountryChange: (value) { + if (widget.onCountryChange != null) { + widget.onCountryChange!(value); + } + }, isEnable: true, - // focusNode: widget.myFocusNode, isReadOnly: widget.isFromSavedLogin, prefix: widget.isForEmail ? null : widget.countryCode, isBorderAllowed: false, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index b8d36e7..ca19ee6 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -28,11 +28,12 @@ class TextInputWidget extends StatelessWidget { final bool isCountryDropDown; final bool hasError; final String? errorMessage; + Function(CountryEnum)? onCountryChange; // final List countryList; // final Function(Country)? onCountryChange; - const TextInputWidget({ + TextInputWidget({ Key? key, required this.labelText, required this.hintText, @@ -52,6 +53,7 @@ class TextInputWidget extends StatelessWidget { this.isCountryDropDown = false, this.hasError = false, this.errorMessage, + this.onCountryChange, // this.countryList = const [], // this.onCountryChange, }) : super(key: key); @@ -74,32 +76,27 @@ class TextInputWidget extends StatelessWidget { child: Row( textDirection: Directionality.of(context), children: [ - if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) - _buildLeadingIcon(context), - isCountryDropDown - ? CustomCountryDropdown( - countryList: CountryEnum.values, - onCountryChange: (CountryEnum? value) { - print(value); - }, - isRtl: Directionality.of(context) == TextDirection.rtl, - isFromBottomSheet: isCountryDropDown, - isEnableTextField: true, - onPhoneNumberChanged: (value) { - print(value); - }, - textField: _buildTextField(context), - ) - : Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildLabelText(), - _buildTextField(context), - ], - ), + if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) _buildLeadingIcon(context), + isCountryDropDown + ? CustomCountryDropdown( + countryList: CountryEnum.values, + onCountryChange: onCountryChange, + isRtl: Directionality.of(context) == TextDirection.rtl, + isFromBottomSheet: isCountryDropDown, + isEnableTextField: true, + onPhoneNumberChanged: onChange, + // textField: _buildTextField(context), + ) + : Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabelText(), + _buildTextField(context), + ], ), + ), ], ), ), From efb22aec4ef08c5cbb5cb01d068a41ec7a8ab9fa Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 4 Sep 2025 11:27:25 +0300 Subject: [PATCH 04/11] prescription details page implemented --- .../images/svg/prescription_reminder_icon.svg | 5 + assets/langs/ar-SA.json | 5 +- assets/langs/en-US.json | 4 +- lib/core/api_consts.dart | 1 - lib/core/app_assets.dart | 1 + lib/core/utils/calendar_utils.dart | 4 - .../prescription_detail_response_model.dart | 5 +- .../prescriptions_view_model.dart | 8 + lib/generated/locale_keys.g.dart | 2 + .../prescription_detail_page.dart | 599 ++++++++++-------- .../prescriptions_list_page.dart | 4 +- lib/theme/colors.dart | 1 + 12 files changed, 374 insertions(+), 265 deletions(-) create mode 100644 assets/images/svg/prescription_reminder_icon.svg diff --git a/assets/images/svg/prescription_reminder_icon.svg b/assets/images/svg/prescription_reminder_icon.svg new file mode 100644 index 0000000..3a854c3 --- /dev/null +++ b/assets/images/svg/prescription_reminder_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 718bf83..91d5d4f 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -782,5 +782,8 @@ "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم." + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", + + "checkAvailability": "التحقق من التوفر", + "readInstructions": "قراءة التعليمات" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6743993..f91d98b 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -778,5 +778,7 @@ "resultsPending": "Results Pending", "resultsAvailable": "Results Available", "viewReport": "View Report", - "prescriptionDeliveryError": "This clinic doesn't support refill" + "prescriptionDeliveryError": "This clinic doesn't support refill", + "checkAvailability": "Check Availability", + "readInstructions": "Read Instructions" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 6e1e92a..11c44ba 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -749,7 +749,6 @@ class ApiConsts { static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; - static setBackendURLs() { if (isDevelopment) { baseUrl = "https://uat.hmgwebservices.com/"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bd3e541..7d32262 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -67,6 +67,7 @@ class AppAssets { static const String rating_icon = '$svgBasePath/rating_icon.svg'; static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; + static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; //bottom navigation// diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 39c7a14..8786f7f 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -4,8 +4,6 @@ import 'dart:io'; import 'dart:ui'; import 'package:device_calendar/device_calendar.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:manage_calendar_events/manage_calendar_events.dart' as ios; import 'package:timezone/data/latest.dart' as tzl; @@ -137,14 +135,12 @@ class CalendarUtils { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } else { await _myPlugin.createEvent(calendarId: writableCalendars.id!, event: iosCalEvent).catchError((e) { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID iOS " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } } diff --git a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart index 3ca8396..b35cfef 100644 --- a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart +++ b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart @@ -32,6 +32,7 @@ class PrescriptionDetailResponseModel { String? sKU; num? scaleOffset; String? startDate; + bool? hasReminder; PrescriptionDetailResponseModel( {this.address, @@ -66,7 +67,8 @@ class PrescriptionDetailResponseModel { this.route, this.sKU, this.scaleOffset, - this.startDate}); + this.startDate, + this.hasReminder = false}); PrescriptionDetailResponseModel.fromJson(Map json) { address = json['Address']; @@ -102,6 +104,7 @@ class PrescriptionDetailResponseModel { sKU = json['SKU']; scaleOffset = json['ScaleOffset']; startDate = json['StartDate']; + hasReminder = false; } Map toJson() { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 23a818e..ff93f84 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -43,6 +43,14 @@ class PrescriptionsViewModel extends ChangeNotifier { notifyListeners(); } + setPrescriptionItemReminder(bool value, PrescriptionDetailResponseModel item) { + int index = prescriptionDetailsList.indexOf(item); + if (index != -1) { + prescriptionDetailsList[index].hasReminder = value; + notifyListeners(); + } + } + setIsSortByClinic(bool value) { isSortByClinic = value; if (isSortByClinic) { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 54a1136..cd59c15 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -781,5 +781,7 @@ abstract class LocaleKeys { static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; static const prescriptionDeliveryError = 'prescriptionDeliveryError'; + static const checkAvailability = 'checkAvailability'; + static const readInstructions = 'readInstructions'; } diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index d14da43..65dc550 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -29,6 +29,8 @@ class PrescriptionDetailPage extends StatefulWidget { class _PrescriptionDetailPageState extends State { late PrescriptionsViewModel prescriptionsViewModel; + bool _isSwitched = false; // Initial state of the switch + @override void initState() { scheduleMicrotask(() { @@ -46,266 +48,353 @@ class _PrescriptionDetailPageState extends State { title: LocaleKeys.prescriptions.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, ), - body: SingleChildScrollView( - child: Consumer(builder: (context, prescriptionVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - widget.prescriptionsResponseModel.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), - ], - ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.textColor, - iconSize: 13.h, - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - iconSize: 13.h, - text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.name!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - ], + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Consumer(builder: (context, prescriptionVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, - itemBuilder: (context, index) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + widget.prescriptionsResponseModel.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + icon: AppAssets.doctor_calendar_icon, + iconColor: AppColors.textColor, + iconSize: 13.h, + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Image.network( - prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, - width: 60.h, - height: 60.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), - ), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 8.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), - SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 16.h), - ], - ) + CustomButton( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + iconSize: 13.h, + text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), ], ), - ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.name!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], ), - ), - ); - }, - ).paddingSymmetrical(24.h, 0.h), - ], - ); - }), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, + itemBuilder: (context, index) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 14.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the consumption time".toText10(color: AppColors.textColorLight), + ], + ), + SizedBox(width: 12.h), + Switch( + activeColor: AppColors.successColor, + value: prescriptionVM.prescriptionDetailsList[index].hasReminder!, + onChanged: (newValue) { + setState(() { + prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); + }); + }, + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.checkAvailability.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 13, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ), + SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: LocaleKeys.readInstructions.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 13, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () {}, + backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.prescription_refill_icon, + iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], ), ); } diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 4aa1a1b..9574f28 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -196,7 +196,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -209,7 +209,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 018727c..61938b0 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -29,6 +29,7 @@ class AppColors { static const Color bgRedLightColor = Color(0xFFFEE9EA); static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); + static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); From 2f2a8f3f8fd52fe154b71b3523ec4f25d95bf5b7 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Thu, 4 Sep 2025 14:55:59 +0300 Subject: [PATCH 05/11] search lab result screen added --- assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 3 +- lib/core/app_assets.dart | 1 + lib/core/utils/size_utils.dart | 4 + lib/features/lab/lab_view_model.dart | 8 ++ lib/generated/locale_keys.g.dart | 1 + lib/presentation/lab/lab_orders_page.dart | 28 ++++- lib/presentation/lab/search_lab_report.dart | 116 ++++++++++++++++++++ lib/widgets/common_bottom_sheet.dart | 82 ++++++++++++++ 9 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 lib/presentation/lab/search_lab_report.dart create mode 100644 lib/widgets/common_bottom_sheet.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 718bf83..e0b2fd3 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -782,5 +782,6 @@ "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم." + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", + "searchLabReport" : "ابحث عن تقرير المختبر" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6743993..55338dc 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -778,5 +778,6 @@ "resultsPending": "Results Pending", "resultsAvailable": "Results Available", "viewReport": "View Report", - "prescriptionDeliveryError": "This clinic doesn't support refill" + "prescriptionDeliveryError": "This clinic doesn't support refill", + "searchLabReport" : "Search Lab Report" } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bd3e541..02e7026 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -75,6 +75,7 @@ class AppAssets { static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg'; + static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 0b37082..6ad1835 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -11,6 +11,10 @@ extension ResponsiveExtension on num { double get h => ((this * _width) / FIGMA_DESIGN_WIDTH); double get fSize => ((this * _width) / FIGMA_DESIGN_WIDTH); + static double get screenHeight => SizeUtils.height; + + /// Full screen width + static double get screenWidth => SizeUtils.width; } extension FormatExtension on double { diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index acd87eb..115b28d 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -12,6 +12,10 @@ class LabViewModel extends ChangeNotifier { List patientLabOrders = []; + List labSuggestionsList =[]; + + get labSuggestions => labSuggestionsList; + LabViewModel({required this.labRepo, required this.errorHandlerService}); initLabProvider() { @@ -42,4 +46,8 @@ class LabViewModel extends ChangeNotifier { }, ); } + + filterSuggestions(){ + + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 54a1136..f7566f3 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -781,5 +781,6 @@ abstract class LocaleKeys { static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; static const prescriptionDeliveryError = 'prescriptionDeliveryError'; + static const searchLabReport = 'searchLabReport'; } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 8a0ce26..97e2216 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -5,14 +5,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_config.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -25,7 +30,7 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; - + List?> labSuggestions = []; int? expandedIndex; @override @@ -57,7 +62,17 @@ class _LabOrdersPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.labResults.tr(context: context).toText24(isBold: true), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() { + if(model.isLabOrdersLoading){ + return; + }else { + labSuggestions = getLabSuggestions(model); + showCommonBottomSheet(context, child: SearchLabResultsContent(), + callBackFunc: () {}, + title: LocaleKeys.searchLabReport.tr(), + height: ResponsiveExtension.screenHeight, + isCloseButtonVisible: true); + } }), ], ), SizedBox(height: 16.h), @@ -262,4 +277,13 @@ class _LabOrdersPageState extends State { return ""; } } + getLabSuggestions(LabViewModel model) { + if(model.patientLabOrders.isEmpty){ + return []; + } + return model.patientLabOrders.map((m) => m.testDetails).toList(); + + + } + } diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart new file mode 100644 index 0000000..1c05174 --- /dev/null +++ b/lib/presentation/lab/search_lab_report.dart @@ -0,0 +1,116 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:sizer/sizer.dart'; + +class SearchLabResultsContent extends StatelessWidget { + const SearchLabResultsContent({super.key}); + + final List _chipLabels = const [ + "Blood Test", + "X-Ray", + "MRI Scan", + "CT Scan", + "Ultrasound", + "Urine Test", + "Allergy Test", + "Cholesterol Test", + "Diabetes Test", + "Thyroid Test", + ]; + + @override + Widget build(BuildContext context) { + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText:"Search lab results", + hintText: "Type test name", + controller: TextEditingController(), + isEnable: true, + prefix: null, + autoFocus: true, + isBorderAllowed: false, + padding: EdgeInsets.symmetric(vertical:ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), + + ), + SizedBox(height: ResponsiveExtension(20).h), + "Suggestions".toText16(isBold: true), + const SizedBox(height: 12), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Wrap( + alignment: WrapAlignment.start, + spacing: 10, + runSpacing: 10, + children: _chipLabels + .map((label) => SuggestionChip( + label: label, + onTap: () {}, + )) + .toList(), + ), + ), + ), + Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context), + ), + ), + ], + ); + } +} + +class SuggestionChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const SuggestionChip({ + super.key, + required this.label, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, // optional tap callback + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + ), + child: label.toText12( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart new file mode 100644 index 0000000..3d72864 --- /dev/null +++ b/lib/widgets/common_bottom_sheet.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +void showCommonBottomSheet(BuildContext context, {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true}) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: AppColors.scaffoldBgColor, + builder: (BuildContext context) { + return Container( + height: height, + decoration: const BoxDecoration( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: ButtonSheetContent( + title: title!, + isCloseButtonVisible: isCloseButtonVisible, + child: child, + ), + ); + }).then((value) { + callBackFunc(); + }); +} + +class ButtonSheetContent extends StatelessWidget { + final Widget child; + final String title; + final bool isCloseButtonVisible; + + const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20.h,), + Center( + child: Container( + margin: const EdgeInsets.only(top: 18, bottom: 12), + height: 4, + width: 40.h, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), + ), + ), + ), + + // Close button + isCloseButtonVisible + ? Padding( + padding: EdgeInsets.symmetric(horizontal: 16), child: Utils.buildSvgWithAssets( icon: AppAssets.closeBottomNav, + width: 32, + height: 32).onPress((){ + Navigator.of(context).pop(); + }), + ) + : SizedBox(), + + SizedBox(height: 20,), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + title, + style: TextStyle(fontSize: 27.h, fontWeight: FontWeight.bold), + ), + ), + const SizedBox(height: 16), + + Expanded(child: child) + ], + ); + + } +} From e8cc6407b62ff3f6965378074b476e51bf6ad246 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 4 Sep 2025 14:58:00 +0300 Subject: [PATCH 06/11] Insurance update implementation contd. --- assets/images/svg/cancel_circle.svg | 3 + assets/images/svg/insurance_history_icon.svg | 4 + assets/images/svg/update_insurance_card.svg | 4 + lib/core/app_assets.dart | 3 + lib/core/dependencies.dart | 10 ++ lib/features/insurance/insurance_repo.dart | 77 +++++++++++ .../insurance/insurance_view_model.dart | 43 ++++++ ...ient_insurance_details_response_model.dart | 96 +++++++++++++ lib/main.dart | 7 + lib/presentation/home/navigation_screen.dart | 15 +- .../home/widgets/small_service_card.dart | 6 + .../insurance/insurance_home_page.dart | 82 +++++++++++ .../widgets/patient_insurance_card.dart | 128 ++++++++++++++++++ .../medical_file/medical_file_page.dart | 112 +++------------ 14 files changed, 485 insertions(+), 105 deletions(-) create mode 100644 assets/images/svg/cancel_circle.svg create mode 100644 assets/images/svg/insurance_history_icon.svg create mode 100644 assets/images/svg/update_insurance_card.svg create mode 100644 lib/features/insurance/insurance_repo.dart create mode 100644 lib/features/insurance/insurance_view_model.dart create mode 100644 lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart create mode 100644 lib/presentation/insurance/insurance_home_page.dart create mode 100644 lib/presentation/insurance/widgets/patient_insurance_card.dart diff --git a/assets/images/svg/cancel_circle.svg b/assets/images/svg/cancel_circle.svg new file mode 100644 index 0000000..3ceda6f --- /dev/null +++ b/assets/images/svg/cancel_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/insurance_history_icon.svg b/assets/images/svg/insurance_history_icon.svg new file mode 100644 index 0000000..4ae2a1c --- /dev/null +++ b/assets/images/svg/insurance_history_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/update_insurance_card.svg b/assets/images/svg/update_insurance_card.svg new file mode 100644 index 0000000..9b21aab --- /dev/null +++ b/assets/images/svg/update_insurance_card.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 7d32262..530064e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -68,6 +68,9 @@ class AppAssets { static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; + static const String insurance_history_icon = '$svgBasePath/insurance_history_icon.svg'; + static const String cancel_circle_icon = '$svgBasePath/cancel_circle.svg'; + static const String update_insurance_card_icon = '$svgBasePath/update_insurance_card.svg'; //bottom navigation// diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 65fd2c6..aa32fa6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -6,6 +6,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; @@ -65,6 +67,7 @@ class AppDependencies { getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -90,6 +93,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart new file mode 100644 index 0000000..a5c624f --- /dev/null +++ b/lib/features/insurance/insurance_repo.dart @@ -0,0 +1,77 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class InsuranceRepo { + Future>>> getPatientInsuranceDetails({required String patientId}); +} + +class InsuranceRepoImp implements InsuranceRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + InsuranceRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientInsuranceDetails({required String patientId}) async { + final mapDevice = { + "isDentalAllowedBackend": false, + "VersionID": 50.0, + "Channel": 3, + "LanguageID": 2, + "IPAdress": "10.20.10.20", + "generalid": "Cs2020@2016\$2958", + "Latitude": 0.0, + "Longitude": 0.0, + "DeviceTypeID": 1, + "PatientType": 1, + "PatientTypeID": 1, + "TokenID": "@dm!n", + "PatientID": "3628599", + "PatientOutSA": "0", + "SessionID": "03478TYC02N80874CTYN04883475!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PAtIENTS_INSURANCE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['List_PatientInsuranceCard']; + if (list == null || list.isEmpty) { + throw Exception("insurance list is empty"); + } + + final labOrders = list.map((item) => PatientInsuranceDetailsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + + throw UnimplementedError(); + } +} diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart new file mode 100644 index 0000000..545a32a --- /dev/null +++ b/lib/features/insurance/insurance_view_model.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class InsuranceViewModel extends ChangeNotifier { + bool isInsuranceLoading = false; + + InsuranceRepo insuranceRepo; + ErrorHandlerService errorHandlerService; + + List patientInsuranceList = []; + + InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService}); + + initInsuranceProvider() { + patientInsuranceList.clear(); + isInsuranceLoading = true; + getPatientInsuranceDetails(); + notifyListeners(); + } + + Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientInsuranceList = apiResponse.data!; + isInsuranceLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart new file mode 100644 index 0000000..b734abc --- /dev/null +++ b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart @@ -0,0 +1,96 @@ +class PatientInsuranceDetailsResponseModel { + String? setupID; + int? projectID; + bool? isActive; + int? patientID; + int? companyID; + int? subCategoryID; + dynamic companyType; + String? patientCardID; + String? cardValidTo; + int? patientCreditLimit; + String? subPolicyNo; + String? companyName; + String? companyNameN; + String? subCategoryDesc; + dynamic subCategoryDescN; + bool? isElectronicClaim; + String? subCategoryValidTo; + dynamic groupID; + String? groupName; + dynamic groupNameN; + String? insurancePolicyNo; + + PatientInsuranceDetailsResponseModel( + {this.setupID, + this.projectID, + this.isActive, + this.patientID, + this.companyID, + this.subCategoryID, + this.companyType, + this.patientCardID, + this.cardValidTo, + this.patientCreditLimit, + this.subPolicyNo, + this.companyName, + this.companyNameN, + this.subCategoryDesc, + this.subCategoryDescN, + this.isElectronicClaim, + this.subCategoryValidTo, + this.groupID, + this.groupName, + this.groupNameN, + this.insurancePolicyNo}); + + PatientInsuranceDetailsResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + isActive = json['IsActive']; + patientID = json['PatientID']; + companyID = json['CompanyID']; + subCategoryID = json['SubCategoryID']; + companyType = json['CompanyType']; + patientCardID = json['PatientCardID']; + cardValidTo = json['CardValidTo']; + patientCreditLimit = json['PatientCreditLimit']; + subPolicyNo = json['SubPolicyNo']; + companyName = json['CompanyName']; + companyNameN = json['CompanyNameN']; + subCategoryDesc = json['SubCategoryDesc']; + subCategoryDescN = json['SubCategoryDescN']; + isElectronicClaim = json['IsElectronicClaim']; + subCategoryValidTo = json['SubCategoryValidTo']; + groupID = json['GroupID']; + groupName = json['GroupName']; + groupNameN = json['GroupNameN']; + insurancePolicyNo = json['InsurancePolicyNo']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['IsActive'] = this.isActive; + data['PatientID'] = this.patientID; + data['CompanyID'] = this.companyID; + data['SubCategoryID'] = this.subCategoryID; + data['CompanyType'] = this.companyType; + data['PatientCardID'] = this.patientCardID; + data['CardValidTo'] = this.cardValidTo; + data['PatientCreditLimit'] = this.patientCreditLimit; + data['SubPolicyNo'] = this.subPolicyNo; + data['CompanyName'] = this.companyName; + data['CompanyNameN'] = this.companyNameN; + data['SubCategoryDesc'] = this.subCategoryDesc; + data['SubCategoryDescN'] = this.subCategoryDescN; + data['IsElectronicClaim'] = this.isElectronicClaim; + data['SubCategoryValidTo'] = this.subCategoryValidTo; + data['GroupID'] = this.groupID; + data['GroupName'] = this.groupName; + data['GroupNameN'] = this.groupNameN; + data['InsurancePolicyNo'] = this.insurancePolicyNo; + return data; + } +} diff --git a/lib/main.dart b/lib/main.dart index 035c6bf..ce69f5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; @@ -71,6 +72,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index f3e088b..152bbd2 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -20,22 +20,19 @@ class _LandingNavigationState extends State { body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), - children: const [ - LandingPage(), + children: [ + const LandingPage(), MedicalFilePage(), - LandingPage(), - LandingPage(), - LandingPage(), + const LandingPage(), + const LandingPage(), + const LandingPage(), ], ), bottomNavigationBar: BottomNavigation( currentIndex: _currentIndex, onTap: (index) { setState(() => _currentIndex = index); - _pageController.animateToPage( - index, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut); + _pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); }, ), ); diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 297ed2b..f988e18 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -78,6 +79,11 @@ class SmallServiceCard extends StatelessWidget { ); break; case "insurance_update": + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); break; default: // Handle unknown service diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart new file mode 100644 index 0000000..d348f8d --- /dev/null +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -0,0 +1,82 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class InsuranceHomePage extends StatefulWidget { + const InsuranceHomePage({super.key}); + + @override + State createState() => _InsuranceHomePageState(); +} + +class _InsuranceHomePageState extends State { + late InsuranceViewModel insuranceViewModel; + + @override + void initState() { + scheduleMicrotask(() { + insuranceViewModel.initInsuranceProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.insurance.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: SingleChildScrollView( + child: Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), + CustomButton( + icon: AppAssets.insurance_history_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 21.h, + text: LocaleKeys.history.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart new file mode 100644 index 0000000..7726b94 --- /dev/null +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -0,0 +1,128 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class PatientInsuranceCard extends StatelessWidget { + PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); + + PatientInsuranceDetailsResponseModel insuranceCardDetailsModel; + bool isInsuranceExpired = false; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Haroon Amjad".toText18(isBold: true), + "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), + ], + ), + CustomButton( + icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 13.h, + text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active", + onPressed: () {}, + backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), + borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), + textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + SizedBox(height: 12.h), + insuranceCardDetailsModel.groupName!.toText12(isBold: true), + insuranceCardDetailsModel.companyName!.toText12(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + children: [ + CustomButton( + icon: AppAssets.doctor_calendar_icon, + iconColor: AppColors.blackColor, + iconSize: 13.h, + text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + children: [ + CustomButton( + text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + SizedBox(height: 10.h), + isInsuranceExpired + ? CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () {}, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ) + : Container(), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 7fa4fe9..da5227a 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1,20 +1,28 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; class MedicalFilePage extends StatelessWidget { - const MedicalFilePage({super.key}); + MedicalFilePage({super.key}); + + late InsuranceViewModel insuranceViewModel; @override Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -162,101 +170,13 @@ class MedicalFilePage extends StatelessWidget { ), SizedBox(height: 16.h), //Insurance Tab Data - Container( - // height: 150.h, - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Haroon Amjad".toText18(isBold: true), - "Policy: 223123345".toText12(isBold: true, color: AppColors.lightGrayColor), - ], - ), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Insurance Expired", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 12.h), - "NCCI".toText12(isBold: true), - "NC_Dr Sulaiman Al Habib Medical Group".toText12(isBold: true), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Expiry: 18 Mar, 2025", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - SizedBox(width: 5.h), - CustomButton( - text: "Patient Card ID: 3628599", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 10.h), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () {}, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ), - ), + Consumer(builder: (context, insuranceVM, child) { + return insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget() + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + }), SizedBox(height: 10.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), From 333b813f907bfebd9eefb6aca09118093439e000 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 4 Sep 2025 15:22:54 +0300 Subject: [PATCH 07/11] bottom sheet fixes --- .../insurance/insurance_home_page.dart | 13 ++- .../widgets/patient_insurance_card.dart | 5 ++ lib/presentation/lab/lab_orders_page.dart | 2 +- lib/widgets/common_bottom_sheet.dart | 79 ++++++++++--------- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index d348f8d..eea50c5 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -9,9 +9,11 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -56,7 +58,16 @@ class _InsuranceHomePageState extends State { iconColor: AppColors.primaryRedColor, iconSize: 21.h, text: LocaleKeys.history.tr(context: context), - onPressed: () {}, + onPressed: () { + showCommonBottomSheet( + context, + child: Container(), + callBackFunc: () {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.5, + isCloseButtonVisible: false, + ); + }, backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), borderColor: AppColors.primaryRedColor.withOpacity(0.0), textColor: AppColors.primaryRedColor, diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 7726b94..2701233 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -5,10 +5,12 @@ import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; class PatientInsuranceCard extends StatelessWidget { PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); @@ -16,8 +18,11 @@ class PatientInsuranceCard extends StatelessWidget { PatientInsuranceDetailsResponseModel insuranceCardDetailsModel; bool isInsuranceExpired = false; + late InsuranceViewModel insuranceViewModel; + @override Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); return Container( width: double.infinity, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 97e2216..e829c93 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -65,7 +65,7 @@ class _LabOrdersPageState extends State { Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() { if(model.isLabOrdersLoading){ return; - }else { + } else { labSuggestions = getLabSuggestions(model); showCommonBottomSheet(context, child: SearchLabResultsContent(), callBackFunc: () {}, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 3d72864..f3d7425 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -9,14 +9,13 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; void showCommonBottomSheet(BuildContext context, {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true}) { showModalBottomSheet( context: context, - isScrollControlled: true, + isScrollControlled: false, + showDragHandle: false, backgroundColor: AppColors.scaffoldBgColor, builder: (BuildContext context) { return Container( height: height, - decoration: const BoxDecoration( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h), child: ButtonSheetContent( title: title!, isCloseButtonVisible: isCloseButtonVisible, @@ -37,46 +36,48 @@ class ButtonSheetContent extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20.h,), - Center( - child: Container( - margin: const EdgeInsets.only(top: 18, bottom: 12), - height: 4, - width: 40.h, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox( + // height: 20.h, + // ), + Center( + child: Container( + margin: const EdgeInsets.only(top: 18, bottom: 12), + height: 4, + width: 40.h, + decoration: BoxDecoration( + color: Colors.grey[400], + borderRadius: BorderRadius.circular(2), ), ), + ), - // Close button - isCloseButtonVisible - ? Padding( - padding: EdgeInsets.symmetric(horizontal: 16), child: Utils.buildSvgWithAssets( icon: AppAssets.closeBottomNav, - width: 32, - height: 32).onPress((){ - Navigator.of(context).pop(); - }), - ) - : SizedBox(), + // Close button + isCloseButtonVisible + ? Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() { + Navigator.of(context).pop(); + }), + ) + : SizedBox(), - SizedBox(height: 20,), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - title, - style: TextStyle(fontSize: 27.h, fontWeight: FontWeight.bold), - ), + SizedBox( + height: 20, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + title, + style: TextStyle(fontSize: 27.h, fontWeight: FontWeight.bold), ), - const SizedBox(height: 16), - - Expanded(child: child) - ], - ); + ), + const SizedBox(height: 16), + Expanded(child: child) + ], + ); } } From 3855ba276f5791d98c5701e148f0e6f3d1394fb2 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 4 Sep 2025 16:22:43 +0300 Subject: [PATCH 08/11] calender & signup fixes. --- lib/core/app_state.dart | 13 +- .../authentication_view_model.dart | 47 ++++- lib/presentation/authentication/login.dart | 2 +- lib/presentation/authentication/register.dart | 37 ++-- .../authentication/register_step2.dart | 199 ++++++++++-------- lib/widgets/input_widget.dart | 56 +++-- pubspec.lock | 10 +- pubspec.yaml | 1 + 8 files changed, 226 insertions(+), 139 deletions(-) diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 7c42042..eb9315c 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -22,13 +22,8 @@ class AppState { set setUserLong(v) => userLong = v; - final PostParamsModel _postParamsInitConfig = PostParamsModel( - channel: 3, - versionID: ApiConsts.VERSION_ID, - ipAddress: '10.20.10.20', - generalId: 'Cs2020@2016\$2958', - deviceTypeID: "2", - sessionID: 'TMRhVmkGhOsvamErw'); + final PostParamsModel _postParamsInitConfig = + PostParamsModel(channel: 3, versionID: ApiConsts.VERSION_ID, ipAddress: '10.20.10.20', generalId: 'Cs2020@2016\$2958', deviceTypeID: "2", sessionID: 'TMRhVmkGhOsvamErw'); void setPostParamsInitConfig() { isAuthenticated = false; @@ -50,7 +45,9 @@ class AppState { bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar"; - int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + int getLanguageID() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar" ? 1 : 2; + + String? getLanguageCode() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode; AuthenticatedUser? _authenticatedUser; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 9abd673..c34c339 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,8 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -21,18 +25,53 @@ class AuthenticationViewModel extends ChangeNotifier { required this.dialogService, }); - final TextEditingController nationalIdController = TextEditingController(); - final TextEditingController phoneNumberController = TextEditingController(); - CountryEnum selectedCountry = CountryEnum.saudiArabia; + final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; + MaritalStatusTypeEnum? maritalStatus; + GenderTypeEnum? genderType; bool isTermsAccepted = false; + List? countriesList; + NationalityCountries? pickedCountryByUAEUser; void login() { if (ValidationUtils.isValidatePhoneAndId(nationalId: nationalIdController.text, phoneNumber: phoneNumberController.text)) { } else {} } + void clearDefaults() { + nationalIdController.clear(); + phoneNumberController.clear(); + dobController.clear(); + maritalStatus = null; + genderType = null; + isTermsAccepted = false; + selectedCountrySignup = CountryEnum.saudiArabia; + pickedCountryByUAEUser = null; + } + void onCountryChange(CountryEnum country) { - selectedCountry = country; + selectedCountrySignup = country; + notifyListeners(); + } + + void loadCountriesData({required BuildContext context}) async { + final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); + final List data = json.decode(response); + countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); + } + + void onMaritalStatusChange(String? status) { + maritalStatus = MaritalStatusTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onGenderChange(String? status) { + genderType = GenderTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onUAEUserCountrySelection(String? value) { + pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value); notifyListeners(); } diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index f171443..aef877f 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -151,7 +151,7 @@ class _LoginScreen extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - countryCode: authVM.selectedCountry.countryCode, + countryCode: authVM.selectedCountrySignup.countryCode, initialPhoneNumber: "", textController: authVM.phoneNumberController, isEnableCountryDropdown: true, diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 85ff402..4756cba 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -83,30 +83,31 @@ class _RegisterNew extends State { children: [ CustomCountryDropdown( countryList: CountryEnum.values, - onCountryChange: (CountryEnum? value) {}, + onCountryChange: authVm.onCountryChange, isRtl: Directionality.of(context) == TextDirection.LTR, ).withVerticalPadding(8.h), Divider(height: 1.h), TextInputWidget( - labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: "xxxxxxxxx", - controller: TextEditingController(), - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.student_card, - onChange: (value) { - print(value); - }).withVerticalPadding(8), + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.student_card, + // onChange: (value) { + // print(value); + // } + ).withVerticalPadding(8), Divider(height: 1), TextInputWidget( labelText: LocaleKeys.dob.tr(), hintText: "11 July, 1994", - controller: TextEditingController(), + controller: authVm.dobController, isEnable: true, prefix: null, isAllowRadius: true, @@ -114,7 +115,7 @@ class _RegisterNew extends State { isAllowLeadingIcon: true, padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.birthday_cake, - onChange: (value) {}, + selectionType: SelectionTypeEnum.calendar, ).withVerticalPadding(8), ], ), @@ -211,7 +212,7 @@ class _RegisterNew extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - countryCode: authVM.selectedCountry.countryCode, + countryCode: authVM.selectedCountrySignup.countryCode, initialPhoneNumber: "", textController: authVM.phoneNumberController, isEnableCountryDropdown: true, diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 660ba69..483ce5c 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; @@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; @@ -18,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; class RegisterNewStep2 extends StatefulWidget { var nHICData; @@ -31,30 +34,31 @@ class RegisterNewStep2 extends StatefulWidget { class _RegisterNew extends State { bool isFromDubai = true; - List countriesList = []; - AppState? appState; - GenderTypeEnum? selectedGenderType; - MaritalStatusTypeEnum? selectedMaritalStatusType; - CountryEnum? selectedCountry; + AuthenticationViewModel? authVM; @override void initState() { super.initState(); + authVM = context.read(); + authVM!.loadCountriesData(context: context); // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); - loadCountriesList(); } - loadCountriesList() async { - appState = getIt.get(); - final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); - final List data = json.decode(response); - countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); + @override + void dispose() { + super.dispose(); + authVM!.clearDefaults(); } + @override Widget build(BuildContext context) { + AppState appState = getIt.get(); return Scaffold( appBar: CustomAppBar( - onBackPressed: () {}, + onBackPressed: () { + Navigator.of(context).pop(); + authVM!.clearDefaults(); + }, onLanguageChanged: (lang) {}, hideLogoAndLang: true, ), @@ -84,10 +88,7 @@ class _RegisterNew extends State { isReadOnly: isFromDubai ? false : true, leadingIcon: AppAssets.user_circle) .paddingSymmetrical(0.h, 16.h), - Divider( - height: 1, - color: AppColors.greyColor, - ), + Divider(height: 1, color: AppColors.greyColor), TextInputWidget( labelText: LocaleKeys.nationalIdNumber.tr(), hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), @@ -100,28 +101,28 @@ class _RegisterNew extends State { isReadOnly: true, leadingIcon: AppAssets.student_card) .paddingSymmetrical(0.h, 16.h), - Divider( - height: 1, - color: AppColors.greyColor, - ), + Divider(height: 1, color: AppColors.greyColor), isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.gender.tr(), - hintText: LocaleKeys.malE.tr(), - isEnable: true, - dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), - selectedValue: appState!.isArabic() ? selectedGenderType!.typeAr : selectedGenderType?.type, - // selectionType: SelectionType.dropdown, - onChange: (val) { - if (val != null) {} - }, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.user_full, - ).withVerticalPadding(8) + ? Selector( + selector: (_, authViewModel) => authViewModel.genderType, + shouldRebuild: (previous, next) => previous != next, + builder: (context, genderType, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.gender.tr(), + hintText: LocaleKeys.malE.tr(), + isEnable: true, + dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: genderType != null ? (appState!.isArabic() ? genderType!.typeAr : genderType!.type) : "", + onChange: authVM.onGenderChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.user_full, + ).withVerticalPadding(8); + }) : TextInputWidget( labelText: LocaleKeys.gender.tr(), hintText: (widget.nHICData!.gender ?? ""), @@ -135,25 +136,29 @@ class _RegisterNew extends State { leadingIcon: AppAssets.user_full, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), - Divider( - height: 1, - color: AppColors.greyColor, - ), + Divider(height: 1, color: AppColors.greyColor), isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.maritalStatus.tr(), - hintText: LocaleKeys.married.tr(), - isEnable: true, - dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), - selectedValue: appState!.isArabic() ? selectedMaritalStatusType!.typeAr : selectedMaritalStatusType?.type, - onChange: (val) {}, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.smart_phone, - ).withVerticalPadding(8) + ? Selector( + selector: (_, authViewModel) => authViewModel.maritalStatus, + shouldRebuild: (previous, next) => previous != next, + builder: (context, maritalStatus, child) { + final authVM = context.read(); // For onChange + return DropdownWidget( + labelText: LocaleKeys.maritalStatus.tr(), + hintText: LocaleKeys.married.tr(), + isEnable: true, + dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: maritalStatus != null ? (appState!.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "", + onChange: authVM.onMaritalStatusChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.smart_phone, + ).withVerticalPadding(8); + }, + ) : TextInputWidget( labelText: LocaleKeys.maritalStatus.tr(), hintText: appState!.isArabic() @@ -168,30 +173,41 @@ class _RegisterNew extends State { leadingIcon: AppAssets.smart_phone, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), - Divider( - height: 1, - color: AppColors.greyColor, - ), + Divider(height: 1, color: AppColors.greyColor), isFromDubai - ? DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText: LocaleKeys.uae.tr(), - isEnable: true, - dropdownItems: countriesList.map((e) => appState!.isArabic() ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: appState!.isArabic() ? selectedCountry!.nameArabic ?? "" : selectedCountry?.name ?? "", - onChange: (val) {}, - isBorderAllowed: false, - hasSelectionCustomIcon: true, - isAllowRadius: false, - padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8) + ? Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( + selector: (context, authViewModel) { + final appState = getIt.get(); + return (countriesList: authViewModel.countriesList, selectedCountry: authViewModel.pickedCountryByUAEUser, isArabic: appState.isArabic()); + }, + shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, + builder: (context, data, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.country.tr(), + hintText: LocaleKeys.uae.tr(), + isEnable: true, + dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), + selectedValue: data.selectedCountry != null + ? data.isArabic + ? data.selectedCountry!.nameN ?? "" + : data.selectedCountry!.name ?? "" + : "", + onChange: authVM.onUAEUserCountrySelection, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.globe, + ).withVerticalPadding(8); + }, + ) : TextInputWidget( labelText: LocaleKeys.nationality.tr(), - hintText: appState!.isArabic() - ? (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") - : (countriesList.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + hintText: appState.isArabic() + ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), isEnable: true, prefix: null, isAllowRadius: false, @@ -208,7 +224,7 @@ class _RegisterNew extends State { TextInputWidget( labelText: LocaleKeys.mobileNumber.tr(), hintText: ("widget.payload.mobileNo" ?? ""), - controller: null, + controller: authVM!.phoneNumberController, isEnable: true, prefix: null, isAllowRadius: false, @@ -223,20 +239,17 @@ class _RegisterNew extends State { color: AppColors.greyColor, ), TextInputWidget( - labelText: LocaleKeys.dob.tr(), - hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), - controller: null, - isEnable: true, - prefix: null, - isBorderAllowed: false, - isAllowLeadingIcon: true, - isReadOnly: true, - // : SelectionType.calendar, - // selectedValue: widget.payload.dob != null ? Utils.formatDateToDisplay(widget.payload.dob.toString()) : null, - // selectionCustomIcon: AppAssets.calendar, - leadingIcon: AppAssets.birthday_cake, - onChange: (value) {}) - .paddingSymmetrical(0.h, 16.h), + labelText: LocaleKeys.dob.tr(), + hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + controller: authVM!.dobController, + isEnable: true, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.birthday_cake, + selectionType: SelectionTypeEnum.calendar, + ).paddingSymmetrical(0.h, 16.h), ], ), ), @@ -246,12 +259,12 @@ class _RegisterNew extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel, + text: LocaleKeys.cancel.tr(), icon: AppAssets.cancel, onPressed: () { Navigator.of(context).pop(); + authVM!.clearDefaults(); }, - // fontFamily: context.fontFamily, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index ca19ee6..0f520c6 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import '../core/dependencies.dart'; + // TODO: Import AppColors if bgRedColor is defined there // import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart'; @@ -29,6 +34,7 @@ class TextInputWidget extends StatelessWidget { final bool hasError; final String? errorMessage; Function(CountryEnum)? onCountryChange; + SelectionTypeEnum? selectionType; // final List countryList; // final Function(Country)? onCountryChange; @@ -54,6 +60,7 @@ class TextInputWidget extends StatelessWidget { this.hasError = false, this.errorMessage, this.onCountryChange, + this.selectionType, // this.countryList = const [], // this.onCountryChange, }) : super(key: key); @@ -97,6 +104,7 @@ class TextInputWidget extends StatelessWidget { ], ), ), + if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), ], ), ), @@ -128,6 +136,38 @@ class TextInputWidget extends StatelessWidget { child: Utils.buildSvgWithAssets(icon: leadingIcon!)); } + Widget _buildTrailingIcon(BuildContext context) { + final AppState appState = getIt.get(); + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.zero, + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor), + child: GestureDetector( + onTap: () async { + bool isGregorian = true; + final picked = await showHijriGregBottomSheet(context, + switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), + language: appState.getLanguageCode()!, + initialDate: DateTime.now(), + okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + onCalendarTypeChanged: (bool value) { + isGregorian = value; + }); + if (picked != null && onChange != null) { + // if (onCalendarTypeChanged != null) { + // onCalendarTypeChanged.call(isGregorian); + // } + onChange!(picked.toIso8601String()); + } + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.calendar), + ), + ); + } + Widget _buildLabelText() { return Text( labelText, @@ -154,23 +194,11 @@ class TextInputWidget extends StatelessWidget { onChanged: onChange, focusNode: focusNode, autofocus: autoFocus, - style: TextStyle( - fontSize: 14.fSize, - height: 21 / 14, - fontWeight: FontWeight.w500, - color: AppColors.textColor, - letterSpacing: -0.2, - ), + style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), decoration: InputDecoration( isDense: true, hintText: hintText, - hintStyle: TextStyle( - fontSize: 14.fSize, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: Color(0xff898A8D), - letterSpacing: -0.2, - ), + hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), prefixIconConstraints: BoxConstraints(minWidth: 45.h), prefixIcon: prefix == null ? null diff --git a/pubspec.lock b/pubspec.lock index 5665d11..e2ebb3b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -742,6 +742,14 @@ packages: url: "https://pub.dev" source: hosted version: "13.1.3" + hijri_gregorian_calendar: + dependency: "direct main" + description: + name: hijri_gregorian_calendar + sha256: "9d23b52192783c1ad134b1ac001be7977342cb579c6b380647b6494fbd464d29" + url: "https://pub.dev" + source: hosted + version: "0.0.4" html: dependency: transitive description: @@ -1636,5 +1644,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.8.1 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0ec8324..cfb94d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: web: any flutter_staggered_animations: ^1.1.1 smooth_corner: ^1.1.1 + hijri_gregorian_calendar: ^0.0.4 dev_dependencies: flutter_test: From 2f9fbaef7ab183d70ab490f8e995c39d2fbd1617 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 4 Sep 2025 16:25:49 +0300 Subject: [PATCH 09/11] insurance update implementation contd. --- assets/images/svg/close_bottom_sheet_icon.svg | 4 ++ lib/core/app_assets.dart | 2 +- .../insurance/insurance_view_model.dart | 7 +++ .../insurance/insurance_home_page.dart | 13 ++-- .../insurance/widgets/insurance_history.dart | 39 ++++++++++++ lib/widgets/common_bottom_sheet.dart | 59 ++++++++++--------- 6 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 assets/images/svg/close_bottom_sheet_icon.svg create mode 100644 lib/presentation/insurance/widgets/insurance_history.dart diff --git a/assets/images/svg/close_bottom_sheet_icon.svg b/assets/images/svg/close_bottom_sheet_icon.svg new file mode 100644 index 0000000..599ce85 --- /dev/null +++ b/assets/images/svg/close_bottom_sheet_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 2077440..7f1b6d1 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -71,9 +71,9 @@ class AppAssets { static const String insurance_history_icon = '$svgBasePath/insurance_history_icon.svg'; static const String cancel_circle_icon = '$svgBasePath/cancel_circle.svg'; static const String update_insurance_card_icon = '$svgBasePath/update_insurance_card.svg'; + static const String close_bottom_sheet_icon = '$svgBasePath/close_bottom_sheet_icon.svg'; //bottom navigation// - static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 545a32a..fac09da 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class InsuranceViewModel extends ChangeNotifier { bool isInsuranceLoading = false; + bool isInsuranceHistoryLoading = false; InsuranceRepo insuranceRepo; ErrorHandlerService errorHandlerService; @@ -17,10 +18,16 @@ class InsuranceViewModel extends ChangeNotifier { initInsuranceProvider() { patientInsuranceList.clear(); isInsuranceLoading = true; + isInsuranceHistoryLoading = true; getPatientInsuranceDetails(); notifyListeners(); } + setIsInsuranceHistoryLoading(bool val) { + isInsuranceHistoryLoading = val; + notifyListeners(); + } + Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index eea50c5..9f9271a 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -17,6 +17,8 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import 'widgets/insurance_history.dart'; + class InsuranceHomePage extends StatefulWidget { const InsuranceHomePage({super.key}); @@ -59,14 +61,9 @@ class _InsuranceHomePageState extends State { iconSize: 21.h, text: LocaleKeys.history.tr(context: context), onPressed: () { - showCommonBottomSheet( - context, - child: Container(), - callBackFunc: () {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.5, - isCloseButtonVisible: false, - ); + insuranceVM.setIsInsuranceHistoryLoading(true); + showCommonBottomSheet(context, + child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.5, isCloseButtonVisible: false, isFullScreen: false); }, backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), borderColor: AppColors.primaryRedColor.withOpacity(0.0), diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart new file mode 100644 index 0000000..0708c7c --- /dev/null +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -0,0 +1,39 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class InsuranceHistory extends StatelessWidget { + InsuranceHistory({super.key}); + + late InsuranceViewModel insuranceViewModel; + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.history.tr(context: context).toText24(isBold: true), + Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon).onPress(() { + Navigator.of(context).pop(); + }), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceHistoryLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) : Container() + ], + ); + }); + } +} diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index f3d7425..f6039eb 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -2,14 +2,19 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -void showCommonBottomSheet(BuildContext context, {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true}) { +void showCommonBottomSheet(BuildContext context, + {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true, bool isFullScreen = true}) { showModalBottomSheet( + sheetAnimationStyle: AnimationStyle( + duration: Duration(milliseconds: 500), // Custom animation duration + reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration + ), context: context, - isScrollControlled: false, + isScrollControlled: true, showDragHandle: false, backgroundColor: AppColors.scaffoldBgColor, builder: (BuildContext context) { @@ -19,6 +24,7 @@ void showCommonBottomSheet(BuildContext context, {required Widget child, require child: ButtonSheetContent( title: title!, isCloseButtonVisible: isCloseButtonVisible, + isFullScreen: isFullScreen, child: child, ), ); @@ -31,8 +37,9 @@ class ButtonSheetContent extends StatelessWidget { final Widget child; final String title; final bool isCloseButtonVisible; + final bool isFullScreen; - const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title}); + const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title, required this.isFullScreen}); @override Widget build(BuildContext context) { @@ -42,20 +49,20 @@ class ButtonSheetContent extends StatelessWidget { // SizedBox( // height: 20.h, // ), - Center( - child: Container( - margin: const EdgeInsets.only(top: 18, bottom: 12), - height: 4, - width: 40.h, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(2), - ), - ), - ), + // Center( + // child: Container( + // margin: const EdgeInsets.only(top: 18, bottom: 12), + // height: 4, + // width: 40.h, + // decoration: BoxDecoration( + // color: Colors.grey[400], + // borderRadius: BorderRadius.circular(2), + // ), + // ), + // ), // Close button - isCloseButtonVisible + isCloseButtonVisible && isFullScreen ? Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() { @@ -64,17 +71,15 @@ class ButtonSheetContent extends StatelessWidget { ) : SizedBox(), - SizedBox( - height: 20, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text( - title, - style: TextStyle(fontSize: 27.h, fontWeight: FontWeight.bold), - ), - ), - const SizedBox(height: 16), + isFullScreen + ? Column( + children: [ + SizedBox(height: 20.h), + Padding(padding: EdgeInsets.symmetric(horizontal: 16.h), child: title.toText24(isBold: true)), + SizedBox(height: 16.h), + ], + ) + : SizedBox(), Expanded(child: child) ], From 29aae347269f672b460da36242c9e6a51f0947fc Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Thu, 4 Sep 2025 16:27:08 +0300 Subject: [PATCH 10/11] lab search added --- lib/features/lab/lab_view_model.dart | 12 ++- lib/presentation/lab/lab_orders_page.dart | 13 +-- lib/presentation/lab/search_lab_report.dart | 97 +++++++++++++++------ 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 115b28d..94ddd87 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -12,9 +12,9 @@ class LabViewModel extends ChangeNotifier { List patientLabOrders = []; - List labSuggestionsList =[]; + late List _labSuggestionsList = []; - get labSuggestions => labSuggestionsList; + List get labSuggestions => _labSuggestionsList; LabViewModel({required this.labRepo, required this.errorHandlerService}); @@ -38,6 +38,7 @@ class LabViewModel extends ChangeNotifier { patientLabOrders = apiResponse.data!; isLabOrdersLoading = false; isLabResultsLoading = false; + filterSuggestions(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -48,6 +49,13 @@ class LabViewModel extends ChangeNotifier { } filterSuggestions(){ + final List labels = patientLabOrders + .expand((order) => order.testDetails!) // flatten testDetails + .map((detail) => detail.description) // pick description + .whereType() // remove nulls if any + .toList(); + _labSuggestionsList = labels.toSet().toList(); // remove duplicates by converting to a set and back to a list + notifyListeners(); } } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 97e2216..ce70f9b 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; -import 'package:hmg_patient_app_new/core/utils/size_config.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -15,7 +14,6 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; @@ -66,8 +64,8 @@ class _LabOrdersPageState extends State { if(model.isLabOrdersLoading){ return; }else { - labSuggestions = getLabSuggestions(model); - showCommonBottomSheet(context, child: SearchLabResultsContent(), + + showCommonBottomSheet(context, child: SearchLabResultsContent(labSuggestionsList: model.labSuggestions), callBackFunc: () {}, title: LocaleKeys.searchLabReport.tr(), height: ResponsiveExtension.screenHeight, @@ -277,13 +275,6 @@ class _LabOrdersPageState extends State { return ""; } } - getLabSuggestions(LabViewModel model) { - if(model.patientLabOrders.isEmpty){ - return []; - } - return model.patientLabOrders.map((m) => m.testDetails).toList(); - } - } diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart index 1c05174..d513b35 100644 --- a/lib/presentation/lab/search_lab_report.dart +++ b/lib/presentation/lab/search_lab_report.dart @@ -9,25 +9,54 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:sizer/sizer.dart'; -class SearchLabResultsContent extends StatelessWidget { - const SearchLabResultsContent({super.key}); +class SearchLabResultsContent extends StatefulWidget { + final List labSuggestionsList; - final List _chipLabels = const [ - "Blood Test", - "X-Ray", - "MRI Scan", - "CT Scan", - "Ultrasound", - "Urine Test", - "Allergy Test", - "Cholesterol Test", - "Diabetes Test", - "Thyroid Test", - ]; + const SearchLabResultsContent({super.key, required this.labSuggestionsList}); @override - Widget build(BuildContext context) { + State createState() => _SearchLabResultsContentState(); +} + +class _SearchLabResultsContentState extends State { + TextEditingController searchEditingController = TextEditingController(); + List filteredSuggestions = []; + + @override + void initState() { + super.initState(); + filteredSuggestions = List.from(widget.labSuggestionsList); + + // Listen for changes in the search field + searchEditingController.addListener(() { + filterSuggestions(); + }); + } + + @override + void dispose() { + searchEditingController.dispose(); + super.dispose(); + } + + void filterSuggestions() { + final query = searchEditingController.text.toLowerCase(); + if (query.isEmpty) { + setState(() { + filteredSuggestions = List.from(widget.labSuggestionsList); + }); + } else { + setState(() { + filteredSuggestions = widget.labSuggestionsList + .where((suggestion) => suggestion.toLowerCase().contains(query)) + .toList(); + }); + } + } + + @override + Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -37,19 +66,23 @@ class SearchLabResultsContent extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ TextInputWidget( - labelText:"Search lab results", + labelText: "Search lab results", hintText: "Type test name", - controller: TextEditingController(), + controller: searchEditingController, isEnable: true, prefix: null, autoFocus: true, isBorderAllowed: false, - padding: EdgeInsets.symmetric(vertical:ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), - + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), ), - SizedBox(height: ResponsiveExtension(20).h), - "Suggestions".toText16(isBold: true), - const SizedBox(height: 12), + SizedBox(height: ResponsiveExtension(20).h), + if (filteredSuggestions.isNotEmpty) ...[ + "Suggestions".toText16(isBold: true), + const SizedBox(height: 12), + ], ], ), ), @@ -60,11 +93,13 @@ class SearchLabResultsContent extends StatelessWidget { alignment: WrapAlignment.start, spacing: 10, runSpacing: 10, - children: _chipLabels + children: filteredSuggestions .map((label) => SuggestionChip( - label: label, - onTap: () {}, - )) + label: label, + onTap: () { + searchEditingController.text = label; + }, + )) .toList(), ), ), @@ -76,7 +111,7 @@ class SearchLabResultsContent extends StatelessWidget { text: LocaleKeys.search.tr(), icon: AppAssets.search_icon, iconColor: Colors.white, - onPressed: () => Navigator.pop(context), + onPressed: () => Navigator.pop(context, searchEditingController.text), ), ), ], @@ -99,12 +134,16 @@ class SuggestionChip extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, // optional tap callback + onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.greyColor, + width: 1, + ), ), child: label.toText12( color: isSelected ? Colors.white : Colors.black87, @@ -113,4 +152,4 @@ class SuggestionChip extends StatelessWidget { ), ); } -} +} \ No newline at end of file From c27d7301d97ef445dcc4eced493135f9dd8ece5f Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 4 Sep 2025 16:28:18 +0300 Subject: [PATCH 11/11] calender & signup fixes. --- lib/extensions/string_extensions.dart | 123 +++++++------------------- 1 file changed, 31 insertions(+), 92 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 8ecdc3a..352415b 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -13,16 +13,13 @@ extension CapExtension on String { String get allInCaps => this.toUpperCase(); - String get capitalizeFirstofEach => - this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8( - {Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => - Text( + Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -35,14 +32,7 @@ extension EmailValidator on String { ), ); - Widget toText10( - {Color? color, - bool isBold = false, - bool isUnderLine = false, - int? maxlines, - FontStyle? fontStyle, - TextOverflow? textOverflow}) => - Text( + Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -56,15 +46,7 @@ extension EmailValidator on String { decorationColor: color ?? AppColors.blackColor), ); - Widget toText11( - {Color? color, - FontWeight? weight, - bool isUnderLine = false, - bool isCenter = false, - bool isBold = false, - int maxLine = 0, - double letterSpacing = 0.64}) => - Text( + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -78,20 +60,11 @@ extension EmailValidator on String { ), ); - Widget toText12( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - FontWeight? fontWeight, - bool isCenter = false, - double? height, - int maxLine = 0}) => - Text( + Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( - fontSize: 12.fSize, fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, @@ -102,9 +75,7 @@ extension EmailValidator on String { ), ); - Widget toText12Auto( - {Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => - AutoSizeText( + Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -165,14 +136,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText14( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines}) => - Text( + Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -184,14 +148,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText15( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines}) => - Text( + Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -227,40 +184,24 @@ extension EmailValidator on String { Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 17.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, - style: TextStyle( - fontSize: 18.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 19.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText20({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 20.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 20.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( @@ -272,34 +213,34 @@ extension EmailValidator on String { Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 1, - color: color ?? AppColors.blackColor, - fontSize: 22.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 23 / 24, - color: color ?? AppColors.blackColor, - fontSize: 24.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + ); + + Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); + + Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + ); + + + Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 32.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( @@ -340,9 +281,7 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } String toFormattedDate() {