diff --git a/android/CustomFlutterFirebaseMessagingService.java b/android/CustomFlutterFirebaseMessagingService.java index 0a4d83be..113f256f 100644 --- a/android/CustomFlutterFirebaseMessagingService.java +++ b/android/CustomFlutterFirebaseMessagingService.java @@ -2,14 +2,33 @@ package io.flutter.plugins.firebasemessaging; import android.content.Intent; +import java.util.concurrent.TimeUnit; + import com.google.firebase.messaging.RemoteMessage; +//public class CustomFlutterFirebaseMessagingService extends FlutterFirebaseMessagingService { +// @Override +// public void onMessageReceived(RemoteMessage remoteMessage) { +// if (remoteMessage.getData().containsKey("is_call")) { +// Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); +// startActivity(intent); +// super.onMessageReceived(remoteMessage); +// } else +// super.onMessageReceived(remoteMessage); +// } +//} + public class CustomFlutterFirebaseMessagingService extends FlutterFirebaseMessagingService { @Override public void onMessageReceived(RemoteMessage remoteMessage) { if (remoteMessage.getData().containsKey("is_call")) { Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); + try { + TimeUnit.SECONDS.sleep(5); + } catch (Exception e) { + } super.onMessageReceived(remoteMessage); } else super.onMessageReceived(remoteMessage); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index eb397698..8aa7ad73 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ + @@ -30,7 +31,7 @@ - + @@ -42,6 +43,8 @@ android:name=".Application" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" + android:showOnLockScreen="true" + android:screenOrientation="sensorPortrait" android:label="Dr. Alhabib"> if (methodCall.method == HMG_INTERNET_WIFI_CONNECT_METHOD) { - connectHMGInternetWifi(methodCall,result) - - }else if (methodCall.method == HMG_GUEST_WIFI_CONNECT_METHOD) { - connectHMGGuestWifi(methodCall,result) - - }else if (methodCall.method == ENABLE_WIFI_IF_NOT) { - enableWifiIfNot(methodCall,result) - }else if (methodCall.method == REGISTER_HMG_GEOFENCES) { - registerHmgGeofences(methodCall,result) - }else if (methodCall.method == UN_REGISTER_HMG_GEOFENCES) { - unRegisterHmgGeofences(methodCall,result) - }else{ - + connectHMGInternetWifi(methodCall, result) + + } else if (methodCall.method == HMG_GUEST_WIFI_CONNECT_METHOD) { + connectHMGGuestWifi(methodCall, result) + + } else if (methodCall.method == ENABLE_WIFI_IF_NOT) { + enableWifiIfNot(methodCall, result) + } else if (methodCall.method == REGISTER_HMG_GEOFENCES) { + registerHmgGeofences(methodCall, result) + } else if (methodCall.method == UN_REGISTER_HMG_GEOFENCES) { + unRegisterHmgGeofences(methodCall, result) + } else if (methodCall.method == IS_DRAW_OVER_APPS_PERMISSION_ALLOWED) { + isDrawOverAppsPermissionAllowed(methodCall, result) + } else if (methodCall.method == ASK_DRAW_OVER_APPS_PERMISSION) { + askDrawOverAppsPermission(methodCall, result) + } else if (methodCall.method == GET_INTENT) { + getIntentData(methodCall, result) + } else { result.notImplemented() } } - val res = channel.invokeMethod("localizedValue","errorConnectingHmgNetwork") + val res = channel.invokeMethod("localizedValue", "errorConnectingHmgNetwork") } - private fun connectHMGInternetWifi(methodCall: MethodCall, result: MethodChannel.Result){ + private fun connectHMGInternetWifi(methodCall: MethodCall, result: MethodChannel.Result) { (methodCall.arguments as ArrayList<*>).let { - require(it.size > 0 && (it[0] is String),lazyMessage = { + require(it.size > 0 && (it[0] is String), lazyMessage = { "Missing or invalid arguments (Must have one argument 'String at 0'" }) val patientId = it[0].toString() HMG_Internet(mainActivity) - .connectToHMGGuestNetwork(patientId){ status, message -> + .connectToHMGGuestNetwork(patientId) { status, message -> mainActivity.runOnUiThread { - result.success(if(status) 1 else 0) + result.success(if (status) 1 else 0) HMGUtils.popFlutterText(mainActivity, message) Log.v(this.javaClass.simpleName, "$status | $message") @@ -76,10 +92,10 @@ class PlatformBridge(private var flutterEngine: FlutterEngine, private var mainA } - private fun connectHMGGuestWifi(methodCall: MethodCall, result: MethodChannel.Result){ + private fun connectHMGGuestWifi(methodCall: MethodCall, result: MethodChannel.Result) { HMG_Guest(mainActivity).connectToHMGGuestNetwork { status, message -> mainActivity.runOnUiThread { - result.success(if(status) 1 else 0) + result.success(if (status) 1 else 0) HMGUtils.popFlutterText(mainActivity, message) Log.v(this.javaClass.simpleName, "$status | $message") @@ -89,38 +105,76 @@ class PlatformBridge(private var flutterEngine: FlutterEngine, private var mainA private fun enableWifiIfNot(methodCall: MethodCall, result: MethodChannel.Result) { val wm = mainActivity.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? - if (wm != null){ + if (wm != null) { if (!wm.isWifiEnabled) wm.isWifiEnabled = true result.success(true) - }else - result.error("101","Error while opening wifi, Please try to open wifi yourself and try again","'WifiManager' service failed"); + } else + result.error("101", "Error while opening wifi, Please try to open wifi yourself and try again", "'WifiManager' service failed"); } private fun registerHmgGeofences(methodCall: MethodCall, result: MethodChannel.Result) { - channel.invokeMethod("getGeoZones",null, object : MethodChannel.Result{ + channel.invokeMethod("getGeoZones", null, object : MethodChannel.Result { override fun success(result: Any?) { - if(result is String) { + if (result is String) { val geoZones = GeoZoneModel().listFrom(result) - HMG_Geofence.shared(mainActivity).register(){ s, e -> } + HMG_Geofence.shared(mainActivity).register() { s, e -> } } } - override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) { } - override fun notImplemented() { } + override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {} + override fun notImplemented() {} }) } - + private fun unRegisterHmgGeofences(methodCall: MethodCall, result: MethodChannel.Result) { HMG_Geofence.shared(mainActivity).unRegisterAll { status, exception -> - if(status) + if (status) result.success(true) else result.error("101", exception?.localizedMessage, exception); } } - + + private fun isDrawOverAppsPermissionAllowed(methodCall: MethodCall, result: MethodChannel.Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if ( + Settings.canDrawOverlays(mainActivity) + ) { + result.success(true) + } else { + result.success(false) + } + } else { + result.success(false) + } + } + + private fun askDrawOverAppsPermission(methodCall: MethodCall, result: MethodChannel.Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) + val uri = Uri.parse("package:" + mainActivity.getPackageName()) + intent.setData(uri) + startActivityForResult(mainActivity, intent, 102, null) + result.success(true) + } else { + result.success(false) + } + } + + private fun getIntentData(methodCall: MethodCall, result: MethodChannel.Result) { + + val bundle: Bundle? = getIntent("").extras + if (bundle != null) { + val message = bundle.getString("notification") // 1 + System.out.println("BundleExtra:" + message) + Toast.makeText(this.mainActivity, message + "", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this.mainActivity, "Bundle Null", Toast.LENGTH_SHORT).show(); + } + result.success(true); + } } diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 53427e0c..b3315e38 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -3f8c659591fcdd0e47e3895f74af395c \ No newline at end of file +8d8845c5c035b7f87f2849054cdedb69 \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6c02c983..9bf7caa5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -520,7 +520,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.hmg.smartphone; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -659,7 +659,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.hmg.smartphone; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -692,7 +692,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.hmg.smartphone; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart b/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart index 9cc3d3b9..d5022f0f 100644 --- a/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart +++ b/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart @@ -1,4 +1,5 @@ import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; +import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; import 'package:flutter/cupertino.dart'; class PackagesCustomerRequestModel { @@ -19,7 +20,9 @@ class PackagesCustomerRequestModel { this.email = user.emailAddress; this.phone = user.mobileNumber; this.national_id = user.patientIdentificationNo; - this.date_of_birth = user.dateofBirth; + + String isoDateTime = DateUtil.getISODateFormat(user.dateofBirthDataTime); + this.date_of_birth = isoDateTime; } Map json() { diff --git a/lib/core/model/packages_offers/responses/tamara_payment_option.dart b/lib/core/model/packages_offers/responses/tamara_payment_option.dart index ad79f5f3..2b694a23 100644 --- a/lib/core/model/packages_offers/responses/tamara_payment_option.dart +++ b/lib/core/model/packages_offers/responses/tamara_payment_option.dart @@ -3,6 +3,7 @@ class TamaraPaymentOption { double minLimit; double maxLimit; int id; + bool enable = true; String fullName() => '$name Months'; diff --git a/lib/core/service/packages_offers/PackagesOffersServices.dart b/lib/core/service/packages_offers/PackagesOffersServices.dart index ae5396a7..6c4e0d24 100644 --- a/lib/core/service/packages_offers/PackagesOffersServices.dart +++ b/lib/core/service/packages_offers/PackagesOffersServices.dart @@ -75,6 +75,7 @@ class OffersAndPackagesServices extends BaseService { Future> getTamaraOptions({@required BuildContext context, @required bool showLoading = true}) async { if (tamaraPaymentOptions != null && tamaraPaymentOptions.isNotEmpty) return tamaraPaymentOptions; + tamaraPaymentOptions.clear(); var url = EXA_CART_API_BASE_URL + PACKAGES_TAMARA_OPT; await baseAppClient.simpleGet(url, headers: packagesAuthHeader, onSuccess: (dynamic stringResponse, int statusCode) { if (statusCode == 200) { diff --git a/lib/core/service/pharmacy_categorise_service.dart b/lib/core/service/pharmacy_categorise_service.dart index be376c91..280022e0 100644 --- a/lib/core/service/pharmacy_categorise_service.dart +++ b/lib/core/service/pharmacy_categorise_service.dart @@ -5,6 +5,7 @@ import 'package:diplomaticquarterapp/core/model/pharmacy/categorise_parent_model import 'package:diplomaticquarterapp/core/model/pharmacy/final_products_model.dart'; import 'package:diplomaticquarterapp/core/model/pharmacy/pharmacy_categorise.dart'; import 'package:diplomaticquarterapp/core/model/pharmacy/scan_qr_model.dart'; +import 'package:diplomaticquarterapp/models/LiveCare/validators.dart'; import 'base_service.dart'; @@ -303,9 +304,9 @@ class PharmacyCategoriseService extends BaseService { _parentProductsList.clear(); endPoint = FILTERED_PRODUCTS + "$categoryId" + - "&manufacturerids=$brandId" + - "&price_min=$min" + - "&price_max=$max&page=1&limit=50"; + "$brandId" + + "$min" + + "$max&page=1&limit=50"; await baseAppClient.getPharmacy( endPoint, onSuccess: (dynamic response, int statusCode) { @@ -328,9 +329,9 @@ class PharmacyCategoriseService extends BaseService { _subProductsList.clear(); endPoint = FILTERED_PRODUCTS + "$categoryId" + - "&manufacturerids=$brandId" + - "&price_min=$min" + - "&price_max=$max&page=1&limit=50"; + "$brandId" + + "$min" + + "$max&page=1&limit=50"; await baseAppClient.getPharmacy( endPoint, onSuccess: (dynamic response, int statusCode) { diff --git a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart index af750eab..b1fcc1d1 100644 --- a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart +++ b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart @@ -2,6 +2,7 @@ import 'package:diplomaticquarterapp/core/model/hospitals/hospitals_model.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/tamara_payment_option.dart'; import 'package:diplomaticquarterapp/core/service/packages_offers/PackagesOffersServices.dart'; import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart'; import 'package:diplomaticquarterapp/locator.dart'; @@ -25,6 +26,22 @@ class PackagesViewModel extends BaseViewModel { List get cartItemList => service.cartItemList; List get hospitals => service.hospitals; + List get tamara_options => service.tamaraPaymentOptions; + bool allowTamara = true; + + setTamaraIllegablity(double amount){ + bool illegible = true; + if(tamara_options == null || tamara_options.isEmpty) + illegible = false; + else{ + tamara_options.forEach((element) { + final ill = (amount >= element.minLimit && amount <= element.maxLimit); + element.enable = ill; + illegible = illegible || ill; + }); + } + allowTamara = illegible; + } String _cartItemCount = ""; diff --git a/lib/main.dart b/lib/main.dart index 0dd18a2d..93af691e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -168,7 +168,8 @@ class _MyApp extends State { // ), // ), // ), - initialRoute: SPLASH, + // initialRoute: SPLASH, + initialRoute: CALL_PAGE, // initialRoute: OPENTOK_CALL_PAGE, // initialRoute: PACKAGES_OFFERS, // initialRoute: PACKAGES_ORDER_COMPLETED, diff --git a/lib/pages/BookAppointment/BookConfirm.dart b/lib/pages/BookAppointment/BookConfirm.dart index bdf387d6..7377bcc2 100644 --- a/lib/pages/BookAppointment/BookConfirm.dart +++ b/lib/pages/BookAppointment/BookConfirm.dart @@ -9,6 +9,7 @@ import 'package:diplomaticquarterapp/routes.dart'; import 'package:diplomaticquarterapp/services/appointment_services/GetDoctorsList.dart'; import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service.dart'; import 'package:diplomaticquarterapp/theme/colors.dart'; +import 'package:diplomaticquarterapp/uitl/PlatformBridge.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; @@ -21,6 +22,7 @@ import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'book_reminder_page.dart'; @@ -240,6 +242,44 @@ class _BookConfirmState extends State { ); } + Future askVideoCallPermission() async { + if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { + return false; + } + if (!(await PlatformBridge.shared().isDrawOverAppsPermissionAllowed())) { + await drawOverAppsMessageDialog(context); + return false; + } + return true; + } + + Future drawOverAppsMessageDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text("Please select 'Diplomatic Quarter' from the list and allow draw over app permission to use live care."), + contentPadding: EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 0.0), + actions: [ + FlatButton( + child: Text("Cancel"), + onPressed: () { + Navigator.pop(context); + }, + ), + FlatButton( + child: Text("Go to Settings"), + onPressed: () async { + await PlatformBridge.shared().askDrawOverAppsPermission(); + Navigator.pop(context); + }, + ) + ], + ); + }, + ); + } + cancelAppointment(DoctorList docObject, AppoitmentAllHistoryResultList appo, BuildContext context) { ConfirmDialog.closeAlertDialog(context); GifLoaderDialogUtils.showMyDialog(context); diff --git a/lib/pages/conference/conference_page.dart b/lib/pages/conference/conference_page.dart index dc00bf6e..c30e7cdf 100644 --- a/lib/pages/conference/conference_page.dart +++ b/lib/pages/conference/conference_page.dart @@ -3,6 +3,7 @@ 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/landing/landing_page.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'; @@ -152,6 +153,7 @@ class _ConferencePageState extends State { Future _onHangup() async { print('onHangup'); await _conferenceRoom.disconnect(); + LandingPage.isOpenCallPage = false; Navigator.of(context).pop(); } diff --git a/lib/pages/conference/web_rtc/call_home_page.dart b/lib/pages/conference/web_rtc/call_home_page.dart index 7153bf92..892871cc 100644 --- a/lib/pages/conference/web_rtc/call_home_page.dart +++ b/lib/pages/conference/web_rtc/call_home_page.dart @@ -2,6 +2,9 @@ import 'dart:async'; import 'package:diplomaticquarterapp/pages/conference/web_rtc/widgets/cam_view_widget.dart'; import 'package:diplomaticquarterapp/pages/conference/widgets/noise_box.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/call_page.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/signaling.dart'; +import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; @@ -9,6 +12,11 @@ import 'package:flutter_webrtc/flutter_webrtc.dart'; import '../conference_button_bar.dart'; class CallHomePage extends StatefulWidget { + final String receiverId; + final String callerId; + + const CallHomePage({Key key, this.receiverId, this.callerId}) : super(key: key); + @override _CallHomePageState createState() => _CallHomePageState(); } @@ -24,33 +32,68 @@ class _CallHomePageState extends State { final StreamController _onButtonBarHeightStreamController = StreamController.broadcast(); //Stream to enable video - MediaStream stream; + MediaStream localMediaStream; + MediaStream remoteMediaStream; + Signaling signaling = Signaling()..init(); @override void initState() { // TODO: implement initState super.initState(); - _localRenderer.initialize(); - _remoteRenderer.initialize(); + startCall(); + } - enableVideo(); + startCall() async{ + await _localRenderer.initialize(); + await _remoteRenderer.initialize(); + final connected = await receivedCall(); } - enableVideo() async { - //Stream to enable video - stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true}); - // _audioButton.add(false); + + Future receivedCall() async { + //Stream local media + localMediaStream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true}); + _localRenderer.srcObject = localMediaStream; + + final connected = await signaling.acceptCall(widget.callerId, widget.receiverId, localMediaStream: localMediaStream, onRemoteMediaStream: (remoteMediaStream){ + this.remoteMediaStream = remoteMediaStream; + _remoteRenderer.srcObject = remoteMediaStream; + }); + + if(connected){ + signaling.signalR.listen( + onAcceptCall: (arg0){ + print(arg0.toString()); + }, + onCandidate: (candidateJson){ + signaling.addCandidate(candidateJson); + }, + onDeclineCall: (arg0,arg1){ + _onHangup(); + }, + onHangupCall: (arg0){ + _onHangup(); + }, + + onOffer: (offerSdp, callerUser) async{ + print('${offerSdp.toString()} | ${callerUser.toString()}'); + await signaling.answerOffer(offerSdp); + } + ); + } + return connected; } @override void dispose() { // TODO: implement dispose super.dispose(); - _localRenderer.dispose(); - _remoteRenderer.dispose(); - _audioButton.close(); - _videoButton.close(); - stream.dispose(); + _localRenderer?.dispose(); + _remoteRenderer?.dispose(); + _audioButton?.close(); + _videoButton?.close(); + localMediaStream?.dispose(); + remoteMediaStream?.dispose(); _disposeStreamsAndSubscriptions(); } @@ -83,7 +126,6 @@ class _CallHomePageState extends State { CamViewWidget( localRenderer: _localRenderer, remoteRenderer: _remoteRenderer, - stream: stream, constraints: constraints, onButtonBarVisibleStreamController: _onButtonBarVisibleStreamController, onButtonBarHeightStreamController: _onButtonBarHeightStreamController, @@ -130,26 +172,25 @@ class _CallHomePageState extends State { } Function _onAudioEnable() { - bool enabled = stream.getAudioTracks()[0].enabled; - stream.getAudioTracks()[0].enabled = !enabled; - _audioButton.add(!enabled); + final audioTrack = localMediaStream.getAudioTracks()[0]; + final mute = audioTrack.muted; + Helper.setMicrophoneMute(!mute, audioTrack); + _audioButton.add(mute); } Function _onVideoEnabled() { - bool enabled = stream.getVideoTracks()[0].enabled; - stream.getVideoTracks()[0].enabled = !enabled; - _videoButton.add(!enabled); + final videoTrack = localMediaStream.getVideoTracks()[0]; + bool videoEnabled = videoTrack.enabled; + localMediaStream.getVideoTracks()[0].enabled = !videoEnabled; + _videoButton.add(!videoEnabled); } Function _onSwitchCamera() { - // stream.getAudioTracks()[0].enabled = false; - // stream.getVideoTracks()[0].enabled = false; - Helper.switchCamera(stream.getVideoTracks()[0]); + Helper.switchCamera(localMediaStream.getVideoTracks()[0]); } void _onShowBar() { setState(() { - SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]); }); _onButtonBarVisibleStreamController.add(true); } @@ -166,7 +207,10 @@ class _CallHomePageState extends State { } Future _onHangup() async { + signaling.hangupCall(widget.callerId, widget.receiverId); print('onHangup'); Navigator.of(context).pop(); } + + } diff --git a/lib/pages/conference/web_rtc/widgets/cam_view_widget.dart b/lib/pages/conference/web_rtc/widgets/cam_view_widget.dart index 3db0c74b..d27dad55 100644 --- a/lib/pages/conference/web_rtc/widgets/cam_view_widget.dart +++ b/lib/pages/conference/web_rtc/widgets/cam_view_widget.dart @@ -9,12 +9,12 @@ import 'draggable_cam.dart'; class CamViewWidget extends StatefulWidget { RTCVideoRenderer localRenderer; RTCVideoRenderer remoteRenderer; - MediaStream stream; + MediaStream localStream; BoxConstraints constraints; StreamController onButtonBarVisibleStreamController; StreamController onButtonBarHeightStreamController; - CamViewWidget({this.localRenderer, this.remoteRenderer, this.stream, this.constraints, this.onButtonBarVisibleStreamController, this.onButtonBarHeightStreamController}); + CamViewWidget({this.localRenderer, this.remoteRenderer, this.constraints, this.onButtonBarVisibleStreamController, this.onButtonBarHeightStreamController}); @override _CamViewWidgetState createState() => _CamViewWidgetState(); @@ -24,16 +24,6 @@ class _CamViewWidgetState extends State { @override void initState() { super.initState(); - Future.delayed(const Duration(milliseconds: 300), () { - showCamera(); - }); - } - - showCamera() async { - setState(() async { - widget.localRenderer.srcObject = widget.stream; - widget.remoteRenderer.srcObject = widget.stream; - }); } @override @@ -43,15 +33,18 @@ class _CamViewWidgetState extends State { height: double.infinity, child: Stack( children: [ - Container( - child: RTCVideoView(widget.localRenderer, mirror: true), + FractionallySizedBox( + heightFactor: 1, widthFactor: 1, + child: Container( + child: RTCVideoView(widget.remoteRenderer, mirror: true), + ), ), DraggableCam( key: Key('publisher'), onButtonBarHeight: widget.onButtonBarHeightStreamController.stream, onButtonBarVisible: widget.onButtonBarVisibleStreamController.stream, availableScreenSize: widget.constraints.biggest, - child: RTCVideoView(widget.remoteRenderer), + child: RTCVideoView(widget.localRenderer), ), // Expanded(child: RTCVideoView(widget.remoteRenderer)), ], diff --git a/lib/pages/final_products_page.dart b/lib/pages/final_products_page.dart index 91e953ad..253d7c08 100644 --- a/lib/pages/final_products_page.dart +++ b/lib/pages/final_products_page.dart @@ -93,6 +93,8 @@ class _FinalProductsPageState extends State { isShowAppBar: true, backgroundColor: Colors.white, isShowDecPage: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, baseViewModel: model, body: Container( height: MediaQuery.of(context).size.height * 5.87, @@ -485,7 +487,7 @@ class _FinalProductsPageState extends State { color: CustomColors.green, ), onPressed: () async { - if (model.finalProducts[index].rxMessage == null) { + if (model.finalProducts[index].isRx == false) { GifLoaderDialogUtils.showMyDialog(context); await addToCartFunction(1, model.finalProducts[index].id); GifLoaderDialogUtils.hideDialog(context); diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index 2bca7a9e..db41840d 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -204,10 +204,15 @@ class _LandingPageState extends State with WidgetsBindingObserver { AppGlobal.context = context; if (state == AppLifecycleState.resumed) { if (LandingPage.isOpenCallPage) { + if (Platform.isAndroid) { + return; + } if (!isPageNavigated) { isPageNavigated = true; Navigator.push(context, MaterialPageRoute(builder: (context) => IncomingCall(incomingCallData: LandingPage.incomingCallData))).then((value) { - isPageNavigated = false; + Future.delayed(Duration(seconds: 5), () { + isPageNavigated = false; + }); }); } } @@ -329,12 +334,17 @@ class _LandingPageState extends State with WidgetsBindingObserver { Map myMap = new Map.from(message['data']); print(myMap); + if (LandingPage.isOpenCallPage) { + return; + } 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; + Future.delayed(Duration(seconds: 5), () { + isPageNavigated = false; + }); }); } } else { diff --git a/lib/pages/livecare/incoming_call.dart b/lib/pages/livecare/incoming_call.dart index a463fc00..7e586942 100644 --- a/lib/pages/livecare/incoming_call.dart +++ b/lib/pages/livecare/incoming_call.dart @@ -3,6 +3,7 @@ import 'package:diplomaticquarterapp/models/LiveCare/room_model.dart'; import 'package:diplomaticquarterapp/pages/conference/conference_page.dart'; import 'package:diplomaticquarterapp/pages/conference/web_rtc/call_home_page.dart'; import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart'; +import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:just_audio/just_audio.dart'; @@ -135,6 +136,7 @@ class _IncomingCallState extends State with SingleTickerProviderSt Container( child: RawMaterialButton( onPressed: () { + LandingPage.isOpenCallPage = false; backToHome(); }, elevation: 2.0, diff --git a/lib/pages/packages_offers/OfferAndPackagesCartPage.dart b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart index f4743d2c..cd640f6f 100644 --- a/lib/pages/packages_offers/OfferAndPackagesCartPage.dart +++ b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart @@ -263,13 +263,18 @@ class _PackagesCartPageState extends State with AfterLayoutMix } fetchData() async { - await viewModel.service.cartItems(context: context).then((value) { - subtotal = value['subtotal'] ?? 0.0; - tax = value['tax'] ?? 0.0; - total = value['total'] ?? 0.0; - }).catchError((error) {}); - - setState(() {}); + final cartResponse = await viewModel.service.cartItems(context: context).catchError((error) {}); + if(cartResponse != null){ + subtotal = cartResponse['subtotal'] ?? 0.0; + tax = cartResponse['tax'] ?? 0.0; + total = cartResponse['total'] ?? 0.0; + viewModel.service.getTamaraOptions(context: context, showLoading: true).then((tamara_options){ + if(tamara_options != null || tamara_options.isNotEmpty) { + viewModel.setTamaraIllegablity(total); + setState(() {}); + } + }); + } } paymentClosed({@required int orderId, @required bool withStatus, dynamic data}) async { @@ -282,6 +287,7 @@ class _PackagesCartPageState extends State with AfterLayoutMix debugPrint(error); }); } + } // Widget _payNow(BuildContext context, {double subtotal, double tax, double total, @required VoidCallback onPayNowClick}) { diff --git a/lib/pages/parent_categorise_page.dart b/lib/pages/parent_categorise_page.dart index 4e58225e..393cbb27 100644 --- a/lib/pages/parent_categorise_page.dart +++ b/lib/pages/parent_categorise_page.dart @@ -84,7 +84,8 @@ class _ParentCategorisePageState extends State { isShowAppBar: true, backgroundColor: Colors.white, isShowDecPage: false, - baseViewModel: model, + showPharmacyCart: false, + showHomeAppBarIcon: false,baseViewModel: model, body: SmartRefresher( enablePullDown: false, controller: controller, @@ -469,7 +470,7 @@ class _ParentCategorisePageState extends State { GifLoaderDialogUtils.showMyDialog(context); await model.getFilteredProducts( - min: minField.text.toString(), max: maxField.text.toString(), categoryId: categoriesId, brandId: brandIds); + min: minField.text.isEmpty ? "" : "&price_min=" + minField.text.toString(), max: maxField.text.isEmpty ? "" : "&price_max=" + maxField.text.toString(), categoryId: categoriesId, brandId: brandIds.isEmpty ? "" : "&manufacturerids=" + brandIds); GifLoaderDialogUtils.hideDialog(context); Navigator.pop(context); @@ -896,7 +897,7 @@ class _ParentCategorisePageState extends State { color: CustomColors.green, ), onPressed: () async { - if (model.parentProducts[index].rxMessage == null) { + if (model.parentProducts[index].isRx == false) { GifLoaderDialogUtils.showMyDialog(context); await addToCartFunction(1, model.parentProducts[index].id); GifLoaderDialogUtils.hideDialog(context); diff --git a/lib/pages/pharmacies/ProductCheckTypeWidget.dart b/lib/pages/pharmacies/ProductCheckTypeWidget.dart index d0a112c6..8ac83fd2 100644 --- a/lib/pages/pharmacies/ProductCheckTypeWidget.dart +++ b/lib/pages/pharmacies/ProductCheckTypeWidget.dart @@ -37,6 +37,7 @@ class _ProductCheckTypeWidgetState extends State { productImage: widget.model.wishListList[index].product.images[0].src, productID: widget.model.wishListList[index].product.id, onDelete: deleteWishListItem, + isRx:widget.model.wishListList[index].product.isRx, ), ), diff --git a/lib/pages/pharmacies/compare.dart b/lib/pages/pharmacies/compare.dart index 82a11610..f458304f 100644 --- a/lib/pages/pharmacies/compare.dart +++ b/lib/pages/pharmacies/compare.dart @@ -33,6 +33,9 @@ class _ComparePageState extends State { appBarTitle: TranslationBase.of(context).compare, isShowAppBar: true, isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isBottomBar: true, body: SingleChildScrollView( child: Container( child: compareList(), diff --git a/lib/pages/pharmacies/my_reviews.dart b/lib/pages/pharmacies/my_reviews.dart index 08ba3d14..39304cac 100644 --- a/lib/pages/pharmacies/my_reviews.dart +++ b/lib/pages/pharmacies/my_reviews.dart @@ -26,6 +26,9 @@ class _MyReviewsPageState extends State { appBarTitle: TranslationBase.of(context).reviews, isShowAppBar: true, isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isBottomBar: true, baseViewModel: model, body: model.reviewListList.length == 0 ? Container( diff --git a/lib/pages/pharmacies/screens/product-details/shared/product_details_app_bar.dart b/lib/pages/pharmacies/screens/product-details/shared/product_details_app_bar.dart index 9ed3c037..18749662 100644 --- a/lib/pages/pharmacies/screens/product-details/shared/product_details_app_bar.dart +++ b/lib/pages/pharmacies/screens/product-details/shared/product_details_app_bar.dart @@ -3,6 +3,7 @@ import 'package:diplomaticquarterapp/core/service/AuthenticatedUserObject.dart'; import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/OrderPreviewViewModel.dart'; import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/product_detail_view_model.dart'; import 'package:diplomaticquarterapp/pages/landing/landing_page_pharmcy.dart'; +import 'package:diplomaticquarterapp/pages/pharmacies/screens/cart-page/cart-order-page.dart'; import 'package:diplomaticquarterapp/pages/pharmacies/screens/product-details/product-detail.dart'; import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/navigation_service.dart'; @@ -26,9 +27,19 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { final bool isInWishList; final Function addToCartFunction; - ProductAppBar({Key key, this.product, this.model, this.addToWishlistFunction, this.quantity, this.deleteFromWishlistFunction, this.isInWishList, this.addToCartFunction}) : super(key: key); + ProductAppBar( + {Key key, + this.product, + this.model, + this.addToWishlistFunction, + this.quantity, + this.deleteFromWishlistFunction, + this.isInWishList, + this.addToCartFunction}) + : super(key: key); - AuthenticatedUserObject authenticatedUserObject = locator(); + AuthenticatedUserObject authenticatedUserObject = + locator(); @override Widget build(BuildContext context) { @@ -66,13 +77,23 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { color: Colors.grey[800], onPress: () { Navigator.pushAndRemoveUntil( - locator().navigatorKey.currentContext, MaterialPageRoute(builder: (context) => LandingPagePharmacy(currentTab: 3)), (Route r) => false); + locator() + .navigatorKey + .currentContext, + MaterialPageRoute( + builder: (context) => + LandingPagePharmacy(currentTab: 3)), + (Route r) => false); // Navigator.push( // context, // MaterialPageRoute(builder: (context) => CartOrderPage()), // ); }), - if (Provider.of(context, listen: false).cartResponse.quantityCount != 0) + if (Provider.of(context, + listen: false) + .cartResponse + .quantityCount != + 0) Positioned( top: 0, right: -1.0, @@ -85,7 +106,11 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { height: 18, child: Center( child: Texts( - Provider.of(context, listen: false).cartResponse.quantityCount.toString(), + Provider.of(context, + listen: false) + .cartResponse + .quantityCount + .toString(), style: "caption", medium: true, color: Colors.white, @@ -124,7 +149,7 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { return Container( child: new Wrap( children: [ - if (product.stockAvailability != 'Out of stock' && !product.isRx) + if (product.stockAvailability != 'Out of stock' && product.isRx != true) new ListTile( leading: Icon(Icons.shopping_cart), title: Text( @@ -133,12 +158,18 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { onTap: () async { if (quantity > 0) { { - await addToCartFunction(quantity: quantity, itemID: itemID, model: model); + await addToCartFunction( + quantity: quantity, + itemID: itemID, + model: model); Navigator.of(context).pop(); } } else { - AppToast.showErrorToast(message: TranslationBase.of(context).addQuantity); + AppToast.showErrorToast( + message: TranslationBase.of(context).addQuantity + // "you should add quantity" + ); } }), ListTile( @@ -147,7 +178,9 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { color: !isInWishList ? Colors.white : Colors.red[800], ), title: Text( - isInWishList ? TranslationBase.of(context).removeFromWishlist : TranslationBase.of(context).addToWishlist, + isInWishList + ? TranslationBase.of(context).removeFromWishlist + : TranslationBase.of(context).addToWishlist, ), onTap: () async { if (isInWishList) @@ -162,7 +195,8 @@ class ProductAppBar extends StatelessWidget with PreferredSizeWidget { TranslationBase.of(context).compare, ), onTap: () { - Provider.of(context, listen: false).addItem(specificationData, context); + Provider.of(context, listen: false) + .addItem(specificationData, context); Navigator.of(context).pop(); }, ), diff --git a/lib/pages/pharmacies/widgets/ProductOrderItem.dart b/lib/pages/pharmacies/widgets/ProductOrderItem.dart index afe74371..8d74cb48 100644 --- a/lib/pages/pharmacies/widgets/ProductOrderItem.dart +++ b/lib/pages/pharmacies/widgets/ProductOrderItem.dart @@ -252,7 +252,7 @@ class _ProductOrderItemState extends State { } _quantityController.text = "${widget.item.quantity}"; _totalPrice = - "${(widget.item.product.price * widget.item.quantity).toStringAsFixed(2)}"; + "${(widget.item.product.price * widget.item.quantity).toStringAsFixed(2)}"; } }); } diff --git a/lib/pages/pharmacies/wishlist.dart b/lib/pages/pharmacies/wishlist.dart index 3d1c8114..00088b59 100644 --- a/lib/pages/pharmacies/wishlist.dart +++ b/lib/pages/pharmacies/wishlist.dart @@ -17,6 +17,9 @@ class WishlistPage extends StatelessWidget { isShowAppBar: true, isShowDecPage: false, isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isBottomBar: true, baseViewModel: model, body: model.wishListList.length == 0 ? Container( @@ -35,8 +38,8 @@ class WishlistPage extends StatelessWidget { ), Padding( padding: const EdgeInsets.all(8.0), - child: Text( - 'There is no data', + child: Text(TranslationBase.of(context).noData, + // 'There is no data', style: TextStyle(fontSize: 30), ), ) diff --git a/lib/pages/pharmacy/order/Order.dart b/lib/pages/pharmacy/order/Order.dart index 9eda1004..ef5c9346 100644 --- a/lib/pages/pharmacy/order/Order.dart +++ b/lib/pages/pharmacy/order/Order.dart @@ -66,6 +66,9 @@ class _OrderPageState extends State with SingleTickerProviderStateMix baseViewModel: model, isShowAppBar: true, isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isBottomBar: true, body: Container( child: Column( children: [ diff --git a/lib/pages/pharmacy/order/OrderDetails.dart b/lib/pages/pharmacy/order/OrderDetails.dart index fc4ae184..6b7c957e 100644 --- a/lib/pages/pharmacy/order/OrderDetails.dart +++ b/lib/pages/pharmacy/order/OrderDetails.dart @@ -84,6 +84,9 @@ class _OrderDetailsPageState extends State { appBarTitle: TranslationBase.of(context).orderDetail, isShowAppBar: true, isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isBottomBar: true, baseViewModel: model, body: model.orderListModel.length > 0 ? Container( diff --git a/lib/pages/pharmacy/profile/profile.dart b/lib/pages/pharmacy/profile/profile.dart index 1f2520b6..72fa43dc 100644 --- a/lib/pages/pharmacy/profile/profile.dart +++ b/lib/pages/pharmacy/profile/profile.dart @@ -108,10 +108,10 @@ class _ProfilePageState extends State { builder: (_, model, wi) => AppScaffold( appBarTitle: TranslationBase.of(context).myAccount, isShowAppBar: true, - isShowDecPage: true, + isShowDecPage: false, isPharmacy: true, - - //isBottomBar: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, isMainPharmacyPages: true, body: user != null ? Container( diff --git a/lib/pages/sub_categories_modalsheet.dart b/lib/pages/sub_categories_modalsheet.dart index d6499c8e..ed2403db 100644 --- a/lib/pages/sub_categories_modalsheet.dart +++ b/lib/pages/sub_categories_modalsheet.dart @@ -40,11 +40,13 @@ class _SubCategoriseModalsheetState extends State { builder: (_, model, wi) => AppScaffold( // appBarTitle: titleName, appBarTitle: TranslationBase.of(context).categorise, - isBottomBar: false, + isBottomBar: true, isShowAppBar: true, isPharmacy: true, backgroundColor: Colors.white, isShowDecPage: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, baseViewModel: model, body: Container( color: Colors.white, @@ -77,7 +79,11 @@ class _SubCategoriseModalsheetState extends State { context, FadePage( page: SubCategorisePage( - title: model.categoriseParent[index].name, + title: projectViewModel + .isArabic + ? model.categoriseParent[index].namen + : model.categoriseParent[index].name, + // title: model.categoriseParent[index].name, id: model.categoriseParent[index].id, parentId: id, )), diff --git a/lib/pages/sub_categorise_page.dart b/lib/pages/sub_categorise_page.dart index 4bc069dc..1856aedb 100644 --- a/lib/pages/sub_categorise_page.dart +++ b/lib/pages/sub_categorise_page.dart @@ -14,6 +14,7 @@ import 'package:diplomaticquarterapp/uitl/utils.dart'; import 'package:diplomaticquarterapp/widgets/buttons/button.dart'; import 'package:diplomaticquarterapp/widgets/data_display/text.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_pharmacy_widget.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:diplomaticquarterapp/widgets/others/entity_checkbox_list.dart'; import 'package:diplomaticquarterapp/widgets/others/network_base_view.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; @@ -76,12 +77,15 @@ class _SubCategorisePageState extends State { allowAny: true, builder: (BuildContext context, PharmacyCategoriseViewModel model, Widget child) => - PharmacyAppScaffold( + AppScaffold( + isPharmacy: true, appBarTitle: title, - isBottomBar: false, + isBottomBar: true, isShowAppBar: true, backgroundColor: Colors.white, isShowDecPage: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, baseViewModel: model, body: SmartRefresher( controller: controller, @@ -643,16 +647,20 @@ class _SubCategorisePageState extends State { context); await model.getFilteredSubProducts( - min: minField - .text - .toString(), - max: maxField - .text - .toString(), + min: minField.text.isEmpty + ? "" + : minField.text + .toString(), + max: maxField.text.isEmpty + ? "" + : maxField.text + .toString(), categoryId: categoriesId, - brandId: - brandIds); + brandId: brandIds.isEmpty + ? "" + : "&manufacturerids=" + + brandIds); GifLoaderDialogUtils .hideDialog( context); @@ -1215,8 +1223,7 @@ class _SubCategorisePageState extends State { if (model .subProducts[ index] - .rxMessage == - null) { + .isRx == false) { GifLoaderDialogUtils .showMyDialog( context); diff --git a/lib/pages/webRTC/call_page.dart b/lib/pages/webRTC/call_page.dart index c5b41aa3..0c6caf54 100644 --- a/lib/pages/webRTC/call_page.dart +++ b/lib/pages/webRTC/call_page.dart @@ -1,8 +1,20 @@ +import 'dart:io'; + +import 'package:diplomaticquarterapp/models/LiveCare/IncomingCallData.dart'; +import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; +import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart'; import 'package:diplomaticquarterapp/pages/webRTC/signaling.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; +final DOCTOR_TOKEN = 'cj2TFgotT6q-F0SCGVFaKR:APA91bFBY0NCY5tlCooYMKS9JWI8TMJQ6YcPLT0T5gNyn-qjfQgFVQWh60QeXDz_aNqL2U3B9qOjpDbaA32e4oBlK1klR-7uUYL0DwWiHKnU1hV_wlW9-Tib16CBVkN3ods7OMlKbPv9'; +String My_ID = '54321'; +String My_Mobile = '0500409598'; +String doctor_ID = '12345'; +String doctor_Mobile = '966500409598'; + class CallPage extends StatefulWidget { @override _CallPageState createState() => _CallPageState(); @@ -20,10 +32,12 @@ class _CallPageState extends State { _localRenderer.initialize(); _remoteRenderer.initialize(); - signaling.onAddRemoteStream = ((stream) { - _remoteRenderer.srcObject = stream; - setState(() {}); - }); + // signaling.onRemoteStream = ((stream) { + // _remoteRenderer.srcObject = stream; + // setState(() {}); + // }); + + fcmConfigure(); super.initState(); } @@ -35,24 +49,13 @@ class _CallPageState extends State { super.dispose(); } - void _getUserMedia() async { - final Map constraints = { - 'audio': 'false', - 'video': {'facingMode': 'user'}, - }; - - MediaStream stream = await navigator.mediaDevices.getUserMedia(constraints); - setState(() { - _localRenderer.srcObject = stream; - }); - } - - void initializeRenderers() async { - _localRenderer.initialize(); - } @override Widget build(BuildContext context) { + FirebaseMessaging().getToken().then((value){ + print('FCM_TOKEN: $value'); + }); + return AppScaffold( isShowAppBar: true, showNewAppBar: true, @@ -64,44 +67,21 @@ class _CallPageState extends State { SizedBox(height: 8), Wrap( children: [ - ElevatedButton( - onPressed: () { - setState(() { - signaling.openUserMedia(_localRenderer, _remoteRenderer); - }); - }, - child: Text("Open camera & microphone"), - ), - SizedBox( - width: 8, - ), - ElevatedButton( - onPressed: () async { - // roomId = await signaling.createRoom(_remoteRenderer); - textEditingController.text = roomId; - setState(() {}); - }, - child: Text("Create room"), - ), SizedBox( width: 8, ), ElevatedButton( onPressed: () { - // Add roomId - // signaling.joinRoom( - // textEditingController.text, - // _remoteRenderer, - // ); + dummyCall(); }, - child: Text("Join room"), + child: Text("Call"), ), SizedBox( width: 8, ), ElevatedButton( onPressed: () { - // signaling.hangUp(_localRenderer); + signaling.hangUp(_localRenderer); }, child: Text("Hangup"), ) @@ -143,5 +123,57 @@ class _CallPageState extends State { ], ), ); + } + + dummyCall() async{ + final json = { + "callerID": "12345", + "receiverID": "54321", + "msgID": "123", + "notfID": "123", + "notification_foreground": "true", + "count": "1", + "message": "Doctor is calling ", + "AppointmentNo": "123", + "title": "Rayyan Hospital", + "ProjectID": "123", + "NotificationType": "10", + "background": "1", + "doctorname": "Dr Sulaiman Al Habib", + "clinicname": "ENT Clinic", + "speciality": "Speciality", + "appointmentdate": "Sun, 15th Dec, 2019", + "appointmenttime": "09:00", + "type": "video", + "session_id": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0LTE1OTg3NzQ1MDYiLCJpc3MiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0Iiwic3ViIjoiQUNhYWQ1YTNmOGM2NGZhNjczNTY3NTYxNTc0N2YyNmMyYiIsImV4cCI6MTU5ODc3ODEwNiwiZ3JhbnRzIjp7ImlkZW50aXR5IjoiSGFyb29uMSIsInZpZGVvIjp7InJvb20iOiJTbWFsbERhaWx5U3RhbmR1cCJ9fX0.7XUS5uMQQJfkrBZu9EjQ6STL6R7iXkso6BtO1HmrQKk", + "identity": "Haroon1", + "name": "SmallDailyStandup", + "videoUrl": "video", + "picture": "video", + "is_call": "true" + }; + + IncomingCallData incomingCallData = IncomingCallData.fromJson(json); + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => IncomingCall(incomingCallData: incomingCallData))); + } + + fcmConfigure(){ + FirebaseMessaging().configure( + onMessage: (message) async{ + print(message.toString()); + + IncomingCallData incomingCallData; + if(Platform.isAndroid) + incomingCallData = IncomingCallData.fromJson(message['data']); + else if(Platform.isIOS) + incomingCallData = IncomingCallData.fromJson(message); + if(incomingCallData != null) + final result = await Navigator.push(context, MaterialPageRoute(builder: (context) => IncomingCall(incomingCallData: incomingCallData))); + + } + ); + } + + } diff --git a/lib/pages/webRTC/call_page_bkp.dart b/lib/pages/webRTC/call_page_bkp.dart new file mode 100644 index 00000000..4a31feae --- /dev/null +++ b/lib/pages/webRTC/call_page_bkp.dart @@ -0,0 +1,147 @@ +// import 'package:diplomaticquarterapp/pages/webRTC/signaling_bkp.dart'; +// import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_webrtc/flutter_webrtc.dart'; +// +// class CallPage extends StatefulWidget { +// @override +// _CallPageState createState() => _CallPageState(); +// } +// +// class _CallPageState extends State { +// Signaling signaling = Signaling(); +// RTCVideoRenderer _localRenderer = RTCVideoRenderer(); +// RTCVideoRenderer _remoteRenderer = RTCVideoRenderer(); +// String roomId; +// TextEditingController textEditingController = TextEditingController(text: ''); +// +// @override +// void initState() { +// _localRenderer.initialize(); +// _remoteRenderer.initialize(); +// +// signaling.onAddRemoteStream = ((stream) { +// _remoteRenderer.srcObject = stream; +// setState(() {}); +// }); +// +// super.initState(); +// } +// +// @override +// void dispose() { +// _localRenderer.dispose(); +// _remoteRenderer.dispose(); +// super.dispose(); +// } +// +// void _getUserMedia() async { +// final Map constraints = { +// 'audio': 'false', +// 'video': {'facingMode': 'user'}, +// }; +// +// MediaStream stream = await navigator.mediaDevices.getUserMedia(constraints); +// setState(() { +// _localRenderer.srcObject = stream; +// }); +// } +// +// void initializeRenderers() async { +// _localRenderer.initialize(); +// } +// +// @override +// Widget build(BuildContext context) { +// return AppScaffold( +// isShowAppBar: true, +// showNewAppBar: true, +// showNewAppBarTitle: true, +// isShowDecPage: false, +// appBarTitle: "WebRTC Calling", +// body: Column( +// children: [ +// SizedBox(height: 8), +// Wrap( +// children: [ +// ElevatedButton( +// onPressed: () { +// setState(() { +// signaling.openUserMedia(_localRenderer, _remoteRenderer); +// }); +// }, +// child: Text("Open camera & microphone"), +// ), +// SizedBox( +// width: 8, +// ), +// ElevatedButton( +// onPressed: () async { +// roomId = await signaling.createRoom(_remoteRenderer); +// textEditingController.text = roomId; +// setState(() {}); +// }, +// child: Text("Create room"), +// ), +// SizedBox( +// width: 8, +// ), +// ElevatedButton( +// onPressed: () { +// // Add roomId +// signaling.joinRoom( +// textEditingController.text, +// _remoteRenderer, +// ); +// }, +// child: Text("Join room"), +// ), +// SizedBox( +// width: 8, +// ), +// ElevatedButton( +// onPressed: () { +// signaling.hangUp(_localRenderer); +// }, +// child: Text("Hangup"), +// ) +// ], +// ), +// SizedBox(height: 8), +// Expanded( +// child: Padding( +// padding: const EdgeInsets.all(0.0), +// child: Stack( +// children: [ +// Positioned(top: 0.0, right: 0.0, left: 0.0, bottom: 0.0, child: RTCVideoView(_remoteRenderer)), +// Positioned( +// top: 20.0, +// right: 100.0, +// left: 20.0, +// bottom: 300.0, +// child: RTCVideoView(_localRenderer, mirror: true), +// ), +// ], +// ), +// ), +// ), +// Padding( +// padding: const EdgeInsets.all(8.0), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// Text("Join the following Room: "), +// Flexible( +// child: TextFormField( +// controller: textEditingController, +// ), +// ) +// ], +// ), +// ), +// SizedBox(height: 8) +// ], +// ), +// ); +// } +// } diff --git a/lib/pages/webRTC/fcm/FCMSendNotification.dart b/lib/pages/webRTC/fcm/FCMSendNotification.dart new file mode 100644 index 00000000..f67537cf --- /dev/null +++ b/lib/pages/webRTC/fcm/FCMSendNotification.dart @@ -0,0 +1,86 @@ + +import 'dart:convert'; + +import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart'; +import 'package:http/http.dart' as http; + +const _serverFCMToken = 'ya29.a0ARrdaM9U7fZtxF64ntg2Y1Nve-cd4rPazyGcWN69cQmOsddUqxAL1X8GUQ8V6sW2gWxM8ln1BIbmh0OrzQtCiTGrsmcL3jZlGXoQhZN51nX3O7F3g1AXCW_Zt_pjiworCJEGSRkl7QirxE7RFzlwBONsOuft'; +const doctorID = '12345'; + +class FCM{ + static Future sendCallNotifcationTo(String token, String patientID, String patientMobile) async{ + var headers = { + 'Authorization': 'Bearer $_serverFCMToken', + 'Content-Type': 'application/json' + }; + + final body = { + "message": { + "token": token, + "notification": { + "title": "Dr Sulaiman Al Habib", + "body": "Doctor is calling" + }, + "apns": { + "payload": { + "aps": { + "sound": "ring_30Sec.mp3", + "token": "466e0f16fecf1e32c51f812cccc84fcbc807f958b15eb55675a5fa971a775829", + "badge": 1 + } + }, + "headers": { + "apns-priority": "10", + "apns-expiration": "0", + "apns-collapse-id": "2561368006" + } + }, + "data": { + "callerID" : doctorID, + "receiverID" : patientID, + "receiverMobile" : patientMobile, + "msgID": "123", + "notfID": "123", + "notification_foreground": "true", + "count": "1", + "message": "Doctor is calling ", + "AppointmentNo": "123", + "title": "Rayyan Hospital", + "ProjectID": "123", + "NotificationType": "10", + "background": "1", + "doctorname": "Dr Sulaiman Al Habib", + "clinicname": "ENT Clinic", + "speciality": "Speciality", + "appointmentdate": "Sun, 15th Dec, 2019", + "appointmenttime": "09:00", + "type": "video", + "session_id": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0LTE1OTg3NzQ1MDYiLCJpc3MiOiJTS2I2NjYyOWMzN2ZhOTM3YjFjNDI2Zjg1MTgyNWFmN2M0Iiwic3ViIjoiQUNhYWQ1YTNmOGM2NGZhNjczNTY3NTYxNTc0N2YyNmMyYiIsImV4cCI6MTU5ODc3ODEwNiwiZ3JhbnRzIjp7ImlkZW50aXR5IjoiSGFyb29uMSIsInZpZGVvIjp7InJvb20iOiJTbWFsbERhaWx5U3RhbmR1cCJ9fX0.7XUS5uMQQJfkrBZu9EjQ6STL6R7iXkso6BtO1HmrQKk", + "identity": "Haroon1", + "name": "SmallDailyStandup", + "videoUrl": "video", + "picture": "video", + "is_call": "true" + } + } + }; + + bool success = false; + await BaseAppClient().simplePost('https://fcm.googleapis.com/v1/projects/api-project-815750722565/messages:send', body: body, headers: headers, onSuccess: (data, statusCode){ + success = true; + }, onFailure: (error, statusCode){ + success = false; + }); + + return success; + // final response = await http.post('https://fcm.googleapis.com/v1/projects/api-project-815750722565/messages:send', headers:headers, body: body); + + + } +} + +String toB64(String data){ + var bytes = utf8.encode(data); + var base64Str = base64.encode(bytes); + return base64Str; +} \ No newline at end of file diff --git a/lib/pages/webRTC/signaling.dart b/lib/pages/webRTC/signaling.dart index 3f840854..90127e31 100644 --- a/lib/pages/webRTC/signaling.dart +++ b/lib/pages/webRTC/signaling.dart @@ -1,11 +1,45 @@ import 'dart:convert'; -// import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/fcm/FCMSendNotification.dart'; +import 'package:diplomaticquarterapp/uitl/SignalRUtil.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'call_page.dart'; + typedef void StreamStateCallback(MediaStream stream); +typedef void RTCIceGatheringStateCallback(RTCIceGatheringState state); +typedef void RTCPeerConnectionStateCallback(RTCPeerConnectionState state); +typedef void RTCSignalingStateCallback(RTCSignalingState state); class Signaling { + + dispose(){ + if(peerConnection != null) + peerConnection.dispose(); + signalR.closeConnection(); + } + + init(){ + // Create Peer Connection + createPeerConnection(configuration).then((value){ + peerConnection = value; + registerPeerConnectionListeners(); + }); + } + + + initializeSignalR() async{ + if(signalR != null) + await signalR.closeConnection(); + + signalR = SignalRUtil(hubName: "https://VCallApi.hmg.com/WebRTCHub?source=$My_Mobile&username=$My_ID"); + final connected = await signalR.openConnection(); + if(!connected) + throw 'Failed to connect SignalR'; + } + Map configuration = { 'iceServers': [ { @@ -14,209 +48,96 @@ class Signaling { ] }; + SignalRUtil signalR; + RTCPeerConnection peerConnection; MediaStream localStream; MediaStream remoteStream; - String roomId; - String currentRoomText; - StreamStateCallback onAddRemoteStream; - - // Future createRoom(RTCVideoRenderer remoteRenderer) async { - // // FirebaseFirestore db = FirebaseFirestore.instance; - // // DocumentReference roomRef = db.collection('rooms').doc(); - // - // print('Create PeerConnection with configuration: $configuration'); - // - // peerConnection = await createPeerConnection(configuration); - // - // registerPeerConnectionListeners(); - // - // localStream.getTracks().forEach((track) { - // peerConnection?.addTrack(track, localStream); - // }); - // - // // Code for collecting ICE candidates below - // var callerCandidatesCollection = roomRef.collection('callerCandidates'); - // - // peerConnection?.onIceCandidate = (RTCIceCandidate candidate) { - // print('Got candidate: ${candidate.toMap()}'); - // callerCandidatesCollection.add(candidate.toMap()); - // }; - // // Finish Code for collecting ICE candidate - // - // // Add code for creating a room - // RTCSessionDescription offer = await peerConnection.createOffer(); - // await peerConnection.setLocalDescription(offer); - // print('Created offer: $offer'); - // - // Map roomWithOffer = {'offer': offer.toMap()}; - // - // await roomRef.set(roomWithOffer); - // var roomId = roomRef.id; - // print('New room created with SDK offer. Room ID: $roomId'); - // currentRoomText = 'Current room is $roomId - You are the caller!'; - // // Created a Room - // - // peerConnection?.onTrack = (RTCTrackEvent event) { - // print('Got remote track: ${event.streams[0]}'); - // - // event.streams[0].getTracks().forEach((track) { - // print('Add a track to the remoteStream $track'); - // remoteStream?.addTrack(track); - // }); - // }; - // - // // Listening for remote session description below - // roomRef.snapshots().listen((snapshot) async { - // print('Got updated room: ${snapshot.data()}'); - // - // Map data = snapshot.data() as Map; - // if (peerConnection?.getRemoteDescription() != null && data['answer'] != null) { - // var answer = RTCSessionDescription( - // data['answer']['sdp'], - // data['answer']['type'], - // ); - // - // print("Someone tried to connect"); - // await peerConnection?.setRemoteDescription(answer); - // } - // }); - // // Listening for remote session description above - // - // // Listen for remote Ice candidates below - // roomRef.collection('calleeCandidates').snapshots().listen((snapshot) { - // snapshot.docChanges.forEach((change) { - // if (change.type == DocumentChangeType.added) { - // Map data = change.doc.data() as Map; - // print('Got new remote ICE candidate: ${jsonEncode(data)}'); - // peerConnection.addCandidate( - // RTCIceCandidate( - // data['candidate'], - // data['sdpMid'], - // data['sdpMLineIndex'], - // ), - // ); - // } - // }); - // }); - // // Listen for remote ICE candidates above - // - // return roomId; - // } - - // Future joinRoom(String roomId, RTCVideoRenderer remoteVideo) async { - // FirebaseFirestore db = FirebaseFirestore.instance; - // DocumentReference roomRef = db.collection('rooms').doc('$roomId'); - // var roomSnapshot = await roomRef.get(); - // print('Got room ${roomSnapshot.exists}'); - // - // if (roomSnapshot.exists) { - // print('Create PeerConnection with configuration: $configuration'); - // peerConnection = await createPeerConnection(configuration); - // - // registerPeerConnectionListeners(); - // - // localStream.getTracks().forEach((track) { - // peerConnection?.addTrack(track, localStream); - // }); - // - // // Code for collecting ICE candidates below - // var calleeCandidatesCollection = roomRef.collection('calleeCandidates'); - // peerConnection.onIceCandidate = (RTCIceCandidate candidate) { - // if (candidate == null) { - // print('onIceCandidate: complete!'); - // return; - // } - // print('onIceCandidate: ${candidate.toMap()}'); - // calleeCandidatesCollection.add(candidate.toMap()); - // }; - // // Code for collecting ICE candidate above - // - // peerConnection?.onTrack = (RTCTrackEvent event) { - // print('Got remote track: ${event.streams[0]}'); - // event.streams[0].getTracks().forEach((track) { - // print('Add a track to the remoteStream: $track'); - // remoteStream?.addTrack(track); - // }); - // }; - // - // // Code for creating SDP answer below - // var data = roomSnapshot.data() as Map; - // print('Got offer $data'); - // var offer = data['offer']; - // await peerConnection?.setRemoteDescription( - // RTCSessionDescription(offer['sdp'], offer['type']), - // ); - // var answer = await peerConnection.createAnswer(); - // print('Created Answer $answer'); - // - // await peerConnection.setLocalDescription(answer); - // - // Map roomWithAnswer = { - // 'answer': {'type': answer.type, 'sdp': answer.sdp} - // }; - // - // await roomRef.update(roomWithAnswer); - // // Finished creating SDP answer - // - // // Listening for remote ICE candidates below - // // roomRef.collection('callerCandidates').snapshots().listen((snapshot) { - // // snapshot.docChanges.forEach((document) { - // // var data = document.doc.data() as Map; - // // print(data); - // // print('Got new remote ICE candidate: $data'); - // // peerConnection.addCandidate( - // // RTCIceCandidate( - // // data['candidate'], - // // data['sdpMid'], - // // data['sdpMLineIndex'], - // // ), - // // ); - // // }); - // // }); - // } - // } - - Future openUserMedia( - RTCVideoRenderer localVideo, - RTCVideoRenderer remoteVideo, - ) async { - var stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': false}); - - localVideo.srcObject = stream; - localStream = stream; - - remoteVideo.srcObject = await createLocalMediaStream('key'); + RTCDataChannel dataChannel; + + Future call(String patientId, String mobile, {@required RTCVideoRenderer localVideo, @required RTCVideoRenderer remoteVideo}) async { + initializeSignalR(); + + final isCallPlaced = await FCM.sendCallNotifcationTo(DOCTOR_TOKEN, patientId, mobile); + if(!isCallPlaced) + throw 'Failed to notify target for call'; + + return isCallPlaced; + } + + + Future acceptCall(String caller, String receiver, {@required MediaStream localMediaStream, @required Function(MediaStream) onRemoteMediaStream}) async{ + await initializeSignalR(); + signalR.setContributors(caller: caller, receiver: receiver); + await signalR.acceptCall(receiver, caller).catchError((e) => throw 'Failed to inform signalR that i accepted a call'); + + peerConnection.addStream(localMediaStream); + + peerConnection?.onAddStream = (MediaStream stream) { + remoteStream = stream; + onRemoteMediaStream?.call(stream); + }; + + return true; + } + + Future hangupCall(String caller, String receiver) async{ + await signalR.hangupCall(caller, receiver); + dispose(); } - // Future hangUp(RTCVideoRenderer localVideo) async { - // List tracks = localVideo.srcObject.getTracks(); - // tracks.forEach((track) { - // track.stop(); - // }); - // - // if (remoteStream != null) { - // remoteStream.getTracks().forEach((track) => track.stop()); - // } - // if (peerConnection != null) peerConnection.close(); - // - // if (roomId != null) { - // var db = FirebaseFirestore.instance; - // var roomRef = db.collection('rooms').doc(roomId); - // var calleeCandidates = await roomRef.collection('calleeCandidates').get(); - // calleeCandidates.docs.forEach((document) => document.reference.delete()); - // - // var callerCandidates = await roomRef.collection('callerCandidates').get(); - // callerCandidates.docs.forEach((document) => document.reference.delete()); - // - // await roomRef.delete(); - // } - // - // localStream.dispose(); - // remoteStream?.dispose(); - // } + answerOffer(String sdp) async{ + final offer = jsonDecode(sdp); + final caller = offer['caller']; + final receiver = offer['target']; + final offerSdp = offer['sdp']; + peerConnection.setRemoteDescription(rtcSessionDescriptionFrom(offerSdp)) + .then((value) { + return peerConnection.createAnswer(); + }) + .then((anwser) { + return peerConnection.setLocalDescription(anwser); + }) + .then((value) { + return peerConnection.getLocalDescription(); + }) + .then((answer) { + return signalR.answerOffer(answer, caller, receiver); + }); + + } + + Future hangUp(RTCVideoRenderer localVideo) async { + + } + + Future createSdpAnswer(String toOfferSdp) async { + final offerSdp = rtcSessionDescriptionFrom(jsonDecode(toOfferSdp)); + peerConnection.setRemoteDescription(offerSdp); + + final answer = await peerConnection.createAnswer(); + var answerSdp = json.encode(answer); // Send SDP via Push or any channel + return answerSdp; + } + + Future createSdpOffer() async { + final offer = await peerConnection.createOffer(); + await peerConnection.setLocalDescription(offer); + final map = offer.toMap(); + var offerSdp = json.encode(map); // Send SDP via Push or any channel + return offerSdp; + } + + addCandidate(String candidateJson){ + peerConnection.addCandidate(rtcIceCandidateFrom(candidateJson)); + } void registerPeerConnectionListeners() { + peerConnection.onIceCandidate = (RTCIceCandidate candidate){ + print(json.encode(candidate.toMap())); + signalR.addIceCandidate(json.encode(candidate.toMap())); + }; + peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { print('ICE gathering state changed: $state'); }; @@ -228,15 +149,17 @@ class Signaling { peerConnection?.onSignalingState = (RTCSignalingState state) { print('Signaling state change: $state'); }; + } +} - peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { - print('ICE connection state change: $state'); - }; - peerConnection?.onAddStream = (MediaStream stream) { - print("Add remote stream"); - onAddRemoteStream?.call(stream); - remoteStream = stream; - }; - } +rtcSessionDescriptionFrom(Map sdp){ + return RTCSessionDescription( + sdp['sdp'],sdp['type'], + ); +} + +rtcIceCandidateFrom(String json){ + final map = jsonDecode(json)['candidate']; + return RTCIceCandidate(map['candidate'], map['sdpMid'], map['sdpMLineIndex']); } diff --git a/lib/pages/webRTC/signaling_bkp.dart b/lib/pages/webRTC/signaling_bkp.dart new file mode 100644 index 00000000..f987e285 --- /dev/null +++ b/lib/pages/webRTC/signaling_bkp.dart @@ -0,0 +1,242 @@ +import 'dart:convert'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; + +typedef void StreamStateCallback(MediaStream stream); + +class Signaling { + Map configuration = { + 'iceServers': [ + { + 'urls': ['stun:stun1.l.google.com:19302', 'stun:stun2.l.google.com:19302'] + } + ] + }; + + RTCPeerConnection peerConnection; + MediaStream localStream; + MediaStream remoteStream; + String roomId; + String currentRoomText; + StreamStateCallback onAddRemoteStream; + + Future createRoom(RTCVideoRenderer remoteRenderer) async { + FirebaseFirestore db = FirebaseFirestore.instance; + DocumentReference roomRef = db.collection('rooms').doc(); + + print('Create PeerConnection with configuration: $configuration'); + + peerConnection = await createPeerConnection(configuration); + + registerPeerConnectionListeners(); + + localStream.getTracks().forEach((track) { + peerConnection?.addTrack(track, localStream); + }); + + // Code for collecting ICE candidates below + var callerCandidatesCollection = roomRef.collection('callerCandidates'); + + peerConnection?.onIceCandidate = (RTCIceCandidate candidate) { + print('Got candidate: ${candidate.toMap()}'); + callerCandidatesCollection.add(candidate.toMap()); + }; + // Finish Code for collecting ICE candidate + + // Add code for creating a room + RTCSessionDescription offer = await peerConnection.createOffer(); + await peerConnection.setLocalDescription(offer); + print('Created offer: $offer'); + + Map roomWithOffer = {'offer': offer.toMap()}; + + await roomRef.set(roomWithOffer); + var roomId = roomRef.id; + print('New room created with SDK offer. Room ID: $roomId'); + currentRoomText = 'Current room is $roomId - You are the caller!'; + // Created a Room + + peerConnection?.onTrack = (RTCTrackEvent event) { + print('Got remote track: ${event.streams[0]}'); + + event.streams[0].getTracks().forEach((track) { + print('Add a track to the remoteStream $track'); + remoteStream?.addTrack(track); + }); + }; + + // Listening for remote session description below + roomRef.snapshots().listen((snapshot) async { + print('Got updated room: ${snapshot.data()}'); + + Map data = snapshot.data() as Map; + if (peerConnection?.getRemoteDescription() != null && data['answer'] != null) { + var answer = RTCSessionDescription( + data['answer']['sdp'], + data['answer']['type'], + ); + + print("Someone tried to connect"); + await peerConnection?.setRemoteDescription(answer); + } + }); + // Listening for remote session description above + + // Listen for remote Ice candidates below + roomRef.collection('calleeCandidates').snapshots().listen((snapshot) { + snapshot.docChanges.forEach((change) { + if (change.type == DocumentChangeType.added) { + Map data = change.doc.data() as Map; + print('Got new remote ICE candidate: ${jsonEncode(data)}'); + peerConnection.addCandidate( + RTCIceCandidate( + data['candidate'], + data['sdpMid'], + data['sdpMLineIndex'], + ), + ); + } + }); + }); + // Listen for remote ICE candidates above + + return roomId; + } + + Future joinRoom(String roomId, RTCVideoRenderer remoteVideo) async { + FirebaseFirestore db = FirebaseFirestore.instance; + DocumentReference roomRef = db.collection('rooms').doc('$roomId'); + var roomSnapshot = await roomRef.get(); + print('Got room ${roomSnapshot.exists}'); + + if (roomSnapshot.exists) { + print('Create PeerConnection with configuration: $configuration'); + peerConnection = await createPeerConnection(configuration); + + registerPeerConnectionListeners(); + + localStream.getTracks().forEach((track) { + peerConnection?.addTrack(track, localStream); + }); + + // Code for collecting ICE candidates below + var calleeCandidatesCollection = roomRef.collection('calleeCandidates'); + peerConnection.onIceCandidate = (RTCIceCandidate candidate) { + if (candidate == null) { + print('onIceCandidate: complete!'); + return; + } + print('onIceCandidate: ${candidate.toMap()}'); + calleeCandidatesCollection.add(candidate.toMap()); + }; + // Code for collecting ICE candidate above + + peerConnection?.onTrack = (RTCTrackEvent event) { + print('Got remote track: ${event.streams[0]}'); + event.streams[0].getTracks().forEach((track) { + print('Add a track to the remoteStream: $track'); + remoteStream?.addTrack(track); + }); + }; + + // Code for creating SDP answer below + var data = roomSnapshot.data() as Map; + print('Got offer $data'); + var offer = data['offer']; + await peerConnection?.setRemoteDescription( + RTCSessionDescription(offer['sdp'], offer['type']), + ); + var answer = await peerConnection.createAnswer(); + print('Created Answer $answer'); + + await peerConnection.setLocalDescription(answer); + + Map roomWithAnswer = { + 'answer': {'type': answer.type, 'sdp': answer.sdp} + }; + + await roomRef.update(roomWithAnswer); + // Finished creating SDP answer + + // Listening for remote ICE candidates below + // roomRef.collection('callerCandidates').snapshots().listen((snapshot) { + // snapshot.docChanges.forEach((document) { + // var data = document.doc.data() as Map; + // print(data); + // print('Got new remote ICE candidate: $data'); + // peerConnection.addCandidate( + // RTCIceCandidate( + // data['candidate'], + // data['sdpMid'], + // data['sdpMLineIndex'], + // ), + // ); + // }); + // }); + } + } + + Future openUserMedia( + RTCVideoRenderer localVideo, + RTCVideoRenderer remoteVideo, + ) async { + var stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': false}); + + localVideo.srcObject = stream; + localStream = stream; + + remoteVideo.srcObject = await createLocalMediaStream('key'); + } + + Future hangUp(RTCVideoRenderer localVideo) async { + List tracks = localVideo.srcObject.getTracks(); + tracks.forEach((track) { + track.stop(); + }); + + if (remoteStream != null) { + remoteStream.getTracks().forEach((track) => track.stop()); + } + if (peerConnection != null) peerConnection.close(); + + if (roomId != null) { + var db = FirebaseFirestore.instance; + var roomRef = db.collection('rooms').doc(roomId); + var calleeCandidates = await roomRef.collection('calleeCandidates').get(); + calleeCandidates.docs.forEach((document) => document.reference.delete()); + + var callerCandidates = await roomRef.collection('callerCandidates').get(); + callerCandidates.docs.forEach((document) => document.reference.delete()); + + await roomRef.delete(); + } + + localStream.dispose(); + remoteStream?.dispose(); + } + + void registerPeerConnectionListeners() { + peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { + print('ICE gathering state changed: $state'); + }; + + peerConnection?.onConnectionState = (RTCPeerConnectionState state) { + print('Connection state change: $state'); + }; + + peerConnection?.onSignalingState = (RTCSignalingState state) { + print('Signaling state change: $state'); + }; + + peerConnection?.onIceGatheringState = (RTCIceGatheringState state) { + print('ICE connection state change: $state'); + }; + + peerConnection?.onAddStream = (MediaStream stream) { + print("Add remote stream"); + onAddRemoteStream?.call(stream); + remoteStream = stream; + }; + } +} diff --git a/lib/routes.dart b/lib/routes.dart index 72599bb2..9525bc96 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -21,6 +21,8 @@ import 'package:diplomaticquarterapp/pages/symptom-checker/info.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/select-gender.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/symtom-checker.dart'; import 'package:diplomaticquarterapp/pages/webRTC/OpenTok/OpenTok.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/call_page.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/call_page_bkp.dart'; import 'package:diplomaticquarterapp/splashPage.dart'; const String INIT_ROUTE = '/'; @@ -48,6 +50,7 @@ const String PACKAGES_ORDER_COMPLETED = 'packages-offers-cart'; const String TEST_PAGE = 'test-page'; const String OPENTOK_CALL_PAGE = 'OPENTOK_CALL_PAGE'; const String CART_ORDER_PAGE = 'cart-order-page'; +const String CALL_PAGE = 'CALL_PAGE'; const String HEALTH_WEATHER = 'health-weather'; const APP_UPDATE = 'app-update'; @@ -76,6 +79,7 @@ var routes = { APP_UPDATE: (_) => AppUpdatePage(), SETTINGS: (_) => Settings(), CART_ORDER_PAGE: (_) => CartOrderPage(), + CALL_PAGE: (_) => CallPage(), OPENTOK_CALL_PAGE: (_) => OpenTokConnectCallPage( apiKey: '46209962', sessionId: '1_MX40NjIwOTk2Mn5-MTYzNDY0ODM3NDY2Nn5PcnpnNGM0R1Q3ODZ6UXlFQ01lMDF5YWJ-fg', diff --git a/lib/uitl/PlatformBridge.dart b/lib/uitl/PlatformBridge.dart index 56020781..45970e5e 100644 --- a/lib/uitl/PlatformBridge.dart +++ b/lib/uitl/PlatformBridge.dart @@ -1,4 +1,3 @@ - import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/localized_values.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; @@ -89,6 +88,9 @@ class PlatformBridge { static const show_loading_method = "loading"; static const register_Hmg_Geofences = "registerHmgGeofences"; static const un_register_Hmg_Geofences = "unRegisterHmgGeofences"; + static const IS_DRAW_OVER_APPS_PERMISSION_ALLOWED = "isDrawOverAppsPermissionAllowed"; + static const ASK_DRAW_OVER_APPS_PERMISSION = "askDrawOverAppsPermission"; + static const GET_INTENT = "getIntent"; Future connectHMGInternetWifi(String patientId) { try { @@ -142,4 +144,19 @@ class PlatformBridge { void unRegisterHmgGeofences() async { var result = await platform.invokeMethod(un_register_Hmg_Geofences); } + + Future isDrawOverAppsPermissionAllowed() async { + var result = await platform.invokeMethod(IS_DRAW_OVER_APPS_PERMISSION_ALLOWED); + return result as bool; + } + + Future askDrawOverAppsPermission() async { + var result = await platform.invokeMethod(ASK_DRAW_OVER_APPS_PERMISSION); + return result as bool; + } + + Future getIntentData() async { + var result = await platform.invokeMethod(GET_INTENT); + return result as bool; + } } diff --git a/lib/uitl/SignalRUtil.dart b/lib/uitl/SignalRUtil.dart index a1a411e0..d46a2ea8 100644 --- a/lib/uitl/SignalRUtil.dart +++ b/lib/uitl/SignalRUtil.dart @@ -1,36 +1,57 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:http/io_client.dart'; import 'package:signalr_core/signalr_core.dart'; class SignalRUtil { String hubName; - BuildContext context; - SignalRUtil({@required this.hubName, @required this.context}); + String sourceUser; + String destinationUser; + setContributors({@required String caller, @required String receiver}){ + this.sourceUser = caller; + this.destinationUser = receiver; + } + + Function(bool) onConnected; + SignalRUtil({@required this.hubName}); + - HubConnection connectionBuilder; + HubConnection connectionHub; - void startSignalRConnection() async { - connectionBuilder = HubConnectionBuilder() + closeConnection() async{ + if(connectionHub != null) { + connectionHub.off('OnIncomingCallAsync'); + connectionHub.off('OnCallDeclinedAsync'); + connectionHub.off('OnCallAcceptedAsync'); + connectionHub.off('nHangUpAsync'); + connectionHub.off('OnIceCandidateAsync'); + connectionHub.off('OnOfferAsync'); + await connectionHub.stop(); + } + } + + Future openConnection() async { + connectionHub = HubConnectionBuilder() .withUrl( - hubName, - HttpConnectionOptions( - client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true), - logging: (level, message) => print(message), - )) - .build(); + hubName, + HttpConnectionOptions( + logMessageContent: true, + client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true), + logging: (level, message) => print(message), + )).build(); - await connectionBuilder.start(); + await connectionHub.start(); + await Future.delayed(Duration(seconds: 1)); - connectionBuilder.on('ReceiveMessage', (message) { + connectionHub.on('ReceiveMessage', (message) { handleIncomingMessage(message); }); - } - void closeSignalRConnection() { - connectionBuilder.stop(); + return getConnectionState(); } void handleIncomingMessage(List message) { @@ -38,11 +59,114 @@ class SignalRUtil { } void sendMessage(List args) async { - await connectionBuilder.invoke('SendMessage', args: args); //['Bob', 'Says hi!'] + await connectionHub.invoke('SendMessage', args: args); //['Bob', 'Says hi!'] + } + + listen({Function(CallUser) onAcceptCall, Function(CallUser) onHangupCall, Function(String, CallUser) onDeclineCall, Function(String, CallUser) onOffer, Function(String) onCandidate}){ + + connectionHub.on('OnIncomingCallAsync', (arguments) { + print('OnIncomingCallAsync: ${arguments.toString()}'); + }); + + connectionHub.on('OnCallDeclinedAsync', (arguments) { + print('OnCallDeclinedAsync: ${arguments.toString()}'); + onDeclineCall(arguments.first, CallUser.from(arguments.last)); + }); + + connectionHub.on('OnCallAcceptedAsync', (arguments) { + print('OnCallAcceptedAsync: ${arguments.toString()}'); + }); + + connectionHub.on('OnHangUpAsync', (arguments) { + print('nHangUpAsync: ${arguments.toString()}'); + onHangupCall(CallUser.from(arguments.first)); + }); + + connectionHub.on('OnIceCandidateAsync', (arguments) { + print('OnIceCandidateAsync: ${arguments.toString()}'); + onCandidate(arguments.first); + }); + + connectionHub.on('OnOfferAsync', (arguments) { + print('OnOfferAsync: ${arguments.toString()}'); + onOffer(arguments.first, CallUser.from(arguments.last)); + }); + } + // CallUserAsync(string currentUserId, string targerUserId) + Future callUser(String from, to) async{ + return await connectionHub.invoke('CallUserAsync', args: [from, to]); + } + + // CallDeclinedAsync(string currentUserId, string targerUserId) + Future declineCall(String from, to) async{ + return await connectionHub.invoke('CallDeclinedAsync', args: [from, to]); + } + + // AnswerCallAsync(string currentUserId, string targetUserId) + Future answerCall(String from, to) async{ + return await connectionHub.invoke('AnswerCallAsync', args: [from, to]); + } + + // IceCandidateAsync(string targetUserId, string candidate) + Future addIceCandidate(String candidate) async{ + final target = destinationUser; + return await connectionHub.invoke('IceCandidateAsync', args: [target, candidate]); + } + + // OfferAsync(string targetUserId,string currentUserId, string targetOffer) + Future offer(String from, to, offer) async{ + return await connectionHub.invoke('OfferAsync', args: [from, to, offer]); + } + + // AnswerOfferAsync(string targetUserId, string CallerOffer) + Future answerOffer(RTCSessionDescription answerSdp, caller, receiver) async{ + final payload = { + 'target': receiver, + 'caller': caller, + 'sdp': answerSdp.toMap(), + }; + return await connectionHub.invoke('AnswerOfferAsync', args: [caller, jsonEncode(payload)]); + } + + // HangUpAsync(string currentUserId, string targetUserId) + Future hangupCall(String from, to) async{ + return await connectionHub.invoke('HangUpAsync', args: [from, to]); + } + + // CallAccepted(string currentUserId,string targetUserId) + Future acceptCall(String from, to) async{ + return await connectionHub.invoke('CallAccepted', args: [from, to]); + } + + bool getConnectionState() { - if (connectionBuilder.state == HubConnectionState.connected || connectionBuilder.state == HubConnectionState.connecting) return true; - if (connectionBuilder.state == HubConnectionState.disconnected || connectionBuilder.state == HubConnectionState.disconnecting) return false; + if (connectionHub.state == HubConnectionState.connected) return true; + if (connectionHub.state == HubConnectionState.disconnected) return false; + return false; } } + + +class CallUser{ + String Id; + String UserName; + String Email; + String Phone; + String Title; + dynamic UserStatus; + String Image; + int UnreadMessageCount = 0; + + CallUser.from(Map map){ + Id = map['Id']; + UserName = map['UserName']; + Email = map['Email']; + Phone = map['Phone']; + Title = map['Title']; + UserStatus = map['UserStatus']; + Image = map['Image']; + UnreadMessageCount = map['UnreadMessageCount']; + } +} \ No newline at end of file diff --git a/lib/uitl/date_uitl.dart b/lib/uitl/date_uitl.dart index a11b85e6..177b5127 100644 --- a/lib/uitl/date_uitl.dart +++ b/lib/uitl/date_uitl.dart @@ -131,6 +131,10 @@ class DateUtil { dateObj.year.toString(); } + static String getISODateFormat(DateTime dateTime){ // 2020-04-30T00:00:00.000 + return dateTime.toIso8601String(); + } + /// get month by /// [month] convert month number in to month name static getMonth(int month) { diff --git a/lib/widgets/drawer/app_drawer_widget.dart b/lib/widgets/drawer/app_drawer_widget.dart index 8b3819f1..b7e00d54 100644 --- a/lib/widgets/drawer/app_drawer_widget.dart +++ b/lib/widgets/drawer/app_drawer_widget.dart @@ -15,6 +15,7 @@ import 'package:diplomaticquarterapp/pages/DrawerPages/notifications/notificatio import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; import 'package:diplomaticquarterapp/pages/rateAppointment/rate_appointment_doctor.dart'; import 'package:diplomaticquarterapp/pages/webRTC/call_page.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/call_page_bkp.dart'; import 'package:diplomaticquarterapp/routes.dart'; import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service.dart'; diff --git a/lib/widgets/pharmacy/product_tile.dart b/lib/widgets/pharmacy/product_tile.dart index b7293ce1..23a6dd9b 100644 --- a/lib/widgets/pharmacy/product_tile.dart +++ b/lib/widgets/pharmacy/product_tile.dart @@ -1,13 +1,16 @@ import 'package:diplomaticquarterapp/core/model/pharmacies/PharmacyProduct.dart'; import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/product_detail_view_model.dart'; +import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; import 'package:diplomaticquarterapp/pages/pharmacies/screens/cart-page/cart-order-page.dart'; import 'package:diplomaticquarterapp/pages/pharmacy/order/ProductReview.dart'; +import 'package:diplomaticquarterapp/uitl/app_toast.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:rating_bar/rating_bar.dart'; class productTile extends StatelessWidget { @@ -27,6 +30,7 @@ class productTile extends StatelessWidget { final dynamic productID; final Function onDelete; final dynamic approvedTotalReviews; + final dynamic isRx; // final VoidCallback deleteWishlistItems; @@ -47,10 +51,12 @@ class productTile extends StatelessWidget { this.productID, this.onDelete, this.approvedTotalReviews, + this.isRx }); @override Widget build(BuildContext context) { + ProjectViewModel projectViewModel = Provider.of(context); return Container( height: 180, width: double.infinity, @@ -87,7 +93,16 @@ class productTile extends StatelessWidget { children: [ Container( margin: EdgeInsets.all(5), - child: Align( + child: projectViewModel + .isArabic ? Align( + alignment: Alignment.topRight, + child: RichText( + text: TextSpan( + text: productName, + style: TextStyle(color: Colors.black54, fontSize: 15, fontWeight: FontWeight.bold), + ), + ), + ): Align( alignment: Alignment.topLeft, child: RichText( text: TextSpan( @@ -99,7 +114,17 @@ class productTile extends StatelessWidget { ), Container( margin: EdgeInsets.all(5), - child: Align( + child: projectViewModel + .isArabic ? Align( + alignment: Alignment.topRight, + child: RichText( + text: TextSpan( + text: 'SAR $productPrice', + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black, fontSize: 13), + ), + ), + ) + :Align( alignment: Alignment.topLeft, child: RichText( text: TextSpan( @@ -163,10 +188,14 @@ class productTile extends StatelessWidget { color: Colors.green, ), onPressed: () async { + if(isRx == false){ GifLoaderDialogUtils.showMyDialog(context); await addToCartFunction(1, productID, context); GifLoaderDialogUtils.hideDialog(context); - Utils.navigateToCartPage(); + Utils.navigateToCartPage();} + else { + AppToast.showErrorToast(message: TranslationBase.of(context).needPrescription); + } }, ), ],