diff --git a/Certs/ApplePushCert.p12 b/Certs/ApplePushCert.p12 new file mode 100644 index 00000000..3a1d15c8 Binary files /dev/null and b/Certs/ApplePushCert.p12 differ diff --git a/Certs/ApplePushServices.certSigningRequest b/Certs/ApplePushServices.certSigningRequest new file mode 100644 index 00000000..7d509a0a --- /dev/null +++ b/Certs/ApplePushServices.certSigningRequest @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICkDCCAXgCAQAwSzEjMCEGCSqGSIb3DQEJARYUaGFyb29uNjEzOEBnbWFpbC5j +b20xFzAVBgNVBAMMDk1vaGFtZWQgTWVrYXd5MQswCQYDVQQGEwJBRTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnHqsyE7WfiVcE1Lpa4t4OVO6qlll2q +1djs0XG06R/dlDtIqv4940/XLj+hU93mzAcVvFW4DSIEdD3InM3+T6oMTjPu6meU +69h9ryaVkluQRrT/tdGI1EKO4MWGMe4MDIt7DqMhMfAcxTwekwdxdKaCEhaw3qnA +l/64AelY6URW1pHHJMA0VV7j+pE3jVNai+muMXPrhQ1VrOrV8FftpY3bEeRJR2Cl +T0tv0LhEMu4SfLnVWCzGQQC82hilDw3rH3ZDs8DFxF9agNVdwKlYamarh1dQXwRq +Yx2+sjY1/51r9L4VS+GAh9ECxz0e+43NpzfZ/N+mTeDYKDepaBwPQ6kCAwEAAaAA +MA0GCSqGSIb3DQEBCwUAA4IBAQB89OyLfywKT7ftmpEqCmgsmaJexb580q9w8wOk +1JhJkNV5ec+p1dnge2NZeJ4LGII/5wmPj1vANNW0GZdmJDgnC+2gg9toq1QLCAsF +rW7/LMpgAoEH+P5bhrHV9RRv6BQi0ZmN0apBHjp/pqZfm2Cl5jQPEWjUEf2tIF4l +LSKdok6IPO9n4Fgyk0XdUNSEhgVhsLtZkGiXnkI1YovKDnupTFYPXMLp103bc9zP +xDxwscvOysNDijlzZAkJPg2z8NrJIRDrKvLRHzxQwZ/1LHVB/51bp/1iyks3vOjh +qw5XVsrtGAjCjU9md7q3XkPSyKzhK9UqPdOxdvl1OY0KKIIY +-----END CERTIFICATE REQUEST----- diff --git a/Certs/Pass.rtf b/Certs/Pass.rtf new file mode 100644 index 00000000..2fe18706 --- /dev/null +++ b/Certs/Pass.rtf @@ -0,0 +1,8 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2513 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 Hmg54321} \ No newline at end of file diff --git a/Certs/aps.cer b/Certs/aps.cer new file mode 100644 index 00000000..fb46df86 Binary files /dev/null and b/Certs/aps.cer differ diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist new file mode 100644 index 00000000..0c093a2a --- /dev/null +++ b/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r + API_KEY + AIzaSyA_6ayGCk4fly7o7eTVBrj9kuHBYHMAOfs + GCM_SENDER_ID + 864393916058 + PLIST_VERSION + 1 + BUNDLE_ID + com.cloud.diplomaticquarterapp + PROJECT_ID + diplomaticquarter-d2385 + STORAGE_BUCKET + diplomaticquarter-d2385.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:864393916058:ios:13f787bbfe6051f8b97923 + DATABASE_URL + https://diplomaticquarter-d2385.firebaseio.com + + \ No newline at end of file diff --git a/android/google-services.json b/android/google-services.json new file mode 100644 index 00000000..a4655794 --- /dev/null +++ b/android/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "864393916058", + "firebase_url": "https://diplomaticquarter-d2385.firebaseio.com", + "project_id": "diplomaticquarter-d2385", + "storage_bucket": "diplomaticquarter-d2385.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:864393916058:android:5b5a65cd6d8c18b4b97923", + "android_client_info": { + "package_name": "com.cloud.diplomaticquarterapp" + } + }, + "oauth_client": [ + { + "client_id": "864393916058-tphjrn8j39ntevt32ekcvmll8aue7qql.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBdV3mos1BPhUzNKCj2KANJtiO3o2zh9IM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "864393916058-tphjrn8j39ntevt32ekcvmll8aue7qql.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/assets/sounds/ring_30Sec.caf b/assets/sounds/ring_30Sec.caf new file mode 100644 index 00000000..b5bb4b88 Binary files /dev/null and b/assets/sounds/ring_30Sec.caf differ diff --git a/assets/sounds/ring_30Sec.mp3 b/assets/sounds/ring_30Sec.mp3 new file mode 100644 index 00000000..f647cca8 Binary files /dev/null and b/assets/sounds/ring_30Sec.mp3 differ diff --git a/assets/sounds/ring_60Sec.mp3 b/assets/sounds/ring_60Sec.mp3 new file mode 100644 index 00000000..ac32394e Binary files /dev/null and b/assets/sounds/ring_60Sec.mp3 differ diff --git a/google-services.json b/google-services.json new file mode 100644 index 00000000..a4655794 --- /dev/null +++ b/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "864393916058", + "firebase_url": "https://diplomaticquarter-d2385.firebaseio.com", + "project_id": "diplomaticquarter-d2385", + "storage_bucket": "diplomaticquarter-d2385.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:864393916058:android:5b5a65cd6d8c18b4b97923", + "android_client_info": { + "package_name": "com.cloud.diplomaticquarterapp" + } + }, + "oauth_client": [ + { + "client_id": "864393916058-tphjrn8j39ntevt32ekcvmll8aue7qql.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBdV3mos1BPhUzNKCj2KANJtiO3o2zh9IM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "864393916058-tphjrn8j39ntevt32ekcvmll8aue7qql.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist new file mode 100644 index 00000000..0c093a2a --- /dev/null +++ b/ios/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r + API_KEY + AIzaSyA_6ayGCk4fly7o7eTVBrj9kuHBYHMAOfs + GCM_SENDER_ID + 864393916058 + PLIST_VERSION + 1 + BUNDLE_ID + com.cloud.diplomaticquarterapp + PROJECT_ID + diplomaticquarter-d2385 + STORAGE_BUCKET + diplomaticquarter-d2385.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:864393916058:ios:13f787bbfe6051f8b97923 + DATABASE_URL + https://diplomaticquarter-d2385.firebaseio.com + + \ No newline at end of file diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 00000000..0c093a2a --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r + API_KEY + AIzaSyA_6ayGCk4fly7o7eTVBrj9kuHBYHMAOfs + GCM_SENDER_ID + 864393916058 + PLIST_VERSION + 1 + BUNDLE_ID + com.cloud.diplomaticquarterapp + PROJECT_ID + diplomaticquarter-d2385 + STORAGE_BUCKET + diplomaticquarter-d2385.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:864393916058:ios:13f787bbfe6051f8b97923 + DATABASE_URL + https://diplomaticquarter-d2385.firebaseio.com + + \ No newline at end of file diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/config/config.dart b/lib/config/config.dart index ddedb282..55b44a34 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -107,6 +107,19 @@ const IS_ALLOW_ASK_DOCTOR = '/Doctors.svc/REST/GetPatientDoctorAppointmentResult const GET_CALL_REQUEST_TYPE = '/Doctors.svc/REST/GetCallRequestType_LOV'; const SEND_CALL_REQUEST = '/Doctors.svc/REST/InsertCallInfo'; +const GET_LIVECARE_CLINICS = '/ER_VirtualCall.svc/REST/PatientER_GetClinics'; +const GET_LIVECARE_CLINIC_TIMING = '/ER_VirtualCall.svc/REST/PatientER_GetClinicsServiceTimingsSchedule'; + +const GET_ER_APPOINTMENT_FEES = '/DoctorApplication.svc/REST/GetERAppointmentFees'; +const GET_ER_APPOINTMENT_TIME = '/ER_VirtualCall.svc/REST/GetRestTime'; + +const ADD_NEW_CALL_FOR_PATIENT_ER = '/DoctorApplication.svc/REST/NewCallForPatientER'; + +const GET_LIVECARE_HISTORY = '/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory'; +const CANCEL_LIVECARE_REQUEST = '/ER_VirtualCall.svc/REST/DeleteErRequest'; +const SEND_LIVECARE_INVOICE_EMAIL = '/Notifications.svc/REST/SendInvoiceForLiveCare'; + + //URL to get medicine and pharmacies list const CHANNEL = 3; const GENERAL_ID = 'Cs2020@2016\$2958'; diff --git a/lib/config/localized_values.dart b/lib/config/localized_values.dart index 0cec3446..8abaca71 100644 --- a/lib/config/localized_values.dart +++ b/lib/config/localized_values.dart @@ -334,4 +334,8 @@ const Map> localizedValues = { "ContactUs": {"en": "Contact Us", 'ar': 'الوصول إلينا'}, "ViewAllWaysReachUs": {"en": "View All Ways Reach Us", 'ar': 'جميع طرق الاتصال بنا'}, "medicalProfile": {"en": "Medical Profile", 'ar': 'الملف الطبي'}, + "requestType": {"en": "Request Type", "ar": "نوع الاستفسار"}, + "consultation": {"en": "Consultation", "ar": "استشارة"}, + "logs": {"en": "Logs", "ar": "السجلات"} + }; diff --git a/lib/core/service/medical/vital_sign_service.dart b/lib/core/service/medical/vital_sign_service.dart index 1add63cf..ad35d357 100644 --- a/lib/core/service/medical/vital_sign_service.dart +++ b/lib/core/service/medical/vital_sign_service.dart @@ -7,8 +7,14 @@ class VitalSignService extends BaseService { List vitalSignResModelList = List(); Map body = Map(); - Future getPatientRadOrders() async { + Future getPatientRadOrders({int appointmentNo, int projectID}) async { hasError = false; + + if (appointmentNo != null && projectID != null) { + body['TransNo'] = appointmentNo; + body['ProjectID'] = projectID; + } + await baseAppClient.post(GET_PATIENT_VITAL_SIGN, onSuccess: (dynamic response, int statusCode) { vitalSignResModelList.clear(); @@ -18,6 +24,6 @@ class VitalSignService extends BaseService { }, onFailure: (String error, int statusCode) { hasError = true; super.error = error; - }, body: Map()); + }, body: body); } } diff --git a/lib/core/viewModels/medical/vital_sign_view_model.dart b/lib/core/viewModels/medical/vital_sign_view_model.dart index 5a2d8514..f534140f 100644 --- a/lib/core/viewModels/medical/vital_sign_view_model.dart +++ b/lib/core/viewModels/medical/vital_sign_view_model.dart @@ -8,12 +8,18 @@ import '../../../locator.dart'; class VitalSignViewModel extends BaseViewModel { VitalSignService _vitalSignService = locator(); - List get vitalSignResModelList => _vitalSignService.vitalSignResModelList; + List get vitalSignResModelList => + _vitalSignService.vitalSignResModelList; - - getPatientRadOrders() async { + getPatientRadOrders({int appointmentNo, int projectID}) async { setState(ViewState.Busy); - await _vitalSignService.getPatientRadOrders(); + + if (appointmentNo != null && projectID != null) { + await _vitalSignService.getPatientRadOrders( + appointmentNo: appointmentNo, projectID: projectID); + } else { + await _vitalSignService.getPatientRadOrders(); + } if (_vitalSignService.hasError) { error = _vitalSignService.error; setState(ViewState.Error); diff --git a/lib/locator.dart b/lib/locator.dart index da133687..66face42 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -1,6 +1,5 @@ import 'package:diplomaticquarterapp/core/viewModels/dashboard_view_model.dart'; -import 'package:diplomaticquarterapp/routes.dart'; - +import 'package:diplomaticquarterapp/uitl/navigation_service.dart'; import 'package:get_it/get_it.dart'; import 'core/service/appointment_rate_service.dart'; @@ -44,6 +43,7 @@ void setupLocator() { locator.registerLazySingleton(() => InsuranceCardService()); locator.registerLazySingleton(() => VitalSignService()); locator.registerLazySingleton(() => MedicalService()); + locator.registerLazySingleton(() => NavigationService()); locator.registerLazySingleton(() => ReportsService()); locator.registerLazySingleton(() => DashboardService()); locator.registerLazySingleton(() => AppointmentRateService()); diff --git a/lib/main.dart b/lib/main.dart index a369aa1a..ca2e7a82 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,7 @@ +import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; +import 'package:diplomaticquarterapp/pages/login/login.dart'; import 'package:diplomaticquarterapp/routes.dart'; +import 'package:diplomaticquarterapp/uitl/navigation_service.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -9,7 +12,7 @@ import 'config/size_config.dart'; import 'core/viewModels/project_view_model.dart'; import 'locator.dart'; -void main() { +void main() async { setupLocator(); runApp(MyApp()); } diff --git a/lib/models/FamilyFiles/PatientERVirtualHistoryResponse.dart b/lib/models/FamilyFiles/PatientERVirtualHistoryResponse.dart new file mode 100644 index 00000000..11a8157c --- /dev/null +++ b/lib/models/FamilyFiles/PatientERVirtualHistoryResponse.dart @@ -0,0 +1,108 @@ +class PatientERVirtualHistoryResponse { + List erRequestHistoryList; + + PatientERVirtualHistoryResponse({this.erRequestHistoryList}); + + PatientERVirtualHistoryResponse.fromJson(Map json) { + if (json['ErRequestHistoryList'] != null) { + erRequestHistoryList = new List(); + json['ErRequestHistoryList'].forEach((v) { + erRequestHistoryList.add(new ErRequestHistoryList.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.erRequestHistoryList != null) { + data['ErRequestHistoryList'] = + this.erRequestHistoryList.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ErRequestHistoryList { + dynamic appointmentNo; + String arrivalTime; + int callDuration; + int callStatus; + String clientRequestID; + String doctorID; + String doctorName; + String doctorNameN; + String doctorTitle; + String exWaitingTime; + bool isAppointmentHaveRating; + int patCount; + int projectID; + String sArrivalTime; + int serviceID; + String stringCallStatus; + int vCID; + int watingtimeInteger; + + ErRequestHistoryList( + {this.appointmentNo, + this.arrivalTime, + this.callDuration, + this.callStatus, + this.clientRequestID, + this.doctorID, + this.doctorName, + this.doctorNameN, + this.doctorTitle, + this.exWaitingTime, + this.isAppointmentHaveRating, + this.patCount, + this.projectID, + this.sArrivalTime, + this.serviceID, + this.stringCallStatus, + this.vCID, + this.watingtimeInteger}); + + ErRequestHistoryList.fromJson(Map json) { + appointmentNo = json['AppointmentNo'] != null ? json['AppointmentNo'] : "0"; + arrivalTime = json['ArrivalTime']; + callDuration = json['CallDuration']; + callStatus = json['CallStatus']; + clientRequestID = json['ClientRequestID']; + doctorID = json['DoctorID']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + doctorTitle = json['DoctorTitle']; + exWaitingTime = json['Ex_WaitingTime']; + isAppointmentHaveRating = json['IsAppointmentHaveRating']; + patCount = json['Pat_Count']; + projectID = json['ProjectID']; + sArrivalTime = json['SArrivalTime']; + serviceID = json['ServiceID']; + stringCallStatus = json['StringCallStatus']; + vCID = json['VC_ID']; + watingtimeInteger = json['WatingtimeInteger']; + } + + Map toJson() { + final Map data = new Map(); + data['AppointmentNo'] = this.appointmentNo; + data['ArrivalTime'] = this.arrivalTime; + data['CallDuration'] = this.callDuration; + data['CallStatus'] = this.callStatus; + data['ClientRequestID'] = this.clientRequestID; + data['DoctorID'] = this.doctorID; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['DoctorTitle'] = this.doctorTitle; + data['Ex_WaitingTime'] = this.exWaitingTime; + data['IsAppointmentHaveRating'] = this.isAppointmentHaveRating; + data['Pat_Count'] = this.patCount; + data['ProjectID'] = this.projectID; + data['SArrivalTime'] = this.sArrivalTime; + data['ServiceID'] = this.serviceID; + data['StringCallStatus'] = this.stringCallStatus; + data['VC_ID'] = this.vCID; + data['WatingtimeInteger'] = this.watingtimeInteger; + return data; + } +} diff --git a/lib/models/LiveCare/ClinicsServiceTimingsResponse.dart b/lib/models/LiveCare/ClinicsServiceTimingsResponse.dart new file mode 100644 index 00000000..223b633f --- /dev/null +++ b/lib/models/LiveCare/ClinicsServiceTimingsResponse.dart @@ -0,0 +1,122 @@ +class ClinicsServiceTimingsResponse { + List + patientERGetClinicsServiceTimingsList; + + ClinicsServiceTimingsResponse({this.patientERGetClinicsServiceTimingsList}); + + ClinicsServiceTimingsResponse.fromJson(Map json) { + if (json['PatientER_GetClinicsServiceTimingsList'] != null) { + patientERGetClinicsServiceTimingsList = + new List(); + json['PatientER_GetClinicsServiceTimingsList'].forEach((v) { + patientERGetClinicsServiceTimingsList + .add(new PatientERGetClinicsServiceTimingsList.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.patientERGetClinicsServiceTimingsList != null) { + data['PatientER_GetClinicsServiceTimingsList'] = this + .patientERGetClinicsServiceTimingsList + .map((v) => v.toJson()) + .toList(); + } + return data; + } +} + +class PatientERGetClinicsServiceTimingsList { + int iD; + int serviceID; + Null shiftID; + int dayOfWeek; + String dayOfWeekStr; + Null startTime; + Null endTime; + bool isActive; + String createdOn; + String createdBy; + bool projectOutSA; + String dayOfWeekStrN; + List shiftTimings; + + PatientERGetClinicsServiceTimingsList( + {this.iD, + this.serviceID, + this.shiftID, + this.dayOfWeek, + this.dayOfWeekStr, + this.startTime, + this.endTime, + this.isActive, + this.createdOn, + this.createdBy, + this.projectOutSA, + this.dayOfWeekStrN, + this.shiftTimings}); + + PatientERGetClinicsServiceTimingsList.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + shiftID = json['ShiftID']; + dayOfWeek = json['DayOfWeek']; + dayOfWeekStr = json['DayOfWeekStr']; + startTime = json['StartTime']; + endTime = json['EndTime']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + projectOutSA = json['ProjectOutSA']; + dayOfWeekStrN = json['DayOfWeekStrN']; + if (json['ShiftTimings'] != null) { + shiftTimings = new List(); + json['ShiftTimings'].forEach((v) { + shiftTimings.add(new ShiftTimings.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['ShiftID'] = this.shiftID; + data['DayOfWeek'] = this.dayOfWeek; + data['DayOfWeekStr'] = this.dayOfWeekStr; + data['StartTime'] = this.startTime; + data['EndTime'] = this.endTime; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['ProjectOutSA'] = this.projectOutSA; + data['DayOfWeekStrN'] = this.dayOfWeekStrN; + if (this.shiftTimings != null) { + data['ShiftTimings'] = this.shiftTimings.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ShiftTimings { + String endTime; + int shiftID; + String startTime; + + ShiftTimings({this.endTime, this.shiftID, this.startTime}); + + ShiftTimings.fromJson(Map json) { + endTime = json['EndTime']; + shiftID = json['ShiftID']; + startTime = json['StartTime']; + } + + Map toJson() { + final Map data = new Map(); + data['EndTime'] = this.endTime; + data['ShiftID'] = this.shiftID; + data['StartTime'] = this.startTime; + return data; + } +} diff --git a/lib/models/LiveCare/ERAppointmentFeesResponse.dart b/lib/models/LiveCare/ERAppointmentFeesResponse.dart new file mode 100644 index 00000000..a6cc061a --- /dev/null +++ b/lib/models/LiveCare/ERAppointmentFeesResponse.dart @@ -0,0 +1,61 @@ +class ERAppointmentFeesResponse { + GetERAppointmentFeesList getERAppointmentFeesList; + + ERAppointmentFeesResponse({this.getERAppointmentFeesList}); + + ERAppointmentFeesResponse.fromJson(Map json) { + getERAppointmentFeesList = json['GetERAppointmentFeesList'] != null + ? new GetERAppointmentFeesList.fromJson( + json['GetERAppointmentFeesList']) + : null; + } + + Map toJson() { + final Map data = new Map(); + if (this.getERAppointmentFeesList != null) { + data['GetERAppointmentFeesList'] = this.getERAppointmentFeesList.toJson(); + } + return data; + } +} + +class GetERAppointmentFeesList { + String amount; + String companyName; + bool isInsured; + bool isShowInsuranceUpdateModule; + String tax; + String total; + String currency; + + GetERAppointmentFeesList( + {this.amount, + this.companyName, + this.isInsured, + this.isShowInsuranceUpdateModule, + this.tax, + this.total, + this.currency}); + + GetERAppointmentFeesList.fromJson(Map json) { + amount = json['Amount']; + companyName = json['CompanyName']; + isInsured = json['IsInsured']; + isShowInsuranceUpdateModule = json['IsShowInsuranceUpdateModule']; + tax = json['Tax']; + total = json['Total']; + currency = json['currency']; + } + + Map toJson() { + final Map data = new Map(); + data['Amount'] = this.amount; + data['CompanyName'] = this.companyName; + data['IsInsured'] = this.isInsured; + data['IsShowInsuranceUpdateModule'] = this.isShowInsuranceUpdateModule; + data['Tax'] = this.tax; + data['Total'] = this.total; + data['currency'] = this.currency; + return data; + } +} diff --git a/lib/models/LiveCare/IncomingCallData.dart b/lib/models/LiveCare/IncomingCallData.dart new file mode 100644 index 00000000..9234c876 --- /dev/null +++ b/lib/models/LiveCare/IncomingCallData.dart @@ -0,0 +1,104 @@ +class IncomingCallData { + String msgID; + String notfID; + String notificationForeground; + String count; + String message; + String appointmentNo; + String title; + String projectID; + String notificationType; + String background; + String doctorname; + String clinicname; + String speciality; + String appointmentdate; + String appointmenttime; + String type; + String sessionId; + String identity; + String name; + String videoUrl; + String picture; + String isCall; + String sound; + + IncomingCallData( + {this.msgID, + this.notfID, + this.notificationForeground, + this.count, + this.message, + this.appointmentNo, + this.title, + this.projectID, + this.notificationType, + this.background, + this.doctorname, + this.clinicname, + this.speciality, + this.appointmentdate, + this.appointmenttime, + this.type, + this.sessionId, + this.identity, + this.name, + this.videoUrl, + this.picture, + this.isCall, + this.sound}); + + IncomingCallData.fromJson(Map json) { + msgID = json['msgID']; + notfID = json['notfID']; + notificationForeground = json['notification_foreground']; + count = json['count']; + message = json['message']; + appointmentNo = json['AppointmentNo']; + title = json['title']; + projectID = json['ProjectID']; + notificationType = json['NotificationType']; + background = json['background']; + doctorname = json['doctorname']; + clinicname = json['clinicname']; + speciality = json['speciality']; + appointmentdate = json['appointmentdate']; + appointmenttime = json['appointmenttime']; + type = json['type']; + sessionId = json['session_id']; + identity = json['identity']; + name = json['name']; + videoUrl = json['videoUrl']; + picture = json['picture']; + isCall = json['is_call']; + sound = json['sound']; + } + + Map toJson() { + final Map data = new Map(); + data['msgID'] = this.msgID; + data['notfID'] = this.notfID; + data['notification_foreground'] = this.notificationForeground; + data['count'] = this.count; + data['message'] = this.message; + data['AppointmentNo'] = this.appointmentNo; + data['title'] = this.title; + data['ProjectID'] = this.projectID; + data['NotificationType'] = this.notificationType; + data['background'] = this.background; + data['doctorname'] = this.doctorname; + data['clinicname'] = this.clinicname; + data['speciality'] = this.speciality; + data['appointmentdate'] = this.appointmentdate; + data['appointmenttime'] = this.appointmenttime; + data['type'] = this.type; + data['session_id'] = this.sessionId; + data['identity'] = this.identity; + data['name'] = this.name; + data['videoUrl'] = this.videoUrl; + data['picture'] = this.picture; + data['is_call'] = this.isCall; + data['sound'] = this.sound; + return data; + } +} diff --git a/lib/models/LiveCare/LiveCareClinicsListResponse.dart b/lib/models/LiveCare/LiveCareClinicsListResponse.dart new file mode 100644 index 00000000..094fb1a5 --- /dev/null +++ b/lib/models/LiveCare/LiveCareClinicsListResponse.dart @@ -0,0 +1,129 @@ +class LiveCareClinicsListResponse { + List patientERGetClinicsList; + + LiveCareClinicsListResponse({this.patientERGetClinicsList}); + + LiveCareClinicsListResponse.fromJson(Map json) { + if (json['PatientER_GetClinicsList'] != null) { + patientERGetClinicsList = new List(); + json['PatientER_GetClinicsList'].forEach((v) { + patientERGetClinicsList.add(new PatientERGetClinicsList.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.patientERGetClinicsList != null) { + data['PatientER_GetClinicsList'] = + this.patientERGetClinicsList.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class PatientERGetClinicsList { + int iD; + int serviceID; + String serviceName; + String serviceNameN; + int clinicID; + int age; + bool isCheckAgeBelow; + int gender; + bool isActive; + String createdOn; + String createdBy; + int isOnline; + Null endTime; + bool projectOutSA; + List shiftTimings; + Null startTime; + + PatientERGetClinicsList( + {this.iD, + this.serviceID, + this.serviceName, + this.serviceNameN, + this.clinicID, + this.age, + this.isCheckAgeBelow, + this.gender, + this.isActive, + this.createdOn, + this.createdBy, + this.isOnline, + this.endTime, + this.projectOutSA, + this.shiftTimings, + this.startTime}); + + PatientERGetClinicsList.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + serviceName = json['ServiceName']; + serviceNameN = json['ServiceNameN']; + clinicID = json['ClinicID']; + age = json['Age']; + isCheckAgeBelow = json['IsCheckAgeBelow']; + gender = json['Gender']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + isOnline = json['IsOnline']; + endTime = json['EndTime']; + projectOutSA = json['ProjectOutSA']; + if (json['ShiftTimings'] != null) { + shiftTimings = new List(); + json['ShiftTimings'].forEach((v) { + shiftTimings.add(new ShiftTimings.fromJson(v)); + }); + } + startTime = json['StartTime']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['ServiceName'] = this.serviceName; + data['ServiceNameN'] = this.serviceNameN; + data['ClinicID'] = this.clinicID; + data['Age'] = this.age; + data['IsCheckAgeBelow'] = this.isCheckAgeBelow; + data['Gender'] = this.gender; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['IsOnline'] = this.isOnline; + data['EndTime'] = this.endTime; + data['ProjectOutSA'] = this.projectOutSA; + if (this.shiftTimings != null) { + data['ShiftTimings'] = this.shiftTimings.map((v) => v.toJson()).toList(); + } + data['StartTime'] = this.startTime; + return data; + } +} + +class ShiftTimings { + String endTime; + int shiftID; + String startTime; + + ShiftTimings({this.endTime, this.shiftID, this.startTime}); + + ShiftTimings.fromJson(Map json) { + endTime = json['EndTime']; + shiftID = json['ShiftID']; + startTime = json['StartTime']; + } + + Map toJson() { + final Map data = new Map(); + data['EndTime'] = this.endTime; + data['ShiftID'] = this.shiftID; + data['StartTime'] = this.startTime; + return data; + } +} diff --git a/lib/models/LiveCare/room_model.dart b/lib/models/LiveCare/room_model.dart new file mode 100644 index 00000000..6ab64d1d --- /dev/null +++ b/lib/models/LiveCare/room_model.dart @@ -0,0 +1,65 @@ +import 'package:diplomaticquarterapp/models/LiveCare/room_validators.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/twilio_enums.dart'; + +class RoomModel with RoomValidators { + final String name; + final bool isLoading; + final bool isSubmitted; + final String token; + final String identity; + final TwilioRoomType type; + + RoomModel({ + this.name, + this.isLoading = false, + this.isSubmitted = false, + this.token, + this.identity, + this.type = TwilioRoomType.groupSmall, + }); + + static String getTypeText(TwilioRoomType type) { + switch (type) { + case TwilioRoomType.peerToPeer: + return 'peer 2 peer'; + break; + case TwilioRoomType.group: + return 'large (max 50 participants)'; + break; + case TwilioRoomType.groupSmall: + return 'small (max 4 participants)'; + break; + } + return ''; + } + + String get nameErrorText { + return isSubmitted && !nameValidator.isValid(name) ? invalidNameErrorText : null; + } + + String get typeText { + return RoomModel.getTypeText(type); + } + + bool get canSubmit { + return nameValidator.isValid(name); + } + + RoomModel copyWith({ + String name, + bool isLoading, + bool isSubmitted, + String token, + String identity, + TwilioRoomType type, + }) { + return RoomModel( + name: name ?? this.name, + token: token ?? this.token, + identity: identity ?? this.identity, + isLoading: isLoading ?? this.isLoading, + isSubmitted: isSubmitted ?? this.isSubmitted, + type: type ?? this.type, + ); + } +} diff --git a/lib/models/LiveCare/room_validators.dart b/lib/models/LiveCare/room_validators.dart new file mode 100644 index 00000000..44fe95f0 --- /dev/null +++ b/lib/models/LiveCare/room_validators.dart @@ -0,0 +1,6 @@ +import 'package:diplomaticquarterapp/models/LiveCare/validators.dart'; + +mixin RoomValidators { + final StringValidator nameValidator = NonEmptyStringValidator(); + final String invalidNameErrorText = 'Room name can\'t be empty'; +} diff --git a/lib/models/LiveCare/twilio_enums.dart b/lib/models/LiveCare/twilio_enums.dart new file mode 100644 index 00000000..4d4f6f69 --- /dev/null +++ b/lib/models/LiveCare/twilio_enums.dart @@ -0,0 +1,17 @@ +enum TwilioRoomType { + peerToPeer, + group, + groupSmall, +} + +enum TwilioRoomStatus { + completed, + inProgress, +} + +enum TwilioStatusCallbackMethod { + GET, + POST, +} + +enum TwilioVideoCodec { VP8, H264 } diff --git a/lib/models/LiveCare/validators.dart b/lib/models/LiveCare/validators.dart new file mode 100644 index 00000000..aa42f231 --- /dev/null +++ b/lib/models/LiveCare/validators.dart @@ -0,0 +1,13 @@ +abstract class StringValidator { + bool isValid(String value); +} + +class NonEmptyStringValidator implements StringValidator { + @override + bool isValid(String value) { + if (value == null) { + return false; + } + return value.isNotEmpty; + } +} diff --git a/lib/pages/BookAppointment/BookConfirm.dart b/lib/pages/BookAppointment/BookConfirm.dart index a31292fa..8c1d781c 100644 --- a/lib/pages/BookAppointment/BookConfirm.dart +++ b/lib/pages/BookAppointment/BookConfirm.dart @@ -13,6 +13,7 @@ import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:rating_bar/rating_bar.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import 'BookSuccess.dart'; @@ -33,7 +34,6 @@ class BookConfirm extends StatefulWidget { PatientShareResponse patientShareResponse; AuthenticatedUser authUser; - @override _BookConfirmState createState() => _BookConfirmState(); } @@ -303,14 +303,19 @@ class _BookConfirmState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(widget.authUser.firstName + " " + widget.authUser.lastName, + Text( + widget.authUser.firstName + + " " + + widget.authUser.lastName, style: TextStyle( fontSize: 14.0, color: Colors.grey[900], letterSpacing: 1.0)), Container( margin: EdgeInsets.only(top: 5.0), - child: Text("Gender: " + widget.authUser.genderDescription, + child: Text( + "Gender: " + + widget.authUser.genderDescription, style: TextStyle( fontSize: 12.0, color: Colors.grey[600], @@ -318,7 +323,8 @@ class _BookConfirmState extends State { ), Container( margin: EdgeInsets.only(top: 5.0, bottom: 3.0), - child: Text("Age: " + widget.authUser.age.toString(), + child: Text( + "Age: " + widget.authUser.age.toString(), style: TextStyle( fontSize: 12.0, color: Colors.grey[600], @@ -352,7 +358,6 @@ class _BookConfirmState extends State { disabledTextColor: Colors.white, disabledColor: new Color(0xFFbcc2c4), onPressed: () { -// navigateToBookSuccess(context); insertAppointment(context, widget.doctor); }, child: Text(TranslationBase.of(context).bookNow, @@ -363,31 +368,43 @@ class _BookConfirmState extends State { ); } - cancelAppointment(DoctorList docObject, AppoitmentAllHistoryResultList appo, BuildContext context) { + cancelAppointment(DoctorList docObject, AppoitmentAllHistoryResultList appo, + BuildContext context) { ConfirmDialog.closeAlertDialog(context); DoctorsListService service = new DoctorsListService(); service.cancelAppointment(appo, context).then((res) { if (res['MessageStatus'] == 1) { - insertAppointment(context, docObject); + Future.delayed(new Duration(milliseconds: 1500), () { + insertAppointment(context, docObject); + }); } else { AppToast.showErrorToast(message: res['ErrorEndUserMessage']); } }).catchError((err) { print(err); - }); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } insertAppointment(context, DoctorList docObject) { AppoitmentAllHistoryResultList appo; widget.service - .insertAppointment(docObject.doctorID, docObject.clinicID, - docObject.projectID, widget.selectedTime, widget.selectedDate, context) + .insertAppointment( + docObject.doctorID, + docObject.clinicID, + docObject.projectID, + widget.selectedTime, + widget.selectedDate, + context) .then((res) { if (res['MessageStatus'] == 1) { AppToast.showSuccessToast(message: "Appointment Booked Successfully"); print(res['AppointmentNo']); - getPatientShare(context, res['AppointmentNo'], docObject.clinicID, - docObject.projectID, docObject); + + Future.delayed(new Duration(milliseconds: 1800), () { + getPatientShare(context, res['AppointmentNo'], docObject.clinicID, + docObject.projectID, docObject); + }); } else { appo = new AppoitmentAllHistoryResultList(); appo.appointmentNo = res['SameClinicApptList'][0]['AppointmentNo']; @@ -411,8 +428,10 @@ class _BookConfirmState extends State { dialog.showAlertDialog(context); } }).catchError((err) { + AppToast.showErrorToast(message: err); print(err); - }); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } getPatientShare(context, String appointmentNo, int clinicID, int projectID, @@ -425,7 +444,8 @@ class _BookConfirmState extends State { navigateToBookSuccess(context, docObject, widget.patientShareResponse); }).catchError((err) { print(err); - }); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } String getTime(DateTime dateTime) { @@ -471,8 +491,8 @@ class _BookConfirmState extends State { getPatientData() async { AppSharedPreferences sharedPref = AppSharedPreferences(); if (await sharedPref.getObject(USER_PROFILE) != null) { - var data = AuthenticatedUser.fromJson( - await sharedPref.getObject(USER_PROFILE)); + var data = + AuthenticatedUser.fromJson(await sharedPref.getObject(USER_PROFILE)); setState(() { print(data); widget.authUser = data; diff --git a/lib/pages/BookAppointment/BookSuccess.dart b/lib/pages/BookAppointment/BookSuccess.dart index 163c42e2..7a19ea28 100644 --- a/lib/pages/BookAppointment/BookSuccess.dart +++ b/lib/pages/BookAppointment/BookSuccess.dart @@ -12,6 +12,7 @@ import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart'; import 'package:diplomaticquarterapp/widgets/in_app_browser/InAppBrowser.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import 'QRCode.dart'; @@ -506,7 +507,6 @@ class _BookSuccessState extends State { if (url.contains(element)) { if (widget.browser.isOpened()) widget.browser.close(); MyInAppBrowser.isPaymentDone = true; - Utils.hideProgressDialog(); return; } }); @@ -515,7 +515,6 @@ class _BookSuccessState extends State { if (url.contains(element)) { if (widget.browser.isOpened()) widget.browser.close(); MyInAppBrowser.isPaymentDone = false; - Utils.hideProgressDialog(); return; } }); @@ -544,7 +543,7 @@ class _BookSuccessState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } createAdvancePayment(res, AppoitmentAllHistoryResultList appo) { @@ -561,7 +560,7 @@ class _BookSuccessState extends State { appo.appointmentNo.toString()); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } // // Future navigateToQR( @@ -586,7 +585,7 @@ class _BookSuccessState extends State { getAppoQR(context); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Widget _getQRAppo() { @@ -743,7 +742,7 @@ class _BookSuccessState extends State { navigateToQR(context, res['AppointmentQR']); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToQR(context, String appoQR) async { diff --git a/lib/pages/BookAppointment/DoctorProfile.dart b/lib/pages/BookAppointment/DoctorProfile.dart index 42f2b3e8..b9f6e566 100644 --- a/lib/pages/BookAppointment/DoctorProfile.dart +++ b/lib/pages/BookAppointment/DoctorProfile.dart @@ -1,7 +1,12 @@ +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/models/Appointments/DoctorListResponse.dart'; import 'package:diplomaticquarterapp/models/Appointments/DoctorProfile.dart'; +import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/routes.dart'; +import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/dialogs/confirm_dialog.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:rating_bar/rating_bar.dart'; @@ -14,6 +19,8 @@ class DoctorProfile extends StatefulWidget { DoctorList doctor; DoctorProfileList docProfileList; + AuthenticatedUser authUser; + DoctorProfile({@required this.doctor, @required this.docProfileList}); @override @@ -28,6 +35,8 @@ class _DoctorProfileState extends State @override void initState() { _tabController = new TabController(length: 2, vsync: this); + widget.authUser = new AuthenticatedUser(); + getPatientData(); super.initState(); } @@ -175,13 +184,43 @@ class _DoctorProfileState extends State ); } + getPatientData() async { + AppSharedPreferences sharedPref = AppSharedPreferences(); + if (await sharedPref.getObject(USER_PROFILE) != null) { + var data = + AuthenticatedUser.fromJson(await sharedPref.getObject(USER_PROFILE)); + setState(() { + print(data); + widget.authUser = data; + }); + } + } + void goToBookConfirm() { - if (DocAvailableAppointments.areSlotsAvailable) - navigateToBookConfirm(context); - else + if (DocAvailableAppointments.areSlotsAvailable) { + if (widget.authUser.patientID != null) { + navigateToBookConfirm(context); + } else { + ConfirmDialog dialog = new ConfirmDialog( + context: context, + confirmMessage: "You have to login to use this service", + okText: TranslationBase.of(context).confirm, + cancelText: TranslationBase.of(context).cancel_nocaps, + okFunction: () => {navigateToLogin()}, + cancelFunction: () => {}); + dialog.showAlertDialog(context); + } + } else AppToast.showErrorToast(message: "Please select Time Slot to continue"); } + navigateToLogin() { + ConfirmDialog.closeAlertDialog(context); + Navigator.of(context).pushNamed( + WELCOME_LOGIN, + ); + } + Future navigateToBookConfirm(context) async { Navigator.push( context, diff --git a/lib/pages/BookAppointment/QRCode.dart b/lib/pages/BookAppointment/QRCode.dart index a850437b..c632a26e 100644 --- a/lib/pages/BookAppointment/QRCode.dart +++ b/lib/pages/BookAppointment/QRCode.dart @@ -14,6 +14,7 @@ import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/dialogs/confirm_dialog.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; class QRCode extends StatefulWidget { PatientShareResponse patientShareResponse; @@ -251,7 +252,7 @@ class _QRCodeState extends State { ConfirmDialog.closeAlertDialog(context); AppToast.showErrorToast(message: err); print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); }, cancelFunction: () => {}); dialog.showAlertDialog(context); diff --git a/lib/pages/BookAppointment/components/DocAvailableAppointments.dart b/lib/pages/BookAppointment/components/DocAvailableAppointments.dart index 19c7cc79..3c5e49ff 100644 --- a/lib/pages/BookAppointment/components/DocAvailableAppointments.dart +++ b/lib/pages/BookAppointment/components/DocAvailableAppointments.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:table_calendar/table_calendar.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import '../../../uitl/date_uitl.dart'; @@ -339,7 +340,7 @@ class _DocAvailableAppointmentsState extends State } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Widget _buildEventsMarker(DateTime date, List events) { diff --git a/lib/pages/BookAppointment/components/SearchByClinic.dart b/lib/pages/BookAppointment/components/SearchByClinic.dart index 63053077..edfa9cd1 100644 --- a/lib/pages/BookAppointment/components/SearchByClinic.dart +++ b/lib/pages/BookAppointment/components/SearchByClinic.dart @@ -5,6 +5,7 @@ import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import '../SearchResults.dart'; @@ -96,7 +97,7 @@ class _SearchByClinicState extends State { } else {} }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } getDoctorsList(BuildContext context) { @@ -118,7 +119,7 @@ class _SearchByClinicState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToSearchResults(context, docList) async { diff --git a/lib/pages/BookAppointment/components/SearchByDoctor.dart b/lib/pages/BookAppointment/components/SearchByDoctor.dart index 668426ad..e1afea54 100644 --- a/lib/pages/BookAppointment/components/SearchByDoctor.dart +++ b/lib/pages/BookAppointment/components/SearchByDoctor.dart @@ -3,6 +3,7 @@ import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsLis import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import '../SearchResults.dart'; @@ -94,7 +95,7 @@ class _SearchByDoctorState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } _onDocTextChanged(content) { diff --git a/lib/pages/BookAppointment/widgets/DoctorView.dart b/lib/pages/BookAppointment/widgets/DoctorView.dart index 031f53c3..38838917 100644 --- a/lib/pages/BookAppointment/widgets/DoctorView.dart +++ b/lib/pages/BookAppointment/widgets/DoctorView.dart @@ -4,6 +4,7 @@ import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsLis import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:flutter/material.dart'; import 'package:rating_bar/rating_bar.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import '../DoctorProfile.dart'; @@ -130,7 +131,7 @@ class DoctorView extends StatelessWidget { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToDoctorProfile(context, docObject, docProfile) async { diff --git a/lib/pages/MyAppointments/MyAppointments.dart b/lib/pages/MyAppointments/MyAppointments.dart index acd6fba9..63034373 100644 --- a/lib/pages/MyAppointments/MyAppointments.dart +++ b/lib/pages/MyAppointments/MyAppointments.dart @@ -6,6 +6,7 @@ import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; class MyAppointments extends StatefulWidget { List appoList = []; @@ -93,7 +94,7 @@ class _MyAppointmentsState extends State } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } bool isConfirmed(AppoitmentAllHistoryResultList appo) { diff --git a/lib/pages/MyAppointments/VisitTicket.dart b/lib/pages/MyAppointments/VisitTicket.dart index 923ef67d..54dcf13f 100644 --- a/lib/pages/MyAppointments/VisitTicket.dart +++ b/lib/pages/MyAppointments/VisitTicket.dart @@ -6,6 +6,7 @@ import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:diplomaticquarterapp/widgets/progress_indicator/app_circular_progress_Indeicator.dart'; import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; class VisitTicket extends StatefulWidget { List appoList = []; @@ -68,7 +69,7 @@ class _VisitTicketState extends State { }).catchError((err) { print(err); loading(false); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } loading(bool flag) { diff --git a/lib/pages/MyAppointments/models/ArrivedButtons.dart b/lib/pages/MyAppointments/models/ArrivedButtons.dart index df7ee3c8..4a288730 100644 --- a/lib/pages/MyAppointments/models/ArrivedButtons.dart +++ b/lib/pages/MyAppointments/models/ArrivedButtons.dart @@ -30,8 +30,8 @@ class ArrivedButtons { { "title": "Vital Signs", "subtitle": "Values", - "icon": "assets/images/new-design/location_icon.png", - "caller": "navigateToProject" + "icon": "assets/images/new-design/vital_signs.png", + "caller": "VitalSigns" }, { "title": TranslationBase.of(AppGlobal.context).raise, diff --git a/lib/pages/MyAppointments/models/ArrivedButtonsAllowCheckIn.dart b/lib/pages/MyAppointments/models/ArrivedButtonsAllowCheckIn.dart index 2e1034b7..de9bc564 100644 --- a/lib/pages/MyAppointments/models/ArrivedButtonsAllowCheckIn.dart +++ b/lib/pages/MyAppointments/models/ArrivedButtonsAllowCheckIn.dart @@ -30,8 +30,8 @@ class ArrivedButtons { { "title": "Vital Signs", "subtitle": "Values", - "icon": "assets/images/new-design/location_icon.png", - "caller": "navigateToProject" + "icon": "assets/images/new-design/vital_signs.png", + "caller": "VitalSigns" }, { "title": TranslationBase.of(AppGlobal.context).raise, diff --git a/lib/pages/MyAppointments/widgets/AppointmentActions.dart b/lib/pages/MyAppointments/widgets/AppointmentActions.dart index 1176140b..a876160f 100644 --- a/lib/pages/MyAppointments/widgets/AppointmentActions.dart +++ b/lib/pages/MyAppointments/widgets/AppointmentActions.dart @@ -14,10 +14,11 @@ import 'package:diplomaticquarterapp/pages/MyAppointments/widgets/askDocDialog.d import 'package:diplomaticquarterapp/pages/MyAppointments/widgets/reminder_dialog.dart'; import 'package:diplomaticquarterapp/pages/insurance/insurance_approval_screen.dart'; import 'package:diplomaticquarterapp/pages/medical/radiology/radiology_details_page.dart'; +import 'package:diplomaticquarterapp/pages/medical/vital_sign/vital_sign_details_screen.dart'; import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsList.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; -import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import 'package:diplomaticquarterapp/widgets/dialogs/confirm_dialog.dart'; import 'package:diplomaticquarterapp/widgets/in_app_browser/InAppBrowser.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; @@ -184,6 +185,10 @@ class _AppointmentActionsState extends State { case "Insurance": navigateToInsuranceApprovals(widget.appo.appointmentNo); break; + + case "VitalSigns": + navigateToVitalSigns(widget.appo.appointmentNo, widget.appo.projectID); + break; } } @@ -352,7 +357,7 @@ class _AppointmentActionsState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } openAppointmentRadiology() { @@ -369,7 +374,7 @@ class _AppointmentActionsState extends State { }).catchError((err) { print(err); AppToast.showErrorToast(message: err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } openPrescriptionReport() { @@ -389,7 +394,7 @@ class _AppointmentActionsState extends State { }).catchError((err) { print(err); AppToast.showErrorToast(message: err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToMedicinePrescriptionReport( @@ -455,7 +460,7 @@ class _AppointmentActionsState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } getCallRequestType() { @@ -465,19 +470,15 @@ class _AppointmentActionsState extends State { res['ListReqTypes'].forEach((element) { requestData.add(new AskDocRequestType.fromJson(element)); }); -// print(requestData.length); - Utils.hideProgressDialog(); Future.delayed(const Duration(milliseconds: 400), () { showAskDocRequestDialog(requestData); }); }).catchError((err) { print(err); - Utils.hideProgressDialog(); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } showAskDocRequestDialog(List requestData) { - Utils.hideProgressDialog(); showGeneralDialog( barrierColor: Colors.black.withOpacity(0.5), transitionBuilder: (context, a1, a2, widget) { @@ -519,7 +520,7 @@ class _AppointmentActionsState extends State { }).catchError((err) { print(err); AppToast.showErrorToast(message: err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } confirmAppointment() { @@ -536,7 +537,7 @@ class _AppointmentActionsState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } navigateToInsuranceApprovals(int appoNo) { @@ -544,6 +545,11 @@ class _AppointmentActionsState extends State { context, FadePage(page: InsuranceApproval(appointmentNo: appoNo))); } + navigateToVitalSigns(int appoNo, int projectID) { + Navigator.push( + context, FadePage(page: VitalSignDetailsScreen(appointmentNo: appoNo, projectID: projectID))); + } + rateAppointment() { widget.browser = new MyInAppBrowser(); widget.browser.openBrowser('http://hmg.com/SitePages/pso.aspx?p=' + diff --git a/lib/pages/MyAppointments/widgets/PrescriptionReport.dart b/lib/pages/MyAppointments/widgets/PrescriptionReport.dart index ae19a900..932211ba 100644 --- a/lib/pages/MyAppointments/widgets/PrescriptionReport.dart +++ b/lib/pages/MyAppointments/widgets/PrescriptionReport.dart @@ -4,7 +4,7 @@ import 'package:diplomaticquarterapp/models/Appointments/AppoimentAllHistoryResu import 'package:diplomaticquarterapp/pages/medical/prescriptions/prescription_details_page.dart'; import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsList.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; -import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; import 'package:diplomaticquarterapp/widgets/buttons/button.dart'; import 'package:diplomaticquarterapp/widgets/data_display/text.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; @@ -115,12 +115,10 @@ class _PrescriptionReportState extends State { DoctorsListService service = new DoctorsListService(); service.sendPrescriptionEmail(widget.appo.appointmentDate, widget.appo.setupID, widget.listPres, context).then((res) { AppToast.showSuccessToast(message: 'A copy has been sent to the e-mail'); - Utils.hideProgressDialog(); }).catchError((err) { print(err); - Utils.hideProgressDialog(); AppToast.showErrorToast(message: err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } navigateToPrescriptionDetails(PrescriptionReportEnh prescriptionReportEnh) { diff --git a/lib/pages/ToDoList/ToDo.dart b/lib/pages/ToDoList/ToDo.dart index 24b7aa74..dc96864a 100644 --- a/lib/pages/ToDoList/ToDo.dart +++ b/lib/pages/ToDoList/ToDo.dart @@ -13,11 +13,11 @@ import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart'; -import 'package:diplomaticquarterapp/widgets/bottom_navigation/bottom_nav_bar.dart'; import 'package:diplomaticquarterapp/widgets/in_app_browser/InAppBrowser.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:rating_bar/rating_bar.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; class ToDo extends StatefulWidget { PatientShareResponse patientShareResponse; @@ -443,7 +443,8 @@ class _ToDoState extends State { } }).catchError((err) { print(err); - }); + AppToast.showErrorToast(message: err); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } getPatientShare(context, AppoitmentAllHistoryResultList appo) { @@ -456,7 +457,7 @@ class _ToDoState extends State { openPaymentDialog(appo, widget.patientShareResponse); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } getAppoQR(context, AppoitmentAllHistoryResultList appo) { @@ -477,7 +478,7 @@ class _ToDoState extends State { navigateToQR(context, res['AppointmentQR'], patientShareResponse); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToQR( @@ -555,7 +556,6 @@ class _ToDoState extends State { if (url.contains(element)) { if (widget.browser.isOpened()) widget.browser.close(); MyInAppBrowser.isPaymentDone = true; - Utils.hideProgressDialog(); return; } }); @@ -564,7 +564,6 @@ class _ToDoState extends State { if (url.contains(element)) { if (widget.browser.isOpened()) widget.browser.close(); MyInAppBrowser.isPaymentDone = false; - Utils.hideProgressDialog(); return; } }); @@ -593,7 +592,7 @@ class _ToDoState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } createAdvancePayment(res, AppoitmentAllHistoryResultList appo) { @@ -611,7 +610,7 @@ class _ToDoState extends State { appo); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } addAdvancedNumberRequest(String advanceNumber, String paymentReference, @@ -625,7 +624,7 @@ class _ToDoState extends State { getAppoQR(context, appo); }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } Future navigateToPaymentMethod( @@ -671,6 +670,6 @@ class _ToDoState extends State { } }).catchError((err) { print(err); - }); + }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); } } diff --git a/lib/pages/conference/clipped_video.dart b/lib/pages/conference/clipped_video.dart new file mode 100644 index 00000000..1c01c27c --- /dev/null +++ b/lib/pages/conference/clipped_video.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class ClippedVideo extends StatefulWidget { + final double width; + final double height; + final Widget child; + + const ClippedVideo({ + Key key, + @required this.width, + @required this.height, + @required this.child, + }) : super(key: key); + + @override + _ClippedVideoState createState() => _ClippedVideoState(); +} + +class _ClippedVideoState extends State { + @override + Widget build(BuildContext context) { + return Container( + width: widget.width, + height: widget.height, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + border: Border.all( + color: Colors.white24, + ), + ), + child: ClipRRect( + child: widget.child, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + ); + } +} diff --git a/lib/pages/conference/conference_button_bar.dart b/lib/pages/conference/conference_button_bar.dart new file mode 100644 index 00000000..d9abd212 --- /dev/null +++ b/lib/pages/conference/conference_button_bar.dart @@ -0,0 +1,238 @@ +import 'dart:async'; + +import 'package:after_layout/after_layout.dart'; +import 'package:diplomaticquarterapp/pages/conference/widgets/circle_button.dart'; +import 'package:flutter/material.dart'; + +class ConferenceButtonBar extends StatefulWidget { + final VoidCallback onVideoEnabled; + final VoidCallback onAudioEnabled; + final VoidCallback onHangup; + final VoidCallback onSwitchCamera; + final VoidCallback onPersonAdd; + final VoidCallback onPersonRemove; + final void Function(double) onHeight; + final VoidCallback onHide; + final VoidCallback onShow; + final Stream videoEnabled; + final Stream audioEnabled; + + const ConferenceButtonBar({ + Key key, + this.onVideoEnabled, + this.onAudioEnabled, + this.onHangup, + this.onSwitchCamera, + this.onPersonAdd, + this.onPersonRemove, + @required this.videoEnabled, + @required this.audioEnabled, + this.onHeight, + this.onHide, + this.onShow, + }) : assert(videoEnabled != null), + assert(audioEnabled != null), + super(key: key); + + @override + _ConferenceButtonBarState createState() => _ConferenceButtonBarState(); +} + +class _ConferenceButtonBarState extends State with AfterLayoutMixin { + var _bottom = -100.0; + Timer _timer; + int _remaining; + var _videoEnabled = true; + var _audioEnabled = true; + double _hidden; + double _visible; + final _keyButtonBarHeight = GlobalKey(); + + final Duration timeout = const Duration(seconds: 5); + final Duration ms = const Duration(milliseconds: 1); + final Duration periodicDuration = const Duration(milliseconds: 100); + + Timer startTimeout([int milliseconds]) { + final duration = milliseconds == null ? timeout : ms * milliseconds; + _remaining = duration.inMilliseconds; + return Timer.periodic(periodicDuration, (Timer timer) { + _remaining -= periodicDuration.inMilliseconds; + if (_remaining <= 0) { + timer.cancel(); + _toggleBar(); + } + }); + } + + void _pauseTimer() { + if (_timer == null) { + return; + } + _timer.cancel(); + _timer = null; + } + + void _resumeTimer() { + // resume the timer only when there is no timer active or when + // the bar is not already hidden. + if ((_timer != null && _timer.isActive) || _bottom == _hidden) { + return; + } + _timer = startTimeout(_remaining); + } + + void _toggleBar() { + setState(() { + _bottom = _bottom == _visible ? _hidden : _visible; + if (_bottom == _visible && widget.onShow != null) { + widget.onShow(); + } + if (_bottom == _hidden && widget.onHide != null) { + widget.onHide(); + } + }); + } + + void _toggleBarOnEnd() { + if (_timer != null) { + if (_timer.isActive) { + _timer.cancel(); + } + _timer = null; + } + if (_bottom == 0) { + _timer = startTimeout(); + } + } + + @override + void initState() { + super.initState(); + _timer = startTimeout(); + } + + @override + void didChangeDependencies() { + _visible = MediaQuery.of(context).viewPadding.bottom; + super.didChangeDependencies(); + } + + @override + void afterFirstLayout(BuildContext context) { + final RenderBox renderBoxButtonBar = _keyButtonBarHeight.currentContext.findRenderObject(); + final heightButtonBar = renderBoxButtonBar.size.height; + // Because the `didChangeDependencies` fires before the `afterFirstLayout`, we can use the `_visible` property here. + _hidden = -(heightButtonBar + _visible); + widget.onHeight(heightButtonBar); + _toggleBar(); + } + + @override + void dispose() { + super.dispose(); + if (_timer != null && _timer.isActive) { + _timer.cancel(); + _timer = null; + } + } + + @override + Widget build(BuildContext context) { + return Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: GestureDetector( + key: Key('show-hide-button-bar-gesture'), + behavior: HitTestBehavior.translucent, + onTapDown: (_) => _pauseTimer(), + onTapUp: (_) => _toggleBar(), + onTapCancel: () => _resumeTimer(), + child: Stack( + children: [ + AnimatedPositioned( + key: Key('button-bar'), + bottom: _bottom, + left: 0, + right: 0, + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + child: _buildRow(context), + onEnd: _toggleBarOnEnd, + ), + ], + ), + ), + ); + } + + void _onPressed(VoidCallback callback) { + if (callback != null) { + callback(); + } + if (_timer != null && _timer.isActive) { + _timer.cancel(); + } + _timer = startTimeout(); + } + + Widget _buildRow(BuildContext context) { + return Padding( + key: _keyButtonBarHeight, + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + CircleButton( + child: StreamBuilder( + stream: widget.videoEnabled, + initialData: _videoEnabled, + builder: (context, snapshot) { + _videoEnabled = snapshot.data; + return Icon( + _videoEnabled ? Icons.videocam : Icons.videocam_off, + color: Colors.white, + ); + }), + key: Key('camera-button'), + onPressed: () => _onPressed(widget.onVideoEnabled), + ), + CircleButton( + child: StreamBuilder( + stream: widget.audioEnabled, + initialData: _audioEnabled, + builder: (context, snapshot) { + _audioEnabled = snapshot.data; + return Icon( + _audioEnabled ? Icons.mic : Icons.mic_off, + color: Colors.white, + ); + }), + key: Key('microphone-button'), + onPressed: () => _onPressed(widget.onAudioEnabled), + ), + CircleButton( + radius: 35, + child: const RotationTransition( + turns: AlwaysStoppedAnimation(135 / 360), + child: Icon( + Icons.phone, + color: Colors.white, + size: 40, + ), + ), + color: Colors.red.withAlpha(200), + key: Key('hangup-button'), + onPressed: () => _onPressed(widget.onHangup), + ), + CircleButton( + child: const Icon(Icons.switch_camera, color: Colors.white), + key: Key('switch-camera-button'), + onPressed: () => _onPressed(widget.onSwitchCamera), + ), + ], + ), + ); + } +} diff --git a/lib/pages/conference/conference_page.dart b/lib/pages/conference/conference_page.dart new file mode 100644 index 00000000..60ffb0d5 --- /dev/null +++ b/lib/pages/conference/conference_page.dart @@ -0,0 +1,388 @@ +import 'dart:async'; + +import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart'; +import 'package:diplomaticquarterapp/pages/conference/conference_button_bar.dart'; +import 'package:diplomaticquarterapp/pages/conference/conference_room.dart'; +import 'package:diplomaticquarterapp/pages/conference/draggable_publisher.dart'; +import 'package:diplomaticquarterapp/pages/conference/participant_widget.dart'; +import 'package:diplomaticquarterapp/pages/conference/widgets/noise_box.dart'; +import 'package:diplomaticquarterapp/pages/conference/widgets/platform_alert_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:wakelock/wakelock.dart'; + +class ConferencePage extends StatefulWidget { + final RoomModel roomModel; + + const ConferencePage({Key key, this.roomModel}) : super(key: key); + + @override + _ConferencePageState createState() => _ConferencePageState(); +} + +class _ConferencePageState extends State { + final StreamController _onButtonBarVisibleStreamController = StreamController.broadcast(); + final StreamController _onButtonBarHeightStreamController = StreamController.broadcast(); + ConferenceRoom _conferenceRoom; + StreamSubscription _onConferenceRoomException; + + @override + void initState() { + super.initState(); + _lockInPortrait(); + _connectToRoom(); + _wakeLock(true); + } + + void _connectToRoom() async { + try { + final conferenceRoom = ConferenceRoom( + name: widget.roomModel.name, + token: widget.roomModel.token, + identity: widget.roomModel.identity, + ); + await conferenceRoom.connect(); + setState(() { + _conferenceRoom = conferenceRoom; + _onConferenceRoomException = _conferenceRoom.onException.listen((err) async { + await PlatformAlertDialog( + title: err is PlatformException ? err.message : 'An error occured', + content: err is PlatformException ? err.details : err.toString(), + defaultActionText: 'OK', + ).show(context); + }); + _conferenceRoom.addListener(_conferenceRoomUpdated); + }); + } catch (err) { + print(err); + await PlatformAlertDialog( + title: err is PlatformException ? err.message : 'An error occured', + content: err is PlatformException ? err.details : err.toString(), + defaultActionText: 'OK', + ).show(context); + + Navigator.of(context).pop(); + } + } + + Future _lockInPortrait() async { + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + } + + @override + void dispose() { + _freePortraitLock(); + _wakeLock(false); + _disposeStreamsAndSubscriptions(); + if (_conferenceRoom != null) _conferenceRoom.removeListener(_conferenceRoomUpdated); + super.dispose(); + } + + Future _freePortraitLock() async { + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeRight, + DeviceOrientation.landscapeLeft, + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + } + + Future _disposeStreamsAndSubscriptions() async { + if (_onButtonBarVisibleStreamController != null) await _onButtonBarVisibleStreamController.close(); + if (_onButtonBarHeightStreamController != null) await _onButtonBarHeightStreamController.close(); + if (_onConferenceRoomException != null) await _onConferenceRoomException.cancel(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: Scaffold( + backgroundColor: Colors.black, + body: _conferenceRoom == null ? showProgress() : buildLayout(), + ), + ); + } + + LayoutBuilder buildLayout() { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Stack( + children: [ + _buildParticipants(context, constraints.biggest, _conferenceRoom), + ConferenceButtonBar( + audioEnabled: _conferenceRoom.onAudioEnabled, + videoEnabled: _conferenceRoom.onVideoEnabled, + onAudioEnabled: _conferenceRoom.toggleAudioEnabled, + onVideoEnabled: _conferenceRoom.toggleVideoEnabled, + onHangup: _onHangup, + onSwitchCamera: _conferenceRoom.switchCamera, + onPersonAdd: _onPersonAdd, + onPersonRemove: _onPersonRemove, + onHeight: _onHeightBar, + onShow: _onShowBar, + onHide: _onHideBar, + ), + ], + ); + }, + ); + } + + Widget showProgress() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center(child: CircularProgressIndicator()), + SizedBox( + height: 10, + ), + Text( + 'Connecting to the call...', + style: TextStyle(color: Colors.white), + ), + ], + ); + } + + Future _onHangup() async { + print('onHangup'); + await _conferenceRoom.disconnect(); + Navigator.of(context).pop(); + } + + void _onPersonAdd() { + print('onPersonAdd'); + try { + _conferenceRoom.addDummy( + child: Stack( + children: [ + const Placeholder(), + Center( + child: Text( + (_conferenceRoom.participants.length + 1).toString(), + style: const TextStyle( + shadows: [ + Shadow( + blurRadius: 3.0, + color: Color.fromARGB(255, 0, 0, 0), + ), + Shadow( + blurRadius: 8.0, + color: Color.fromARGB(255, 255, 255, 255), + ), + ], + fontSize: 80, + ), + ), + ), + ], + ), + ); + } on PlatformException catch (err) { + PlatformAlertDialog( + title: err.message, + content: err.details, + defaultActionText: 'OK', + ).show(context); + } + } + + void _onPersonRemove() { + print('onPersonRemove'); + _conferenceRoom.removeDummy(); + } + + Widget _buildParticipants(BuildContext context, Size size, ConferenceRoom conferenceRoom) { + final children = []; + final length = conferenceRoom.participants.length; + + if (length <= 2) { + _buildOverlayLayout(context, size, children); + return Stack(children: children); + } + + void buildInCols(bool removeLocalBeforeChunking, bool moveLastOfEachRowToNextRow, int columns) { + _buildLayoutInGrid( + context, + size, + children, + removeLocalBeforeChunking: removeLocalBeforeChunking, + moveLastOfEachRowToNextRow: moveLastOfEachRowToNextRow, + columns: columns, + ); + } + + if (length <= 3) { + buildInCols(true, false, 1); + } else if (length == 5) { + buildInCols(false, true, 2); + } else if (length <= 6 || length == 8) { + buildInCols(false, false, 2); + } else if (length == 7 || length == 9) { + buildInCols(true, false, 2); + } else if (length == 10) { + buildInCols(false, true, 3); + } else if (length == 13 || length == 16) { + buildInCols(true, false, 3); + } else if (length <= 18) { + buildInCols(false, false, 3); + } + + return Column( + children: children, + ); + } + + void _buildOverlayLayout(BuildContext context, Size size, List children) { + final participants = _conferenceRoom.participants; + if (participants.length == 1) { + children.add(_buildNoiseBox()); + } else { + final remoteParticipant = participants.firstWhere((ParticipantWidget participant) => participant.isRemote, orElse: () => null); + if (remoteParticipant != null) { + children.add(remoteParticipant); + } + } + + final localParticipant = participants.firstWhere((ParticipantWidget participant) => !participant.isRemote, orElse: () => null); + if (localParticipant != null) { + children.add(DraggablePublisher( + key: Key('publisher'), + child: localParticipant, + availableScreenSize: size, + onButtonBarVisible: _onButtonBarVisibleStreamController.stream, + onButtonBarHeight: _onButtonBarHeightStreamController.stream, + )); + } + } + + void _buildLayoutInGrid( + BuildContext context, + Size size, + List children, { + bool removeLocalBeforeChunking = false, + bool moveLastOfEachRowToNextRow = false, + int columns = 2, + }) { + final participants = _conferenceRoom.participants; + ParticipantWidget localParticipant; + if (removeLocalBeforeChunking) { + localParticipant = participants.firstWhere((ParticipantWidget participant) => !participant.isRemote, orElse: () => null); + if (localParticipant != null) { + participants.remove(localParticipant); + } + } + final chunkedParticipants = chunk(array: participants, size: columns); + if (localParticipant != null) { + chunkedParticipants.last.add(localParticipant); + participants.add(localParticipant); + } + + if (moveLastOfEachRowToNextRow) { + for (var i = 0; i < chunkedParticipants.length - 1; i++) { + var participant = chunkedParticipants[i].removeLast(); + chunkedParticipants[i + 1].insert(0, participant); + } + } + + for (final participantChunk in chunkedParticipants) { + final rowChildren = []; + for (final participant in participantChunk) { + rowChildren.add( + Container( + width: size.width / participantChunk.length, + height: size.height / chunkedParticipants.length, + child: participant, + ), + ); + } + children.add( + Container( + height: size.height / chunkedParticipants.length, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: rowChildren, + ), + ), + ); + } + } + + NoiseBox _buildNoiseBox() { + return NoiseBox( + density: NoiseBoxDensity.xLow, + backgroundColor: Colors.grey.shade900, + child: Center( + child: Container( + color: Colors.black54, + width: double.infinity, + height: 40, + child: Center( + child: Text( + 'Waiting for another participant to connect to the call...', + key: Key('text-wait'), + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white), + ), + ), + ), + ), + ); + } + + List> chunk({@required List array, @required int size}) { + final result = >[]; + if (array.isEmpty || size <= 0) { + return result; + } + var first = 0; + var last = size; + final totalLoop = array.length % size == 0 ? array.length ~/ size : array.length ~/ size + 1; + for (var i = 0; i < totalLoop; i++) { + if (last > array.length) { + result.add(array.sublist(first, array.length)); + } else { + result.add(array.sublist(first, last)); + } + first = last; + last = last + size; + } + return result; + } + + void _onHeightBar(double height) { + _onButtonBarHeightStreamController.add(height); + } + + void _onShowBar() { + setState(() { + SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]); + }); + _onButtonBarVisibleStreamController.add(true); + } + + void _onHideBar() { + setState(() { + SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); + }); + _onButtonBarVisibleStreamController.add(false); + } + + Future _wakeLock(bool enable) async { + try { + return await (enable ? Wakelock.enable() : Wakelock.disable()); + } catch (err) { + print('Unable to change the Wakelock and set it to $enable'); + print(err); + } + } + + void _conferenceRoomUpdated() { + setState(() {}); + } +} diff --git a/lib/pages/conference/conference_room.dart b/lib/pages/conference/conference_room.dart new file mode 100644 index 00000000..9c8c1c9a --- /dev/null +++ b/lib/pages/conference/conference_room.dart @@ -0,0 +1,530 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:diplomaticquarterapp/pages/conference/participant_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:twilio_programmable_video/twilio_programmable_video.dart'; + +class ConferenceRoom with ChangeNotifier { + final String name; + final String token; + final String identity; + + final StreamController _onAudioEnabledStreamController = StreamController.broadcast(); + Stream onAudioEnabled; + final StreamController _onVideoEnabledStreamController = StreamController.broadcast(); + Stream onVideoEnabled; + final StreamController _onExceptionStreamController = StreamController.broadcast(); + Stream onException; + + final Completer _completer = Completer(); + + final List _participants = []; + final List _participantBuffer = []; + final List _streamSubscriptions = []; + final List _dataTracks = []; + final List _messages = []; + + CameraCapturer _cameraCapturer; + Room _room; + Timer _timer; + + ConferenceRoom({ + @required this.name, + @required this.token, + @required this.identity, + }) { + onAudioEnabled = _onAudioEnabledStreamController.stream; + onVideoEnabled = _onVideoEnabledStreamController.stream; + onException = _onExceptionStreamController.stream; + } + + List get participants { + return [..._participants]; + } + + Future connect() async { + print('ConferenceRoom.connect()'); + try { + await TwilioProgrammableVideo.debug(dart: true, native: true); + await TwilioProgrammableVideo.setSpeakerphoneOn(true); + + _cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA); + var connectOptions = ConnectOptions( + token, + roomName: name, + preferredAudioCodecs: [OpusCodec()], + audioTracks: [LocalAudioTrack(true)], + dataTracks: [LocalDataTrack()], + videoTracks: [LocalVideoTrack(true, _cameraCapturer)], + enableDominantSpeaker: true, + ); + + _room = await TwilioProgrammableVideo.connect(connectOptions); + + _streamSubscriptions.add(_room.onConnected.listen(_onConnected)); + _streamSubscriptions.add(_room.onConnectFailure.listen(_onConnectFailure)); + + return _completer.future; + } catch (err) { + print(err); + rethrow; + } + } + + Future disconnect() async { + print('ConferenceRoom.disconnect()'); + if (_timer != null) { + _timer.cancel(); + } + await _room.disconnect(); + } + + @override + void dispose() { + print('ConferenceRoom.dispose()'); + _disposeStreamsAndSubscriptions(); + super.dispose(); + } + + Future _disposeStreamsAndSubscriptions() async { + await _onAudioEnabledStreamController.close(); + await _onVideoEnabledStreamController.close(); + await _onExceptionStreamController.close(); + for (var streamSubscription in _streamSubscriptions) { + await streamSubscription.cancel(); + } + } + + Future sendMessage(String message) async { + final tracks = _room.localParticipant.localDataTracks; + final localDataTrack = tracks.isEmpty ? null : tracks[0].localDataTrack; + if (localDataTrack == null || _messages.isNotEmpty) { + print('ConferenceRoom.sendMessage => Track is not available yet, buffering message.'); + _messages.add(message); + return; + } + await localDataTrack.send(message); + } + + Future sendBufferMessage(ByteBuffer message) async { + final tracks = _room.localParticipant.localDataTracks; + final localDataTrack = tracks.isEmpty ? null : tracks[0].localDataTrack; + if (localDataTrack == null) { + return; + } + await localDataTrack.sendBuffer(message); + } + + Future toggleVideoEnabled() async { + final tracks = _room.localParticipant.localVideoTracks; + final localVideoTrack = tracks.isEmpty ? null : tracks[0].localVideoTrack; + if (localVideoTrack == null) { + print('ConferenceRoom.toggleVideoEnabled() => Track is not available yet!'); + return; + } + await localVideoTrack.enable(!localVideoTrack.isEnabled); + + var index = _participants.indexWhere((ParticipantWidget participant) => !participant.isRemote); + if (index < 0) { + return; + } + var participant = _participants[index]; + _participants.replaceRange( + index, + index + 1, + [ + participant.copyWith(videoEnabled: localVideoTrack.isEnabled), + ], + ); + print('ConferenceRoom.toggleVideoEnabled() => ${localVideoTrack.isEnabled}'); + _onVideoEnabledStreamController.add(localVideoTrack.isEnabled); + notifyListeners(); + } + + Future toggleAudioEnabled() async { + final tracks = _room.localParticipant.localAudioTracks; + final localAudioTrack = tracks.isEmpty ? null : tracks[0].localAudioTrack; + if (localAudioTrack == null) { + print('ConferenceRoom.toggleAudioEnabled() => Track is not available yet!'); + return; + } + await localAudioTrack.enable(!localAudioTrack.isEnabled); + + var index = _participants.indexWhere((ParticipantWidget participant) => !participant.isRemote); + if (index < 0) { + return; + } + var participant = _participants[index]; + _participants.replaceRange( + index, + index + 1, + [ + participant.copyWith(audioEnabled: localAudioTrack.isEnabled), + ], + ); + print('ConferenceRoom.toggleAudioEnabled() => ${localAudioTrack.isEnabled}'); + _onAudioEnabledStreamController.add(localAudioTrack.isEnabled); + notifyListeners(); + } + + Future switchCamera() async { + print('ConferenceRoom.switchCamera()'); + try { + await _cameraCapturer.switchCamera(); + } on FormatException catch (e) { + print( + 'ConferenceRoom.switchCamera() failed because of FormatException with message: ${e.message}', + ); + } + } + + void addDummy({Widget child}) { + print('ConferenceRoom.addDummy()'); + if (_participants.length >= 18) { + throw PlatformException( + code: 'ConferenceRoom.maximumReached', + message: 'Maximum reached', + details: 'Currently the lay-out can only render a maximum of 18 participants', + ); + } + _participants.insert( + 0, + ParticipantWidget( + id: (_participants.length + 1).toString(), + child: child, + isRemote: true, + audioEnabled: true, + videoEnabled: true, + isDummy: true, + ), + ); + notifyListeners(); + } + + void removeDummy() { + print('ConferenceRoom.removeDummy()'); + var dummy = _participants.firstWhere((participant) => participant.isDummy, orElse: () => null); + if (dummy != null) { + _participants.remove(dummy); + notifyListeners(); + } + } + + void _onConnected(Room room) { + print('ConferenceRoom._onConnected => state: ${room.state}'); + + // When connected for the first time, add remote participant listeners + _streamSubscriptions.add(_room.onParticipantConnected.listen(_onParticipantConnected)); + _streamSubscriptions.add(_room.onParticipantDisconnected.listen(_onParticipantDisconnected)); + _streamSubscriptions.add(_room.onDominantSpeakerChange.listen(_onDominantSpeakerChanged)); + // Only add ourselves when connected for the first time too. + _participants.add( + _buildParticipant( + child: room.localParticipant.localVideoTracks[0].localVideoTrack.widget(), + id: identity, + audioEnabled: true, + videoEnabled: true, + ), + ); + for (final remoteParticipant in room.remoteParticipants) { + var participant = _participants.firstWhere((participant) => participant.id == remoteParticipant.sid, orElse: () => null); + if (participant == null) { + print('Adding participant that was already present in the room ${remoteParticipant.sid}, before I connected'); + _addRemoteParticipantListeners(remoteParticipant); + } + } + // We have to listen for the [onDataTrackPublished] event on the [LocalParticipant] in + // order to be able to use the [send] method. + _streamSubscriptions.add(room.localParticipant.onDataTrackPublished.listen(_onLocalDataTrackPublished)); + notifyListeners(); + _completer.complete(room); + + _timer = Timer.periodic(const Duration(minutes: 1), (_) { + // Let's see if we can send some data over the DataTrack API + sendMessage('And another minute has passed since I connected...'); + // Also try the ByteBuffer way of sending data + final list = 'This data has been sent over the ByteBuffer channel of the DataTrack API'.codeUnits; + var bytes = Uint8List.fromList(list); + sendBufferMessage(bytes.buffer); + }); + } + + void _onLocalDataTrackPublished(LocalDataTrackPublishedEvent event) { + // Send buffered messages, if any... + while (_messages.isNotEmpty) { + var message = _messages.removeAt(0); + print('Sending buffered message: $message'); + event.localDataTrackPublication.localDataTrack.send(message); + } + } + + void _onConnectFailure(RoomConnectFailureEvent event) { + print('ConferenceRoom._onConnectFailure: ${event.exception}'); + _completer.completeError(event.exception); + } + + void _onDominantSpeakerChanged(DominantSpeakerChangedEvent event) { + print('ConferenceRoom._onDominantSpeakerChanged: ${event.remoteParticipant.identity}'); + var oldDominantParticipant = _participants.firstWhere((p) => p.isDominant, orElse: () => null); + if (oldDominantParticipant != null) { + var oldDominantParticipantIndex = _participants.indexOf(oldDominantParticipant); + _participants.replaceRange(oldDominantParticipantIndex, oldDominantParticipantIndex + 1, [oldDominantParticipant.copyWith(isDominant: false)]); + } + + var newDominantParticipant = _participants.firstWhere((p) => p.id == event.remoteParticipant.sid); + var newDominantParticipantIndex = _participants.indexOf(newDominantParticipant); + _participants.replaceRange(newDominantParticipantIndex, newDominantParticipantIndex + 1, [newDominantParticipant.copyWith(isDominant: true)]); + notifyListeners(); + } + + void _onParticipantConnected(RoomParticipantConnectedEvent event) { + print('ConferenceRoom._onParticipantConnected, ${event.remoteParticipant.sid}'); + _addRemoteParticipantListeners(event.remoteParticipant); + } + + void _onParticipantDisconnected(RoomParticipantDisconnectedEvent event) { + print('ConferenceRoom._onParticipantDisconnected: ${event.remoteParticipant.sid}'); + _participants.removeWhere((ParticipantWidget p) => p.id == event.remoteParticipant.sid); + notifyListeners(); + } + + ParticipantWidget _buildParticipant({ + @required Widget child, + @required String id, + @required bool audioEnabled, + @required bool videoEnabled, + RemoteParticipant remoteParticipant, + }) { + return ParticipantWidget( + id: remoteParticipant?.sid, + isRemote: remoteParticipant != null, + child: child, + audioEnabled: audioEnabled, + videoEnabled: videoEnabled, + ); + } + + void _addRemoteParticipantListeners(RemoteParticipant remoteParticipant) { + print('ConferenceRoom._addRemoteParticipantListeners() => Adding listeners to remoteParticipant ${remoteParticipant.sid}'); + _streamSubscriptions.add(remoteParticipant.onAudioTrackDisabled.listen(_onAudioTrackDisabled)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackEnabled.listen(_onAudioTrackEnabled)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackPublished.listen(_onAudioTrackPublished)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackSubscribed.listen(_onAudioTrackSubscribed)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackSubscriptionFailed.listen(_onAudioTrackSubscriptionFailed)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackUnpublished.listen(_onAudioTrackUnpublished)); + _streamSubscriptions.add(remoteParticipant.onAudioTrackUnsubscribed.listen(_onAudioTrackUnsubscribed)); + + _streamSubscriptions.add(remoteParticipant.onDataTrackPublished.listen(_onDataTrackPublished)); + _streamSubscriptions.add(remoteParticipant.onDataTrackSubscribed.listen(_onDataTrackSubscribed)); + _streamSubscriptions.add(remoteParticipant.onDataTrackSubscriptionFailed.listen(_onDataTrackSubscriptionFailed)); + _streamSubscriptions.add(remoteParticipant.onDataTrackUnpublished.listen(_onDataTrackUnpublished)); + _streamSubscriptions.add(remoteParticipant.onDataTrackUnsubscribed.listen(_onDataTrackUnsubscribed)); + + _streamSubscriptions.add(remoteParticipant.onVideoTrackDisabled.listen(_onVideoTrackDisabled)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackEnabled.listen(_onVideoTrackEnabled)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackPublished.listen(_onVideoTrackPublished)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackSubscribed.listen(_onVideoTrackSubscribed)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackSubscriptionFailed.listen(_onVideoTrackSubscriptionFailed)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackUnpublished.listen(_onVideoTrackUnpublished)); + _streamSubscriptions.add(remoteParticipant.onVideoTrackUnsubscribed.listen(_onVideoTrackUnsubscribed)); + } + + void _onAudioTrackDisabled(RemoteAudioTrackEvent event) { + print('ConferenceRoom._onAudioTrackDisabled(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}, isEnabled: ${event.remoteAudioTrackPublication.isTrackEnabled}'); + _setRemoteAudioEnabled(event); + } + + void _onAudioTrackEnabled(RemoteAudioTrackEvent event) { + print('ConferenceRoom._onAudioTrackEnabled(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}, isEnabled: ${event.remoteAudioTrackPublication.isTrackEnabled}'); + _setRemoteAudioEnabled(event); + } + + void _onAudioTrackPublished(RemoteAudioTrackEvent event) { + print('ConferenceRoom._onAudioTrackPublished(), ${event.remoteParticipant.sid}}'); + } + + void _onAudioTrackSubscribed(RemoteAudioTrackSubscriptionEvent event) { + print('ConferenceRoom._onAudioTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}'); + _addOrUpdateParticipant(event); + } + + void _onAudioTrackSubscriptionFailed(RemoteAudioTrackSubscriptionFailedEvent event) { + print('ConferenceRoom._onAudioTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}'); + _onExceptionStreamController.add( + PlatformException( + code: 'ConferenceRoom.audioTrackSubscriptionFailed', + message: 'AudioTrack Subscription Failed', + details: event.exception.toString(), + ), + ); + } + + void _onAudioTrackUnpublished(RemoteAudioTrackEvent event) { + print('ConferenceRoom._onAudioTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrackPublication.trackSid}'); + } + + void _onAudioTrackUnsubscribed(RemoteAudioTrackSubscriptionEvent event) { + print('ConferenceRoom._onAudioTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteAudioTrack.sid}'); + } + + void _onDataTrackPublished(RemoteDataTrackEvent event) { + print('ConferenceRoom._onDataTrackPublished(), ${event.remoteParticipant.sid}}'); + } + + void _onDataTrackSubscribed(RemoteDataTrackSubscriptionEvent event) { + print('ConferenceRoom._onDataTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}'); + final dataTrack = event.remoteDataTrackPublication.remoteDataTrack; + _dataTracks.add(dataTrack); + _streamSubscriptions.add(dataTrack.onMessage.listen(_onMessage)); + _streamSubscriptions.add(dataTrack.onBufferMessage.listen(_onBufferMessage)); + } + + void _onDataTrackSubscriptionFailed(RemoteDataTrackSubscriptionFailedEvent event) { + print('ConferenceRoom._onDataTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}'); + _onExceptionStreamController.add( + PlatformException( + code: 'ConferenceRoom.dataTrackSubscriptionFailed', + message: 'DataTrack Subscription Failed', + details: event.exception.toString(), + ), + ); + } + + void _onDataTrackUnpublished(RemoteDataTrackEvent event) { + print('ConferenceRoom._onDataTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteDataTrackPublication.trackSid}'); + } + + void _onDataTrackUnsubscribed(RemoteDataTrackSubscriptionEvent event) { + print('ConferenceRoom._onDataTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteDataTrack.sid}'); + } + + void _onVideoTrackDisabled(RemoteVideoTrackEvent event) { + print('ConferenceRoom._onVideoTrackDisabled(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}, isEnabled: ${event.remoteVideoTrackPublication.isTrackEnabled}'); + _setRemoteVideoEnabled(event); + } + + void _onVideoTrackEnabled(RemoteVideoTrackEvent event) { + print('ConferenceRoom._onVideoTrackEnabled(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}, isEnabled: ${event.remoteVideoTrackPublication.isTrackEnabled}'); + _setRemoteVideoEnabled(event); + } + + void _onVideoTrackPublished(RemoteVideoTrackEvent event) { + print('ConferenceRoom._onVideoTrackPublished(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}'); + } + + void _onVideoTrackSubscribed(RemoteVideoTrackSubscriptionEvent event) { + print('ConferenceRoom._onVideoTrackSubscribed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrack.sid}'); + _addOrUpdateParticipant(event); + } + + void _onVideoTrackSubscriptionFailed(RemoteVideoTrackSubscriptionFailedEvent event) { + print('ConferenceRoom._onVideoTrackSubscriptionFailed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}'); + _onExceptionStreamController.add( + PlatformException( + code: 'ConferenceRoom.videoTrackSubscriptionFailed', + message: 'VideoTrack Subscription Failed', + details: event.exception.toString(), + ), + ); + } + + void _onVideoTrackUnpublished(RemoteVideoTrackEvent event) { + print('ConferenceRoom._onVideoTrackUnpublished(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrackPublication.trackSid}'); + } + + void _onVideoTrackUnsubscribed(RemoteVideoTrackSubscriptionEvent event) { + print('ConferenceRoom._onVideoTrackUnsubscribed(), ${event.remoteParticipant.sid}, ${event.remoteVideoTrack.sid}'); + } + + void _onMessage(RemoteDataTrackStringMessageEvent event) { + print('onMessage => ${event.remoteDataTrack.sid}, ${event.message}'); + } + + void _onBufferMessage(RemoteDataTrackBufferMessageEvent event) { + print('onBufferMessage => ${event.remoteDataTrack.sid}, ${String.fromCharCodes(event.message.asUint8List())}'); + } + + void _setRemoteAudioEnabled(RemoteAudioTrackEvent event) { + if (event.remoteAudioTrackPublication == null) { + return; + } + var index = _participants.indexWhere((ParticipantWidget participant) => participant.id == event.remoteParticipant.sid); + if (index < 0) { + return; + } + var participant = _participants[index]; + _participants.replaceRange( + index, + index + 1, + [ + participant.copyWith(audioEnabled: event.remoteAudioTrackPublication.isTrackEnabled), + ], + ); + notifyListeners(); + } + + void _setRemoteVideoEnabled(RemoteVideoTrackEvent event) { + if (event.remoteVideoTrackPublication == null) { + return; + } + var index = _participants.indexWhere((ParticipantWidget participant) => participant.id == event.remoteParticipant.sid); + if (index < 0) { + return; + } + var participant = _participants[index]; + _participants.replaceRange( + index, + index + 1, + [ + participant.copyWith(videoEnabled: event.remoteVideoTrackPublication.isTrackEnabled), + ], + ); + notifyListeners(); + } + + void _addOrUpdateParticipant(RemoteParticipantEvent event) { + print('ConferenceRoom._addOrUpdateParticipant(), ${event.remoteParticipant.sid}'); + final participant = _participants.firstWhere( + (ParticipantWidget participant) => participant.id == event.remoteParticipant.sid, + orElse: () => null, + ); + if (participant != null) { + print('Participant found: ${participant.id}, updating A/V enabled values'); + _setRemoteVideoEnabled(event); + _setRemoteAudioEnabled(event); + } else { + final bufferedParticipant = _participantBuffer.firstWhere( + (ParticipantBuffer participant) => participant.id == event.remoteParticipant.sid, + orElse: () => null, + ); + if (bufferedParticipant != null) { + _participantBuffer.remove(bufferedParticipant); + } else if (event is RemoteAudioTrackEvent) { + print('Audio subscription came first, waiting for the video subscription...'); + _participantBuffer.add( + ParticipantBuffer( + id: event.remoteParticipant.sid, + audioEnabled: event.remoteAudioTrackPublication?.remoteAudioTrack?.isEnabled ?? true, + ), + ); + return; + } + if (event is RemoteVideoTrackSubscriptionEvent) { + print('New participant, adding: ${event.remoteParticipant.sid}'); + _participants.insert( + 0, + _buildParticipant( + child: event.remoteVideoTrack.widget(), + id: event.remoteParticipant.sid, + remoteParticipant: event.remoteParticipant, + audioEnabled: bufferedParticipant?.audioEnabled ?? true, + videoEnabled: event.remoteVideoTrackPublication?.remoteVideoTrack?.isEnabled ?? true, + ), + ); + } + notifyListeners(); + } + } +} diff --git a/lib/pages/conference/draggable_publisher.dart b/lib/pages/conference/draggable_publisher.dart new file mode 100644 index 00000000..a1a0b142 --- /dev/null +++ b/lib/pages/conference/draggable_publisher.dart @@ -0,0 +1,173 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:diplomaticquarterapp/pages/conference/clipped_video.dart'; +import 'package:flutter/material.dart'; + +class DraggablePublisher extends StatefulWidget { + final Size availableScreenSize; + final Widget child; + final double scaleFactor; + final Stream onButtonBarVisible; + final Stream onButtonBarHeight; + + const DraggablePublisher({ + Key key, + @required this.availableScreenSize, + this.child, + @required this.onButtonBarVisible, + @required this.onButtonBarHeight, + + /// The portion of the screen the DraggableWidget should use. + this.scaleFactor = .25, + }) : assert(scaleFactor != null && scaleFactor > 0 && scaleFactor <= .4), + assert(availableScreenSize != null), + assert(onButtonBarVisible != null), + assert(onButtonBarHeight != null), + super(key: key); + + @override + _DraggablePublisherState createState() => _DraggablePublisherState(); +} + +class _DraggablePublisherState extends State { + bool _isButtonBarVisible = true; + double _buttonBarHeight = 0; + double _width; + double _height; + double _top; + double _left; + double _viewPaddingTop; + double _viewPaddingBottom; + final double _padding = 8.0; + final Duration _duration300ms = const Duration(milliseconds: 300); + final Duration _duration0ms = const Duration(milliseconds: 0); + Duration _duration; + StreamSubscription _streamSubscription; + StreamSubscription _streamHeightSubscription; + + @override + void initState() { + super.initState(); + _duration = _duration300ms; + _width = widget.availableScreenSize.width * widget.scaleFactor; + _height = _width * (widget.availableScreenSize.height / widget.availableScreenSize.width); + _top = widget.availableScreenSize.height - (_buttonBarHeight + _padding) - _height; + _left = widget.availableScreenSize.width - _padding - _width; + + _streamSubscription = widget.onButtonBarVisible.listen(_buttonBarVisible); + _streamHeightSubscription = widget.onButtonBarHeight.listen(_getButtonBarHeight); + } + + @override + void didChangeDependencies() { + final mediaQuery = MediaQuery.of(context); + _viewPaddingTop = mediaQuery.viewPadding.top; + _viewPaddingBottom = mediaQuery.viewPadding.bottom; + super.didChangeDependencies(); + } + + @override + void dispose() { + _streamSubscription.cancel(); + _streamHeightSubscription.cancel(); + super.dispose(); + } + + void _getButtonBarHeight(double height) { + setState(() { + _buttonBarHeight = height; + _positionWidget(); + }); + } + + void _buttonBarVisible(bool visible) { + if (!mounted) { + return; + } + setState(() { + _isButtonBarVisible = visible; + if (_duration == _duration300ms) { + // only position the widget when we are not currently dragging it around + _positionWidget(); + } + }); + } + + @override + Widget build(BuildContext context) { + return AnimatedPositioned( + top: _top, + left: _left, + width: _width, + height: _height, + duration: _duration, + child: Listener( + onPointerDown: (_) => _duration = _duration0ms, + onPointerMove: (PointerMoveEvent event) { + setState(() { + _left = (_left + event.delta.dx).roundToDouble(); + _top = (_top + event.delta.dy).roundToDouble(); + }); + }, + onPointerUp: (_) => _positionWidget(), + onPointerCancel: (_) => _positionWidget(), + child: ClippedVideo( + height: _height, + width: _width, + child: widget.child, + ), + ), + ); + } + + double _getCurrentStatusBarHeight() { + if (_isButtonBarVisible) { + return _viewPaddingTop; + } + final _defaultViewPaddingTop = Platform.isIOS ? 20.0 : Platform.isAndroid ? 24.0 : 0.0; + if (_viewPaddingTop > _defaultViewPaddingTop) { + // There must be a hardware notch in the display. + return _viewPaddingTop; + } + return 0.0; + } + + double _getCurrentButtonBarHeight() { + if (_isButtonBarVisible) { + return _buttonBarHeight + _viewPaddingBottom; + } + return _viewPaddingBottom; + } + + void _positionWidget() { + // Determine the center of the object being dragged so we can decide + // in which corner the object should be placed. + var dx = (_width / 2) + _left; + dx = dx < 0 ? 0 : dx >= widget.availableScreenSize.width ? widget.availableScreenSize.width - 1 : dx; + var dy = (_height / 2) + _top; + dy = dy < 0 ? 0 : dy >= widget.availableScreenSize.height ? widget.availableScreenSize.height - 1 : dy; + final draggableCenter = Offset(dx, dy); + + setState(() { + _duration = _duration300ms; + if (Rect.fromLTRB(0, 0, widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2).contains(draggableCenter)) { + // Top-left + _top = _getCurrentStatusBarHeight() + _padding; + _left = _padding; + } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, 0, widget.availableScreenSize.width, widget.availableScreenSize.height / 2).contains(draggableCenter)) { + // Top-right + _top = _getCurrentStatusBarHeight() + _padding; + _left = widget.availableScreenSize.width - _padding - _width; + } else if (Rect.fromLTRB(0, widget.availableScreenSize.height / 2, widget.availableScreenSize.width / 2, widget.availableScreenSize.height).contains(draggableCenter)) { + // Bottom-left + _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; + _left = _padding; + } else if (Rect.fromLTRB(widget.availableScreenSize.width / 2, widget.availableScreenSize.height / 2, widget.availableScreenSize.width, widget.availableScreenSize.height).contains(draggableCenter)) { + // Bottom-right + _top = widget.availableScreenSize.height - (_getCurrentButtonBarHeight() + _padding) - _height; + _left = widget.availableScreenSize.width - _padding - _width; + } + }); + } +} diff --git a/lib/pages/conference/participant_widget.dart b/lib/pages/conference/participant_widget.dart new file mode 100644 index 00000000..5b8d1ff2 --- /dev/null +++ b/lib/pages/conference/participant_widget.dart @@ -0,0 +1,197 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class ParticipantBuffer { + final bool audioEnabled; + final String id; + + ParticipantBuffer({ + @required this.audioEnabled, + @required this.id, + }) : assert(audioEnabled != null), + assert(id != null); +} + +class ParticipantWidget extends StatelessWidget { + final Widget child; + final String id; + final bool audioEnabled; + final bool videoEnabled; + final bool isRemote; + final bool isDummy; + final bool isDominant; + + const ParticipantWidget({ + Key key, + @required this.child, + @required this.audioEnabled, + @required this.videoEnabled, + @required this.id, + @required this.isRemote, + this.isDominant = false, + this.isDummy = false, + }) : assert(child != null), + assert(audioEnabled != null), + assert(videoEnabled != null), + assert(isRemote != null), + assert(isDominant != null), + assert(isDummy != null), + super(key: key); + + ParticipantWidget copyWith({ + Widget child, + bool audioEnabled, + bool videoEnabled, + bool isDominant, + }) { + return ParticipantWidget( + id: id, + child: child ?? this.child, + audioEnabled: audioEnabled ?? this.audioEnabled, + videoEnabled: videoEnabled ?? this.videoEnabled, + isDominant: isDominant ?? this.isDominant, + isRemote: isRemote, + ); + } + + @override + Widget build(BuildContext context) { + final children = []; + final icons = []; + if (!videoEnabled) { + icons.add(_buildVideoEnabledIcon()); + children.add( + ClipRect( + // Need to clip this BackdropFilter, otherwise it will blur the entire screen + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + decoration: BoxDecoration(color: Colors.black.withOpacity(.1)), + child: child, + ), + ), + ), + ); + } else { + children.add(child); + } + children.add(Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedOpacity( + duration: Duration(milliseconds: 500), + opacity: isDominant ? 1 : 0, + child: Icon( + Icons.volume_up, + color: Colors.white, + ), + ), + )); + if (!audioEnabled) { + icons.add(_buildAudioEnabledIcon()); + } + if (icons.isNotEmpty) { + if (isRemote) { + final rows = []; + rows.add(_buildRow(icons)); + if (!audioEnabled && !videoEnabled) { + rows.add(_buildRow(_fitText('The camera and microphone are off', Colors.white24))); + } else if (!audioEnabled) { + rows.add(_buildRow(_fitText('The microphone is off', Colors.black26))); + } else if (!videoEnabled) { + rows.add(_buildRow(_fitText('The camera is off', Colors.white24))); + } + children.add( + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: rows, + ), + ); + } else { + children.add(Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: icons, + )); + } + } + + return Stack( + children: children, + ); + } + + List _fitText(String text, Color color) { + return [ + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: Text(text, maxLines: 1, style: _buildTextStyle(color)), + ), + ), + ), + ]; + } + + TextStyle _buildTextStyle(Color color) { + return TextStyle( + color: color, + shadows: [ + Shadow( + blurRadius: 1.0, + color: Color.fromARGB(255, 0, 0, 0), + ), + Shadow( + blurRadius: 1.0, + color: Color.fromARGB(24, 255, 255, 255), + ), + ], + fontSize: 15, + ); + } + + Widget _buildRow(List children) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: children, + ); + } + + Widget _buildAudioEnabledIcon() { + return Padding( + padding: const EdgeInsets.all(8), + child: CircleAvatar( + maxRadius: 15, + child: FittedBox( + child: Icon( + Icons.mic_off, + color: Colors.black, + key: Key('microphone-off-icon'), + ), + ), + backgroundColor: Colors.white24, + ), + ); + } + + Widget _buildVideoEnabledIcon() { + return Padding( + padding: const EdgeInsets.all(8), + child: CircleAvatar( + maxRadius: 15, + child: FittedBox( + child: Icon( + Icons.videocam_off, + color: Colors.black, + key: Key('videocam-off-icon'), + ), + ), + backgroundColor: Colors.white24, + ), + ); + } +} diff --git a/lib/pages/conference/widgets/button_to_progress.dart b/lib/pages/conference/widgets/button_to_progress.dart new file mode 100644 index 00000000..2879dc60 --- /dev/null +++ b/lib/pages/conference/widgets/button_to_progress.dart @@ -0,0 +1,106 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +class ButtonToProgress extends StatefulWidget { + final double height; + final double progressHeight; + final String loadingText; + final Duration duration; + final TextStyle loadingTextStyle; + final VoidCallback onPressed; + final Stream onLoading; + final Widget child; + + const ButtonToProgress({ + Key key, + this.height = 40.0, + this.progressHeight = 5.0, + this.loadingText, + this.duration = const Duration(milliseconds: 300), + this.loadingTextStyle, + this.onPressed, + this.onLoading, + @required this.child, + }) : assert(child != null), + assert(height != null && height > 0), + assert(progressHeight != null && progressHeight > 0 && progressHeight <= height), + super(key: key); + + @override + _ButtonToProgressState createState() => _ButtonToProgressState(); +} + +class _ButtonToProgressState extends State { + double _height; + double _opacity = 0; + bool _isLoading = false; + + StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + _height = widget.height; + if (widget.onLoading != null) { + _subscription = widget.onLoading.listen((bool isLoading) { + setState(() { + _isLoading = isLoading; + _height = isLoading ? widget.progressHeight : widget.height; + _opacity = isLoading ? 1 : 0; + }); + }); + } + } + + @override + void dispose() { + if (_subscription != null) { + _subscription.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: widget.height, + child: Stack( + children: [ + if (widget.loadingText == null) + Container() + else + Padding( + padding: EdgeInsets.only(bottom: widget.progressHeight), + child: AnimatedOpacity( + child: Center( + child: FittedBox( + child: Text( + widget.loadingText, + style: widget.loadingTextStyle, + ), + ), + ), + opacity: _opacity, + duration: Duration(milliseconds: widget.duration.inMilliseconds + 200), + curve: Curves.easeInCubic, + ), + ), + AnimatedPadding( + duration: widget.duration, + padding: EdgeInsets.only( + top: math.max(widget.height - _height, 0), + ), + child: AnimatedContainer( + duration: widget.duration, + height: _height, + width: double.infinity, + child: _isLoading ? const LinearProgressIndicator() : widget.child, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/conference/widgets/circle_button.dart b/lib/pages/conference/widgets/circle_button.dart new file mode 100644 index 00000000..37702c29 --- /dev/null +++ b/lib/pages/conference/widgets/circle_button.dart @@ -0,0 +1,103 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:native_device_orientation/native_device_orientation.dart'; + +class CircleButton extends StatefulWidget { + final VoidCallback onLongPress; + final VoidCallback onPressed; + final GestureTapDownCallback onTapDown; + final VoidCallback onTapCancel; + final Widget child; + final Color color; + final double radius; + + const CircleButton({ + Key key, + this.onLongPress, + this.onPressed, + this.child, + this.color, + this.radius = 25.0, + this.onTapCancel, + this.onTapDown, + }) : assert(radius != null), + super(key: key); + + @override + _CircleButtonState createState() => _CircleButtonState(); +} + +class _CircleButtonState extends State { + double _rotationAngle = 0.0; + + final Stream _orientationStream = NativeDeviceOrientationCommunicator().onOrientationChanged(useSensor: true); + StreamSubscription _orientationSubscription; + + void _handleOrientationChange(NativeDeviceOrientation orientation) { + var targetAngle = 0.0; + switch (orientation) { + case NativeDeviceOrientation.unknown: + case NativeDeviceOrientation.portraitUp: + targetAngle = 0.0; + break; + case NativeDeviceOrientation.portraitDown: + targetAngle = 180.0; + break; + case NativeDeviceOrientation.landscapeLeft: + targetAngle = 90.0; + break; + case NativeDeviceOrientation.landscapeRight: + targetAngle = 270.0; + break; + } + setState(() { + _rotationAngle = targetAngle; + }); + } + + @override + void initState() { + super.initState(); + _orientationSubscription = _orientationStream.listen( + _handleOrientationChange, + onError: (dynamic err) => print(err), + ); + } + + @override + void dispose() { + super.dispose(); + _orientationSubscription.cancel(); + } + + @override + Widget build(BuildContext context) { + final size = 2 * widget.radius; + + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: (widget.color ?? Colors.blue).withAlpha(200), + borderRadius: BorderRadius.all( + Radius.circular(widget.radius), + ), + ), + child: GestureDetector( + onLongPress: widget.onLongPress, + onTapDown: widget.onTapDown, + onTapCancel: widget.onTapCancel, + child: RawMaterialButton( + onPressed: widget.onPressed, + child: RotationTransition( + child: widget.child, + turns: AlwaysStoppedAnimation(_rotationAngle / 360), + ), + elevation: 0, + shape: const CircleBorder(), + ), + ), + ); + } +} diff --git a/lib/pages/conference/widgets/noise_box.dart b/lib/pages/conference/widgets/noise_box.dart new file mode 100644 index 00000000..5f638127 --- /dev/null +++ b/lib/pages/conference/widgets/noise_box.dart @@ -0,0 +1,144 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; + +enum NoiseBoxDensity { + high, + medium, + low, + xHigh, + xLow, +} + +class NoiseBox extends StatefulWidget { + final NoiseBoxDensity density; + final Color backgroundColor; + final Widget child; + + const NoiseBox({ + Key key, + this.backgroundColor, + this.child, + this.density = NoiseBoxDensity.medium, + }) : assert(density != null), + super(key: key); + + @override + _NoiseBoxState createState() => _NoiseBoxState(); +} + +class _NoiseBoxState extends State with SingleTickerProviderStateMixin { + AnimationController _animationController; + int _density; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 60), + ); + _animationController.repeat(); + switch (widget.density) { + case NoiseBoxDensity.high: + _density = 5; + break; + case NoiseBoxDensity.medium: + _density = 7; + break; + case NoiseBoxDensity.low: + _density = 10; + break; + case NoiseBoxDensity.xHigh: + _density = 3; + break; + case NoiseBoxDensity.xLow: + _density = 12; + break; + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => Container( + color: widget.backgroundColor, + width: constraints.biggest.width, + height: constraints.biggest.height, + child: AnimatedBuilder( + animation: _animationController, + builder: (BuildContext context, Widget w) { + final children = [ + CustomPaint( + painter: NoisePainter( + width: constraints.biggest.width, + height: constraints.biggest.height, + density: _density, + ), + ), + ]; + if (widget.child != null) { + children.add(widget.child); + } + return Stack( + children: children, + ); + }, + ), + ), + ); + } +} + +class NoisePainter extends CustomPainter { + final double width; + final double height; + final int density; + + NoisePainter({ + @required this.width, + @required this.height, + @required this.density, + }) : assert(width != null), + assert(height != null), + assert(density != null && density >= 3 && density < math.min(width, height)); + + List colors = [ + Colors.black, + Colors.grey, + Colors.blueGrey, + Colors.red, + Colors.green, + Colors.blue, + Colors.white, + ]; + + @override + void paint(Canvas canvas, Size size) { + final random = math.Random(); + for (var w = 0; w < width; w += density) { + for (var h = 0; h < height; h += density) { + final offset = Offset( + random.nextDouble() * width, + random.nextDouble() * height, + ); + final paint = Paint(); + paint.color = colors[random.nextInt(colors.length)]; + paint.strokeWidth = random.nextDouble() * 2; + + canvas.drawPoints(PointMode.points, [offset], paint); + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/pages/conference/widgets/platform_alert_dialog.dart b/lib/pages/conference/widgets/platform_alert_dialog.dart new file mode 100644 index 00000000..1adaa223 --- /dev/null +++ b/lib/pages/conference/widgets/platform_alert_dialog.dart @@ -0,0 +1,98 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +import './platform_widget.dart'; + +class PlatformAlertDialog extends PlatformWidget { + PlatformAlertDialog({@required this.title, @required this.content, @required this.defaultActionText, this.cancelActionText}) + : assert(title != null), + assert(content != null), + assert(defaultActionText != null); + + final String title; + final String content; + final String defaultActionText; + final String cancelActionText; + + Future show(BuildContext context) async { + return Platform.isIOS + ? await showCupertinoDialog( + context: context, + builder: (BuildContext context) => this, + ) + : await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) => this, + ); + } + + @override + Widget buildCupertinoWidget(BuildContext context) { + return CupertinoAlertDialog( + title: Text(title), + content: Text(content), + actions: _buildActions(context), + ); + } + + @override + Widget buildMaterialWidget(BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: _buildActions(context), + ); + } + + List _buildActions(BuildContext context) { + final actions = []; + if (cancelActionText != null) { + actions.add( + PlatformAlertDialogAction( + child: Text(cancelActionText), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ); + } + actions.add( + PlatformAlertDialogAction( + child: Text(defaultActionText), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ); + return actions; + } +} + +class PlatformAlertDialogAction extends PlatformWidget { + PlatformAlertDialogAction({ + this.child, + this.onPressed, + }); + + final Widget child; + final VoidCallback onPressed; + + @override + Widget buildCupertinoWidget(BuildContext context) { + return CupertinoDialogAction( + child: child, + onPressed: onPressed, + ); + } + + @override + Widget buildMaterialWidget(BuildContext context) { + return FlatButton( + child: child, + onPressed: onPressed, + ); + } +} diff --git a/lib/pages/conference/widgets/platform_exception_alert_dialog.dart b/lib/pages/conference/widgets/platform_exception_alert_dialog.dart new file mode 100644 index 00000000..931a6e10 --- /dev/null +++ b/lib/pages/conference/widgets/platform_exception_alert_dialog.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import './platform_alert_dialog.dart'; + +class PlatformExceptionAlertDialog extends PlatformAlertDialog { + PlatformExceptionAlertDialog({ + String title = 'An error occurred', + @required Exception exception, + }) : super( + title: title, + content: exception is PlatformException ? _message(exception) : exception.toString(), + defaultActionText: 'OK', + ); + + static String _message(PlatformException exception) { + return _errors[exception.code] ?? (exception.details != null ? (exception.details['message'] ?? exception.message) : exception.message); + } + + static final Map _errors = { + 'ERROR_CODE': 'Error description...', + }; +} diff --git a/lib/pages/conference/widgets/platform_widget.dart b/lib/pages/conference/widgets/platform_widget.dart new file mode 100644 index 00000000..5773190f --- /dev/null +++ b/lib/pages/conference/widgets/platform_widget.dart @@ -0,0 +1,16 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; + +abstract class PlatformWidget extends StatelessWidget { + Widget buildCupertinoWidget(BuildContext context); + + Widget buildMaterialWidget(BuildContext context); + + @override + Widget build(BuildContext context) { + if (Platform.isIOS) { + return buildCupertinoWidget(context); + } + return buildMaterialWidget(context); + } +} diff --git a/lib/pages/conference/widgets/responsive_save_area.dart b/lib/pages/conference/widgets/responsive_save_area.dart new file mode 100644 index 00000000..f9a85467 --- /dev/null +++ b/lib/pages/conference/widgets/responsive_save_area.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +typedef ResponsiveBuilder = Widget Function( + BuildContext context, + Size size, +); + +class ResponsiveSafeArea extends StatelessWidget { + const ResponsiveSafeArea({ + @required ResponsiveBuilder builder, + Key key, + }) : responsiveBuilder = builder, + assert(builder != null), + super(key: key); + + final ResponsiveBuilder responsiveBuilder; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return responsiveBuilder( + context, + constraints.biggest, + ); + }, + ), + ); + } +} diff --git a/lib/pages/landing/home_page.dart b/lib/pages/landing/home_page.dart index eb5f4695..6478c957 100644 --- a/lib/pages/landing/home_page.dart +++ b/lib/pages/landing/home_page.dart @@ -1,6 +1,7 @@ import 'package:diplomaticquarterapp/config/size_config.dart'; import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; import 'package:diplomaticquarterapp/widgets/data_display/text.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index da9400ad..6a651a02 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -1,33 +1,46 @@ +import 'dart:io'; + +import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; import 'package:diplomaticquarterapp/models/Authentication/select_device_imei_res.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart'; import 'package:diplomaticquarterapp/pages/BookAppointment/BookingOptions.dart'; import 'package:diplomaticquarterapp/pages/ToDoList/ToDo.dart'; -import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; +import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart'; import 'package:diplomaticquarterapp/pages/medical/medical_profile_page.dart'; import 'package:diplomaticquarterapp/pages/medical/my_admissions_page.dart'; +import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/bottom_navigation/bottom_nav_bar.dart'; import 'package:diplomaticquarterapp/widgets/drawer/app_drawer_widget.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; + import 'home_page.dart'; class LandingPage extends StatefulWidget { + static bool isOpenCallPage = false; + + static IncomingCallData incomingCallData = new IncomingCallData(); + @override _LandingPageState createState() => _LandingPageState(); } -class _LandingPageState extends State { +class _LandingPageState extends State with WidgetsBindingObserver { int currentTab = 0; PageController pageController; final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); final authService = new AuthProvider(); + + bool isPageNavigated = false; + _changeCurrentTab(int tab) { setState(() { currentTab = tab; @@ -35,19 +48,150 @@ class _LandingPageState extends State { }); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + + var route = ModalRoute.of(context); + + if (route != null) { + print(route.settings.name); + } + + setState(() { + print("didChangeAppLifecycleState"); + print('state = $state'); + AppGlobal.context = context; + if (state == AppLifecycleState.resumed) { + print(LandingPage.isOpenCallPage); + if (LandingPage.isOpenCallPage) { + if (!isPageNavigated) { + isPageNavigated = true; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => IncomingCall( + incomingCallData: LandingPage.incomingCallData))) + .then((value) { + isPageNavigated = false; + }); + } + } + } + + if (state == AppLifecycleState.paused) { + isPageNavigated = false; + } + + if (state == AppLifecycleState.inactive) { + isPageNavigated = false; + } + }); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); + setState(() { + AppGlobal.context = context; + }); pageController = PageController(keepPage: true); + _firebaseMessaging.setAutoInitEnabled(true); _firebaseMessaging.getToken().then((String token) { sharedPref.setString(PUSH_TOKEN, token); if (token != null) { checkUserStatus(token); } requestPermissions(); - //assert(token != null); }); + + //_firebase Background message handler + _firebaseMessaging.configure( + onMessage: (Map message) async { + print("onMessage: $message"); + print(message); + print(message['name']); + print(message['appointmentdate']); + + if (Platform.isIOS) { + if (message['is_call'] == "true") { + var route = ModalRoute.of(context); + + if (route != null) { + print(route.settings.name); + } + + Map myMap = + new Map.from(message); + print(myMap); + LandingPage.isOpenCallPage = true; + LandingPage.incomingCallData = IncomingCallData.fromJson(myMap); + if (!isPageNavigated) { + isPageNavigated = true; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => IncomingCall( + incomingCallData: LandingPage.incomingCallData))) + .then((value) { + isPageNavigated = false; + }); + } + } else { + print("Is Call Not Found iOS"); + } + } else { + print("Is Call Not Found iOS"); + } + + if (Platform.isAndroid) { + if (message['data'].containsKey("is_call")) { + var route = ModalRoute.of(context); + + if (route != null) { + print(route.settings.name); + } + + Map myMap = + new Map.from(message['data']); + print(myMap); + LandingPage.isOpenCallPage = true; + LandingPage.incomingCallData = IncomingCallData.fromJson(myMap); + if (!isPageNavigated) { + isPageNavigated = true; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => IncomingCall( + incomingCallData: LandingPage.incomingCallData))) + .then((value) { + isPageNavigated = false; + }); + } + } else { + print("Is Call Not Found Android"); + } + } else { + print("Is Call Not Found Android"); + } + }, + onBackgroundMessage: Platform.isIOS ? null : myBackgroundMessageHandler, + onLaunch: (Map message) async { + print("onLaunch: $message"); + }, + onResume: (Map message) async { + print("onResume: $message"); + }, + ); } + void requestPermissions() async { await [ Permission.location, @@ -85,7 +229,12 @@ class _LandingPageState extends State { physics: NeverScrollableScrollPhysics(), controller: pageController, children: [ - HomePage(), MedicalProfilePage(), MyAdmissionsPage(), ToDo(), BookingOptions()], // Please do not remove the BookingOptions from this array + HomePage(), + MedicalProfilePage(), + MyAdmissionsPage(), + ToDo(), + BookingOptions() + ], // Please do not remove the BookingOptions from this array ), bottomNavigationBar: BottomNavBar(changeIndex: _changeCurrentTab), ); @@ -122,6 +271,25 @@ class _LandingPageState extends State { } } + static Future myBackgroundMessageHandler( + Map message) async { + Map myMap = new Map.from(message['data']); + print(myMap); + + print("myBackgroundMessageHandler Out"); + if (message.containsKey('data')) { + print("myBackgroundMessageHandler Inside"); + LandingPage.incomingCallData = IncomingCallData.fromJson(myMap); + print(LandingPage.incomingCallData.doctorname); + LandingPage.isOpenCallPage = true; + } + + if (message.containsKey('notification')) { + final dynamic notification = message['notification']; + print(notification); + } + } + void setUserValues(value) async { sharedPref.setObject(IMEI_USER_DATA, value); } diff --git a/lib/pages/livecare/incoming_call.dart b/lib/pages/livecare/incoming_call.dart new file mode 100644 index 00000000..de80b4ae --- /dev/null +++ b/lib/pages/livecare/incoming_call.dart @@ -0,0 +1,255 @@ +import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart'; +import 'package:diplomaticquarterapp/pages/conference/conference_page.dart'; +import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:just_audio/just_audio.dart'; + +class IncomingCall extends StatefulWidget { + IncomingCallData incomingCallData; + + IncomingCall({@required this.incomingCallData}); + + @override + _IncomingCallState createState() => _IncomingCallState(); +} + +class _IncomingCallState extends State + with SingleTickerProviderStateMixin { + AnimationController _animationController; + + final player = AudioPlayer(); + + @override + void initState() { + _animationController = + AnimationController(vsync: this, duration: Duration(milliseconds: 500)); + + WidgetsBinding.instance.addPostFrameCallback((_) => _runAnimation()); + + print(widget.incomingCallData.doctorname); + print(widget.incomingCallData.clinicname); + print(widget.incomingCallData.speciality); + + super.initState(); + } + + @override + void dispose() { + _animationController.dispose(); + player.stop(); + disposeAudioResources(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + isShowAppBar: false, + body: SafeArea( + child: Container( + decoration: BoxDecoration(color: Colors.grey[700]), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + margin: EdgeInsets.only(top: 30.0), + alignment: Alignment.center, + child: Text("Incoming Video Call", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 26.0, + color: Colors.white, + letterSpacing: 1.0)), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.fromLTRB(50.0, 30.0, 50.0, 20.0), + child: Image.asset( + 'assets/images/new-design/hmg_full_logo_hd_white.png'), + ), + Container( + margin: EdgeInsets.fromLTRB(30.0, 10.0, 30.0, 0.0), + child: Divider( + color: Colors.white, + thickness: 1.0, + ), + ), + Container( + margin: EdgeInsets.only(top: 20.0), + alignment: Alignment.center, + child: Text("Dr Eyad Ismail Abu Jayab", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22.0, + fontWeight: FontWeight.bold, + letterSpacing: 0.8, + color: Colors.white)), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + alignment: Alignment.center, + child: Text("ENT Clinic", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22.0, + letterSpacing: 0.8, + color: Colors.white)), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + alignment: Alignment.center, + child: Text("Speciality", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22.0, + letterSpacing: 0.8, + color: Colors.white)), + ), + Container( + decoration: BoxDecoration( + color: Colors.grey[900].withOpacity(0.8), + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + padding: EdgeInsets.all(20.0), + margin: EdgeInsets.only(top: 20.0), + child: Column( + children: [ + Text("Appointment Information", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + letterSpacing: 1.0, + color: Colors.white)), + Container( + margin: EdgeInsets.only(top: 20.0), + child: Text("Sun, 15th Dec, 2019, 09:00", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20.0, + letterSpacing: 1.0, + color: Colors.white)), + ), + Container( + margin: EdgeInsets.only(top: 20.0), + child: Text("ENT Clinic", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20.0, + letterSpacing: 1.0, + color: Colors.white)), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 100.0), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RotationTransition( + turns: Tween(begin: 0.0, end: -.1) + .chain(CurveTween(curve: Curves.elasticIn)) + .animate(_animationController), + child: Container( + child: RawMaterialButton( + onPressed: () { + _submit(); + }, + elevation: 2.0, + fillColor: Colors.green, + child: Icon( + Icons.call, + color: Colors.white, + size: 35.0, + ), + padding: EdgeInsets.all(15.0), + shape: CircleBorder(), + ), + )), + Container( + child: RawMaterialButton( + onPressed: () { + backToHome(); + }, + elevation: 2.0, + fillColor: Colors.red, + child: Icon( + Icons.call_end, + color: Colors.white, + size: 35.0, + ), + padding: EdgeInsets.all(15.0), + shape: CircleBorder(), + ), + ), + ], + ), + ), + ], + )), + ), + ); + } + + void _runAnimation() async { + setAudioFile(); + for (int i = 0; i < 100; i++) { + await _animationController.forward(); + await _animationController.reverse(); + } + } + + Future _submit() async { + backToHome(); + try { + final roomModel = RoomModel( + name: widget.incomingCallData.name, + token: widget.incomingCallData.sessionId, + identity: widget.incomingCallData.identity); + + await Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => + ConferencePage(roomModel: roomModel), + ), + ); + } catch (err) { + print(err); + await PlatformExceptionAlertDialog( + exception: err, + ).show(context); + } + } + + void backToHome() { + player.stop(); +// disposeAudioResources(); + Navigator.of(context).pop(); + } + + disposeAudioResources() async { + await player.dispose(); + } + + void setAudioFile() async { + player.stop(); + await player.setVolume(1.0); // full volume + try { + await player.setAsset('assets/sounds/ring_60Sec.mp3').then((value) { + player.setLoopMode(LoopMode.one); // loop ring sound + player.play(); + }).catchError((err) { + print("Error: $err"); + }); + } catch (e) { + print("Error: $e"); + } + } +} diff --git a/lib/pages/livecare/livecare_home.dart b/lib/pages/livecare/livecare_home.dart new file mode 100644 index 00000000..f3129823 --- /dev/null +++ b/lib/pages/livecare/livecare_home.dart @@ -0,0 +1,130 @@ +import 'package:diplomaticquarterapp/models/FamilyFiles/PatientERVirtualHistoryResponse.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/LiveCarePendingRequest.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/clinic_list.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/livecare_logs.dart'; +import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; + +class LiveCareHome extends StatefulWidget { + static bool showFooterButton = true; + + @override + _LiveCareHomeState createState() => _LiveCareHomeState(); +} + +class _LiveCareHomeState extends State + with SingleTickerProviderStateMixin { + TabController _tabController; + + bool isDataLoaded = false; + bool hasLiveCareRequest = false; + + List erRequestHistoryList; + + ErRequestHistoryList pendingERRequestHistoryList; + + @override + void initState() { + _tabController = new TabController(length: 2, vsync: this); + erRequestHistoryList = List(); + + pendingERRequestHistoryList = new ErRequestHistoryList(); + + WidgetsBinding.instance.addPostFrameCallback((_) => getLiveCareHistory()); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + appBarTitle: "LiveCare", + isShowAppBar: true, + body: Container( + child: Column(children: [ + /// this is will not colored with theme data + TabBar( + tabs: [ + Tab(text: TranslationBase.of(context).consultation), + Tab(text: TranslationBase.of(context).logs), + ], + controller: _tabController, + ), + Divider( + color: Colors.grey[600], + thickness: 0.5, + ), + Expanded( + child: TabBarView( + physics: NeverScrollableScrollPhysics(), + children: [ + isDataLoaded && !hasLiveCareRequest + ? ClinicList( + getLiveCareHistory: getLiveCareHistory, + ) + : isDataLoaded + ? LiveCarePendingRequest( + getLiveCareHistory: getLiveCareHistory, + pendingERRequestHistoryList: + pendingERRequestHistoryList) + : Container(), + isDataLoaded + ? LiveCareLogs( + erRequestHistoryList: erRequestHistoryList, + ) + : Container(), + ], + controller: _tabController, + ), + ), + ]), + ), + ); + } + + void getLiveCareHistory() { + setState(() { + isDataLoaded = false; + hasLiveCareRequest = false; + }); + LiveCareService service = new LiveCareService(); + PatientERVirtualHistoryResponse patientERVirtualHistoryResponse = + new PatientERVirtualHistoryResponse(); + service + .getLivecareHistory(context) + .then((res) { + setState(() { + print(res['ErRequestHistoryList'].length); + if (res['ErRequestHistoryList'].length != 0) { + patientERVirtualHistoryResponse = + PatientERVirtualHistoryResponse.fromJson(res); + erRequestHistoryList = + patientERVirtualHistoryResponse.erRequestHistoryList; + + if (patientERVirtualHistoryResponse + .erRequestHistoryList[0].callStatus < + 4) { + pendingERRequestHistoryList = + patientERVirtualHistoryResponse.erRequestHistoryList[0]; + hasLiveCareRequest = true; + } else { + hasLiveCareRequest = false; + } + } + }); + }) + .catchError((err) { + print(err); + }) + .showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)) + .then((value) { + setState(() { + isDataLoaded = true; + }); + }); + } +} diff --git a/lib/pages/livecare/widgets/ClinicTimingsDialog.dart b/lib/pages/livecare/widgets/ClinicTimingsDialog.dart new file mode 100644 index 00000000..d5fce79e --- /dev/null +++ b/lib/pages/livecare/widgets/ClinicTimingsDialog.dart @@ -0,0 +1,118 @@ +import 'package:diplomaticquarterapp/models/LiveCare/ClinicsServiceTimingsResponse.dart'; +import 'package:flutter/material.dart'; + +class ClinicTimingsDialog extends StatefulWidget { + final clinicName; + final List + patientERGetClinicsServiceTimingsList; + + ClinicTimingsDialog( + {@required this.clinicName, + @required this.patientERGetClinicsServiceTimingsList}); + + @override + _ClinicTimingsDialogState createState() => _ClinicTimingsDialogState(); +} + +class _ClinicTimingsDialogState extends State { + @override + void initState() { + print(widget.patientERGetClinicsServiceTimingsList); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + final double itemHeight = ((size.height - kToolbarHeight - 24) * 0.42) / 2; + final double itemWidth = size.width / 2; + return Container( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + child: Container( + height: MediaQuery.of(context).size.height * 0.68, + margin: EdgeInsets.all(20.0), + width: 450.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 10.0), + child: Text("Clinic Schedule", + textAlign: TextAlign.center, + style: + TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Container( + margin: EdgeInsets.only(bottom: 20.0, top: 10.0), + child: Text(widget.clinicName, + style: + TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + CustomScrollView( + primary: false, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + slivers: [ + SliverPadding( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), + sliver: SliverGrid.count( + crossAxisCount: 2, + childAspectRatio: (itemWidth / itemHeight), + children: widget.patientERGetClinicsServiceTimingsList + .map((e) => Container( + height: 10.0, + child: Column( + children: [ + Text(e.dayOfWeekStr), + Text(e.shiftTimings[0].startTime + + " - " + + e.shiftTimings[0].endTime), + ], + ), + )) + .toList()), + ), + ], + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Container( + alignment: Alignment.center, + height: 30.0, + child: Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Container( + child: Text("OK", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + )), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/livecare/widgets/LiveCareHistoryCard.dart b/lib/pages/livecare/widgets/LiveCareHistoryCard.dart new file mode 100644 index 00000000..25ec8514 --- /dev/null +++ b/lib/pages/livecare/widgets/LiveCareHistoryCard.dart @@ -0,0 +1,212 @@ +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/models/FamilyFiles/PatientERVirtualHistoryResponse.dart'; +import 'package:diplomaticquarterapp/pages/feedback/feedback_home_page.dart'; +import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; +import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/dialogs/confirm_dialog.dart'; +import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; +import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; + +class LiveCareHistoryCard extends StatefulWidget { + ErRequestHistoryList erRequestHistoryList; + + LiveCareHistoryCard({this.erRequestHistoryList}); + + @override + _LiveCareHistoryCardState createState() => _LiveCareHistoryCardState(); +} + +class _LiveCareHistoryCardState extends State { + AuthenticatedUser authUser = new AuthenticatedUser(); + AppSharedPreferences sharedPref = AppSharedPreferences(); + + @override + void initState() { + getAuthenticatedUser(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.all(10.0), + child: Card( + margin: EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 8.0), + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.22, + padding: EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Requested date:", + style: + TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(widget.erRequestHistoryList.sArrivalTime, + style: TextStyle(fontSize: 14.0)), + Text( + "Call Duration\n" + + getCallTime( + widget.erRequestHistoryList.callDuration), + textAlign: TextAlign.center, + style: + TextStyle(fontSize: 14.0, color: Colors.grey[600])), + ], + ), + ), + Container( + padding: EdgeInsets.all(7.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(5)), + color: Colors.green, + ), + margin: EdgeInsets.only(top: 5.0, bottom: 5.0), + child: Text(widget.erRequestHistoryList.stringCallStatus, + style: TextStyle(fontSize: 14.0, color: Colors.white)), + ), + Divider( + color: Colors.grey[500], + ), + Container( + margin: EdgeInsets.all(5.0), + child: Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: InkWell( + onTap: () { + print("Invoice"); + openInvoice(); + }, + child: Container( + child: Row( + children: [ + Icon(Icons.content_paste, color: Colors.blue), + Container( + margin: EdgeInsets.only(left: 10.0), + child: Text("Invoice", + textAlign: TextAlign.center), + ), + ], + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + print("Complaints"); + openComplaint(); + }, + child: Container( + child: Row( + children: [ + Icon(Icons.add, size: 24.0, color: Colors.red), + Container( + margin: EdgeInsets.only(left: 10.0), + child: Text("Complaints", + textAlign: TextAlign.center), + ), + ], + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + print("Rate Dr & Appointment"); + }, + child: Container( + child: Row( + children: [ + Icon(Icons.star, + size: 24.0, color: Colors.yellow[700]), + Container( + width: MediaQuery.of(context).size.width * 0.2, + margin: EdgeInsets.only(left: 10.0), + child: Text("Rate Dr & Appointment", + overflow: TextOverflow.clip, + textAlign: TextAlign.center), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + openInvoice() { + ConfirmDialog dialog = new ConfirmDialog( + context: context, + confirmMessage: "Send a copy of this invoice to the email: " + + authUser.emailAddress, + okText: TranslationBase.of(context).confirm, + cancelText: TranslationBase.of(context).cancel_nocaps, + okFunction: () => {sendInvoiceEmail(context)}, + cancelFunction: () => {}); + dialog.showAlertDialog(context); + } + + openComplaint() { + Navigator.push(context, FadePage(page: FeedbackHomePage())); + } + + sendInvoiceEmail(context) { + ConfirmDialog.closeAlertDialog(context); + LiveCareService service = new LiveCareService(); + service + .sendLiveCareInvoiceEmail( + widget.erRequestHistoryList.appointmentNo.toString(), + widget.erRequestHistoryList.projectID, + authUser.emailAddress, + context) + .then((res) { + AppToast.showSuccessToast(message: "LiveCare invoice sent successfully"); + }).catchError((err) { + AppToast.showErrorToast(message: err); + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); + } + + getAuthenticatedUser() async { + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + setState(() { + authUser = data; + }); + } + } + + getCallTime(int number) { + number = number.round(); + var hours = (number / 60 / 60).floor(); + var minutes = (number / 60).floor() - (hours * 60).floor(); + var seconds = number % 60; + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/pages/livecare/widgets/LiveCareInfoDialog.dart b/lib/pages/livecare/widgets/LiveCareInfoDialog.dart new file mode 100644 index 00000000..0fa02cbe --- /dev/null +++ b/lib/pages/livecare/widgets/LiveCareInfoDialog.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; + +class LiveCareInfoDialog extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + child: Container( + height: MediaQuery.of(context).size.height * 0.61, + margin: EdgeInsets.all(20.0), + width: 450.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 10.0), + child: Icon(Icons.info_outline, + size: 80.0, color: Colors.red[900]), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 20.0), + child: Text("Important Instructions", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + color: Colors.red[900])), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 20.0), + child: Text( + "Please make sure that you're logged in on Al Habib Mobile Application", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: Colors.black)), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 20.0), + child: Icon(Icons.not_interested, + size: 80.0, color: Colors.red[900]), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 40.0), + child: Text( + "Otherwise, you will not receive the doctor's call.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22.0, + fontWeight: FontWeight.bold, + color: Colors.red[900])), + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Container( + alignment: Alignment.center, + height: 40.0, + child: Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.pop(context, false); + }, + child: Container( + child: Text("Cancel", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, color: Colors.red[700])), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + Navigator.pop(context, true); + }, + child: Container( + child: Text("Ok", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + )), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/livecare/widgets/LiveCarePaymentDialog.dart b/lib/pages/livecare/widgets/LiveCarePaymentDialog.dart new file mode 100644 index 00000000..e2dab5b0 --- /dev/null +++ b/lib/pages/livecare/widgets/LiveCarePaymentDialog.dart @@ -0,0 +1,296 @@ +import 'package:diplomaticquarterapp/models/LiveCare/ERAppointmentFeesResponse.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/material.dart'; + +class LiveCarePaymentDialog extends StatefulWidget { + GetERAppointmentFeesList getERAppointmentFeesList; + int waitingTime; + String clinicName; + + LiveCarePaymentDialog( + {@required this.getERAppointmentFeesList, @required this.waitingTime, @required this.clinicName}); + + @override + _LiveCarePaymentDialogState createState() => _LiveCarePaymentDialogState(); +} + +class _LiveCarePaymentDialogState extends State { + int _selected = 0; + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + final double itemHeight = ((size.height - kToolbarHeight - 24) * 0.42) / 2; + final double itemWidth = size.width / 2; + + return Container( + child: Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), + child: Container( + height: MediaQuery.of(context).size.height * 0.691, + margin: EdgeInsets.all(20.0), + width: 450.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 10.0), + child: Text("Online Consultation", + textAlign: TextAlign.center, + style: + TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: Text("Waiting time to start LiveCare consultation", + textAlign: TextAlign.end, + style: TextStyle(fontSize: 13.0)), + ), + Expanded( + child: Container( + child: Icon(Icons.access_time, + size: 36.0, color: Colors.red[800]), + ), + ), + Expanded( + child: Text(widget.waitingTime.toString() + " Minutes", + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Colors.red[900])), + ), + ], + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 10.0, top: 10.0), + child: Text(widget.clinicName, + textAlign: TextAlign.center, + style: + TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold)), + ), + Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: new BorderRadius.only( + topLeft: const Radius.circular(5.0), + topRight: const Radius.circular(5.0), + ), + border: Border.all(color: Colors.black87)), + alignment: Alignment.center, + margin: EdgeInsets.only(top: 5.0), + padding: EdgeInsets.all(5.0), + child: Text("Consultation fee", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: Colors.white)), + ), + Container( + decoration: BoxDecoration( + borderRadius: new BorderRadius.only( + bottomLeft: const Radius.circular(5.0), + bottomRight: const Radius.circular(5.0), + ), + border: Border.all(color: Colors.black54)), + child: Table( + children: [ + TableRow(children: [ + TableCell( + child: _getNormalText( + TranslationBase.of(context).patientShareToDo)), + TableCell( + child: _getNormalText( + widget.getERAppointmentFeesList.amount + + " " + + widget.getERAppointmentFeesList.currency)), + ]), + TableRow(children: [ + TableCell( + child: _getNormalText( + TranslationBase.of(context).patientTaxToDo)), + TableCell( + child: _getNormalText( + widget.getERAppointmentFeesList.tax + + " " + + widget.getERAppointmentFeesList.currency)), + ]), + TableRow(children: [ + TableCell( + child: _getMarginText(TranslationBase.of(context) + .patientShareTotalToDo)), + TableCell( + child: _getMarginText( + widget.getERAppointmentFeesList.total + + " " + + widget.getERAppointmentFeesList.currency)), + ]), + ], + ), + ), + Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 10.0), + decoration: BoxDecoration( + borderRadius: new BorderRadius.all( + const Radius.circular(5.0), + ), + color: Colors.green[200].withOpacity(0.5)), + margin: EdgeInsets.only(top: 20.0), + child: Row( + children: [ + Image.asset("assets/images/new-design/alert-triangle.png"), + Container( + margin: EdgeInsets.only(left: 10.0), + width: MediaQuery.of(context).size.width * 0.55, + child: Text( + "If you're Insurance patient, you have only have to pay the co-payment", + style: TextStyle(fontSize: 13.0)), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Container( + child: new Radio( + value: 1, + groupValue: _selected, + onChanged: onRadioChanged, + ), + ), + Container( + child: new Text( + 'I Accept the Terms And Conditions', + style: new TextStyle(fontSize: 14.0), + ), + ), +// Container( +//// alignment: Alignment.centerRight, +// child: new Text( +// 'Click Here', +// textAlign: TextAlign.end, +// style: new TextStyle(fontSize: 16.0), +// ), +// ), + ], + ), + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(top: 10.0), + child: new Text( + 'You can pay by the following Options:', + textAlign: TextAlign.center, + style: new TextStyle(fontSize: 14.0), + ), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 5.0), + child: Image.asset( + "assets/images/new-design/payment_options_invoice_confirmation.png", + width: 300), + ), + Divider( + thickness: 1.0, + color: Colors.grey[400], + ), + Container( + alignment: Alignment.center, + height: 40.0, + child: Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: InkWell( + onTap: () { + Navigator.pop(context, false); + }, + child: Container( + child: Text("Cancel", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, color: Colors.red[700])), + ), + ), + ), + Expanded( + child: InkWell( + onTap: () { + if(_selected == 0) { + AppToast.showErrorToast(message: "Please accept terms & conditions to continue"); + } else { + Navigator.pop(context, true); + } + }, + child: Container( + child: Text("Ok", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + )), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void onRadioChanged(int value) { + setState(() { + _selected = value; + }); + } + + _getNormalText(text) { + return Container( + margin: EdgeInsets.only(top: 10.0, right: 10.0), + child: Text(text, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14, + fontFamily: 'Open-Sans', + letterSpacing: 0.5, + color: Colors.black)), + ); + } + + _getMarginText(text) { + return Container( + margin: EdgeInsets.only(top: 10.0, right: 10.0, bottom: 10.0), + child: Text(text, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14, + fontFamily: 'Open-Sans', + letterSpacing: 0.5, + fontWeight: FontWeight.bold, + color: Colors.black)), + ); + } +} diff --git a/lib/pages/livecare/widgets/LiveCarePendingRequest.dart b/lib/pages/livecare/widgets/LiveCarePendingRequest.dart new file mode 100644 index 00000000..bfd42e62 --- /dev/null +++ b/lib/pages/livecare/widgets/LiveCarePendingRequest.dart @@ -0,0 +1,167 @@ +import 'package:circular_countdown_timer/circular_countdown_timer.dart'; +import 'package:diplomaticquarterapp/models/FamilyFiles/PatientERVirtualHistoryResponse.dart'; +import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; +import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; + +class LiveCarePendingRequest extends StatefulWidget { + ErRequestHistoryList pendingERRequestHistoryList; + final Function getLiveCareHistory; + + LiveCarePendingRequest( + {@required this.getLiveCareHistory, this.pendingERRequestHistoryList}); + + @override + _LiveCarePendingRequestState createState() => _LiveCarePendingRequestState(); +} + +class _LiveCarePendingRequestState extends State { + @override + void initState() { + // TODO: implement initState + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]), + borderRadius: BorderRadius.circular(10), + color: Colors.white, + shape: BoxShape.rectangle, + ), + margin: EdgeInsets.all(15.0), + padding: EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: Text("In Progress:", + style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(top: 10.0), + child: Text("Estimated Waiting Time: ", + style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), + ), + Container( + transform: Matrix4.translationValues(0.0, -50.0, 0.0), + alignment: Alignment.center, + child: CircularCountDownTimer( + duration: + widget.pendingERRequestHistoryList.watingtimeInteger * 60, + width: MediaQuery.of(context).size.width / 3, + height: MediaQuery.of(context).size.height / 3, + color: Colors.white, + fillColor: Colors.green[700], + strokeWidth: 15.0, + textStyle: TextStyle( + fontSize: 22.0, + color: Colors.black87, + fontWeight: FontWeight.bold), + isReverse: true, + isTimerTextShown: true, + onComplete: () { + print('Countdown Ended'); + }, + ), + ), + Container( + transform: Matrix4.translationValues(0.0, -60.0, 0.0), + child: Divider( + color: Colors.grey[500], + thickness: 0.7, + ), + ), + Container( + transform: Matrix4.translationValues(0.0, -50.0, 0.0), + child: Text("Requested date:", + style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)), + ), + Container( + transform: Matrix4.translationValues(0.0, -30.0, 0.0), + child: Text( + DateUtil.getDateFormatted( + widget.pendingERRequestHistoryList.arrivalTime), + style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), + ), + Container( + transform: Matrix4.translationValues(0.0, -20.0, 0.0), + padding: EdgeInsets.all(7.0), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(5)), + color: Colors.red[800], + ), + margin: EdgeInsets.only(top: 5.0, bottom: 5.0), + child: Text(widget.pendingERRequestHistoryList.stringCallStatus, + style: TextStyle(fontSize: 14.0, color: Colors.white)), + ), + Container( + transform: Matrix4.translationValues(0.0, 0.0, 0.0), + child: Divider( + color: Colors.grey[500], + thickness: 0.7, + ), + ), + Container( + alignment: Alignment.center, + transform: Matrix4.translationValues(0.0, 10.0, 0.0), + child: Text( + "Your turn is after " + + widget.pendingERRequestHistoryList.patCount.toString() + + " Patients", + style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + Container( + transform: Matrix4.translationValues(0.0, 130.0, 0.0), + alignment: Alignment.bottomCenter, + width: MediaQuery.of(context).size.width, + child: ButtonTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + minWidth: MediaQuery.of(context).size.width, + height: 45.0, + child: RaisedButton( + color: Colors.red[800], + textColor: Colors.white, + disabledTextColor: Colors.white, + disabledColor: new Color(0xFFbcc2c4), + onPressed: () { + cancelLiveCareRequest(); + }, + child: Text(TranslationBase.of(context).cancel, + style: TextStyle(fontSize: 18.0)), + ), + ), + ), + ], + ), + ); + } + + cancelLiveCareRequest() { + LiveCareService service = new LiveCareService(); + service + .cancelLiveCareRequest(widget.pendingERRequestHistoryList.vCID, context) + .then((res) { + AppToast.showSuccessToast( + message: "LiveCare request cancelled successfully"); + }) + .catchError((err) { + print(err); + }) + .showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)) + .then((value) { + widget.getLiveCareHistory(); + }); + } +} diff --git a/lib/pages/livecare/widgets/clinic_card.dart b/lib/pages/livecare/widgets/clinic_card.dart new file mode 100644 index 00000000..99c4b271 --- /dev/null +++ b/lib/pages/livecare/widgets/clinic_card.dart @@ -0,0 +1,136 @@ +import 'package:diplomaticquarterapp/models/LiveCare/ClinicsServiceTimingsResponse.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/LiveCareClinicsListResponse.dart'; +import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; + +import 'ClinicTimingsDialog.dart'; + +// ignore: must_be_immutable +class ClinicCard extends StatefulWidget { + bool isSelected; + final PatientERGetClinicsList patientERGetClinicsList; + var languageID; + + ClinicCard( + {this.isSelected, + this.languageID, + @required this.patientERGetClinicsList}); + + @override + _State createState() => _State(); +} + +class _State extends State { + ClinicsServiceTimingsResponse clinicsServiceTimingsResponse; + + @override + void initState() { + clinicsServiceTimingsResponse = new ClinicsServiceTimingsResponse(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Card( + margin: EdgeInsets.fromLTRB(13.0, 10.0, 8.0, 8.0), + color: widget.isSelected ? Colors.blue : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Container( + width: MediaQuery.of(context).size.width * 0.8, + padding: EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Text( + widget.languageID == 'ar' + ? widget.patientERGetClinicsList.serviceNameN + : widget.patientERGetClinicsList.serviceName, + style: TextStyle( + fontSize: 16.0, + color: + widget.isSelected ? Colors.white : Colors.black)), + ), + ], + ), + ), + ), + InkWell( + onTap: () { + getClinicTimings(widget.patientERGetClinicsList); + }, + child: Card( + margin: EdgeInsets.fromLTRB(8.0, 10.0, 8.0, 8.0), + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Icon(Icons.access_time, size: 26.0, color: Colors.red[800]), + ], + ), + ), + ), + ), + ], + ); + } + + getClinicTimings(PatientERGetClinicsList patientERGetClinicsList) { + LiveCareService service = new LiveCareService(); + service + .getLivecareClinicTiming(patientERGetClinicsList.serviceID, context) + .then((res) { + if (res['MessageStatus'] == 1) { + setState(() { + clinicsServiceTimingsResponse = + ClinicsServiceTimingsResponse.fromJson(res); + print(clinicsServiceTimingsResponse + .patientERGetClinicsServiceTimingsList.length); + + showGeneralDialog( + barrierColor: Colors.black.withOpacity(0.5), + transitionBuilder: (context, a1, a2, widget) { + final curvedValue = + Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: + Matrix4.translationValues(0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: ClinicTimingsDialog( + clinicName: patientERGetClinicsList.serviceName, + patientERGetClinicsServiceTimingsList: + clinicsServiceTimingsResponse + .patientERGetClinicsServiceTimingsList, + ), + ), + ); + }, + transitionDuration: Duration(milliseconds: 500), + barrierDismissible: true, + barrierLabel: '', + context: context, + pageBuilder: (context, animation1, animation2) {}); + }); + } else { + AppToast.showErrorToast(message: res['ErrorEndUserMessage']); + } + }).catchError((err) { + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); + } +} diff --git a/lib/pages/livecare/widgets/clinic_list.dart b/lib/pages/livecare/widgets/clinic_list.dart new file mode 100644 index 00000000..77c3e741 --- /dev/null +++ b/lib/pages/livecare/widgets/clinic_list.dart @@ -0,0 +1,388 @@ +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:diplomaticquarterapp/models/Appointments/AppoimentAllHistoryResultList.dart'; +import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/ERAppointmentFeesResponse.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/LiveCareClinicsListResponse.dart'; +import 'package:diplomaticquarterapp/pages/ToDoList/payment_method_select.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/LiveCareInfoDialog.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/LiveCarePaymentDialog.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/clinic_card.dart'; +import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsList.dart'; +import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; +import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; +import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:diplomaticquarterapp/widgets/in_app_browser/InAppBrowser.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:smart_progress_bar/smart_progress_bar.dart'; + +class ClinicList extends StatefulWidget { + final Function getLiveCareHistory; + + ClinicList({@required this.getLiveCareHistory}); + + @override + _clinic_listState createState() => _clinic_listState(); +} + +class _clinic_listState extends State { + int currentSelectedIndex = 0; + LiveCareClinicsListResponse liveCareClinicsListResponse; + + bool isDataLoaded = false; + var languageID; + + int selectedClinicID = 1; + String selectedClinicName = "-"; + + AppSharedPreferences sharedPref = AppSharedPreferences(); + + AuthenticatedUser authUser; + AuthProvider authProvider = new AuthProvider(); + + MyInAppBrowser browser; + + @override + void initState() { + liveCareClinicsListResponse = new LiveCareClinicsListResponse(); + WidgetsBinding.instance.addPostFrameCallback((_) { +// Future.delayed(new Duration(milliseconds: 1200), () { + getLiveCareClinicsList(); +// }); + }); + getLanguageID(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + isShowAppBar: false, + body: SingleChildScrollView( + child: isDataLoaded + ? Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.all(15.0), + child: Text("Online Clinics: ", + style: TextStyle( + fontSize: 20.0, fontWeight: FontWeight.bold)), + ), + ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + physics: ScrollPhysics(), + padding: EdgeInsets.all(0.0), + itemCount: liveCareClinicsListResponse + .patientERGetClinicsList.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + updateSelectedIndex(liveCareClinicsListResponse + .patientERGetClinicsList[index]); + }, + child: ClinicCard( + isSelected: selectedClinicID == + liveCareClinicsListResponse + .patientERGetClinicsList[index] + .serviceID + ? true + : false, + patientERGetClinicsList: liveCareClinicsListResponse + .patientERGetClinicsList[index], + languageID: languageID, + ), + ); + }, + ), + Container( + height: 80.0, + ), + ], + ), + ) + : Container(), + ), + bottomSheet: Container( + width: MediaQuery.of(context).size.width, + height: 50.0, + margin: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + child: ButtonTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + minWidth: MediaQuery.of(context).size.width * 0.7, + height: 45.0, + child: RaisedButton( + color: new Color(0xFF60686b), + textColor: Colors.white, + disabledTextColor: Colors.white, + disabledColor: new Color(0xFFbcc2c4), + onPressed: startLiveCare, + child: Text("Start", style: TextStyle(fontSize: 18.0)), + ), + ), + ), + ); + } + + void startLiveCare() { + LiveCareService service = new LiveCareService(); + ERAppointmentFeesResponse erAppointmentFeesResponse = + new ERAppointmentFeesResponse(); + service + .getERAppointmentFees(selectedClinicID, context) + .then((res) { + erAppointmentFeesResponse = ERAppointmentFeesResponse.fromJson(res); + }) + .catchError((err) { + print(err); + }) + .showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)) + .then((value) { + getERAppointmentTime( + erAppointmentFeesResponse.getERAppointmentFeesList); + }); + } + + getERAppointmentTime(GetERAppointmentFeesList getERAppointmentFeesList) { + LiveCareService service = new LiveCareService(); + service.getERAppointmentTime(selectedClinicID, context).then((res) { + print(res['WatingtimeInteger']); + showLiveCarePaymentDialog( + getERAppointmentFeesList, res['WatingtimeInteger']); + }).catchError((err) { + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); + } + + showLiveCarePaymentDialog( + GetERAppointmentFeesList getERAppointmentFeesList, int waitingTime) { + showGeneralDialog( + barrierColor: Colors.black.withOpacity(0.5), + transitionBuilder: (context, a1, a2, widget) { + final curvedValue = + Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: + Matrix4.translationValues(0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: LiveCarePaymentDialog( + getERAppointmentFeesList: getERAppointmentFeesList, + waitingTime: waitingTime, + clinicName: selectedClinicName), + ), + ); + }, + transitionDuration: Duration(milliseconds: 500), + barrierDismissible: true, + barrierLabel: '', + context: context, + pageBuilder: (context, animation1, animation2) {}) + .then((value) { + if (value) { + if (getERAppointmentFeesList.total == "0" || + getERAppointmentFeesList.total == "0.0") { + showLiveCareInfoDialog(getERAppointmentFeesList); + } else { + navigateToPaymentMethod(getERAppointmentFeesList, context); + } + } + }); + } + + showLiveCareInfoDialog( + GetERAppointmentFeesList getERAppointmentFeesList) async { + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + setState(() { + authUser = data; + }); + } + + showGeneralDialog( + barrierColor: Colors.black.withOpacity(0.5), + transitionBuilder: (context, a1, a2, widget) { + final curvedValue = + Curves.easeInOutBack.transform(a1.value) - 1.0; + return Transform( + transform: + Matrix4.translationValues(0.0, curvedValue * 200, 0.0), + child: Opacity( + opacity: a1.value, + child: LiveCareInfoDialog(), + ), + ); + }, + transitionDuration: Duration(milliseconds: 500), + barrierDismissible: true, + barrierLabel: '', + context: context, + pageBuilder: (context, animation1, animation2) {}) + .then((value) { + if (value) { + if (getERAppointmentFeesList.total == "0" || + getERAppointmentFeesList.total == "0.0") { + addNewCallForPatientER(authUser.patientID.toString() + + "" + + DateTime.now().millisecondsSinceEpoch.toString()); + } else { + navigateToPaymentMethod(getERAppointmentFeesList, context); + } + } + }); + } + + Future navigateToPaymentMethod( + GetERAppointmentFeesList getERAppointmentFeesList, context) async { + AppoitmentAllHistoryResultList appo = new AppoitmentAllHistoryResultList(); + + appo.clinicID = selectedClinicID; + appo.appointmentNo = DateTime.now().millisecondsSinceEpoch; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + setState(() { + authUser = data; + }); + } + + Navigator.push( + context, MaterialPageRoute(builder: (context) => PaymentMethod())) + .then((value) { + print(value); + if (value != null) { + openPayment(value, authUser, + double.parse(getERAppointmentFeesList.total), appo); + } + }); + } + + openPayment(String paymentMethod, AuthenticatedUser authenticatedUser, + double amount, AppoitmentAllHistoryResultList appo) { + browser = new MyInAppBrowser( + onExitCallback: onBrowserExit, + appo: appo, + onLoadStartCallback: onBrowserLoadStart); + + browser.openPaymentBrowser( + amount, + "LiveCare Payment", + Utils.getAppointmentTransID(12, appo.clinicID, appo.appointmentNo), + "12", + authenticatedUser.emailAddress, + paymentMethod, + authenticatedUser, + browser); + } + + onBrowserLoadStart(String url) { + print("onBrowserLoadStart"); + print(url); + + MyInAppBrowser.successURLS.forEach((element) { + if (url.contains(element)) { + if (browser.isOpened()) browser.close(); + MyInAppBrowser.isPaymentDone = true; + return; + } + }); + + MyInAppBrowser.errorURLS.forEach((element) { + if (url.contains(element)) { + if (browser.isOpened()) browser.close(); + MyInAppBrowser.isPaymentDone = false; + return; + } + }); + } + + onBrowserExit(AppoitmentAllHistoryResultList appo, bool isPaymentMade) { + print("onBrowserExit Called!!!!"); + if (isPaymentMade) checkPaymentStatus(appo); + } + + checkPaymentStatus(AppoitmentAllHistoryResultList appo) { + DoctorsListService service = new DoctorsListService(); + service + .checkPaymentStatus( + Utils.getAppointmentTransID( + appo.projectID, appo.clinicID, appo.appointmentNo), + context) + .then((res) { + print("Printing Payment Status Reponse!!!!"); + print(res); + String paymentInfo = res['Response_Message']; + if (paymentInfo == 'Success') { + addNewCallForPatientER(Utils.getAppointmentTransID( + appo.projectID, appo.clinicID, appo.appointmentNo)); + } else { + AppToast.showErrorToast(message: res['Response_Message']); + } + }).catchError((err) { + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); + } + + addNewCallForPatientER(String clientRequestID) { + LiveCareService service = new LiveCareService(); + service + .addNewCallForPatientER(selectedClinicID, clientRequestID, context) + .then((res) { + AppToast.showSuccessToast( + message: "New Call has been added successfully"); + }).catchError((err) { + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)).then((value) { + widget.getLiveCareHistory(); + }); + } + + getLanguageID() async { + languageID = await sharedPref.getString(APP_LANGUAGE); + } + + getLiveCareClinicsList() { + isDataLoaded = false; + LiveCareService service = new LiveCareService(); + service.getLivecareClinics(context).then((res) { + print(res['PatientER_GetClinicsList'].length); + if (res['MessageStatus'] == 1) { + setState(() { + liveCareClinicsListResponse = + LiveCareClinicsListResponse.fromJson(res); + print(liveCareClinicsListResponse.patientERGetClinicsList.length); + selectedClinicID = + liveCareClinicsListResponse.patientERGetClinicsList[0].serviceID; + selectedClinicName = liveCareClinicsListResponse + .patientERGetClinicsList[0].serviceName; + isDataLoaded = true; + }); + } else { + isDataLoaded = true; + AppToast.showErrorToast(message: res['ErrorEndUserMessage']); + } + }).catchError((err) { + print(err); + }).showProgressBar( + text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6)); + } + + updateSelectedIndex(PatientERGetClinicsList patientERGetClinicsList) { + setState(() { + selectedClinicID = patientERGetClinicsList.serviceID; + selectedClinicName = patientERGetClinicsList.serviceName; + }); + } +} diff --git a/lib/pages/livecare/widgets/livecare_logs.dart b/lib/pages/livecare/widgets/livecare_logs.dart new file mode 100644 index 00000000..a7f7fdc0 --- /dev/null +++ b/lib/pages/livecare/widgets/livecare_logs.dart @@ -0,0 +1,40 @@ +import 'package:diplomaticquarterapp/models/FamilyFiles/PatientERVirtualHistoryResponse.dart'; +import 'package:diplomaticquarterapp/pages/livecare/widgets/LiveCareHistoryCard.dart'; +import 'package:flutter/material.dart'; + +class LiveCareLogs extends StatefulWidget { + List erRequestHistoryList; + + LiveCareLogs({@required this.erRequestHistoryList}); + + @override + _LiveCareLogsState createState() => _LiveCareLogsState(); +} + +class _LiveCareLogsState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + child: ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + physics: ScrollPhysics(), + padding: EdgeInsets.all(0.0), + itemCount: widget.erRequestHistoryList.length, + itemBuilder: (context, index) { + return widget.erRequestHistoryList[index].callStatus < 4 + ? Container() + : LiveCareHistoryCard( + erRequestHistoryList: widget.erRequestHistoryList[index]); + }, + ), + ), + ); + } +} diff --git a/lib/pages/medical/vital_sign/vital_sign_details_screen.dart b/lib/pages/medical/vital_sign/vital_sign_details_screen.dart index 61da0967..a0dc78ff 100644 --- a/lib/pages/medical/vital_sign/vital_sign_details_screen.dart +++ b/lib/pages/medical/vital_sign/vital_sign_details_screen.dart @@ -14,174 +14,203 @@ import '../../../d_q_icons_icons.dart'; class VitalSignDetailsScreen extends StatelessWidget { static const String url = "assets/images/"; + int appointmentNo; + int projectID; + + VitalSignDetailsScreen({this.appointmentNo, this.projectID}); + @override Widget build(BuildContext context) { return BaseView( - onModelReady: (model) => model.getPatientRadOrders(), + onModelReady: appointmentNo != null && projectID != null + ? (model) => model.getPatientRadOrders( + appointmentNo: appointmentNo, projectID: projectID) + : (model) => model.getPatientRadOrders(), builder: (_, mode, widget) => AppScaffold( isShowAppBar: true, appBarTitle: 'Vital Sign', baseViewModel: mode, - body: mode.vitalSignResModelList.length> 0? Container( - child: ListView( - children: [ - Row( - children: [ - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.Height, - pageTitle: 'Height', - vitalList: mode.vitalSignResModelList, + body: mode.vitalSignResModelList.length > 0 + ? Container( + child: ListView( + children: [ + Row( + children: [ + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.Height, + pageTitle: 'Height', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: Container( + child: VitalSignItem( + des: TranslationBase.of(context).height, + icon: DQIcons.height, + lastVal: mode + .vitalSignResModelList[ + mode.vitalSignResModelList.length - 1] + .heightCm + .toString(), + unit: ' Cm', + ), + ), ), - ), - ), - child: Container( - child: VitalSignItem( - des: TranslationBase.of(context).height, - icon: DQIcons.height, - lastVal: mode.vitalSignResModelList[mode.vitalSignResModelList.length-1].heightCm.toString(), - unit: ' Cm', - ), - ), - ), - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.Weight, - pageTitle: 'Weight', - vitalList: mode.vitalSignResModelList, + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.Weight, + pageTitle: 'Weight', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: VitalSignItem( + des: TranslationBase.of(context).weight, + icon: DQIcons.weight_scale, + unit: ' Kg', + lastVal: mode + .vitalSignResModelList[ + mode.vitalSignResModelList.length - 1] + .weightKg + .toString(), + ), ), - ), - ), - child: VitalSignItem( - des: TranslationBase.of(context).weight, - icon: DQIcons.weight_scale, - unit: ' Kg', - lastVal: mode.vitalSignResModelList[mode.vitalSignResModelList.length-1].weightKg.toString(), + ], ), - ), - - ], - ), - Row( - children: [ - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.BodyMeasurements, - pageTitle: 'BMI', - vitalList: mode.vitalSignResModelList, + Row( + children: [ + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.BodyMeasurements, + pageTitle: 'BMI', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: VitalSignItem( + des: TranslationBase.of(context).bodyMeasurements, + icon: DQIcons.bmi, + lastVal: mode + .vitalSignResModelList[0].pulseBeatPerMinute + .toString(), + unit: 'BMI', + ), ), - ), - ), - child: VitalSignItem( - des: TranslationBase.of(context).bodyMeasurements, - icon: DQIcons.bmi, - lastVal: mode.vitalSignResModelList[1].pulseBeatPerMinute - .toString(), - unit: 'BMI', - ), - ), - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.Temperature, - pageTitle: 'Temperature', - vitalList: mode.vitalSignResModelList, + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.Temperature, + pageTitle: 'Temperature', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: Container( + child: VitalSignItem( + des: TranslationBase.of(context).temperature, + icon: DQIcons.thermometer, + lastVal: mode + .vitalSignResModelList[0].temperatureCelcius + .toString(), + unit: 'C', + ), + ), ), - ), + ], ), - child: Container( - child: VitalSignItem( - des: TranslationBase.of(context).temperature, - icon: DQIcons.thermometer, - lastVal: mode - .vitalSignResModelList[1].temperatureCelcius - .toString(), - unit: 'C', - ), - ), - ), - - ], - ), - Row( - children: [ - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.heart, - pageTitle: 'Hart ', - vitalList: mode.vitalSignResModelList, + Row( + children: [ + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.heart, + pageTitle: 'Hart ', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: VitalSignItem( + des: TranslationBase.of(context).heart, + icon: DQIcons.heart, + lastVal: mode + .vitalSignResModelList[ + mode.vitalSignResModelList.length - 1] + .pulseBeatPerMinute + .toString(), + unit: ' bpm', + ), ), - ), - ), - child: VitalSignItem( - des: TranslationBase.of(context).heart, - icon: DQIcons.heart, - lastVal: mode.vitalSignResModelList[mode.vitalSignResModelList.length-1].pulseBeatPerMinute - .toString(), - unit: ' bpm', - ), - ), - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.Respiration, - pageTitle: 'Respiration Rate', - vitalList: mode.vitalSignResModelList, + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.Respiration, + pageTitle: 'Respiration Rate', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: VitalSignItem( + des: TranslationBase.of(context).respirationRate, + icon: DQIcons.outline, + lastVal: mode + .vitalSignResModelList[ + mode.vitalSignResModelList.length - 1] + .respirationBeatPerMinute + .toString(), + unit: ' bmp', + ), ), - ), - ), - child: VitalSignItem( - des: TranslationBase.of(context).respirationRate, - icon: DQIcons.outline, - lastVal: mode.vitalSignResModelList[mode.vitalSignResModelList.length-1].respirationBeatPerMinute.toString(), - unit: ' bmp', + ], ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - InkWell( - onTap: () => Navigator.push( - context, - FadePage( - page: VitalSignItemDetailsScreen( - pageKey: VitalSignDetails.BloodPressure, - pageTitle: 'BloodPressure', - vitalList: mode.vitalSignResModelList, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () => Navigator.push( + context, + FadePage( + page: VitalSignItemDetailsScreen( + pageKey: VitalSignDetails.BloodPressure, + pageTitle: 'BloodPressure', + vitalList: mode.vitalSignResModelList, + ), + ), + ), + child: VitalSignItem( + des: TranslationBase.of(context).pulse, + icon: DQIcons.blood_pressure, + lastVal: mode + .vitalSignResModelList[ + mode.vitalSignResModelList.length - 1] + .bloodPressure + .toString(), + unit: ' SBP/DBP', + ), ), - ), - ), - child: VitalSignItem( - des: TranslationBase.of(context).pulse, - icon: DQIcons.blood_pressure, - lastVal: mode.vitalSignResModelList[mode.vitalSignResModelList.length -1].bloodPressure - .toString(), - unit: ' SBP/DBP', + ], ), - ), - ], + ], + ), + ) + : Center( + child: Texts('No Data'), ), - ], - ), - ) : Center(child: Texts('No Data'),), ), ); } diff --git a/lib/routes.dart b/lib/routes.dart index 4c890806..b1f216e8 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,5 +1,6 @@ import 'package:diplomaticquarterapp/pages/family/my-family.dart'; import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; +import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; import 'package:diplomaticquarterapp/pages/login/confirm-login.dart'; import 'package:diplomaticquarterapp/pages/login/forgot-password.dart'; import 'package:diplomaticquarterapp/pages/login/register-info.dart'; @@ -19,6 +20,7 @@ const String REGISTER = 'register'; const String CONFIRM_LOGIN = 'confrim-login'; const String REGISTER_INFO = 'register-info'; const String MY_FAMILIY = 'my-family'; +const String LIVE_CARE = 'live-care'; var routes = { HOME: (_) => LandingPage(), WELCOME_LOGIN: (_) => WelcomeLogin(), @@ -28,5 +30,6 @@ var routes = { REGISTER: (_) => Register(), CONFIRM_LOGIN: (_) => ConfirmLogin(), REGISTER_INFO: (_) => RegisterInfo(), - MY_FAMILIY: (_) => MyFamily() + MY_FAMILIY: (_) => MyFamily(), + LIVE_CARE: (_) => LiveCareHome() }; diff --git a/lib/services/appointment_services/GetDoctorsList.dart b/lib/services/appointment_services/GetDoctorsList.dart index 05205de2..6e6f804e 100644 --- a/lib/services/appointment_services/GetDoctorsList.dart +++ b/lib/services/appointment_services/GetDoctorsList.dart @@ -10,7 +10,6 @@ import 'package:diplomaticquarterapp/models/Request.dart'; import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; -import 'package:diplomaticquarterapp/uitl/utils.dart'; import 'package:flutter/cupertino.dart'; class DoctorsListService extends BaseService { @@ -22,7 +21,6 @@ class DoctorsListService extends BaseService { Future getDoctorsList( int clinicID, int projectID, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -61,9 +59,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_DOCTORS_LIST_URL, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -117,7 +113,6 @@ class DoctorsListService extends BaseService { Future getDoctorsProfile( int docID, int clinicID, int projectID, context) async { - Utils.showProgressDialog(context); Map request; var languageID = await sharedPref.getString(APP_LANGUAGE); Request req = appGlobal.getPublicRequest(); @@ -145,9 +140,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_DOCTOR_PROFILE, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -155,7 +148,6 @@ class DoctorsListService extends BaseService { Future getDoctorFreeSlots( int docID, int clinicID, int projectID, BuildContext context) async { - Utils.showProgressDialog(context); Map request; var languageID = await sharedPref.getString(APP_LANGUAGE); Request req = appGlobal.getPublicRequest(); @@ -183,9 +175,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_DOCTOR_FREE_SLOTS, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -193,8 +183,6 @@ class DoctorsListService extends BaseService { Future insertAppointment(int docID, int clinicID, int projectID, String selectedTime, String selectedDate, BuildContext context) async { - Utils.showProgressDialog(context); - Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -240,9 +228,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(INSERT_SPECIFIC_APPOINTMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -250,7 +236,6 @@ class DoctorsListService extends BaseService { Future getPatientShare( String appoID, int clinicID, int projectID, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -287,9 +272,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_PATIENT_SHARE, onSuccess: (response, statusCode) async { localRes = response['OnlineCheckInAppointments'][0]; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -297,7 +280,6 @@ class DoctorsListService extends BaseService { Future getPatientAppointmentHistory( bool isActiveAppointment, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -331,9 +313,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_PATIENT_APPOINTMENT_HISTORY, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -375,7 +355,6 @@ class DoctorsListService extends BaseService { Future confirmAppointment( int appoNo, int clinicID, int projectID, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -412,9 +391,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(CONFIRM_APPOINTMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -422,7 +399,6 @@ class DoctorsListService extends BaseService { Future cancelAppointment( AppoitmentAllHistoryResultList appo, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -465,9 +441,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(CANCEL_APPOINTMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -475,7 +449,6 @@ class DoctorsListService extends BaseService { Future generateAppointmentQR( PatientShareResponse patientShareResponse, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -512,9 +485,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GENERATE_QR_APPOINTMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -529,7 +500,6 @@ class DoctorsListService extends BaseService { String QR, String speciality, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { @@ -569,9 +539,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(EMAIL_QR_APPOINTMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -655,7 +623,6 @@ class DoctorsListService extends BaseService { } Future isAllowedToAskDoctor(int docID, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -689,16 +656,13 @@ class DoctorsListService extends BaseService { await baseAppClient.post(IS_ALLOW_ASK_DOCTOR, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); } Future getCallRequestType(BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -727,9 +691,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_CALL_REQUEST_TYPE, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -737,7 +699,6 @@ class DoctorsListService extends BaseService { Future sendAskDocCallRequest(AppoitmentAllHistoryResultList appo, String requestType, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -790,16 +751,13 @@ class DoctorsListService extends BaseService { await baseAppClient.post(SEND_CALL_REQUEST, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); } Future getPatientRadOrders(String appoNo, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -829,9 +787,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_PATIENT_ORDERS, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -839,7 +795,6 @@ class DoctorsListService extends BaseService { Future getPatientPrescriptionReports( AppoitmentAllHistoryResultList appo, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -873,9 +828,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(GET_PRESCRIPTION_REPORT_ENH, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -883,7 +836,6 @@ class DoctorsListService extends BaseService { Future sendPrescriptionEmail(String appoDate, String setupId, dynamic prescriptionReportEnhList, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -920,9 +872,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(SEND_PRESCRIPTION_EMAIL, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); @@ -934,7 +884,6 @@ class DoctorsListService extends BaseService { String paymentReference, String paymentMethodName, BuildContext context) async { - Utils.showProgressDialog(context); Map request; if (await this.sharedPref.getObject(USER_PROFILE) != null) { var data = AuthenticatedUser.fromJson( @@ -978,9 +927,7 @@ class DoctorsListService extends BaseService { await baseAppClient.post(CREATE_ADVANCE_PAYMENT, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); diff --git a/lib/services/clinic_services/get_clinic_service.dart b/lib/services/clinic_services/get_clinic_service.dart index 05f6a2cb..42c9754c 100644 --- a/lib/services/clinic_services/get_clinic_service.dart +++ b/lib/services/clinic_services/get_clinic_service.dart @@ -1,17 +1,14 @@ import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/service/base_service.dart'; -import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart'; import 'package:diplomaticquarterapp/models/Request.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; -import 'package:diplomaticquarterapp/uitl/utils.dart'; class ClinicListService extends BaseService { AppSharedPreferences sharedPref = AppSharedPreferences(); AppGlobal appGlobal = new AppGlobal(); Future getClinicsList(context) async { - Utils.showProgressDialog(context); Map request; var languageID = await sharedPref.getString(APP_LANGUAGE); Request req = appGlobal.getPublicRequest(); @@ -32,9 +29,7 @@ class ClinicListService extends BaseService { await baseAppClient.post(GET_CLINICS_LIST_URL, onSuccess: (response, statusCode) async { localRes = response; - Utils.hideProgressDialog(); }, onFailure: (String error, int statusCode) { - Utils.hideProgressDialog(); throw error; }, body: request); return Future.value(localRes); diff --git a/lib/services/livecare_services/livecare_provider.dart b/lib/services/livecare_services/livecare_provider.dart new file mode 100644 index 00000000..f76e0336 --- /dev/null +++ b/lib/services/livecare_services/livecare_provider.dart @@ -0,0 +1,304 @@ +import 'dart:io'; + +import 'package:diplomaticquarterapp/config/config.dart'; +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:diplomaticquarterapp/core/service/base_service.dart'; +import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/models/Request.dart'; +import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; +import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; +import 'package:flutter/material.dart'; + +class LiveCareService extends BaseService { + AppSharedPreferences sharedPref = AppSharedPreferences(); + AppGlobal appGlobal = new AppGlobal(); + + AuthenticatedUser authUser = new AuthenticatedUser(); + AuthProvider authProvider = new AuthProvider(); + + Future getLivecareClinics(BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); + Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "IPAdress": "10.20.10.20", + "VersionID": req.VersionID, + "Channel": req.Channel, + "generalid": 'Cs2020@2016\$2958', + "PatientOutSA": 0, + "TokenID": "", + "DeviceTypeID": req.DeviceTypeID, + "SessionID": "YckwoXhUmWBsnHKEKig", + "Age": authUser.age != null ? authUser.age : 0, + "PatientID": authUser.patientID != null ? authUser.patientID : 0, + "Gender": authUser.gender != null ? authUser.gender : 0 + }; + + dynamic localRes; + + await baseAppClient.post(GET_LIVECARE_CLINICS, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future getLivecareHistory(BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); +// Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "TokenID": "", + "SessionID": "YckwoXhUmWBsnHKEKig" + }; + + dynamic localRes; + + await baseAppClient.post(GET_LIVECARE_HISTORY, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future getLivecareClinicTiming( + int serviceID, BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); + Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "IPAdress": "10.20.10.20", + "VersionID": req.VersionID, + "Channel": req.Channel, + "generalid": 'Cs2020@2016\$2958', + "PatientOutSA": 0, + "ServiceID": serviceID, + "DeviceTypeID": req.DeviceTypeID, + "SessionID": "YckwoXhUmWBsnHKEKig", + "Age": authUser.age != null ? authUser.age : 0, + "PatientID": authUser.patientID != null ? authUser.patientID : 0, + "Gender": authUser.gender != null ? authUser.gender : 0 + }; + + dynamic localRes; + + await baseAppClient.post(GET_LIVECARE_CLINIC_TIMING, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future getERAppointmentFees(int serviceID, BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); + Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "IPAdress": "10.20.10.20", + "VersionID": req.VersionID, + "Channel": req.Channel, + "generalid": 'Cs2020@2016\$2958', + "PatientOutSA": 0, + "ServiceID": serviceID, + "ProjectID": 15, + "DeviceTypeID": req.DeviceTypeID, + "PatientType": authUser.patientType != null ? authUser.patientType : 0, + "PatientTypeID": authUser.patientType != null ? authUser.patientType : 0, + "SessionID": "YckwoXhUmWBsnHKEKig", + "Age": authUser.age != null ? authUser.age : 0, + "PatientID": authUser.patientID != null ? authUser.patientID : 0, + "Gender": authUser.gender != null ? authUser.gender : 0 + }; + + dynamic localRes; + + await baseAppClient.post(GET_ER_APPOINTMENT_FEES, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future getERAppointmentTime(int serviceID, BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); + Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "IPAdress": "10.20.10.20", + "VersionID": req.VersionID, + "Channel": req.Channel, + "generalid": 'Cs2020@2016\$2958', + "PatientOutSA": 0, + "ServiceID": serviceID, + "ProjectID": 15, + "DeviceTypeID": req.DeviceTypeID, + "PatientType": authUser.patientType != null ? authUser.patientType : 0, + "PatientTypeID": authUser.patientType != null ? authUser.patientType : 0, + "SessionID": "YckwoXhUmWBsnHKEKig", + "Age": authUser.age != null ? authUser.age : 0, + "PatientID": authUser.patientID != null ? authUser.patientID : 0, + "Gender": authUser.gender != null ? authUser.gender : 0 + }; + + dynamic localRes; + + await baseAppClient.post(GET_ER_APPOINTMENT_TIME, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future addNewCallForPatientER( + int serviceID, String clientRequestID, BuildContext context) async { + Map request; + + String deviceToken; + getDeviceToken().then((value) { + print(value); + deviceToken = value; + }); + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + + var languageID = await sharedPref.getString(APP_LANGUAGE); + Request req = appGlobal.getPublicRequest(); + request = { + "LanguageID": languageID == 'ar' ? 1 : 2, + "IPAdress": "10.20.10.20", + "VersionID": req.VersionID, + "Channel": req.Channel, + "generalid": 'Cs2020@2016\$2958', + "PatientOutSA": 0, + "ErServiceID": serviceID, + "ClientRequestID": clientRequestID, + "DeviceToken": deviceToken, + "VoipToken": "", + "Latitude": "24.708488", + "Longitude": "46.665925", + "DeviceType": Platform.isIOS ? 'iOS' : 'Android', + "PatientType": authUser.patientType != null ? authUser.patientType : 0, + "PatientTypeID": authUser.patientType != null ? authUser.patientType : 0, + "SessionID": "YckwoXhUmWBsnHKEKig", + "Age": authUser.age != null ? authUser.age : 0, + "PatientID": authUser.patientID != null ? authUser.patientID : 0, + "Gender": authUser.gender != null ? authUser.gender : 0 + }; + + dynamic localRes; + + await baseAppClient.post(ADD_NEW_CALL_FOR_PATIENT_ER, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future getDeviceToken() async { + String deviceToken = await sharedPref.getString(PUSH_TOKEN); + return deviceToken; + } + + Future cancelLiveCareRequest(int vc_id, BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + request = {"VCID": vc_id}; + + dynamic localRes; + + await baseAppClient.post(CANCEL_LIVECARE_REQUEST, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } + + Future sendLiveCareInvoiceEmail(String appoNo, int projectID, + String emailAddress, BuildContext context) async { + Map request; + + if (await this.sharedPref.getObject(USER_PROFILE) != null) { + var data = AuthenticatedUser.fromJson( + await this.sharedPref.getObject(USER_PROFILE)); + authUser = data; + } + request = { + "To": emailAddress, + "ProjectID": projectID, + "AppointmentNo": appoNo + }; + + dynamic localRes; + + await baseAppClient.post(SEND_LIVECARE_INVOICE_EMAIL, + onSuccess: (response, statusCode) async { + localRes = response; + }, onFailure: (String error, int statusCode) { + throw error; + }, body: request); + return Future.value(localRes); + } +} diff --git a/lib/uitl/navigation_service.dart b/lib/uitl/navigation_service.dart new file mode 100644 index 00000000..d4cd426b --- /dev/null +++ b/lib/uitl/navigation_service.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + final GlobalKey navigatorKey = + new GlobalKey(); + + Future navigateTo(String routeName) { + return navigatorKey.currentState.pushNamed(routeName); + } +} diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 8943e84c..13681a86 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -421,6 +421,11 @@ class TranslationBase { String get viewAllWaysReachUs =>localizedValues['ViewAllWaysReachUs'][locale.languageCode]; String get medicalProfile =>localizedValues['medicalProfile'][locale.languageCode]; + + String get consultation => + localizedValues['consultation'][locale.languageCode]; + String get logs => + localizedValues['logs'][locale.languageCode]; } class TranslationBaseDelegate extends LocalizationsDelegate { diff --git a/lib/uitl/utils.dart b/lib/uitl/utils.dart index 7be65a40..7100fa5f 100644 --- a/lib/uitl/utils.dart +++ b/lib/uitl/utils.dart @@ -1,8 +1,6 @@ import 'package:connectivity/connectivity.dart'; -import 'package:diplomaticquarterapp/models/Appointments/AppoimentAllHistoryResultList.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:progress_dialog/progress_dialog.dart'; import 'app_shared_preferences.dart'; import 'app_toast.dart'; @@ -10,7 +8,6 @@ import 'app_toast.dart'; AppSharedPreferences sharedPref = new AppSharedPreferences(); class Utils { - static ProgressDialog pr; ///show custom Error Toast /// [message] to show for user @@ -109,34 +106,4 @@ class Utils { var length = loginType == 1 ? 10 : 7; return "([0-9]{" + length.toString() + "})"; } - - static showProgressDialog(context, [String message = "Loading..."]) async { - pr = ProgressDialog(context, - type: ProgressDialogType.Normal, isDismissible: false, showLogs: false); - pr.style( - message: message, - borderRadius: 10.0, - backgroundColor: Colors.white, - elevation: 10.0, - insetAnimCurve: Curves.easeInOut, - progress: 0.0, - maxProgress: 100.0, - progressTextStyle: TextStyle( - color: Colors.black, fontSize: 13.0, fontWeight: FontWeight.w400), - messageTextStyle: TextStyle( - color: Colors.black, fontSize: 19.0, fontWeight: FontWeight.w600)); - - if (!pr.isShowing()) { - await pr.show(); - } else { - await pr.hide(); - await pr.show(); - } - } - - static hideProgressDialog() async { - if (pr.isShowing()) { - await pr.hide(); - } - } } diff --git a/lib/widgets/dialogs/alert_dialog.dart b/lib/widgets/dialogs/alert_dialog.dart new file mode 100644 index 00000000..81507326 --- /dev/null +++ b/lib/widgets/dialogs/alert_dialog.dart @@ -0,0 +1,45 @@ +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class AlertDialogBox { + final BuildContext context; + + final confirmMessage; + final okText; + final Function okFunction; + + AlertDialogBox( + {@required this.context, + @required this.confirmMessage, + @required this.okText, + @required this.okFunction}); + + showAlertDialog(BuildContext context) { + Widget continueButton = + FlatButton(child: Text(this.okText), onPressed: this.okFunction); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: Text(TranslationBase.of(context).confirm), + content: Text(this.confirmMessage), + actions: [ + continueButton, + ], + ); + + // show the dialog + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } + + static closeAlertDialog(BuildContext context) { + Navigator.of(context).pop(); + } + +} diff --git a/pubspec.yaml b/pubspec.yaml index c711d722..ca6963a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: url_launcher: ^5.5.0 shared_preferences: ^0.5.8 flutter_flexible_toast: ^0.1.4 - firebase_messaging: ^6.0.16 + firebase_messaging: 6.0.12 # Progress bar progress_hud_v2: ^2.0.0 @@ -61,7 +61,7 @@ dependencies: # Notification Banner dropdown_banner: ^1.4.0 - flutter_local_notifications: +# flutter_local_notifications: # charts charts_flutter: ^0.9.0 @@ -87,13 +87,24 @@ dependencies: #InAppBrowser flutter_inappwebview: ^4.0.0+4 - #ProgressDialog - progress_dialog: ^1.2.4 + #Circular progress bar for reverse timer + circular_countdown_timer: ^0.0.5 + + smart_progress_bar: ^0.1.6 + + #Just Audio to play ringing for incoming video call + just_audio: ^0.3.4 #hijri hijri: ^2.0.3 - + #Dependencies for video call implementation + native_device_orientation: ^0.3.0 + enum_to_string: ^1.0.9 +# recase: ^3.0.0 + wakelock: ^0.1.4 + after_layout: ^1.0.7 + twilio_programmable_video: ^0.5.0+3 dev_dependencies: flutter_test: @@ -112,6 +123,7 @@ flutter: - assets/images/new-design/ - assets/images/login/ - assets/json/ + - assets/sounds/ fonts: