main_production_audio_video_call
Sikander Saleem 3 weeks ago
parent f2afab96fc
commit b26538b3d6

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
@ -43,6 +44,7 @@
<data android:scheme="tel" />
</intent>
</queries>
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
@ -51,6 +53,17 @@
android:showWhenLocked="true"
android:turnScreenOn="true"
android:usesCleartextTraffic="true">
<service
android:name="com.hiennv.flutter_callkit_incoming.CallkitNotificationService"
android:exported="false"
android:foregroundServiceType="phoneCall"
tools:replace="android:exported"/>
<service
android:name="com.hiennv.flutter_callkit_incoming.CallkitSoundPlayerService"
android:exported="false"
android:foregroundServiceType="phoneCall"
tools:replace="android:exported"/>
<!-- 16KB page size support - Required for modern Android devices -->
<property

@ -6,6 +6,7 @@ import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_event.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';
@ -13,6 +14,7 @@ import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:google_api_availability/google_api_availability.dart';
import 'package:huawei_push/huawei_push.dart' as h_push;
import 'package:test_sa/controllers/notification/notification_manger.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/models/device/device_transfer.dart';
import 'package:test_sa/models/new_models/gas_refill_model.dart';
import 'package:test_sa/modules/cm_module/cm_detail_page.dart';
@ -28,7 +30,23 @@ import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if (message.notification == null && message.data != null) {
handleDataMessage(message.data);
handleDataMessage(message.data, true);
}
}
@pragma('vm:entry-point')
Future<void> onBackgroundMessage(CallEvent event) async {
// This executes when a background/killed action happens
switch (event.eventName) {
case CallEventConstants.actionCallAccept:
"actionCallAccept".showToast;
break;
case CallEventConstants.actionCallDecline:
"actionCallDecline".showToast;
break;
default:
break;
}
}
@ -268,7 +286,7 @@ class FirebaseNotificationManger {
}
}
if (message.notification == null && message.data != null) {
handleDataMessage(message.data);
handleDataMessage(message.data, false);
}
return;
});
@ -279,7 +297,7 @@ class FirebaseNotificationManger {
}
}
Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
Future<void> handleDataMessage(Map<String, dynamic> messageData, bool isFromBackground) async {
print("handleDataMessage:${messageData}");
// if (messageData["data"] != null) {
// messageData = messageData["data"];
@ -310,8 +328,7 @@ Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
final referenceId = messageData['referenceId'] as String?;
final conversationId = messageData['conversationId'] as String?;
log('📱 [FOREGROUND] Showing CallKit UI...', name: 'FirebaseNotificationManger');
WidgetsFlutterBinding.ensureInitialized();
Timer? _callTimeoutTimer;
try {
final callKitParams = CallKitParams(
@ -324,10 +341,16 @@ Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
duration: 30000,
missedCallNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
isShowCallback: false,
subtitle: 'Missed call',
callbackText: 'Call back',
),
callingNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
subtitle: 'Calling...',
callbackText: 'Hang Up',
),
extra: {
'callId': callId,
'callerId': callerId,
@ -341,7 +364,7 @@ Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
'timestamp': DateTime.now().toIso8601String(),
},
android: const AndroidParams(
isCustomNotification: true,
// isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
@ -368,11 +391,11 @@ Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
ringtonePath: 'system_ringtone_default',
),
);
// await FlutterCallkitIncoming.requestNotificationPermission({
// "title": "Notification permission",
// "rationaleMessagePermission": "Notification permission is required, to show notification.",
// "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting."
// });
await FlutterCallkitIncoming.requestNotificationPermission({
// // "title": "Notification permission",
// // "rationaleMessagePermission": "Notification permission is required, to show notification.",
// // "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting."
});
await FlutterCallkitIncoming.canUseFullScreenIntent();
await FlutterCallkitIncoming.requestFullIntentPermission();
@ -382,6 +405,8 @@ Future<void> handleDataMessage(Map<String, dynamic> messageData) async {
_callTimeoutTimer = Timer(const Duration(seconds: 30), () async {
await FlutterCallkitIncoming.endCall(callId);
await FlutterCallkitIncoming.endAllCalls();
await FlutterCallkitIncoming.showMissCallNotification(callKitParams);
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
});

@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:localization/localization.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:provider/provider.dart';
@ -119,6 +120,7 @@ import 'package:test_sa/views/widgets/equipment/my_assets_page.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'controllers/notification/firebase_notification_manger.dart';
import 'controllers/notification/notification_manger.dart';
import 'controllers/providers/api/gas_refill_comments.dart';
import 'controllers/providers/api/user_provider.dart';
@ -156,6 +158,8 @@ import 'modules/cleaner_module/provider/cleaner_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage);
FlutterCallkitIncoming.endAllCalls();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
// HttpOverrides.global = MyHttpOverrides(); // for later use.
_configureLocalTimeZone();

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
@ -25,6 +26,7 @@ class AudioCallPage extends StatefulWidget {
class _AudioCallPageState extends State<AudioCallPage> {
late CallProvider callPro;
late ChatProvider chatProvider;
// Participants? participants;
String currentStatus = "Connecting...";
@ -39,6 +41,14 @@ class _AudioCallPageState extends State<AudioCallPage> {
}
void makeCall() async {
bool isConnected = await callPro.buildHubConnection(widget.sender.employeeNumber!);
if (!isConnected) {
context.showConfirmDialog("Error while connecting to call service. Please try again later.", title: "Connection Error", onTap: () {
Navigator.pop(context);
Navigator.pop(context);
});
return;
}
callPro.initAudioCallListeners();
callPro.startCall(widget.sender.employeeNumber ?? "", widget.recipient?.employeeNumber ?? "", false, chatProvider.moduleID, chatProvider.referenceID);

@ -8,8 +8,13 @@ import 'package:signalr_netcore/http_connection_options.dart';
import 'package:signalr_netcore/hub_connection.dart';
import 'package:signalr_netcore/hub_connection_builder.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/modules/cx_module/chat/api_client.dart';
import 'package:http/http.dart' as http;
import '../chat_provider.dart';
import '../model/chat_login_response_model.dart';
HubConnection? callHubConnection;
class CallProvider with ChangeNotifier, DiagnosticableTreeMixin {
///////////////////// Web RTC Video Calling //////////////////////
@ -27,112 +32,93 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin {
bool isPeerMuted = false;
bool isPeerCameraOn = true;
bool isLoading = false;
Future<void> _disposeConnection() async {
try {
if (chatHubConnection != null) {
await chatHubConnection!.stop();
if (kDebugMode) {
print('🔌 SignalR connection closed successfully');
}
if (callHubConnection != null) {
await callHubConnection!.stop();
}
} catch (e) {
if (kDebugMode) {
print('⚠️ Error closing SignalR connection: $e');
}
} finally {
chatHubConnection = null;
callHubConnection = null;
}
// isLoading = false;
// notifyListeners();
}
@override
void dispose() {
_disposeConnection().then((_) {
if (kDebugMode) {
print('✅ ChatProvider disposed');
}
}).catchError((error) {
if (kDebugMode) {
print('⚠️ Error during ChatProvider disposal: $error');
}
});
_disposeConnection().then((_) {}).catchError((error) {});
super.dispose();
}
Future<HubConnection> getHubConnection() async {
if (kDebugMode) {
print('🔧 Creating new SignalR hub connection...');
}
Future<HubConnection> getHubConnection(String token) async {
HubConnection hub;
HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true);
hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?accessTokenFactory=${URLs.chatApiKey}", options: httpOp).withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build();
if (kDebugMode) {
print('✅ SignalR hub connection created');
}
hub = HubConnectionBuilder().withUrl("${URLs.chatHubUrlChat}?accessTokenFactory=$token}", options: httpOp).withAutomaticReconnect(retryDelays: <int>[2000, 5000, 10000, 20000]).build();
return hub;
}
Future<bool> buildHubConnection() async {
Future<bool> buildHubConnection(String employeeNumber) async {
try {
// Dispose existing connection if any
await _disposeConnection();
chatHubConnection = await getHubConnection();
await chatHubConnection!.start();
if (kDebugMode) {
print("🔌 SignalR Hub Connection: Started");
// isLoading = true;
// notifyListeners();
http.Response response = await ApiClient().postJsonForResponse(URLs.chatSdkToken, {"apiKey": URLs.chatApiKey, "employeeNumber": employeeNumber, "voIPToken": null});
if (response.statusCode == 200) {
ChatLoginResponse chatResponse = ChatLoginResponse.fromJson(jsonDecode(response.body));
print("chatResponse:${chatResponse.token}");
callHubConnection = await getHubConnection(chatResponse.token!);
await callHubConnection!.start();
// isLoading = false;
// notifyListeners();
return true;
}
return true;
//group On message
// chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
// isLoading = false;
// notifyListeners();
return false;
} catch (e) {
if (kDebugMode) {
print('⚠️ Error building SignalR connection: $e');
}
// Clean up on error
print("buildHubConnectionE:$e");
await _disposeConnection();
return false;
rethrow; // Rethrow so caller knows about the error
}
}
void initAudioCallListeners() {
chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection!.on("OnOfferAsync", onOfferAsync);
chatHubConnection!.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection!.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync);
chatHubConnection!.on('OnAudioToggle', onAudioToggle);
callHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync);
callHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync);
callHubConnection!.on("OnOfferAsync", onOfferAsync);
callHubConnection!.on("OnAnswerOffer", onAnswerOffer);
callHubConnection!.on("OnHangUpAsync", onHangUpAsync);
callHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync);
callHubConnection!.on('OnAudioToggle', onAudioToggle);
}
void initCallListeners() {
chatHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection!.on("OnOfferAsync", onOfferAsync);
chatHubConnection!.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection!.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync);
chatHubConnection!.on('OnAudioToggle', onAudioToggle);
chatHubConnection!.on('OnCameraToggle', onCameraToggle);
// chatHubConnection!.on('OnIncomingCallAsync', onHandleIncomingCall);
// chatHubConnection!.on('OnCallAcceptedAsync', _handleCallAccepted);
// chatHubConnection!.on('OnCallDeclinedAsync', _handleCallDeclined);
// chatHubConnection!.on('OnHangUpAsync', onHangUpAsync);
// chatHubConnection!.on('OnOfferAsync', _handleOffer);
// chatHubConnection!.on('OnAnswerOfferAsync', onAnswerOffer);
// chatHubConnection!.on('OnIceCandidateAsync', _handleIceCandidate);
// chatHubConnection!.on('OnAudioToggle', _handleAudioToggle);
// chatHubConnection!.on('OnCameraToggle', _handleCameraToggle);
// chatHubConnection!.on('OnUserOnlineAsync', _handleUserOnline);
// chatHubConnection!.on('OnUserOfflineAsync', _handleUserOffline);
// chatHubConnection!.on('OnCallHistoryUpdated', _handleCallHistoryUpdated);
// chatHubConnection!.on('OnError', _handleError);
callHubConnection!.on("OnCallAcceptedAsync", onCallAcceptedAsync);
callHubConnection!.on("OnIceCandidateAsync", onIceCandidateAsync);
callHubConnection!.on("OnOfferAsync", onOfferAsync);
callHubConnection!.on("OnAnswerOffer", onAnswerOffer);
callHubConnection!.on("OnHangUpAsync", onHangUpAsync);
callHubConnection!.on("OnCallDeclinedAsync", onCallDeclinedAsync);
callHubConnection!.on('OnAudioToggle', onAudioToggle);
callHubConnection!.on('OnCameraToggle', onCameraToggle);
// callHubConnection!.on('OnIncomingCallAsync', onHandleIncomingCall);
// callHubConnection!.on('OnCallAcceptedAsync', _handleCallAccepted);
// callHubConnection!.on('OnCallDeclinedAsync', _handleCallDeclined);
// callHubConnection!.on('OnHangUpAsync', onHangUpAsync);
// callHubConnection!.on('OnOfferAsync', _handleOffer);
// callHubConnection!.on('OnAnswerOfferAsync', onAnswerOffer);
// callHubConnection!.on('OnIceCandidateAsync', _handleIceCandidate);
// callHubConnection!.on('OnAudioToggle', _handleAudioToggle);
// callHubConnection!.on('OnCameraToggle', _handleCameraToggle);
// callHubConnection!.on('OnUserOnlineAsync', _handleUserOnline);
// callHubConnection!.on('OnUserOfflineAsync', _handleUserOffline);
// callHubConnection!.on('OnCallHistoryUpdated', _handleCallHistoryUpdated);
// callHubConnection!.on('OnError', _handleError);
}
//Video Constraints
@ -337,16 +323,16 @@ class CallProvider with ChangeNotifier, DiagnosticableTreeMixin {
} else if (invokeMethod == "AnswerOfferAsync") {
args = [targetUserID, data];
}
await chatHubConnection!.invoke(invokeMethod, args: args);
await callHubConnection?.invoke(invokeMethod, args: args);
}
void stopListeners() async {
chatHubConnection!.off('OnCallDeclinedAsync');
chatHubConnection!.off('OnCallAcceptedAsync');
chatHubConnection!.off('OnIceCandidateAsync');
chatHubConnection!.off('OnAnswerOffer');
chatHubConnection!.off('OnOfferAsync');
chatHubConnection!.off('OnHangUpAsync');
chatHubConnection!.off('OnAudioToggle');
callHubConnection?.off('OnCallDeclinedAsync');
callHubConnection?.off('OnCallAcceptedAsync');
callHubConnection?.off('OnIceCandidateAsync');
callHubConnection?.off('OnAnswerOffer');
callHubConnection?.off('OnOfferAsync');
callHubConnection?.off('OnHangUpAsync');
callHubConnection?.off('OnAudioToggle');
}
}

@ -227,9 +227,10 @@ class _LandPageState extends State<LandPage> {
break;
case CallEventConstants.actionCallAccept:
var activeCalls = await FlutterCallkitIncoming.activeCalls();
if (activeCalls is List && activeCalls.isNotEmpty) {
final lastCall = activeCalls.last;
await FlutterCallkitIncoming.endCall(lastCall.extra?["callId"]);
FlutterCallkitIncoming.endAllCalls();
Navigator.of(context).push(MaterialPageRoute(
builder: (_) =>
AudioCallPage(sender: Participants(employeeNumber: lastCall.extra?["calleeEmployeeNumber"]), recipient: Participants(employeeNumber: lastCall.extra?["callerEmployeeNumber"]))));

Loading…
Cancel
Save