From 5620cc4442c30abc2939ac9ea242821c09e2cc47 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Wed, 10 Sep 2025 22:44:44 +0300 Subject: [PATCH] medical file implementation contd. --- assets/images/svg/alarm_clock_icon.svg | 5 + lib/core/app_assets.dart | 1 + .../my_appointments/my_appointments_repo.dart | 49 ++ .../my_appointments_view_model.dart | 10 + .../appointment_details_page.dart | 4 +- .../appointments/my_appointments_page.dart | 2 +- lib/presentation/home/landing_page.dart | 41 +- .../medical_file/medical_file_page.dart | 506 ++++++++++++------ .../medical_file/widgets/lab_rad_card.dart | 65 +++ .../medical_file_appointment_card.dart | 181 +++++++ 10 files changed, 680 insertions(+), 184 deletions(-) create mode 100644 assets/images/svg/alarm_clock_icon.svg create mode 100644 lib/presentation/medical_file/widgets/lab_rad_card.dart create mode 100644 lib/presentation/medical_file/widgets/medical_file_appointment_card.dart diff --git a/assets/images/svg/alarm_clock_icon.svg b/assets/images/svg/alarm_clock_icon.svg new file mode 100644 index 0000000..50b64bd --- /dev/null +++ b/assets/images/svg/alarm_clock_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 02160a2..23945d0 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -99,6 +99,7 @@ class AppAssets { static const String notes_icon = '$svgBasePath/notes_icon.svg'; static const String forward_chevron_icon = '$svgBasePath/forward_chevron_icon.svg'; static const String logout = '$svgBasePath/logout.svg'; + static const String alarm_clock_icon = '$svgBasePath/alarm_clock_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 804f9f9..a5528cf 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -32,6 +32,8 @@ abstract class MyAppointmentsRepo { Future>> sendCheckInNfcRequest( {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); + + Future>>> getPatientAppointmentsForTimeLine(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -395,4 +397,51 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientAppointmentsForTimeLine() async { + Map mapDevice = { + "isDentalAllowedBackend": false, + "PatientTypeID": 1, + "IsComingFromCOC": false, + "PatientType": 1, + "IsForTimeLine": true, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_APPOINTMENT_HISTORY, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppoimentAllHistoryResultList']; + // if (list == null || list.isEmpty) { + // throw Exception("Appointments list is empty"); + // } + + final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } 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())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 4a921da..d3121af 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -12,12 +12,15 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; + bool isTimeLineAppointmentsLoading = false; List patientAppointmentsHistoryList = []; List patientUpcomingAppointmentsHistoryList = []; List patientArrivedAppointmentsHistoryList = []; + List patientTimelineAppointmentsList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -31,8 +34,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); + patientTimelineAppointmentsList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; + isTimeLineAppointmentsLoading = true; notifyListeners(); } @@ -46,6 +51,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientTimeLineAppointmentLoading(bool val) { + isTimeLineAppointmentsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 18b778a..ee2a042 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -57,8 +57,8 @@ class _AppointmentDetailsPageState extends State { @override Widget build(BuildContext context) { - myAppointmentsViewModel = Provider.of(context); - prescriptionsViewModel = Provider.of(context); + myAppointmentsViewModel = Provider.of(context, listen: false); + prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 333c8b1..5a3205d 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -35,7 +35,7 @@ class _MyAppointmentsPageState extends State { @override Widget build(BuildContext context) { - myAppointmentsViewModel = Provider.of(context); + myAppointmentsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index d148b83..b63b404 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -13,6 +13,8 @@ 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/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; @@ -43,6 +45,8 @@ class _LandingPageState extends State { late final HabibWalletViewModel habibWalletVM; late AppState appState; + late MyAppointmentsViewModel myAppointmentsViewModel; + late PrescriptionsViewModel prescriptionsViewModel; @override void initState() { @@ -51,13 +55,16 @@ class _LandingPageState extends State { authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - showQuickLogin(context); + showQuickLogin(context); }); } scheduleMicrotask(() { if (appState.isAuthenticated) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + prescriptionsViewModel.initPrescriptionsViewModel(); } }); super.initState(); @@ -67,7 +74,8 @@ class _LandingPageState extends State { Widget build(BuildContext context) { appState = getIt.get(); NavigationService navigationService = getIt.get(); - + myAppointmentsViewModel = Provider.of(context, listen: false); + prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( @@ -338,29 +346,22 @@ class _LandingPageState extends State { void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( - context, title: "", isCloseButtonVisible: false, - child: - StatefulBuilder( - builder: (context, setState) { - return QuickLogin( - isDone: isDone, - onPressed: () { - // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - authVM.loginWithFingerPrintFace((){ - - isDone = true; - setState(() { - + child: StatefulBuilder(builder: (context, setState) { + return QuickLogin( + isDone: isDone, + onPressed: () { + // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); + authVM.loginWithFingerPrintFace(() { + isDone = true; + setState(() {}); }); - }); - - }, - ); + }, + ); }), - // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, + // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, callBackFunc: () { isDone = true; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 47951c4..4860bb1 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -2,18 +2,25 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; 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/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.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/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/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_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'; @@ -23,6 +30,8 @@ import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import 'widgets/medical_file_appointment_card.dart'; + class MedicalFilePage extends StatefulWidget { MedicalFilePage({super.key}); @@ -33,6 +42,7 @@ class MedicalFilePage extends StatefulWidget { class _MedicalFilePageState extends State { late InsuranceViewModel insuranceViewModel; late AppState appState; + late MyAppointmentsViewModel myAppointmentsViewModel; int currentIndex = 0; @@ -46,174 +56,174 @@ class _MedicalFilePageState extends State { @override Widget build(BuildContext context) { - insuranceViewModel = Provider.of(context); + insuranceViewModel = Provider.of(context, listen: false); + myAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: const Text('Appointments'), - backgroundColor: AppColors.bgScaffoldColor, - ), body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record", - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.student_card, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( + child: Padding( + padding: EdgeInsets.only(top: 80.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Type any record", + controller: TextEditingController(), + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + leadingIcon: AppAssets.student_card, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + width: 56.h, + height: 56.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true), + SizedBox(height: 4.h), + Row( + children: [ + CustomButton( + icon: AppAssets.file_icon, + iconColor: AppColors.blackColor, + iconSize: 12.h, + text: "File no: ${appState.getAuthenticatedUser()!.patientId}", + 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(width: 4.h), + CustomButton( + text: LocaleKeys.verified.tr(context: context), + 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: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "${appState.getAuthenticatedUser()!.age} Years Old", + 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(width: 4.h), + CustomButton( + icon: AppAssets.blood_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "Blood: ${appState.getUserBloodGroup}", + 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(width: 4.h), + // CustomButton( + // icon: AppAssets.insurance_active_icon, + // iconColor: AppColors.successColor, + // iconSize: 13.h, + // text: "Insurance Active", + // onPressed: () {}, + // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + // borderColor: AppColors.bgGreenColor.withOpacity(0.0), + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.normal, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + Consumer(builder: (context, medicalFileVM, child) { + return Column( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - width: 56.h, - height: 56.h, - ), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true), - SizedBox(height: 4.h), - Row( - children: [ - CustomButton( - icon: AppAssets.file_icon, - iconColor: AppColors.blackColor, - iconSize: 12.h, - text: "File no: ${appState.getAuthenticatedUser()!.patientId}", - 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(width: 4.h), - CustomButton( - text: LocaleKeys.verified.tr(context: context), - 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, - ), - ], - ), - ], - ) + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), + CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.more, "More".needTranslation), ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor, height: 1.h), - SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: "${appState.getAuthenticatedUser()!.age} Years Old", - 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(width: 4.h), - CustomButton( - icon: AppAssets.blood_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Blood: ${appState.getUserBloodGroup}", - 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(width: 4.h), - // CustomButton( - // icon: AppAssets.insurance_active_icon, - // iconColor: AppColors.successColor, - // iconSize: 13.h, - // text: "Insurance Active", - // onPressed: () {}, - // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - // borderColor: AppColors.bgGreenColor.withOpacity(0.0), - // textColor: AppColors.blackColor, - // fontSize: 10, - // fontWeight: FontWeight.normal, - // borderRadius: 12, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 30.h, - // ), - ], - ), - SizedBox(height: 8.h), + onTabChange: (index) { + print(index); + medicalFileVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), + getSelectedTabData(medicalFileVM.selectedTabIndex), ], - ), - ), - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - Consumer(builder: (context, medicalFileVM, child) { - return Column( - children: [ - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), - CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.more, "More".needTranslation), - ], - onTabChange: (index) { - print(index); - medicalFileVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 24.h), - getSelectedTabData(medicalFileVM.selectedTabIndex), - ], - ); - }), - ], + ); + }), + ], + ), ), ), ); @@ -224,6 +234,7 @@ class _MedicalFilePageState extends State { case 0: //General Tab Data return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -244,6 +255,179 @@ class _MedicalFilePageState extends State { ), ); }), + Consumer(builder: (context, myAppointmentsVM, child) { + return SizedBox( + height: 200.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.only(top: 16.h, left: 24.h, right: 24.h, bottom: 0.h), + shrinkWrap: true, + itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientAppointmentsHistoryList.length, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: myAppointmentsVM.isMyAppointmentsLoading + ? MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsVM, + ) + : MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h), + ), + ); + }), + SizedBox(height: 24.h), + "Lab & Radiology".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: LabRadCard( + icon: AppAssets.lab_result_icon, + labelText: LocaleKeys.labResults.tr(context: context), + labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"], + ), + ), + SizedBox(width: 16.h), + Expanded( + child: LabRadCard( + icon: AppAssets.radiology_icon, + labelText: LocaleKeys.radiology.tr(context: context), + labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], + ), + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + "Active Medications & Prescriptions".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsOrdersLoading + ? const MoviesShimmerWidget() + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.patientPrescriptionOrders.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_item_icon, + width: 40.h, + height: 40.h, + ), + SizedBox(width: 8.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + children: [ + // SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), + // SizedBox( + // width: 150.h, + // child: + // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + // .needTranslation + // .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), + // ), + ], + ), + SizedBox(width: 68.h), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + // Navigator.of(context).push( + // FadePage( + // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + // ), + // ); + }), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + // Navigator.of(context) + // .push( + // FadePage( + // page: PrescriptionsListPage(), + // ), + // ) + // .then((val) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + }), + SizedBox(height: 24.h), ], ); case 1: diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart new file mode 100644 index 0000000..aa53b2f --- /dev/null +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -0,0 +1,65 @@ +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 LabRadCard extends StatelessWidget { + LabRadCard({super.key, required this.icon, required this.labelText, required this.labOrderTests}); + + String icon; + String labelText; + List labOrderTests = []; + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: false), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: icon, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: false, radius: 12.h), + SizedBox(width: 8.h), + labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.h, height: 32.h), + ], + ), + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (cxt, index) { + return labOrderTests[index].toText12(isBold: true, maxLine: 1).toShimmer2(isShow: false, radius: 6.h, height: 24.h, width: 120.h); + }, + separatorBuilder: (cxt, index) => SizedBox(height: 8.h), + itemCount: labOrderTests.length, + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox.shrink(), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 15.h, + height: 15.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor + ).toShimmer2(isShow: false, radius: 12.h), + ], + ) + ], + ).paddingAll(16.h), + ); + } +} diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart new file mode 100644 index 0000000..48cc328 --- /dev/null +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -0,0 +1,181 @@ +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/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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.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/transitions/fade_page.dart'; + +class MedicalFileAppointmentCard extends StatelessWidget { + MedicalFileAppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel}); + + PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, + borderColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.greyLightColor : AppColors.secondaryLightRedColor, + textColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, + iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + iconSize: 16.h, + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + width: 200.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 25.h, + height: 27.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + SizedBox(width: 8.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.clinicName ?? "") + .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) + .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ], + ), + ), + ], + ), + SizedBox(height: 12.h), + Row( + children: [ + myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.h, radius: 12.h) + : Expanded( + flex: 6, + child: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading) + : CustomButton( + text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + )) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 2, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + ), + child: Padding( + padding: EdgeInsets.all(10.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), + ), + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { + Navigator.of(context) + .push( + FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }), + ), + ], + ), + ], + ).paddingAll(16.h), + ), + ], + ); + } + + Widget getArrivedAppointmentButton(BuildContext context) { + return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) + : CustomButton( + text: "Rebook".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } +}