From 31ed9483ad53f84b7a8fe6e3d95d374e9fcf11de Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 20 Sep 2026 12:52:59 +0300 Subject: [PATCH] CR Implementation contd. --- assets/langs/ar-SA.json | 16 +- assets/langs/en-US.json | 16 +- lib/core/api_consts.dart | 2 + .../immediate_livecare_view_model.dart | 2 +- ...tient_livecare_history_response_model.dart | 5 +- lib/generated/locale_keys.g.dart | 13 ++ ...mediate_livecare_pending_request_page.dart | 37 +++- .../widgets/livecare_reschedule_widget.dart | 8 +- .../widgets/test_connection_sheet.dart | 177 ++++++++++++++++++ 9 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 lib/presentation/book_appointment/livecare/widgets/test_connection_sheet.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f5923f29..24f046dd 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -2058,5 +2058,19 @@ "liveCareTermsHeading16": "14.\tالشكاوى", "liveCareTermsConditions47": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ", "liveCareTermsConditions48": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011", - "liveCareInsurancePolicyText": "تنبيه: الاجراءات ما بعد الاستشارة (مثل التحاليل أو الأدوية) غير مشمولة بتغطية التأمين, ويتحمل المريض تكلفتها." + "liveCareInsurancePolicyText": "تنبيه: الاجراءات ما بعد الاستشارة (مثل التحاليل أو الأدوية) غير مشمولة بتغطية التأمين, ويتحمل المريض تكلفتها.", + "liveCarePendingRequestText": "أنت الآن في قائمة الانتظار. سيتواصل معك الطبيب في أقرب وقت ممكن, الرجاء التأكد من تفعيل اشعارات التطبيق وضبط اعدادات الكاميرا والمايكروفون بشكل صحيح.", + "testConnection": "اختبار الاتصال", + "cameraPermissionText": "إذن الكاميرا", + "microphonePermissionText": "إذن استخدام الميكروفون", + "notificationPermissionText": "إذن الإشعارات", + "testLiveCareConnection": "اختبار اتصال لايف كير", + "cameraFeedInactive": "بث الكاميرا غير نشط", + + "cantWaitLiveCare": "معاودة الاتصال بي, لا أستطيع الانتظار”", + "whenCallBackLiveCare": "متى تود أن نعاود الاتصال بك؟", + "after30Mins": "اتصل بي بعد 30 دقيقة", + "after30MinsDesc": "سيتصل بك الطبيب بعد 30 دقيقة.", + "after1Hour": "اتصل بي بعد ساعة", + "after1HourDesc": "سيتصل بك الطبيب بعد ساعة واحدة." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 02a3aeaa..693b31f5 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -2048,7 +2048,21 @@ "liveCareTermsHeading16": "14. COMPLAINTS", "liveCareTermsConditions47": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", "liveCareTermsConditions48": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553", - "liveCareInsurancePolicyText": "Note: Procedure following the consultation (such as Lab tests or medication) are not covered by insurance and will be charged directly to the patient." + "liveCareInsurancePolicyText": "Note: Procedure following the consultation (such as Lab tests or medication) are not covered by insurance and will be charged directly to the patient.", + "liveCarePendingRequestText": "You are now in the waiting queue. The doctor will contact you as soon as possible. Please make sure your App Notifications are turned on, and your camera and microphone settings are properly configured.", + "testConnection": "Test Connection", + "cameraPermissionText": "Camera Permission", + "microphonePermissionText": "Microphone Permission", + "notificationPermissionText": "Notifications Permission", + "testLiveCareConnection": "Test LiveCare Connection", + "cameraFeedInactive": "Camera feed inactive", + + "cantWaitLiveCare": "I can't wait, Please call me later", + "whenCallBackLiveCare": "When would you like to be called back?", + "after30Mins": "After 30 Mins", + "after30MinsDesc": "You will be called by the doctor after 30 mins", + "after1Hour": "After 1 Hour", + "after1HourDesc": "You will be called by the doctor after 1 hour" } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9b6613fd..e4f43aaa 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -1023,6 +1023,8 @@ const DOWNLOAD_PHARMACY_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadI const GET_APPOINTMENT_PARKING_QR = 'Services/outps.svc/rest/pms_getParkingTicketByAppointmentNo'; +const RESCHEDULE_LIVECARE_CALL = 'Services/ER_VirtualCall.svc/ArrangeCall'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 126cf2cf..4ccddebc 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -314,7 +314,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientLiveCareHistoryList = apiResponse.data!; if (patientLiveCareHistoryList.isNotEmpty) { - if (patientLiveCareHistoryList[0].callStatus! < 4) { + if ((patientLiveCareHistoryList[0].callStatus ?? 0) < 4 || (patientLiveCareHistoryList[0].callStatus ?? 0) == 15) { patientHasPendingLiveCareRequest = true; countdownDuration = Duration(minutes: patientLiveCareHistoryList.first.watingtimeInteger!, seconds: 0); durationNotifier.value = countdownDuration; diff --git a/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart b/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart index 0077d830..c44232a9 100644 --- a/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart +++ b/lib/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart @@ -17,6 +17,7 @@ class PatientLiveCareHistory { String? stringCallStatus; int? vCID; int? watingtimeInteger; + String? callType; PatientLiveCareHistory( {this.appointmentNo, @@ -36,7 +37,8 @@ class PatientLiveCareHistory { this.serviceID, this.stringCallStatus, this.vCID, - this.watingtimeInteger}); + this.watingtimeInteger, + this.callType}); PatientLiveCareHistory.fromJson(Map json) { appointmentNo = json['AppointmentNo']; @@ -57,6 +59,7 @@ class PatientLiveCareHistory { stringCallStatus = json['StringCallStatus']; vCID = json['VC_ID']; watingtimeInteger = json['WatingtimeInteger']; + callType = json['CallType']; } Map toJson() { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 68678a7d..68601822 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -2039,5 +2039,18 @@ abstract class LocaleKeys { static const liveCareTermsConditions47 = 'liveCareTermsConditions47'; static const liveCareTermsConditions48 = 'liveCareTermsConditions48'; static const liveCareInsurancePolicyText = 'liveCareInsurancePolicyText'; + static const liveCarePendingRequestText = 'liveCarePendingRequestText'; + static const testConnection = 'testConnection'; + static const cameraPermissionText = 'cameraPermissionText'; + static const microphonePermissionText = 'microphonePermissionText'; + static const notificationPermissionText = 'notificationPermissionText'; + static const testLiveCareConnection = 'testLiveCareConnection'; + static const cameraFeedInactive = 'cameraFeedInactive'; + static const cantWaitLiveCare = 'cantWaitLiveCare'; + static const whenCallBackLiveCare = 'whenCallBackLiveCare'; + static const after30Mins = 'after30Mins'; + static const after30MinsDesc = 'after30MinsDesc'; + static const after1Hour = 'after1Hour'; + static const after1HourDesc = 'after1HourDesc'; } 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 bbb634e8..a3c54da6 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 @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.da import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/livecare_reschedule_widget.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/test_connection_sheet.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'; @@ -47,7 +48,6 @@ class _ImmediateLiveCarePendingRequestPageState extends State createState() => _ZoomConnectionTestSheetState(); +} + +class _ZoomConnectionTestSheetState extends State { + PermissionStatus? _cameraPermission; + PermissionStatus? _micPermission; + PermissionStatus? _notificationPermission; + + final ZoomVideoSdk _zoomSdk = ZoomVideoSdk(); + bool _isPreviewingCamera = false; + + @override + void dispose() { + _stopCameraPreview(); + super.dispose(); + } + + // 1. Request and verify system permissions + Future _checkPermissions() async { + final cameraStatus = await Permission.camera.request(); + final micStatus = await Permission.microphone.request(); + final notificationStatus = await Permission.notification.request(); + + setState(() { + _cameraPermission = cameraStatus; + _micPermission = micStatus; + _notificationPermission = notificationStatus; + }); + + if (cameraStatus.isGranted && micStatus.isGranted) { + _startCameraPreview(); + } + } + + // 2. Turn on camera feed via Zoom Video SDK VideoHelper + Future _startCameraPreview() async { + try { + await _zoomSdk.videoHelper.startVideo(); + setState(() { + _isPreviewingCamera = true; + }); + } catch (e) { + debugPrint('Error starting Zoom camera feed: $e'); + } + } + + Future _stopCameraPreview() async { + if (_isPreviewingCamera) { + await _zoomSdk.videoHelper.stopVideo(); + setState(() { + _isPreviewingCamera = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(20.h), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 'Zoom Device Diagnostics'.toText18(isBold: true), + // const SizedBox(height: 16), + + // Status Indicators + _StatusRow(label: LocaleKeys.cameraPermissionText.tr(context: context), status: _cameraPermission), + _StatusRow(label: LocaleKeys.microphonePermissionText.tr(context: context), status: _micPermission), + _StatusRow(label: LocaleKeys.notificationPermissionText.tr(context: context), status: _notificationPermission), + + SizedBox(height: 16.h), + + // Zoom Native Platform View Widget + Container( + height: 200.h, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.antiAlias, + child: _isPreviewingCamera + ? zoom_view.View( + creationParams: const { + "userId": "", + "sharing": false, + "preview": true, // Instructs SDK to operate in local preview mode + "focused": false, + "hasMultiCamera": false, + "multiCameraIndex": "0", + "videoAspect": "FullFilled", + "fullScreen": false, + }, + ) + : Center( + child: Text( + LocaleKeys.cameraFeedInactive.tr(), + style: TextStyle(color: Colors.white70), + ), + ), + ), + + SizedBox(height: 20.h), + + CustomButton( + text: LocaleKeys.testConnection.tr(context: context), + onPressed: () { + _checkPermissions(); + }, + backgroundColor: AppColors.infoColor.withValues(alpha: 0.20), + borderColor: AppColors.infoColor.withValues(alpha: 0.01), + textColor: AppColors.infoColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.infoColor, + iconSize: 18.h, + icon: AppAssets.call, + ) + + // ElevatedButton.icon( + // icon: const Icon(Icons.sensors), + // label: const Text('Test Permissions & Devices'), + // onPressed: _checkPermissions, + // ) + ], + ), + ); + } +} + +class _StatusRow extends StatelessWidget { + final String label; + final PermissionStatus? status; + + const _StatusRow({required this.label, this.status}); + + @override + Widget build(BuildContext context) { + final isOk = status?.isGranted ?? false; + final icon = status == null + ? Icons.help_outline + : isOk + ? Icons.check_circle + : Icons.cancel; + + return Padding( + padding: EdgeInsets.symmetric(vertical: 4.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label), + Icon(icon, color: status == null ? Colors.grey : (isOk ? Colors.green : Colors.red)), + ], + ), + ); + } +}