Erwartete Ergebnisse:
Ich habe erwartet, dass die App aufwacht und den CallKit-Bildschirm anzeigt, wenn eine VoIP-Push-Benachrichtigung empfangen wird, während die App geschlossen ist > und Übergang zur Anrufseite, wo ich kann Nehmen Sie den Anruf an.
Tatsächliche Ergebnisse:
Wenn die App geschlossen ist, CallKit wird nicht ausgelöst und die App navigiert nicht zum Anrufbildschirm, selbst nach Erhalt der VoIP-Push-Benachrichtigung.
Zusätzliche Informationen:
Ich habe versucht, der Dokumentation zu folgen CallKit und VoIP Push Notifications auf iOS, aber ich kann die App immer noch nicht richtig funktionieren lassen, wenn sie vollständig geschlossen ist. Wenn jemand Erfahrung mit der Handhabung von VoIP-Anrufen und CallKit in Flutter (oder sogar mit nativem iOS-Code) hat, würde ich mich über Hinweise oder Vorschläge dazu freuen So beheben Sie dieses Problem.
Code: Select all
import 'dart:async';
import 'dart:developer' as dev;
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:legalis/AppUtil.dart';
import 'package:legalis/l10n/support_locale.dart';
import 'package:legalis/notify_controller.dart';
import 'package:legalis/provider/auth/auth_provider.dart';
import 'package:legalis/provider/call/call_provider.dart';
import 'package:legalis/provider/category/category_provider.dart';
import 'package:legalis/provider/chat/chat_detail_provider.dart';
import 'package:legalis/provider/communication/communication_provider.dart';
import 'package:legalis/provider/home/lawyer_report_provider.dart';
import 'package:legalis/provider/language_provider.dart';
import 'package:legalis/provider/localization/localization_provider.dart';
import 'package:legalis/provider/message/message_list_provider.dart';
import 'package:legalis/provider/navigation/navigation_provider.dart';
import 'package:legalis/provider/service/user_service_provider.dart';
import 'package:legalis/provider/user/user_provider.dart';
import 'package:legalis/screens/call/call_page.dart';
import 'package:legalis/screens/pages/home/home_page.dart';
import 'package:legalis/static/call_accept_observer.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
import 'firebase_options.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Bu dosya tüm providerları içeriyor.
import 'package:legalis/screens/splash_screen.dart';
import 'package:legalis/screens/auth/login_screen.dart';
import 'package:legalis/screens/pages/message/message_page.dart';
import 'package:legalis/screens/pages/profile/profile_page.dart';
import 'package:legalis/screens/pages/call/call_history.dart';
import 'package:legalis/wrapper/main_wrapper.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
int getUniqueNotificationId() {
var num = Random().nextInt(2000);
AppUtil.setNotification(num);
return num;
}
GlobalKey navigatorKey = GlobalKey();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Initialize navigatorKey at the start
navigatorKey = GlobalKey();
// Initialize CallKit event handler
Widget? initialRoute;
// Add CallKit listener initialization here
await _initCallKitListener();
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Set background message handler only once
FirebaseMessaging.onBackgroundMessage(handleNotification);
// Configure foreground message handling
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
dev.log('Foreground message received');
await handleNotification(message);
});
// Configure message handling when app is opened from terminated state
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
dev.log('App opened from terminated state with message');
await handleNotification(message);
});
await Future.delayed(const Duration(seconds: 1));
if (Platform.isIOS) {
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
String? apnsToken = await messaging.getAPNSToken();
Logger().t('APNS Token: $apnsToken');
if (apnsToken != null) {
print('APNS Token: $apnsToken');
}
}
}
// Get FCM token only once
String? fcmToken = await messaging.getToken();
Logger().t('FCM Token: $fcmToken');
// Remove duplicate call
// FirebaseMessaging.onBackgroundMessage(handleNotification);
AwesomeNotifications().initialize(
null,
[
NotificationChannel(
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: const Color(0xFF9D50DD),
ledColor: Colors.white,
),
NotificationChannel(
channelKey: 'call_channel',
channelName: 'Call notifications',
channelDescription: 'Notification channel for call notifications',
defaultColor: Colors.orange,
ledColor: Colors.white,
importance: NotificationImportance.Max,
channelShowBadge: true,
locked: false,
playSound: true,
defaultRingtoneType: DefaultRingtoneType.Ringtone,
),
NotificationChannel(
channelKey: 'message_channel',
channelName: 'Message notifications',
channelDescription: 'Notification channel for message notifications',
defaultColor: Colors.blue,
ledColor: Colors.white,
importance: NotificationImportance.High,
channelShowBadge: true,
),
],
);
await FirebaseMessaging.instance.setAutoInitEnabled(true);
// Firebase Dynamic Links
runApp(MyApp(page: initialRoute));
}
@pragma('vm:entry-point')
Future handleNotification(RemoteMessage message) async {
Logger().d('Remote Message ${message.toString()}');
Logger().d('Remote Message ${message.data.toString()}');
String? title = message.data['sender_name'] ?? 'Yeni Mesaj';
String? body = message.data['body'];
String channelKey = message.data['channel_key'] ?? 'default_channel';
String? id = message.data['id'] ?? '0';
if (channelKey == 'call_channel') {
// Clear any existing calls first
await FlutterCallkitIncoming.endAllCalls();
final uuid = const Uuid().v4();
final params = CallKitParams(
id: uuid,
nameCaller: title,
appName: 'Legalis',
avatar: message.data['caller_avatar'] ?? '',
handle: body ?? '',
type: 0,
duration: 30000,
textAccept: Platform.isIOS ? 'Accept' : 'Qəbul et',
textDecline: Platform.isIOS ? 'Decline' : 'Rədd et',
extra: {
...message.data,
'timestamp': DateTime.now().toIso8601String(),
'uuid': uuid, // Add unique identifier
},
headers: {},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: '',
actionColor: '#4CAF50',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call"),
ios: const IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: false,
maximumCallGroups: 1, // Limit to 1 call group
maximumCallsPerCallGroup: 1,
audioSessionMode: 'voicechat',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
),
);
try {
await FlutterCallkitIncoming.showCallkitIncoming(params);
} catch (e) {
Logger().e('Error showing CallKit: $e');
}
} else if (channelKey == 'message_channel') {
if (AppUtil.connectionUserId != id) {
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: getUniqueNotificationId(),
channelKey: channelKey,
color: Colors.blue,
title: title,
body: body,
category: NotificationCategory.Message,
backgroundColor: Colors.blue,
payload: {'message-api-id': id, 'username': title}),
actionButtons: [
NotificationActionButton(
key: 'READ',
label: 'Read Message',
color: Colors.green,
),
NotificationActionButton(
key: 'DISMISS',
label: 'Dismiss',
color: Colors.red,
),
],
localizations: {
// Azərbaycanca
'az': NotificationLocalization(buttonLabels: {
'READ': 'Mesajı oxu',
'DISMISS': 'İmtina et',
}),
// EN
'en': NotificationLocalization(
buttonLabels: {
'READ': 'Read Message',
'DISMISS': 'Dismiss',
},
),
// Rus
'ru': NotificationLocalization(
buttonLabels: {
'READ': 'Прочитать сообщение',
'DISMISS': 'Отклонить',
},
),
},
);
}
}
AwesomeNotifications().setListeners(
onActionReceivedMethod: (ReceivedAction receivedAction) async {
AppUtil.init();
AppUtil.setNotification(receivedAction.id);
NotificationController.onActionReceivedMethod(receivedAction);
},
onNotificationCreatedMethod:
(ReceivedNotification receivedNotification) async {
AppUtil.setNotification(receivedNotification.id);
NotificationController.onNotificationCreatedMethod(receivedNotification);
},
onNotificationDisplayedMethod:
(ReceivedNotification receivedNotification) async {
NotificationController.onNotificationDisplayedMethod(
receivedNotification);
},
onDismissActionReceivedMethod: (ReceivedAction receivedAction) async {
NotificationController.onDismissActionReceivedMethod(receivedAction);
},
);
}
@pragma('vm:entry-point')
Future _initCallKitListener() async {
try {
FlutterCallkitIncoming.onEvent.listen((event) async {
if (event?.event == null || event?.body == null) return;
Logger().i('CallKit Event: ${event?.event}, Body: ${event?.body}');
final data = event?.body;
if (data == null) return;
switch (event?.event) {
case Event.actionCallAccept:
if (AppUtil.activeCallUser) {
Logger().d('Call already active, ignoring accept action');
return;
}
AppUtil.activeCallUser = true;
if (navigatorKey.currentState == null) {
await Future.delayed(const Duration(seconds: 2));
}
// Navigate to CallPage and remove all previous routes
await navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => CallPage(
userName: data['nameCaller'],
callId: data['extra']?['callId'],
recieverId: data['extra']?['userId'] ?? '',
accep: true,
),
),
(route) => false, // Remove all previous routes
);
break;
case Event.actionCallDecline:
await AppUtil.endCallAsync(callId: data['extra']?['callId']);
await FlutterCallkitIncoming.endAllCalls();
break;
case Event.actionCallEnded:
if (AppUtil.activeCallUser) {
await AppUtil.endCallAsync(callId: data['extra']?['callId']);
await FlutterCallkitIncoming.endAllCalls();
AppUtil.activeCallUser = false;
}
break;
case Event.actionCallIncoming:
AppUtil.activeCallUser = false;
break;
default:
break;
}
});
} catch (e, stackTrace) {
Logger().e("Error in call listener", error: e, stackTrace: stackTrace);
}
}
// ignore: must_be_immutable
class MyApp extends StatefulWidget {
final Widget? page;
const MyApp({super.key, this.page});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void initState() {
super.initState();
// Remove _listenerCallKit call since we already initialized it in main()
// _listenerCallKit();
AwesomeNotifications().setListeners(
onActionReceivedMethod: NotificationController.onActionReceivedMethod,
onNotificationCreatedMethod:
NotificationController.onNotificationCreatedMethod,
onNotificationDisplayedMethod:
NotificationController.onNotificationDisplayedMethod,
onDismissActionReceivedMethod:
NotificationController.onDismissActionReceivedMethod);
}
@override
Widget build(BuildContext context) {
// Fbs
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MessageListProvider()),
ChangeNotifierProvider(create: (_) => ChatDetailProvider()),
ChangeNotifierProvider(create: (_) => LawyerReportProvider()),
ChangeNotifierProvider(create: (_) => UserServiceProvider()),
ChangeNotifierProvider(create: (_) => CallProvider()),
ChangeNotifierProvider(create: (_) => CallAcceptProvider()),
ChangeNotifierProvider(create: (_) => NavigationProvider()),
ChangeNotifierProvider(create: (_) => LocalizationProvider()),
ChangeNotifierProvider(create: (_) => CommunicationProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => CategoryProvider()),
ChangeNotifierProvider(create: (_) => LanguageProvider()),
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: Consumer(
builder: (context, provider, child) {
return MaterialApp(
onGenerateRoute: (settings) {
Logger().i('Route: ${settings}');
},
title: 'Legalis',
navigatorKey: navigatorKey,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
locale: provider.locale,
supportedLocales: L10n.support,
theme: ThemeData(
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Color(0xFF000E2B)),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
fontFamily: 'SF-Pro-Display',
primarySwatch: Colors.green,
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF000E2B),
elevation: 0,
iconTheme: IconThemeData(color: Colors.white),
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w700,
),
),
scaffoldBackgroundColor: Colors.white,
),
home: widget.page ?? const SplashPage(),
routes: {
'/login': (context) => const LoginScreen(),
'/home': (context) => const HomePageView(),
'/messagePage': (context) => const MessagePage(),
'/profilePage': (context) => const ProfilePage(),
'/callHistory': (context) => const CallHistory(),
'/mainWrapper': (context) => MainWrapper(),
},
);
},
),
);
}
}
Mobile version