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