From 8f0e910e4fb90bb3ecf359214fe79d193adc1916 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 17 Sep 2025 15:49:28 +0300 Subject: [PATCH 1/5] profile and settings ui completed. --- assets/images/svg/accessibility.svg | 4 + assets/images/svg/arrow_forward.svg | 4 + assets/images/svg/call_fill.svg | 3 + assets/images/svg/email_transparent.svg | 3 + assets/images/svg/emergency.svg | 3 + assets/images/svg/external_link.svg | 4 + assets/images/svg/language_change.svg | 11 ++ assets/images/svg/minus.svg | 4 + assets/images/svg/my_address.svg | 4 + assets/images/svg/permission.svg | 3 + assets/images/svg/privacy_terms.svg | 4 + assets/images/svg/rate.svg | 3 + assets/images/svg/smart_phone_fill.svg | 3 + assets/images/svg/touch_face_id.svg | 4 + lib/core/app_assets.dart | 16 ++- lib/extensions/int_extensions.dart | 4 +- lib/extensions/string_extensions.dart | 6 +- .../profile_settings/profile_settings.dart | 126 ++++++++++++++++-- lib/widgets/buttons/custom_button.dart | 40 +++--- 19 files changed, 214 insertions(+), 35 deletions(-) create mode 100644 assets/images/svg/accessibility.svg create mode 100644 assets/images/svg/arrow_forward.svg create mode 100644 assets/images/svg/call_fill.svg create mode 100644 assets/images/svg/email_transparent.svg create mode 100644 assets/images/svg/emergency.svg create mode 100644 assets/images/svg/external_link.svg create mode 100644 assets/images/svg/language_change.svg create mode 100644 assets/images/svg/minus.svg create mode 100644 assets/images/svg/my_address.svg create mode 100644 assets/images/svg/permission.svg create mode 100644 assets/images/svg/privacy_terms.svg create mode 100644 assets/images/svg/rate.svg create mode 100644 assets/images/svg/smart_phone_fill.svg create mode 100644 assets/images/svg/touch_face_id.svg diff --git a/assets/images/svg/accessibility.svg b/assets/images/svg/accessibility.svg new file mode 100644 index 0000000..fbe3d12 --- /dev/null +++ b/assets/images/svg/accessibility.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/arrow_forward.svg b/assets/images/svg/arrow_forward.svg new file mode 100644 index 0000000..2638c5b --- /dev/null +++ b/assets/images/svg/arrow_forward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/call_fill.svg b/assets/images/svg/call_fill.svg new file mode 100644 index 0000000..25509ed --- /dev/null +++ b/assets/images/svg/call_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/email_transparent.svg b/assets/images/svg/email_transparent.svg new file mode 100644 index 0000000..6f45a86 --- /dev/null +++ b/assets/images/svg/email_transparent.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/emergency.svg b/assets/images/svg/emergency.svg new file mode 100644 index 0000000..7a5a6e3 --- /dev/null +++ b/assets/images/svg/emergency.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/external_link.svg b/assets/images/svg/external_link.svg new file mode 100644 index 0000000..822ebc2 --- /dev/null +++ b/assets/images/svg/external_link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/language_change.svg b/assets/images/svg/language_change.svg new file mode 100644 index 0000000..e84f0c3 --- /dev/null +++ b/assets/images/svg/language_change.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/svg/minus.svg b/assets/images/svg/minus.svg new file mode 100644 index 0000000..01d8ebd --- /dev/null +++ b/assets/images/svg/minus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/my_address.svg b/assets/images/svg/my_address.svg new file mode 100644 index 0000000..59faec9 --- /dev/null +++ b/assets/images/svg/my_address.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/permission.svg b/assets/images/svg/permission.svg new file mode 100644 index 0000000..00d6f5c --- /dev/null +++ b/assets/images/svg/permission.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/privacy_terms.svg b/assets/images/svg/privacy_terms.svg new file mode 100644 index 0000000..ee79318 --- /dev/null +++ b/assets/images/svg/privacy_terms.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/rate.svg b/assets/images/svg/rate.svg new file mode 100644 index 0000000..bfdaf3f --- /dev/null +++ b/assets/images/svg/rate.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/smart_phone_fill.svg b/assets/images/svg/smart_phone_fill.svg new file mode 100644 index 0000000..70beb7f --- /dev/null +++ b/assets/images/svg/smart_phone_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/touch_face_id.svg b/assets/images/svg/touch_face_id.svg new file mode 100644 index 0000000..07808bd --- /dev/null +++ b/assets/images/svg/touch_face_id.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 7fbd212..fbd4945 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -4,6 +4,8 @@ class AppAssets { static const String hmg = '$svgBasePath/hmg.svg'; static const String arrow_back = '$svgBasePath/arrow-back.svg'; + static const String arrow_forward = '$svgBasePath/arrow_forward.svg'; + static const String externalLink = '$svgBasePath/external_link.svg'; static const String calendar = '$svgBasePath/calendar.svg'; static const String hmc = '$svgBasePath/hmc.svg'; static const String ksa = '$svgBasePath/ksa.svg'; @@ -115,6 +117,18 @@ class AppAssets { static const String free_med_delivery_icon = '$svgBasePath/free_med_delivery_icon.svg'; static const String livecare_book_icon = '$svgBasePath/livecare_book_icon.svg'; static const String doctor_profile_icon = '$svgBasePath/doctor_profile_icon.svg'; + static const String accessibility = '$svgBasePath/accessibility.svg'; + static const String call_fill = '$svgBasePath/call_fill.svg'; + static const String email_transparent = '$svgBasePath/email_transparent.svg'; + static const String emergency = '$svgBasePath/emergency.svg'; + static const String language_change = '$svgBasePath/language_change.svg'; + static const String my_address = '$svgBasePath/my_address.svg'; + static const String permission = '$svgBasePath/permission.svg'; + static const String privacy_terms = '$svgBasePath/privacy_terms.svg'; + static const String rate = '$svgBasePath/rate.svg'; + static const String smart_phone_fill = '$svgBasePath/smart_phone_fill.svg'; + static const String touch_face_id = '$svgBasePath/touch_face_id.svg'; + static const String minus = '$svgBasePath/minus.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -125,6 +139,7 @@ class AppAssets { static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; static const String feedback = '$svgBasePath/feedback.svg'; static const String news = '$svgBasePath/news.svg'; + // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; static const String livecare_service = '$pngBasePath/livecare_service.png'; @@ -146,5 +161,4 @@ class AppAnimations { static const String loadingAnimation = '$lottieBasePath/Loader.json'; static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json'; static const String warningAnimation = '$lottieBasePath/warningAnimation.json'; - } diff --git a/lib/extensions/int_extensions.dart b/lib/extensions/int_extensions.dart index 4a2e477..80b3171 100644 --- a/lib/extensions/int_extensions.dart +++ b/lib/extensions/int_extensions.dart @@ -6,9 +6,7 @@ extension IntExtensions on int { Widget get width => SizedBox(width: toDouble()); - Widget get divider => Divider(height: toDouble(), thickness: toDouble(), color: AppColors.buttonColor); + Widget get divider => Divider(height: toDouble(), thickness: toDouble(), color: Color(0x30D2D2D2)); Widget get makeItSquare => SizedBox(width: toDouble(), height: toDouble()); - - } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 509385c..c4e2849 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -132,10 +132,12 @@ 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, double? letterSpacing = -1}) => Text( + Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines, double? letterSpacing = -1, TextOverflow? textOverflow}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, + overflow: textOverflow, style: TextStyle( color: color ?? AppColors.blackColor, fontSize: 14.fSize, @@ -162,6 +164,7 @@ extension EmailValidator on String { bool isBold = false, bool isCenter = false, int? maxlines, + double? height, TextAlign? textAlign, FontWeight? weight, double? letterSpacing = -0.4, @@ -174,6 +177,7 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, fontSize: 16.fSize, letterSpacing: letterSpacing, + height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, ), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index baccbe2..037bb66 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -3,6 +3,7 @@ import 'package:flutter_swiper_view/flutter_swiper_view.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/int_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/profile_settings/profile_settings_view_model.dart'; @@ -70,7 +71,7 @@ class _ProfileSettingsState extends State { }, ), GridView( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 9, mainAxisSpacing: 9), physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(24), shrinkWrap: true, @@ -83,17 +84,106 @@ class _ProfileSettingsState extends State { hasShadow: true, ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, children: [ Row( + spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.h, height: 40.h), + "Al Habib Wallet".needTranslation.toText14(weight: FontWeight.w600, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), ], - ) + ), + Spacer(), + RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: 14.h, height: 14.h, iconColor: Colors.black.withValues(alpha: 0.31)), + ), + TextSpan( + text: " 247", + style: TextStyle(color: AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -4, fontWeight: FontWeight.w600), + ), + ], + ), + ), + CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 24.h, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: "Recharge", + borderWidth: 0.h, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.h), + fontSize: 14, + onPressed: () {}, + ), ], ), ) ], - ) + ), + "Quick Actions".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24, right: 24), + Container( + margin: EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 24), + padding: EdgeInsets.only(top: 4, bottom: 4), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Column( + children: [ + actionItem(AppAssets.language_change, "Language".needTranslation, () {}, trailingLabel: "English".needTranslation), + 1.divider, + actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}), + 1.divider, + actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}), + 1.divider, + actionItem(AppAssets.touch_face_id, "Touch ID / Face ID Services".needTranslation, () {}, switchValue: true), + ], + ), + ), + "Personal Information".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24, right: 24), + Container( + margin: EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 24), + padding: EdgeInsets.only(top: 4, bottom: 4), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Column( + children: [ + actionItem(AppAssets.email_transparent, "Update Email Address".needTranslation, () {}), + 1.divider, + actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), + 1.divider, + actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), + 1.divider, + actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), + ], + ), + ), + "Help & Support".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24, right: 24), + Container( + margin: EdgeInsets.only(left: 24, right: 24, top: 16), + padding: EdgeInsets.only(top: 4, bottom: 4), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Column( + children: [ + actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () {}, trailingLabel: "9200666666"), + 1.divider, + actionItem(AppAssets.permission, "Permissions".needTranslation, () {}, trailingLabel: "Location, Camera"), + 1.divider, + actionItem(AppAssets.rate, "Rate Our App".needTranslation, () {}, isExternalLink: true), + 1.divider, + actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () {}, isExternalLink: true), + 1.divider, + actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () {}, isExternalLink: true), + ], + ), + ), + CustomButton(icon: AppAssets.minus, text: "Deactivate account".needTranslation, onPressed: () {}).paddingAll(24), ], ); }, @@ -101,6 +191,28 @@ class _ProfileSettingsState extends State { ), ); } + + Widget actionItem(String icon, String label, VoidCallback onPress, {String trailingLabel = "", bool? switchValue, bool isExternalLink = false}) { + return SizedBox( + height: 56, + child: Row( + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor), + label.toText14(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + if (trailingLabel.isNotEmpty) trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1), + switchValue != null + ? Switch( + value: switchValue, + onChanged: (value) {}, + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + ) + : Utils.buildSvgWithAssets(icon: isExternalLink ? AppAssets.externalLink : AppAssets.arrow_forward), + ], + ).paddingOnly(left: 16, right: 16).onPress(onPress), + ); + } } class FamilyCardWidget extends StatelessWidget { @@ -160,12 +272,8 @@ class FamilyCardWidget extends StatelessWidget { ), ], ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded, - Divider( - height: 1, - thickness: 1, - color: Color(0x30D2D2D2), - ), - CustomButton(icon: AppAssets.add_family, text: "Add a new family member", onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), + 1.divider, + CustomButton(icon: AppAssets.add_family, text: "Add a new family member".needTranslation, onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16), ], ), ); diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index e1448dc..30e4e5d 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -48,6 +48,7 @@ class CustomButton extends StatelessWidget { onTap: isDisabled ? null : onPressed, child: Container( height: height, + padding: padding, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: isDisabled ? Colors.transparent : backgroundColor, borderRadius: borderRadius, @@ -55,28 +56,25 @@ class CustomButton extends StatelessWidget { width: borderWidth, color: isDisabled ? borderColor.withOpacity(0.5) : borderColor, )), - child: Padding( - padding: padding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), - ), - Text( - text, - style: context.dynamicTextStyle( - fontSize: fontSize.fSize, - color: isDisabled ? textColor.withOpacity(0.5) : textColor, - letterSpacing: -0.4, - fontWeight: fontWeight, - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), ), - ], - ), + Text( + text, + style: context.dynamicTextStyle( + fontSize: fontSize.fSize, + color: isDisabled ? textColor.withOpacity(0.5) : textColor, + letterSpacing: -0.4, + fontWeight: fontWeight, + ), + ), + ], ), ) From c83754fc4a0ac690e1237fe4f6a24c504daffa07 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 17 Sep 2025 16:18:05 +0300 Subject: [PATCH 2/5] user profile welcome widget improvement. --- lib/extensions/string_extensions.dart | 14 +- lib/presentation/home/landing_page.dart | 332 +++++++++--------- .../home/widgets/welcome_widget.dart | 25 +- .../profile_settings/profile_settings.dart | 11 +- 4 files changed, 197 insertions(+), 185 deletions(-) diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index c4e2849..713350f 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -132,7 +132,16 @@ 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, double? letterSpacing = -1, TextOverflow? textOverflow}) => + Widget toText14( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + bool isCenter = false, + FontWeight? weight, + int? maxlines, + double? letterSpacing = -1, + double? height, + TextOverflow? textOverflow}) => Text( this, textAlign: isCenter ? TextAlign.center : null, @@ -142,6 +151,7 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, fontSize: 14.fSize, letterSpacing: letterSpacing, + height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null), ); @@ -167,6 +177,7 @@ extension EmailValidator on String { double? height, TextAlign? textAlign, FontWeight? weight, + TextOverflow? textOverflow, double? letterSpacing = -0.4, }) => Text( @@ -178,6 +189,7 @@ extension EmailValidator on String { fontSize: 16.fSize, letterSpacing: letterSpacing, height: height, + overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 2d40869..41e1483 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -95,33 +95,32 @@ class _LandingPageState extends State { children: [ appState.isAuthenticated ? WelcomeWidget( - onTap: () { - Navigator.of(context).push( - FadePage( - page: ProfileSettings(), - ), - ); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageUrl: appState - .getAuthenticatedUser() - ?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - ) + onTap: () { + Navigator.of(context).push( + FadePage( + page: ProfileSettings(), + ), + ); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + ).expanded : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, - ), + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50, + ), Row( + mainAxisSize: MainAxisSize.min, children: [ Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 20, width: 20).onPress(() { Navigator.of(context).push( @@ -155,156 +154,155 @@ class _LandingPageState extends State { SizedBox(height: 16.h), appState.isAuthenticated ? Column( - children: [ - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context) - .push( - FadePage( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Quick Links".toText16(isBold: true), - Row( - children: [ - "View medical file".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - ), - ); - }), - SizedBox(height: 12.h), - Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + child: Padding( + padding: EdgeInsets.all(12.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context).push( + FadePage( + page: BookAppointmentPage(), ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ), + ], ), ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - ], - ) - : Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Quick Links".toText16(isBold: true), + Row( + children: [ + "View medical file".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + ), + ); + }), + SizedBox(height: 12.h), + Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + ], + ) + : Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ), + ], ), ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -374,7 +372,7 @@ class _LandingPageState extends State { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); authVM.loginWithFingerPrintFace(() { isDone = true; - cacheService.saveBool(key: CacheConst.quickLoginEnabled,value: true); + cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true); setState(() {}); }); }, diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart index d761a7f..4baee01 100644 --- a/lib/presentation/home/widgets/welcome_widget.dart +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -1,7 +1,9 @@ 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/theme/colors.dart'; class WelcomeWidget extends StatelessWidget { @@ -23,32 +25,25 @@ class WelcomeWidget extends StatelessWidget { borderRadius: BorderRadius.circular(30), child: Row( mainAxisSize: MainAxisSize.min, + spacing: 8.h, children: [ - // Profile image - Image.asset(imageUrl, width: 40, height: 40), - - const SizedBox(width: 10), - - // Text column Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + mainAxisSize: MainAxisSize.min, children: [ - - "Welcome".toText14(color: AppColors.greyTextColor), - - + "Welcome".needTranslation.toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), Row( + spacing: 4.h, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - - name.toText16(isBold: true), - - const SizedBox(width: 4), + name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1).expanded, const Icon(Icons.keyboard_arrow_down, size: 20, color: Colors.black), ], ), ], - ), + ).expanded, ], ), ); diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 037bb66..190fe03 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -7,10 +7,13 @@ import 'package:hmg_patient_app_new/extensions/int_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/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; class ProfileSettings extends StatefulWidget { @@ -123,10 +126,14 @@ class _ProfileSettingsState extends State { backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), padding: EdgeInsets.all(8.h), fontSize: 14, - onPressed: () {}, + onPressed: () { + Navigator.of(context).push(FadePage(page: RechargeWalletPage())); + }, ), ], - ), + ).onPress(() { + Navigator.of(context).push(FadePage(page: HabibWalletPage())); + }), ) ], ), From c00e69ef0c22a8d986bcbefdc192eb448123b6f3 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 17 Sep 2025 17:26:21 +0300 Subject: [PATCH 3/5] profile wallet linked with view model. --- lib/core/utils/utils.dart | 18 ++++++ .../habib_wallet/habib_wallet_page.dart | 3 +- lib/presentation/home/landing_page.dart | 7 +-- .../lab/collapsing_list_view.dart | 13 ++-- .../profile_settings/profile_settings.dart | 23 +++----- lib/widgets/routes/custom_page_route.dart | 59 +++++++++++++++++++ .../routes/spring_page_route_builder.dart | 38 ++++++++++++ 7 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 lib/widgets/routes/custom_page_route.dart create mode 100644 lib/widgets/routes/spring_page_route_builder.dart diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index d34a44c..152cd54 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -650,6 +650,24 @@ class Utils { ); } + static Widget getPaymentAmountWithSymbol2(num habibWalletAmount, Color iconColor, double iconSize, {bool isSaudiCurrency = true, bool isExpanded = true}) { + return RichText( + text: TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: 14.h, height: 14.h, iconColor: Colors.black.withValues(alpha: 0.31)), + ), + TextSpan( + text: " $habibWalletAmount", + style: TextStyle(color: AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -4, fontWeight: FontWeight.w600, height: 1), + ), + ], + ), + ); + } + static Future isGoogleServicesAvailable() async { GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); String status = availability.toString().split('.').last; diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 1d24d05..1be3e12 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -51,6 +51,7 @@ class _HabibWalletState extends State { children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -58,7 +59,7 @@ class _HabibWalletState extends State { "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: AppColors.whiteColor), "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor), ], - ), + ).expanded, Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h), ], ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 41e1483..a68bd83 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -34,6 +34,7 @@ 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/custom_tab_bar.dart' show CustomTabBar; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -96,11 +97,7 @@ class _LandingPageState extends State { appState.isAuthenticated ? WelcomeWidget( onTap: () { - Navigator.of(context).push( - FadePage( - page: ProfileSettings(), - ), - ); + Navigator.of(context).push(springPageRoute(ProfileSettings())); }, name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, diff --git a/lib/presentation/lab/collapsing_list_view.dart b/lib/presentation/lab/collapsing_list_view.dart index f84872d..8f63f76 100644 --- a/lib/presentation/lab/collapsing_list_view.dart +++ b/lib/presentation/lab/collapsing_list_view.dart @@ -41,11 +41,14 @@ class CollapsingListView extends StatelessWidget { systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, backgroundColor: AppColors.bgScaffoldColor, - leading: isLeading ? IconButton( - icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), - padding: EdgeInsets.only(left: 12), - onPressed: () => Navigator.pop(context), - ) : SizedBox.shrink(), + leading: isLeading + ? IconButton( + icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), + padding: EdgeInsets.only(left: 12), + onPressed: () => Navigator.pop(context), + highlightColor: Colors.transparent, + ) + : SizedBox.shrink(), flexibleSpace: LayoutBuilder( builder: (context, constraints) { final double maxHeight = 100; diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 190fe03..bc99774 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_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/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; @@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -100,19 +102,10 @@ class _ProfileSettingsState extends State { ], ), Spacer(), - RichText( - text: TextSpan( - children: [ - WidgetSpan( - child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: 14.h, height: 14.h, iconColor: Colors.black.withValues(alpha: 0.31)), - ), - TextSpan( - text: " 247", - style: TextStyle(color: AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -4, fontWeight: FontWeight.w600), - ), - ], - ), - ), + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, AppColors.whiteColor, 13.h, isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h); + }), CustomButton( height: 40.h, icon: AppAssets.recharge_icon, @@ -127,12 +120,12 @@ class _ProfileSettingsState extends State { padding: EdgeInsets.all(8.h), fontSize: 14, onPressed: () { - Navigator.of(context).push(FadePage(page: RechargeWalletPage())); + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); }, ), ], ).onPress(() { - Navigator.of(context).push(FadePage(page: HabibWalletPage())); + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); }), ) ], diff --git a/lib/widgets/routes/custom_page_route.dart b/lib/widgets/routes/custom_page_route.dart new file mode 100644 index 0000000..49ebc98 --- /dev/null +++ b/lib/widgets/routes/custom_page_route.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +/// Reusable spring route +class CustomPageRoute extends PageRouteBuilder { + final Widget page; + final AxisDirection direction; + + CustomPageRoute({required this.page, this.direction = AxisDirection.right}) + : super( + transitionDuration: const Duration(milliseconds: 1000), + pageBuilder: (_, __, ___) => page, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + final spring = SpringDescription(mass: 1, stiffness: 100, damping: 15); + + // Drive animation with spring + final curvedAnimation = animation.drive( + Tween(begin: 0.0, end: 1.0).chain(CurveTween(curve: _SpringCurve(spring))), + ); + + // Choose offset based on direction + Offset beginOffset; + switch (direction) { + case AxisDirection.left: + beginOffset = const Offset(-1.0, 0.0); + break; + case AxisDirection.right: + beginOffset = const Offset(1.0, 0.0); + break; + case AxisDirection.up: + beginOffset = const Offset(0.0, -1.0); + break; + case AxisDirection.down: + beginOffset = const Offset(0.0, 1.0); + break; + } + + final offsetAnimation = Tween(begin: beginOffset, end: Offset.zero).animate(curvedAnimation); + + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + ); +} + +/// Custom spring curve +class _SpringCurve extends Curve { + final SpringDescription spring; + + _SpringCurve(this.spring); + + @override + double transform(double t) { + final sim = SpringSimulation(spring, 0, 1, 0); + return sim.x(t * 1.5); // scaled time + } +} diff --git a/lib/widgets/routes/spring_page_route_builder.dart b/lib/widgets/routes/spring_page_route_builder.dart new file mode 100644 index 0000000..61611e1 --- /dev/null +++ b/lib/widgets/routes/spring_page_route_builder.dart @@ -0,0 +1,38 @@ +import 'package:flutter/physics.dart'; +import 'package:flutter/widgets.dart'; + +PageRouteBuilder springPageRoute(Widget page) { + return PageRouteBuilder( + pageBuilder: (_, __, ___) => page, + transitionDuration: const Duration(milliseconds: 1000), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + final spring = SpringDescription(mass: 1, stiffness: 100, damping: 15); + + final curvedAnimation = animation.drive( + Tween(begin: 0.0, end: 1.0).chain(CurveTween(curve: _SpringCurve(spring))), + ); + + final offsetAnimation = Tween( + begin: const Offset(-1.0, 0.0), // slide from left + end: Offset.zero, + ).animate(curvedAnimation); + + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + ); +} + +class _SpringCurve extends Curve { + final SpringDescription spring; + + _SpringCurve(this.spring); + + @override + double transform(double t) { + final sim = SpringSimulation(spring, 0, 1, 0); + return sim.x(t * 1.5); // scale time so it completes properly + } +} From 57827549b846db693e045222f39d07c2897a516a Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 18 Sep 2025 10:16:19 +0300 Subject: [PATCH 4/5] language switch added in drawer, and ui improvements --- lib/core/utils/calendar_utils.dart | 4 +- lib/presentation/home/landing_page.dart | 122 +++++++++--------- .../profile_settings/profile_settings.dart | 12 +- lib/widgets/app_language_change.dart | 87 +++++++++++++ lib/widgets/common_bottom_sheet.dart | 10 +- 5 files changed, 163 insertions(+), 72 deletions(-) create mode 100644 lib/widgets/app_language_change.dart diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 979ce1b..2068db9 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -285,11 +285,11 @@ Future checkAndRemove(hasReminder, {bool delete = false, String itemDescri if (calendarUtils.calendars != null) { if (Platform.isAndroid) { - await processEvents(calendarUtils.calendars, calendarUtils, params, delete, itemDescriptionN,hasReminder); + await processEvents(calendarUtils.calendars, calendarUtils, params, delete, itemDescriptionN, hasReminder); } else { List? iosCalendars = await _myPlugin.getCalendars(); if (iosCalendars != null) { - await processEvents(iosCalendars.map((cal) => Calendar(id: cal.id, name: cal.name, accountName: cal.accountName)).toList(), calendarUtils, params, delete, itemDescriptionN,hasReminder); + await processEvents(iosCalendars.map((cal) => Calendar(id: cal.id, name: cal.name, accountName: cal.accountName)).toList(), calendarUtils, params, delete, itemDescriptionN, hasReminder); } } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a68bd83..f8cb8fd 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -87,68 +87,68 @@ class _LandingPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( + padding: EdgeInsets.only(top: kToolbarHeight + 12.h, bottom: 24), child: Column( + spacing: 16.h, children: [ - Padding( - padding: EdgeInsets.only(top: 50.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - appState.isAuthenticated - ? WelcomeWidget( - onTap: () { - Navigator.of(context).push(springPageRoute(ProfileSettings())); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - ).expanded - : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, + Row( + spacing: 8.h, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + appState.isAuthenticated + ? WelcomeWidget( + onTap: () { + Navigator.of(context).push(springPageRoute(ProfileSettings())); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + ).expanded + : CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50, + ), + Row( + mainAxisSize: MainAxisSize.min, + spacing: 12.h, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 20, width: 20).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }).paddingSymmetrical(8.h, 0), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 20, width: 20).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }).paddingSymmetrical(8.h, 0), - Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 20, width: 20).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }).paddingSymmetrical(8.h, 0), - ], - ) - ], - ).paddingSymmetrical(24.h, 0.h), - ), - SizedBox(height: 16.h), + ); + }), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }), + Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 20, width: 20).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }), + ], + ) + ], + ).paddingSymmetrical(24.h, 0.h), appState.isAuthenticated ? Column( children: [ @@ -300,7 +300,6 @@ class _LandingPageState extends State { ), ), ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -314,7 +313,6 @@ class _LandingPageState extends State { ), ], ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), SizedBox( height: 325.h, child: Column( @@ -348,9 +346,7 @@ class _LandingPageState extends State { ], ), ), - SizedBox(height: 16.h), appState.isAuthenticated ? HabibWalletCard() : SizedBox(), - SizedBox(height: 16.h), ], ), ), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index bc99774..832031a 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_swiper_view/flutter_swiper_view.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -12,8 +13,10 @@ import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page. import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/app_language_change.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -137,7 +140,9 @@ class _ProfileSettingsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: Column( children: [ - actionItem(AppAssets.language_change, "Language".needTranslation, () {}, trailingLabel: "English".needTranslation), + actionItem(AppAssets.language_change, "Language".needTranslation, () { + showCommonBottomSheetWithoutHeight(context, title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); + }, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation), 1.divider, actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}), 1.divider, @@ -208,7 +213,10 @@ class _ProfileSettingsState extends State { activeColor: AppColors.successColor, activeTrackColor: AppColors.successColor.withValues(alpha: .15), ) - : Utils.buildSvgWithAssets(icon: isExternalLink ? AppAssets.externalLink : AppAssets.arrow_forward), + : Transform.scale( + scaleX: Utils.appState.isArabic() ? -1 : 1, + child: Utils.buildSvgWithAssets(icon: isExternalLink ? AppAssets.externalLink : AppAssets.arrow_forward), + ) ], ).paddingOnly(left: 16, right: 16).onPress(onPress), ); diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart new file mode 100644 index 0000000..de9cda5 --- /dev/null +++ b/lib/widgets/app_language_change.dart @@ -0,0 +1,87 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/int_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/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 AppLanguageChange extends StatefulWidget { + AppLanguageChange({Key? key}) : super(key: key); + + @override + _AppLanguageChangeState createState() { + return _AppLanguageChangeState(); + } +} + +class _AppLanguageChangeState extends State { + String? selectedValue; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + selectedValue ??= context.locale.languageCode; + return Column( + spacing: 24.h, + children: [ + Container( + padding: EdgeInsets.only(top: 4, bottom: 4), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Column( + children: [ + languageItem("English".needTranslation, "en"), + 1.divider, + languageItem("العربية".needTranslation, "ar"), + ], + ), + ), + CustomButton( + text: LocaleKeys.save.tr(), + onPressed: () { + context.setLocale(selectedValue == 'en' ? Locale('en', 'US') : Locale('ar', 'SA')).then((val) { + Navigator.pop(context); + }); + }), + ], + ); + } + + Widget languageItem(String title, String _value) { + return SizedBox( + height: 72, + child: Row( + spacing: 8.h, + children: [ + Radio( + value: _value, + groupValue: selectedValue, + activeColor: AppColors.errorColor, + onChanged: (value) { + setState(() { + selectedValue = _value; + }); + }, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + title.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + ], + ).paddingOnly(left: 16, right: 16).onPress(() { + setState(() { + selectedValue = _value; + }); + }), + ); + } +} diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 576e923..c16e6a3 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -109,11 +109,11 @@ void showCommonBottomSheetWithoutHeight( required Widget child, required VoidCallback callBackFunc, String title = "", - - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget,}) { + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget, +}) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( duration: Duration(milliseconds: 500), // Custom animation duration From c96081d07c38e34c7ee377a89e641f6dd7f3be7a Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 18 Sep 2025 10:42:20 +0300 Subject: [PATCH 5/5] reverse animation time changed to 500ms. --- lib/widgets/routes/custom_page_route.dart | 3 ++- lib/widgets/routes/spring_page_route_builder.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/widgets/routes/custom_page_route.dart b/lib/widgets/routes/custom_page_route.dart index 49ebc98..733993f 100644 --- a/lib/widgets/routes/custom_page_route.dart +++ b/lib/widgets/routes/custom_page_route.dart @@ -8,7 +8,8 @@ class CustomPageRoute extends PageRouteBuilder { CustomPageRoute({required this.page, this.direction = AxisDirection.right}) : super( - transitionDuration: const Duration(milliseconds: 1000), + transitionDuration: const Duration(milliseconds: 1500), + reverseTransitionDuration: const Duration(milliseconds: 500), pageBuilder: (_, __, ___) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { final spring = SpringDescription(mass: 1, stiffness: 100, damping: 15); diff --git a/lib/widgets/routes/spring_page_route_builder.dart b/lib/widgets/routes/spring_page_route_builder.dart index 61611e1..0533408 100644 --- a/lib/widgets/routes/spring_page_route_builder.dart +++ b/lib/widgets/routes/spring_page_route_builder.dart @@ -4,7 +4,8 @@ import 'package:flutter/widgets.dart'; PageRouteBuilder springPageRoute(Widget page) { return PageRouteBuilder( pageBuilder: (_, __, ___) => page, - transitionDuration: const Duration(milliseconds: 1000), + transitionDuration: const Duration(milliseconds: 1500), + reverseTransitionDuration: const Duration(milliseconds: 500), transitionsBuilder: (context, animation, secondaryAnimation, child) { final spring = SpringDescription(mass: 1, stiffness: 100, damping: 15);