feat(message): add conversation notification mute feature
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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('错误', '设置免打扰失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user