/** * 系统通知服务 * 使用 expo-notifications 处理原生系统通知 * 支持本地通知,类似 QQ 在通知栏显示消息 */ import * as Notifications from 'expo-notifications'; import { Platform, AppState, AppStateStatus } from 'react-native'; import type { WSChatMessage, WSNotificationMessage, WSAnnouncementMessage } from './websocketService'; import { extractTextFromSegments } from '../types/dto'; // 通知渠道配置 const CHANNEL_ID = 'default'; const CHANNEL_NAME = '默认通知'; const CHANNEL_DESCRIPTION = '接收系统消息、互动通知等'; // 通知类型 export type AppNotificationType = | 'like_post' | 'like_comment' | 'like_reply' | 'favorite_post' | 'comment' | 'reply' | 'follow' | 'mention' | 'system' | 'announcement' | 'announce' | 'chat'; // 获取通知标题 export function getNotificationTitle(type: AppNotificationType, operatorName?: string): string { const titles: Record = { like_post: '有人赞了你的帖子', like_comment: '有人赞了你的评论', like_reply: '有人赞了你的回复', favorite_post: '有人收藏了你的帖子', comment: '新评论', reply: '新回复', follow: '新粉丝', mention: '@提及', system: '系统通知', announcement: '公告', announce: '公告', chat: '新消息', }; return titles[type] || '新通知'; } class SystemNotificationService { private isInitialized: boolean = false; private appStateSubscription: any = null; private currentAppState: AppStateStatus = 'active'; async initialize(): Promise { if (this.isInitialized) { return true; } try { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.log('[SystemNotification] 通知权限未授权'); return false; } // 设置 notification handler,确保前台也能显示通知、播放声音和震动 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, shouldShowBanner: true, shouldShowList: true, }), }); if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync(CHANNEL_ID, { name: CHANNEL_NAME, importance: Notifications.AndroidImportance.HIGH, // HIGH 优先级足以触发震动 vibrationPattern: [0, 300, 200, 300], // 等待0ms,震动300ms,停止200ms,震动300ms lightColor: '#FF6B35', enableVibrate: true, // 启用震动 enableLights: true, // 启用呼吸灯 }); } this.currentAppState = AppState.currentState; this.appStateSubscription = AppState.addEventListener('change', (nextAppState) => { this.currentAppState = nextAppState; }); this.isInitialized = true; console.log('[SystemNotification] 通知服务初始化成功'); return true; } catch (error) { console.error('[SystemNotification] 初始化失败:', error); return false; } } async showNotification(options: { title: string; body: string; data?: Record; type: AppNotificationType; }): Promise { if (!this.isInitialized) { await this.initialize(); } try { // 使用 scheduleNotificationAsync 立即显示通知 // 配合 setNotificationHandler 确保前台也能显示 // 构建通知内容 const content: Notifications.NotificationContentInput = { title: options.title, body: options.body, data: options.data as Record | undefined, // 显式设置震动(仅 Android 生效) ...(Platform.OS === 'android' ? { vibrationPattern: [0, 300, 200, 300], } : {}), }; const notificationId = await Notifications.scheduleNotificationAsync({ content, trigger: null, // null 表示立即显示 }); console.log('[SystemNotification] 通知已显示:', notificationId); return notificationId; } catch (error) { console.error('[SystemNotification] 显示通知失败:', error); return null; } } async showChatNotification(message: WSChatMessage): Promise { const body = extractTextFromSegments(message.segments); return this.showNotification({ title: '新消息', body, data: { type: 'chat', conversationId: message.conversation_id, messageId: String(message.id), senderId: message.sender_id, }, type: 'chat', }); } async showWSNotification(message: WSNotificationMessage | WSAnnouncementMessage): Promise { const type = message.system_type as AppNotificationType; const title = getNotificationTitle(type); const body = message.content; return this.showNotification({ title, body, data: { type: message.type, id: String(message.id), senderId: message.sender_id, receiverId: message.receiver_id, systemType: message.system_type, extraData: JSON.stringify(message.extra_data || {}), }, type, }); } async handleWSMessage(message: WSChatMessage | WSNotificationMessage | WSAnnouncementMessage): Promise { console.log('[SystemNotification] handleWSMessage 被调用, 当前AppState:', this.currentAppState); // 仅在后台时显示通知,前台时不显示(用户正在使用应用,可以直接看到消息) if (this.currentAppState !== 'active') { // 判断是否是聊天消息(通过 segments 字段) if ('segments' in message) { const chatMsg = message as WSChatMessage; const body = extractTextFromSegments(chatMsg.segments); console.log('[SystemNotification] 后台模式 - 显示聊天通知:', chatMsg.id, body); await this.showChatNotification(chatMsg); } else { const notifMsg = message as WSNotificationMessage | WSAnnouncementMessage; console.log('[SystemNotification] 后台模式 - 显示系统通知:', notifMsg.id, notifMsg.content); await this.showWSNotification(notifMsg); } } else { console.log('[SystemNotification] 前台模式 - 不显示通知'); } } async clearAllNotifications(): Promise { await Notifications.dismissAllNotificationsAsync(); } async clearBadge(): Promise { await Notifications.setBadgeCountAsync(0); } async setBadgeCount(count: number): Promise { await Notifications.setBadgeCountAsync(count); } cleanup(): void { if (this.appStateSubscription) { this.appStateSubscription.remove(); this.appStateSubscription = null; } this.isInitialized = false; } getAppState(): AppStateStatus { return this.currentAppState; } } export const systemNotificationService = new SystemNotificationService();