From 1d03c1ff1cac58a277454086e57aabc515ac048c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 26 Apr 2026 16:04:32 +0300 Subject: [PATCH 01/11] updates --- .../appointments/widgets/appointment_card.dart | 9 ++++++--- lib/presentation/home/landing_page.dart | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index aec94e10..31a030d9 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -256,10 +256,13 @@ class _AppointmentCardState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), - (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") - .toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + Expanded( + child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") + .toText16(isBold: true, maxlines: 2, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + ), SizedBox(width: 12.w), (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) ? Image.network( @@ -275,7 +278,7 @@ class _AppointmentCardState extends State { Wrap( direction: Axis.horizontal, spacing: 6.h, - runSpacing: 4.h, + runSpacing: 6.h, children: [ AppCustomChipWidget( labelText: widget.isLoading diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 2beaa344..32f9d1df 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -459,7 +459,7 @@ class _LandingPageState extends State { padding: EdgeInsets.only(left: 16.h, right: 16.h), itemBuilder: (context, index) { return SizedBox( - height: 255.h, + height: isFoldable ? 290.h : 255.h, width: 250.w, child: getIndexSwiperCard(index), ); From cb299c5bca380e5e598ad5778f374dce46145e66 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 26 Apr 2026 17:49:24 +0300 Subject: [PATCH 02/11] Design changes on fold --- ios/Podfile.lock | 4 +- lib/core/api/api_client.dart | 2 +- lib/core/utils/push_notification_handler.dart | 97 ++-- lib/extensions/string_extensions.dart | 2 + .../appointment_details_page.dart | 184 +++++--- .../widgets/appointment_card.dart | 201 +++++---- .../medical_file_appointment_card.dart | 420 +++++++++--------- .../symptoms_checker/user_info_selection.dart | 15 +- .../user_info_flow_manager.dart | 2 +- pubspec.lock | 8 +- pubspec.yaml | 2 - 11 files changed, 515 insertions(+), 422 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b796a719..601f8fb0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -601,7 +601,7 @@ SPEC CHECKSUMS: OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba - PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82 + PayFortSDK: e12f79051ae15b2e29536771a45354900180bfcb permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 @@ -612,7 +612,7 @@ SPEC CHECKSUMS: shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 + SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 269b204c..34f56efe 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.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/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; @@ -16,7 +17,6 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:http/http.dart' as http; -import '../exceptions/api_failure.dart'; abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 23cb4f51..c96591ab 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -9,18 +9,17 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_callkit_incoming/entities/android_params.dart'; -import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/entities/call_kit_params.dart'; import 'package:flutter_callkit_incoming/entities/ios_params.dart'; import 'package:flutter_callkit_incoming/entities/notification_params.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart'; import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uuid/uuid.dart'; @@ -33,7 +32,7 @@ String? _currentSessionId; // Store session ID for API call // |--> Push Notification Background @pragma('vm:entry-point') Future backgroundMessageHandler(dynamic message) async { - print("Firebase backgroundMessageHandler!!!"); + log("Firebase backgroundMessageHandler!!!"); await Firebase.initializeApp(); fir.RemoteMessage message_; @@ -47,20 +46,20 @@ Future backgroundMessageHandler(dynamic message) async { callPage(String sessionID, String token) async {} _incomingCall(Map data) async { - print('the value of the _incomingCall remote message is $data'); + log('the value of the _incomingCall remote message is $data'); // Check if there's already a call in progress to prevent duplicates if (_isCallInProgress && _currentCallId != null) { - print('โš ๏ธ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); + log('โš ๏ธ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); return; } String roomID = data['session_id'] ?? ''; String callTypeID = data['AppointmentNo'] ?? ''; - print('๐Ÿ” Extracted from notification data:'); - print(' - roomID (session_id): "$roomID"'); - print(' - callTypeID (AppointmentNo): "$callTypeID"'); + log('๐Ÿ” Extracted from notification data:'); + log(' - roomID (session_id): "$roomID"'); + log(' - callTypeID (AppointmentNo): "$callTypeID"'); // Generate unique call ID var _currentUuid = Uuid().v4(); @@ -68,16 +67,16 @@ _incomingCall(Map data) async { _isCallInProgress = true; _currentSessionId = roomID; // Store session ID for decline API call - print('๐Ÿ“ž Starting new call with ID: $_currentCallId, Session: $_currentSessionId'); + log('๐Ÿ“ž Starting new call with ID: $_currentCallId, Session: $_currentSessionId'); await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID); await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID); await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true); - print('๐Ÿ’พ Saved to cache:'); - print(' - CacheConst.zoomRoomID: "$roomID"'); - print(' - CacheConst.callTypeID: "$callTypeID"'); - print(' - CacheConst.isAppOpenedFromCall: true'); + log('๐Ÿ’พ Saved to cache:'); + log(' - CacheConst.zoomRoomID: "$roomID"'); + log(' - CacheConst.callTypeID: "$callTypeID"'); + log(' - CacheConst.isAppOpenedFromCall: true'); WidgetsFlutterBinding.ensureInitialized(); @@ -148,7 +147,7 @@ _incomingCall(Map data) async { // Start 60-second timeout timer to auto-dismiss call _callTimeoutTimer?.cancel(); // Cancel any existing timer _callTimeoutTimer = Timer(Duration(seconds: 40), () async { - print('โฑ๏ธ Call timeout (60 seconds) - auto dismissing call notification'); + log('โฑ๏ธ Call timeout (60 seconds) - auto dismissing call notification'); await _endCallAndCleanup(_currentUuid); }); @@ -158,8 +157,6 @@ _incomingCall(Map data) async { // Helper method to end call and cleanup Future _endCallAndCleanup(String callId, {bool isDeclined = false}) async { try { - print('๐Ÿงน Cleaning up call: $callId'); - // If call was declined, update session status via API if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) { await _updateSessionStatus(_currentSessionId!, 3, 'Patient'); @@ -184,10 +181,7 @@ Future _endCallAndCleanup(String callId, {bool isDeclined = false}) async await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false); await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, ''); await Utils.saveStringFromPrefs(CacheConst.callTypeID, ''); - - print('โœ… Call and push notification cleanup completed successfully'); } catch (e) { - print('โŒ Error during call cleanup: $e'); // Force reset flags even if there's an error _isCallInProgress = false; _currentCallId = null; @@ -200,27 +194,20 @@ Future _endCallAndCleanup(String callId, {bool isDeclined = false}) async // Helper method to update session status via API Future _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async { try { - final apiClient = getIt.get(); - Map requestBody = { - "Open_SessionID": sessionId, - "SessionStatus": sessionStatus, - "SessionEndedBy": sessionEndedBy - }; + Map requestBody = {"Open_SessionID": sessionId, "SessionStatus": sessionStatus, "SessionEndedBy": sessionEndedBy}; await apiClient.post( CHANGE_PATIENT_ER_SESSION, body: requestBody, - onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - print('โœ… Session status updated successfully: $response'); - }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) {}, onFailure: (error, statusCode, {messageStatus, failureType}) { - print('โŒ Failed to update session status: $error'); + log('โŒ Failed to update session status: $error'); }, ); } catch (e) { - print('โŒ Exception updating session status: $e'); + log('โŒ Exception updating session status: $e'); } } @@ -252,7 +239,7 @@ Future openCallPage(BuildContext context) async { // ); // } } catch (err) { - print(err); + log(err.toString()); // await PlatformExceptionAlertDialog( // exception: Exception(err), // ).show(context); @@ -351,7 +338,7 @@ class PushNotificationHandler { // int seconds = 30, // }) async { // timeOutTimer = Timer(Duration(seconds: seconds), () async { - // print('๐ŸŽˆ example: timeOut'); + // log('๐ŸŽˆ example: timeOut'); // final incomingCallerName = await voIPKit.getIncomingCallerName(); // voIPKit.unansweredIncomingCall( // skipLocalNotification: false, @@ -366,19 +353,19 @@ class PushNotificationHandler { if (Platform.isIOS) { voIPKit.getVoIPToken().then((value) { - print("๐ŸŽˆ APNS VOIP KIT TOKEN: $value"); + log("๐ŸŽˆ APNS VOIP KIT TOKEN: $value"); Utils.saveStringFromPrefs(CacheConst.voipToken, value ?? ""); // AppSharedPreferences().setString(APNS_TOKEN, value!); }); voIPKit.onDidUpdatePushToken = (String token) { - print('๐ŸŽˆ example: onDidUpdatePushToken: $token'); + log('๐ŸŽˆ example: onDidUpdatePushToken: $token'); }; voIPKit.onDidReceiveIncomingPush = ( Map payload, ) async { - print('๐ŸŽˆ example: onDidReceiveIncomingPush $payload'); + log('๐ŸŽˆ example: onDidReceiveIncomingPush $payload'); // _timeOut(); }; @@ -387,20 +374,22 @@ class PushNotificationHandler { String callerId, ) async { try { - print('๐ŸŽˆ example: onDidRejectIncomingCall $uuid - $callerId'); + log('๐ŸŽˆ example: onDidRejectIncomingCall $uuid - $callerId'); timeOutTimer.cancel(); // Cleanup on reject with API call if (_currentCallId != null) { await _endCallAndCleanup(_currentCallId!, isDeclined: true); } - } catch (err) {} + } catch (err) { + log(err.toString()); + } }; voIPKit.onDidAcceptIncomingCall = ( String uuid, String callerId, ) async { - print('๐ŸŽˆ example: onDidAcceptIncomingCall $uuid - $callerId'); + log('๐ŸŽˆ example: onDidAcceptIncomingCall $uuid - $callerId'); await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); await voIPKit.callConnected(); @@ -430,12 +419,12 @@ class PushNotificationHandler { if (Platform.isAndroid) { try { final fcmToken = await FirebaseMessaging.instance.getToken().catchError((err) { - print(err); + log(err); }); if (fcmToken != null) onToken(fcmToken); // } } catch (ex) { - print("Notification Exception: $ex"); + log("Notification Exception: $ex"); } FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); } @@ -467,7 +456,7 @@ class PushNotificationHandler { } catch (ex) {} FirebaseMessaging.onMessage.listen((RemoteMessage message) async { - print("Firebase onMessage!!!"); + log("Firebase onMessage!!!"); // showCallkitIncoming(); if (Platform.isIOS) { await Future.delayed(Duration(milliseconds: 3000)).then((value) { @@ -479,7 +468,7 @@ class PushNotificationHandler { }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async { - print("Firebase onMessageOpenedApp!!!"); + log("Firebase onMessageOpenedApp!!!"); if (Platform.isIOS) { await Future.delayed(Duration(milliseconds: 3000)).then((value) { newMessage(message); @@ -491,21 +480,21 @@ class PushNotificationHandler { if (Platform.isIOS) { FirebaseMessaging.instance.getAPNSToken().then((String? token) { - print("Push Notification getAPNSToken: ${token!}"); + log("Push Notification getAPNSToken: ${token!}"); }).catchError((err) { - print("Push Notification getAPNSToken ERR: ${err.toString()}"); + log("Push Notification getAPNSToken ERR: ${err.toString()}"); }); } FirebaseMessaging.instance.getToken().then((String? token) { - print("Push Notification getToken: ${token!}"); + log("Push Notification getToken: ${token!}"); onToken(token!); }).catchError((err) { - print(err); + log(err); }); FirebaseMessaging.instance.onTokenRefresh.listen((fcm_token) { - print("Push Notification onTokenRefresh: $fcm_token"); + log("Push Notification onTokenRefresh: $fcm_token"); onToken(fcm_token); }); @@ -521,11 +510,11 @@ class PushNotificationHandler { } newMessage(RemoteMessage remoteMessage) async { - print("Remote Message: ${remoteMessage.data}"); + log("Remote Message: ${remoteMessage.data}"); if (remoteMessage.data.isEmpty) { return; } - debugPrint('the value of the remote message is ${remoteMessage.data}'); + log('the value of the remote message is ${remoteMessage.data}'); if (remoteMessage.data['is_call'] == 'true' || remoteMessage.data['is_call'] == true) { _incomingCall(remoteMessage.data); // showCallkitIncoming(); @@ -547,7 +536,7 @@ class PushNotificationHandler { } onToken(String token) async { - print("Push Notification Token: $token"); + log("Push Notification Token: $token"); await Utils.saveStringFromPrefs(CacheConst.pushToken, token); } @@ -569,11 +558,11 @@ class PushNotificationHandler { // Permission.audio, // Permission.microphone, ].request(); - print("=-=-=-=-=-=-=-=-=-=-"); - print(statuses[Permission.notification]); + log("=-=-=-=-=-=-=-=-=-=-"); + log(statuses[Permission.notification].toString()); } } catch (_) { - debugPrint(_.toString()); + log(_.toString()); } } } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 7a71b345..469745cb 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -115,12 +115,14 @@ extension EmailValidator on String { bool isCenter = false, double? height, double? letterSpacing, + TextOverflow? textOverflow, int maxLine = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : textAlignment, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( + overflow: textOverflow, fontSize: 12.f, fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 87ca5e93..83fd4342 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -262,7 +262,10 @@ class _AppointmentDetailsPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.appointmentDetails.tr(context: context), - report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment==false && widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==true && widget.patientAppointmentHistoryResponseModel.isActiveDoctor! == true + report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment == false && + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == true && + widget.patientAppointmentHistoryResponseModel.isActiveDoctor! == true ? () { contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'ุดูƒูˆู‰ ุนู„ู‰ ู…ูˆุนุฏ')); contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); @@ -280,8 +283,8 @@ class _AppointmentDetailsPageState extends State { children: [ AppointmentDoctorCard( // renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - renderWidgetForERDisplay: - ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), + renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); @@ -416,14 +419,17 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisSize: MainAxisSize.max, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 40.w, height: 40.h, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_reminder_icon, width: 40.w, height: 40.h, applyThemeColor: false), SizedBox(width: 8.w), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), + LocaleKeys.notifyMeBeforeAppointment + .tr(context: context) + .toText11(color: AppColors.textColorLight, isBold: true), ], ), const Spacer(), @@ -438,48 +444,53 @@ class _AppointmentDetailsPageState extends State { inactiveCircleColor: AppColors.greyTextColor, activeIconColor: AppColors.bgGreenColor, inactiveIconColor: AppColors.greyTextColor, - activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), - inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), + activeIcon: + Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), + inactiveIcon: + Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), onChanged: (newValue) async { CalenderUtilsNew calender = CalenderUtilsNew.instance; bool isEventAddedOrRemoved = false; if (newValue == true) { DateTime startDate = DateTime.now(); - DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); + DateTime endDate = + DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); // Show reminder bottom sheet and check if permission was granted bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet( - context, - endDate, - widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", - "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", - "", - "", - title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - description: - "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - onSuccess: () { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - isMultiAllowed: true, - onMultiDateSuccess: (int selectedIndex) async { - isEventAddedOrRemoved = await calender.createOrUpdateEvent( - title: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - description: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), - eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - location: '', - reminderMinutes: selectedIndex); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); - }, - isForAppointment: true - ); + context, + endDate, + widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", + "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", + "", + "", + title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + description: + "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", + onSuccess: () { + setState(() { + myAppointmentsViewModel.setAppointmentReminder( + newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + isMultiAllowed: true, + onMultiDateSuccess: (int selectedIndex) async { + isEventAddedOrRemoved = await calender.createOrUpdateEvent( + title: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + description: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", + scheduleDateTime: + DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), + eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + location: '', + reminderMinutes: selectedIndex); + setState(() { + myAppointmentsViewModel.setAppointmentReminder( + isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + }); + }, + isForAppointment: true); // If permission was not granted, revert the switch back to OFF if (!permissionGranted) { @@ -490,7 +501,8 @@ class _AppointmentDetailsPageState extends State { id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); setState(() { - myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + myAppointmentsViewModel.setAppointmentReminder( + !isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); }); } }, @@ -581,8 +593,12 @@ class _AppointmentDetailsPageState extends State { LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), SizedBox(height: 4.h), (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true) - : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, isBold: true)), + ? LocaleKeys.notConfirmed + .tr(context: context) + .toText12(color: AppColors.primaryRedColor, isBold: true) + : LocaleKeys.confirmed + .tr(context: context) + .toText12(color: AppColors.successColor, isBold: true)), SizedBox(height: 16.h), ], ), @@ -640,7 +656,9 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), + LocaleKeys.doctorWillCallYou + .tr(context: context) + .toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -648,13 +666,17 @@ class _AppointmentDetailsPageState extends State { ) : Stack( children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24.r), - // Todo: what is this???? Api Key??? ๐Ÿ˜ฒ - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", - fit: BoxFit.contain, + SizedBox( + width: double.infinity, + child: ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24.r), + // Todo: what is this???? Api Key??? ๐Ÿ˜ฒ + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", + fit: BoxFit.cover, + width: double.infinity, + ), ), ), Positioned( @@ -663,7 +685,8 @@ class _AppointmentDetailsPageState extends State { width: MediaQuery.of(context).size.width - 85.w, child: CustomButton( onPressed: () async { - if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) { + if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || + widget.patientAppointmentHistoryResponseModel.projectID == 120) { showDirectionsBottomSheet(); } else { await MapLauncher.showMarker( @@ -683,7 +706,9 @@ class _AppointmentDetailsPageState extends State { }, text: LocaleKeys.getDirections.tr(context: context), backgroundColor: AppColors.bookAppointment.withValues(alpha: 0.8), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + borderColor: AppointmentType.getNextActionButtonColor( + widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), textColor: Colors.white, fontSize: 14.f, fontWeight: FontWeight.w600, @@ -974,7 +999,8 @@ class _AppointmentDetailsPageState extends State { ); Navigator.of(context).push( CustomPageRoute( - page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), + page: PrescriptionDetailPage( + isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), ), ); } else { @@ -1289,7 +1315,8 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || + widget.patientAppointmentHistoryResponseModel.nextAction == 20) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1297,7 +1324,10 @@ class _AppointmentDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), - Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, + Utils.getPaymentAmountWithSymbol( + widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), + AppColors.blackColor, + 13, isSaudiCurrency: true), ], ), @@ -1305,8 +1335,10 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), - "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), + Expanded( + child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), + "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})" + .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), ], ), SizedBox(height: 18.h), @@ -1320,7 +1352,10 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, + Utils.getPaymentAmountWithSymbol( + widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), + AppColors.blackColor, + 17, isSaudiCurrency: true), ], ), @@ -1358,7 +1393,8 @@ class _AppointmentDetailsPageState extends State { handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white, fontSize: 16.f, fontWeight: FontWeight.w600, @@ -1398,7 +1434,10 @@ class _AppointmentDetailsPageState extends State { text: LocaleKeys.insideHospital.tr(context: context), onPressed: () { Navigator.pop(context); - initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), + initPenguinSDK( + widget.patientAppointmentHistoryResponseModel.projectID == 130 + ? 1 + : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString()); }, backgroundColor: AppColors.primaryRedColor, @@ -1417,12 +1456,14 @@ class _AppointmentDetailsPageState extends State { Navigator.pop(context); await MapLauncher.showMarker( mapType: MapType.google, - coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", ).catchError((err) { MapLauncher.showMarker( mapType: Platform.isIOS ? MapType.apple : MapType.google, - coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", ); }); @@ -1456,7 +1497,9 @@ class _AppointmentDetailsPageState extends State { Permission.bluetoothScan, Permission.activityRecognition, ].request().whenComplete(() { - PenguinMethodChannel().launch("penguin", getIt.get().isArabic() ? "ar" : "en", getIt.get().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data); + PenguinMethodChannel().launch("penguin", getIt.get().isArabic() ? "ar" : "en", + getIt.get().getAuthenticatedUser()?.patientId?.toString() ?? "", true, + details: data); }); } } @@ -1514,12 +1557,20 @@ class _AppointmentDetailsPageState extends State { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { + showCommonBottomSheet(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); Navigator.of(context).pop(); - }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true); + }, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isAutoDismiss: true, + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + isSuccessDialog: true); }); // LoaderBottomSheet.hideLoader(); case 15: @@ -1539,7 +1590,8 @@ class _AppointmentDetailsPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.warningAnimation, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox( height: 12, ), diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 8b178c47..d53b541b 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:ui' as ui; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -18,6 +20,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie 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/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -26,8 +29,6 @@ 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/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'dart:ui' as ui; -import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:lottie/lottie.dart'; class AppointmentCard extends StatefulWidget { @@ -42,6 +43,7 @@ class AppointmentCard extends StatefulWidget { final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; final bool isForRate; + // bool isAppointmentWithin4Hours = false; const AppointmentCard( @@ -189,20 +191,28 @@ class _AppointmentCardState extends State { runSpacing: 6.h, children: [ AppCustomChipWidget( - icon: widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), + icon: + widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), iconColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), - labelText: widget.isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), + labelText: widget.isLoading + ? LocaleKeys.walkin.tr(context: context) + : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), backgroundColor: widget.isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), ).toShimmer2(isShow: widget.isLoading), AppCustomChipWidget( - labelText: - widget.isLoading ? 'OutPatient' : (appState.isArabic() ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + labelText: widget.isLoading + ? 'OutPatient' + : (appState.isArabic() + ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! + : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!), backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), textColor: AppColors.warningColorYellow, ).toShimmer2(isShow: widget.isLoading), AppCustomChipWidget( - labelText: widget.isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), + labelText: widget.isLoading + ? 'Booked' + : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ).toShimmer2(isShow: widget.isLoading), @@ -218,7 +228,9 @@ class _AppointmentCardState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.network( - widget.isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : widget.patientAppointmentHistoryResponseModel.doctorImageURL!, + widget.isLoading + ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' + : widget.patientAppointmentHistoryResponseModel.doctorImageURL!, width: 63.h, height: 63.h, fit: BoxFit.cover, @@ -242,8 +254,10 @@ class _AppointmentCardState extends State { Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), SizedBox(height: 2.h), (isFoldable || isTablet) - ? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true) - : "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + ? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}" + .toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}" + .toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ], ), ).circle(100).toShimmer2(isShow: widget.isLoading), @@ -258,10 +272,16 @@ class _AppointmentCardState extends State { Row( children: [ (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), - (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") - .toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + Expanded( + child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}").toText16( + isBold: true, + maxlines: 1, + textOverflow: TextOverflow.ellipsis, + isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + ), SizedBox(width: 12.w), - (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) + (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && + widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) ? Image.network( widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, @@ -419,40 +439,40 @@ class _AppointmentCardState extends State { // } else { return CustomButton( text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), - onPressed: () { - if (widget.isFromMedicalReport) { - if (widget.isForFeedback) { - widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); - } else { - widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); - } - Navigator.pop(context, false); + onPressed: () { + if (widget.isFromMedicalReport) { + if (widget.isForFeedback) { + widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); } else { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); } - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); + Navigator.pop(context, false); + } else { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + } + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); // } } else { if (widget.isFromMedicalReport) { @@ -486,7 +506,7 @@ class _AppointmentCardState extends State { } return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) ? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && - widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) ? // Show only View Details button without arrow when rebooking not allowed _getArrivedButton(context) : Row( @@ -498,8 +518,10 @@ class _AppointmentCardState extends State { : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () => handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context), - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w600, @@ -589,10 +611,10 @@ class _AppointmentCardState extends State { // Show Rebook button return CustomButton( - borderSide: BorderSide( - color: AppColors.textColor, - width: 1.2, - ), + borderSide: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), text: LocaleKeys.rebookSameDoctor.tr(context: context), onPressed: () => openDoctorScheduleCalendar(context), backgroundColor: AppColors.transparent, @@ -620,7 +642,8 @@ class _AppointmentCardState extends State { ); } else { if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { - widget.bookAppointmentsViewModel.getAppointmentNearestGate(projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID); + widget.bookAppointmentsViewModel.getAppointmentNearestGate( + projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID); } Navigator.of(context) .push( @@ -708,46 +731,47 @@ class _AppointmentCardState extends State { children: [ Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), - SizedBox(height: 12,), - LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( - color: AppColors.textColor, - isCenter: true, + SizedBox( + height: 12, ), + LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( + color: AppColors.textColor, + isCenter: true, + ), SizedBox(height: 24.h), - // Countdown Timer - DD : HH : MM : SS format with labels Directionality( - textDirection: ui.TextDirection.ltr, - child:Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Days - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', - LocaleKeys.days.tr(context: context), - ), - _buildTimeSeparator(), - // Hours - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', - LocaleKeys.hours.tr(context: context), - ), - _buildTimeSeparator(), - // Minutes - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', - LocaleKeys.minutes.tr(context: context), - ), - _buildTimeSeparator(), - // Seconds - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', - LocaleKeys.seconds.tr(context: context), - ), - ], - )), + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Days + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', + LocaleKeys.days.tr(context: context), + ), + _buildTimeSeparator(), + // Hours + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', + LocaleKeys.hours.tr(context: context), + ), + _buildTimeSeparator(), + // Minutes + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.minutes.tr(context: context), + ), + _buildTimeSeparator(), + // Seconds + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.seconds.tr(context: context), + ), + ], + )), SizedBox(height: 24.h), // Green Acknowledge button with checkmark icon CustomButton( @@ -800,4 +824,3 @@ class _AppointmentCardState extends State { } } } - diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 393dc3f6..9c64884a 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:ui' as ui; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -18,10 +20,8 @@ import 'package:hmg_patient_app_new/presentation/appointments/appointment_paymen 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/common_bottom_sheet.dart'; - -import 'dart:ui' as ui; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class MedicalFileAppointmentCard extends StatefulWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; @@ -138,169 +138,193 @@ class _MedicalFileAppointmentCardState extends State richText: Directionality( textDirection: ui.TextDirection.ltr, child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false) - .toText12(color: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) + .toText12( + color: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + isBold: true, + isEnglishOnly: true) .paddingSymmetrical(8.w, 0), ), - icon: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, + icon: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? AppAssets.appointment_calendar_icon + : AppAssets.alarm_clock_icon, iconColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconSize: 16.w, - backgroundColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, + backgroundColor: + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, textColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, padding: EdgeInsets.only(top: 12.h, bottom: 12.h, left: 8.w, right: 8.w), ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), - width: 200.w, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Image.network( - widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 25.w, - height: 27.h, - fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - SizedBox(width: 8.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - (widget.patientAppointmentHistoryResponseModel.clinicName ?? "") - .toText12(maxLine: 1, isBold: true, color: AppColors.greyTextColor) - .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - ], + IntrinsicWidth( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 25.w, + height: 27.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "") + .toText14( + isBold: true, + maxlines: 1, + isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")) + .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + (widget.patientAppointmentHistoryResponseModel.clinicName ?? "") + .toText12(maxLine: 1, textOverflow: TextOverflow.ellipsis, isBold: true, color: AppColors.greyTextColor) + .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + ], + ), ), - ), - ], - ), - SizedBox(height: 12.h), - // Check if doctor is active - if not, show only View Details button - (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) - ? // Doctor is active - check rebooking logic - (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && - widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) - ? // Show only the button without arrow when rebooking not allowed - widget.myAppointmentsViewModel.isMyAppointmentsLoading - ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) - : AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : // Normal flow - show button with arrow - Row( - children: [ - widget.myAppointmentsViewModel.isMyAppointmentsLoading - ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) - : Expanded( - flex: 7, - child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - ), - SizedBox(width: 8.w), - ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - ? SizedBox.shrink() - : Expanded( - flex: 2, - child: Container( - height: 40.h, - width: 40.w, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.r, - ), - child: Padding( - padding: EdgeInsets.all(10.w), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - iconColor: AppColors.whiteColor, - icon: AppAssets.forward_arrow_icon_small, - width: 40.w, - height: 40.h, - fit: BoxFit.contain, - ), + ], + ), + SizedBox(height: 8.h), + // Check if doctor is active - if not, show only View Details button + (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) + ? // Doctor is active - check rebooking logic + (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) + ? // Show only the button without arrow when rebooking not allowed + widget.myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) + : AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); + }, + backgroundColor: + AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) + : // Normal flow - show button with arrow + Row( + children: [ + widget.myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) + : Expanded( + flex: 7, + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context) + .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); + }, + backgroundColor: + AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.15), + borderColor: + AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), + textColor: + AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: + AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + ), + SizedBox(width: 8.w), + ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + ? SizedBox.shrink() + : Expanded( + flex: 2, + child: Container( + height: 40.h, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.r, ), - ), - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + child: Padding( + padding: EdgeInsets.all(10.w), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + iconColor: AppColors.whiteColor, + icon: AppAssets.forward_arrow_icon_small, + width: 40.w, + height: 40.h, + fit: BoxFit.contain, + ), + ), ), - ) - .then((val) { - // widget.myAppointmentsViewModel.initAppointmentsViewModel(); - // widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }), - ), - ], - ) - : // Doctor is not active - show only View Details button - CustomButton( - text: LocaleKeys.viewDetails.tr(context: context), - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - ], - ).paddingAll(16.w), + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }), + ), + ], + ) + : // Doctor is not active - show only View Details button + CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + ], + ).paddingAll(16.w), + ), ), ], ); @@ -393,16 +417,16 @@ class _MedicalFileAppointmentCardState extends State // No action needed - go to details Navigator.of(context) .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) .then((val) {}); break; case 10: // Confirm appointment - go to details Navigator.of(context) .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) .then((val) {}); break; case 15: @@ -425,41 +449,42 @@ class _MedicalFileAppointmentCardState extends State children: [ // Message text LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( - color: AppColors.textColor, - isCenter: true, - ), + color: AppColors.textColor, + isCenter: true, + ), SizedBox(height: 24.h), // Countdown Timer - DD : HH : MM : SS format with labels Directionality( - textDirection: ui.TextDirection.ltr, child:Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Days - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', - LocaleKeys.days.tr(context: context), - ), - _buildTimeSeparator(), - // Hours - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', - LocaleKeys.hours.tr(context: context), - ), - _buildTimeSeparator(), - // Minutes - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', - LocaleKeys.minutes.tr(context: context), - ), - _buildTimeSeparator(), - // Seconds - _buildTimeUnit( - _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', - LocaleKeys.seconds.tr(context: context), - ), - ], - )), + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Days + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', + LocaleKeys.days.tr(context: context), + ), + _buildTimeSeparator(), + // Hours + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', + LocaleKeys.hours.tr(context: context), + ), + _buildTimeSeparator(), + // Minutes + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.minutes.tr(context: context), + ), + _buildTimeSeparator(), + // Seconds + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.seconds.tr(context: context), + ), + ], + )), SizedBox(height: 24.h), // Green Acknowledge button with checkmark icon CustomButton( @@ -502,26 +527,25 @@ class _MedicalFileAppointmentCardState extends State // Confirm livecare - go to details Navigator.of(context) .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) .then((val) {}); break; case 90: // Check-in - go to details Navigator.of(context) .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) .then((val) {}); break; default: // Default - go to details Navigator.of(context) .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) .then((val) {}); } } } - diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index 9113487d..bef52a59 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -99,9 +99,8 @@ class _UserInfoSelectionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText14(isBold: true), - subTitle - .toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) - .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), + subTitle.toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true).toShimmer2( + isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), ], @@ -159,7 +158,9 @@ class _UserInfoSelectionPageState extends State { } } } else { - name = ""; /// as per the mahmooud instruction, if user is not authenticated, we will not show any name in the greeting message + name = ""; + + /// as per the mahmooud instruction, if user is not authenticated, we will not show any name in the greeting message } return Scaffold( @@ -167,7 +168,10 @@ class _UserInfoSelectionPageState extends State { body: Consumer2( builder: (context, viewModel, hmgServicesVM, child) { // Check if any field is empty - bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null; + bool hasEmptyFields = viewModel.selectedGender == null || + viewModel.selectedAge == null || + viewModel.selectedHeight == null || + viewModel.selectedWeight == null; // Get display values String genderText = _getLocalizedGender(viewModel.selectedGender, context); @@ -313,6 +317,7 @@ class _UserInfoSelectionPageState extends State { ), ], ), + SizedBox(height: 24.h), ], ).paddingSymmetrical(24.w, 0), ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart index fdfe2288..6672a968 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart @@ -181,7 +181,7 @@ class _UserInfoFlowManagerState extends State { color: AppColors.whiteColor, borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), ), - padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), + padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), child: SafeArea( top: false, child: isSingleEdit diff --git a/pubspec.lock b/pubspec.lock index dd27bab8..3bd9576a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1042,13 +1042,13 @@ packages: source: hosted version: "1.2.1" image_picker_android: - dependency: transitive + dependency: "direct main" description: name: image_picker_android - sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622" url: "https://pub.dev" source: hosted - version: "0.8.13+10" + version: "0.8.13+16" image_picker_for_web: dependency: transitive description: @@ -1082,7 +1082,7 @@ packages: source: hosted version: "0.2.2+1" image_picker_platform_interface: - dependency: transitive + dependency: "direct main" description: name: image_picker_platform_interface sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" diff --git a/pubspec.yaml b/pubspec.yaml index a27fb251..7cb61939 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,9 +101,7 @@ dependencies: flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301 flutter_image_compress: ^2.3.0 - scrollable_positioned_list: ^0.3.8 - in_app_review: ^2.0.11 dev_dependencies: From 0e2b8db63a9cdc7216b95778f91c74a73445979b Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 27 Apr 2026 11:50:33 +0300 Subject: [PATCH 03/11] Updates & fixes --- .../book_appointments_view_model.dart | 18 +++++++++--------- lib/main.dart | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 7ac6ae45..1d15fdd2 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -421,14 +421,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { }); } } else { - for (var group in doctorsListGrouped) { - group.sort((a, b) { - var aSlot = a.decimalDoctorRate; - var bSlot = b.decimalDoctorRate; - if (aSlot == null || bSlot == null) return 0; - return bSlot.compareTo(aSlot); - }); - } + // for (var group in doctorsListGrouped) { + // group.sort((a, b) { + // var aSlot = a.decimalDoctorRate; + // var bSlot = b.decimalDoctorRate; + // if (aSlot == null || bSlot == null) return 0; + // return bSlot.compareTo(aSlot); + // }); + // } // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); } @@ -677,7 +677,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { doctorsList = apiResponse.data!; filteredDoctorList = doctorsList; isDoctorsListLoading = false; - doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); + // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); initializeFilteredList(); clearSearchFilters(); getFiltersFromDoctorList(); diff --git a/lib/main.dart b/lib/main.dart index 23f7c4d7..4aa20551 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -88,7 +88,9 @@ Future callAppStateInitializations() async { appState.setDeviceTypeID = deviceTypeId; // Pass all uncaught "fatal" errors from the framework to Crashlytics - FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; + if (!kDebugMode) { + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; + } // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics PlatformDispatcher.instance.onError = (error, stack) { From 7b1684c45508210d52e307bd468506a017542843 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 27 Apr 2026 18:00:41 +0300 Subject: [PATCH 04/11] Design fixes on Fold --- lib/core/utils/size_utils.dart | 39 +- ....dart => health_data_transformations.dart} | 2 +- .../health_provider.dart | 37 +- .../health_service.dart | 2 +- .../{Vitals.dart => vitals_data_model.dart} | 42 +- .../health_connect_helper.dart | 2 +- .../appointment_details_page.dart | 453 +---- .../widgets/appointment_doctor_card.dart | 49 +- .../widgets/appointment_calendar.dart | 30 +- .../hmg_services/services_page.dart | 859 ++++----- lib/presentation/home/landing_page.dart | 897 ++++----- .../home/widgets/large_service_card.dart | 176 +- .../medical_file/medical_file_page.dart | 1716 ++++++++--------- .../medical_file_appointment_card.dart | 305 +-- .../widgets/medical_file_card.dart | 8 +- .../smartwatches/activity_detail.dart | 2 +- .../smartwatches/huawei_health_example.dart | 1563 --------------- .../smartwatches/smart_watch_activity.dart | 252 --- .../smart_watches_health_data_screen.dart | 188 ++ .../smartwatches/smartwatch_home_page.dart | 70 +- .../smartwatch_instructions_page.dart | 111 +- lib/routes/app_routes.dart | 2 - 22 files changed, 2348 insertions(+), 4457 deletions(-) rename lib/features/smartwatch_health_data/{HealthDataTransformation.dart => health_data_transformations.dart} (99%) rename lib/features/smartwatch_health_data/model/{Vitals.dart => vitals_data_model.dart} (69%) delete mode 100644 lib/presentation/smartwatches/huawei_health_example.dart delete mode 100644 lib/presentation/smartwatches/smart_watch_activity.dart create mode 100644 lib/presentation/smartwatches/smart_watches_health_data_screen.dart diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index d04ad70d..c7f3fa06 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'dart:math' as math; import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design. @@ -26,8 +25,14 @@ extension ResponsiveExtension on num { /// Check if device is likely a foldable bool get _isFoldable { double aspectRatio = _screenWidth / _screenHeight; - // Foldable devices typically have aspect ratios close to 1:1 when unfolded - return (aspectRatio > 0.9 && aspectRatio < 1.1) && (_screenWidth > 700 || _screenHeight > 700); + double shorterSide = _screenWidth < _screenHeight ? _screenWidth : _screenHeight; + + // Foldable devices (unfolded) typically have: + // - Shorter side > 600 logical pixels (to exclude regular phones) + // - Aspect ratio between 0.80 and 0.92 (almost square, like Galaxy Z Fold) + // Galaxy Z Fold 5: 1812x2176 physical, ~690x796 logical = 0.866 aspect ratio + // Regular phones: typically 375-430 width, aspect ratio 0.45-0.55 + return (shorterSide > 600) && (aspectRatio > 0.80 && aspectRatio < 0.92); } /// Scale text size - enhanced for foldable devices @@ -53,7 +58,7 @@ extension ResponsiveExtension on num { double get w { double baseScale = (this * _screenWidth) / figmaDesignWidth; - if (_isFoldable|| isTablet ) { + if (_isFoldable || isTablet) { // For foldables, use more conservative width scaling double scale = _screenWidth / figmaDesignWidthTF; scale = scale.clamp(0.8, 1.4); @@ -67,7 +72,7 @@ extension ResponsiveExtension on num { double get h { double baseScale = (this * _screenHeight) / figmaDesignHeight; - if (_isFoldable || isTablet ) { + if (_isFoldable || isTablet) { // For foldables, use height-based scaling but with constraints double scale = (_screenHeight / figmaDesignHeightTF).clamp(0.8, 1.4); return this * scale; @@ -225,10 +230,16 @@ class SizeUtils { deviceType = DeviceType.mobile; } - log("longerSide: $longerSide"); - log("shorterSide: $shorterSide"); - log("isTablet: $isTablet"); - log("isFoldable: $isFoldable"); + debugPrint("============ Device Detection ============"); + debugPrint("longerSide: $longerSide"); + debugPrint("shorterSide: $shorterSide"); + debugPrint("width: $width"); + debugPrint("height: $height"); + debugPrint("deviceType: $deviceType"); + debugPrint("isTablet: $isTablet"); + debugPrint("isFoldable: $isFoldable"); + debugPrint("aspectRatio: ${width / height}"); + debugPrint("=========================================="); } } @@ -241,6 +252,12 @@ bool get isDesktop => SizeUtils.deviceType == DeviceType.desktop; bool get isFoldable { double aspectRatio = SizeUtils.width / SizeUtils.height; - // Foldable devices typically have aspect ratios close to 1:1 when unfolded - return (aspectRatio > 0.9 && aspectRatio < 1.1) && (SizeUtils.width > 700 || SizeUtils.height > 700); + double shorterSide = SizeUtils.width < SizeUtils.height ? SizeUtils.width : SizeUtils.height; + + // Foldable devices (unfolded) typically have: + // - Shorter side > 600 logical pixels (to exclude regular phones) + // - Aspect ratio between 0.80 and 0.92 (almost square, like Galaxy Z Fold) + // Galaxy Z Fold 5: 1812x2176 physical, ~690x796 logical = 0.866 aspect ratio + // Regular phones: typically 375-430 width, aspect ratio 0.45-0.55 + return (shorterSide > 600) && (aspectRatio > 0.80 && aspectRatio < 0.92); } diff --git a/lib/features/smartwatch_health_data/HealthDataTransformation.dart b/lib/features/smartwatch_health_data/health_data_transformations.dart similarity index 99% rename from lib/features/smartwatch_health_data/HealthDataTransformation.dart rename to lib/features/smartwatch_health_data/health_data_transformations.dart index ffda4f4f..6f777353 100644 --- a/lib/features/smartwatch_health_data/HealthDataTransformation.dart +++ b/lib/features/smartwatch_health_data/health_data_transformations.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:intl/intl.dart'; -import 'model/Vitals.dart'; +import 'model/vitals_data_model.dart'; enum Durations { daily("daily"), diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart index 963c0352..c296f717 100644 --- a/lib/features/smartwatch_health_data/health_provider.dart +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -1,18 +1,17 @@ import 'package:flutter/foundation.dart'; import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.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/loading_utils.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smart_watches_health_data_screen.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; -import '../../core/common_models/data_points.dart'; -import '../../core/dependencies.dart'; -import '../../presentation/smartwatches/activity_detail.dart' show ActivityDetails; -import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; -import '../../services/navigation_service.dart' show NavigationService; -import 'HealthDataTransformation.dart'; -import 'model/Vitals.dart'; +import 'health_data_transformations.dart'; +import 'model/vitals_data_model.dart'; class HealthProvider with ChangeNotifier { final HealthService _healthService = HealthService(); @@ -22,7 +21,8 @@ class HealthProvider with ChangeNotifier { String selectedTimeRange = '7D'; int selectedTabIndex = 0; - SmartWatchTypes? selectedWatchType ; + SmartWatchTypes? selectedWatchType; + String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; HealthDataTransformation healthDataTransformation = HealthDataTransformation(); @@ -90,7 +90,7 @@ class HealthProvider with ChangeNotifier { healthData[type] = data; notifyListeners(); } catch (e) { - print('Error refreshing metric $type: $e'); + debugPrint('Error refreshing metric $type: $e'); } } @@ -129,14 +129,12 @@ class HealthProvider with ChangeNotifier { await getVitals(); // LoaderBottomSheet.hideLoader(); // await Future.delayed(Duration(seconds: 5)); - getIt.get().pushPage(page: SmartWatchActivity()); - print('Device initialized successfully'); + getIt.get().pushPage(page: SmartWatchesHealthDataScreen()); } notifyListeners(); } Future getVitals() async { - final result = await _healthService.getVitals(); vitals = result; LoaderBottomSheet.hideLoader(); @@ -186,15 +184,17 @@ class HealthProvider with ChangeNotifier { } selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection); break; - default: - {} - ; } notifyListeners(); } void navigateToDetails(String value, {required String sectionName, required String uom}) { - getIt.get().pushPage(page: ActivityDetails(selectedActivity: value, sectionName:sectionName, uom: uom,)); + getIt.get().pushPage( + page: ActivityDetails( + selectedActivity: value, + sectionName: sectionName, + uom: uom, + )); } void saveSelectedSection(String value) { @@ -243,7 +243,6 @@ class HealthProvider with ChangeNotifier { count++; } }); - print("total count is $count and total is $total"); averageValue = count > 0 ? total / count : null; notifyListeners(); } @@ -261,7 +260,7 @@ class HealthProvider with ChangeNotifier { String firstNonEmptyValue(List dataPoints) { try { - return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value; + return dataPoints.firstWhere((dp) => dp.value.trim().isNotEmpty).value; } catch (e) { return "0"; // no non-empty value found } diff --git a/lib/features/smartwatch_health_data/health_service.dart b/lib/features/smartwatch_health_data/health_service.dart index 7d42092d..78411bfb 100644 --- a/lib/features/smartwatch_health_data/health_service.dart +++ b/lib/features/smartwatch_health_data/health_service.dart @@ -5,7 +5,7 @@ import 'dart:io'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; -import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/Vitals.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/vitals_data_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; import 'package:permission_handler/permission_handler.dart'; diff --git a/lib/features/smartwatch_health_data/model/Vitals.dart b/lib/features/smartwatch_health_data/model/vitals_data_model.dart similarity index 69% rename from lib/features/smartwatch_health_data/model/Vitals.dart rename to lib/features/smartwatch_health_data/model/vitals_data_model.dart index 96386f2d..ba1ac9f1 100644 --- a/lib/features/smartwatch_health_data/model/Vitals.dart +++ b/lib/features/smartwatch_health_data/model/vitals_data_model.dart @@ -1,5 +1,5 @@ class Vitals { - String value; + String value; final String timestamp; final String unitOfMeasure; @@ -16,11 +16,6 @@ class Vitals { unitOfMeasure: map['uom'] ?? "", ); } - - - toString(){ - return "{\"value\": \"$value\", \"timeStamp\": \"$timestamp\", \"uom\": \"$unitOfMeasure\"}"; - } } class VitalsWRTType { @@ -31,15 +26,21 @@ class VitalsWRTType { final List activity; final List bodyOxygen; final List bodyTemperature; - double maxHeartRate = double.negativeInfinity; - double maxSleep = double.negativeInfinity; - double maxStep= double.negativeInfinity; - double maxActivity = double.negativeInfinity; - double maxBloodOxygen = double.negativeInfinity; - double maxBodyTemperature = double.negativeInfinity; - + double maxHeartRate = double.negativeInfinity; + double maxSleep = double.negativeInfinity; + double maxStep = double.negativeInfinity; + double maxActivity = double.negativeInfinity; + double maxBloodOxygen = double.negativeInfinity; + double maxBodyTemperature = double.negativeInfinity; - VitalsWRTType({required this.distance, required this.bodyOxygen, required this.bodyTemperature, required this.heartRate, required this.sleep, required this.step, required this.activity}); + VitalsWRTType( + {required this.distance, + required this.bodyOxygen, + required this.bodyTemperature, + required this.heartRate, + required this.sleep, + required this.step, + required this.activity}); factory VitalsWRTType.fromMap(Map map) { List activity = []; @@ -82,16 +83,23 @@ class VitalsWRTType { map["distance"].forEach((element) { element["uom"] = "km"; var data = Vitals.fromMap(element); - data.value = (double.parse(data.value)/1000).toStringAsFixed(2); + data.value = (double.parse(data.value) / 1000).toStringAsFixed(2); distance.add(data); }); - return VitalsWRTType(bodyTemperature: bodyTemperature, bodyOxygen: bodyOxygen, heartRate: heartRate, sleep: sleeps, step: steps, activity: activity, distance: distance); + return VitalsWRTType( + bodyTemperature: bodyTemperature, + bodyOxygen: bodyOxygen, + heartRate: heartRate, + sleep: sleeps, + step: steps, + activity: activity, + distance: distance); } Map> getVitals() { return { - "heartRate": heartRate , + "heartRate": heartRate, "sleep": sleep, "steps": step, "activity": activity, diff --git a/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart index 87e1cf3a..b77b5430 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart @@ -6,7 +6,7 @@ import 'package:health/health.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart' show WatchHelper; import 'package:permission_handler/permission_handler.dart'; -import '../model/Vitals.dart'; +import '../model/vitals_data_model.dart'; class HealthConnectHelper extends WatchHelper { final Health health = Health(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 83fd4342..1c0347e1 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -364,10 +364,6 @@ class _AppointmentDetailsPageState extends State { isFullScreen: false, isCloseButtonVisible: true, ); - // var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // }); }, onRescheduleTap: () async { openDoctorScheduleCalendar(); @@ -507,67 +503,11 @@ class _AppointmentDetailsPageState extends State { } }, ) - - // Switch( - // activeThumbColor: AppColors.successColor, - // // activeTrackColor: AppColors.successColor.withValues(alpha: .15), - // value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - // onChanged: (newValue) async { - // CalenderUtilsNew calender = CalenderUtilsNew.instance; - // bool isEventAddedOrRemoved = false; - // if(newValue == true){ - // DateTime startDate = DateTime.now(); - // DateTime endDate = DateUtil.convertStringToDate(widget - // .patientAppointmentHistoryResponseModel.appointmentDate); - // BottomSheetUtils().showReminderBottomSheet( - // context, - // endDate, - // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", - // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", - // "", - // "", - // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - // description: - // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - // onSuccess: () { - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - // }); - // }, - // isMultiAllowed: true, - // onMultiDateSuccess: (int selectedIndex) async { - // isEventAddedOrRemoved = await calender.createOrUpdateEvent( - // title: - // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // description: - // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - // scheduleDateTime: DateUtil.convertStringToDate(widget - // .patientAppointmentHistoryResponseModel.appointmentDate), - // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // location: '', - // reminderMinutes: selectedIndex - // ); - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // }); - // }, - // ); - // }else { - // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // }); - // } - // - // - // }, - // ), ], ).paddingSymmetrical(16.w, 0) ], ), ), - SizedBox(height: 16.h), !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? Column( @@ -602,48 +542,6 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 16.h), ], ), - // ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10) - // ? CustomButton( - // text: LocaleKeys.confirm.tr(), - // onPressed: () async { - // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); - // await myAppointmentsViewModel.confirmAppointment( - // patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - // onSuccess: (apiResponse) { - // LoaderBottomSheet.hideLoader(); - // myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - // myAppointmentsViewModel.initAppointmentsViewModel(); - // // myAppointmentsViewModel.getPatientAppointments(true, false); - // showCommonBottomSheetWithoutHeight( - // title: "", - // context, - // child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), - // callBackFunc: () { - // Navigator.pushAndRemoveUntil( - // context, - // CustomPageRoute( - // page: LandingNavigation(), - // ), - // (r) => false); - // }, - // isFullScreen: false, - // isCloseButtonVisible: false, - // isAutoDismiss: true - // ); - // }); - // }, - // backgroundColor: AppColors.successColor, - // borderColor: AppColors.successColor, - // textColor: Colors.white, - // fontSize: 14.f, - // isBold: true, - // borderRadius: 12.r, - // height: 40.h, - // icon: AppAssets.confirm_appointment_icon, - // iconColor: Colors.white, - // iconSize: 16.h, - // ) - // : SizedBox.shrink()) ], ), //TODO Add countdown timer in case of LiveCare Appointment @@ -748,148 +646,6 @@ class _AppointmentDetailsPageState extends State { ); }), SizedBox(height: 16.h), - // Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.whiteColor, - // borderRadius: 20.r, - // hasShadow: false, - // ), - // child: Row( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h, applyThemeColor: false), - // SizedBox(width: 8.h), - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - // LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), - // ], - // ), - // const Spacer(), - // BellAnimatedSwitch( - // key: _bellSwitchKey, - // initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false, - // activeColor: AppColors.successColor.withOpacity(0.2), - // inactiveColor: AppColors.lightGrayBGColor, - // activeCircleColor: AppColors.successColor, - // inactiveCircleColor: AppColors.greyTextColor, - // activeIconColor: AppColors.bgGreenColor, - // inactiveIconColor: AppColors.greyTextColor, - // activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h), - // inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h), - // onChanged: (newValue) async { - // CalenderUtilsNew calender = CalenderUtilsNew.instance; - // bool isEventAddedOrRemoved = false; - // if (newValue == true) { - // DateTime startDate = DateTime.now(); - // DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); - // - // // Show reminder bottom sheet and check if permission was granted - // bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet( - // context, - // endDate, - // widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", - // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", - // "", - // "", - // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - // description: - // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - // onSuccess: () { - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - // }); - // }, - // isMultiAllowed: true, - // onMultiDateSuccess: (int selectedIndex) async { - // isEventAddedOrRemoved = await calender.createOrUpdateEvent( - // title: - // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // description: - // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - // scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), - // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // location: '', - // reminderMinutes: selectedIndex); - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // }); - // }, - // ); - // - // // If permission was not granted, revert the switch back to OFF - // if (!permissionGranted) { - // _bellSwitchKey.currentState?.setSwitchValue(false); - // } - // } else { - // isEventAddedOrRemoved = await calender.checkAndRemove( - // id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // ); - // setState(() { - // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // }); - // } - // }, - // ) - // - // // Switch( - // // activeThumbColor: AppColors.successColor, - // // // activeTrackColor: AppColors.successColor.withValues(alpha: .15), - // // value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - // // onChanged: (newValue) async { - // // CalenderUtilsNew calender = CalenderUtilsNew.instance; - // // bool isEventAddedOrRemoved = false; - // // if(newValue == true){ - // // DateTime startDate = DateTime.now(); - // // DateTime endDate = DateUtil.convertStringToDate(widget - // // .patientAppointmentHistoryResponseModel.appointmentDate); - // // BottomSheetUtils().showReminderBottomSheet( - // // context, - // // endDate, - // // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", - // // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", - // // "", - // // "", - // // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - // // description: - // // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - // // onSuccess: () { - // // setState(() { - // // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - // // }); - // // }, - // // isMultiAllowed: true, - // // onMultiDateSuccess: (int selectedIndex) async { - // // isEventAddedOrRemoved = await calender.createOrUpdateEvent( - // // title: - // // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // // description: - // // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - // // scheduleDateTime: DateUtil.convertStringToDate(widget - // // .patientAppointmentHistoryResponseModel.appointmentDate), - // // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - // // location: '', - // // reminderMinutes: selectedIndex - // // ); - // // setState(() { - // // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // // }); - // // }, - // // ); - // // }else { - // // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - // // setState(() { - // // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - // // }); - // // } - // // - // // - // // }, - // // ), - // ], - // ).paddingSymmetrical(16.h, 16.h), - // ), SizedBox(height: 16.h), ], ) @@ -900,7 +656,7 @@ class _AppointmentDetailsPageState extends State { crossAxisCount: 3, crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 115.h, + childAspectRatio: isFoldable ? 1.2 : (isTablet ? 1.1 : 0.78), ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, @@ -1093,213 +849,6 @@ class _AppointmentDetailsPageState extends State { ], ); }), - - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // "Lab & Radiology".needTranslation.toText18(isBold: true), - // 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"], - // // labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, - // labOrderTests: [], - // // isLoading: labViewModel.isLabOrdersLoading, - // isLoading: false, - // ).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: LabOrdersPage(), - // ), - // ); - // }), - // ), - // 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"], - // labOrderTests: [], - // isLoading: false, - // ).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: RadiologyOrdersPage(), - // ), - // ); - // }), - // ), - // ], - // ), - // SizedBox(height: 16.h), - // LocaleKeys.prescriptions.tr(context: context).toText18(isBold: true), - // SizedBox(height: 16.h), - // Consumer(builder: (context, prescriptionVM, child) { - // return prescriptionVM.isPrescriptionsDetailsLoading - // ? const MoviesShimmerWidget() - // : Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: Colors.white, - // borderRadius: 20.r, - // ), - // padding: EdgeInsets.all(16.w), - // child: Column( - // children: [ - // // ListView.separated( - // // itemCount: prescriptionVM.prescriptionDetailsList.length, - // // shrinkWrap: true, - // // padding: EdgeInsets.only(right: 8.w), - // // 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: [ - // // prescriptionVM.prescriptionDetailsList[index].itemDescription! - // // .toText12(isBold: true, maxLine: 1), - // // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - // // .needTranslation - // // .toText10( - // // weight: FontWeight.w600, - // // color: AppColors.greyTextColor, - // // letterSpacing: -0.4), - // // ], - // // ), - // // SizedBox(width: 68.w), - // // Transform.flip( - // // flipX: appState.isArabic(), - // // child: Utils.buildSvgWithAssets( - // // icon: AppAssets.forward_arrow_icon, - // // iconColor: AppColors.blackColor, - // // width: 18.w, - // // height: 13.h, - // // fit: BoxFit.contain, - // // ), - // // ), - // // ], - // // ), - // // ], - // // ), - // // ), - // // ), - // // ); - // // }, - // // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - // // ).onPress(() { - // // prescriptionVM.setPrescriptionsDetailsLoading(); - // // Navigator.of(context).push( - // // CustomPageRoute( - // // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - // // ), - // // ); - // // }), - // SizedBox(height: 16.h), - // const Divider(color: AppColors.dividerColor), - // SizedBox(height: 16.h), - // // Wrap( - // // runSpacing: 6.w, - // // children: [ - // // // Expanded( - // // // child: CustomButton( - // // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - // // // onPressed: () {}, - // // // backgroundColor: AppColors.secondaryLightRedColor, - // // // borderColor: AppColors.secondaryLightRedColor, - // // // textColor: AppColors.primaryRedColor, - // // // fontSize: 14, - // // // isBold: true, - // // // borderRadius: 12.h, - // // // height: 40.h, - // // // icon: AppAssets.appointment_calendar_icon, - // // // iconColor: AppColors.primaryRedColor, - // // // iconSize: 16.h, - // // // ), - // // // ), - // // // SizedBox(width: 16.h), - // // Expanded( - // // child: CustomButton( - // // text: "Refill & Delivery".needTranslation, - // // onPressed: () { - // // Navigator.of(context) - // // .push( - // // CustomPageRoute( - // // page: PrescriptionsListPage(), - // // ), - // // ) - // // .then((val) { - // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // // }); - // // }, - // // backgroundColor: AppColors.secondaryLightRedColor, - // // borderColor: AppColors.secondaryLightRedColor, - // // textColor: AppColors.primaryRedColor, - // // fontSize: 14.f, - // // isBold: true, - // // borderRadius: 12.r, - // // height: 40.h, - // // icon: AppAssets.requests, - // // iconColor: AppColors.primaryRedColor, - // // iconSize: 16.h, - // // ), - // // ), - // // - // // SizedBox(width: 16.w), - // // Expanded( - // // child: CustomButton( - // // text: "All Prescriptions".needTranslation, - // // onPressed: () { - // // Navigator.of(context) - // // .push( - // // CustomPageRoute( - // // page: PrescriptionsListPage(), - // // ), - // // ) - // // .then((val) { - // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // // }); - // // }, - // // backgroundColor: AppColors.secondaryLightRedColor, - // // borderColor: AppColors.secondaryLightRedColor, - // // textColor: AppColors.primaryRedColor, - // // fontSize: 14.f, - // // isBold: true, - // // borderRadius: 12.r, - // // height: 40.h, - // // icon: AppAssets.requests, - // // iconColor: AppColors.primaryRedColor, - // // iconSize: 16.h, - // // ), - // // ), - // // ], - // // ), - // ], - // ), - // ); - // }), - // ], - // ), ], ).paddingAll(24.w), ), diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 05358243..951583d6 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -17,8 +19,6 @@ 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/common_bottom_sheet.dart'; -import 'dart:ui' as ui; - import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -67,8 +67,8 @@ class AppointmentDoctorCard extends StatelessWidget { Transform.translate( offset: Offset(0.0, -20.h), child: Container( - width: 40.w, - height: 40.h, + width: 50.h, + height: 50.h, decoration: BoxDecoration( color: AppColors.whiteColor, shape: BoxShape.circle, // Makes the container circular @@ -80,9 +80,10 @@ class AppointmentDoctorCard extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.h, height: 15.h, iconColor: AppColors.ratingColorYellow), SizedBox(height: 2.h), - "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}" + .toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ], ), ).circle(100), @@ -97,9 +98,14 @@ class AppointmentDoctorCard extends StatelessWidget { children: [ Row( children: [ - patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")), + patientAppointmentHistoryResponseModel.doctorNameObj!.toText16( + isBold: true, + isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), + textOverflow: TextOverflow.ellipsis, + ), SizedBox(width: 12.w), - (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) + (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && + patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) ? Image.network( patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, @@ -130,19 +136,25 @@ class AppointmentDoctorCard extends StatelessWidget { child: AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), icon: AppAssets.doctor_calendar_icon, - richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( + richText: + "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false, )}" - .toText10(isBold: true), + .toText10(isBold: true), ), ), AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), - icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, + icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? AppAssets.walkin_appointment_icon + : AppAssets.small_livecare_icon, iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, - labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), - backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, + labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? LocaleKeys.livecare.tr(context: context) + : LocaleKeys.walkin.tr(context: context), + backgroundColor: + !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, ), ], @@ -150,10 +162,12 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - - patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false + patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false || + patientAppointmentHistoryResponseModel.isActiveDoctor! == false ? SizedBox.shrink() - : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { + : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) + .onPress(() async { DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; @@ -197,8 +211,7 @@ class AppointmentDoctorCard extends StatelessWidget { AppointmentType.isArrived(patientAppointmentHistoryResponseModel), ), ), - if (timerWidget != null) - timerWidget ?? SizedBox() + if (timerWidget != null) timerWidget ?? SizedBox() ], ), ), diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 9e83a70d..0b9d9029 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -92,7 +92,7 @@ class _AppointmentCalendarState extends State { // ], // ), SizedBox( - height: 350.h, + height: MediaQuery.of(context).size.height * 0.45, child: Directionality( textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, child: Localizations.override( @@ -102,7 +102,7 @@ class _AppointmentCalendarState extends State { controller: _calendarController, minDate: DateTime.now(), showNavigationArrow: true, - headerHeight: 60.h, + // headerHeight: 60.h, headerStyle: CalendarHeaderStyle( backgroundColor: AppColors.transparent, textAlign: isArabic ? TextAlign.end : TextAlign.start, @@ -157,8 +157,11 @@ class _AppointmentCalendarState extends State { ), //TODO: Add Next Day Span here dayEvents.isNotEmpty - ? SizedBox( - height: 100.h, + ? ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.15, + minHeight: 0, + ), child: Directionality( textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, child: SingleChildScrollView( @@ -169,7 +172,7 @@ class _AppointmentCalendarState extends State { spacing: 6.h, runSpacing: 6.h, children: List.generate( - dayEvents.length, // Generate a large number of items to ensure scrolling + dayEvents.length, (index) => TimeSlotChip( label: dayEvents[index].isoTime!, isSelected: index == selectedButtonIndex, @@ -204,7 +207,8 @@ class _AppointmentCalendarState extends State { ), ); } else { - bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); + bookAppointmentsViewModel.getAppointmentNearestGate( + projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); Navigator.of(context).pop(); Navigator.of(context).push( @@ -221,7 +225,8 @@ class _AppointmentCalendarState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.errorAnimation, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), (LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor), SizedBox(height: 16.h), @@ -320,7 +325,12 @@ class _AppointmentCalendarState extends State { selectedButtonIndex = 0; List> timeList = []; for (var i = 0; i < dayEvents.length; i++) { - Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; + Map timeSlot = { + "isoTime": dayEvents[i].isoTime, + "start": dayEvents[i].start.toString(), + "end": dayEvents[i].end.toString(), + "vidaDate": dayEvents[i].vidaDate + }; timeList.add(timeSlot); } if (dayEvents.isNotEmpty) { @@ -333,9 +343,7 @@ class _AppointmentCalendarState extends State { final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); final isArabic = appState.isArabic(); setState(() { - selectedDateDisplay = isArabic - ? DateUtil.getMonthDayYearDateFormattedAr(day) - : DateUtil.getMonthDayYearDateFormatted(day); + selectedDateDisplay = isArabic ? DateUtil.getMonthDayYearDateFormattedAr(day) : DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), isArabic ? "ar" : "en"); _calendarController.selectedDate = day; openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots); diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 4ea290b0..d75c83e7 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -1,12 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -14,10 +12,8 @@ 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/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; -import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; @@ -29,13 +25,11 @@ import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_se 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/hmg_services/services_view.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/service_info_page.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; -import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart'; -import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; -import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -49,8 +43,6 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../core/dependencies.dart' show getIt; -import '../../features/qr_parking/qr_parking_view_model.dart'; -import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; class ServicesPage extends StatefulWidget { bool showBackIcon; @@ -69,7 +61,14 @@ class _ServicesPageState extends State { late WeatherMonitorViewModel weatherVM; late final List hmgServices = [ - HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { + HmgServicesComponentModel( + 11, + LocaleKeys.emergencyServices.tr(), + "", + AppAssets.emergency_services_icon, + bgColor: AppColors.primaryRedColor, + true, + route: null, onTap: () async { // if (getIt.get().isAuthenticated) { // getIt.get().flushData(); // getIt.get().getTransportationOrders( @@ -88,11 +87,19 @@ class _ServicesPageState extends State { // await getIt.get().onLoginPressed(); // } }), - HmgServicesComponentModel(11, LocaleKeys.bookAppointmentService.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { + HmgServicesComponentModel( + 11, + LocaleKeys.bookAppointmentService.tr(), + "", + AppAssets.appointment_calendar_icon, + bgColor: AppColors.bookAppointment, + true, + route: null, onTap: () { getIt.get().onTabChanged(0); Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); }), - HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + HmgServicesComponentModel( + 5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); } else { @@ -152,7 +159,8 @@ class _ServicesPageState extends State { // ); // }, // ), - HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + HmgServicesComponentModel( + 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.eReferralPage); } else { @@ -403,14 +411,13 @@ class _ServicesPageState extends State { }, ), HmgServicesComponentModel( - 103, - LocaleKeys.watchUsOnYoutube.tr(), - "", - AppAssets.youtube, - bgColor: AppColors.whiteColor, - true, - onTap:()=> launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals")) - ), + 103, + LocaleKeys.watchUsOnYoutube.tr(), + "", + AppAssets.youtube, + bgColor: AppColors.whiteColor, + true, + onTap: () => launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))), HmgServicesComponentModel( 104, LocaleKeys.connectOnLinkedin.tr(), @@ -418,9 +425,8 @@ class _ServicesPageState extends State { AppAssets.linkedin, bgColor: AppColors.whiteColor, true, - onTap:()=> launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group")), + onTap: () => launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group")), ), - ]; @override @@ -431,8 +437,6 @@ class _ServicesPageState extends State { weatherVM.initiateFetchWeather(); } - - @override Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); @@ -451,456 +455,393 @@ class _ServicesPageState extends State { children: [ const WeatherWidget(), SizedBox(height: 16.h), - LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 21.w, - mainAxisSpacing: 18.h, - childAspectRatio: 80 / 94), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: hmgServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false); - }, - ).paddingSymmetrical(24.w, 0), - SizedBox(height: 24.h), - LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - SizedBox( - height: 350.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getServiceCardsList.length, + LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row + ), + physics: NeverScrollableScrollPhysics(), shrinkWrap: true, - padding: EdgeInsets.symmetric(horizontal: 24.w), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: LargeServiceCard( - serviceCardData: LandingPageData.getServiceCardsList[index], - image: LandingPageData.getServiceCardsList[index].icon, - title: LandingPageData.getServiceCardsList[index].title, - subtitle: LandingPageData.getServiceCardsList[index].subtitle, - icon: LandingPageData.getServiceCardsList[index].largeCardIcon, - isPNG: LandingPageData.getServiceCardsList[index].isPNG, + itemCount: hmgServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false); + }, + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + ConstrainedBox( + constraints: BoxConstraints( + minHeight: 320.h, + maxHeight: isFoldable ? 400.h : (isTablet ? 360.h : 340.h), + ), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: LargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + isPNG: LandingPageData.getServiceCardsList[index].isPNG, + ), ), ), - ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), + ), + ), + SizedBox(height: 24.h), + getIt.get().isAuthenticated + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + height: 183.h, + width: 183.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2( + num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), + isExpanded: false, + letterSpacing: -1) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) + : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 24.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: LocaleKeys.recharge.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + } else { + await getIt.get().onLoginPressed(); + } + }), + ), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + height: 183.h, + width: 183.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.familyTitle.tr().toText16(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Wrap( + spacing: -12.h, + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 32.h, + width: 32.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.maleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], + ) + : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.add_icon, + iconSize: 24.h, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: getIt.get().isArabic() ? LocaleKeys.add.tr() : LocaleKeys.addMember.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + // Navigator.of(context).push( + // CustomPageRoute( + // page: MedicalFilePage(), + // ), + // ); + Navigator.of(context).push( + CustomPageRoute( + direction: AxisDirection.down, + page: FamilyMedicalScreen(), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } + }), + ), + ), + ], + ).paddingSymmetrical(24.w, 0), + ], + ) + : SizedBox(), + SizedBox(height: 24.h), + LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row + mainAxisSpacing: 18.h, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgHealthToolServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem( + hmgHealthToolServices[index], + index, + false, + isHealthToolIcon: true, ); }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), - ), - ), - SizedBox(height: 24.h), - getIt.get().isAuthenticated - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, + ).paddingSymmetrical(24.w, 0), + SizedBox(height: 24.h), + LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( children: [ - LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - height: 183.h, - width: 183.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], - ), - Spacer(), - getIt.get().isAuthenticated - ? Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), - isExpanded: false, letterSpacing: -1) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }) - : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 24.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); - } else { - await getIt.get().onLoginPressed(); - } - }), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.latest_news_icon, + width: 32.h, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + LocaleKeys.latestNews.tr().toText14(isBold: true) + ], ), ), - SizedBox(width: 16.w), - Expanded( - child: Container( - height: 183.h, - width: 183.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.familyTitle.tr().toText16(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], - ), - Spacer(), - getIt.get().isAuthenticated - ? Wrap( - spacing: -12.h, - // runSpacing: 0.h, - children: [ - Utils.buildImgWithAssets( - icon: AppAssets.babyGirlImg, - height: 32.h, - width: 32.w, - border: 1, - fit: BoxFit.contain, - borderRadius: 50.r, - ), - Utils.buildImgWithAssets( - icon: AppAssets.femaleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - Utils.buildImgWithAssets( - icon: AppAssets.maleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - ], - ) - : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.add_icon, - iconSize: 24.h, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - text: getIt.get().isArabic() ? LocaleKeys.add.tr() :LocaleKeys.addMember.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalFileViewModel.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), - onVerificationPress: () { - medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - // Navigator.of(context).push( - // CustomPageRoute( - // page: MedicalFilePage(), - // ), - // ); - Navigator.of(context).push( - CustomPageRoute( - direction: AxisDirection.down, - page: FamilyMedicalScreen(), - ), - ); - } else { - await getIt.get().onLoginPressed(); - } - }), + ).onPress(() { + Utils.openWebView( + url: 'https://x.com/HMG', + ); + }), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.hmg_contact_icon, + width: 32.h, + height: 32.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.w), + Expanded(child: LocaleKeys.hmgContact.tr().toText14(isBold: true)) + ], ), ), - ], - ).paddingSymmetrical(24.w, 0), + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + ) ], - ) - : SizedBox(), - SizedBox(height: 24.h), - LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 21.w, - mainAxisSpacing: 18.h, - childAspectRatio: 80.w / 94.h, - ), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: hmgHealthToolServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return ServiceGridViewItem( - hmgHealthToolServices[index], - index, - false, - isHealthToolIcon: true, - ); - }, - ).paddingSymmetrical(24.w, 0), - SizedBox(height: 24.h), - LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Row( - // children: [ - // // Expanded( - // // child: Container( - // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // // color: AppColors.whiteColor, - // // borderRadius: 12.h, - // // hasShadow: false, - // // ), - // // child: Padding( - // // padding: EdgeInsets.all(16.h), - // // child: Row( - // // children: [ - // // Utils.buildSvgWithAssets( - // // icon: AppAssets.virtual_tour_icon, - // // width: 32.w, - // // height: 32.h, - // // fit: BoxFit.contain, - // // ), - // // SizedBox(width: 8.w), - // // LocaleKeys.virtualTour.tr().toText14(isBold: true) - // // ], - // // ), - // // ), - // // ).onPress(() { - // // Utils.openWebView( - // // url: 'https://hmgwebservices.com/vt_mobile/html/index.html', - // // ); - // // }), - // // ), - // SizedBox(width: 16.w), - // Expanded( - // child: Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.whiteColor, - // borderRadius: 12.h, - // hasShadow: false, - // ), - // child: Padding( - // padding: EdgeInsets.all(16.h), - // child: Row( - // children: [ - // Utils.buildSvgWithAssets( - // icon: AppAssets.car_parking_icon, - // width: 32.w, - // height: 32.h, - // fit: BoxFit.contain, - // ), - // SizedBox(width: 8.w), - // LocaleKeys.carParking.tr().toText14(isBold: true) - // ], - // ).onPress(() { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (_) => ChangeNotifierProvider( - // create: (_) => getIt(), - // child: const ParkingPage(), - // ), - // ), - // ); - // }), - // ), - // ), - // ), - // ], - // ), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.latest_news_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - LocaleKeys.latestNews.tr().toText14(isBold: true) - ], + ), + SizedBox(height: 24.h), + LocaleKeys.hmgPolicies.tr().toText18(weight: FontWeight.bold), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://x.com/HMG', - ); - }), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.hmg_contact_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - Expanded(child: LocaleKeys.hmgContact.tr().toText14(isBold: true)) - ], + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), + SizedBox(width: 8.w), + Expanded(child: LocaleKeys.termsConditoins.tr().toText14(isBold: true)) + ], + ), ), - ), - ).onPress(() { - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, - ); - }), - ) - ], - ), - SizedBox(height: 24.h), - LocaleKeys.hmgPolicies.tr().toText18(weight: FontWeight.bold), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), - SizedBox(width: 8.w), - Expanded(child: LocaleKeys.termsConditoins.tr().toText14(isBold: true)) - ], + ).onPress(() { + Utils.openWebView( + url: 'https://hmg.com/en/Pages/Terms.aspx', + ); + }), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://hmg.com/en/Pages/Terms.aspx', - ); - }), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), - SizedBox(width: 8.w), - Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(isBold: true)) - ], + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), + SizedBox(width: 8.w), + Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(isBold: true)) + ], + ), ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://hmg.com/en/Pages/Privacy.aspx', - ); - }), - ) - ], - ) - ], - ).paddingSymmetrical(24.w, 0), + ).onPress(() { + Utils.openWebView( + url: 'https://hmg.com/en/Pages/Privacy.aspx', + ); + }), + ) + ], + ) + ], + ).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row - crossAxisSpacing: 21.w, + crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row mainAxisSpacing: 18.h, - childAspectRatio: 80.w / 94.h, ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -915,11 +856,11 @@ class _ServicesPageState extends State { ); }, ).paddingSymmetrical(24.w, 0), - SizedBox(height: 24.h), - ], + SizedBox(height: 24.h), + ], + ), ), ), - ), ); } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 706041ec..b8313323 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -10,7 +11,6 @@ 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/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/core/enums.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'; @@ -25,8 +25,6 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.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/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_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'; @@ -40,44 +38,35 @@ import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; -import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; -import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/notifications/notifications_list_page.dart'; -import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; -import 'package:hmg_patient_app_new/services/zoom_service.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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/countdown_timer.dart'; -import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; +import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; -import 'dart:ui' as ui; - class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -159,11 +148,14 @@ class _LandingPageState extends State { // Commented as per new requirement to remove rating popup from the app - if(!appState.isRatedVisible) { - appointmentRatingViewModel.getLastRatingAppointment(onSuccess: (response) { - if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - appointmentRatingViewModel.getAppointmentDetails(appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, appointmentRatingViewModel.appointmentRatedList.last.projectID!, - onSuccess: ((response) { + if (!appState.isRatedVisible) { + appointmentRatingViewModel.getLastRatingAppointment( + onSuccess: (response) { + if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + appointmentRatingViewModel.getAppointmentDetails( + appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, + appointmentRatingViewModel.appointmentRatedList.last.projectID!, + onSuccess: ((response) { appointmentRatingViewModel.setClinicOrDoctor(false); appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); @@ -222,15 +214,16 @@ class _LandingPageState extends State { controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( - top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + top: (appState.isAuthenticated && + !insuranceVM.isInsuranceLoading && + insuranceVM.isInsuranceExpired && + insuranceVM.isInsuranceExpiryBannerShown) ? (MediaQuery.paddingOf(context).top + 70.h) : kToolbarHeight + 0.h, bottom: 24), child: Column( spacing: 16.h, children: [ - - Row( spacing: 8.h, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -241,42 +234,40 @@ class _LandingPageState extends State { // ...existing navigation code... Navigator.of(context).push( - CustomPageRoute( - direction: AxisDirection.down, - page: FamilyMedicalScreen(), - ), - ); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageWidget: UserAvatarWidget( - width: 42.w, - height: 42.h, - fit: BoxFit.cover, - isCircular: true, - ), - ).expanded + CustomPageRoute( + direction: AxisDirection.down, + page: FamilyMedicalScreen(), + ), + ); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageWidget: UserAvatarWidget( + width: 42.w, + height: 42.h, + fit: BoxFit.cover, + isCircular: true, + ), + ).expanded : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); - // ...existing commented code... - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + // ...existing commented code... + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, fontWeight: FontWeight.w700, borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), - height: 40.h, - ), + padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), + height: 40.h, + ), Consumer(builder: (context, todoSectionVM, child) { return Row( mainAxisSize: MainAxisSize.min, // spacing: 18.h, children: [ - - Stack(clipBehavior: Clip.none, children: [ if (appState.isAuthenticated) Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async { @@ -295,33 +286,35 @@ class _LandingPageState extends State { }), (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) ? Positioned( - // right: appState.isArabic() ? 8.w : -8.w, - top: -8.h, - // left: 4.h, - // bottom: 4.h, - child: Container( - width: 18.w, - height: 18.h, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColors.primaryRedColor, - borderRadius: BorderRadius.circular(20.r), - ), - child: Text( - todoSectionVM.notificationsCount.toString(), - style: TextStyle( - color: Colors.white, - fontFamily: "Poppins", - fontSize: 10.f, - fontWeight: FontWeight.w600, - ), - textAlign: TextAlign.center, - ), - ), - ) + // right: appState.isArabic() ? 8.w : -8.w, + top: -8.h, + // left: 4.h, + // bottom: 4.h, + child: Container( + width: 18.w, + height: 18.h, + padding: EdgeInsets.all(2), + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + todoSectionVM.notificationsCount.toString(), + style: TextStyle( + color: Colors.white, + fontFamily: "Poppins", + fontSize: 10.f, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + ), + ) : SizedBox.shrink(), ]), - SizedBox(width: 24.w,), + SizedBox( + width: 24.w, + ), Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { // openIndoorNavigationBottomSheet(context); showCommonBottomSheetWithoutHeight( @@ -343,9 +336,16 @@ class _LandingPageState extends State { // ); // }), !appState.isAuthenticated - ?Row(children: [ SizedBox(width: 24.w,), Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { - context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); - })]) + ? Row(children: [ + SizedBox( + width: 24.w, + ), + Utils.buildSvgWithAssets( + icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h) + .onPress(() { + context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); + }) + ]) : SizedBox() ], ); @@ -354,140 +354,141 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h), !appState.isAuthenticated ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - width: 50.w, - height: 60.h, - icon: AppAssets.symptomCheckerIcon, - fit: BoxFit.contain, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), - LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true), - SizedBox(height: 14.h), - CustomButton( - text: LocaleKeys.checkYourSymptoms.tr(context: context), - onPressed: () async { - context.navigateWithName(AppRoutes.userInfoSelection); - }, - padding: EdgeInsetsGeometry.zero, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - height: 40.h, - ), - ], - ).expanded - ], - ), - ), - ).paddingSymmetrical(24.w, 0.h) + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + width: 50.w, + height: 60.h, + icon: AppAssets.symptomCheckerIcon, + fit: BoxFit.contain, + ), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true), + SizedBox(height: 14.h), + CustomButton( + text: LocaleKeys.checkYourSymptoms.tr(context: context), + onPressed: () async { + context.navigateWithName(AppRoutes.userInfoSelection); + }, + padding: EdgeInsetsGeometry.zero, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 40.h, + ), + ], + ).expanded + ], + ), + ), + ).paddingSymmetrical(24.w, 0.h) : SizedBox.shrink(), appState.isAuthenticated ? Column( - children: [ - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - myAppointmentsViewModel.onTabChange(0); - myAppointmentsViewModel.updateListWRTTab(0); - Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); - }), - Consumer3( - builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: true, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 16.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : isTablet - ? SizedBox( - height: isFoldable ? 290.h : 255.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: 3, - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return SizedBox( - height: 255.h, - width: 250.w, - child: getIndexSwiperCard(index), - ); - // return AnimationConfiguration.staggeredList( - // position: index, - // duration: const Duration(milliseconds: 1000), - // child: SlideAnimation( - // horizontalOffset: 100.0, - // child: FadeInAnimation( - // child: SizedBox( - // height: 255.h, - // width: 250.w, - // child: getIndexSwiperCard(index), - // ), - // ), - // ), - // ); - }, - separatorBuilder: (BuildContext cxt, int index) => - SizedBox( - width: 10.w, - ), - ), - ) - : SizedBox( - height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + children: [ + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + myAppointmentsViewModel.onTabChange(0); + myAppointmentsViewModel.updateListWRTTab(0); + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + }), + Consumer3( + builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 16.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : isTablet + ? SizedBox( + height: isFoldable ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 3, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox( + width: 10.w, + ), + ), + ) + : SizedBox( + height: 255.h + + 20 + + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space child: Builder( builder: (context) { final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading @@ -532,159 +533,217 @@ class _LandingPageState extends State { ? _buildLiveCareRequestCard().paddingSymmetrical(24.h, 16.h) : Container( width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - getIt.get().onTabChanged(0); - Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14.f, - fontWeight: FontWeight.w600, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - height: 40.h, + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + getIt.get().onTabChanged(0); + Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w600, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + height: 40.h, + ), + ], ), - ], - ), - ), - ).paddingSymmetrical(24.h, 16.h); + ), + ).paddingSymmetrical(24.h, 16.h); }, ), // Consumer for ER Online Check-In pending request - // Consumer( - // builder: (context, emergencyServicesVM, child) { - // return emergencyServicesVM.patientHasAdvanceERBalance - // ? Column( - // children: [ - // SizedBox(height: 16.h), - // Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.whiteColor, - // borderRadius: 20.r, - // hasShadow: false, - // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), - // ), - // width: double.infinity, - // child: Padding( - // padding: EdgeInsets.all(16.h), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // // Row( - // // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // // children: [ - // // AppCustomChipWidget( - // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), - // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), - // // textColor: AppColors.primaryRedColor, - // // ), - // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), - // // ], - // // ), - // SizedBox(height: 8.h), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), - // Transform.flip( - // flipX: getIt.get().isArabic(), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.forward_arrow_icon_small, - // iconColor: AppColors.blackColor, - // width: 20.h, - // height: 15.h, - // fit: BoxFit.contain, - // ), - // ), - // ], - // ), - // ], - // ), - // ), - // ).paddingSymmetrical(24.h, 0.h).onPress(() { - // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); - // // context.read().navigateToEROnlineCheckIn(); - // }), - // SizedBox(height: 12.h), - // ], - // ) - // : SizedBox(height: 0.h); - // }, - // ), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); - }), - SizedBox(height: 16.h), - Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) { - return Container( - // height: 121.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - todoSectionVM.patientAncillaryOrdersList.isNotEmpty - ? Container( - height: 50.h, - decoration: ShapeDecoration( - color: AppColors.eReferralCardColor.withAlpha(50), - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - smoothness: 1, - ), + // Consumer( + // builder: (context, emergencyServicesVM, child) { + // return emergencyServicesVM.patientHasAdvanceERBalance + // ? Column( + // children: [ + // SizedBox(height: 16.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.r, + // hasShadow: false, + // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + // ), + // width: double.infinity, + // child: Padding( + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // // Row( + // // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // // children: [ + // // AppCustomChipWidget( + // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), + // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + // // textColor: AppColors.primaryRedColor, + // // ), + // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + // // ], + // // ), + // SizedBox(height: 8.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), + // Transform.flip( + // flipX: getIt.get().isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon_small, + // iconColor: AppColors.blackColor, + // width: 20.h, + // height: 15.h, + // fit: BoxFit.contain, + // ), + // ), + // ], + // ), + // ], + // ), + // ), + // ).paddingSymmetrical(24.h, 0.h).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // // context.read().navigateToEROnlineCheckIn(); + // }), + // SizedBox(height: 12.h), + // ], + // ) + // : SizedBox(height: 0.h); + // }, + // ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewMedicalFileLandingPage + .tr(context: context) + .toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); + }), + SizedBox(height: 16.h), + Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( children: [ - LocaleKeys.pendingAncillaryOrders.tr(context: context).toText14(color: AppColors.eReferralCardColor, isBold: true).paddingSymmetrical(24.h, 0.h), - CustomButton( - text: LocaleKeys.view.tr(context: context), - onPressed: () { - todoSectionVM.setIsAncillaryOrdersNeedReloading(true); - Navigator.of(context).push( - CustomPageRoute( - page: ToDoPage(), - ), - ); - }, - backgroundColor: AppColors.eReferralCardColor, - borderColor: AppColors.eReferralCardColor, - textColor: AppColors.whiteColor, - fontSize: 10.f, - fontWeight: FontWeight.w600, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(15, 0, 15, 0), - height: 30.h, - ).paddingSymmetrical(24.h, 0.h), + todoSectionVM.patientAncillaryOrdersList.isNotEmpty + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.eReferralCardColor.withAlpha(50), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.pendingAncillaryOrders + .tr(context: context) + .toText14(color: AppColors.eReferralCardColor, isBold: true) + .paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.view.tr(context: context), + onPressed: () { + todoSectionVM.setIsAncillaryOrdersNeedReloading(true); + Navigator.of(context).push(CustomPageRoute(page: ToDoPage())); + }, + backgroundColor: AppColors.eReferralCardColor, + borderColor: AppColors.eReferralCardColor, + textColor: AppColors.whiteColor, + fontSize: 10.f, + fontWeight: FontWeight.w600, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], + ), + ) + : SizedBox.shrink(), + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( + controller: _horizontalScrollController, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only( + top: 92.h + 32.h, + left: MediaQuery.sizeOf(context).width / 2.5 - 10, + right: MediaQuery.sizeOf(context).width / 2.5 - 10), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData().getLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 200), + 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) => 10.width, + ).paddingSymmetrical(16.h, 0.h), + ), + ), + SizedBox(height: 16.h), ], ), - ) - : SizedBox.shrink(), + ).paddingSymmetrical(24.h, 0.h); + }), + ], + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( + children: [ SizedBox( height: 92.h + 32.h - 4.h, child: RawScrollbar( @@ -696,104 +755,45 @@ class _LandingPageState extends State { trackColor: Color(0xffD9D9D9), trackBorderColor: Colors.transparent, trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery - .sizeOf(context) - .width / 2.5 - 10, right: MediaQuery - .sizeOf(context) - .width / 2.5 - 10), + padding: EdgeInsets.only( + top: 92.h + 32.h, + left: MediaQuery.sizeOf(context).width / 2.5 - 10, + right: MediaQuery.sizeOf(context).width / 2.5 - 10), child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: LandingPageData().getLoggedInServiceCardsList.length, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, shrinkWrap: true, controller: _horizontalScrollController, padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( position: index, - duration: const Duration(milliseconds: 200), - child: SlideAnimation( + 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, + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, + 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) => 10.width, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ).paddingSymmetrical(16.h, 0.h), ), ), SizedBox(height: 16.h), ], ), - ).paddingSymmetrical(24.h, 0.h); - }), - ], - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - SizedBox( - height: 92.h + 32.h - 4.h, - child: RawScrollbar( - controller: _horizontalScrollController, - thumbVisibility: true, - radius: Radius.circular(10.0), - thumbColor: AppColors.primaryRedColor, - trackVisibility: true, - trackColor: Color(0xffD9D9D9), - trackBorderColor: Colors.transparent, - trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery - .sizeOf(context) - .width / 2.5 - 10, right: MediaQuery - .sizeOf(context) - .width / 2.5 - 10), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, - 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, - ).paddingSymmetrical(16.h, 0.h), - ), - ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -809,8 +809,8 @@ class _LandingPageState extends State { }), ], ).paddingSymmetrical(24.w, 0.h), - SizedBox( - height: 431.h, + ConstrainedBox( + constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h), child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, @@ -862,7 +862,10 @@ class _LandingPageState extends State { ), ), ), - (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + (appState.isAuthenticated && + !insuranceVM.isInsuranceLoading && + insuranceVM.isInsuranceExpired && + insuranceVM.isInsuranceExpiryBannerShown) ? Container( height: MediaQuery.paddingOf(context).top + 50.h, decoration: ShapeDecoration( @@ -879,17 +882,24 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive + .tr(context: context) + .toText14(color: AppColors.primaryRedColor, isBold: true) + .paddingSymmetrical(0.h, 0.h), Row( children: [ CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { insuranceVM.setIsInsuranceUpdateDetailsLoading(true); - insuranceVM.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); showCommonBottomSheetWithoutHeight(context, - child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + child: PatientInsuranceCardUpdateCard(), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: false, + isFullScreen: false); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, @@ -1029,7 +1039,8 @@ class _LandingPageState extends State { SizedBox(height: 6.h), _buildServingNowSection(), SizedBox(height: 5.h), - _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "") + .toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), ], ), ), @@ -1058,7 +1069,8 @@ class _LandingPageState extends State { hasShadow: false, ), padding: EdgeInsets.all(6.h), - child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + child: Lottie.asset(AppAnimations.hourGlass, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), ], ); } @@ -1171,7 +1183,6 @@ class _LandingPageState extends State { height: 40.h, iconColor: AppColors.whiteColor, iconSize: 18.h, - ), // _buildLiveCareWaitingTime(), ], @@ -1238,7 +1249,8 @@ class _LandingPageState extends State { hasShadow: false, ), padding: EdgeInsets.all(6.h), - child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + child: Lottie.asset(AppAnimations.hourGlass, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), // Utils.buildSvgWithAssets( // icon: AppAssets.waiting_icon, // width: 24.h, @@ -1286,12 +1298,8 @@ class _LandingPageState extends State { // Appointment Card Wrapper (reusable) Widget _buildAppointmentCardWrapper(appointment) { return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - hasDenseShadow: true - ), + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), child: AppointmentCard( patientAppointmentHistoryResponseModel: appointment, myAppointmentsViewModel: myAppointmentsViewModel, @@ -1447,9 +1455,7 @@ class _DirectionalDotPaginationBuilder extends SwiperPlugin { @override Widget build(BuildContext context, SwiperPluginConfig config) { - final int activeIndex = isRTL - ? (totalCount - 1 - config.activeIndex) - : config.activeIndex; + final int activeIndex = isRTL ? (totalCount - 1 - config.activeIndex) : config.activeIndex; print("the index is $activeIndex"); print("the config.activeIndex is ${config.activeIndex}"); @@ -1471,4 +1477,3 @@ class _DirectionalDotPaginationBuilder extends SwiperPlugin { ); } } - diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index c36f7a90..72b11ef3 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -43,83 +43,87 @@ class LargeServiceCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: 350.h, width: 230.w, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 24.r), - child: Stack( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, children: [ ClipRRect( - borderRadius: BorderRadius.circular(24.r), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.r), + topRight: Radius.circular(24.r), + ), child: Image.asset( serviceCardData.largeCardIcon, fit: BoxFit.cover, + width: double.infinity, + height: isFoldable ? 190.h : (isTablet ? 200.h : 180.h), ), ), - Positioned( - bottom: 0.0, // Positions the child 0 logical pixels from the bottom - left: 0.0, - right: 0.0, - child: Container( - height: 180.h, - padding: EdgeInsets.only(bottom: 16.h, top: 16.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - customBorder: BorderRadius.only( - bottomLeft: Radius.circular(24.r), - bottomRight: Radius.circular(24.r), - ), - ), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - isPNG ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h) : Container( - height: 48.h, - width: 48.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: serviceCardData.backgroundColor!, - borderRadius: 12.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Utils.buildSvgWithAssets( - icon: serviceCardData.icon, - iconColor: serviceCardData.iconColor, - fit: BoxFit.contain, - applyThemeColor: false, + Container( + padding: EdgeInsets.all(16.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + isPNG + ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h) + : Container( + height: 48.h, + width: 48.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: serviceCardData.backgroundColor!, + borderRadius: 12.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, + iconColor: serviceCardData.iconColor, + fit: BoxFit.contain, + applyThemeColor: false, + ), + ), ), - ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + serviceCardData.title.tr(context: context).toText14( + isBold: true, + color: AppColors.textColor, + maxlines: 1, + textOverflow: TextOverflow.ellipsis, + ), + serviceCardData.subtitle.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight, maxLine: 2), + ], ), - SizedBox(width: 12.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), - serviceCardData.subtitle.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight, maxLine: 2), - ], - ), - ), - ], - ).paddingSymmetrical(8.w, 0.h).expanded, - CustomButton( - text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), - onPressed: () { - handleOnTap(); - }, - padding: EdgeInsets.zero, - backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor, - borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor, - textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 10.r, - height: 40.h, - ).paddingSymmetrical(16.w, 0.h), - ], - ), + ), + ], + ), + SizedBox(height: 24.h), + CustomButton( + text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), + onPressed: () { + handleOnTap(); + }, + padding: EdgeInsets.zero, + backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor, + borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor, + textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 40.h, + ), + ], ), ), ], @@ -229,27 +233,25 @@ class FadedLargeServiceCard extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - isPNG ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h) : Container( - height: 32.h, - width: 32.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: serviceCardData.backgroundColor!, - borderRadius: 30.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(8.h), - child: Transform.flip( - flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets( - icon: serviceCardData.icon, - iconColor: serviceCardData.iconColor, - fit: BoxFit.contain, - applyThemeColor: false + isPNG + ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h) + : Container( + height: 32.h, + width: 32.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: serviceCardData.backgroundColor!, + borderRadius: 30.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, iconColor: serviceCardData.iconColor, fit: BoxFit.contain, applyThemeColor: false), + ), + ), ), - ), - ), - ), SizedBox(width: 12.w), serviceCardData.title.tr(context: context).toText18(isBold: true, color: AppColors.textColor).expanded, ], diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 86807150..008a3849 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -7,17 +8,13 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_config.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_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/active_prescriptions/models/active_prescriptions_response_model.dart'; -import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; @@ -26,7 +23,6 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; -import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart'; @@ -37,16 +33,13 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/active_medication/active_medication_page.dart'; import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; -import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; -import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_approvals_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; @@ -56,14 +49,11 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.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/health_tracker_menu_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tools_card.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/presentation/medical_file/widgets/medical_report_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; -import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; -import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; @@ -71,7 +61,6 @@ import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -79,20 +68,16 @@ 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/expandable_list_widget.dart'; -import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; import '../prescriptions/prescription_detail_page.dart'; import 'widgets/medical_file_appointment_card.dart'; -import 'dart:ui' as ui; - class MedicalFilePage extends StatefulWidget { bool showBackIcon; @@ -119,10 +104,6 @@ class _MedicalFilePageState extends State { int currentIndex = 0; - // Used to make the PageView height follow the card's intrinsic height - final GlobalKey _vitalSignMeasureKey = GlobalKey(); - double? _vitalSignMeasuredHeight; - @override void initState() { appState = getIt.get(); @@ -143,22 +124,6 @@ class _MedicalFilePageState extends State { super.initState(); } - void _scheduleVitalSignMeasure() { - WidgetsBinding.instance.addPostFrameCallback((_) { - final ctx = _vitalSignMeasureKey.currentContext; - if (ctx == null) return; - final box = ctx.findRenderObject(); - if (box is RenderBox) { - final h = box.size.height; - if (h > 0 && h != _vitalSignMeasuredHeight) { - setState(() { - _vitalSignMeasuredHeight = h; - }); - } - } - }); - } - @override Widget build(BuildContext context) { labViewModel = Provider.of(context, listen: false); @@ -251,7 +216,7 @@ class _MedicalFilePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ UserAvatarWidget( - width: 56.w, + width: 56.h, height: 56.h, fit: BoxFit.cover, isCircular: true, @@ -273,7 +238,8 @@ class _MedicalFilePageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}" + .toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -297,7 +263,9 @@ class _MedicalFilePageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}, context: context), + labelText: LocaleKeys.ageYearsOld.tr( + namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}, + context: context), labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), ), AppCustomChipWidget( @@ -340,9 +308,14 @@ class _MedicalFilePageState extends State { onChipTap: () { if (!insuranceVM.isInsuranceActive) { insuranceVM.setIsInsuranceUpdateDetailsLoading(true); - insuranceVM.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: false, + isFullScreen: false); // showCommonBottomSheetWithoutHeight( // title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), // navigationService.navigatorKey.currentContext!, @@ -438,17 +411,16 @@ class _MedicalFilePageState extends State { ); } - // The cards define their own height; measure the first rendered page once - _scheduleVitalSignMeasure(); - final double hostHeight = _vitalSignMeasuredHeight ?? (135.h); - - return SizedBox( - height: hostHeight, + // Responsive PageView with dynamic height constraint + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: 135.h, + maxHeight: isFoldable ? 160.h : (isTablet ? 165.h : 135.h), + ), child: PageView( controller: hmgServicesVM.vitalSignPageController, onPageChanged: (index) { hmgServicesVM.setVitalSignCurrentPage(index); - _scheduleVitalSignMeasure(); }, children: _buildVitalSignPages( vitalSign: hmgServicesVM.vitalSignList.first, @@ -459,7 +431,6 @@ class _MedicalFilePageState extends State { ), ); }, - measureKey: _vitalSignMeasureKey, currentPageIndex: hmgServicesVM.vitalSignCurrentPage, ), ), @@ -517,7 +488,9 @@ class _MedicalFilePageState extends State { getSelectedTabData(0), ], ), - ExpandableListItem(title: LocaleKeys.medicalReports.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, + ExpandableListItem( + title: LocaleKeys.medicalReports.tr(context: context).toText18(isBold: true), + expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), getSelectedTabData(2), @@ -588,871 +561,848 @@ class _MedicalFilePageState extends State { }); } - Widget getSelectedTabData(int index) { - switch (index) { - case 0: - //General Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + Widget buildInsuranceTab(int index) { + return Column( + children: [ + Consumer(builder: (context, insuranceVM, child) { + return insuranceVM.isInsuranceLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ).paddingSymmetrical(0.w, 0.0) + : insuranceVM.patientInsuranceList.isNotEmpty + ? PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter( + DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), + ), + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bgRedLightColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, + callToActionButton: CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () { + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: false, + isFullScreen: false); + }, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), + height: isFoldable ? 50.h : 40.h, + ).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h), + ), + ).paddingSymmetrical(0.w, 0.h); + }), + SizedBox(height: 10.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.h, + mainAxisSpacing: 16.w, + // mainAxisExtent: 120.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 12.h), + shrinkWrap: true, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ).paddingSymmetrical(0.w, 0.h).onPress(() { + MedicalFileCard( + label: LocaleKeys.updateInsuranceInfo.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.update_insurance_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: InsuranceHomePage())); + }), + MedicalFileCard( + label: "${LocaleKeys.approvals1.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.insurance_approval_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { Navigator.of(context).push( CustomPageRoute( - page: MyAppointmentsPage(), + page: InsuranceApprovalsPage(), ), ); }), - SizedBox(height: 16.h), - Consumer(builder: (context, myAppointmentsVM, child) { - // Provide an explicit height so the horizontal ListView has a bounded height - return SizedBox( - height: 192.h, - child: myAppointmentsVM.isMyAppointmentsLoading - ? MedicalFileAppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsVM, - onRescheduleTap: () {}, - onAskDoctorTap: () {}, - ) - : myAppointmentsVM.patientAppointmentsHistoryList.isEmpty - ? Container( - padding: EdgeInsets.all(12.w), - width: MediaQuery.of(context).size.width, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - getIt.get().onTabChanged(0); - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: isFoldable || isTablet ? 50.h : 40.h, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ), - ], - ), - ) - : ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length, - padding: EdgeInsets.zero, - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: MedicalFileAppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - onRescheduleTap: () { - openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); - }, - onAskDoctorTap: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); - await myAppointmentsViewModel.isDoctorAvailable( - projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, - doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, - clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID, - onSuccess: (value) async { - if (value) { - await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.askDoctor.tr(context: context), - child: AskDoctorRequestTypeSelect( - askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - LoaderBottomSheet.hideLoader(); - } - }, - onError: (_) { - LoaderBottomSheet.hideLoader(); - }, - ); - }, - ), - ), - ), - )); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.w), - ), + MedicalFileCard( + label: LocaleKeys.myInvoicesList.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.invoices_list_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesList(), + ), ); }), - SizedBox(height: 10.h), - LocaleKeys.labAndRadiology.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - 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"], - labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, - isLoading: labViewModel.isLabOrdersLoading, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: LabOrdersPage(), - ), - ); - }), - ), - SizedBox(width: 8.w), - Expanded( - child: LabRadCard( - icon: AppAssets.radiology_icon, - labelText: LocaleKeys.radiologyLabResults.tr(context: context), - // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar", - // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], - labOrderTests: [], - isLoading: false, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: RadiologyOrdersPage(), - ), - ); - }), + MedicalFileCard( + label: LocaleKeys.ancillaryOrdersListNew.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ancillary_orders_list_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + getIt.get().setIsAncillaryOrdersNeedReloading(true); + Navigator.of(context).push( + CustomPageRoute( + page: ToDoPage(), ), - ], - ).paddingSymmetrical(0.w, 0.h), - SizedBox(height: 24.h), + ); + }), + ], + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 16.h), + ], + ); + } + + Widget buildMedicalServicesTab() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.w), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ).onPress(() { - // myAppointmentsViewModel.getPatientMyDoctors(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ); - }), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], ), - SizedBox(height: 16.h), - Consumer(builder: (context, prescriptionVM, child) { - return prescriptionVM.isPrescriptionsOrdersLoading - ? const CommonShimmerWidget().paddingSymmetrical(0.w, 0.h) - : prescriptionVM.patientPrescriptionOrders.isNotEmpty - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false), - child: Padding( - padding: EdgeInsets.all(16.w), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, - shrinkWrap: true, - padding: EdgeInsets.only(left: 0, right: 8.w), - 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: [ - Image.network( - prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, - width: 40.w, - height: 40.h, - fit: BoxFit.cover, - ).circle(100.r), - SizedBox(width: 16.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), - SizedBox(height: 4.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.w, - runSpacing: 4.w, - children: [ - AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), - Directionality( - textDirection: ui.TextDirection.ltr, - child: AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), - false, - ), - isEnglishOnly: true, - ), - ), - ], - ), - ], + ], + ).paddingSymmetrical(0.w, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + }), + SizedBox(height: 16.h), + Consumer(builder: (context, myAppointmentsVM, child) { + // Dynamic height that adapts to device and content + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: 150.h, + maxHeight: isFoldable ? 230.h : (isTablet ? 240.h : 180.h), + ), + child: myAppointmentsVM.isMyAppointmentsLoading + ? MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsVM, + onRescheduleTap: () {}, + onAskDoctorTap: () {}, + ) + : myAppointmentsVM.patientAppointmentsHistoryList.isEmpty + ? Container( + padding: EdgeInsets.all(12.w), + width: MediaQuery.of(context).size.width, + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + getIt.get().onTabChanged(0); + Navigator.of(context).push( + CustomPageRoute( + page: BookAppointmentPage(), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isFoldable || isTablet ? 50.h : 40.h, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ), + ], + ), + ) + : ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: myAppointmentsVM.patientAppointmentsHistoryList.length, + padding: EdgeInsets.zero, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + onRescheduleTap: () { + openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); + }, + onAskDoctorTap: () async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, + doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, + clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID, + onSuccess: (value) async { + if (value) { + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], ), - ), - // SizedBox(width: 40.h), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), - ], - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), - ), - ); - }), - ), - )); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + LoaderBottomSheet.hideLoader(); + } + }, + onError: (_) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + ), + ), ), - SizedBox(height: 16.h), - // Divider(color: AppColors.dividerColor), - // SizedBox(height: 16.h), - // Row( - // children: [ - // Expanded( - // child: CustomButton( - // text: LocaleKeys.allPrescriptions.tr(context: context), - // onPressed: () { - // Navigator.of(context).push( - // CustomPageRoute( - // page: PrescriptionsListPage(), - // ), - // ); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 12.f, - // isBold: true, - // borderRadius: 12.r, - // height: 40.h, - // icon: AppAssets.requests, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.w, - // ), - // ), - // SizedBox(width: 6.w), - // Expanded( - // child: CustomButton( - // text: LocaleKeys.allMedications.tr(context: context), - // onPressed: () { Navigator.of(context).push( - // CustomPageRoute( - // page: ActiveMedicationPage(), - // ), - // );}, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 12.f, - // isBold: true, - // borderRadius: 12.h, - // height: 40.h, - // icon: AppAssets.all_medications_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // ], - // ), - ], - ), - ), - ).paddingSymmetrical(0.w, 0.h) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context), - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), - ).paddingSymmetrical(0.w, 0.h); - }), - SizedBox(height: 24.h), - //My Doctor Section + )); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.w), + ), + ); + }), + SizedBox(height: 10.h), + LocaleKeys.labAndRadiology.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), + 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"], + labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, + isLoading: labViewModel.isLabOrdersLoading, + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: LabOrdersPage(), + ), + ); + }), + ), + SizedBox(width: 8.w), + Expanded( + child: LabRadCard( + icon: AppAssets.radiology_icon, + labelText: LocaleKeys.radiologyLabResults.tr(context: context), + // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar", + // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], + labOrderTests: [], + isLoading: false, + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: RadiologyOrdersPage(), + ), + ); + }), + ), + ], + ).paddingSymmetrical(0.w, 0.h), + SizedBox(height: 24.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.myDoctor.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.w), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ).onPress(() { - // myAppointmentsViewModel.getPatientMyDoctors(); - Navigator.of(context).push( - CustomPageRoute( - page: MyDoctorsPage(), - ), - ); - }), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.w), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], - ).paddingSymmetrical(0.w, 0.h), - SizedBox(height: 16.h), - Consumer(builder: (context, myAppointmentsVM, child) { - return myAppointmentsVM.isPatientMyDoctorsLoading - ? Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.network( - "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 64.w, - height: 64.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: true, radius: 50.r), - SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith").toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: true), - ], - ) - : myAppointmentsVM.patientMyDoctorsList.isEmpty - ? Container( - width: SizeConfig.screenWidth, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: LocaleKeys.youDontHaveAnyCompletedVisitsYet.tr(context: context), - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), - ).paddingSymmetrical(0.w, 0.h) - : SizedBox( - height: 110.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: myAppointmentsVM.patientMyDoctorsList.length, - shrinkWrap: true, - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SizedBox( - // width: 80.w, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + ).onPress(() { + // myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionsListPage(), + ), + ); + }), + ], + ), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsOrdersLoading + ? const CommonShimmerWidget().paddingSymmetrical(0.w, 0.h) + : prescriptionVM.patientPrescriptionOrders.isNotEmpty + ? Container( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, + shrinkWrap: true, + padding: EdgeInsets.only(left: 0, right: 8.w), + 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: [ Image.network( - myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, - width: 64.w, - height: 64.h, + prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, + width: 40.h, + height: 40.h, fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: false, radius: 50.r), - SizedBox(height: 8.h), - SizedBox( - width: 80.w, - child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: false), + ).circle(100.r), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.w, + runSpacing: 4.w, + children: [ + AppCustomChipWidget( + labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate( + prescriptionVM.patientPrescriptionOrders[index].appointmentDate), + false, + ), + isEnglishOnly: true, + ), + ), + ], + ), + ], + ), ), + // SizedBox(width: 40.h), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + width: 15.w, + height: 15.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor)), ], - ), - ).onPress(() async { - bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( - clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, - projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, - doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, - )); - LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { - LoaderBottomSheet.hideLoader(); + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), + page: PrescriptionDetailPage( + isFromAppointments: false, + prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); - }, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }), - ), - )); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), - ), - ).paddingSymmetrical(0.w, 0); - }), - SizedBox(height: 24.h), - LocaleKeys.others.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - SizedBox(height: 16.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 115.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.eyeMeasurements.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.eye_result_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - myAppointmentsViewModel.setIsEyeMeasurementsAppointmentsLoading(true); - myAppointmentsViewModel.onEyeMeasurementsTabChanged(0); - myAppointmentsViewModel.getPatientEyeMeasurementAppointments(); - Navigator.of(context).push( - CustomPageRoute( - page: EyeMeasurementsAppointmentsPage(), - ), - ); - }), - MedicalFileCard( - label: LocaleKeys.illurgyInfomation.tr(), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.allergy_info_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - medicalFileViewModel.getPatientAllergiesList(); - Navigator.of(context).push( - CustomPageRoute( - page: AllergiesListPage(), - ), - ); - }), - MedicalFileCard( - label: LocaleKeys.vaccineInfomation.tr(), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.vaccine_info_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: VaccineListPage(), - ), - ); - }), - ], - ).paddingSymmetrical(0.w, 0.0), - SizedBox(height: 24.h), - ], - ); - case 1: - //Insurance Tab Data - return Column( - children: [ - Consumer(builder: (context, insuranceVM, child) { - return insuranceVM.isInsuranceLoading - ? LabResultItemView( - onTap: () {}, - labOrder: null, - index: index, - isLoading: true, - ).paddingSymmetrical(0.w, 0.0) - : insuranceVM.patientInsuranceList.isNotEmpty - ? PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter( - DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo), - ), - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context), - isSmallWidget: true, - width: 62.w, - height: 62.h, - callToActionButton: CustomButton( - icon: AppAssets.update_insurance_card_icon, - iconColor: AppColors.successColor, - iconSize: 15.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () { - insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }), + ), + )); }, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), - height: isFoldable ? 50.h : 40.h, - ).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h), - ), - ).paddingSymmetrical(0.w, 0.h); - }), - SizedBox(height: 10.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 120.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.only(top: 12.h), - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.updateInsuranceInfo.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.update_insurance_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: InsuranceHomePage())); - }), - MedicalFileCard( - label: "${LocaleKeys.approvals1.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}", - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.insurance_approval_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: InsuranceApprovalsPage(), - ), - ); - }), - MedicalFileCard( - label: LocaleKeys.myInvoicesList.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.invoices_list_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MyInvoicesList(), - ), - ); - }), - MedicalFileCard( - label: LocaleKeys.ancillaryOrdersListNew.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.ancillary_orders_list_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - getIt.get().setIsAncillaryOrdersNeedReloading(true); - Navigator.of(context).push( - CustomPageRoute( - page: ToDoPage(), - ), - ); - }), - ], - ).paddingSymmetrical(0.w, 0.0), - SizedBox(height: 16.h), - ], - ); - case 2: - // Requests Tab Data - return Column( + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 16.h), + // Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allPrescriptions.tr(context: context), + // onPressed: () { + // Navigator.of(context).push( + // CustomPageRoute( + // page: PrescriptionsListPage(), + // ), + // ); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // isBold: true, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.requests, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.w, + // ), + // ), + // SizedBox(width: 6.w), + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allMedications.tr(context: context), + // onPressed: () { Navigator.of(context).push( + // CustomPageRoute( + // page: ActiveMedicationPage(), + // ), + // );}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // isBold: true, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.all_medications_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // ], + // ), + ], + ), + ), + ).paddingSymmetrical(0.w, 0.h) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.youDontHaveAnyPrescriptionsYet.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ).paddingSymmetrical(0.w, 0.h); + }), + SizedBox(height: 24.h), + //My Doctor Section + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + LocaleKeys.myDoctor.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}".toText16(isBold: true), - Row( + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.w), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ).onPress(() { + // myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + CustomPageRoute( + page: MyDoctorsPage(), + ), + ); + }), + ], + ).paddingSymmetrical(0.w, 0.h), + SizedBox(height: 16.h), + Consumer(builder: (context, myAppointmentsVM, child) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 64.h, + height: 64.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: true, radius: 50.r), + SizedBox(height: 8.h), + ("Dr. John Smith Smith Smith").toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: true), ], + ) + : myAppointmentsVM.patientMyDoctorsList.isEmpty + ? Container( + width: SizeConfig.screenWidth, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.youDontHaveAnyCompletedVisitsYet.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ).paddingSymmetrical(0.w, 0.h) + : ConstrainedBox( + constraints: BoxConstraints( + minHeight: 100.h, + maxHeight: isFoldable ? 130.h : (isTablet ? 140.h : 115.h), + ), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SizedBox( + // width: 80.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.h, + height: 64.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false, radius: 50.r), + SizedBox(height: 8.h), + SizedBox( + width: 80.w, + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(isBold: true, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], + ), + ).onPress(() async { + bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( + clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, + projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, + doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, + )); + LoaderBottomSheet.showLoader(); + await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage( + isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), + ), + )); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), + ), + ).paddingSymmetrical(0.w, 0); + }), + SizedBox(height: 24.h), + LocaleKeys.others.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + crossAxisSpacing: 10.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.eyeMeasurements.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + myAppointmentsViewModel.setIsEyeMeasurementsAppointmentsLoading(true); + myAppointmentsViewModel.onEyeMeasurementsTabChanged(0); + myAppointmentsViewModel.getPatientEyeMeasurementAppointments(); + Navigator.of(context).push( + CustomPageRoute( + page: EyeMeasurementsAppointmentsPage(), ), - ], + ); + }), + MedicalFileCard( + label: LocaleKeys.illurgyInfomation.tr(), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 36.w, ).onPress(() { + medicalFileViewModel.getPatientAllergiesList(); Navigator.of(context).push( CustomPageRoute( - page: PatientSickleavesListPage(), + page: AllergiesListPage(), ), ); }), - SizedBox(height: 16.h), - Consumer(builder: (context, medicalFileVM, child) { - return medicalFileVM.isPatientSickLeaveListLoading + MedicalFileCard( + label: LocaleKeys.vaccineInfomation.tr(), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: VaccineListPage(), + ), + ); + }), + ], + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 24.h), + ], + ); + } + + Widget buildRequestsTab() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}".toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: PatientSickleavesListPage(), + ), + ); + }), + SizedBox(height: 16.h), + Consumer(builder: (context, medicalFileVM, child) { + return medicalFileVM.isPatientSickLeaveListLoading + ? PatientSickLeaveCard( + patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), + isLoading: true, + ).paddingSymmetrical(0.w, 0.0) + : medicalFileVM.patientSickLeaveList.isNotEmpty ? PatientSickLeaveCard( - patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), - isLoading: true, + patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, + isLoading: false, ).paddingSymmetrical(0.w, 0.0) - : medicalFileVM.patientSickLeaveList.isNotEmpty - ? PatientSickLeaveCard( - patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, - isLoading: false, - ).paddingSymmetrical(0.w, 0.0) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context), - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), - ).paddingSymmetrical(0.w, 0.h); + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ).paddingSymmetrical(0.w, 0.h); + }), + SizedBox(height: 16.h), + Selector listRequest, List listReady})>( + selector: (context, vm) => ( + isLoading: vm.isPatientMedicalReportsListLoading, + listRequest: vm.patientMedicalReportRequestedList, + listReady: vm.patientMedicalReportReadyList + ), + builder: (context, data, _) { + return MedicalReportCard(isLoading: data.isLoading, listRequest: data.listRequest, listReady: data.listReady); + }, + ), + SizedBox(height: 24.h), + ], + ); + } + + Widget buildHealthToolsTab() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 10.h, + mainAxisSpacing: 16.w, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 12.h), + shrinkWrap: true, + children: [ + // Health Trackers + HealthToolsCard( + label: LocaleKeys.healthTrackers.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.general_health, + iconColor: null, + iconSize: 24.w, + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.healthTrackersPage); + } }), - SizedBox(height: 16.h), - Selector listRequest, List listReady})>( - selector: (context, vm) => (isLoading: vm.isPatientMedicalReportsListLoading, listRequest: vm.patientMedicalReportRequestedList, listReady: vm.patientMedicalReportReadyList), - builder: (context, data, _){ - return MedicalReportCard( - isLoading: data.isLoading, listRequest: data.listRequest, listReady: data.listReady + // Daily Water Monitor + HealthToolsCard( + label: LocaleKeys.dailyWaterMonitor.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.daily_water_monitor_icon, + iconColor: AppColors.infoColor, + iconSize: 24.w, + ).onPress(() async { + if (getIt.get().isAuthenticated) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); + final waterMonitorVM = getIt.get(); + await waterMonitorVM.fetchUserDetailsForMonitoring( + onSuccess: (userDetail) { + LoaderBottomSheet.hideLoader(); + if (userDetail == null) { + waterMonitorVM.populateFromAuthenticatedUser(); + context.navigateWithName(AppRoutes.waterMonitorSettingsPage); + } else { + context.navigateWithName(AppRoutes.waterConsumptionPage); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + context.navigateWithName(AppRoutes.waterConsumptionPage); + }, ); - }, - ) - // GridView( - // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - // crossAxisCount: 3, - // crossAxisSpacing: 10.h, - // mainAxisSpacing: 16.w, - // mainAxisExtent: 110.h, - // ), - // physics: NeverScrollableScrollPhysics(), - // padding: EdgeInsets.zero, - // shrinkWrap: true, - // children: [ - // // MedicalFileCard( - // // label: LocaleKeys.monthlyReports.tr(context: context), - // // textColor: AppColors.blackColor, - // // backgroundColor: AppColors.whiteColor, - // // svgIcon: AppAssets.monthly_reports_icon, - // // isLargeText: true, - // // iconSize: 36.h, - // // ).onPress(() { - // // monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false); - // // Navigator.of(context).push( - // // CustomPageRoute( - // // page: MonthlyReport(), - // // ), - // // ); - // // }), - // ///todo te changes of the medical report should be displayed here. - // /// - // - // // MedicalFileCard( - // // label: LocaleKeys.medicalReports.tr(context: context), - // // textColor: AppColors.blackColor, - // // backgroundColor: AppColors.whiteColor, - // // svgIcon: AppAssets.medical_reports_icon, - // // isLargeText: true, - // // iconSize: 36.w, - // // ).onPress(() { - // // - // // Navigator.of(context).push( - // // CustomPageRoute( - // // page: MedicalReportsPage(), - // // ), - // // ); - // // }), - // // MedicalFileCard( - // // label: LocaleKeys.sickLeaveReport.tr(context: context), - // // textColor: AppColors.blackColor, - // // backgroundColor: AppColors.whiteColor, - // // svgIcon: AppAssets.sick_leave_report_icon, - // // isLargeText: true, - // // iconSize: 36.h, - // // ).onPress(() { - // // Navigator.of(context).push( - // // CustomPageRoute( - // // page: PatientSickleavesListPage(), - // // ), - // // ); - // // }), - // ], - // ).paddingSymmetrical(0.w, 0.0), - ,SizedBox(height: 24.h), + } + }), + // Health Calculators + HealthToolsCard( + label: LocaleKeys.healthCalculatorsServices.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.health_calculators_services_icon, + iconColor: AppColors.successColor, + iconSize: 24.w, + ).onPress(() { + context.navigateWithName(AppRoutes.healthCalculatorsPage); + }), + // Health Converters + HealthToolsCard( + label: LocaleKeys.healthConvertersServices.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.health_converters_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 24.w, + ).onPress(() { + context.navigateWithName(AppRoutes.healthConvertersPage); + }), + // Smart Watches + HealthToolsCard( + label: LocaleKeys.smartWatchesServices.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.smartwatch_icon, + iconColor: AppColors.warningColorYellow, + iconSize: 24.w, + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.smartWatches); + } + }), ], - ); + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 24.h), + ], + ); + } + + Widget getSelectedTabData(int index) { + switch (index) { + case 0: + //General Tab Data + return buildMedicalServicesTab(); + case 1: + //Insurance Tab Data + return buildInsuranceTab(index); + case 2: + // Requests Tab Data + return buildRequestsTab(); case 3: // Health Tools Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 120.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.only(top: 12.h), - shrinkWrap: true, - children: [ - // Health Trackers - HealthToolsCard( - label: LocaleKeys.healthTrackers.tr(context: context), - textColor: AppColors.blackColor, - svgIcon: AppAssets.general_health, - iconColor:null, - iconSize: 24.w, - ).onPress(() { - if (getIt.get().isAuthenticated) { - context.navigateWithName(AppRoutes.healthTrackersPage); - } - }), - // Daily Water Monitor - HealthToolsCard( - label: LocaleKeys.dailyWaterMonitor.tr(context: context), - textColor: AppColors.blackColor, - svgIcon: AppAssets.daily_water_monitor_icon, - iconColor: AppColors.infoColor, - iconSize: 24.w, - ).onPress(() async { - if (getIt.get().isAuthenticated) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); - final waterMonitorVM = getIt.get(); - await waterMonitorVM.fetchUserDetailsForMonitoring( - onSuccess: (userDetail) { - LoaderBottomSheet.hideLoader(); - if (userDetail == null) { - waterMonitorVM.populateFromAuthenticatedUser(); - context.navigateWithName(AppRoutes.waterMonitorSettingsPage); - } else { - context.navigateWithName(AppRoutes.waterConsumptionPage); - } - }, - onError: (error) { - LoaderBottomSheet.hideLoader(); - context.navigateWithName(AppRoutes.waterConsumptionPage); - }, - ); - } - }), - // Health Calculators - HealthToolsCard( - label: LocaleKeys.healthCalculatorsServices.tr(context: context), - textColor: AppColors.blackColor, - svgIcon: AppAssets.health_calculators_services_icon, - iconColor: AppColors.successColor, - iconSize: 24.w, - ).onPress(() { - context.navigateWithName(AppRoutes.healthCalculatorsPage); - }), - // Health Converters - HealthToolsCard( - label: LocaleKeys.healthConvertersServices.tr(context: context), - textColor: AppColors.blackColor, - svgIcon: AppAssets.health_converters_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 24.w, - ).onPress(() { - context.navigateWithName(AppRoutes.healthConvertersPage); - }), - // Smart Watches - HealthToolsCard( - label: LocaleKeys.smartWatchesServices.tr(context: context), - textColor: AppColors.blackColor, - svgIcon: AppAssets.smartwatch_icon, - iconColor: AppColors.warningColorYellow, - iconSize: 24.w, - ).onPress(() { - if (getIt.get().isAuthenticated) { - context.navigateWithName(AppRoutes.smartWatches); - } - }), - ], - ).paddingSymmetrical(0.w, 0.0), - SizedBox(height: 24.h), - ], - ); + return buildHealthToolsTab(); default: return Container(); } @@ -1538,13 +1488,12 @@ class _MedicalFilePageState extends State { List _buildVitalSignPages({ required VitalSignResModel vitalSign, required VoidCallback onTap, - required GlobalKey measureKey, required int currentPageIndex, }) { return [ // Page 1: BMI + Height Padding( - padding: EdgeInsets.only(left: 24.w), + padding: EdgeInsets.symmetric(horizontal: 24.w), child: Row( children: [ Expanded( @@ -1574,7 +1523,7 @@ class _MedicalFilePageState extends State { ), // Page 2: Weight + Blood Pressure Padding( - padding: EdgeInsets.symmetric(horizontal: 12.w), + padding: EdgeInsets.symmetric(horizontal: 24.w), child: Row( children: [ Expanded( @@ -1592,13 +1541,17 @@ class _MedicalFilePageState extends State { child: _buildVitalSignCard( icon: AppAssets.bloodPressure, label: LocaleKeys.bloodPressure.tr(context: context), - value: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && - vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0) + value: (vitalSign.bloodPressureLower != null && + vitalSign.bloodPressureHigher != null && + vitalSign.bloodPressureLower != 0 && + vitalSign.bloodPressureHigher != 0) ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" : '--', unit: '', - status: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && - vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0) + status: (vitalSign.bloodPressureLower != null && + vitalSign.bloodPressureHigher != null && + vitalSign.bloodPressureLower != 0 && + vitalSign.bloodPressureHigher != 0) ? _getBloodPressureStatus( systolic: vitalSign.bloodPressureHigher, diastolic: vitalSign.bloodPressureLower, @@ -1682,7 +1635,8 @@ class _MedicalFilePageState extends State { weight: FontWeight.w600, ), ), - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h), + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -1702,11 +1656,7 @@ class _MedicalFilePageState extends State { mainAxisSize: MainAxisSize.min, children: [ Flexible( - child: value.toText17( - isBold: true, - color: AppColors.textColor, - isEnglishOnly: true - ), + child: value.toText17(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ), if (unit.isNotEmpty && value != '--' && value != '0') ...[ SizedBox(width: 3.w), diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 9c64884a..5a9ae8b6 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -152,7 +152,7 @@ class _MedicalFileAppointmentCardState extends State backgroundColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, textColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, - padding: EdgeInsets.only(top: 12.h, bottom: 12.h, left: 8.w, right: 8.w), + padding: EdgeInsets.only(top: 12.h, left: 8.w, right: 8.w, bottom: 8.h), ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), SizedBox(height: 16.h), IntrinsicWidth( @@ -166,10 +166,10 @@ class _MedicalFileAppointmentCardState extends State Image.network( widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 25.w, - height: 27.h, + width: 30.h, + height: 30.h, fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + ).circle(100.r).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), SizedBox(width: 8.w), Expanded( child: Column( @@ -190,138 +190,7 @@ class _MedicalFileAppointmentCardState extends State ], ), SizedBox(height: 8.h), - // Check if doctor is active - if not, show only View Details button - (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) - ? // Doctor is active - check rebooking logic - (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && - widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) - ? // Show only the button without arrow when rebooking not allowed - widget.myAppointmentsViewModel.isMyAppointmentsLoading - ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) - : AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); - }, - backgroundColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) - .withValues(alpha: 0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) - .withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : // Normal flow - show button with arrow - Row( - children: [ - widget.myAppointmentsViewModel.isMyAppointmentsLoading - ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) - : Expanded( - flex: 7, - child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton(context) - .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); - }, - backgroundColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) - .withValues(alpha: 0.15), - borderColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) - .withValues(alpha: 0.01), - textColor: - AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: - AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), - ), - SizedBox(width: 8.w), - ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - ? SizedBox.shrink() - : Expanded( - flex: 2, - child: Container( - height: 40.h, - width: 40.w, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.r, - ), - child: Padding( - padding: EdgeInsets.all(10.w), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - iconColor: AppColors.whiteColor, - icon: AppAssets.forward_arrow_icon_small, - width: 40.w, - height: 40.h, - fit: BoxFit.contain, - ), - ), - ), - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((val) { - // widget.myAppointmentsViewModel.initAppointmentsViewModel(); - // widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }), - ), - ], - ) - : // Doctor is not active - show only View Details button - CustomButton( - text: LocaleKeys.viewDetails.tr(context: context), - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + _buildAppointmentActionButton(context, appState), ], ).paddingAll(16.w), ), @@ -330,6 +199,170 @@ class _MedicalFileAppointmentCardState extends State ); } + /// Builds the appropriate action button based on appointment state and doctor status + Widget _buildAppointmentActionButton(BuildContext context, AppState appState) { + final isLoading = widget.myAppointmentsViewModel.isMyAppointmentsLoading; + final appointment = widget.patientAppointmentHistoryResponseModel; + final isDoctorActive = appointment.isActiveDoctor ?? true; + + // If doctor is not active, show only View Details button + if (!isDoctorActive) { + return _buildViewDetailsButton(context); + } + + // Doctor is active - check rebooking logic + final isArrived = AppointmentType.isArrived(appointment); + final isRebookingNotAllowed = appointment.isClinicReBookingAllowed == false; + + // If arrived and rebooking not allowed, show button without arrow + if (isArrived && isRebookingNotAllowed) { + return _buildSingleButton(context, isLoading); + } + + // Normal flow - show button with arrow + return _buildButtonWithArrow(context, appState, isLoading); + } + + /// Builds a single button without arrow (for no rebooking scenarios) + Widget _buildSingleButton(BuildContext context, bool isLoading) { + if (isLoading) { + return Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r); + } + + final isArrived = AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel); + + if (isArrived) { + return getArrivedAppointmentButton(context).toShimmer2(isShow: isLoading); + } + + return _buildNextActionButton(context, isLoading); + } + + /// Builds the next action button (Pay Now, Confirm, etc.) + Widget _buildNextActionButton(BuildContext context, bool isLoading) { + final appointment = widget.patientAppointmentHistoryResponseModel; + + return CustomButton( + text: AppointmentType.getNextActionText(appointment.nextAction), + onPressed: () { + handleAppointmentNextAction(appointment.nextAction, context); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(appointment.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(appointment.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(appointment.nextAction), + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(appointment.nextAction), + iconColor: AppointmentType.getNextActionTextColor(appointment.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: isLoading); + } + + /// Builds button with arrow (normal flow with navigation arrow) + Widget _buildButtonWithArrow(BuildContext context, AppState appState, bool isLoading) { + return Row( + children: [ + _buildMainActionButton(context, isLoading), + SizedBox(width: 8.w), + _buildNavigationArrow(context, appState, isLoading), + ], + ); + } + + /// Builds the main action button in the row (left side) + Widget _buildMainActionButton(BuildContext context, bool isLoading) { + if (isLoading) { + return Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r); + } + + final isArrived = AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel); + + return Expanded( + flex: 7, + child: isArrived ? getArrivedAppointmentButton(context).toShimmer2(isShow: isLoading) : _buildNextActionButton(context, isLoading), + ); + } + + /// Builds the navigation arrow button (right side) + Widget _buildNavigationArrow(BuildContext context, AppState appState, bool isLoading) { + final appointment = widget.patientAppointmentHistoryResponseModel; + final isArrived = AppointmentType.isArrived(appointment); + + // Check if arrow should be hidden + final shouldHideArrow = (appointment.isLiveCareAppointment ?? false) || + (appointment.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(appointment.clinicID ?? 0); + + if (shouldHideArrow && isArrived) { + return SizedBox.shrink(); + } + + return Expanded( + flex: 2, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.r, + ), + child: Padding( + padding: EdgeInsets.all(10.w), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + iconColor: AppColors.whiteColor, + icon: AppAssets.forward_arrow_icon_small, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ), + ).toShimmer2(isShow: isLoading).onPress(() { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: appointment), + ), + ) + .then((val) { + // Can refresh appointments here if needed + }); + }), + ); + } + + /// Builds the View Details button (for inactive doctors) + Widget _buildViewDetailsButton(BuildContext context) { + return CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isFoldable ? 36.h : 40.h, + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading); + } + Widget getArrivedAppointmentButton(BuildContext context) { // Check if rebooking is not allowed - show View Details button if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) { diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index a79d8aa9..ebf569e1 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -30,11 +30,7 @@ class MedicalFileCard extends StatelessWidget { Widget build(BuildContext context) { final iconS = iconSize ?? 30.w; return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: backgroundColor, - borderRadius: 20.r, - hasShadow: false - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: backgroundColor, borderRadius: 20.r, hasShadow: false), padding: EdgeInsets.all(12.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -64,5 +60,3 @@ class MedicalFileCard extends StatelessWidget { ); } } - - diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart index 2d2532b5..b023e04f 100644 --- a/lib/presentation/smartwatches/activity_detail.dart +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -14,7 +14,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:provider/provider.dart'; -import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_data_transformations.dart' as durations; import 'package:dartz/dartz.dart' show Tuple2; import '../../core/utils/date_util.dart'; diff --git a/lib/presentation/smartwatches/huawei_health_example.dart b/lib/presentation/smartwatches/huawei_health_example.dart deleted file mode 100644 index 857d26aa..00000000 --- a/lib/presentation/smartwatches/huawei_health_example.dart +++ /dev/null @@ -1,1563 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:huawei_health/huawei_health.dart'; -// -// const String packageName = 'com.ejada.hmg'; -// -// class HuaweiHealthExample extends StatefulWidget { -// const HuaweiHealthExample({Key? key}) : super(key: key); -// -// @override -// State createState() => _HuaweiHealthExampleState(); -// } -// -// class _HuaweiHealthExampleState extends State { -// /// Styles -// static const TextStyle cardTitleTextStyle = TextStyle( -// isBold: true, -// fontSize: 18, -// ); -// static const EdgeInsets componentPadding = EdgeInsets.all(8.0); -// -// /// Text Controllers for showing the logs of different modules -// final TextEditingController _activityTextController = TextEditingController(); -// final TextEditingController _dataTextController = TextEditingController(); -// final TextEditingController _settingTextController = TextEditingController(); -// final TextEditingController _autoRecorderTextController = TextEditingController(); -// final TextEditingController _consentTextController = TextEditingController(); -// final TextEditingController _healthTextController = TextEditingController(); -// -// /// Data controller reference to initialize at startup. -// late DataController _dataController; -// -// String? accessToken = ''; -// -// @override -// void initState() { -// super.initState(); -// if (!mounted) return; -// // Initialize Event Callbacks -// AutoRecorderController.autoRecorderStream.listen(_onAutoRecorderEvent); -// // Initialize a DataController -// initDataController(); -// } -// -// /// Prints the specified text on both the console and the specified text controller. -// void log( -// String methodName, -// TextEditingController controller, -// LogOptions logOption, { -// String? result = '', -// String? error = '', -// }) { -// String log = ''; -// switch (logOption) { -// case LogOptions.call: -// log = '$methodName called'; -// break; -// case LogOptions.success: -// log = '$methodName [Success: $result] '; -// break; -// case LogOptions.error: -// log = '$methodName [Error: $error] [Error Description: ${HiHealthStatusCodes.getStatusCodeMessage(error ?? '')}]'; -// break; -// case LogOptions.custom: -// log = methodName; // Custom text -// break; -// } -// debugPrint(log); -// setState(() { -// controller.text = '$log\n${controller.text}'; -// }); -// } -// -// /// Authorizes Huawei Health Kit for the user, with defined scopes. -// void signIn() async { -// // List of scopes to ask for authorization. -// // -// // Note: These scopes should also be authorized on the Huawei Developer Console. -// final List scopes = [ -// Scope.HEALTHKIT_ACTIVITY_READ, -// Scope.HEALTHKIT_ACTIVITY_WRITE, -// Scope.HEALTHKIT_BLOODGLUCOSE_READ, -// Scope.HEALTHKIT_BLOODGLUCOSE_WRITE, -// Scope.HEALTHKIT_CALORIES_READ, -// Scope.HEALTHKIT_CALORIES_WRITE, -// Scope.HEALTHKIT_DISTANCE_READ, -// Scope.HEALTHKIT_DISTANCE_WRITE, -// Scope.HEALTHKIT_HEARTRATE_READ, -// Scope.HEALTHKIT_HEARTRATE_WRITE, -// Scope.HEALTHKIT_HEIGHTWEIGHT_READ, -// Scope.HEALTHKIT_HEIGHTWEIGHT_WRITE, -// Scope.HEALTHKIT_LOCATION_READ, -// Scope.HEALTHKIT_LOCATION_WRITE, -// Scope.HEALTHKIT_PULMONARY_READ, -// Scope.HEALTHKIT_PULMONARY_WRITE, -// Scope.HEALTHKIT_SLEEP_READ, -// Scope.HEALTHKIT_SLEEP_WRITE, -// Scope.HEALTHKIT_SPEED_READ, -// Scope.HEALTHKIT_SPEED_WRITE, -// Scope.HEALTHKIT_STEP_READ, -// Scope.HEALTHKIT_STEP_WRITE, -// Scope.HEALTHKIT_STRENGTH_READ, -// Scope.HEALTHKIT_STRENGTH_WRITE, -// Scope.HEALTHKIT_BODYFAT_READ, -// Scope.HEALTHKIT_BODYFAT_WRITE, -// Scope.HEALTHKIT_NUTRITION_READ, -// Scope.HEALTHKIT_NUTRITION_WRITE, -// Scope.HEALTHKIT_BLOODPRESSURE_READ, -// Scope.HEALTHKIT_BLOODPRESSURE_WRITE, -// Scope.HEALTHKIT_BODYTEMPERATURE_READ, -// Scope.HEALTHKIT_BODYTEMPERATURE_WRITE, -// Scope.HEALTHKIT_OXYGENSTATURATION_READ, -// Scope.HEALTHKIT_OXYGENSTATURATION_WRITE, -// Scope.HEALTHKIT_REPRODUCTIVE_READ, -// Scope.HEALTHKIT_REPRODUCTIVE_WRITE, -// Scope.HEALTHKIT_ACTIVITY_RECORD_READ, -// Scope.HEALTHKIT_ACTIVITY_RECORD_WRITE, -// Scope.HEALTHKIT_HEARTRATE_REALTIME, -// Scope.HEALTHKIT_STEP_REALTIME, -// Scope.HEALTHKIT_HEARTHEALTH_WRITE, -// Scope.HEALTHKIT_HEARTHEALTH_READ, -// Scope.HEALTHKIT_STRESS_WRITE, -// Scope.HEALTHKIT_STRESS_READ, -// Scope.HEALTHKIT_OXYGEN_SATURATION_WRITE, -// Scope.HEALTHKIT_OXYGEN_SATURATION_READ, -// Scope.HEALTHKIT_HISTORYDATA_OPEN_WEEK, -// Scope.HEALTHKIT_HISTORYDATA_OPEN_MONTH, -// Scope.HEALTHKIT_HISTORYDATA_OPEN_YEAR, -// ]; -// try { -// AuthHuaweiId? result = await HealthAuth.signIn(scopes); -// debugPrint( -// 'Granted Scopes for User(${result?.displayName}): ${result?.grantedScopes?.toString()}', -// ); -// showSnackBar( -// 'Authorization Success.', -// color: Colors.green, -// ); -// setState(() => accessToken = result?.accessToken); -// } on PlatformException catch (e) { -// debugPrint('Error on authorization, Error:${e.toString()}'); -// showSnackBar( -// 'Error on authorization, Error:${e.toString()}, Error Description: ' -// '${HiHealthStatusCodes.getStatusCodeMessage(e.message ?? '')}', -// ); -// } -// } -// -// // ActivityRecordsController -// // -// /// Adds an ActivityRecord with an ActivitySummary, time range is 2 hours from now. -// Future addActivityRecord() async { -// log( -// 'addActivityRecord', -// _activityTextController, -// LogOptions.call, -// ); -// DateTime startTime = DateTime.now().subtract(const Duration(hours: 2)); -// DateTime endTime = DateTime.now(); -// // Build an ActivityRecord object -// ActivityRecord activityRecord = ActivityRecord( -// startTime: startTime, -// endTime: endTime, -// id: 'ActivityRecordId0', -// name: 'AddActivityRecord', -// activityTypeId: HiHealthActivities.running, -// description: 'This is a test for ActivityRecord', -// activitySummary: ActivitySummary( -// paceSummary: PaceSummary( -// avgPace: 247.27626, -// bestPace: 212.0, -// britishPaceMap: { -// '102802480': 365.0, -// }, -// britishPartTimeMap: { -// '1.0': 263.0, -// }, -// partTimeMap: { -// '1.0': 456.0, -// }, -// paceMap: { -// '1.0': 263.0, -// }, -// ), -// dataSummary: [ -// SamplePoint( -// dataType: DataType.DT_CONTINUOUS_DISTANCE_TOTAL, -// startTime: startTime.add(Duration(seconds: 1)), -// endTime: endTime.subtract(Duration(seconds: 1)), -// fieldValueOptions: FieldFloat(Field.FIELD_DISTANCE, 400), -// timeUnit: TimeUnit.MILLISECONDS, -// ), -// SamplePoint( -// dataType: DataType.POLYMERIZE_CONTINUOUS_SPEED_STATISTICS, -// fieldValueOptions: FieldFloat(Field.FIELD_AVG, 60.0), -// startTime: startTime.add(Duration(seconds: 1)), -// endTime: endTime.subtract(Duration(seconds: 1)), -// timeUnit: TimeUnit.MILLISECONDS, -// ) -// ..setFieldValue(Field.FIELD_MIN, 40.0) -// ..setFieldValue(Field.FIELD_MAX, 80.0), -// ]), -// ); -// -// // Build the dataCollector object -// DataCollector dataCollector = DataCollector( -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// dataType: DataType.DT_INSTANTANEOUS_STEPS_RATE, -// name: 'AddActivityRecord1923', -// ); -// -// // You can use sampleSets to add more sample points to the sampling dataset. -// // Build a list of sampling point objects and add it to the sampling dataSet -// List samplePoints = [ -// SamplePoint( -// dataCollector: dataCollector, -// startTime: startTime.add(Duration(seconds: 1)), -// endTime: endTime.subtract(Duration(seconds: 1)), -// fieldValueOptions: FieldFloat(Field.FIELD_STEP_RATE, 10.0), -// timeUnit: TimeUnit.MILLISECONDS, -// ), -// ]; -// SampleSet sampleSet = SampleSet( -// dataCollector, -// samplePoints, -// ); -// -// try { -// await ActivityRecordsController.addActivityRecord( -// ActivityRecordInsertOptions( -// activityRecord: activityRecord, -// sampleSets: [ -// sampleSet, -// ], -// ), -// ); -// log( -// 'addActivityRecord', -// _activityTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'addActivityRecord', -// _activityTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Obtains saved ActivityRecords between yesterday and now, -// /// with the DT_CONTINUOUS_STEPS_DELTA data type -// void getActivityRecord() async { -// log( -// 'getActivityRecord', -// _activityTextController, -// LogOptions.call, -// ); -// // Create start time that will be used to read activity record. -// DateTime startTime = DateTime.now().subtract(const Duration(days: 1)); -// -// // Create end time that will be used to read activity record. -// DateTime endTime = DateTime.now().add(const Duration(hours: 3)); -// -// ActivityRecordReadOptions activityRecordReadOptions = ActivityRecordReadOptions( -// activityRecordId: "ActivityRecordId0", -// activityRecordName: null, -// startTime: startTime, -// endTime: endTime, -// timeUnit: TimeUnit.MILLISECONDS, -// dataType: DataType.DT_INSTANTANEOUS_STEPS_RATE, -// ); -// try { -// List result = await ActivityRecordsController.getActivityRecord( -// activityRecordReadOptions, -// ); -// log( -// 'getActivityRecord', -// _activityTextController, -// LogOptions.success, -// result: '[IDs: ${result.map((ActivityRecord e) => e.id).toList()}]', -// ); -// } on PlatformException catch (e) { -// log( -// 'getActivityRecord', -// _activityTextController, -// LogOptions.error, -// result: e.message, -// ); -// } -// } -// -// /// Starts the ActivityRecord with the id:`ActivityRecordRun1` -// void beginActivityRecord() async { -// try { -// log( -// 'beginActivityRecord', -// _activityTextController, -// LogOptions.call, -// ); -// // Build an ActivityRecord object -// ActivityRecord activityRecord = ActivityRecord( -// id: 'ActivityRecordRun0', -// name: 'BeginActivityRecord', -// description: 'This is ActivityRecord begin test!', -// activityTypeId: HiHealthActivities.running, -// startTime: DateTime.now().subtract(const Duration(hours: 1)), -// ); -// await ActivityRecordsController.beginActivityRecord( -// activityRecord, -// ); -// log( -// 'beginActivityRecord', -// _activityTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'beginActivityRecord', -// _activityTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Stops the ActivityRecord with the id:`ActivityRecordRun1` -// void endActivityRecord() async { -// try { -// log( -// 'endActivityRecord', -// _activityTextController, -// LogOptions.call, -// ); -// final List result = await ActivityRecordsController.endActivityRecord( -// 'ActivityRecordRun0', -// ); -// // Return the list of activity records that have stopped -// log( -// 'endActivityRecord', -// _activityTextController, -// LogOptions.success, -// result: result.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'endActivityRecord', -// _activityTextController, -// LogOptions.error, -// result: e.message, -// ); -// } -// } -// -// /// Ends all the ongoing activity records. -// /// -// /// Result list will be null if there is no ongoing activity record. -// void endAllActivityRecords() async { -// try { -// log( -// 'endAllActivityRecords', -// _activityTextController, -// LogOptions.call, -// ); -// // Return the list of activity records that have stopped -// List result = await ActivityRecordsController.endAllActivityRecords(); -// log( -// 'endAllActivityRecords', -// _activityTextController, -// LogOptions.success, -// result: '[IDs: ${result.map((ActivityRecord e) => e.id).toList()}]', -// ); -// } on PlatformException catch (e) { -// log( -// 'endAllActivityRecords', -// _activityTextController, -// LogOptions.error, -// result: e.message, -// ); -// } -// } -// -// // -// // -// // End of ActivityRecordsController Methods -// -// // DataController Methods -// // -// // -// /// Initializes a DataController instance with a list of HiHealtOptions. -// void initDataController() async { -// if (!mounted) return; -// log( -// 'init', -// _dataTextController, -// LogOptions.call, -// ); -// try { -// _dataController = await DataController.init(); -// log( -// 'init', -// _dataTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'init', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Clears all the data inserted by the app. -// void clearAll() async { -// log('clearAll', _dataTextController, LogOptions.call); -// try { -// await _dataController.clearAll(); -// log('clearAll', _dataTextController, LogOptions.success); -// } on PlatformException catch (e) { -// log('clearAll', _dataTextController, LogOptions.error, error: e.message); -// } -// } -// -// /// Deletes DT_CONTINUOUS_STEPS_DELTA type data by the specified time range. -// void delete() async { -// log( -// 'delete', -// _dataTextController, -// LogOptions.call, -// ); -// // Build the dataCollector object -// DataCollector dataCollector = DataCollector( -// dataType: DataType.DT_CONTINUOUS_STEPS_DELTA, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// dataStreamName: 'STEPS_DELTA', -// ); -// -// // Build the time range for the deletion: start time and end time. -// DeleteOptions deleteOptions = DeleteOptions( -// dataCollectors: [dataCollector], -// startTime: DateTime.parse('2020-10-10 08:00:00'), -// endTime: DateTime.parse('2020-10-10 12:30:00'), -// ); -// -// // Call the api with the constructed DeleteOptions instance. -// try { -// _dataController.delete(deleteOptions); -// log( -// 'delete', -// _dataTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'delete', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Inserts a sampling set with the DT_CONTINUOUS_STEPS_DELTA data type at the -// /// specified start and end dates. -// void insert() async { -// log( -// 'insert', -// _dataTextController, -// LogOptions.call, -// ); -// // Build the dataCollector object -// DataCollector dataCollector = DataCollector( -// dataType: DataType.DT_CONTINUOUS_STEPS_DELTA, -// dataStreamName: 'STEPS_DELTA', -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ); -// // You can use sampleSets to add more sampling points to the sampling dataset. -// SampleSet sampleSet = SampleSet( -// dataCollector, -// [ -// SamplePoint( -// dataCollector: dataCollector, -// startTime: DateTime.parse('2020-10-10 12:00:00'), -// endTime: DateTime.parse('2020-10-10 12:12:00'), -// fieldValueOptions: FieldInt( -// Field.FIELD_STEPS_DELTA, -// 100, -// ), -// ), -// ], -// ); -// // Call the api with the constructed sample set. -// try { -// _dataController.insert(sampleSet); -// log( -// 'insert', -// _dataTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'insert', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// // Reads the user data between the specified start and end dates. -// void read() async { -// log( -// 'read', -// _dataTextController, -// LogOptions.call, -// ); -// // Build the dataCollector object -// DataCollector dataCollector = DataCollector( -// dataType: DataType.DT_CONTINUOUS_STEPS_DELTA, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// dataStreamName: 'STEPS_DELTA', -// ); -// -// // Build the time range for the query: start time and end time. -// ReadOptions readOptions = ReadOptions( -// dataCollectors: [ -// dataCollector, -// ], -// startTime: DateTime.parse('2020-10-10 12:00:00'), -// endTime: DateTime.parse('2020-10-10 12:12:00'), -// )..groupByTime(10000); -// -// // Call the api with the constructed ReadOptions instance. -// try { -// ReadReply? readReply = await _dataController.read(readOptions); -// log( -// 'read', -// _dataTextController, -// LogOptions.success, -// result: readReply.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'read', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Reads the daily summation between the dates: `2020.10.02` to `2020.12.15` for multiple data types. -// /// Note that the time format is different for this method. -// void readDailySummationList() async { -// log( -// 'readDailySummationList', -// _dataTextController, -// LogOptions.call, -// ); -// try { -// List? sampleSets = await _dataController.readDailySummationList( -// [DataType.DT_CONTINUOUS_STEPS_DELTA, DataType.DT_CONTINUOUS_CALORIES_BURNT], -// 20201002, -// 20201003, -// ); -// log( -// 'readDailySummationList', -// _dataTextController, -// LogOptions.success, -// result: sampleSets.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'readDailySummationList', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Reads the steps summation for today. -// void readTodaySummation() async { -// log( -// 'readTodaySummation', -// _dataTextController, -// LogOptions.call, -// ); -// try { -// SampleSet? sampleSet = await _dataController.readTodaySummation( -// DataType.DT_CONTINUOUS_STEPS_DELTA, -// ); -// log( -// 'readTodaySummation', -// _dataTextController, -// LogOptions.success, -// result: sampleSet.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'readTodaySummation', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Updates DT_CONTINUOUS_STEPS_DELTA for the specified dates. -// void update() async { -// log( -// 'update', -// _dataTextController, -// LogOptions.call, -// ); -// -// // Build the dataCollector object -// DataCollector dataCollector = DataCollector( -// dataType: DataType.DT_CONTINUOUS_STEPS_DELTA, -// dataStreamName: 'STEPS_DELTA', -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ); -// -// // You can use sampleSets to add more sampling points to the sampling dataset. -// SampleSet sampleSet = SampleSet( -// dataCollector, -// [ -// SamplePoint( -// dataCollector: dataCollector, -// startTime: DateTime.parse('2020-12-12 09:00:00'), -// endTime: DateTime.parse('2020-12-12 09:05:00'), -// fieldValueOptions: FieldInt( -// Field.FIELD_STEPS_DELTA, -// 120, -// ), -// ), -// ], -// ); -// -// // Build a parameter object for the update. -// // Note: (1) The start time of the modified object updateOptions can not be greater than the minimum -// // value of the start time of all sample data points in the modified data sample set -// // (2) The end time of the modified object updateOptions can not be less than the maximum value of the -// // end time of all sample data points in the modified data sample set -// UpdateOptions updateOptions = UpdateOptions( -// startTime: DateTime.parse('2020-12-12 08:00:00'), -// endTime: DateTime.parse('2020-12-12 09:25:00'), -// sampleSet: sampleSet, -// ); -// try { -// await _dataController.update(updateOptions); -// log( -// 'update', -// _dataTextController, -// LogOptions.success, -// result: sampleSet.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'update', -// _dataTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// // -// // -// // End of DataController Methods -// -// // SettingController Methods -// // -// /// Adds a custom DataType with the FIELD_ALTITUDE. -// void addDataType() async { -// log( -// 'addDataType', -// _settingTextController, -// LogOptions.call, -// ); -// try { -// // The name of the created data type must be prefixed with the package name -// // of the app. Otherwise, the creation fails. If the same data type is tried to -// // be added again an exception will be thrown. -// DataTypeAddOptions options = DataTypeAddOptions( -// '$packageName.myCustomDataType', -// [ -// const Field.newIntField('myIntField'), -// Field.FIELD_ALTITUDE, -// ], -// ); -// final DataType dataTypeResult = await SettingController.addDataType( -// options, -// ); -// log( -// 'addDataType', -// _settingTextController, -// LogOptions.success, -// result: dataTypeResult.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'addDataType', -// _settingTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Reads the inserted data type on the [addDataType] method. -// void readDataType() async { -// log( -// 'readDataType', -// _settingTextController, -// LogOptions.call, -// ); -// try { -// final DataType dataTypeResult = await SettingController.readDataType( -// '$packageName.myCustomDataType', -// ); -// log( -// 'readDataType', -// _settingTextController, -// LogOptions.success, -// result: dataTypeResult.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'readDataType', -// _settingTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Disables the Health Kit function, cancels user authorization, and cancels -// /// all data records. (The task takes effect in 24 hours.) -// void disableHiHealth() async { -// log( -// 'disableHiHealth', -// _settingTextController, -// LogOptions.call, -// ); -// try { -// await SettingController.disableHiHealth(); -// log( -// 'disableHiHealth', -// _settingTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'disableHiHealth', -// _settingTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Checks the user privacy authorization to Health Kit. Redirects the user to -// /// the Authorization screen if the permissions are not given. -// void checkHealthAppAuthorization() async { -// log( -// 'checkHealthAppAuthorization', -// _settingTextController, -// LogOptions.call, -// ); -// try { -// await SettingController.checkHealthAppAuthorization(); -// log( -// 'checkHealthAppAuthorization', -// _settingTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'checkHealthAppAuthorization', -// _settingTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Checks the user privacy authorization to Health Kit. If authorized `true` -// /// value would be returned. -// void getHealthAppAuthorization() async { -// log( -// 'getHealthAppAuthorization', -// _settingTextController, -// LogOptions.call, -// ); -// try { -// final bool result = await SettingController.getHealthAppAuthorization(); -// log( -// 'getHealthAppAuthorization', -// _settingTextController, -// LogOptions.success, -// result: result.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'getHealthAppAuthorization', -// _settingTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// void requestAuth() async { -// final HealthKitAuthResult res = await SettingController.requestAuthorizationIntent( -// [ -// Scope.HEALTHKIT_STEP_READ, -// Scope.HEALTHKIT_STEP_WRITE, -// Scope.HEALTHKIT_HEIGHTWEIGHT_READ, -// Scope.HEALTHKIT_HEIGHTWEIGHT_WRITE, -// Scope.HEALTHKIT_HEARTRATE_READ, -// Scope.HEALTHKIT_HEARTRATE_WRITE, -// Scope.HEALTHKIT_ACTIVITY_RECORD_READ, -// Scope.HEALTHKIT_ACTIVITY_RECORD_WRITE, -// Scope.HEALTHKIT_HEARTHEALTH_READ, -// Scope.HEALTHKIT_HEARTHEALTH_WRITE, -// ], -// true, -// ); -// debugPrint(res.authAccount?.accessToken); -// } -// -// // -// // -// // End of SettingController Methods -// -// // AutoRecorderController Methods -// // -// // -// // Callback function for AutoRecorderStream event. -// void _onAutoRecorderEvent(SamplePoint? res) { -// log( -// '[AutoRecorderEvent] obtained, SamplePoint Field Value is ${res?.fieldValues?.toString()}', -// _autoRecorderTextController, -// LogOptions.custom, -// ); -// } -// -// /// Starts an Android Foreground Service to count the steps of the user. -// /// The steps will be emitted to the AutoRecorderStream. -// void startRecord() async { -// log( -// 'startRecord', -// _autoRecorderTextController, -// LogOptions.call, -// ); -// try { -// await AutoRecorderController.startRecord( -// DataType.DT_CONTINUOUS_STEPS_TOTAL, -// NotificationProperties( -// title: 'HMS Flutter Health Demo', -// text: 'Counting steps', -// subText: 'this is a subtext', -// ticker: 'this is a ticker', -// showChronometer: true, -// ), -// ); -// log( -// 'startRecord', -// _autoRecorderTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'startRecord', -// _autoRecorderTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Ends the Foreground service and stops the step count events. -// void stopRecord() async { -// log( -// 'endRecord', -// _autoRecorderTextController, -// LogOptions.call, -// ); -// try { -// await AutoRecorderController.stopRecord( -// DataType.DT_CONTINUOUS_STEPS_TOTAL, -// ); -// log( -// 'endRecord', -// _autoRecorderTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'endRecord', -// _autoRecorderTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// // -// // -// // End of AutoRecorderController Methods -// -// // ConsentController Methods -// // -// /// Obtains the application id from the agconnect-services.json file. -// void getAppId() async { -// log( -// 'getAppId', -// _consentTextController, -// LogOptions.call, -// ); -// try { -// final String appId = await ConsentsController.getAppId(); -// log( -// 'getAppId', -// _consentTextController, -// LogOptions.success, -// result: appId, -// ); -// } on PlatformException catch (e) { -// log( -// 'getAppId', -// _consentTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Gets the granted permission scopes for the app. -// void getScopes() async { -// log( -// 'getScopes', -// _consentTextController, -// LogOptions.call, -// ); -// try { -// final String appId = await ConsentsController.getAppId(); -// final ScopeLangItem scopeLangItem = await ConsentsController.getScopes( -// 'en-gb', -// appId, -// ); -// log( -// 'getScopes', -// _consentTextController, -// LogOptions.success, -// result: scopeLangItem.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'getScopes', -// _consentTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Revokes all the permissions that authorized for this app. -// void revoke() async { -// log( -// 'revoke', -// _consentTextController, -// LogOptions.call, -// ); -// try { -// final String appId = await ConsentsController.getAppId(); -// await ConsentsController.revoke(appId); -// log( -// 'revoke', -// _consentTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'revoke', -// _consentTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// /// Revokes the distance read/write permissions for the app. -// void revokeWithScopes() async { -// log( -// 'revokeWithScopes', -// _consentTextController, -// LogOptions.call, -// ); -// try { -// // Obtain the application id. -// final String appId = await ConsentsController.getAppId(); -// // Call the revokeWithScopes method with desired scopes. -// await ConsentsController.revokeWithScopes( -// appId, -// [ -// Scope.HEALTHKIT_DISTANCE_WRITE, -// Scope.HEALTHKIT_DISTANCE_READ, -// ], -// ); -// log( -// 'revokeWithScopes', -// _consentTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'revokeWithScopes', -// _consentTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// // -// // -// // End of ConsentController Methods -// -// // HealthController Methods -// // -// void addHealthRecord() async { -// log( -// 'addHealthRecord', -// _healthTextController, -// LogOptions.call, -// ); -// try { -// final DateTime startTime = DateTime(2023, 5, 11); -// final DateTime endTime = DateTime(2023, 5, 13); -// -// DataCollector contDataCollector = DataCollector( -// dataStreamName: 'contDataCollector', -// packageName: packageName, -// dataType: DataType.POLYMERIZE_CONTINUOUS_HEART_RATE_STATISTICS, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ); -// -// DataCollector instDataCollector = DataCollector( -// dataStreamName: 'instDataCollector', -// packageName: packageName, -// dataType: DataType.DT_INSTANTANEOUS_HEART_RATE, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ); -// -// List subDataDetails = [ -// SampleSet(instDataCollector, [ -// SamplePoint( -// dataCollector: instDataCollector, -// ) -// ..setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) -// ..setFieldValue(Field.FIELD_BPM, 88.0) -// ]) -// ]; -// -// List subDataSummary = [ -// SamplePoint( -// dataCollector: contDataCollector, -// ) -// ..setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS) -// ..setFieldValue(Field.FIELD_AVG, 90.0) -// ..setFieldValue(Field.FIELD_MAX, 100.0) -// ..setFieldValue(Field.FIELD_MIN, 80.0) -// ..setFieldValue(Field.LAST, 85.0) -// ]; -// -// final HealthRecord healthRecord = HealthRecord( -// startTime: startTime, -// endTime: endTime, -// metadata: 'Data', -// dataCollector: DataCollector( -// dataStreamName: 'such as step count', -// packageName: packageName, -// dataType: HealthDataTypes.DT_HEALTH_RECORD_BRADYCARDIA, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ), -// ) -// ..setSubDataSummary(subDataSummary) -// ..setSubDataDetails(subDataDetails) -// ..setFieldValue(HealthFields.FIELD_THRESHOLD, 42.0) -// ..setFieldValue(HealthFields.FIELD_MAX_HEART_RATE, 48.0) -// ..setFieldValue(HealthFields.FIELD_MIN_HEART_RATE, 42.0) -// ..setFieldValue(HealthFields.FIELD_AVG_HEART_RATE, 45.0); -// -// final String? result = await HealthRecordController.addHealthRecord( -// HealthRecordInsertOptions( -// healthRecord: healthRecord, -// ), -// ); -// log( -// 'addHealthRecord', -// _healthTextController, -// LogOptions.success, -// result: result.toString(), -// ); -// } on PlatformException catch (e) { -// log( -// 'addHealthRecord', -// _healthTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// void getHealthRecord() async { -// log( -// 'getHealthRecord', -// _healthTextController, -// LogOptions.call, -// ); -// try { -// final DateTime startTime = DateTime(2023, 5, 11); -// final DateTime endTime = DateTime(2023, 5, 13); -// -// HealthRecordReply result = await HealthRecordController.getHealthRecord( -// HealthRecordReadOptions( -// packageName: packageName, -// ) -// ..setSubDataTypeList( -// [ -// DataType.DT_INSTANTANEOUS_HEART_RATE, -// ], -// ) -// ..setTimeInterval( -// startTime, -// endTime, -// TimeUnit.MILLISECONDS, -// ) -// ..readByDataType( -// HealthDataTypes.DT_HEALTH_RECORD_BRADYCARDIA, -// ) -// ..readHealthRecordsFromAllApps(), -// ); -// log( -// 'getHealthRecord', -// _healthTextController, -// LogOptions.success, -// result: result.healthRecords[0].toJson(), -// ); -// } on PlatformException catch (e) { -// log( -// 'getHealthRecord', -// _healthTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// void updateHealthRecord() async { -// log( -// 'updateHealthRecord', -// _healthTextController, -// LogOptions.call, -// ); -// try { -// final DateTime startTime = DateTime(2022, 10, 11); -// final DateTime endTime = DateTime(2022, 10, 12); -// final HealthRecord healthRecord = HealthRecord( -// startTime: startTime, -// endTime: endTime, -// metadata: 'Data', -// dataCollector: DataCollector( -// dataStreamName: 'such as step count', -// packageName: packageName, -// dataType: HealthDataTypes.DT_HEALTH_RECORD_BRADYCARDIA, -// dataGenerateType: DataGenerateType.DATA_TYPE_RAW, -// ), -// ) -// ..setFieldValue(HealthFields.FIELD_THRESHOLD, 41.9) -// ..setFieldValue(HealthFields.FIELD_MAX_HEART_RATE, 49.1) -// ..setFieldValue(HealthFields.FIELD_MIN_HEART_RATE, 41.1) -// ..setFieldValue(HealthFields.FIELD_AVG_HEART_RATE, 45.1); -// await HealthRecordController.updateHealthRecord( -// HealthRecordUpdateOptions( -// healthRecord: healthRecord, -// healthRecordId: '', -// ), -// ); -// log( -// 'updateHealthRecord', -// _healthTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'updateHealthRecord', -// _healthTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// void deleteHealthRecord() async { -// log( -// 'deleteHealthRecord', -// _healthTextController, -// LogOptions.call, -// ); -// try { -// await HealthRecordController.deleteHealthRecord( -// HealthRecordDeleteOptions( -// startTime: DateTime.now().subtract(const Duration(days: 14)), -// endTime: DateTime.now(), -// )..setHealthRecordIds( -// [ -// '', -// ], -// ), -// ); -// log( -// 'deleteHealthRecord', -// _healthTextController, -// LogOptions.success, -// ); -// } on PlatformException catch (e) { -// log( -// 'deleteHealthRecord', -// _healthTextController, -// LogOptions.error, -// error: e.message, -// ); -// } -// } -// -// // -// // -// // End of HealthController Methods -// -// // App's widgets. -// // -// // -// Widget expansionCard({ -// required String titleText, -// required List children, -// }) { -// return Card( -// margin: componentPadding, -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(10.0), -// ), -// child: ExpansionTile( -// title: Text( -// titleText, -// style: cardTitleTextStyle, -// ), -// children: children, -// ), -// ); -// } -// -// Widget loggingArea( -// TextEditingController moduleTextController, -// ) { -// return Column( -// children: [ -// Container( -// margin: componentPadding, -// padding: const EdgeInsets.all(8.0), -// height: 200, -// decoration: BoxDecoration( -// borderRadius: BorderRadius.circular(5.0), -// border: Border.all(color: Colors.black12), -// ), -// child: TextField( -// readOnly: true, -// maxLines: 15, -// controller: moduleTextController, -// decoration: const InputDecoration( -// enabledBorder: InputBorder.none, -// ), -// ), -// ), -// TextButton( -// child: const Text('Clear Log'), -// onPressed: () => setState(() { -// moduleTextController.text = ''; -// }), -// ) -// ], -// ); -// } -// -// void showSnackBar( -// String text, { -// Color color = Colors.blue, -// }) { -// final SnackBar snackBar = SnackBar( -// content: Text(text), -// backgroundColor: color, -// action: SnackBarAction( -// label: 'Close', -// textColor: Colors.white, -// onPressed: () { -// ScaffoldMessenger.of(context).removeCurrentSnackBar(); -// }, -// ), -// ); -// ScaffoldMessenger.of(context).showSnackBar(snackBar); -// } -// -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar( -// backgroundColor: Colors.white, -// title: const Text( -// 'Huawei Health Kit', -// style: TextStyle( -// color: Colors.blue, -// fontWeight: FontWeight.bold, -// ), -// ), -// centerTitle: true, -// elevation: 0.0, -// actions: [ -// IconButton( -// onPressed: requestAuth, -// icon: const Icon(Icons.ac_unit), -// ), -// ], -// ), -// body: Builder( -// builder: (BuildContext context) { -// return ListView( -// physics: const BouncingScrollPhysics( -// parent: AlwaysScrollableScrollPhysics(), -// ), -// children: [ -// // Sign In Widgets -// Card( -// margin: componentPadding, -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(10.0), -// ), -// child: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// const Padding( -// padding: componentPadding, -// child: Text( -// 'Tap to SignIn button to obtain the HMS Account to complete ' -// 'login and authorization, and then use other buttons ' -// 'to try the related API functions.', -// textAlign: TextAlign.center, -// ), -// ), -// const Padding( -// padding: componentPadding, -// child: Text( -// 'Note: If the login page is not displayed, change the package ' -// 'name, AppID, and configure the signature file by referring ' -// 'to the developer guide on the official website.', -// textAlign: TextAlign.center, -// style: TextStyle( -// color: Colors.blue, -// ), -// ), -// ), -// Container( -// padding: componentPadding, -// width: double.infinity, -// child: OutlinedButton( -// style: ButtonStyle( -// backgroundColor: MaterialStateProperty.all( -// Colors.blue, -// ), -// ), -// child: const Text( -// 'SignIn', -// style: TextStyle( -// color: Colors.white, -// ), -// ), -// onPressed: () => signIn(), -// ), -// ), -// ], -// ), -// ), -// -// // ActivityRecordsController -// expansionCard( -// titleText: 'ActivityRecords Controller', -// children: [ -// loggingArea(_activityTextController), -// ListTile( -// title: const Text('AddActivityRecord'), -// onTap: () => addActivityRecord(), -// ), -// ListTile( -// title: const Text('GetActivityRecord'), -// onTap: () => getActivityRecord(), -// ), -// ListTile( -// title: const Text('beginActivityRecord'), -// onTap: () => beginActivityRecord(), -// ), -// ListTile( -// title: const Text('endActivityRecord'), -// onTap: () => endActivityRecord(), -// ), -// ListTile( -// title: const Text('endAllActivityRecords'), -// onTap: () => endAllActivityRecords(), -// ), -// ], -// ), -// // DataController Widgets -// expansionCard( -// titleText: 'DataController', -// children: [ -// loggingArea(_dataTextController), -// ListTile( -// title: const Text('readTodaySummation'), -// onTap: () => readTodaySummation(), -// ), -// ListTile( -// title: const Text('readDailySummationList'), -// onTap: () => readDailySummationList(), -// ), -// ListTile( -// title: const Text('insert'), -// onTap: () => insert(), -// ), -// ListTile( -// title: const Text('read'), -// onTap: () => read(), -// ), -// ListTile( -// title: const Text('update'), -// onTap: () => update(), -// ), -// ListTile( -// title: const Text('delete'), -// onTap: () => delete(), -// ), -// ListTile( -// title: const Text('clearAll'), -// onTap: () => clearAll(), -// ), -// ], -// ), -// // SettingController Widgets. -// expansionCard( -// titleText: 'SettingController', -// children: [ -// loggingArea(_settingTextController), -// ListTile( -// title: const Text('addDataType'), -// onTap: () => addDataType(), -// ), -// ListTile( -// title: const Text('readDataType'), -// onTap: () => readDataType(), -// ), -// ListTile( -// title: const Text('disableHiHealth'), -// onTap: () => disableHiHealth(), -// ), -// ListTile( -// title: const Text('checkHealthAppAuthorization'), -// onTap: () => checkHealthAppAuthorization(), -// ), -// ListTile( -// title: const Text('getHealthAppAuthorization'), -// onTap: () => getHealthAppAuthorization(), -// ), -// ], -// ), -// // AutoRecorderController Widgets -// expansionCard( -// titleText: 'AutoRecorderController', -// children: [ -// loggingArea(_autoRecorderTextController), -// ListTile( -// title: const Text('startRecord'), -// onTap: () => startRecord(), -// ), -// ListTile( -// title: const Text('stopRecord'), -// onTap: () => stopRecord(), -// ), -// ], -// ), -// // Consent Controller Widgets -// expansionCard( -// titleText: 'ConsentController', -// children: [ -// loggingArea(_consentTextController), -// ListTile( -// title: const Text('getAppId'), -// onTap: () => getAppId(), -// ), -// ListTile( -// title: const Text('getScopes'), -// onTap: () => getScopes(), -// ), -// ListTile( -// title: const Text('revoke'), -// onTap: () => revoke(), -// ), -// ListTile( -// title: const Text('revokeWithScopes'), -// onTap: () => revokeWithScopes(), -// ), -// ], -// ), -// -// // Health Controller Widgets -// expansionCard( -// titleText: 'HealthController', -// children: [ -// loggingArea(_healthTextController), -// ListTile( -// title: const Text('addHealthRecord'), -// onTap: () => addHealthRecord(), -// ), -// ListTile( -// title: const Text('getHealthRecord'), -// onTap: () => getHealthRecord(), -// ), -// ListTile( -// title: const Text('updateHealthRecord'), -// onTap: () => updateHealthRecord(), -// ), -// ListTile( -// title: const Text('deleteHealthRecord'), -// onTap: () => deleteHealthRecord(), -// ), -// ], -// ), -// ], -// ); -// }, -// ), -// ); -// } -// } -// -// /// Options for logging. -// enum LogOptions { -// call, -// success, -// error, -// custom, -// } diff --git a/lib/presentation/smartwatches/smart_watch_activity.dart b/lib/presentation/smartwatches/smart_watch_activity.dart deleted file mode 100644 index f6c1baa6..00000000 --- a/lib/presentation/smartwatches/smart_watch_activity.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/app_assets.dart'; -import 'package:hmg_patient_app_new/core/app_export.dart'; -import 'package:hmg_patient_app_new/core/dependencies.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/smartwatch_health_data/health_provider.dart'; -import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.dart'; -import 'package:hmg_patient_app_new/services/navigation_service.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:provider/provider.dart'; -import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; - -import '../../core/utils/date_util.dart' show DateUtil; - -class SmartWatchActivity extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "All Health Data".needTranslation, - child: Column( - spacing: 16.h, - children: [ - resultItem( - leadingIcon: AppAssets.watchActivity, - title: "Activity Calories".needTranslation, - description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, - trailingIcon: AppAssets.watchActivityTrailing, - result: context.read().sumOfNonEmptyData(context.read().vitals?.activity??[]), - unitsOfMeasure: "Kcal" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("activity"); - context.read().saveSelectedSection("activity"); - context.read().fetchData(); - context.read().navigateToDetails("activity", sectionName:"Activity Calories", uom: "Kcal"); - - }), - resultItem( - leadingIcon: AppAssets.watchSteps, - title: "Steps".needTranslation, - description: "Step count is the number of steps you take throughout the day.".needTranslation, - trailingIcon: AppAssets.watchStepsTrailing, - result: context.read().sumOfNonEmptyData(context.read().vitals?.step??[]), - unitsOfMeasure: "Steps" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("steps"); - context.read().saveSelectedSection("steps"); - context.read().fetchData(); - context.read().navigateToDetails("steps", sectionName: "Steps", uom: "Steps"); - - }), - resultItem( - leadingIcon: AppAssets.watchSteps, - title: "Distance Covered".needTranslation, - description: "Step count is the distance you take throughout the day.".needTranslation, - trailingIcon: AppAssets.watchStepsTrailing, - result: context.read().sumOfNonEmptyData(context.read().vitals?.distance??[]), - unitsOfMeasure: "Km" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("distance"); - context.read().saveSelectedSection("distance"); - context.read().fetchData(); - context.read().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km"); - - }), - - resultItem( - leadingIcon: AppAssets.watchSleep, - title: "Sleep Score".needTranslation, - description: "This will keep track of how much hours you sleep in a day".needTranslation, - trailingIcon: AppAssets.watchSleepTrailing, - result: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[0], - unitsOfMeasure: "hr", - resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[2], - unitOfSecondMeasure: "min" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("sleep"); - context.read().saveSelectedSection("sleep"); - context.read().fetchData(); - context.read().navigateToDetails("sleep", sectionName:"Sleep Score",uom:""); - - }), - - resultItem( - leadingIcon: AppAssets.watchWeight, - title: "Blood Oxygen".needTranslation, - description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation, - trailingIcon: AppAssets.watchWeightTrailing, - result: context.read().firstNonEmptyValue(context.read().vitals?.bodyOxygen??[], ), - unitsOfMeasure: "%" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("bodyOxygen"); - context.read().saveSelectedSection("bodyOxygen"); - context.read().fetchData(); - context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName:"Blood Oxygen" ); - - }), - resultItem( - leadingIcon: AppAssets.watchWeight, - title: "Body temperature".needTranslation, - description: "This will calculate your Body temprerature to keep track and update history".needTranslation, - trailingIcon: AppAssets.watchWeightTrailing, - result: context.read().firstNonEmptyValue(context.read().vitals?.bodyTemperature??[]), - unitsOfMeasure: "C" - ).onPress((){ - // Map> getVitals() { - // return { - // "heartRate": heartRate , - // "sleep": sleep, - // "steps": step, - // "activity": activity, - // "bodyOxygen": bodyOxygen, - // "bodyTemperature": bodyTemperature, - // }; - // } - context.read().setDurations(durations.Durations.daily); - - context.read().deleteDataIfSectionIsDifferent("bodyTemperature"); - context.read().saveSelectedSection("bodyTemperature"); - context.read().fetchData(); - context.read().navigateToDetails("bodyTemperature" , sectionName: "Body temperature".capitalizeFirstofEach, uom: "C"); - - }), - ], - ).paddingSymmetrical(24.w, 24.h), - )); - } - - Widget resultItem({ - required String leadingIcon, - required String title, - required String description, - required String trailingIcon, - required String result, - required String unitsOfMeasure, - String? resultSecondValue, - String? unitOfSecondMeasure - }) { - return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Row( - spacing: 16.w, - children: [ - Expanded( - child:Column( - spacing: 8.h, - children: [ - Row( - spacing: 8.w, - children: [ - Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w), - title.toText16( weight: FontWeight.w600, color: AppColors.textColor), - ], - ), - description.toText12(isBold: true, color: AppColors.greyTextColor), - Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - spacing: 2.h, - children: [ - result.toText21(isBold: true, color: AppColors.textColor), - unitsOfMeasure.toText10(isBold: true, color:AppColors.greyTextColor ), - if(resultSecondValue != null) - Visibility( - visible: resultSecondValue != null , - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - spacing: 2.h, - children: [ - SizedBox(width: 2.w,), - resultSecondValue.toText21(isBold: true, color: AppColors.textColor), - unitOfSecondMeasure!.toText10(isBold: true, color:AppColors.greyTextColor ) - ], - ), - ) - ], - ), - - ], - ) , - ), - Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h), - ], - ).paddingSymmetrical(16.w, 16.h) - ); - } -} diff --git a/lib/presentation/smartwatches/smart_watches_health_data_screen.dart b/lib/presentation/smartwatches/smart_watches_health_data_screen.dart new file mode 100644 index 00000000..baa65b49 --- /dev/null +++ b/lib/presentation/smartwatches/smart_watches_health_data_screen.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_data_transformations.dart' as durations; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +import '../../core/utils/date_util.dart' show DateUtil; + +class SmartWatchesHealthDataScreen extends StatelessWidget { + const SmartWatchesHealthDataScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "All Health Data".needTranslation, + child: Column( + spacing: 16.h, + children: [ + resultItem( + leadingIcon: AppAssets.watchActivity, + title: "Activity Calories".needTranslation, + description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, + trailingIcon: AppAssets.watchActivityTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.activity ?? []), + unitsOfMeasure: "Kcal") + .onPress(() { + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("activity"); + context.read().saveSelectedSection("activity"); + context.read().fetchData(); + context.read().navigateToDetails("activity", sectionName: "Activity Calories", uom: "Kcal"); + }), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Steps".needTranslation, + description: "Step count is the number of steps you take throughout the day.".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.step ?? []), + unitsOfMeasure: "Steps") + .onPress(() { + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("steps"); + context.read().saveSelectedSection("steps"); + context.read().fetchData(); + context.read().navigateToDetails("steps", sectionName: "Steps", uom: "Steps"); + }), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Distance Covered".needTranslation, + description: "Step count is the distance you take throughout the day.".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.distance ?? []), + unitsOfMeasure: "Km") + .onPress(() { + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("distance"); + context.read().saveSelectedSection("distance"); + context.read().fetchData(); + context.read().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km"); + }), + resultItem( + leadingIcon: AppAssets.watchSleep, + title: "Sleep Score".needTranslation, + description: "This will keep track of how much hours you sleep in a day".needTranslation, + trailingIcon: AppAssets.watchSleepTrailing, + result: DateUtil.millisToHourMin( + int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep ?? []))) + .split(" ")[0], + unitsOfMeasure: "hr", + resultSecondValue: DateUtil.millisToHourMin( + int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep ?? []))) + .split(" ")[2], + unitOfSecondMeasure: "min") + .onPress(() { + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("sleep"); + context.read().saveSelectedSection("sleep"); + context.read().fetchData(); + context.read().navigateToDetails("sleep", sectionName: "Sleep Score", uom: ""); + }), + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Blood Oxygen".needTranslation, + description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: context.read().firstNonEmptyValue( + context.read().vitals?.bodyOxygen ?? [], + ), + unitsOfMeasure: "%") + .onPress(() { + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("bodyOxygen"); + context.read().saveSelectedSection("bodyOxygen"); + context.read().fetchData(); + context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName: "Blood Oxygen"); + }), + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Body temperature".needTranslation, + description: "This will calculate your Body temprerature to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: context.read().firstNonEmptyValue(context.read().vitals?.bodyTemperature ?? []), + unitsOfMeasure: "C") + .onPress(() { + context.read().setDurations(durations.Durations.daily); + context.read().deleteDataIfSectionIsDifferent("bodyTemperature"); + context.read().saveSelectedSection("bodyTemperature"); + context.read().fetchData(); + context.read().navigateToDetails("bodyTemperature", sectionName: "Body temperature".capitalizeFirstofEach, uom: "C"); + }), + ], + ).paddingSymmetrical(24.w, 24.h), + )); + } + + Widget resultItem({ + required String leadingIcon, + required String title, + required String description, + required String trailingIcon, + required String result, + required String unitsOfMeasure, + String? resultSecondValue, + String? unitOfSecondMeasure, + }) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + spacing: 16.w, + children: [ + Expanded( + child: Column( + spacing: 8.h, + children: [ + Row( + spacing: 8.w, + children: [ + Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w), + title.toText16(weight: FontWeight.w600, color: AppColors.textColor), + ], + ), + description.toText12(isBold: true, color: AppColors.greyTextColor), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + result.toText21(isBold: true, color: AppColors.textColor), + unitsOfMeasure.toText10(isBold: true, color: AppColors.greyTextColor), + if (resultSecondValue != null) + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + SizedBox(width: 2.w), + resultSecondValue.toText21(isBold: true, color: AppColors.textColor), + unitOfSecondMeasure!.toText10(isBold: true, color: AppColors.greyTextColor) + ], + ), + ], + ), + ], + ), + ), + Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h), + ], + ).paddingSymmetrical(16.w, 16.h)); + } +} diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index 6016a803..7367af54 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -54,7 +52,6 @@ class SmartwatchHomePage extends StatelessWidget { fontSize: 16.f, isBold: true, borderRadius: 12.r, - height: 50.h, icon: AppAssets.ask_doctor_icon, iconColor: AppColors.infoColor, @@ -69,11 +66,12 @@ class SmartwatchHomePage extends StatelessWidget { child: GridView( padding: EdgeInsets.zero, shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - crossAxisSpacing: 16.h, + crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 240.h, + childAspectRatio: isFoldable ? 1.1 : (isTablet ? 1.3 : 0.7), ), children: [ Container( @@ -83,20 +81,24 @@ class SmartwatchHomePage extends StatelessWidget { ), child: Column( children: [ - Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.h, height: 136.h).paddingSymmetrical(24.w, 8.h), "Apple Watch".needTranslation.toText16(isBold: true), CustomButton( text: LocaleKeys.selectSmartWatch.tr(context: context), onPressed: () { - context.read().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); - getIt.get().pushPage(page: SmartwatchInstructionsPage( - smartwatchDetails: SmartwatchDetails(SmartWatchTypes.apple, - "assets/images/png/smartwatches/apple-watch-5.jpg", - AppAssets.bluetooth, - LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), - LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - LocaleKeys.applewatchshouldbeconnected.tr(context: context)), - )); + context + .read() + .setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); + getIt.get().pushPage( + page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails( + SmartWatchTypes.apple, + "assets/images/png/smartwatches/apple-watch-5.jpg", + AppAssets.bluetooth, + LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.applewatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -110,26 +112,29 @@ class SmartwatchHomePage extends StatelessWidget { ), ), Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ - Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), + Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h) + .paddingSymmetrical(24.w, 8.h), "Samsung Watch".needTranslation.toText16(isBold: true), CustomButton( text: LocaleKeys.selectSmartWatch.tr(context: context), onPressed: () { - context.read().setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); - getIt.get().pushPage(page: SmartwatchInstructionsPage( - smartwatchDetails: SmartwatchDetails(SmartWatchTypes.samsung, - "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", - AppAssets.bluetooth, - LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), - LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), - )); }, + context + .read() + .setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); + getIt.get().pushPage( + page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails( + SmartWatchTypes.samsung, + "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", + AppAssets.bluetooth, + LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), + )); + }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, @@ -187,7 +192,6 @@ class SmartwatchHomePage extends StatelessWidget { CustomButton( text: LocaleKeys.selectSmartWatch.tr(context: context), onPressed: () { - showUnavailableDialog(context); // context.read().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png"); // getIt.get().pushPage(page: SmartwatchInstructionsPage( @@ -221,18 +225,16 @@ class SmartwatchHomePage extends StatelessWidget { } void showUnavailableDialog(BuildContext context) { - showCommonBottomSheetWithoutHeight( title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context), + loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context), isShowActionButtons: false, showOkButton: true, onConfirmTap: () async { context.pop(); - } - ), + }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 6a2aae29..a4f6c0c0 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -2,13 +2,10 @@ 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/common_models/smart_watch.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; -import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -26,62 +23,66 @@ class SmartwatchInstructionsPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "How does it work".needTranslation, - bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - ), - child: CustomButton( - text: LocaleKeys.getStarted.tr(context: context), - onPressed: () { - context.read().initDevice(); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - isBold: true, - borderRadius: 12.r, - height: 50.h, - ).paddingSymmetrical(24.w, 30.h), + body: CollapsingListView( + title: "How does it work".needTranslation, + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, ), - child: Column( - mainAxisSize: MainAxisSize.max, - spacing: 18.h, - children: [ - Image.asset(smartwatchDetails.watchIcon, fit: BoxFit.contain, height: 280.h,width: 280.w,), - DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Column( - children: [ - watchContentDetails( - title: smartwatchDetails.detailsTitle, - description: smartwatchDetails.details, - icon: smartwatchDetails.smallIcon, - descriptionTextColor: AppColors.primaryRedColor - ), - Divider( - color: AppColors.dividerColor, - thickness: 1.h, - ).paddingOnly(top: 16.h, bottom: 16.h), - watchContentDetails( - title: smartwatchDetails.secondTitle, - description: LocaleKeys.updatetheinformation.tr(), - icon: AppAssets.bluetooth, - descriptionTextColor: AppColors.greyTextColor - ), - ], - ).paddingSymmetrical(16.w, 16.h), - ) - ], - ).paddingSymmetrical(24.w, 16.h), + child: CustomButton( + text: LocaleKeys.getStarted.tr(context: context), + onPressed: () { + context.read().initDevice(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + isBold: true, + borderRadius: 12.r, + height: 50.h, + ).paddingSymmetrical(24.w, 30.h), ), + child: Column( + mainAxisSize: MainAxisSize.max, + spacing: 18.h, + children: [ + Image.asset( + smartwatchDetails.watchIcon, + fit: BoxFit.contain, + height: 280.h, + width: 280.w, + ), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Column( + children: [ + watchContentDetails( + title: smartwatchDetails.detailsTitle, + description: smartwatchDetails.details, + icon: smartwatchDetails.smallIcon, + descriptionTextColor: AppColors.primaryRedColor, + ), + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ).paddingOnly(top: 16.h, bottom: 16.h), + watchContentDetails( + title: smartwatchDetails.secondTitle, + description: LocaleKeys.updatetheinformation.tr(), + icon: AppAssets.bluetooth, + descriptionTextColor: AppColors.greyTextColor, + ), + ], + ).paddingSymmetrical(16.w, 16.h), + ) + ], + ).paddingSymmetrical(24.w, 16.h), + ), ); } - Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -90,9 +91,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h), - ), - title.toText16(isBold: true, color: AppColors.textColor), description.toText12(isBold: true, color: descriptionTextColor) ], diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 41cb6331..861cf455 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -19,7 +19,6 @@ import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; -import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; @@ -42,7 +41,6 @@ import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/parking/paking_page.dart'; -import '../presentation/smartwatches/smartwatch_instructions_page.dart'; import '../services/error_handler_service.dart'; class AppRoutes { From 121e5bd031d4e6182bac0b3488654ccb4a33db69 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 28 Apr 2026 11:11:55 +0300 Subject: [PATCH 05/11] Habib Wallet fix in services page --- .../hmg_services/services_page.dart | 25 ++++++++++++++++--- .../insurance_approval_details_page.dart | 9 +++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 4ea290b0..219a238a 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -533,10 +533,27 @@ class _ServicesPageState extends State { Spacer(), getIt.get().isAuthenticated ? Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), - isExpanded: false, letterSpacing: -1) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }) + return Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.saudi_riyal_icon, + iconColor: AppColors.inputLabelTextColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.h), + NumberFormat.decimalPattern() + .format(habibWalletVM.habibWalletAmount) + .toString() + .toText28(isBold: true, isEnglishOnly: true) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + ], + ); + // Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), + // isExpanded: false, letterSpacing: -1) + // .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), Spacer(), getIt.get().isAuthenticated diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index 6d8e2524..29663938 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -108,10 +108,10 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { richText: Row( mainAxisSize: MainAxisSize.min, children: [ - "${LocaleKeys.receiptOn.tr(context: context)} ".toText10(), + "${LocaleKeys.receiptOn.tr(context: context)} ".toText10(isBold: true), Directionality( textDirection: ui.TextDirection.ltr, - child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.receiptOn), false).toText10(isEnglishOnly: true)), + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.receiptOn), false).toText10(isBold: true)), ], ), isEnglishOnly: true, @@ -121,15 +121,14 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { richText: Row( mainAxisSize: MainAxisSize.min, children: [ - "${LocaleKeys.expiryOn.tr(context: context)} ".toText10(), + "${LocaleKeys.expiryOn.tr(context: context)} ".toText10(isBold: true), Directionality( textDirection: ui.TextDirection.ltr, - child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.expiryDate), false).toText10(isEnglishOnly: true)), + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.expiryDate), false).toText10(isBold: true)), ], ), isEnglishOnly: true, ), - ], ), ], From 63fd82407e4e1d3805b801148ad3792cf41cafe8 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 28 Apr 2026 14:40:18 +0300 Subject: [PATCH 06/11] Symptoms checker flow updates and Design Fixes on Fold --- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 42 +++- .../symptoms_checker_repo.dart | 12 +- .../symptoms_checker_view_model.dart | 54 ++++- .../book_appointment/doctor_profile_page.dart | 212 ++++++++++-------- .../widgets/appointment_calendar.dart | 4 +- .../health_calculator_detailed_page.dart | 78 +++---- .../health_calculators_page.dart | 43 ++-- .../widgets/blood_cholesterol.dart | 53 +++-- .../widgets/triglycerides.dart | 58 ++--- .../organ_selector_screen.dart | 28 ++- .../symptoms_checker/triage_screen.dart | 67 ++++-- .../pages/height_selection_page.dart | 21 +- .../widgets/height_scale.dart | 9 +- .../ancillary_procedures_details_page.dart | 44 +--- lib/widgets/appbar/collapsing_list_view.dart | 56 +++-- 16 files changed, 444 insertions(+), 341 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 34f56efe..5172f77b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -17,7 +17,6 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:http/http.dart' as http; - abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); @@ -209,7 +208,7 @@ class ApiClientImp implements ApiClient { // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; - //VIP Patient: 1181868 + //VIP Patient: 1181868body: // body['IdentificationNo'] = "2235558844"; // body['MobileNo'] = "966533147722"; @@ -244,6 +243,7 @@ class ApiClientImp implements ApiClient { http.Response response; try { response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); + debugPrint("response: ${response.body}", wrapWidth: 2048); } on SocketException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 29c690e1..a8f8c2fb 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -11,8 +11,14 @@ class ApiConsts { static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD - static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD - static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD + static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // symptoms API URL PROD + static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD + static String symptomsCheckerApiLive = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD + static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD + + // Symptoms Checker Credentials + static String symptomsCheckerUsername = 'mobile_user'; + static String symptomsCheckerPassword = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'; static var payFortEnvironment = FortEnvironment.production; static var applePayMerchantId = "merchant.com.hmgwebservices"; @@ -25,6 +31,7 @@ class ApiConsts { static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + // static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; @@ -47,6 +54,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/'; QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiLive; + symptomsCheckerUsername = 'mobile_user'; + symptomsCheckerPassword = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'; + break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -59,6 +70,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiUAT; + symptomsCheckerUsername = 'guest_user'; + symptomsCheckerPassword = '123456'; + break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -71,6 +86,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiUAT; + symptomsCheckerUsername = 'guest_user'; + symptomsCheckerPassword = '123456'; + break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -83,6 +102,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/'; QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiUAT; + symptomsCheckerUsername = 'guest_user'; + symptomsCheckerPassword = '123456'; + break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -95,6 +118,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiUAT; + symptomsCheckerUsername = 'guest_user'; + symptomsCheckerPassword = '123456'; + break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -107,6 +134,10 @@ class ApiConsts { rcBaseUrl = 'https://rc.hmg.com/uat/'; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; + symptomsCheckerApi = symptomsCheckerApiUAT; + symptomsCheckerUsername = 'guest_user'; + symptomsCheckerPassword = '123456'; + break; } } @@ -189,7 +220,6 @@ class ApiConsts { static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; - //WATER CONSUMPTION static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; @@ -235,6 +265,7 @@ class ApiConsts { static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New"; + // static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; // static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; @@ -806,7 +837,8 @@ var GET_CUSTOMER_INFO = "VerifyCustomer"; //Pharmacy -var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; +var GET_PHARMACY_CATEGORISE = + 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; var GET_OFFERS_CATEGORISE = 'discountcategories'; var GET_OFFERS_PRODUCTS = 'offerproducts/'; var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; @@ -834,7 +866,7 @@ var FILTERED_PRODUCTS = 'products?categoryids='; var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors"; // var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; -var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC ='Services/Patients.svc/REST/GetAllInvoices'; +var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = 'Services/Patients.svc/REST/GetAllInvoices'; var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo"; var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental"; diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 499a1c59..f8282418 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -17,6 +17,7 @@ abstract class SymptomsCheckerRepo { Future>> getUserDetails({ required String userName, required String password, + String? fileNo, }); Future>> getBodySymptomsByName({ @@ -70,8 +71,17 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { Future>> getUserDetails({ required String userName, required String password, + String? fileNo, }) async { - Map body = {"userName": userName, "password": password}; + Map body = { + "userName": userName, + "password": password, + }; + + // Add fileNo only if provided (user is logged in) + if (fileNo != null && fileNo.isNotEmpty) { + body["fileNo"] = fileNo; + } try { GenericApiModel? apiResponse; diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index 8e949533..4aba984d 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -76,6 +76,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final List> _triageEvidenceList = []; // Store triage evidence with proper format int _triageQuestionCount = 0; // Track number of triage questions answered + // For type=1 questions: single selection across all items + String? _selectedSingleItemId; // Store which item was selected + int? _selectedSingleChoiceIndex; // Store which choice was selected + // Selected risk factors tracking final Set _selectedRiskFactorIds = {}; @@ -170,12 +174,35 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return _selectedTriageChoicesByItemId[itemId]; } + /// Check if current question type is single selection (type=1) + bool get isTriageQuestionSingleSelection => currentTriageQuestion?.type == 1; + + /// Check if current question type is multi-item selection (type=2) + bool get isTriageQuestionMultiItem => currentTriageQuestion?.type == 2; + + /// For type=1 questions: check if a specific item-choice combination is selected + bool isTriageSingleOptionSelected(String itemId, int choiceIndex) { + return _selectedSingleItemId == itemId && _selectedSingleChoiceIndex == choiceIndex; + } + + /// Get selected item ID for type=1 questions + String? get selectedSingleItemId => _selectedSingleItemId; + + /// Get selected choice index for type=1 questions + int? get selectedSingleChoiceIndex => _selectedSingleChoiceIndex; + /// Check if all items in current question have been answered bool get areAllTriageItemsAnswered { if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) { return false; } + // Type 1: Single selection mode - check if any option is selected + if (isTriageQuestionSingleSelection) { + return _selectedSingleItemId != null && _selectedSingleChoiceIndex != null; + } + + // Type 2: Multi-item selection mode - check if all items have answers // Check if we have an answer for each item for (var item in currentTriageQuestion!.items!) { if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) { @@ -277,7 +304,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } } - toggleZoomOut(){ + toggleZoomOut() { if (_currentZoomScale > _minZoomScale) { _currentZoomScale = (_currentZoomScale - _zoomStep).clamp(_minZoomScale, _maxZoomScale); notifyListeners(); @@ -846,7 +873,21 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Select a choice for a specific item (for multi-item questions) void selectTriageChoiceForItem(String itemId, int choiceIndex) { - _selectedTriageChoicesByItemId[itemId] = choiceIndex; + // Type 1: Single selection mode - only one option can be selected across all items + if (isTriageQuestionSingleSelection) { + // If same option clicked again, deselect it + if (_selectedSingleItemId == itemId && _selectedSingleChoiceIndex == choiceIndex) { + _selectedSingleItemId = null; + _selectedSingleChoiceIndex = null; + } else { + // Select new option, clear previous selection + _selectedSingleItemId = itemId; + _selectedSingleChoiceIndex = choiceIndex; + } + } else { + // Type 2: Multi-item selection mode - each item can have one selected option + _selectedTriageChoicesByItemId[itemId] = choiceIndex; + } notifyListeners(); } @@ -854,6 +895,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { void resetTriageChoice() { _selectedTriageChoiceIndex = null; _selectedTriageChoicesByItemId.clear(); + _selectedSingleItemId = null; + _selectedSingleChoiceIndex = null; _triageQuestionCount++; // Increment question count notifyListeners(); } @@ -1018,12 +1061,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { Future getSymptomsUserDetails({ required String userName, required String password, + String? fileNo, Function()? onSuccess, Function(String)? onError, }) async { isBodySymptomsLoading = true; notifyListeners(); - final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password); + final result = await symptomsCheckerRepo.getUserDetails( + userName: userName, + password: password, + fileNo: fileNo, + ); result.fold( (failure) async { diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 29e1c824..137aaa09 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -1,6 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show NumberFormat; 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'; @@ -13,8 +12,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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'; @@ -56,7 +55,9 @@ class DoctorProfilePage extends StatelessWidget { doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0, isActive: viewModel.isFavouriteDoctor, onSuccess: (response) { - Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); + Utils.showToast(viewModel.isFavouriteDoctor + ? LocaleKeys.doctorAddedToFavourite.tr(context: context) + : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); // Successfully added/removed favorite - refresh the favorites list getIt.get().refreshFavouriteDoctors(); }, @@ -75,7 +76,7 @@ class DoctorProfilePage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // SizedBox(height: 24.h), + isFoldable ? SizedBox(height: 24.h) : SizedBox.shrink(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -87,18 +88,21 @@ class DoctorProfilePage extends StatelessWidget { width: 63.h, height: 63.h, fit: BoxFit.cover, - ).circle(100), + ).circle(100.r), SizedBox(width: 8.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - width: 220.h, - child: ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") - .toString() - .toText24(isBold: true), + width: isFoldable ? 250.w : 220.w, + child: + ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") + .toString() + .toText24(isBold: true), ), - (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "") + (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty + ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first + : "") .toString() .toText18(isBold: true, color: AppColors.primaryRedColor), ], @@ -137,12 +141,16 @@ class DoctorProfilePage extends StatelessWidget { children: [ Column( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate - .toString() - .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), + bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16( + isBold: true, + color: AppColors.textColor, + isUnderLine: true, + decorationColor: AppColors.textColor, + fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); @@ -158,11 +166,18 @@ class DoctorProfilePage extends StatelessWidget { SizedBox(width: 36.w), Column( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), - NumberFormat.decimalPattern().format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) - .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), + NumberFormat.decimalPattern() + .format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) + .toText16( + isBold: true, + color: AppColors.textColor, + isUnderLine: true, + decorationColor: AppColors.textColor, + fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); @@ -182,92 +197,97 @@ class DoctorProfilePage extends StatelessWidget { SizedBox(height: 16.h), LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 6.h), - (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(isBold: true, color: AppColors.greyTextColor), + (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "") + .trim() + .toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 24.h), ], ).paddingSymmetrical(24.h, 0.h), ), ), ), - isDoctorAllowedToBook ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), - child: CustomButton( - text: LocaleKeys.viewAvailableAppointments.tr(), - onPressed: () async { - bookAppointmentsViewModel.selectedDoctor.speciality = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; - bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; - bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName; - bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL; - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL; - bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription; - bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName; + isDoctorAllowedToBook + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.viewAvailableAppointments.tr(), + onPressed: () async { + bookAppointmentsViewModel.selectedDoctor.speciality = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; + bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; + bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName; + bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL; + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = + bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL; + bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription; + bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName; - LoaderBottomSheet.showLoader(); - bookAppointmentsViewModel.isLiveCareSchedule - ? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots( - isBookingForLiveCare: true, - onSuccess: (dynamic respData) async { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.pickADate.tr(), - context, - child: AppointmentCalendar(), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }) - : await bookAppointmentsViewModel.getDoctorFreeSlots( - isBookingForLiveCare: false, - onSuccess: (dynamic respData) async { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.pickADate.tr() , - context, - child: AppointmentCalendar(), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 16, - isBold: true, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.calendar, - iconColor: Colors.white, - iconSize: 20.h, - ).paddingSymmetrical(24.h, 24.h), - ) : SizedBox.shrink(), + LoaderBottomSheet.showLoader(); + bookAppointmentsViewModel.isLiveCareSchedule + ? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots( + isBookingForLiveCare: true, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.pickADate.tr(), + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + : await bookAppointmentsViewModel.getDoctorFreeSlots( + isBookingForLiveCare: false, + onSuccess: (dynamic respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.pickADate.tr(), + context, + child: AppointmentCalendar(), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 16, + isBold: true, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.calendar, + iconColor: Colors.white, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ) + : SizedBox.shrink(), ], ), ); diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 0b9d9029..544b55c8 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -319,9 +319,9 @@ class _AppointmentCalendarState extends State { if (bookAppointmentsViewModel.isWaitingAppointmentAvailable && DateUtils.isSameDay(dateStart, DateTime.now())) { dayEvents.add(TimeSlot(isoTime: LocaleKeys.waitingAppointment.tr(context: context), start: DateTime.now(), end: DateTime.now(), vidaDate: "")); } - freeSlots.forEach((v) { + for (var v in freeSlots) { if (v.start == dateStartObj) dayEvents.add(v); - }); + } selectedButtonIndex = 0; List> timeList = []; for (var i = 0; i < dayEvents.length; i++) { diff --git a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index 3d8a60ee..3f86e380 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -1,9 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; @@ -19,7 +17,6 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -27,11 +24,11 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class HealthCalculatorDetailedPage extends StatefulWidget { - HealthCalculatorsTypeEnum calculatorType; - int? clinicID; - int? calculationID; + final HealthCalculatorsTypeEnum calculatorType; + final int? clinicID; + final int? calculationID; - HealthCalculatorDetailedPage({super.key, required this.calculatorType, this.clinicID, this.calculationID}); + const HealthCalculatorDetailedPage({super.key, required this.calculatorType, this.clinicID, this.calculationID}); @override State createState() => _HealthCalculatorDetailedPageState(); @@ -52,8 +49,8 @@ class _HealthCalculatorDetailedPageState extends State createState() => _HealthCalculatorsPageState(); @@ -34,9 +33,8 @@ class _HealthCalculatorsPageState extends State { @override Widget build(BuildContext context) { - DialogService dialogService = getIt.get(); return CollapsingListView( - isLeading: Navigator.canPop(context), + isLeading: Navigator.canPop(context), title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(), child: widget.type == HealthCalConEnum.calculator ? CustomExpandableList( @@ -58,7 +56,8 @@ class _HealthCalculatorsPageState extends State { ), ], theme: ExpandableListTheme.custom( - defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), + defaultTrailingIcon: + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), ), ).paddingSymmetrical(16.w, 0.0) // ? Column( @@ -129,7 +128,8 @@ class _HealthCalculatorsPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), SizedBox(width: 12.w), Flexible( child: Column( @@ -149,7 +149,8 @@ class _HealthCalculatorsPageState extends State { ), Transform.flip( flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ), ], ).paddingAll(16.w)) @@ -166,7 +167,8 @@ class _HealthCalculatorsPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), SizedBox(width: 12.w), Flexible( child: Column( @@ -181,7 +183,8 @@ class _HealthCalculatorsPageState extends State { SizedBox(width: 12.w), Transform.flip( flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ), ], ).paddingAll(16.w)) @@ -198,7 +201,8 @@ class _HealthCalculatorsPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), SizedBox(width: 12.w), Flexible( child: Column( @@ -213,7 +217,8 @@ class _HealthCalculatorsPageState extends State { SizedBox(width: 12.w), Transform.flip( flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ), ], ).paddingAll(16.w)) @@ -247,7 +252,8 @@ class _HealthCalculatorsPageState extends State { page: HealthCalculatorDetailedPage( calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID, - calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID, + calculationID: + type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID, ), ), ); @@ -341,5 +347,14 @@ class HealthComponentModel { int? clinicID; int? calculationID; - HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type, this.clinicID, this.calculationID}); + HealthComponentModel( + {required this.title, + this.subTitle, + required this.icon, + this.iconColor, + this.bgColor, + this.textColor, + required this.type, + this.clinicID, + this.calculationID}); } diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart index 14b956e0..5a835561 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart @@ -1,14 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:provider/provider.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'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; class BloodCholesterolWidget extends StatefulWidget { final Function(dynamic result)? onChange; @@ -107,7 +107,7 @@ class _BloodCholesterolWidgetState extends State { ], ), _buildInputField( - label:LocaleKeys.mmol.tr(), + label: LocaleKeys.mmol.tr(), hint: "3.1", controller: _mmolController, focusNode: _mmolFocus, @@ -123,7 +123,7 @@ class _BloodCholesterolWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child:LocaleKeys.convertBloodcholesterolInfo.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + child: LocaleKeys.convertBloodcholesterolInfo.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), @@ -149,29 +149,26 @@ class _BloodCholesterolWidgetState extends State { isBold: true, color: AppColors.inputLabelTextColor, ), - SizedBox( - height: 40.h, - child: TextField( - controller: controller, - focusNode: focusNode, - keyboardType: const TextInputType.numberWithOptions(decimal: true), - maxLines: 1, - onChanged: onChanged, - cursorHeight: 35.h, - textAlignVertical: TextAlignVertical.center, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.zero, - isCollapsed: true, - hintText: hint, - hintStyle: const TextStyle(color: Colors.grey), - ), - style: TextStyle( - fontSize: 32.f, - fontWeight: FontWeight.bold, - color: Colors.black87, - height: 1.h, - ), + TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + maxLines: 1, + onChanged: onChanged, + cursorHeight: 35.h, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, + height: 1.h, ), ), ], diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart index dd5eb0f0..724a99eb 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart @@ -1,14 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:provider/provider.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'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; class TriglyceridesWidget extends StatefulWidget { final Function(dynamic result)? onChange; @@ -91,7 +91,6 @@ class _TriglyceridesWidgetState extends State { provider.onTriglyceridesMgdlChanged(value); }, ).paddingOnly(top: 16.h), - Row( children: [ const Expanded( @@ -111,7 +110,6 @@ class _TriglyceridesWidgetState extends State { }), ], ), - _buildInputField( label: LocaleKeys.mmol, hint: "1.7", @@ -122,9 +120,7 @@ class _TriglyceridesWidgetState extends State { provider.onTriglyceridesMmolChanged(value); }, ).paddingOnly(bottom: 16.h), - const Divider(height: 1, color: Color(0xFFEEEEEE)), - Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -135,12 +131,10 @@ class _TriglyceridesWidgetState extends State { ), SizedBox(width: 12.w), Expanded( - child: - LocaleKeys.triglycerideInfo.tr() - .toText12( - isBold: true, - color: AppColors.inputLabelTextColor, - ), + child: LocaleKeys.triglycerideInfo.tr().toText12( + isBold: true, + color: AppColors.inputLabelTextColor, + ), ), ], ).paddingSymmetrical(0.w, 16.w), @@ -165,27 +159,23 @@ class _TriglyceridesWidgetState extends State { isBold: true, color: AppColors.inputLabelTextColor, ), - SizedBox( - height: 40.h, - child: TextField( - controller: controller, - focusNode: focusNode, - keyboardType: - const TextInputType.numberWithOptions(decimal: true), - onChanged: onChanged, - cursorHeight: 35.h, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.zero, - isCollapsed: true, - hintText: hint, - hintStyle: const TextStyle(color: Colors.grey), - ), - style: TextStyle( - fontSize: 32.f, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), + TextField( + controller: controller, + focusNode: focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + onChanged: onChanged, + cursorHeight: 35.h, + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isCollapsed: true, + hintText: hint, + hintStyle: const TextStyle(color: Colors.grey), + ), + style: TextStyle( + fontSize: 32.f, + fontWeight: FontWeight.bold, + color: Colors.black87, ), ), ], diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 9d7f78e8..840a24b6 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -46,13 +47,13 @@ class _OrganSelectorPageState extends State { Future _checkAndShowTutorial() async { // final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false; // if (!hasSeenTutorial) { - // Show tutorial after a short delay to ensure the screen is fully built - await Future.delayed(const Duration(milliseconds: 500)); - if (mounted) { - setState(() { - _showTutorial = true; - }); - } + // Show tutorial after a short delay to ensure the screen is fully built + await Future.delayed(const Duration(milliseconds: 500)); + if (mounted) { + setState(() { + _showTutorial = true; + }); + } // } } @@ -74,12 +75,17 @@ class _OrganSelectorPageState extends State { loadingText: LocaleKeys.pleaseWait.tr(context: context), ); - final String userName = 'guest_user'; - final String password = '123456'; + // Get fileNo if user is logged in + String? fileNo; + if (_appState.isAuthenticated) { + final user = _appState.getAuthenticatedUser(); + fileNo = user?.patientId.toString(); + } await viewModel.getSymptomsUserDetails( - userName: userName, - password: password, + userName: ApiConsts.symptomsCheckerUsername, + password: ApiConsts.symptomsCheckerPassword, + fileNo: fileNo, onSuccess: () { LoaderBottomSheet.hideLoader(); context.navigateWithName(AppRoutes.symptomsSelectorPage); diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 13715c71..a94222b6 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -175,20 +175,44 @@ class _TriagePageState extends State { return; } - // Collect all evidence from all items - for (var item in currentQuestion.items!) { - final itemId = item.id ?? ""; - if (itemId.isEmpty) continue; - - final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId); - if (selectedChoiceIndex == null) continue; - - if (item.choices != null && selectedChoiceIndex < item.choices!.length) { - final selectedChoice = item.choices![selectedChoiceIndex]; - final choiceId = selectedChoice.id ?? ""; - - if (choiceId.isNotEmpty) { - viewModel.addTriageEvidence(itemId, choiceId); + // Collect evidence based on question type + if (viewModel.isTriageQuestionSingleSelection) { + // Type 1: Single selection - only one evidence entry + final selectedItemId = viewModel.selectedSingleItemId; + final selectedChoiceIndex = viewModel.selectedSingleChoiceIndex; + + if (selectedItemId != null && selectedChoiceIndex != null) { + // Find the item and choice + for (var item in currentQuestion.items!) { + if (item.id == selectedItemId) { + if (item.choices != null && selectedChoiceIndex < item.choices!.length) { + final selectedChoice = item.choices![selectedChoiceIndex]; + final choiceId = selectedChoice.id ?? ""; + + if (choiceId.isNotEmpty) { + viewModel.addTriageEvidence(selectedItemId, choiceId); + } + } + break; + } + } + } + } else { + // Type 2: Multi-item selection - collect evidence from all items + for (var item in currentQuestion.items!) { + final itemId = item.id ?? ""; + if (itemId.isEmpty) continue; + + final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId); + if (selectedChoiceIndex == null) continue; + + if (item.choices != null && selectedChoiceIndex < item.choices!.length) { + final selectedChoice = item.choices![selectedChoiceIndex]; + final choiceId = selectedChoice.id ?? ""; + + if (choiceId.isNotEmpty) { + viewModel.addTriageEvidence(itemId, choiceId); + } } } } @@ -390,7 +414,10 @@ class _TriagePageState extends State { SizedBox(height: 8.h), // Choices for this item ...List.generate(choices.length, (choiceIndex) { - bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex; + // Check selection based on question type + bool selected = viewModel.isTriageQuestionSingleSelection + ? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex) + : viewModel.getTriageChoiceForItem(itemId) == choiceIndex; return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); }), @@ -468,18 +495,12 @@ class _TriagePageState extends State { text: TextSpan( text: "${LocaleKeys.possibleSymptom.tr(context: context)} ", style: TextStyle( - color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), + color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), children: [ TextSpan( text: suggestedCondition, - style: TextStyle( - color: AppColors.textColor, - fontWeight: FontWeight.w600, - - fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), - - + color: AppColors.textColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), ), ], ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart index 28de7894..42e017ae 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -53,9 +53,10 @@ class HeightSelectionPage extends StatelessWidget { child: Text( 'CM', style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 14.f, - color: viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), + fontFamily: "Poppins"), ), ), ), @@ -74,9 +75,10 @@ class HeightSelectionPage extends StatelessWidget { child: Text( 'FT', style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 14.f, - color: !viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), + fontWeight: FontWeight.w700, + fontSize: 14.f, + color: !viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), + fontFamily: "Poppins"), ), ), ), @@ -128,10 +130,7 @@ class HeightSelectionPage extends StatelessWidget { TextSpan( text: viewModel.isHeightCm ? viewModel.selectedHeight?.round().toString() : viewModel.selectedHeight?.toStringAsFixed(1), - style: TextStyle( - fontSize: 90.f, - color: AppColors.textColor, - height: 1, fontFamily: "Poppins"), + style: TextStyle(fontSize: 90.f, color: AppColors.textColor, height: 1, fontFamily: "Poppins"), ), TextSpan( text: viewModel.isHeightCm ? 'cm' : 'ft', @@ -142,7 +141,7 @@ class HeightSelectionPage extends StatelessWidget { ), ], ), - ).paddingOnly(bottom: 100.h, left: 20.w); + ).paddingOnly(bottom: 100.h, left: isFoldable ? 40.w : 20.w); }, ), ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart index 6695bbd4..d95c6c82 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart @@ -131,12 +131,11 @@ class _HeightScaleState extends State { child: Text( widget.isCm ? height.round().toString() : height.toStringAsFixed(1), style: TextStyle( - fontSize: 11.f, - color: AppColors.greyTextColor, + fontSize: 11.f, + color: AppColors.greyTextColor, fontWeight: FontWeight.w600, - height: 1, - fontFamily: "Poppins" - ), + height: isFoldable ? 0.5 : 1, + fontFamily: "Poppins"), textAlign: TextAlign.right, ), ) diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index 4d2034ba..41428a6e 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -24,8 +25,6 @@ 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:provider/provider.dart'; -import 'dart:ui' as ui; - class AncillaryOrderDetailsList extends StatefulWidget { final int appointmentNoVida; final int orderNo; @@ -653,13 +652,15 @@ class _AncillaryOrderDetailsListState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( - width: 200.h, + width: isFoldable ? 220.w : 200.w, child: Utils.getPaymentMethods(), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.getPaymentAmountWithSymbol(NumberFormat.decimalPattern().format(_getTotalAmount()).toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, isSaudiCurrency: true), + Utils.getPaymentAmountWithSymbol( + NumberFormat.decimalPattern().format(_getTotalAmount()).toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, + isSaudiCurrency: true), ], ), ], @@ -695,40 +696,5 @@ class _AncillaryOrderDetailsListState extends State { ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), ); - - // Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // SizedBox(height: 16.h), - // _buildSummarySection(orderData), - // SizedBox(height: 16.h), - // CustomButton( - // borderWidth: 0, - // backgroundColor: AppColors.infoLightColor, - // text: "Proceed to Payment".needTranslation, - // onPressed: () { - // // Navigate to payment page with selected procedures - // Navigator.of(context).push( - // CustomPageRoute( - // page: AncillaryOrderPaymentPage( - // appointmentNoVida: widget.appointmentNoVida, - // orderNo: widget.orderNo, - // projectID: widget.projectID, - // selectedProcedures: selectedProcedures, - // totalAmount: _getTotalAmount(), - // appointmentDate: orderData.appointmentDate, - // ), - // ), - // ); - // }, - // isDisabled: !isButtonEnabled, - // textColor: AppColors.whiteColor, - // borderRadius: 12.r, - // borderColor: Colors.transparent, - // padding: EdgeInsets.symmetric(vertical: 16.h), - // ), - // SizedBox(height: 22.h), - // ], - // ).paddingSymmetrical(24.w, 0); } } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 805fa677..c3ebe4ba 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -115,7 +115,8 @@ class CollapsingListView extends StatelessWidget { margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), child: Transform.flip( flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), + child: Utils.buildSvgWithAssets( + icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), ), ).onPress(() { if (leadingCallback != null) { @@ -288,7 +289,8 @@ class _ScrollAnimatedTitleState extends State { return Container( // height: (widget.preferredSize.height - _fontSize / 2).h, height: 56.h, - alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), + alignment: + isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), child: Row( spacing: 4.h, @@ -304,19 +306,38 @@ class _ScrollAnimatedTitleState extends State { ), ).expanded, ...[ - if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), - if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), - if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), - if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!), + if (widget.logout != null) + actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) + actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) + actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon) + .onPress(widget.history!), + if (widget.instructions != null) + actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) + actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon) + .onPress(widget.requests!), + if (widget.sendEmail != null) + actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), + if (widget.doctorResponse != null) + actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon) + .onPress(widget.doctorResponse!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), - if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!), - if (widget.downloadReport != null) actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download).onPress(widget.downloadReport!), - if (widget.viewImage != null) actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download).onPress(widget.viewImage!), - if (widget.location != null) actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), - if (widget.downloadInvoice != null) actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download).onPress(widget.downloadInvoice!), + if (widget.aiOverview != null) + actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true) + .onPress(widget.aiOverview!), + if (widget.downloadReport != null) + actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download) + .onPress(widget.downloadReport!), + if (widget.viewImage != null) + actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download) + .onPress(widget.viewImage!), + if (widget.location != null) + actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), + if (widget.downloadInvoice != null) + actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download) + .onPress(widget.downloadInvoice!), if (widget.trailing != null) widget.trailing!, ] ], @@ -330,9 +351,10 @@ class _ScrollAnimatedTitleState extends State { duration: Duration(milliseconds: 150), child: Center( child: Container( - height: 40.h, + height: isFoldable ? 50.h : 40.h, padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), child: ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), @@ -365,7 +387,7 @@ class _ScrollAnimatedTitleState extends State { : AnimatedSize( duration: Duration(milliseconds: 150), child: Container( - height: 40.h, + height: isFoldable ? 50.h : 40.h, padding: EdgeInsets.all(8.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.secondaryLightRedColor, From da904d8183d97eeae2b3b6078d630e2e21e50029 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 28 Apr 2026 14:50:16 +0300 Subject: [PATCH 07/11] updates --- android/app/build.gradle.kts | 8 ++++---- ios/Runner/AppDelegate.swift | 8 +++----- .../livecare/immediate_livecare_pending_request_page.dart | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9dd449c6..c1ccc0b9 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -175,11 +175,11 @@ dependencies { implementation("androidx.navigation:navigation-ui-ktx:2.9.0") implementation("androidx.activity:activity-ktx:1.10.1") -// val room_version = "2.6.1" -// implementation("androidx.room:room-runtime:$room_version") -// annotationProcessor("androidx.room:room-compiler:$room_version") + val room_version = "2.6.1" + implementation("androidx.room:room-runtime:$room_version") + annotationProcessor("androidx.room:room-compiler:$room_version") -// implementation("net.zetetic:android-database-sqlcipher:4.5.4") + implementation("net.zetetic:android-database-sqlcipher:4.5.4") implementation("com.intuit.ssp:ssp-android:1.1.0") implementation("com.intuit.sdp:sdp-android:1.1.0") diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 308891a7..dc11bbed 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -16,11 +16,9 @@ import GoogleMaps return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func initializePlatformChannels(){ -// if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground -// -//// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) -// -// } + if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground + HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) + } } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ // Messaging.messaging().apnsToken = deviceToken diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart index 1c4f63e8..b8e58fa6 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart @@ -230,7 +230,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State().chatRequestID}"); - chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${getIt.get().chatRequestID}"; + chatURL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=${getIt.get().chatRequestID}"; debugPrint("Chat URL: $chatURL"); Uri uri = Uri.parse(chatURL); launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); From 34cc16c24f442e2a1a821e91af19e03f1e43ee0e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 28 Apr 2026 16:02:44 +0300 Subject: [PATCH 08/11] Updates --- assets/langs/ar-SA.json | 3 ++- assets/langs/en-US.json | 3 ++- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- lib/core/utils/size_utils.dart | 9 +++++++-- lib/features/hmg_services/hmg_services_repo.dart | 10 +++++----- lib/generated/locale_keys.g.dart | 1 + .../appointments/widgets/appointment_card.dart | 2 +- .../history/widget/RequestStatus.dart | 2 +- lib/presentation/home/landing_page.dart | 4 ++-- .../insurance/widgets/patient_insurance_card.dart | 5 +++-- 11 files changed, 26 insertions(+), 17 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f0e830f7..0a8ec974 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1841,5 +1841,6 @@ "thisAboveInfoPrescription": "ุชู… ุชุญู„ูŠู„ ุงู„ูˆุตูุฉ ุงู„ุทุจูŠุฉ ู‡ุฐู‡ ุจูˆุงุณุทุฉ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠุŒ ูˆู‡ูŠ ู„ุง ุชูุนุฏู‘ ู†ุตูŠุญุฉ ุทุจูŠุฉ. ุงุณุชุดุฑ ุทุจูŠุจูƒ ุงู„ู…ุฎุชุต ู„ู„ุชุดุฎูŠุต ูˆุงู„ุนู„ุงุฌ.", "liveCareNotificationPermissionsMessage": "ูŠุชุทู„ุจ ู„ุงูŠู ูƒูŠุฑ ุฃุฐูˆู†ุงุช ุงู„ุฅุดุนุงุฑุงุชุŒ ูŠุฑุฌู‰ ุงู„ุณู…ุงุญ ุจู‡ุฐู‡ ุงู„ุฃุฐูˆู†ุงุช ู„ู„ู…ุชุงุจุนุฉ.", "weatherIndicators": "ุทู‚ุณ", - "submitRating": "ุฅุฑุณุงู„ ุงู„ุชู‚ูŠูŠู…" + "submitRating": "ุฅุฑุณุงู„ ุงู„ุชู‚ูŠูŠู…", + "completedPrescriptionOrder": "ุชู…ุช ุงู„ุฎุฏู…ุฉ" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index e17992f9..9b969f79 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1831,7 +1831,8 @@ "thisAboveInfoPrescription": "This prescription was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.", "liveCareNotificationPermissionsMessage": "LiveCare requires Notifications permission, Please allow to proceed.", "weatherIndicators": "Weather", - "submitRating": "Submit" + "submitRating": "Submit", + "completedPrescriptionOrder": "Completed" } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 5172f77b..896fe4a4 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -243,7 +243,7 @@ class ApiClientImp implements ApiClient { http.Response response; try { response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); - debugPrint("response: ${response.body}", wrapWidth: 2048); + // debugPrint("response: ${response.body}", wrapWidth: 2048); } on SocketException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index db4d6a8c..ab94fecb 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -282,7 +282,7 @@ class ApiConsts { static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; // ************ static values for Api **************** - static final double appVersionID = 20.9; + static final double appVersionID = 21.0; // static final double appVersionID = 50.7; static final int appChannelId = 3; diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index c7f3fa06..7f4013ed 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -43,8 +43,13 @@ extension ResponsiveExtension on num { // Enhanced clamping for different device types double clamp; if (SizeUtils.deviceType == DeviceType.tablet || _isFoldable) { - // More conservative scaling for tablets and foldables - clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.6 : 1.4; + if (SizeUtils.deviceType != DeviceType.tablet && _isFoldable) { + // clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.4 : 1.4; + clamp = 1.1; + } else { + // More conservative scaling for tablets and foldables + clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.6 : 1.4; + } } else { // Original logic for phones clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2; diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index b70e024c..1484c91f 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -944,11 +944,11 @@ class HmgServicesRepoImp implements HmgServicesRepo { final vitalSign = VitalSignResModel.fromJson(vitalSignJson); // Debug logging for blood pressure - print('=== Repository Blood Pressure Check ==='); - print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}'); - print('bloodPressureLower: ${vitalSign.bloodPressureLower}'); - print('weightKg: ${vitalSign.weightKg}'); - print('heightCm: ${vitalSign.heightCm}'); + // print('=== Repository Blood Pressure Check ==='); + // print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}'); + // print('bloodPressureLower: ${vitalSign.bloodPressureLower}'); + // print('weightKg: ${vitalSign.weightKg}'); + // print('heightCm: ${vitalSign.heightCm}'); // Check if the record has at least one valid vital sign measurement final hasValidWeight = _isValidValue(vitalSign.weightKg); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index d5410585..5ef765b6 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1834,5 +1834,6 @@ abstract class LocaleKeys { static const liveCareNotificationPermissionsMessage = 'liveCareNotificationPermissionsMessage'; static const weatherIndicators = 'weatherIndicators'; static const submitRating = 'submitRating'; + static const completedPrescriptionOrder = 'completedPrescriptionOrder'; } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 000003f1..9b01ce8b 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -251,7 +251,7 @@ class _AppointmentCardState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 14.h, height: 14.h, iconColor: AppColors.ratingColorYellow), SizedBox(height: 2.h), (isFoldable || isTablet) ? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}" diff --git a/lib/presentation/emergency_services/history/widget/RequestStatus.dart b/lib/presentation/emergency_services/history/widget/RequestStatus.dart index 849565a7..21a4312b 100644 --- a/lib/presentation/emergency_services/history/widget/RequestStatus.dart +++ b/lib/presentation/emergency_services/history/widget/RequestStatus.dart @@ -24,7 +24,7 @@ class RequestStatus extends StatelessWidget { case 2: //processing return LocaleKeys.underProcessing.tr(); case 3: - return LocaleKeys.completed.tr(); + return LocaleKeys.completedPrescriptionOrder.tr(); case 4: //cancel case 6: case 7: diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 780c4d01..4346d8b5 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -448,7 +448,7 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h) : isTablet ? SizedBox( - height: isFoldable ? 290.h : 255.h, + height: isTablet ? 290.h : 255.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: 3, @@ -456,7 +456,7 @@ class _LandingPageState extends State { padding: EdgeInsets.only(left: 16.h, right: 16.h), itemBuilder: (context, index) { return SizedBox( - height: isFoldable ? 290.h : 255.h, + height: isTablet ? 290.h : 255.h, width: 250.w, child: getIndexSwiperCard(index), ); diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 3734bd95..6fab990c 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -162,10 +162,11 @@ class PatientInsuranceCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, // labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", - richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true), + richText: + "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isBold: true), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), - AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)), + AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isBold: true)), ], ), SizedBox(height: 10.h), From c9406aa66d87bd72f18caa7ed9375b6f9ce5249b Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 28 Apr 2026 17:03:31 +0300 Subject: [PATCH 09/11] Updates & http client enhancements --- lib/core/api/api_client.dart | 18 +- lib/core/api/http_client_manager.dart | 224 ++++++++++++++++++ lib/core/dependencies.dart | 4 + lib/main.dart | 5 + .../lab_result_via_clinic/LabResultList.dart | 5 +- lib/services/app_lifecycle_service.dart | 78 ++++++ 6 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 lib/core/api/http_client_manager.dart create mode 100644 lib/services/app_lifecycle_service.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 896fe4a4..64074ff8 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api/http_client_manager.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:http/http.dart' as http; @@ -87,10 +89,14 @@ abstract class ApiClient { class ApiClientImp implements ApiClient { final _analytics = getIt(); final AppState _appState; + final HttpClientManager _httpClient; ApiClientImp({ required AppState appState, - }) : _appState = appState; + HttpClientManager? httpClient, + }) : _appState = appState, + _httpClient = httpClient ?? + HttpClientManager(lifecycleService: getIt()); @override post( @@ -242,7 +248,11 @@ class ApiClientImp implements ApiClient { http.Response response; try { - response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); + response = await _httpClient.post( + uri: Uri.parse(url.trim()), + body: requestBody, + headers: headers, + ); // debugPrint("response: ${response.body}", wrapWidth: 2048); } on SocketException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); @@ -445,8 +455,8 @@ class ApiClientImp implements ApiClient { if (await Utils.checkConnection(bypassConnectionCheck: true)) { http.Response response; try { - response = await http.get( - Uri.parse(url.trim()), + response = await _httpClient.get( + uri: Uri.parse(url.trim()), headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'}, ); } on SocketException catch (e) { diff --git a/lib/core/api/http_client_manager.dart b/lib/core/api/http_client_manager.dart new file mode 100644 index 00000000..d264283d --- /dev/null +++ b/lib/core/api/http_client_manager.dart @@ -0,0 +1,224 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; +import 'package:http/http.dart' as http; + +/// HTTP Client Manager with automatic retry logic for transient network errors +/// Handles background/foreground transitions gracefully +class HttpClientManager { + final http.Client _client; + final AppLifecycleService _lifecycleService; + + static const Duration _defaultTimeout = Duration(seconds: 30); + static const int _maxRetries = 2; + + HttpClientManager({ + required AppLifecycleService lifecycleService, + http.Client? client, + }) : _lifecycleService = lifecycleService, + _client = client ?? http.Client(); + + /// Execute POST request with retry logic + Future post({ + required Uri uri, + Map? headers, + Object? body, + Duration? timeout, + int maxRetries = _maxRetries, + }) async { + return _executeWithRetry( + () => _client + .post(uri, headers: headers, body: body) + .timeout(timeout ?? _defaultTimeout), + endpoint: uri.pathSegments.isNotEmpty ? uri.pathSegments.last : uri.toString(), + maxRetries: maxRetries, + ); + } + + /// Execute GET request with retry logic + Future get({ + required Uri uri, + Map? headers, + Duration? timeout, + int maxRetries = _maxRetries, + }) async { + return _executeWithRetry( + () => _client + .get(uri, headers: headers) + .timeout(timeout ?? _defaultTimeout), + endpoint: uri.pathSegments.isNotEmpty ? uri.pathSegments.last : uri.toString(), + maxRetries: maxRetries, + ); + } + + /// Core retry logic for HTTP requests + Future _executeWithRetry( + Future Function() request, { + required String endpoint, + int maxRetries = _maxRetries, + }) async { + int attempt = 0; + + while (attempt <= maxRetries) { + try { + // Wait for app to be in foreground before attempting request + await _waitForAppForeground(); + + // Check if app just came from background + if (_lifecycleService.lastResumedTime != null && attempt == 0) { + final timeSinceResume = DateTime.now().difference(_lifecycleService.lastResumedTime!); + if (timeSinceResume.inSeconds < 5) { + debugPrint('๐Ÿ”„ App recently resumed (${timeSinceResume.inSeconds}s ago), adding small delay before request...'); + await Future.delayed(const Duration(milliseconds: 500)); + } + } + + return await request(); + + } on SocketException catch (e) { + if (attempt >= maxRetries) { + // If app is in background, wait for it to come back before throwing + if (_lifecycleService.isAppInBackground) { + debugPrint('โธ๏ธ App in background, waiting to resume before final error for $endpoint'); + await _waitForAppForeground(); + } + rethrow; + } + await _handleRetry( + attempt: attempt++, + errorType: 'SocketException', + endpoint: endpoint, + errorDetails: e.message, + ); + } on http.ClientException catch (e) { + if (attempt >= maxRetries) { + // If app is in background, wait for it to come back before throwing + if (_lifecycleService.isAppInBackground) { + debugPrint('โธ๏ธ App in background, waiting to resume before final error for $endpoint'); + await _waitForAppForeground(); + } + rethrow; + } + await _handleRetry( + attempt: attempt++, + errorType: 'ClientException', + endpoint: endpoint, + errorDetails: e.message, + ); + } on TimeoutException catch (e) { + // For timeout, only retry once + if (attempt >= 1) { + // If app is in background, wait for it to come back before throwing + if (_lifecycleService.isAppInBackground) { + debugPrint('โธ๏ธ App in background, waiting to resume before final error for $endpoint'); + await _waitForAppForeground(); + } + rethrow; + } + await _handleRetry( + attempt: attempt++, + errorType: 'TimeoutException', + endpoint: endpoint, + errorDetails: e.message ?? 'Request timed out', + ); + } + } + + throw Exception('Max retries exceeded for $endpoint'); + } + + /// Wait for app to be in foreground before proceeding + Future _waitForAppForeground() async { + if (!_lifecycleService.isAppInBackground) { + return; // App is already in foreground + } + + debugPrint('โธ๏ธ App is in background, pausing request until app resumes...'); + + // Wait for app to resume with a timeout + final completer = Completer(); + late StreamSubscription subscription; + + // Set up a listener for app state changes + subscription = _lifecycleService.appStateStream.listen((state) { + if (state == AppLifecycleState.resumed) { + if (!completer.isCompleted) { + debugPrint('โœ… App resumed, continuing with request'); + completer.complete(); + } + } + }); + + // Also check current state in case it changed + if (_lifecycleService.currentState == AppLifecycleState.resumed) { + if (!completer.isCompleted) { + completer.complete(); + } + } + + try { + // Wait for app to resume with 60 second timeout + await completer.future.timeout( + const Duration(seconds: 60), + onTimeout: () { + debugPrint('โš ๏ธ Timeout waiting for app to resume'); + }, + ); + } finally { + await subscription.cancel(); + } + + // Add small delay after resume to let things stabilize + await Future.delayed(const Duration(milliseconds: 300)); + } + + /// Handle retry delay with exponential backoff and validation + Future _handleRetry({ + required int attempt, + required String errorType, + required String endpoint, + required String errorDetails, + }) async { + // If app went to background during the error, wait for it to come back + if (_lifecycleService.isAppInBackground) { + debugPrint('โธ๏ธ Error occurred while app in background, waiting for app to resume...'); + await _waitForAppForeground(); + } + + // Check network connectivity before retrying + final hasConnection = await Utils.checkConnection(bypassConnectionCheck: true); + if (!hasConnection) { + debugPrint('โš ๏ธ No network connection available, waiting before retry...'); + // Wait a bit and check again instead of immediately throwing + await Future.delayed(const Duration(seconds: 2)); + final recheckConnection = await Utils.checkConnection(bypassConnectionCheck: true); + if (!recheckConnection) { + throw SocketException('No network connection available for retry'); + } + } + + // Exponential backoff: 300ms, 900ms, 2700ms + final delay = Duration(milliseconds: 300 * (1 << attempt)); + + debugPrint( + '๐Ÿ”„ Retry attempt ${attempt + 1}/$_maxRetries for $endpoint\n' + ' Error: $errorType - $errorDetails\n' + ' Waiting: ${delay.inMilliseconds}ms\n' + ' App State: ${_lifecycleService.currentState}' + ); + + await Future.delayed(delay); + } + + /// Close the HTTP client + void dispose() { + _client.close(); + } +} + + + + + diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 3b9eefc0..1de302b7 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -68,6 +68,7 @@ import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -140,6 +141,9 @@ class AppDependencies { loggerService: getIt(), )); + // App Lifecycle Service - must be registered before ApiClient + getIt.registerLazySingleton(() => AppLifecycleService()); + getIt.registerLazySingleton(() => ApiClientImp(appState: getIt())); getIt.registerLazySingleton( () => LocalAuthService(loggerService: getIt(), localAuth: getIt()), diff --git a/lib/main.dart b/lib/main.dart index 4aa20551..5999a04e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -49,6 +49,8 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_mo import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; @@ -123,6 +125,9 @@ Future callInitializations() async { HttpOverrides.global = MyHttpOverrides(); await callAppStateInitializations(); + // Initialize App Lifecycle Service to monitor background/foreground transitions + getIt.get().initialize(); + // Restore persisted dark-mode preference before the first frame. getIt.get().loadDarkMode(); } diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart index 7bb0cf32..8aedd8a5 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultList.dart @@ -20,8 +20,9 @@ class LabResultList extends StatelessWidget { selector: (_, model) => model.mainLabResultsByHospitals, builder: (__, list, ___) { if (list.isEmpty && context.read().labSpecialResult.isEmpty) { - return Utils.getNoDataWidget(context, - noDataText: LocaleKeys.noLabResults.tr(context: context)); + // return Utils.getNoDataWidget(context, + // noDataText: LocaleKeys.noLabResults.tr(context: context)); + return Container(); } else { return ListView.builder( physics: NeverScrollableScrollPhysics(), diff --git a/lib/services/app_lifecycle_service.dart b/lib/services/app_lifecycle_service.dart new file mode 100644 index 00000000..4a0dd947 --- /dev/null +++ b/lib/services/app_lifecycle_service.dart @@ -0,0 +1,78 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +/// Service to monitor and track app lifecycle state changes +/// This helps detect when app goes to background/foreground +class AppLifecycleService with WidgetsBindingObserver { + final _stateController = StreamController.broadcast(); + + /// Stream of app lifecycle state changes + Stream get appStateStream => _stateController.stream; + + AppLifecycleState _currentState = AppLifecycleState.resumed; + + /// Current app lifecycle state + AppLifecycleState get currentState => _currentState; + + /// Check if app is currently active/visible + bool get isAppActive => _currentState == AppLifecycleState.resumed; + + /// Check if app is in background or inactive + bool get isAppInBackground => + _currentState == AppLifecycleState.paused || + _currentState == AppLifecycleState.inactive || + _currentState == AppLifecycleState.detached; + + /// Last time app was resumed + DateTime? _lastResumedTime; + DateTime? get lastResumedTime => _lastResumedTime; + + /// Last time app went to background + DateTime? _lastPausedTime; + DateTime? get lastPausedTime => _lastPausedTime; + + /// Duration app has been in current state + Duration get timeInCurrentState { + final referenceTime = _currentState == AppLifecycleState.resumed + ? _lastResumedTime + : _lastPausedTime; + + if (referenceTime == null) return Duration.zero; + return DateTime.now().difference(referenceTime); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _currentState = state; + + // Track timing + if (state == AppLifecycleState.resumed) { + _lastResumedTime = DateTime.now(); + debugPrint('๐Ÿ“ฑ App RESUMED'); + } else if (state == AppLifecycleState.paused) { + _lastPausedTime = DateTime.now(); + debugPrint('๐Ÿ“ฑ App PAUSED/BACKGROUNDED'); + } else if (state == AppLifecycleState.inactive) { + debugPrint('๐Ÿ“ฑ App INACTIVE'); + } else if (state == AppLifecycleState.detached) { + debugPrint('๐Ÿ“ฑ App DETACHED'); + } + + _stateController.add(state); + } + + /// Initialize the lifecycle observer + void initialize() { + WidgetsBinding.instance.addObserver(this); + debugPrint('๐Ÿ“ฑ AppLifecycleService initialized'); + } + + /// Clean up resources + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _stateController.close(); + debugPrint('๐Ÿ“ฑ AppLifecycleService disposed'); + } +} + + From 03ce3b617d4265261924b7396750197232d09a43 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 28 Apr 2026 17:06:24 +0300 Subject: [PATCH 10/11] Completed the SymptomsChecker Changes --- lib/core/api_consts.dart | 1 + lib/core/utils/utils.dart | 65 ++--- .../schedule_appointment_request_model.dart | 48 ++++ .../schedule_appointment_response_model.dart | 32 +++ .../symptoms_checker_repo.dart | 65 ++++- .../symptoms_checker_view_model.dart | 164 +++++++++++- .../appointment_details_page.dart | 5 +- .../authentication/saved_login_screen.dart | 242 +++++++++--------- .../immediate_livecare_payment_details.dart | 72 ++++-- .../review_appointment_page.dart | 34 +++ .../home/widgets/habib_wallet_card.dart | 42 +-- .../possible_conditions_screen.dart | 20 +- .../symptoms_selector_screen.dart | 66 ++++- .../symptoms_checker/triage_screen.dart | 83 ++++-- 14 files changed, 712 insertions(+), 227 deletions(-) create mode 100644 lib/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart create mode 100644 lib/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index db4d6a8c..c01b298a 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -212,6 +212,7 @@ class ApiConsts { static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition='; + static final String scheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment'; //E-REFERRAL SERVICES static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 615641b5..1cf8094d 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -61,7 +61,8 @@ class Utils { "ProjectOutSA": false, "UsingInDoctorApp": false, "IsHMC": false - },{ + }, + { "Desciption": "Jeddah Fayhaa Hospital", "DesciptionN": "ู…ุณุชุดูู‰ ุฌุฏุฉ ุงู„ููŠุญุงุก", "ID": 3, // Campus ID @@ -153,10 +154,10 @@ class Utils { static String getDayMonthYearDateFormatted(DateTime? dateTime) { if (dateTime == null) return ""; return - // appState.isArabic() - // ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" - // : - "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; + // appState.isArabic() + // ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" + // : + "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; } /// get month by @@ -539,26 +540,26 @@ class Utils { ), ], ) - : showOkButton? - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.ok.tr(), - onPressed: () async { - if (onConfirmTap != null) { - onConfirmTap(); - } - }, - backgroundColor: AppColors.bgGreenColor, - borderColor: AppColors.bgGreenColor, - textColor: Colors.white, - // icon: AppAssets.confirm, - ), - ), - ], - ) - :SizedBox.shrink(), + : showOkButton + ? Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.ok.tr(), + onPressed: () async { + if (onConfirmTap != null) { + onConfirmTap(); + } + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + // icon: AppAssets.confirm, + ), + ), + ], + ) + : SizedBox.shrink(), ], ).center; } @@ -833,12 +834,16 @@ class Utils { final iconH = height ?? 24.h; final iconW = width ?? 24.w; return Container( - width: iconW, height: iconH, + width: iconW, + height: iconH, decoration: BoxDecoration( border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null, borderRadius: borderRadius != null ? BorderRadius.circular(borderRadius ?? 12.r) : null, - image: DecorationImage(image: AssetImage(icon,), fit: fit) - ), + image: DecorationImage( + image: AssetImage( + icon, + ), + fit: fit)), ); } @@ -869,9 +874,8 @@ class Utils { static Widget getPaymentMethods() { return Row( + spacing: 6.w, mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 5.w, children: [ Image.asset(AppAssets.mada, width: 35.h, height: 35.h), Image.asset( @@ -1025,7 +1029,6 @@ class Utils { isHMC: hospital.isHMC); } - static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) { if (item == null) return null; return HospitalsModel( diff --git a/lib/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart b/lib/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart new file mode 100644 index 00000000..7d8f9a25 --- /dev/null +++ b/lib/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart @@ -0,0 +1,48 @@ +class ScheduleAppointmentRequestModel { + final String generalId; + final String fileNo; + final String appointmentNo; + final String doctorId; + final String appointmentDate; + final String mobileNumber; + final int projectId; + final int clinicId; + + ScheduleAppointmentRequestModel({ + required this.generalId, + required this.fileNo, + required this.appointmentNo, + required this.doctorId, + required this.appointmentDate, + required this.mobileNumber, + required this.projectId, + required this.clinicId, + }); + + Map toJson() { + return { + 'generalId': generalId, + 'fileNo': fileNo, + 'appointmentNo': appointmentNo, + 'doctorId': doctorId, + 'appointmentDate': appointmentDate, + 'mobileNumber': mobileNumber, + 'projectId': projectId, + 'clinicId': clinicId, + }; + } + + factory ScheduleAppointmentRequestModel.fromJson(Map json) { + return ScheduleAppointmentRequestModel( + generalId: json['generalId'] ?? '', + fileNo: json['fileNo'] ?? '', + appointmentNo: json['appointmentNo'] ?? '', + doctorId: json['doctorId'] ?? '', + appointmentDate: json['appointmentDate'] ?? '', + mobileNumber: json['mobileNumber'] ?? '', + projectId: json['projectId'] ?? 0, + clinicId: json['clinicId'] ?? 0, + ); + } +} + diff --git a/lib/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart b/lib/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart new file mode 100644 index 00000000..8511e18a --- /dev/null +++ b/lib/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart @@ -0,0 +1,32 @@ +class ScheduleAppointmentResponseModel { + final bool? success; + final String? message; + final String? appointmentId; + final dynamic data; + + ScheduleAppointmentResponseModel({ + this.success, + this.message, + this.appointmentId, + this.data, + }); + + factory ScheduleAppointmentResponseModel.fromJson(Map json) { + return ScheduleAppointmentResponseModel( + success: json['success'], + message: json['message'], + appointmentId: json['appointmentId'], + data: json['data'], + ); + } + + Map toJson() { + return { + 'success': success, + 'message': message, + 'appointmentId': appointmentId, + 'data': data, + }; + } +} + diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index f8282418..824a01c4 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -6,9 +6,11 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -59,6 +61,11 @@ abstract class SymptomsCheckerRepo { required String language, required String userSessionToken, }); + + Future>> saveAppointmentDetailsForSymptomsChecker({ + required ScheduleAppointmentRequestModel request, + required String userSessionToken, + }); } class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { @@ -419,7 +426,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { 'Content-Type': 'application/json', 'Authorization': 'Bearer $userSessionToken', }; - Map body = {}; try { GenericApiModel>? apiResponse; @@ -466,4 +472,61 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> saveAppointmentDetailsForSymptomsChecker({ + required ScheduleAppointmentRequestModel request, + required String userSessionToken, + }) async { + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', + }; + + final body = request.toJson(); + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.scheduleAppointment, + apiHeaders: headers, + body: body, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + loggerService.logError("ScheduleAppointment API Failed: $error"); + failure = failureType ?? ServerFailure(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // Parse response if it's a string + final Map responseData = response is String ? jsonDecode(response) : response; + + ScheduleAppointmentResponseModel scheduleAppointmentResponse = ScheduleAppointmentResponseModel.fromJson(responseData); + + apiResponse = GenericApiModel( + messageStatus: messageStatus ?? 1, + statusCode: statusCode, + errorMessage: errorMessage, + data: scheduleAppointmentResponse, + ); + } catch (e, stackTrace) { + loggerService.logError("Error parsing ScheduleAppointment response: $e"); + loggerService.logError("StackTrace: $stackTrace"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e, stackTrace) { + loggerService.logError("Exception in scheduleAppointment: $e"); + loggerService.logError("StackTrace: $stackTrace"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index 4aba984d..b6154f35 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -5,9 +5,11 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; @@ -62,6 +64,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { bool isRiskFactorsLoading = false; bool isSuggestionsLoading = false; bool isTriageDiagnosisLoading = false; + bool isScheduleAppointmentLoading = false; + + // Flag to track if appointment is being booked via symptoms checker flow + bool isBookingFromSymptomsChecker = false; // API data storage - using API models directly SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel; @@ -89,6 +95,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { // Selected symptoms tracking (organId -> Set of symptom IDs) final Map> _selectedSymptomsByOrgan = {}; + // Symptom search/filter state + String _symptomSearchQuery = ''; + List _filteredOrganSymptomsResults = []; + // User Info Flow State int _userInfoCurrentPage = 0; bool _isSinglePageEditMode = false; // Track if editing single page or full flow @@ -180,9 +190,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Check if current question type is multi-item selection (type=2) bool get isTriageQuestionMultiItem => currentTriageQuestion?.type == 2; - /// For type=1 questions: check if a specific item-choice combination is selected + /// For type=1 questions: check if a specific item is selected (ignore choiceIndex) bool isTriageSingleOptionSelected(String itemId, int choiceIndex) { - return _selectedSingleItemId == itemId && _selectedSingleChoiceIndex == choiceIndex; + return _selectedSingleItemId == itemId; } /// Get selected item ID for type=1 questions @@ -191,6 +201,12 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get selected choice index for type=1 questions int? get selectedSingleChoiceIndex => _selectedSingleChoiceIndex; + /// Set flag for booking from symptoms checker + void setBookingFromSymptomsChecker(bool value) { + isBookingFromSymptomsChecker = value; + notifyListeners(); + } + /// Check if all items in current question have been answered bool get areAllTriageItemsAnswered { if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) { @@ -234,6 +250,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return bodySymptomResponse!.dataDetails!.result ?? []; } + /// Get filtered organ symptoms results based on search query + List get filteredOrganSymptomsResults { + if (_symptomSearchQuery.isEmpty) { + return organSymptomsResults; + } + return _filteredOrganSymptomsResults; + } + + /// Get current search query + String get symptomSearchQuery => _symptomSearchQuery; + + /// Get all symptoms from all organs (for search suggestions) + List get allSymptoms { + List symptoms = []; + for (var organResult in organSymptomsResults) { + if (organResult.bodySymptoms != null) { + symptoms.addAll(organResult.bodySymptoms!); + } + } + return symptoms; + } + int get totalSelectedSymptomsCount { return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length); } @@ -454,6 +492,51 @@ class SymptomsCheckerViewModel extends ChangeNotifier { notifyListeners(); } + /// Filter symptoms based on search query + void filterSymptoms(String query, {bool isArabic = false}) { + _symptomSearchQuery = query; + + if (query.isEmpty) { + _filteredOrganSymptomsResults.clear(); + notifyListeners(); + return; + } + + final lowercaseQuery = query.toLowerCase(); + _filteredOrganSymptomsResults = []; + + for (var organResult in organSymptomsResults) { + if (organResult.bodySymptoms == null || organResult.bodySymptoms!.isEmpty) { + continue; + } + + // Filter symptoms that match the query + final filteredSymptoms = organResult.bodySymptoms!.where((symptom) { + final displayName = symptom.getDisplayName(isArabic).toLowerCase(); + return displayName.contains(lowercaseQuery); + }).toList(); + + // Only add organ result if it has matching symptoms + if (filteredSymptoms.isNotEmpty) { + _filteredOrganSymptomsResults.add( + OrganSymptomResult( + name: organResult.name, + bodySymptoms: filteredSymptoms, + ), + ); + } + } + + notifyListeners(); + } + + /// Clear symptom search filter + void clearSymptomFilter() { + _symptomSearchQuery = ''; + _filteredOrganSymptomsResults.clear(); + notifyListeners(); + } + // Risk Factors Methods /// Toggle risk factor selection @@ -873,16 +956,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Select a choice for a specific item (for multi-item questions) void selectTriageChoiceForItem(String itemId, int choiceIndex) { - // Type 1: Single selection mode - only one option can be selected across all items + // Type 1: Single selection mode - only one ITEM can be selected (choice is always "Yes") if (isTriageQuestionSingleSelection) { - // If same option clicked again, deselect it - if (_selectedSingleItemId == itemId && _selectedSingleChoiceIndex == choiceIndex) { + // If same item clicked again, deselect it + if (_selectedSingleItemId == itemId) { _selectedSingleItemId = null; _selectedSingleChoiceIndex = null; } else { - // Select new option, clear previous selection + // Select new item, clear previous selection _selectedSingleItemId = itemId; - _selectedSingleChoiceIndex = choiceIndex; + // For type=1, we don't use choiceIndex from UI, we'll find "Yes" choice in the API call + _selectedSingleChoiceIndex = 0; // Placeholder, actual Yes choice will be found later } } else { // Type 2: Multi-item selection mode - each item can have one selected option @@ -932,15 +1016,20 @@ class SymptomsCheckerViewModel extends ChangeNotifier { _selectedTriageChoicesByItemId.clear(); _triageQuestionCount = 0; // Reset question count _currentZoomScale = 1.0; // Reset zoom scale + _symptomSearchQuery = ''; // Reset search query + _filteredOrganSymptomsResults.clear(); // Clear filtered results bodySymptomResponse = null; riskFactorsResponse = null; suggestionsResponse = null; triageDataDetails = null; isTriageDiagnosisLoading = false; _selectedTriageChoiceIndex = null; + _selectedSingleItemId = null; + _selectedSingleChoiceIndex = null; _isBottomSheetExpanded = false; _tooltipTimer?.cancel(); _tooltipOrganId = null; + isBookingFromSymptomsChecker = false; // Reset booking flag // Reset user info flow _userInfoCurrentPage = 0; _isSinglePageEditMode = false; @@ -1179,6 +1268,67 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } + /// Schedule appointment for symptoms checker + Future saveAppointmentDetailsForSymptomsChecker({ + required String fileNo, + required String appointmentNo, + required String doctorId, + required String appointmentDate, + required String mobileNumber, + required int projectId, + required int clinicId, + Function(ScheduleAppointmentResponseModel)? onSuccess, + Function(String)? onError, + }) async { + isScheduleAppointmentLoading = true; + notifyListeners(); + + // Import the request model at the top of the file + final request = ScheduleAppointmentRequestModel( + generalId: currentSessionId, + fileNo: fileNo, + appointmentNo: appointmentNo, + doctorId: doctorId, + appointmentDate: appointmentDate, + mobileNumber: mobileNumber, + projectId: projectId, + clinicId: clinicId, + ); + + final result = await symptomsCheckerRepo.saveAppointmentDetailsForSymptomsChecker( + request: request, + userSessionToken: currentSessionAuthToken, + ); + + result.fold( + (failure) async { + isScheduleAppointmentLoading = false; + isBookingFromSymptomsChecker = false; // Reset flag on error + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isScheduleAppointmentLoading = false; + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + isBookingFromSymptomsChecker = false; // Reset flag on success + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data!); + } + } else { + isBookingFromSymptomsChecker = false; // Reset flag on error + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Failed to schedule appointment'); + } + } + }, + ); + } + @override void dispose() { _tooltipTimer?.cancel(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7b63d87f..76790af7 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -920,10 +920,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: 200.h, - child: Utils.getPaymentMethods(), - ), + Utils.getPaymentMethods(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 6d326939..10a69711 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -19,7 +21,6 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; -import 'dart:ui' as ui; class SavedLogin extends StatefulWidget { const SavedLogin({super.key}); @@ -33,6 +34,7 @@ class _SavedLogin extends State { late AuthenticationViewModel authVm; late AppState appState; bool? isOther; + @override void initState() { authVm = context.read(); @@ -90,11 +92,11 @@ class _SavedLogin extends State { : SizedBox(), SizedBox(height: 24.h), Container( - padding: EdgeInsets.all(16.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ - BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), - ]), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ + BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), + ]), child: Column( children: [ // Last login info - show WhatsApp only if isOther AND loginType is SMS @@ -105,7 +107,9 @@ class _SavedLogin extends State { textDirection: ui.TextDirection.ltr, child: appState.getSelectDeviceByImeiRespModelElement != null ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") + ? DateUtil.getFormattedDate( + DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), + "d MMMM, y 'at' HH:mm") : '--') .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) : SizedBox(), @@ -115,10 +119,14 @@ class _SavedLogin extends State { ? Container( margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets( - icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), + icon: (isOther == true && loginType == LoginTypeEnum.sms) + ? AppAssets.whatsapp + : getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54.h, width: 54.w, - iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType.toInt == 4 ? null : AppColors.primaryRedColor)) + iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType.toInt == 4 + ? null + : AppColors.primaryRedColor)) : SizedBox(), // Main login button - for isOther with SMS, show WhatsApp, otherwise keep original login type CustomButton( @@ -126,7 +134,6 @@ class _SavedLogin extends State { ? "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}" : "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { - if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { authVm.loginWithFingerPrintFace(() {}); } else { @@ -147,7 +154,8 @@ class _SavedLogin extends State { height: 40.h, padding: EdgeInsets.symmetric(vertical: 10.h), icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(loginType.toInt), - iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType == LoginTypeEnum.whatsapp ? null : Colors.white, + iconColor: + (isOther == true && loginType == LoginTypeEnum.sms) || loginType == LoginTypeEnum.whatsapp ? null : Colors.white, ), ], ), @@ -159,120 +167,124 @@ class _SavedLogin extends State { padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( LocaleKeys.oR.tr(), - style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w600,), + style: context.dynamicTextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + ), ), ), SizedBox(height: 24.h), // OTP login button loginType.toInt != 1 - ? Column( - children: [ - loginType.toInt != 1 - ? CustomButton( - text: LocaleKeys.loginByOTP.tr(), - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: false, - useSafeArea: true, - backgroundColor: Colors.transparent, - enableDrag: false, - // Prevent dragging to avoid focus conflicts - builder: (bottomSheetContext) => - StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { - return Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), - child: SingleChildScrollView( - child: GenericBottomSheet( - countryCode: "966", - initialPhoneNumber: "", - textController: TextEditingController(), - isFromSavedLogin: true, - isEnableCountryDropdown: true, - onCountryChange: (value) {}, - onChange: (String? value) {}, - buttons: [ - Padding( - padding: EdgeInsets.only(bottom: 10.h), - child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), + ? Column( + children: [ + loginType.toInt != 1 + ? CustomButton( + text: LocaleKeys.loginByOTP.tr(), + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + useSafeArea: true, + backgroundColor: Colors.transparent, + enableDrag: false, + // Prevent dragging to avoid focus conflicts + builder: (bottomSheetContext) => + StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: "966", + initialPhoneNumber: "", + textController: TextEditingController(), + isFromSavedLogin: true, + isEnableCountryDropdown: true, + onCountryChange: (value) {}, + onChange: (String? value) {}, + buttons: [ + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.sms; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + icon: AppAssets.sms), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), + ], + ), + Padding( + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), onPressed: () { Navigator.of(context).pop(); - loginType = LoginTypeEnum.sms; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); + loginType = LoginTypeEnum.whatsapp; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - icon: AppAssets.sms), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.h), - child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), - ], - ), - Padding( - padding: EdgeInsets.only(bottom: 10.h, top: 10.h), - child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), - onPressed: () { - Navigator.of(context).pop(); - loginType = LoginTypeEnum.whatsapp; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); - }, - backgroundColor: AppColors.transparent, - borderColor: AppColors.textColor, - textColor: AppColors.textColor, - icon: AppAssets.whatsapp, - iconColor: null, - applyThemeColor: false, + backgroundColor: AppColors.transparent, + borderColor: AppColors.textColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + iconColor: null, + applyThemeColor: false, + ), ), - ), - ], + ], + ), ), - ), - ); - }), - ); - }, - backgroundColor: AppColors.whiteColor, - borderColor: AppColors.borderOnlyColor, - textColor: AppColors.textColor, - borderWidth: 2, - padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), - icon: AppAssets.sms, - iconColor: AppColors.textColor, - ) - : Container(), - SizedBox( - height: 20.h, - ), - ], - ) - : CustomButton( - text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", - icon: AppAssets.whatsapp, - iconColor: null, - onPressed: () { - if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(() {}); - } else { - loginType = LoginTypeEnum.whatsapp; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); - } - }, - backgroundColor: AppColors.whiteColor, - borderColor: AppColors.textColor, - textColor: AppColors.textColor, - borderWidth: 2.w, - padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), - applyThemeColor: false, - ), + ); + }), + ); + }, + height: isFoldable ? 50.h : 40.h, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + borderWidth: 2, + padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), + icon: AppAssets.sms, + iconColor: AppColors.textColor, + ) + : Container(), + SizedBox( + height: 20.h, + ), + ], + ) + : CustomButton( + text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", + icon: AppAssets.whatsapp, + iconColor: null, + onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(() {}); + } else { + loginType = LoginTypeEnum.whatsapp; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); + } + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.textColor, + textColor: AppColors.textColor, + borderWidth: 2.w, + padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), + applyThemeColor: false, + ), ], const Spacer(flex: 2), @@ -294,7 +306,7 @@ class _SavedLogin extends State { CustomPageRoute( page: LandingNavigation(), ), - (r) => false); + (r) => false); // Navigator.of(context).pushAndRemoveUntil( // MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()) // ); diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 528f262f..2dcdb42a 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -75,7 +75,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true, isEnglishOnly: true), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" + .toText16(isBold: true, isEnglishOnly: true), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -83,13 +84,16 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - richText: Row( - children: [ - "${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true), - LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor), - ], - ),), - AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), + richText: Row( + children: [ + "${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true), + LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor), + ], + ), + ), + AppCustomChipWidget( + labelText: + "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ], ), ], @@ -115,7 +119,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ AppCustomChipWidget( labelText: - "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName) ?? ""}"), + "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName) ?? ""}"), SizedBox(height: 16.h), 1.divider, SizedBox(height: 16.h), @@ -124,7 +128,12 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ Row( children: [ - Utils.buildSvgWithAssets(icon: getLiveCareTypeIcon(immediateLiveCareVM.liveCareSelectedCallType), width: 32.h, height: 32.h, fit: BoxFit.contain, applyThemeColor: false), + Utils.buildSvgWithAssets( + icon: getLiveCareTypeIcon(immediateLiveCareVM.liveCareSelectedCallType), + width: 32.h, + height: 32.h, + fit: BoxFit.contain, + applyThemeColor: false), SizedBox(width: 8.h), getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true), ], @@ -136,7 +145,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ), ), ).onPress(() { - showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), callBackFunc: () async { + showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), + callBackFunc: () async { debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}"); }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); }); @@ -169,11 +179,15 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(24.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive + .tr(context: context) + .toText14(color: AppColors.primaryRedColor, isBold: true) + .paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { - Navigator.of(context).push( + Navigator.of(context) + .push( CustomPageRoute( page: InsuranceHomePage(), ), @@ -214,7 +228,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, + Utils.getPaymentAmountWithSymbol( + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true), + AppColors.blackColor, + 13, isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ุฑูŠุงู„").toLowerCase() == "ุฑูŠุงู„"), ], @@ -224,7 +241,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( - (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax ?? "0.0").toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), AppColors.greyTextColor, 13, + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax ?? "0.0") + .toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), + AppColors.greyTextColor, + 13, isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ุฑูŠุงู„").toLowerCase() == "ุฑูŠุงู„")), ], @@ -233,13 +253,17 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox(width: 200.h, child: Utils.getPaymentMethods()), - Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, + Utils.getPaymentMethods(), + Utils.getPaymentAmountWithSymbol( + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true), + AppColors.blackColor, + 17, isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ุฑูŠุงู„").toLowerCase() == "ุฑูŠุงู„")), ], ).paddingSymmetrical(24.h, 0.h), - (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") // (true) ? CustomButton( text: LocaleKeys.confirmLiveCare.tr(context: context), @@ -248,7 +272,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { if (val) { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); - await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); + await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare( + "${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); await immediateLiveCareVM.getPatientLiveCareHistory(); LoaderBottomSheet.hideLoader(); if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { @@ -296,7 +321,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { borderColor: AppColors.successColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -339,7 +364,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { borderColor: AppColors.infoColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -425,8 +450,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList(); if (newlyPermanent.isNotEmpty) { final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); - final message = - newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; + final message = newlyPermanent.length == 1 + ? '$names permission is permanently denied. Open app settings to allow it.' + : '$names permissions are permanently denied. Open app settings to allow them.'; await LiveCarePermissionService.instance.showOpenSettingsDialog( context, title: "Permissions Required", diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 86702dbb..a8954548 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_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/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -37,6 +38,7 @@ class _ReviewAppointmentPageState extends State { late BookAppointmentsViewModel bookAppointmentsViewModel; late AuthenticationViewModel authVM; late MyAppointmentsViewModel myAppointmentsViewModel; + late SymptomsCheckerViewModel symptomsCheckerViewModel; @override Widget build(BuildContext context) { @@ -44,6 +46,7 @@ class _ReviewAppointmentPageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); authVM = Provider.of(context, listen: false); appState = getIt.get(); + symptomsCheckerViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, body: Column( @@ -363,6 +366,37 @@ class _ReviewAppointmentPageState extends State { isCloseButtonVisible: true, ); }, onSuccess: (apiResp) async { + // Check if booking is from symptoms checker and call the API + if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) { + final appointmentNo = apiResp.data['AppointmentNo']?.toString() ?? ''; + final doctorId = bookAppointmentsViewModel.selectedDoctor.doctorID?.toString() ?? ''; + // Combine date and time for ISO string format + final appointmentDate = '${bookAppointmentsViewModel.selectedAppointmentDate} ${bookAppointmentsViewModel.selectedAppointmentTime}'; + final mobileNumber = appState.getAuthenticatedUser()?.mobileNumber ?? ''; + final fileNo = appState.getAuthenticatedUser()?.patientId?.toString() ?? ''; + final projectId = bookAppointmentsViewModel.selectedDoctor.projectID ?? 0; + final clinicId = bookAppointmentsViewModel.selectedDoctor.clinicID ?? 0; + + await symptomsCheckerViewModel.saveAppointmentDetailsForSymptomsChecker( + fileNo: fileNo, + appointmentNo: appointmentNo, + doctorId: doctorId, + appointmentDate: appointmentDate, + mobileNumber: mobileNumber, + projectId: projectId, + clinicId: clinicId, + onSuccess: (response) { + // Success - continue with normal flow + debugPrint("onSuccess called for saveAppointmentDetailsForSymptomsChecker: ${response.data}"); + + }, + onError: (error) { + // Log error but don't block the user flow + debugPrint("Error saving symptoms checker appointment: $error"); + }, + ); + } + LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 88c2f2b1..5e4b4dfe 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -47,7 +47,9 @@ class HabibWalletCard extends StatelessWidget { child: Stack(children: [ Positioned( right: 0, - child: ClipRRect(borderRadius: BorderRadius.circular(24.0), child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h, applyThemeColor: false)), + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h, applyThemeColor: false)), ), Padding( padding: EdgeInsets.all(16.h), @@ -57,25 +59,25 @@ class HabibWalletCard extends StatelessWidget { // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ - LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), - // Container( - // height: 40.h, - // width: 40.h, - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.textColor, - // borderRadius: 8.h, - // ), - // child: Padding( - // padding: EdgeInsets.all(8.h), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.show_icon, - // width: 12.h, - // height: 12.h, - // fit: BoxFit.contain, - // ), - // ), - // ), - // ], + LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), + // Container( + // height: 40.h, + // width: 40.h, + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.textColor, + // borderRadius: 8.h, + // ), + // child: Padding( + // padding: EdgeInsets.all(8.h), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.show_icon, + // width: 12.h, + // height: 12.h, + // fit: BoxFit.contain, + // ), + // ), + // ), + // ], // ), SizedBox(height: 4.h), Column( diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 01f0dc39..6cb1ccd1 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -120,7 +120,7 @@ class PossibleConditionsPage extends StatelessWidget { categoryName: (condition.conditionDetails!.category!.name) ?? "Other", onSuccess: (value) { LoaderBottomSheet.hideLoader(); - print(symptomsCheckerViewModel.clinicDetailsList.first.clinicID); + debugPrint(symptomsCheckerViewModel.clinicDetailsList.first.clinicID.toString()); initiateBookAppointmentFlow(context); }, onError: (err) { @@ -282,6 +282,9 @@ class PossibleConditionsPage extends StatelessWidget { } initiateBookAppointmentFlow(BuildContext context) { + // Set flag to indicate booking is from symptoms checker + symptomsCheckerViewModel.setBookingFromSymptomsChecker(true); + // bookAppointmentsViewModel.getLocation(); bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel( clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID, @@ -307,6 +310,10 @@ class PossibleConditionsPage extends StatelessWidget { ), ).onPress(() { data.handleBackPress(); + // Reset flag if user goes back during symptoms checker booking + if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) { + symptomsCheckerViewModel.setBookingFromSymptomsChecker(false); + } }); } } @@ -317,10 +324,15 @@ class PossibleConditionsPage extends StatelessWidget { regionalViewModel.flush(); regionalViewModel.setBottomSheetType(type); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, - child: Consumer(builder: (context, data, __) { + showCommonBottomSheetWithoutHeight(context, + title: "", + titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), + isDismissible: false, child: Consumer(builder: (context, data, __) { return getRegionalSelectionWidget(data, context); - }), callBackFunc: () {}); + }), callBackFunc: () { + // Reset flag when bottom sheet is closed/cancelled + symptomsCheckerViewModel.setBookingFromSymptomsChecker(false); + }); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index d7fbf63c..9f9ac86e 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -28,12 +29,20 @@ class SymptomsSelectorPage extends StatefulWidget { class _SymptomsSelectorPageState extends State { late DialogService dialogService; late AppState _appState; + final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); dialogService = getIt(); _appState = getIt(); + + // Listen to search input changes + _searchController.addListener(() { + final viewModel = context.read(); + viewModel.filterSymptoms(_searchController.text, isArabic: _appState.isArabic()); + }); + // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -41,6 +50,12 @@ class _SymptomsSelectorPageState extends State { }); } + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + void _onNextPressed(SymptomsCheckerViewModel viewModel) { if (viewModel.hasSelectedSymptoms) { // Navigate to triage screen @@ -100,7 +115,56 @@ class _SymptomsSelectorPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - ...viewModel.organSymptomsResults.map((organResult) { + // Inline search field + Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + ), + child: TextField( + controller: _searchController, + style: TextStyle( + fontSize: 14.f, + color: AppColors.textColor, + fontFamily: _appState.isArabic() ? 'CairoArabic' : 'Poppins', + ), + decoration: InputDecoration( + hintText: LocaleKeys.search.tr(context: context), + hintStyle: TextStyle( + color: AppColors.greyTextColor, + fontSize: 14.f, + ), + prefixIcon: Icon( + Icons.search, + color: AppColors.greyTextColor, + size: 20.h, + ), + suffixIcon: _searchController.text.isNotEmpty + ? IconButton( + icon: Icon( + Icons.clear, + color: AppColors.greyTextColor, + size: 20.h, + ), + onPressed: () { + _searchController.clear(); + viewModel.clearSymptomFilter(); + }, + ) + : null, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 12.h, + ), + ), + ), + ), + ), + SizedBox(height: 16.h), + ...viewModel.filteredOrganSymptomsResults.map((organResult) { // Find matching organ ID from selected organs String? organId; String? organName; diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index a94222b6..442b9691 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -177,22 +175,29 @@ class _TriagePageState extends State { // Collect evidence based on question type if (viewModel.isTriageQuestionSingleSelection) { - // Type 1: Single selection - only one evidence entry + // Type 1: Single selection - only one evidence entry with "Yes" choice final selectedItemId = viewModel.selectedSingleItemId; - final selectedChoiceIndex = viewModel.selectedSingleChoiceIndex; - if (selectedItemId != null && selectedChoiceIndex != null) { - // Find the item and choice + if (selectedItemId != null) { + // Find the item and its "Yes" choice for (var item in currentQuestion.items!) { if (item.id == selectedItemId) { - if (item.choices != null && selectedChoiceIndex < item.choices!.length) { - final selectedChoice = item.choices![selectedChoiceIndex]; - final choiceId = selectedChoice.id ?? ""; - - if (choiceId.isNotEmpty) { - viewModel.addTriageEvidence(selectedItemId, choiceId); + // Find the "Yes" choice (case-insensitive) + String? yesChoiceId; + if (item.choices != null) { + for (var choice in item.choices!) { + final label = choice.label?.toLowerCase() ?? ''; + if (label == 'yes' || label == 'ู†ุนู…') { + yesChoiceId = choice.id; + break; + } } } + + // If "Yes" choice found, add evidence + if (yesChoiceId != null && yesChoiceId.isNotEmpty) { + viewModel.addTriageEvidence(selectedItemId, yesChoiceId); + } break; } } @@ -221,9 +226,6 @@ class _TriagePageState extends State { List initialEvidenceIds = viewModel.getAllEvidenceIds(); List> triageEvidence = viewModel.getTriageEvidence(); - log("initialEvidenceIds: ${initialEvidenceIds.toString()}"); - log("triageEvidence: ${triageEvidence.toString()}"); - // Call API with updated evidence viewModel.getDiagnosisForTriage( age: viewModel.selectedAge!, @@ -400,8 +402,46 @@ class _TriagePageState extends State { (question.text ?? "").toText16(isBold: true, color: AppColors.textColor), SizedBox(height: 24.h), - // Show all items with dividers - ...List.generate(question.items!.length, (itemIndex) { + // Type 1: Show items as checkboxes only (no choices displayed) + if (viewModel.isTriageQuestionSingleSelection) ...[ + ...List.generate(question.items!.length, (itemIndex) { + final item = question.items![itemIndex]; + final itemId = item.id ?? ""; + final itemName = item.name ?? ""; + final isSelected = viewModel.selectedSingleItemId == itemId; + + return GestureDetector( + onTap: () => _onOptionSelectedForItem(itemId, 0), // Pass 0 as placeholder + child: Container( + margin: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + width: 24.w, + height: 24.w, + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(5.r), + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor, + width: 1.w, + ), + ), + child: isSelected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, + ), + SizedBox(width: 12.w), + Expanded(child: itemName.toText13(isBold: true)), + ], + ), + ), + ); + }), + ] else ...[ + // Type 2: Show all items with their choices + ...List.generate(question.items!.length, (itemIndex) { final item = question.items![itemIndex]; final itemId = item.id ?? ""; final choices = item.choices ?? []; @@ -427,9 +467,10 @@ class _TriagePageState extends State { Divider(color: AppColors.bottomNAVBorder, thickness: 1), SizedBox(height: 10.h), ], - ], - ); - }), + ], + ); + }), + ], ], ), ), @@ -564,7 +605,7 @@ class _TriagePageState extends State { ), ), ], - ), + ), ], ), SizedBox(height: 24.h), From b03cbf518c7b37539725a3e9c71f56ff67fa2fb2 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 28 Apr 2026 18:16:29 +0300 Subject: [PATCH 11/11] updated scheduleApi Fix --- lib/core/api_consts.dart | 2 +- .../symptoms_checker_repo.dart | 2 +- .../symptoms_checker_view_model.dart | 23 ++++++++ .../review_appointment_page.dart | 55 ++++++++++++++----- .../possible_conditions_screen.dart | 33 +++++++---- .../symptoms_checker/triage_screen.dart | 48 ++++++++-------- 6 files changed, 112 insertions(+), 51 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index cc2288be..c9c4aaff 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -15,6 +15,7 @@ class ApiConsts { static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD static String symptomsCheckerApiLive = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD + static String symptomsCheckerScheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment'; // Symptoms Checker Credentials static String symptomsCheckerUsername = 'mobile_user'; @@ -212,7 +213,6 @@ class ApiConsts { static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition='; - static final String scheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment'; //E-REFERRAL SERVICES static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 824a01c4..732b8553 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -490,7 +490,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { Failure? failure; await apiClient.post( - ApiConsts.scheduleAppointment, + ApiConsts.symptomsCheckerScheduleAppointment, apiHeaders: headers, body: body, isExternal: true, diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index b6154f35..b0f7879d 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -204,6 +205,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Set flag for booking from symptoms checker void setBookingFromSymptomsChecker(bool value) { isBookingFromSymptomsChecker = value; + + log("isBookingFromSymptomsChecker: $isBookingFromSymptomsChecker"); notifyListeners(); } @@ -1043,6 +1046,26 @@ class SymptomsCheckerViewModel extends ChangeNotifier { notifyListeners(); } + /// Clear user selections (organs, symptoms, risk factors, questions) but keep results + /// This is useful when user reaches results page and we want to prevent going back to edit selections + void clearSelectionsKeepResults() { + _selectedOrganIds.clear(); + _selectedSymptomsByOrgan.clear(); + _selectedRiskFactorIds.clear(); + _selectedSuggestionsIds.clear(); + _triageEvidenceList.clear(); + _selectedTriageChoicesByItemId.clear(); + _selectedSingleItemId = null; + _selectedSingleChoiceIndex = null; + _symptomSearchQuery = ''; + _filteredOrganSymptomsResults.clear(); + _isBottomSheetExpanded = false; + _tooltipTimer?.cancel(); + _tooltipOrganId = null; + // Keep: bodySymptomResponse, riskFactorsResponse, suggestionsResponse, triageDataDetails, user info, booking flag + notifyListeners(); + } + // User Info Flow Methods /// Set current page in user info flow diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index a8954548..1a40c21b 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -1,10 +1,11 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_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/loading_utils.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'; @@ -17,8 +18,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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'; @@ -77,7 +78,8 @@ class _ReviewAppointmentPageState extends State { Row( children: [ Image.network( - bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", + bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", width: 50.h, height: 50.h, fit: BoxFit.cover, @@ -93,9 +95,11 @@ class _ReviewAppointmentPageState extends State { .toString() .toText16(isBold: true, maxlines: 1), SizedBox(width: 12.w), - (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) + (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) ? Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? + "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, height: 15.h, fit: BoxFit.cover, @@ -104,7 +108,9 @@ class _ReviewAppointmentPageState extends State { ], ), SizedBox(height: 2.h), - (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") + (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty + ? bookAppointmentsViewModel.selectedDoctor.speciality!.first + : "") .toString() .toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1), ], @@ -166,7 +172,8 @@ class _ReviewAppointmentPageState extends State { spacing: 4.h, runSpacing: 4.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), + AppCustomChipWidget( + labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), AppCustomChipWidget( labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), @@ -303,7 +310,9 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.success.tr(context: context), + child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, @@ -315,7 +324,8 @@ class _ReviewAppointmentPageState extends State { }, onError: (error) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), + callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); }, @@ -325,7 +335,9 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); LoaderBottomSheet.showLoader( - loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); + loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment + ? LocaleKeys.reschedulingAppo.tr(context: context) + : LocaleKeys.bookingYourAppointment.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { @@ -336,7 +348,8 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr()); - showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), + callBackFunc: () { bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); bookAppointmentsViewModel.setIsLiveCareSchedule(false); Navigator.pushAndRemoveUntil( @@ -366,12 +379,24 @@ class _ReviewAppointmentPageState extends State { isCloseButtonVisible: true, ); }, onSuccess: (apiResp) async { + log("AppointmentNo: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); // Check if booking is from symptoms checker and call the API if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) { final appointmentNo = apiResp.data['AppointmentNo']?.toString() ?? ''; final doctorId = bookAppointmentsViewModel.selectedDoctor.doctorID?.toString() ?? ''; - // Combine date and time for ISO string format - final appointmentDate = '${bookAppointmentsViewModel.selectedAppointmentDate} ${bookAppointmentsViewModel.selectedAppointmentTime}'; + + // Convert date and time to ISO 8601 format with timezone + DateTime? appointmentDateTime; + try { + // Combine date (YYYY-MM-DD) and time (HH:MM) into ISO format string + final dateTimeString = '${bookAppointmentsViewModel.selectedAppointmentDate}T${bookAppointmentsViewModel.selectedAppointmentTime}:00'; + appointmentDateTime = DateTime.parse(dateTimeString); + } catch (e) { + log("Error parsing appointment date/time: $e"); + } + + // Convert to ISO 8601 format with UTC timezone (e.g., "2026-02-11T13:15:37.652Z") + final appointmentDate = appointmentDateTime != null ? DateUtil.getISODateFormat(appointmentDateTime.toUtc()) : ''; final mobileNumber = appState.getAuthenticatedUser()?.mobileNumber ?? ''; final fileNo = appState.getAuthenticatedUser()?.patientId?.toString() ?? ''; final projectId = bookAppointmentsViewModel.selectedDoctor.projectID ?? 0; @@ -388,7 +413,6 @@ class _ReviewAppointmentPageState extends State { onSuccess: (response) { // Success - continue with normal flow debugPrint("onSuccess called for saveAppointmentDetailsForSymptomsChecker: ${response.data}"); - }, onError: (error) { // Log error but don't block the user flow @@ -399,7 +423,8 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { bookAppointmentsViewModel.setIsLiveCareSchedule(false); bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 6cb1ccd1..3a113b1e 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -17,6 +19,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; @@ -29,17 +32,28 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; -import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; +class PossibleConditionsPage extends StatefulWidget { + const PossibleConditionsPage({super.key}); -class PossibleConditionsPage extends StatelessWidget { - PossibleConditionsPage({super.key}); + @override + State createState() => _PossibleConditionsPageState(); +} +class _PossibleConditionsPageState extends State { late SymptomsCheckerViewModel symptomsCheckerViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late AppointmentViaRegionViewmodel regionalViewModel; - late AppState appState; + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + // Clear selections once user reaches results page + symptomsCheckerViewModel.clearSelectionsKeepResults(); + }); + } + Widget _buildLoadingShimmer() { return ListView.separated( shrinkWrap: true, @@ -114,12 +128,15 @@ class PossibleConditionsPage extends StatelessWidget { return; } + log("isBookingFromSymptomsChecker: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); + // For non-emergency cases, continue with normal booking flow LoaderBottomSheet.showLoader(); symptomsCheckerViewModel.getClinicConditionsFromCategory( categoryName: (condition.conditionDetails!.category!.name) ?? "Other", onSuccess: (value) { LoaderBottomSheet.hideLoader(); + debugPrint("isBookingFromSymptomsChecker:: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); debugPrint(symptomsCheckerViewModel.clinicDetailsList.first.clinicID.toString()); initiateBookAppointmentFlow(context); }, @@ -329,10 +346,7 @@ class PossibleConditionsPage extends StatelessWidget { titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, child: Consumer(builder: (context, data, __) { return getRegionalSelectionWidget(data, context); - }), callBackFunc: () { - // Reset flag when bottom sheet is closed/cancelled - symptomsCheckerViewModel.setBookingFromSymptomsChecker(false); - }); + }), callBackFunc: () {}); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { @@ -368,7 +382,7 @@ class PossibleConditionsPage extends StatelessWidget { } }, onHospitalSearch: (value) { - data.searchHospitals(value ?? ""); + data.searchHospitals(value); }, selectedFacility: data.selectedFacility, hmcCount: data.hmcCount, @@ -390,7 +404,6 @@ class PossibleConditionsPage extends StatelessWidget { } else { return SizedBox.shrink(); } - return SizedBox.shrink(); } void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) { diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 442b9691..79c47d09 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -442,31 +442,31 @@ class _TriagePageState extends State { ] else ...[ // Type 2: Show all items with their choices ...List.generate(question.items!.length, (itemIndex) { - final item = question.items![itemIndex]; - final itemId = item.id ?? ""; - final choices = item.choices ?? []; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Item name (sub-question) - (item.name ?? "").toText14(isBold: true, color: AppColors.textColor), - SizedBox(height: 8.h), - // Choices for this item - ...List.generate(choices.length, (choiceIndex) { - // Check selection based on question type - bool selected = viewModel.isTriageQuestionSingleSelection - ? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex) - : viewModel.getTriageChoiceForItem(itemId) == choiceIndex; - return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); - }), + final item = question.items![itemIndex]; + final itemId = item.id ?? ""; + final choices = item.choices ?? []; - // Add divider between items (but not after the last one) - if (itemIndex < question.items!.length - 1) ...[ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Item name (sub-question) + (item.name ?? "").toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 8.h), - Divider(color: AppColors.bottomNAVBorder, thickness: 1), - SizedBox(height: 10.h), - ], + // Choices for this item + ...List.generate(choices.length, (choiceIndex) { + // Check selection based on question type + bool selected = viewModel.isTriageQuestionSingleSelection + ? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex) + : viewModel.getTriageChoiceForItem(itemId) == choiceIndex; + return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); + }), + + // Add divider between items (but not after the last one) + if (itemIndex < question.items!.length - 1) ...[ + SizedBox(height: 8.h), + Divider(color: AppColors.bottomNAVBorder, thickness: 1), + SizedBox(height: 10.h), + ], ], ); }), @@ -605,7 +605,7 @@ class _TriagePageState extends State { ), ), ], - ), + ), ], ), SizedBox(height: 24.h),