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/images/Dr_Schedule_report.png b/assets/images/Dr_Schedule_report.png
new file mode 100644
index 00000000..9519f719
Binary files /dev/null and b/assets/images/Dr_Schedule_report.png differ
diff --git a/assets/images/al-habib_onlne_pharmacy_bg.png b/assets/images/al-habib_onlne_pharmacy_bg.png
new file mode 100644
index 00000000..d8809570
Binary files /dev/null and b/assets/images/al-habib_onlne_pharmacy_bg.png differ
diff --git a/assets/images/bg_graphic.png b/assets/images/bg_graphic.png
new file mode 100644
index 00000000..01525404
Binary files /dev/null and b/assets/images/bg_graphic.png differ
diff --git a/assets/images/contact_us_bg.png b/assets/images/contact_us_bg.png
new file mode 100644
index 00000000..1a7cc71e
Binary files /dev/null and b/assets/images/contact_us_bg.png differ
diff --git a/assets/images/emergency_service_image.png b/assets/images/emergency_service_image.png
new file mode 100644
index 00000000..4dbcb7d6
Binary files /dev/null and b/assets/images/emergency_service_image.png differ
diff --git a/assets/images/hmg_services_bg .png b/assets/images/hmg_services_bg .png
new file mode 100644
index 00000000..508f9a0d
Binary files /dev/null and b/assets/images/hmg_services_bg .png differ
diff --git a/assets/images/home_healthcare_service_bg.png b/assets/images/home_healthcare_service_bg.png
new file mode 100644
index 00000000..2765b47e
Binary files /dev/null and b/assets/images/home_healthcare_service_bg.png differ
diff --git a/assets/images/offer_icon.png b/assets/images/offer_icon.png
new file mode 100644
index 00000000..527fd5a2
Binary files /dev/null and b/assets/images/offer_icon.png differ
diff --git a/assets/images/timeline_bg.png b/assets/images/timeline_bg.png
new file mode 100644
index 00000000..d7afc4b1
Binary files /dev/null and b/assets/images/timeline_bg.png differ
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 250135ac..e9ee7893 100644
--- a/lib/config/config.dart
+++ b/lib/config/config.dart
@@ -43,15 +43,22 @@ const SEND_RAD_REPORT_EMAIL =
const SEND_FEEDBACK = 'Services/COCWS.svc/REST/InsertCOCItemInSPList';
const GET_STATUS_FOR_COCO = 'Services/COCWS.svc/REST/GetStatusforCOC';
const GET_PATIENT_AppointmentHistory =
- '/Doctors.svc/REST/PateintHasAppoimentHistory';
+ 'Services/Doctors.svc/REST/PateintHasAppoimentHistory';
///VITAL SIGN
-const GET_PATIENT_VITAL_SIGN = '/Doctors.svc/REST/Doctor_GetPatientVitalSign';
+const GET_PATIENT_VITAL_SIGN =
+ 'Services/Doctors.svc/REST/Doctor_GetPatientVitalSign';
///Reports
-const REPORTS = '/Doctors.svc/REST/GetPatientMedicalReportStatusInfo';
+const REPORTS = 'Services/Doctors.svc/REST/GetPatientMedicalReportStatusInfo';
const INSERT_REQUEST_FOR_MEDICAL_REPORT =
- '/Doctors.svc/REST/InsertRequestForMedicalReport';
+ 'Services/Doctors.svc/REST/InsertRequestForMedicalReport';
+
+///Rate
+const IS_LAST_APPOITMENT_RATED =
+ 'Services/Doctors.svc/REST/IsLastAppoitmentRated';
+const GET_APPOINTMENT_DETAILS_BY_NO =
+ 'Services/MobileNotifications.svc/REST/GetAppointmentDetailsByApptNo';
//const BASE_URL = 'https://hmgwebservices.com/'; // Production Environment
//const BASE_URL = 'https://hmgwebservices.com/Services'; // Production Environment
@@ -113,6 +120,25 @@ const GET_CALL_REQUEST_TYPE =
'Services/Doctors.svc/REST/GetCallRequestType_LOV';
const SEND_CALL_REQUEST = 'Services/Doctors.svc/REST/InsertCallInfo';
+const GET_LIVECARE_CLINICS =
+ 'Services/ER_VirtualCall.svc/REST/PatientER_GetClinics';
+const GET_LIVECARE_CLINIC_TIMING =
+ 'Services/ER_VirtualCall.svc/REST/PatientER_GetClinicsServiceTimingsSchedule';
+
+const GET_ER_APPOINTMENT_FEES =
+ 'Services/DoctorApplication.svc/REST/GetERAppointmentFees';
+const GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime';
+
+const ADD_NEW_CALL_FOR_PATIENT_ER =
+ 'Services/DoctorApplication.svc/REST/NewCallForPatientER';
+
+const GET_LIVECARE_HISTORY =
+ 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory';
+const CANCEL_LIVECARE_REQUEST =
+ 'Services/ER_VirtualCall.svc/REST/DeleteErRequest';
+const SEND_LIVECARE_INVOICE_EMAIL =
+ 'Services/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 7e783090..570563ba 100644
--- a/lib/config/localized_values.dart
+++ b/lib/config/localized_values.dart
@@ -327,4 +327,39 @@ const Map> localizedValues = {
"en": "Remove this member?",
"ar": "إزالة ملف العضو؟"
},
+ "MyMedicalFile": {"en": "My Medical File", 'ar': 'ملف الطبي الالكتروني'},
+ "myMedicalFileSubTitle": {
+ "en": "All your medical records",
+ 'ar': 'جميع سجلاتك البية'
+ },
+ "viewMore": {"en": "View More", 'ar': 'عرض المزيد'},
+ "homeHealthCareService": {
+ "en": "Home Health Care Service",
+ 'ar': 'الرعاية الصحية المنزلية'
+ },
+ "OnlinePharmacy": {"en": "Online Pharmacy", 'ar': 'صيدلايات الحبيب'},
+ "EmergencyService": {"en": "Emergency Service", 'ar': 'الفحص الطبي الشامل'},
+ "OnlinePaymentService": {
+ "en": "Online Payment Service",
+ 'ar': 'خدمة الدفع عبر الإلكتدوني'
+ },
+ "OffersAndPackages": {"en": "Offers and Packages", 'ar': 'العروض والباقات'},
+ "ComprehensiveMedicalCheckup": {
+ "en": "Comprehensive Medical Check up",
+ 'ar': 'فحص طبي شامل'
+ },
+ "HMGService": {"en": "HMG Service", 'ar': 'جميع خدمات الحبيب'},
+ "ViewAllHabibMedicalService": {
+ "en": "View All Habib Medical Service",
+ 'ar': 'عرض خدمات الحبيب الطبية'
+ },
+ "viewAll": {"en": "View All", 'ar': 'عرض الكل'},
+ "ContactUs": {"en": "Contact Us", 'ar': 'الوصول إلينا'},
+ "ViewAllWaysReachUs": {
+ "en": "View All Ways Reach Us",
+ 'ar': 'جميع طرق الاتصال بنا'
+ },
+ "medicalProfile": {"en": "Medical Profile", 'ar': 'الملف الطبي'},
+ "consultation": {"en": "Consultation", "ar": "استشارة"},
+ "logs": {"en": "Logs", "ar": "السجلات"}
};
diff --git a/lib/core/model/rate/appointment_details.dart b/lib/core/model/rate/appointment_details.dart
new file mode 100644
index 00000000..30280b4a
--- /dev/null
+++ b/lib/core/model/rate/appointment_details.dart
@@ -0,0 +1,64 @@
+class AppointmentDetails {
+ String setupID;
+ int projectID;
+ int patientID;
+ int appointmentNo;
+ int clinicID;
+ int doctorID;
+ String startTime;
+ String endTime;
+ String appointmentDate;
+ String clinicName;
+ String doctorImageURL;
+ String doctorName;
+ String projectName;
+
+ AppointmentDetails(
+ {this.setupID,
+ this.projectID,
+ this.patientID,
+ this.appointmentNo,
+ this.clinicID,
+ this.doctorID,
+ this.startTime,
+ this.endTime,
+ this.appointmentDate,
+ this.clinicName,
+ this.doctorImageURL,
+ this.doctorName,
+ this.projectName});
+
+ AppointmentDetails.fromJson(Map json) {
+ setupID = json['SetupID'];
+ projectID = json['ProjectID'];
+ patientID = json['PatientID'];
+ appointmentNo = json['AppointmentNo'];
+ clinicID = json['ClinicID'];
+ doctorID = json['DoctorID'];
+ startTime = json['StartTime'];
+ endTime = json['EndTime'];
+ appointmentDate = json['AppointmentDate'];
+ clinicName = json['ClinicName'];
+ doctorImageURL = json['DoctorImageURL'];
+ doctorName = json['DoctorName'];
+ projectName = json['ProjectName'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['SetupID'] = this.setupID;
+ data['ProjectID'] = this.projectID;
+ data['PatientID'] = this.patientID;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['ClinicID'] = this.clinicID;
+ data['DoctorID'] = this.doctorID;
+ data['StartTime'] = this.startTime;
+ data['EndTime'] = this.endTime;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['ClinicName'] = this.clinicName;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ data['DoctorName'] = this.doctorName;
+ data['ProjectName'] = this.projectName;
+ return data;
+ }
+}
diff --git a/lib/core/model/rate/appointment_rate.dart b/lib/core/model/rate/appointment_rate.dart
new file mode 100644
index 00000000..abb249f4
--- /dev/null
+++ b/lib/core/model/rate/appointment_rate.dart
@@ -0,0 +1,100 @@
+class AppointmentRate {
+ int rate;
+ int appointmentNo;
+ int projectID;
+ int doctorID;
+ int clinicID;
+ String note;
+ String mobileNumber;
+ int createdBy;
+ int editedBy;
+ double versionID;
+ int channel;
+ int languageID;
+ String iPAdress;
+ String generalid;
+ int patientOutSA;
+ String sessionID;
+ bool isDentalAllowedBackend;
+ int deviceTypeID;
+ int patientID;
+ String tokenID;
+ int patientTypeID;
+ int patientType;
+
+ AppointmentRate(
+ {this.rate,
+ this.appointmentNo,
+ this.projectID,
+ this.doctorID,
+ this.clinicID,
+ this.note,
+ this.mobileNumber,
+ this.createdBy,
+ this.editedBy,
+ this.versionID,
+ this.channel,
+ this.languageID,
+ this.iPAdress,
+ this.generalid,
+ this.patientOutSA,
+ this.sessionID,
+ this.isDentalAllowedBackend,
+ this.deviceTypeID,
+ this.patientID,
+ this.tokenID,
+ this.patientTypeID,
+ this.patientType});
+
+ AppointmentRate.fromJson(Map json) {
+ rate = json['Rate'];
+ appointmentNo = json['AppointmentNo'];
+ projectID = json['ProjectID'];
+ doctorID = json['DoctorID'];
+ clinicID = json['ClinicID'];
+ note = json['Note'];
+ mobileNumber = json['MobileNumber'];
+ createdBy = json['CreatedBy'];
+ editedBy = json['EditedBy'];
+ versionID = json['VersionID'];
+ channel = json['Channel'];
+ languageID = json['LanguageID'];
+ iPAdress = json['IPAdress'];
+ generalid = json['generalid'];
+ patientOutSA = json['PatientOutSA'];
+ sessionID = json['SessionID'];
+ isDentalAllowedBackend = json['isDentalAllowedBackend'];
+ deviceTypeID = json['DeviceTypeID'];
+ patientID = json['PatientID'];
+ tokenID = json['TokenID'];
+ patientTypeID = json['PatientTypeID'];
+ patientType = json['PatientType'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['Rate'] = this.rate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['ProjectID'] = this.projectID;
+ data['DoctorID'] = this.doctorID;
+ data['ClinicID'] = this.clinicID;
+ data['Note'] = this.note;
+ data['MobileNumber'] = this.mobileNumber;
+ data['CreatedBy'] = this.createdBy;
+ data['EditedBy'] = this.editedBy;
+ data['VersionID'] = this.versionID;
+ data['Channel'] = this.channel;
+ data['LanguageID'] = this.languageID;
+ data['IPAdress'] = this.iPAdress;
+ data['generalid'] = this.generalid;
+ data['PatientOutSA'] = this.patientOutSA;
+ data['SessionID'] = this.sessionID;
+ data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
+ data['DeviceTypeID'] = this.deviceTypeID;
+ data['PatientID'] = this.patientID;
+ data['TokenID'] = this.tokenID;
+ data['PatientTypeID'] = this.patientTypeID;
+ data['PatientType'] = this.patientType;
+ return data;
+ }
+}
diff --git a/lib/core/model/rate/appoitment_rated.dart b/lib/core/model/rate/appoitment_rated.dart
new file mode 100644
index 00000000..95358b23
--- /dev/null
+++ b/lib/core/model/rate/appoitment_rated.dart
@@ -0,0 +1,160 @@
+class AppoitmentRated {
+ String setupID;
+ int projectID;
+ int appointmentNo;
+ String appointmentDate;
+ String appointmentDateN;
+ int appointmentType;
+ String bookDate;
+ int patientType;
+ int patientID;
+ int clinicID;
+ int doctorID;
+ String endDate;
+ String startTime;
+ String endTime;
+ int status;
+ int visitType;
+ int visitFor;
+ int patientStatusType;
+ int companyID;
+ int bookedBy;
+ String bookedOn;
+ int confirmedBy;
+ String confirmedOn;
+ int arrivalChangedBy;
+ String arrivedOn;
+ int editedBy;
+ String editedOn;
+ Null doctorName;
+ String doctorNameN;
+ String statusDesc;
+ String statusDescN;
+ bool vitalStatus;
+ Null vitalSignAppointmentNo;
+ int episodeID;
+ String doctorTitle;
+ bool isAppoitmentLiveCare;
+
+ AppoitmentRated(
+ {this.setupID,
+ this.projectID,
+ this.appointmentNo,
+ this.appointmentDate,
+ this.appointmentDateN,
+ this.appointmentType,
+ this.bookDate,
+ this.patientType,
+ this.patientID,
+ this.clinicID,
+ this.doctorID,
+ this.endDate,
+ this.startTime,
+ this.endTime,
+ this.status,
+ this.visitType,
+ this.visitFor,
+ this.patientStatusType,
+ this.companyID,
+ this.bookedBy,
+ this.bookedOn,
+ this.confirmedBy,
+ this.confirmedOn,
+ this.arrivalChangedBy,
+ this.arrivedOn,
+ this.editedBy,
+ this.editedOn,
+ this.doctorName,
+ this.doctorNameN,
+ this.statusDesc,
+ this.statusDescN,
+ this.vitalStatus,
+ this.vitalSignAppointmentNo,
+ this.episodeID,
+ this.doctorTitle,
+ this.isAppoitmentLiveCare});
+
+ AppoitmentRated.fromJson(Map json) {
+ try {
+ setupID = json['SetupID'];
+ projectID = json['ProjectID'];
+ appointmentNo = json['AppointmentNo'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentDateN = json['AppointmentDateN'];
+ appointmentType = json['AppointmentType'];
+ bookDate = json['BookDate'];
+ patientType = json['PatientType'];
+ patientID = json['PatientID'];
+ clinicID = json['ClinicID'];
+ doctorID = json['DoctorID'];
+ endDate = json['EndDate'];
+ startTime = json['StartTime'];
+ endTime = json['EndTime'];
+ status = json['Status'];
+ visitType = json['VisitType'];
+ visitFor = json['VisitFor'];
+ patientStatusType = json['PatientStatusType'];
+ companyID = json['CompanyID'];
+ bookedBy = json['BookedBy'];
+ bookedOn = json['BookedOn'];
+ confirmedBy = json['ConfirmedBy'];
+ confirmedOn = json['ConfirmedOn'];
+ arrivalChangedBy = json['ArrivalChangedBy'];
+ arrivedOn = json['ArrivedOn'];
+ editedBy = json['EditedBy'];
+ editedOn = json['EditedOn'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ statusDesc = json['StatusDesc'];
+ statusDescN = json['StatusDescN'];
+ vitalStatus = json['VitalStatus'];
+ vitalSignAppointmentNo = json['VitalSignAppointmentNo'];
+ episodeID = json['EpisodeID'];
+ doctorTitle = json['DoctorTitle'];
+ isAppoitmentLiveCare = json['IsAppoitmentLiveCare'];
+ } catch (e) {
+ print(e);
+ }
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['SetupID'] = this.setupID;
+ data['ProjectID'] = this.projectID;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentDateN'] = this.appointmentDateN;
+ data['AppointmentType'] = this.appointmentType;
+ data['BookDate'] = this.bookDate;
+ data['PatientType'] = this.patientType;
+ data['PatientID'] = this.patientID;
+ data['ClinicID'] = this.clinicID;
+ data['DoctorID'] = this.doctorID;
+ data['EndDate'] = this.endDate;
+ data['StartTime'] = this.startTime;
+ data['EndTime'] = this.endTime;
+ data['Status'] = this.status;
+ data['VisitType'] = this.visitType;
+ data['VisitFor'] = this.visitFor;
+ data['PatientStatusType'] = this.patientStatusType;
+ data['CompanyID'] = this.companyID;
+ data['BookedBy'] = this.bookedBy;
+ data['BookedOn'] = this.bookedOn;
+ data['ConfirmedBy'] = this.confirmedBy;
+ data['ConfirmedOn'] = this.confirmedOn;
+ data['ArrivalChangedBy'] = this.arrivalChangedBy;
+ data['ArrivedOn'] = this.arrivedOn;
+ data['EditedBy'] = this.editedBy;
+ data['EditedOn'] = this.editedOn;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['StatusDesc'] = this.statusDesc;
+ data['StatusDescN'] = this.statusDescN;
+ data['VitalStatus'] = this.vitalStatus;
+ data['VitalSignAppointmentNo'] = this.vitalSignAppointmentNo;
+ data['EpisodeID'] = this.episodeID;
+ data['DoctorTitle'] = this.doctorTitle;
+ data['IsAppoitmentLiveCare'] = this.isAppoitmentLiveCare;
+ return data;
+ }
+}
diff --git a/lib/core/service/appointment_rate_service.dart b/lib/core/service/appointment_rate_service.dart
new file mode 100644
index 00000000..a0c2b208
--- /dev/null
+++ b/lib/core/service/appointment_rate_service.dart
@@ -0,0 +1,71 @@
+import 'package:diplomaticquarterapp/config/config.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appointment_rate.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appoitment_rated.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appointment_details.dart';
+import 'package:diplomaticquarterapp/core/service/base_service.dart';
+
+class AppointmentRateService extends BaseService {
+ List appointmentRatedList = List();
+ AppointmentDetails appointmentDetails;
+
+ Future getIsLastAppointmentRatedList() async {
+ hasError = false;
+ await baseAppClient.post(IS_LAST_APPOITMENT_RATED,
+ onSuccess: (dynamic response, int statusCode) {
+ appointmentRatedList.clear();
+ response['IsLastAppoitmentRatedList'].forEach((appoint) {
+ appointmentRatedList.add(AppoitmentRated.fromJson(appoint));
+ });
+ }, onFailure: (String error, int statusCode) {
+ hasError = true;
+ super.error = error;
+ }, body: Map());
+ }
+
+ Future getAppointmentDetails() async {
+ hasError = false;
+ Map bodyData = Map();
+ bodyData['AppointmentNumber'] = appointmentRatedList[0].appointmentNo;
+ bodyData['ProjectID'] = appointmentRatedList[0].projectID;
+ await baseAppClient.post(GET_APPOINTMENT_DETAILS_BY_NO,
+ onSuccess: (dynamic response, int statusCode) {
+ if (response['AppointmentDetails'] != null)
+ appointmentDetails =
+ AppointmentDetails.fromJson(response['AppointmentDetails']);
+ }, onFailure: (String error, int statusCode) {
+ hasError = true;
+ super.error = error;
+ }, body: bodyData);
+ }
+
+ Future sendAppointmentRate(int rate, int appointmentNo, int projectID,
+ int doctorID, int clinicID, String note) async {
+ hasError = false;
+ AppointmentRate appointmentRate = AppointmentRate();
+ appointmentRate.rate = rate;
+ appointmentRate.appointmentNo = appointmentNo;
+ appointmentRate.projectID = projectID;
+ appointmentRate.doctorID = doctorID;
+ appointmentRate.clinicID = clinicID;
+ appointmentRate.note = note;
+
+ await baseAppClient.post(GET_APPOINTMENT_DETAILS_BY_NO,
+ onSuccess: (dynamic response, int statusCode) {},
+ onFailure: (String error, int statusCode) {
+ hasError = true;
+ super.error = error;
+ }, body: appointmentRate.toJson());
+ }
+
+ AppoitmentRated get lastAppointmentRated {
+ if (appointmentRatedList.length > 0)
+ return appointmentRatedList[appointmentRatedList.length - 1];
+ return null;
+ }
+
+ deleteAppointmentRated(AppoitmentRated appointmentRated) {
+ appointmentRatedList.remove(appointmentRated);
+ }
+
+ deleteAllAppAppointmentRate() => appointmentRatedList.clear();
+}
diff --git a/lib/core/service/client/base_app_client.dart b/lib/core/service/client/base_app_client.dart
index 414cc700..f45fe22b 100644
--- a/lib/core/service/client/base_app_client.dart
+++ b/lib/core/service/client/base_app_client.dart
@@ -2,10 +2,16 @@ import 'dart:convert';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
+import 'package:diplomaticquarterapp/pages/login/login-type.dart';
import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart';
import 'package:diplomaticquarterapp/uitl/utils.dart';
+import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart';
+import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
+import '../../../locator.dart';
+import '../../../routes.dart';
+
AppSharedPreferences sharedPref = new AppSharedPreferences();
///Example
@@ -117,6 +123,7 @@ class BaseAppClient {
logout() async {
await sharedPref.remove(LOGIN_TOKEN_ID);
+ Navigator.of(AppGlobal.context).pushReplacementNamed(LOGIN_TYPE);
}
String getSessionId(String id) {
diff --git a/lib/core/service/dashboard_service.dart b/lib/core/service/dashboard_service.dart
new file mode 100644
index 00000000..a15a4b39
--- /dev/null
+++ b/lib/core/service/dashboard_service.dart
@@ -0,0 +1,7 @@
+import 'package:diplomaticquarterapp/config/config.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appoitment_rated.dart';
+import 'package:diplomaticquarterapp/core/service/base_service.dart';
+
+class DashboardService extends BaseService {
+
+}
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/appointment_rate_view_model.dart b/lib/core/viewModels/appointment_rate_view_model.dart
new file mode 100644
index 00000000..23d00366
--- /dev/null
+++ b/lib/core/viewModels/appointment_rate_view_model.dart
@@ -0,0 +1,68 @@
+import 'package:diplomaticquarterapp/core/enum/viewstate.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appointment_details.dart';
+import 'package:diplomaticquarterapp/core/model/rate/appoitment_rated.dart';
+import 'package:diplomaticquarterapp/core/service/appointment_rate_service.dart';
+import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart';
+import '../../locator.dart';
+
+class AppointmentRateViewModel extends BaseViewModel {
+ AppointmentRateService _appointmentRateService =
+ locator();
+ bool isHaveAppointmentNotRate = false;
+
+ AppointmentDetails get appointmentDetails => _appointmentRateService.appointmentDetails;
+
+
+ Future getIsLastAppointmentRatedList() async {
+ isHaveAppointmentNotRate = false;
+ setState(ViewState.Busy);
+ await _appointmentRateService.getIsLastAppointmentRatedList();
+ if (_appointmentRateService.hasError) {
+ error = _appointmentRateService.error;
+ setState(ViewState.Error);
+ } else {
+ if(_appointmentRateService.appointmentRatedList.length>0)
+ await getAppointmentDetails();
+ }
+ }
+
+ getAppointmentDetails() async {
+ await _appointmentRateService.getAppointmentDetails();
+ if (_appointmentRateService.hasError) {
+ error = _appointmentRateService.error;
+ setState(ViewState.Error);
+ } else {
+ if (_appointmentRateService.appointmentDetails != null)
+ isHaveAppointmentNotRate = true;
+ setState(ViewState.Idle);
+ }
+ }
+
+ Future sendAppointmentRate(int rate, int appointmentNo, int projectID,
+ int doctorID, int clinicID, String note) async {
+ setState(ViewState.BusyLocal);
+ await _appointmentRateService.sendAppointmentRate(
+ rate, appointmentNo, projectID, doctorID, clinicID, note);
+ if (_appointmentRateService.hasError) {
+ error = _appointmentRateService.error;
+ setState(ViewState.ErrorLocal);
+ } else {
+ setState(ViewState.Idle);
+ _appointmentRateService.deleteAllAppAppointmentRate();
+ }
+ }
+
+ AppoitmentRated get lastAppointmentRated =>
+ _appointmentRateService.lastAppointmentRated;
+
+ deleteAppointmentRated(AppoitmentRated appointmentRated) =>
+ _appointmentRateService.deleteAppointmentRated(appointmentRated);
+
+ deleteAppAppointmentRate() =>
+ _appointmentRateService.deleteAllAppAppointmentRate();
+
+ setIsRated(bool isRated) {
+ this.isHaveAppointmentNotRate = isRated;
+ notifyListeners();
+ }
+}
diff --git a/lib/core/viewModels/dashboard_view_model.dart b/lib/core/viewModels/dashboard_view_model.dart
new file mode 100644
index 00000000..647d92b7
--- /dev/null
+++ b/lib/core/viewModels/dashboard_view_model.dart
@@ -0,0 +1,8 @@
+import 'package:diplomaticquarterapp/core/enum/viewstate.dart';
+import 'package:diplomaticquarterapp/core/service/dashboard_service.dart';
+import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart';
+import '../../locator.dart';
+
+class DashboardViewModel extends BaseViewModel {
+
+}
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/core/viewModels/project_view_model.dart b/lib/core/viewModels/project_view_model.dart
index e9d5ed0a..07c1d3f8 100644
--- a/lib/core/viewModels/project_view_model.dart
+++ b/lib/core/viewModels/project_view_model.dart
@@ -44,10 +44,10 @@ class ProjectViewModel with ChangeNotifier {
void loadSharedPrefLanguage() async {
currentLanguage = await sharedPref.getString(APP_LANGUAGE);
- _appLocale = Locale(currentLanguage ?? 'en');
+ _appLocale = Locale(currentLanguage ?? 'ar');
_isArabic = currentLanguage != null
? currentLanguage == 'ar' ? true : false
- : false;
+ : true;
notifyListeners();
}
diff --git a/lib/locator.dart b/lib/locator.dart
index 2695379f..66face42 100644
--- a/lib/locator.dart
+++ b/lib/locator.dart
@@ -1,7 +1,9 @@
-import 'package:diplomaticquarterapp/routes.dart';
-
+import 'package:diplomaticquarterapp/core/viewModels/dashboard_view_model.dart';
+import 'package:diplomaticquarterapp/uitl/navigation_service.dart';
import 'package:get_it/get_it.dart';
+import 'core/service/appointment_rate_service.dart';
+import 'core/service/dashboard_service.dart';
import 'core/service/feedback/feedback_service.dart';
import 'core/service/hospital_service.dart';
import 'core/service/medical/labs_service.dart';
@@ -10,6 +12,7 @@ import 'core/service/medical/my_doctor_service.dart';
import 'core/service/medical/prescriptions_service.dart';
import 'core/service/medical/radiology_service.dart';
import 'core/service/medical/vital_sign_service.dart';
+import 'core/viewModels/appointment_rate_view_model.dart';
import 'core/viewModels/feedback/feedback_view_model.dart';
import 'core/service/medical/reports_service.dart';
import 'core/viewModels/hospital_view_model.dart';
@@ -40,7 +43,10 @@ void setupLocator() {
locator.registerLazySingleton(() => InsuranceCardService());
locator.registerLazySingleton(() => VitalSignService());
locator.registerLazySingleton(() => MedicalService());
+ locator.registerLazySingleton(() => NavigationService());
locator.registerLazySingleton(() => ReportsService());
+ locator.registerLazySingleton(() => DashboardService());
+ locator.registerLazySingleton(() => AppointmentRateService());
/// View Model
locator.registerFactory(() => HospitalViewModel());
@@ -54,4 +60,7 @@ void setupLocator() {
locator.registerFactory(() => VitalSignViewModel());
locator.registerFactory(() => InsuranceViewModel());
locator.registerFactory(() => MedicalViewModel());
+ locator.registerFactory(() => DashboardViewModel());
+ locator.registerFactory(() => AppointmentRateViewModel());
+
}
diff --git a/lib/main.dart b/lib/main.dart
index 99924b35..0b71ac6a 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,7 +1,11 @@
+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';
+import 'package:hexcolor/hexcolor.dart';
import 'package:provider/provider.dart';
import 'package:diplomaticquarterapp/services/robo_search/search_provider.dart';
import 'config/size_config.dart';
@@ -9,7 +13,7 @@ import 'core/viewModels/project_view_model.dart';
import 'locator.dart';
import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart';
-void main() {
+void main() async {
setupLocator();
runApp(MyApp());
}
@@ -65,7 +69,7 @@ class MyApp extends StatelessWidget {
hintColor: Colors.grey[400],
disabledColor: Colors.grey[300],
errorColor: Color.fromRGBO(235, 80, 60, 1.0),
- scaffoldBackgroundColor: Colors.grey[100],
+ scaffoldBackgroundColor:Hexcolor('#E0E0E0'),// Colors.grey[100],
textSelectionColor: Color.fromRGBO(80, 100, 253, 0.5),
textSelectionHandleColor: Color.fromRGBO(80, 100, 253, 1.0),
canvasColor: Colors.white,
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 860ebcd6..3a6a5bf9 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';
@@ -19,6 +24,8 @@ class DoctorProfile extends StatefulWidget {
@required this.docProfileList,
this.isOpenAppt = false});
+ AuthenticatedUser authUser;
+
@override
_DoctorProfileState createState() => _DoctorProfileState();
}
@@ -45,6 +52,9 @@ class _DoctorProfileState extends State
});
}
});
+ _tabController = new TabController(length: 2, vsync: this);
+ widget.authUser = new AuthenticatedUser();
+ getPatientData();
super.initState();
}
@@ -192,13 +202,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 68f5c329..4ee5e658 100644
--- a/lib/pages/BookAppointment/components/SearchByClinic.dart
+++ b/lib/pages/BookAppointment/components/SearchByClinic.dart
@@ -10,6 +10,8 @@ import 'package:diplomaticquarterapp/uitl/app_toast.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import 'package:smart_progress_bar/smart_progress_bar.dart';
+
import '../SearchResults.dart';
class SearchByClinic extends StatefulWidget {
@@ -108,7 +110,8 @@ class _SearchByClinicState extends State {
} else {}
}).catchError((err) {
print(err);
- });
+ }).showProgressBar(
+ text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6));
}
getDoctorsList(BuildContext context) {
@@ -130,7 +133,8 @@ 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 02443c98..03ae9919 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';
@@ -100,7 +101,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 62153f2e..ff0e70aa 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';
@@ -131,7 +132,7 @@ class DoctorView extends StatelessWidget {
}
}).catchError((err) {
print(err);
- });
+ }).showProgressBar(text: "Loading", backgroundColor: Colors.blue.withOpacity(0.6));
}
Future navigateToDoctorProfile(context, docObject, docProfile,
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 b172cffc..ad1bb32c 100644
--- a/lib/pages/landing/home_page.dart
+++ b/lib/pages/landing/home_page.dart
@@ -1,16 +1,14 @@
-import 'package:diplomaticquarterapp/core/model/hospitals/hospitals_model.dart';
-import 'package:diplomaticquarterapp/core/viewModels/hospital_view_model.dart';
+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:diplomaticquarterapp/widgets/transitions/fade_page.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
-import 'package:diplomaticquarterapp/core/model/pharmacies/pharmacies_model.dart';
-import 'package:diplomaticquarterapp/core/viewModels/pharmacies_view_model.dart';
-import 'package:diplomaticquarterapp/core/model/pharmacies/pharmacies_list_model.dart';
-
-import '../base/base_view.dart';
-import '../feedback/feedback_home_page.dart';
+import 'package:hexcolor/hexcolor.dart';
+import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
@override
@@ -20,34 +18,436 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State {
@override
Widget build(BuildContext context) {
+ ProjectViewModel projectViewModel = Provider.of(context);
return AppScaffold(
body: Container(
width: double.infinity,
- child: FractionallySizedBox(
- widthFactor: 0.95,
+ child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- InkWell(
- onTap: ()=>Navigator.push(context, FadePage(page: FeedbackHomePage())),
- child: Container(
- margin: EdgeInsets.only(top: 12),
- width: double.infinity,
- height: 180,
- decoration: BoxDecoration(
- shape: BoxShape.rectangle,
- color: Colors.white,
- borderRadius: BorderRadius.circular(12.0),
- border: Border.all(
- color: Colors.grey[400],
- width: 1.0,
- ),
+ Stack(
+ children: [
+ Column(
+ children: [
+ Container(
+ width: double.infinity,
+ height: 210,
+ decoration: BoxDecoration(
+ image: DecorationImage(
+ image: ExactAssetImage(
+ 'assets/images/timeline_bg.png'),
+ fit: BoxFit.cover),
+ ),
+ child: Stack(
+ children: [
+ Positioned(
+ top: 30,
+ left: 15,
+ right: 15,
+ child: Container(
+ width: MediaQuery.of(context).size.width * 0.8,
+ child: Row(
+ children: [
+ Expanded(
+ child: InkWell(
+ onTap: ()=>Navigator.push(context, FadePage(page: LiveCareHome())),
+ child: Container(
+ height: 110,
+ margin: EdgeInsets.all(5),
+ decoration: BoxDecoration(
+ color: Colors.transparent
+ .withOpacity(0.3),
+ ),
+ child: Center(child: Texts('Live care ',color: Colors.white,),),
+ ),
+ ),
+ ),
+ Expanded(
+ child: Container(
+ height: 110,
+ margin: EdgeInsets.all(5),
+ decoration: BoxDecoration(
+ color: Colors.transparent
+ .withOpacity(0.3),
+ )),
+ ),
+ ],
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(width: double.infinity, height: 80)
+ ],
),
- child: Center(
- child: Texts('Feedback'),
+ Positioned(
+ top: 155,
+ left: MediaQuery.of(context).size.width *
+ (MediaQuery.of(context).orientation ==
+ Orientation.landscape
+ ? 0.02
+ : 0.03),
+ right: MediaQuery.of(context).size.width *
+ (MediaQuery.of(context).orientation ==
+ Orientation.landscape
+ ? 0.02
+ : 0.03),
+ child: Container(
+ width: double.infinity,
+ height: 125,
+ decoration: BoxDecoration(
+ color: Hexcolor('#A59E9E'),
+ shape: BoxShape.rectangle,
+ border:
+ Border.all(color: Colors.transparent, width: 0.5),
+ borderRadius: BorderRadius.all(Radius.circular(9)),
+ image: DecorationImage(
+ image: ExactAssetImage(
+ 'assets/images/bg_graphic.png'),
+ fit: BoxFit.cover)),
+ child: Container(
+ margin: EdgeInsets.all(5),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: 8,
+ ),
+ Texts(
+ TranslationBase.of(context).myMedicalFile,
+ color: Colors.black87,
+ bold: true,
+ fontSize: 23,
+ ),
+ SizedBox(
+ height: 5,
+ ),
+ Texts(
+ TranslationBase.of(context).myMedicalFileSubTitle,
+ color: Colors.black,
+ fontSize: 16,
+ ),
+ Align(
+ alignment: projectViewModel.isArabic? Alignment.bottomLeft: Alignment.bottomRight,
+ child: InkWell(
+ onTap: () {},
+ child: Container(
+ margin: EdgeInsets.all(2),
+ width: 90,
+ height: 30,
+ decoration: BoxDecoration(
+ color: Hexcolor('#D81A2E'),
+ shape: BoxShape.rectangle,
+ border: Border.all(
+ color: Colors.transparent, width: 0.5),
+ borderRadius:
+ BorderRadius.all(Radius.circular(9)),
+ ),
+ child: Center(
+ child: Texts(
+ TranslationBase.of(context).viewMore,
+ color: Colors.white,
+ fontSize: 12,
+ ),
+ ),
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
),
+ ],
+ ),
+ Container(
+ margin: EdgeInsets.only(left: 15, right: 15),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ DashboardItem(
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/Dr_Schedule_report.png',
+ width: 50,
+ height: 50,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).homeHealthCareService,
+ textAlign: TextAlign.center,
+ color: Colors.white,
+ bold: true,
+ fontSize: 14,
+ )
+ ],
+ ),
+ ),
+ ),
+ height: 150,
+ imageName: 'home_healthcare_service_bg.png',
+ opacity: 0.5,
+ ),
+ DashboardItem(
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/new-design/pharmacy_icon.png',
+ width: 40,
+ height: 40,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).onlinePharmacy,
+ textAlign: TextAlign.center,
+ color: Colors.white,
+ bold: true,
+
+ )
+ ],
+ ),
+ ),
+ ),
+ height: 150,
+ imageName: 'al-habib_onlne_pharmacy_bg.png',
+ ),
+ DashboardItem(
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/Dr_Schedule_report.png',
+ width: 50,
+ height: 50,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).emergencyService,
+ textAlign: TextAlign.center,
+ color: Colors.white,
+ bold: true,
+ fontSize: 14,
+ )
+ ],
+ ),
+ ),
+ ),
+ height: 150,
+ color: Hexcolor("#747C80"),
+ imageName: 'emergency_service_image.png',
+ ),
+ ],
+ ),
+ ),
+ SizedBox(
+ height: 8,
+ ),
+ Container(
+ margin: EdgeInsets.only(left: 15, right: 15),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Container(
+ width: MediaQuery.of(context).size.width * 0.29,
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/new-design/online_payment_icon.png',
+ width: 80,
+ height: 50,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).onlinePaymentService,
+ textAlign: TextAlign.center,
+ color: Colors.black87,
+ bold: false,
+ fontSize: SizeConfig.textMultiplier * 2,
+ )
+ ],
+ ),
+ ),
+ ),
+ height: 150,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(6.0),
+ color: Colors.white,
+ ),
+ ),
+ Container(
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/offer_icon.png',
+ width: 50,
+ height: 50,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).offersAndPackages,
+ textAlign: TextAlign.center,
+ color: Colors.black87,
+ bold: false,
+ fontSize: SizeConfig.textMultiplier * 2,
+ )
+ ],
+ ),
+ ),
+ ),
+ width: MediaQuery.of(context).size.width * 0.29,
+ height: 150,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(6.0),
+ color: Colors.white,
+ )),
+ Container(
+ width: MediaQuery.of(context).size.width * 0.29,
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ children: [
+ Image.asset(
+ 'assets/images/Dr_Schedule_report.png',
+ width: 50,
+ height: 50,
+ ),
+ SizedBox(
+ height: 15,
+ ),
+ Texts(
+ TranslationBase.of(context).comprehensiveMedicalCheckup,
+ textAlign: TextAlign.center,
+ color: Colors.black87,
+ bold: false,
+ fontSize: SizeConfig.textMultiplier * 1.60,
+ )
+ ],
+ ),
+ ),
+ ),
+ height: 150,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(6.0),
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ ),
+ SizedBox(
+ height: 8,
+ ),
+ Container(
+ margin: EdgeInsets.only(left: 15, right: 15),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ DashboardItem(
+ child: Container(
+ width: double.infinity,
+ padding: EdgeInsets.all(10),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Texts(
+ TranslationBase.of(context).hMGService,
+ color: Colors.white,
+ fontWeight: FontWeight.normal,
+ ),
+
+ Texts(
+ TranslationBase.of(context).viewAllHabibMedicalService,
+ color: Colors.white,
+ fontWeight: FontWeight.normal,
+ fontSize: 10,
+ ),
+ Expanded(
+ child: Container(),
+ ),
+ Text(
+ TranslationBase.of(context).viewMore,
+ //color: Colors.white,
+ //fontWeight: FontWeight.normal,
+ )
+ ],
+ ),
+ ),
+ height: 100,
+ imageName: 'hmg_services_bg .png',
+ opacity: 0.5,
+ color: Colors.grey[700],
+ width: MediaQuery.of(context).size.width * 0.45,
+ ),
+ DashboardItem(
+ child: Container(
+ width: double.infinity,
+ padding: EdgeInsets.all(10),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Texts(
+ TranslationBase.of(context).contactUs,
+ color: Colors.white,
+ fontWeight: FontWeight.normal,
+ ),
+
+ Texts(
+ TranslationBase.of(context).viewAllWaysReachUs,
+ color: Colors.white,
+ fontWeight: FontWeight.normal,
+ fontSize: 10,
+ ),
+ Expanded(
+ child: Container(),
+ ),
+ Texts(
+ TranslationBase.of(context).viewMore,
+ color: Colors.white,
+ fontWeight: FontWeight.normal,
+ )
+ ],
+ ),
+ ),
+ height: 100,
+ imageName: 'contact_us_bg.png',
+ opacity: 0.5,
+ color: Colors.grey[700],
+ width: MediaQuery.of(context).size.width * 0.45,
+ ),
+ ],
),
),
+ SizedBox(
+ height: 80,
+ )
],
),
),
@@ -55,3 +455,60 @@ class _HomePageState extends State {
);
}
}
+
+class DashboardItem extends StatelessWidget {
+ const DashboardItem(
+ {this.hasBorder = false,
+ this.imageName,
+ @required this.child,
+ this.onTap,
+ Key key,
+ this.width,
+ this.height,
+ this.color,
+ this.opacity = 0.4})
+ : super(key: key);
+ final bool hasBorder;
+ final String imageName;
+ final Widget child;
+ final Function onTap;
+ final double width;
+ final double height;
+ final Color color;
+ final double opacity;
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: onTap,
+ child: Container(
+ width: width != null ? width : MediaQuery.of(context).size.width * 0.29,
+ height: height != null
+ ? height
+ : MediaQuery.of(context).orientation == Orientation.portrait
+ ? MediaQuery.of(context).size.height * 0.19
+ : MediaQuery.of(context).size.height * 0.35,
+ decoration: BoxDecoration(
+ color: !hasBorder
+ ? color != null ? color : Hexcolor('#050705').withOpacity(opacity)
+ : Colors.white,
+ borderRadius: BorderRadius.circular(6.0),
+ border: hasBorder
+ ? Border.all(width: 1.0, color: const Color(0xffcccccc))
+ : Border.all(width: 0.0, color: Colors.transparent),
+ image: imageName != null
+ ? DecorationImage(
+ image: AssetImage('assets/images/$imageName'),
+ fit: BoxFit.cover,
+ colorFilter: new ColorFilter.mode(
+ Colors.black.withOpacity(0.2), BlendMode.dstIn),
+ )
+ : null,
+ ),
+ child: Center(
+ child: child,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart
index dc08bedc..5950781b 100644
--- a/lib/pages/landing/landing_page.dart
+++ b/lib/pages/landing/landing_page.dart
@@ -1,34 +1,49 @@
+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/robo_search/search_provider.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';
import 'package:diplomaticquarterapp/core/viewModels/project_view_model.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;
ProjectViewModel projectProvider;
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
final authService = new AuthProvider();
+
+ bool isPageNavigated = false;
+
_changeCurrentTab(int tab) {
setState(() {
currentTab = tab;
@@ -36,18 +51,147 @@ 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 {
@@ -107,7 +251,7 @@ class _LandingPageState extends State {
case 0:
return TranslationBase.of(context).home;
case 1:
- return 'Search Medicine';
+ return 'Medical Profile';
case 2:
return TranslationBase.of(context).mySchedule;
case 3:
@@ -133,6 +277,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: