feat(message): add conversation notification mute feature
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 57s
Frontend CI / ota-android (push) Successful in 10m53s
Frontend CI / build-android-apk (push) Failing after 1h12m12s

Add notification mute functionality for conversations with the following changes:

- Add notification_muted field to ConversationResponse and related DTOs
- Add notificationMutedMap to message store for tracking mute state
- Add setConversationNotificationMuted API method
- Integrate mute toggle in GroupInfoScreen and PrivateChatInfoScreen
- Skip system notifications for muted conversations
- Suppress vibration for muted conversations in WS handler
- Clean up selected mentions when @mentions are removed from text

Chore changes:
- Update PostCard bookmark icon from 'bookmark' to 'star'
- Refine card styles across AppsScreen, MaterialsScreen, and SubjectMaterialsScreen
- Improve week selector scrolling to center selected week in ScheduleScreen
This commit is contained in:
lafay
2026-04-25 21:23:22 +08:00
parent de0afa93a1
commit 5fa5403d6a
15 changed files with 252 additions and 101 deletions

View File

@@ -50,6 +50,7 @@ import { blurActiveElement } from '../../infrastructure/platform';
import { firstRouteParam } from '../../navigation/paramUtils';
import * as hrefs from '../../navigation/hrefs';
import { messageManager } from '../../stores/message';
import { useMessageStore } from '../../stores/message/store';
import { groupManager } from '../../stores/group';
import MutualFollowSelectorModal from './components/MutualFollowSelectorModal';
import { AppBackButton } from '../../components/common';
@@ -95,6 +96,9 @@ const GroupInfoScreen: React.FC = () => {
const [loading, setLoading] = useState(true);
const [isPinned, setIsPinned] = useState(false);
const [pinLoading, setPinLoading] = useState(false);
const isNotificationMuted = useMessageStore(state =>
conversationId ? (state.notificationMutedMap.get(conversationId) || false) : false
);
// 编辑模态框状态
const [editModalVisible, setEditModalVisible] = useState(false);
@@ -212,6 +216,16 @@ const GroupInfoScreen: React.FC = () => {
}
};
// 设置会话免打扰
const handleToggleNotificationMuted = async () => {
if (!conversationId) return;
try {
await messageManager.toggleNotificationMuted(conversationId, !isNotificationMuted);
} catch (error: any) {
Alert.alert('错误', error?.message || '设置免打扰失败');
}
};
// 打开编辑模态框
const openEditModal = () => {
if (group) {
@@ -799,6 +813,24 @@ const GroupInfoScreen: React.FC = () => {
thumbColor={isPinned ? colors.primary.main : colors.background.paper}
/>
</View>
<View style={styles.settingItem}>
<View style={styles.settingIconContainer}>
<MaterialCommunityIcons name="bell-off-outline" size={20} color={colors.primary.main} />
</View>
<View style={styles.settingContent}>
<Text variant="body"></Text>
<Text variant="caption" color={colors.text.secondary}>
{conversationId ? (isNotificationMuted ? '已开启' : '未开启') : '请从聊天页面进入后设置'}
</Text>
</View>
<Switch
value={isNotificationMuted}
onValueChange={handleToggleNotificationMuted}
disabled={!conversationId}
trackColor={{ false: colors.divider, true: colors.primary.light }}
thumbColor={isNotificationMuted ? colors.primary.main : colors.background.paper}
/>
</View>
</View>
</View>

View File

@@ -23,6 +23,7 @@ import { messageService } from '@/services/message';
import { ApiError } from '@/services/core';
import { messageRepository, conversationRepository } from '@/database';
import { messageManager } from '../../stores/message';
import { useMessageStore } from '../../stores/message/store';
import { userManager } from '../../stores/user';
import { Avatar, Text, Button, Loading, Divider } from '../../components/common';
import { User } from '../../types';
@@ -51,7 +52,7 @@ const PrivateChatInfoScreen: React.FC = () => {
const [isBlocked, setIsBlocked] = useState(false);
// 聊天设置状态
const [isMuted, setIsMuted] = useState(false);
const isMuted = useMessageStore(state => state.notificationMutedMap.get(conversationId) || false);
const [isPinned, setIsPinned] = useState(false);
// 加载用户信息
@@ -96,15 +97,12 @@ const PrivateChatInfoScreen: React.FC = () => {
loadChatSettings();
}, [loadUserInfo, loadChatSettings]);
// 切换免打扰仅本地状态实际需要API支持
// 切换免打扰
const toggleMute = async () => {
try {
const newValue = !isMuted;
setIsMuted(newValue);
// TODO: 调用API更新免打扰状态
// await conversationService.updateConversationMute(conversationId, newValue);
await messageManager.toggleNotificationMuted(conversationId, newValue);
} catch (error) {
setIsMuted(!isMuted);
Alert.alert('错误', '设置免打扰失败');
}
};

View File

@@ -705,8 +705,25 @@ export const useChatScreen = (props?: ChatScreenProps) => {
} else {
setActivePanel('none');
}
if (selectedMentions.length > 0) {
const stillPresent = selectedMentions.filter(userId => {
const member = groupMembers.find(m => m.user_id === userId);
const nickname = member?.nickname || member?.user?.nickname;
if (!nickname) return false;
const escapedName = nickname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`@${escapedName}(\\s|$)`, 'g').test(text);
});
if (stillPresent.length !== selectedMentions.length) {
setSelectedMentions(stillPresent);
}
}
if (mentionAll && !text.includes('@所有人')) {
setMentionAll(false);
}
}
}, [isGroupChat]);
}, [isGroupChat, selectedMentions, mentionAll, groupMembers]);
// 【改造】获取发送者信息(群聊)- 优先从消息的 sender 字段获取
const getSenderInfo = useCallback((senderId: string): SenderInfo => {