diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e9bc7171..6168f9ce 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -31,8 +31,7 @@ android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" tools:node="remove" /> + android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" /> @@ -84,6 +83,10 @@ + + + + + + diff --git a/lib/locator.dart b/lib/locator.dart index 61ed983b..a85a5a9d 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -151,8 +151,13 @@ import 'services/pharmacy_services/product_detail_service.dart'; GetIt locator = GetIt.instance; +void setupNavigator(){ + locator.registerSingleton( NavigationService()); +} ///di void setupLocator() { + setupNavigator(); + /// Others locator.registerLazySingleton(() => FlutterLocalNotificationsPlugin()); @@ -168,7 +173,6 @@ 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 03ac65fc..01a4f21b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,10 @@ import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/core/viewModels/PharmacyPagesViewModel.dart'; import 'package:diplomaticquarterapp/core/viewModels/dashboard_view_model.dart'; import 'package:diplomaticquarterapp/models/Appointments/toDoCountProviderModel.dart'; +import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart'; +import 'package:diplomaticquarterapp/pages/conference/zoom/call_screen.dart'; +import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; +import 'package:diplomaticquarterapp/pages/webRTC/OpenTok/OpenTok.dart'; import 'package:diplomaticquarterapp/routes.dart'; import 'package:diplomaticquarterapp/services/payfort_services/payfort_view_model.dart'; import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart'; @@ -14,6 +18,7 @@ import 'package:diplomaticquarterapp/theme/theme_notifier.dart'; import 'package:diplomaticquarterapp/theme/theme_value.dart'; import 'package:diplomaticquarterapp/uitl/DeepLinkHandler.dart'; import 'package:diplomaticquarterapp/uitl/PlatformBridge.dart'; +import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:diplomaticquarterapp/uitl/navigation_service.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -25,6 +30,7 @@ import 'config/size_config.dart'; import 'core/viewModels/pharmacyModule/OrderPreviewViewModel.dart'; import 'core/viewModels/project_view_model.dart'; import 'locator.dart'; +import 'models/LiveCare/IncomingCallData.dart'; import 'pages/pharmacies/compare-list.dart'; void main() async { @@ -102,13 +108,67 @@ class _MyApp extends State { super.dispose(); } - + late final AppLifecycleListener _listener; Future initDeepLinks() async { // Handle links _linkSubscription = AppLinks().uriLinkStream.listen((uri) { debugPrint('onAppLink: $uri'); openAppLink(uri); }); + _listener = AppLifecycleListener( + onShow: () => {}, + onResume: () =>{}, + onHide: () => {}, + onInactive: () => {}, + onPause: () => {}, + onDetach: () => {}, + onRestart: () => {}, + onStateChange: (state) async { + if(state == AppLifecycleState.resumed) { + await Future.delayed(Duration(seconds: 5)); + _handleTransition(); + } + }, + ); + _handleTransition(shouldApplyDelay: true); + } + void _handleTransition({bool shouldApplyDelay = false}) async { + var data = await AppSharedPreferences().getObject('call_data'); + if(data == null) return; + await AppSharedPreferences().remove('call_data'); + if(shouldApplyDelay) + await Future.delayed(Duration(seconds: 5)); + var incomingcallData= IncomingCallData.fromJson(data); + openCallPage(incomingcallData); + } + Future openCallPage(IncomingCallData incomingCallData) async { + try { + LandingPage.isOpenCallPage = false; + + if (incomingCallData!.background == "0") { + // Zoom Call Page + // Navigator.of(context).pop(); + Navigator.pushNamed( + locator().navigatorKey.currentContext!, + "zoom_call_page", + arguments: CallArguments(incomingCallData!.sessionId!, "123", "Patient", "40", "0", false, int.parse(incomingCallData!.appointmentNo!)), + ); + } else { + // OpenTok Call Page + await Navigator.of(locator().navigatorKey.currentContext!).push( + MaterialPageRoute( + builder: (BuildContext context) { + return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: incomingCallData!.sessionId!, token: incomingCallData!.token!); + }, + ), + ); + } + } catch (err) { + print(err); + await PlatformExceptionAlertDialog( + exception: Exception(err), + ).show(context); + } } void openAppLink(Uri uri) { diff --git a/lib/pages/landing/landing_page.dart b/lib/pages/landing/landing_page.dart index ffc9fec5..9b839ad8 100644 --- a/lib/pages/landing/landing_page.dart +++ b/lib/pages/landing/landing_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/model/ImagesInfo.dart'; @@ -37,6 +38,7 @@ import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:diplomaticquarterapp/widgets/others/not_auh_page.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; // import 'package:flutter_app_icon_badge/flutter_app_icon_badge.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -647,6 +649,15 @@ class _LandingPageState extends State with WidgetsBindingObserver { void checkUserStatus(token) async { await PushNotificationHandler.getInstance().requestPermissions(); + if (Platform.isAndroid) { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + + int sdkInt = androidInfo.version.sdkInt ?? 0; + if(sdkInt>= 33){ + await FlutterCallkitIncoming.requestFullIntentPermission(); + } + } // authService.selectDeviceImei(token).then((SelectDeviceIMEIRES? value) => setUserValues(value)); if (authenticatedUserObject.isLogin) { var data = AuthenticatedUser.fromJson(await sharedPref.getObject(USER_PROFILE)); diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 3d9b2927..ba98c3c0 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -81,9 +81,11 @@ class _SplashScreenState extends State { projectProvider.setProjectsDetailList(_privilegeService.projectDetailListModel); double lat = await AppSharedPreferences().getDouble(USER_LAT) ?? 0.0; double long = await AppSharedPreferences().getDouble(USER_LONG) ?? 0.0; + var callData = await AppSharedPreferences().getObject('call_data'); AppSharedPreferences().clear(); // Clearing Shared Preferences On App Launch await AppSharedPreferences().setDouble(USER_LAT, lat); await AppSharedPreferences().setDouble(USER_LONG, long); + await AppSharedPreferences().setObject('call_data', callData); AppSharedPreferences().setString(APP_LANGUAGE, projectProvider.isArabic ? "ar" : "en"); var themeNotifier = Provider.of(context, listen: false); themeNotifier.setTheme(defaultTheme(fontName: projectProvider.isArabic ? 'Cairo' : 'Poppins')); diff --git a/lib/uitl/push-notification-handler.dart b/lib/uitl/push-notification-handler.dart index c6ae36d3..c89ede14 100644 --- a/lib/uitl/push-notification-handler.dart +++ b/lib/uitl/push-notification-handler.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; - +import 'package:flutter_callkit_incoming/entities/call_event.dart'; +import 'package:uuid/uuid.dart'; import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/model/notifications/get_notifications_response_model.dart'; @@ -22,6 +23,12 @@ import 'package:firebase_messaging/firebase_messaging.dart' as fir; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_callkit_incoming/entities/android_params.dart'; +import 'package:flutter_callkit_incoming/entities/call_kit_params.dart'; +import 'package:flutter_callkit_incoming/entities/ios_params.dart'; +import 'package:flutter_callkit_incoming/entities/notification_params.dart'; +import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; +import 'package:flutter_incoming_call/flutter_incoming_call.dart'; // import 'package:flutter_ios_voip_kit/call_state_type.dart'; // import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart'; @@ -31,6 +38,7 @@ import 'package:huawei_hmsavailability/huawei_hmsavailability.dart'; import 'package:huawei_push/huawei_push.dart' as h_push; import 'package:permission_handler/permission_handler.dart'; +import '../pages/conference/widgets/platform_exception_alert_dialog.dart'; import 'app_shared_preferences.dart'; import 'navigation_service.dart'; @@ -44,7 +52,6 @@ Future backgroundMessageHandler(dynamic message) async { message_ = toFirebaseRemoteMessage(message); h_push.Push.localNotification({h_push.HMSLocalNotificationAttr.TITLE: 'Background Message', h_push.HMSLocalNotificationAttr.MESSAGE: "By: BackgroundMessageHandler"}); } - if (message.data != null && (message.data['is_call'] == 'true' || message.data['is_call'] == true)) { // showCallkitIncoming(message); _incomingCall(message.data); @@ -162,18 +169,159 @@ callPage(String sessionID, String token) async { _incomingCall(Map data) async { LandingPage.incomingCallData = IncomingCallData.fromJson(data); + var dataItem = await AppSharedPreferences().getObject('call_data'); + if(dataItem != null ) return; // to stop repeated attempt to invoke the call if (LandingPage.isOpenCallPage == false) { LandingPage.isOpenCallPage = true; - final bool permited = await AppPermission.askVideoCallPermission(currentContext!); - if (permited) - await NavigationService.navigateToPage( - IncomingCall(incomingCallData: LandingPage.incomingCallData), - ); + WidgetsFlutterBinding.ensureInitialized(); + var _currentUuid = Uuid().v4(); + CallKitParams callKitParams = CallKitParams( + id: _currentUuid, + nameCaller: "${LandingPage.incomingCallData.doctorname}" + "\n Dr Sulaiman Al Habib \n ${LandingPage.incomingCallData.clinicname}", + appName: 'Dr Sulaiman Al Habib', + avatar: '', + handle: LandingPage.incomingCallData.name, + type: 0, + textAccept: 'Accept', + textDecline: 'Decline', + missedCallNotification: NotificationParams( + showNotification: true, + isShowCallback: false, + subtitle: 'Missed call', + callbackText: '', + ), + callingNotification: const NotificationParams( + showNotification: true, + isShowCallback: true, + subtitle: 'Calling...', + callbackText: 'Hang Up', + ), + duration: 30000, + extra: {'userId': '1a2b3c4d'}, + headers: {'apiKey': 'Abc@123!', 'platform': 'flutter'}, + android: const AndroidParams( + isCustomNotification: true, + isShowLogo: false, + logoUrl: '', + ringtonePath: 'system_ringtone_default', + backgroundColor: '#D02127', + backgroundUrl: '', + actionColor: '#4CAF50', + textColor: '#ffffff', + incomingCallNotificationChannelName: "Incoming Call", + missedCallNotificationChannelName: "Missed Call", + isShowCallID: false + ), + ios: IOSParams( + iconName: 'Dr Sulaiman Al Habib', + handleType: 'generic', + supportsVideo: true, + maximumCallGroups: 2, + maximumCallsPerCallGroup: 1, + audioSessionMode: 'default', + audioSessionActive: true, + audioSessionPreferredSampleRate: 44100.0, + audioSessionPreferredIOBufferDuration: 0.005, + supportsDTMF: true, + supportsHolding: true, + supportsGrouping: false, + supportsUngrouping: false, + ringtonePath: 'system_ringtone_default', + ), + ); + await FlutterCallkitIncoming.showCallkitIncoming(callKitParams); + try { + FlutterCallkitIncoming.onEvent.listen((event) async { + switch (event!.event) { + case Event.actionCallIncoming: + + break; + case Event.actionCallStart: + + break; + case Event.actionCallAccept: + await FlutterCallkitIncoming.endAllCalls(); + try { + openCallPage(LandingPage.incomingCallData, + locator().navigatorKey + .currentContext!); + } catch (e) { + await AppSharedPreferences().setObject('call_data', data); + var data1 = await AppSharedPreferences().getObject('call_data'); + } + + break; + case Event.actionCallDecline: + + break; + case Event.actionCallEnded: + + break; + case Event.actionCallTimeout: + + break; + case Event.actionCallCallback: + break; + case Event.actionCallToggleHold: + break; + case Event.actionCallToggleMute: + break; + case Event.actionCallToggleDmtf: + break; + case Event.actionCallToggleGroup: + break; + case Event.actionCallToggleAudioSession: + break; + case Event.actionDidUpdateDevicePushTokenVoip: + break; + case Event.actionCallCustom: + break; + } + }); + } on Exception catch (e) { + print(e); + } + } + LandingPage.isOpenCallPage = false; + await Future.delayed(Duration(milliseconds: 500)); +} +Future openCallPage(IncomingCallData incomingCallData, BuildContext context) async { + try { LandingPage.isOpenCallPage = false; + + if (incomingCallData!.background == "0") { + // Zoom Call Page + // Navigator.of(context).pop(); + Navigator.pushReplacementNamed( + context, + "zoom_call_page", + arguments: CallArguments(incomingCallData!.sessionId!, "123", "Patient", "40", "0", false, int.parse(incomingCallData!.appointmentNo!)), + ); + } else { + // OpenTok Call Page + await Navigator.of(context).pushReplacement( + MaterialPageRoute( + // fullscreenDialog: true, + builder: (BuildContext context) { + // final caller = widget.incomingCallData.callerID; + // final receiver = widget.incomingCallData.receiverID; + // final host = widget.incomingCallData.server; + // if(widget.incomingCallData.isWebRTC == "true"){ + // return StartVideoCall(caller: caller, receiver: receiver, iAmCaller: false, host: host); + // }else{ + return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: incomingCallData!.sessionId!, token: incomingCallData!.token!); + }, + ), + ); + } + } catch (err) { + print(err); + await PlatformExceptionAlertDialog( + exception: Exception(err), + ).show(context); } - await Future.delayed(Duration(milliseconds: 500)); - await AppSharedPreferences().remove('call_data'); } class PushNotificationHandler { @@ -382,7 +530,6 @@ class PushNotificationHandler { // }); // } - FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); } newMessage(RemoteMessage remoteMessage) async { @@ -440,10 +587,6 @@ class PushNotificationHandler { } onResume() async { - var call_data = await AppSharedPreferences().getObject('call_data'); - if (call_data != null) { - _incomingCall(call_data); - } } Future requestPermissions() async { diff --git a/pubspec.yaml b/pubspec.yaml index dc3da8dc..9986d6a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,7 +66,7 @@ dependencies: shared_preferences: ^2.0.0 # flutter_flexible_toast: ^0.1.4 fluttertoast: ^8.0.8 - firebase_messaging: 14.1.0 + firebase_messaging: 14.7.7 firebase_analytics: ^10.0.5 # Progress bar flutter_progress_hud: ^2.0.2 @@ -75,6 +75,7 @@ dependencies: # Icons font_awesome_flutter: any cupertino_icons: ^1.0.0 + flutter_callkit_incoming: ^2.5.3-alpha # Image Attachments image_picker: ^1.0.4 @@ -88,6 +89,7 @@ dependencies: # UI Reqs expandable: ^5.0.1 + uuid: ^3.0.7 # charts flutter_charts: ^0.5.2