feat(ui): unify design system to Twitter/X style across screens and improve message handling
Redesign multiple screens and components from card-based responsive design to Twitter/X flat design: - UserProfileHeader: adopt Twitter/X style with larger icons, simplified buttons, and block functionality - AppBackButton: update to chevron-left icon with larger size, remove background styling - Update SettingsScreen, EditProfileScreen, and UserProfileScreen with flat layout approach - ConversationListRow: convert from bordered cards to flat list rows with updated typography Improve message system with WebSocket notification handling: - Add real-time system unread count updates via WebSocket - Track lastSystemMessageAt for accurate system message timestamps - Add notification deduplication and read-status filtering - Combine system and conversation unread counts in background sync - Clear system notifications from notification center on mark as read
This commit is contained in:
@@ -51,14 +51,25 @@ TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => {
|
||||
*/
|
||||
async function syncMessages(): Promise<void> {
|
||||
try {
|
||||
// 同步未读数
|
||||
// 同步会话未读数
|
||||
const unreadData = await messageService.getUnreadCount();
|
||||
const totalUnread = unreadData?.total_unread_count ?? 0;
|
||||
|
||||
// 更新前台服务通知
|
||||
await backgroundSyncManager.updateNotification(totalUnread);
|
||||
// 同步系统消息未读数
|
||||
let systemUnread = 0;
|
||||
try {
|
||||
const sysUnreadData = await messageService.getSystemUnreadCount();
|
||||
systemUnread = sysUnreadData?.unread_count ?? 0;
|
||||
} catch {
|
||||
// 忽略系统未读获取失败
|
||||
}
|
||||
|
||||
console.log('[BackgroundService] 同步完成, 未读数:', totalUnread);
|
||||
const combinedTotal = totalUnread + systemUnread;
|
||||
|
||||
// 更新前台服务通知
|
||||
await backgroundSyncManager.updateNotification(combinedTotal);
|
||||
|
||||
console.log('[BackgroundService] 同步完成, 会话未读:', totalUnread, '系统未读:', systemUnread);
|
||||
} catch (error) {
|
||||
console.error('[BackgroundService] 同步失败:', error);
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ export interface WSNotificationMessage {
|
||||
category?: MessageCategory;
|
||||
system_type?: SystemMessageType;
|
||||
extra_data?: SystemMessageExtraData;
|
||||
is_read?: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
@@ -614,6 +615,12 @@ class WebSocketService {
|
||||
type: 'notification',
|
||||
id: String(payload.id ?? ''),
|
||||
content: payload.content || '',
|
||||
sender_id: payload.sender_id,
|
||||
receiver_id: payload.receiver_id,
|
||||
category: payload.category,
|
||||
system_type: payload.system_type,
|
||||
extra_data: payload.extra_data,
|
||||
is_read: !!payload.is_read,
|
||||
created_at: payload.created_at || new Date().toISOString(),
|
||||
};
|
||||
this.emit('notification', m);
|
||||
|
||||
@@ -54,6 +54,8 @@ class SystemNotificationService {
|
||||
private isInitialized: boolean = false;
|
||||
private appStateSubscription: any = null;
|
||||
private currentAppState: AppStateStatus = 'active';
|
||||
private shownNotificationIds: Set<string> = new Set();
|
||||
private static readonly MAX_DEDUP_SIZE = 500;
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
if (this.isInitialized) {
|
||||
@@ -174,6 +176,32 @@ class SystemNotificationService {
|
||||
if (!getNotificationPreferencesSync().pushEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const msgId = String(message.id || '');
|
||||
if (msgId && this.shownNotificationIds.has(msgId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('is_read' in message && (message as any).is_read) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msgId) {
|
||||
this.shownNotificationIds.add(msgId);
|
||||
if (this.shownNotificationIds.size > SystemNotificationService.MAX_DEDUP_SIZE) {
|
||||
const toRemove: string[] = [];
|
||||
let count = 0;
|
||||
for (const id of this.shownNotificationIds) {
|
||||
if (count >= SystemNotificationService.MAX_DEDUP_SIZE / 2) break;
|
||||
toRemove.push(id);
|
||||
count++;
|
||||
}
|
||||
for (const id of toRemove) {
|
||||
this.shownNotificationIds.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在后台时显示通知,前台时不显示(用户正在使用应用,可以直接看到消息)
|
||||
if (this.currentAppState !== 'active') {
|
||||
// 判断是否是聊天消息(通过 segments 字段)
|
||||
|
||||
Reference in New Issue
Block a user