Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
230
src/services/systemNotificationService.ts
Normal file
230
src/services/systemNotificationService.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 系统通知服务
|
||||
* 使用 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<AppNotificationType, string> = {
|
||||
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<boolean> {
|
||||
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<string, string | number | object>;
|
||||
type: AppNotificationType;
|
||||
}): Promise<string | null> {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用 scheduleNotificationAsync 立即显示通知
|
||||
// 配合 setNotificationHandler 确保前台也能显示
|
||||
|
||||
// 构建通知内容
|
||||
const content: Notifications.NotificationContentInput = {
|
||||
title: options.title,
|
||||
body: options.body,
|
||||
data: options.data as Record<string, string | number | object> | 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<string | null> {
|
||||
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<string | null> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await Notifications.dismissAllNotificationsAsync();
|
||||
}
|
||||
|
||||
async clearBadge(): Promise<void> {
|
||||
await Notifications.setBadgeCountAsync(0);
|
||||
}
|
||||
|
||||
async setBadgeCount(count: number): Promise<void> {
|
||||
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();
|
||||
Reference in New Issue
Block a user