Browse Source

feat(notification): 添加本地通知功能支持

- 在 AndroidManifest.xml 中添加 RECEIVE_BOOT_COMPLETED 权限和启动器徽章权限
- 为应用启动器 Activity 添加 showWhenLocked 和 turnScreenOn 属性
- 集成 flutter_local_notifications 插件并配置 Android 和 iOS 平台设置
- 创建 LocalNotificationService 服务处理本地通知的初始化和显示
- 实现消息类型判断和内容解析功能
- 添加视频通话通知的特殊处理逻辑
- 支持通知点击跳转到对应聊天页面
- 在 IMManager 中集成本地通知服务
- 优化 iOS 平台通知权限申请
- 配置 Podfile 依赖并更新原生项目设置
master
Jolie 3 months ago
parent
commit
917316d76a
6 changed files with 358 additions and 12 deletions
  1. 12
      android/app/src/main/AndroidManifest.xml
  2. 18
      ios/Podfile.lock
  3. 8
      ios/Runner.xcodeproj/project.pbxproj
  4. 3
      ios/Runner/AppDelegate.swift
  5. 14
      lib/im/im_manager.dart
  6. 315
      lib/service/local_notification_service.dart

12
android/app/src/main/AndroidManifest.xml

@ -38,6 +38,7 @@
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" /> <uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" />
<uses-permission android:name="com.huawei.android.launcher.permission.READ_SETTINGS" /> <uses-permission android:name="com.huawei.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS" /> <uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application <application
android:label="趣恋恋" android:label="趣恋恋"
android:name="${applicationName}" android:name="${applicationName}"
@ -48,6 +49,8 @@
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity="" android:taskAffinity=""
android:showWhenLocked="true"
android:turnScreenOn="true"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -85,6 +88,15 @@
<action android:name="${applicationId}.ACTION_INSTALL_COMPLETE"/> <action android:name="${applicationId}.ACTION_INSTALL_COMPLETE"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and

18
ios/Podfile.lock

@ -55,6 +55,8 @@ PODS:
- AgoraRtm/RtmKit (= 2.2.5) - AgoraRtm/RtmKit (= 2.2.5)
- AgoraRtm/RtmBasic (2.2.5) - AgoraRtm/RtmBasic (2.2.5)
- AgoraRtm/RtmKit (2.2.5) - AgoraRtm/RtmKit (2.2.5)
- app_badge_plus (1.2.6):
- Flutter
- app_settings (5.1.1): - app_settings (5.1.1):
- Flutter - Flutter
- audioplayers_darwin (0.0.1): - audioplayers_darwin (0.0.1):
@ -63,6 +65,8 @@ PODS:
- camera_avfoundation (0.0.1): - camera_avfoundation (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (2.4.3): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- fluwx (0.0.1): - fluwx (0.0.1):
@ -132,10 +136,12 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- agora_rtc_engine (from `.symlinks/plugins/agora_rtc_engine/ios`) - agora_rtc_engine (from `.symlinks/plugins/agora_rtc_engine/ios`)
- agora_rtm (from `.symlinks/plugins/agora_rtm/ios`) - agora_rtm (from `.symlinks/plugins/agora_rtm/ios`)
- app_badge_plus (from `.symlinks/plugins/app_badge_plus/ios`)
- app_settings (from `.symlinks/plugins/app_settings/ios`) - app_settings (from `.symlinks/plugins/app_settings/ios`)
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`) - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`) - fluwx (from `.symlinks/plugins/fluwx/ios`)
- im_flutter_sdk_ios (from `.symlinks/plugins/im_flutter_sdk_ios/ios`) - im_flutter_sdk_ios (from `.symlinks/plugins/im_flutter_sdk_ios/ios`)
@ -172,6 +178,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/agora_rtc_engine/ios" :path: ".symlinks/plugins/agora_rtc_engine/ios"
agora_rtm: agora_rtm:
:path: ".symlinks/plugins/agora_rtm/ios" :path: ".symlinks/plugins/agora_rtm/ios"
app_badge_plus:
:path: ".symlinks/plugins/app_badge_plus/ios"
app_settings: app_settings:
:path: ".symlinks/plugins/app_settings/ios" :path: ".symlinks/plugins/app_settings/ios"
audioplayers_darwin: audioplayers_darwin:
@ -180,6 +188,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/camera_avfoundation/ios" :path: ".symlinks/plugins/camera_avfoundation/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
fluwx: fluwx:
@ -220,21 +230,23 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS: SPEC CHECKSUMS:
agora_rtc_engine: da45ea14c402317c9e22fdb9ffe504d9a1acbe68
agora_rtc_engine: b71d04f9c9200b28e89559a154e53ca3366301f4
agora_rtm: 171fb2ddc9da71ca2c30780118a5bc6d005aed49 agora_rtm: 171fb2ddc9da71ca2c30780118a5bc6d005aed49
AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08 AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08
AgoraIrisRTC_iOS: eab58c126439adf5ec99632828a558ea216860da AgoraIrisRTC_iOS: eab58c126439adf5ec99632828a558ea216860da
AgoraIrisRTM_iOS: 8db20697f717de6e1db5dd4c842432889f284220 AgoraIrisRTM_iOS: 8db20697f717de6e1db5dd4c842432889f284220
AgoraRtcEngine_iOS: 97e2398a2addda9057815a2a583a658e36796ff6 AgoraRtcEngine_iOS: 97e2398a2addda9057815a2a583a658e36796ff6
AgoraRtm: d92cdfca825f3e6817c315d7dd6403742494f7ca AgoraRtm: d92cdfca825f3e6817c315d7dd6403742494f7ca
app_badge_plus: d13997a69d78ae6cafd6353b17d4033897b54f4c
app_settings: 58017cd26b604ae98c3e65acbdd8ba173703cc82 app_settings: 58017cd26b604ae98c3e65acbdd8ba173703cc82
audioplayers_darwin: 4027b33a8f471d996c13f71cb77f0b1583b5d923 audioplayers_darwin: 4027b33a8f471d996c13f71cb77f0b1583b5d923
camera_avfoundation: 281867ff09f1da66f031a184ecfbc6f2e625c9f5 camera_avfoundation: 281867ff09f1da66f031a184ecfbc6f2e625c9f5
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
fluwx: 95a2274c23418c1098940bd00dcfe7c975bb0550 fluwx: 95a2274c23418c1098940bd00dcfe7c975bb0550
HyphenateChat: 4523c7fb2075771c49a2c492b31544d6cc82ff50 HyphenateChat: 4523c7fb2075771c49a2c492b31544d6cc82ff50
im_flutter_sdk_ios: 6570b291875ab7b3016e8d21fc0ef58ab347cd38
im_flutter_sdk_ios: f9368064a0965912cf523284ab588f242a6cb8f2
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
iris_method_channel: 4e3fe7fe6b2db1b84feae956e98ce1479402627a iris_method_channel: 4e3fe7fe6b2db1b84feae956e98ce1479402627a
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
@ -252,7 +264,7 @@ SPEC CHECKSUMS:
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1 video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4 webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
WechatOpenSDK-XCFramework: b072030c9eeee91dfff1856a7846f70f7b9a88ed
PODFILE CHECKSUM: 9f921d5f3a6aff46b310102d923e8942abc7114d PODFILE CHECKSUM: 9f921d5f3a6aff46b310102d923e8942abc7114d

8
ios/Runner.xcodeproj/project.pbxproj

@ -296,14 +296,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@ -317,14 +313,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";

3
ios/Runner/AppDelegate.swift

@ -8,6 +8,9 @@ import UIKit
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
} }

14
lib/im/im_manager.dart

@ -19,6 +19,7 @@ import '../widget/message/message_notification_dialog.dart';
import '../widget/message/video_call_invite_dialog.dart'; import '../widget/message/video_call_invite_dialog.dart';
import '../pages/message/video_call_page.dart'; import '../pages/message/video_call_page.dart';
import '../controller/message/call_manager.dart'; import '../controller/message/call_manager.dart';
import '../service/local_notification_service.dart';
// //
class _NotificationMessage { class _NotificationMessage {
@ -67,6 +68,9 @@ class IMManager {
// //
bool _isShowingNotification = false; bool _isShowingNotification = false;
//
final LocalNotificationService _localNotificationService = LocalNotificationService.instance;
IMManager._internal() { IMManager._internal() {
print('IMManager instance created'); print('IMManager instance created');
} }
@ -137,6 +141,8 @@ class IMManager {
// //
_registerListeners(); _registerListeners();
//
await _localNotificationService.initialize();
_initialized = true; _initialized = true;
debugPrint('✅ IM 初始化成功'); debugPrint('✅ IM 初始化成功');
@ -214,10 +220,16 @@ class IMManager {
if (message.direction == MessageDirection.RECEIVE && message.onlineState) { if (message.direction == MessageDirection.RECEIVE && message.onlineState) {
_parseUserInfoFromMessageExt(message); _parseUserInfoFromMessageExt(message);
// //
// APP
// APP
final lifecycleState = WidgetsBinding.instance.lifecycleState; final lifecycleState = WidgetsBinding.instance.lifecycleState;
if (lifecycleState == AppLifecycleState.resumed) { if (lifecycleState == AppLifecycleState.resumed) {
_checkAndShowNotificationDialog(message); _checkAndShowNotificationDialog(message);
} else {
// APP
_localNotificationService.showNotification(
message,
activeChatUserIds: _activeChatControllers.keys.toSet(),
);
} }
} }
} }

315
lib/service/local_notification_service.dart

@ -0,0 +1,315 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
import '../pages/message/chat_page.dart';
import '../controller/message/conversation_controller.dart';
///
class LocalNotificationService {
//
static final LocalNotificationService _instance = LocalNotificationService._internal();
factory LocalNotificationService() => _instance;
static LocalNotificationService get instance => _instance;
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
bool _notificationsInitialized = false;
static int _notificationId = 0; // ID计数器
LocalNotificationService._internal();
///
Future<void> initialize() async {
if (_notificationsInitialized) {
return;
}
try {
// Android
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// iOS
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
//
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
//
final bool? initialized = await _localNotifications.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onNotificationTapped,
);
if (initialized == true) {
_notificationsInitialized = true;
if (Get.isLogEnable) {
Get.log('✅ [LocalNotificationService] 本地通知初始化成功');
}
} else {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 本地通知初始化失败');
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('❌ [LocalNotificationService] 本地通知初始化异常: $e');
}
}
}
///
void _onNotificationTapped(NotificationResponse response) {
try {
final payload = response.payload;
if (payload != null && payload.isNotEmpty) {
// payload : "type|fromId"
final parts = payload.split('|');
if (parts.length >= 2) {
final fromId = parts[1];
//
Get.to(() => ChatPage(
userId: fromId,
userData: null,
));
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 处理通知点击失败: $e');
}
}
}
///
String _getMessageContent(EMMessage message) {
try {
if (message.body.type == MessageType.TXT) {
final body = message.body as EMTextMessageBody;
return body.content ?? '';
} else if (message.body.type == MessageType.IMAGE) {
return '[图片]';
} else if (message.body.type == MessageType.VOICE) {
return '[语音]';
} else if (message.body.type == MessageType.VIDEO) {
return '[视频]';
} else if (message.body.type == MessageType.FILE) {
return '[文件]';
} else if (message.body.type == MessageType.LOCATION) {
return '[位置]';
} else if (message.body.type == MessageType.CUSTOM) {
final body = message.body as EMCustomMessageBody;
if (body.event == 'live_room_invite') {
return '[分享房间]';
} else if (body.event == 'gift') {
return '[礼物]';
} else if (body.event == 'call') {
//
try {
if (body.params != null) {
final callType = body.params!['callType'] ?? 'voice';
if (callType == 'video') {
return '[视频通话]';
} else if (callType == 'voice') {
return '[语音通话]';
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 解析通话消息类型失败: $e');
}
}
return '[通话消息]';
}
return '[自定义消息]';
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 获取消息内容失败: $e');
}
}
return '';
}
/// APP
Future<void> showNotification(EMMessage message, {Set<String>? activeChatUserIds}) async {
if (!_notificationsInitialized) {
await initialize();
}
try {
// ID
final fromId = message.from;
if (fromId == null || fromId.isEmpty) {
return;
}
//
if (activeChatUserIds != null && activeChatUserIds.contains(fromId)) {
//
return;
}
//
String messageContent = _getMessageContent(message);
if (messageContent.isEmpty) {
return;
}
//
Map<String, dynamic>? attributes;
try {
attributes = message.attributes;
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 无法访问消息扩展字段: $e');
}
return;
}
String? nickName;
String? avatarUrl;
if (attributes != null) {
//
nickName = attributes['sender_nickName'] as String?;
avatarUrl = attributes['sender_avatarUrl'] as String?;
}
// ConversationController
if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) {
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
final cachedUserInfo = conversationController.getCachedUserInfo(fromId);
if (cachedUserInfo != null) {
nickName = nickName ?? cachedUserInfo.nickName;
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 从ConversationController获取用户信息失败: $e');
}
}
}
// 使
final finalNickName = nickName ?? fromId;
//
Map<String, dynamic>? callInfo;
String? callType;
String? callStatus;
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'call' && customBody.params != null) {
final params = customBody.params!;
callType = params['callType'] ?? 'voice';
callStatus = params['callStatus'] ?? 'missed';
callInfo = {
'callType': callType,
'callStatus': callStatus,
};
}
}
// missed calling
if (callInfo != null && callType != null && callStatus != null) {
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) {
//
final notificationId = _notificationId++;
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'video_call_channel',
'视频通话',
channelDescription: '视频通话通知',
importance: Importance.max,
priority: Priority.high,
showWhen: true,
);
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
await _localNotifications.show(
notificationId,
'视频通话',
'$finalNickName 邀请您进行视频通话',
platformChannelSpecifics,
payload: 'video_call|$fromId',
);
if (Get.isLogEnable) {
Get.log('📱 [LocalNotificationService] 显示视频通话本地通知: $fromId');
}
return;
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [LocalNotificationService] 处理视频通话通知失败: $e');
}
}
//
final notificationId = _notificationId++;
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'message_channel',
'消息通知',
channelDescription: '新消息通知',
importance: Importance.high,
priority: Priority.high,
showWhen: true,
);
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics,
);
await _localNotifications.show(
notificationId,
finalNickName,
messageContent,
platformChannelSpecifics,
payload: 'message|$fromId',
);
if (Get.isLogEnable) {
Get.log('📱 [LocalNotificationService] 显示本地通知: $fromId, $messageContent');
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('❌ [LocalNotificationService] 显示本地通知失败: $e');
}
}
}
}
Loading…
Cancel
Save