refactor(App, navigation): migrate to Expo Router and clean up navigation structure
- Updated App entry point to utilize Expo Router, simplifying the navigation setup. - Removed legacy navigation components and services to streamline the codebase. - Adjusted package.json to reflect the new entry point and updated dependencies for compatibility. - Enhanced overall application structure by consolidating navigation logic and improving maintainability.
This commit is contained in:
@@ -11,8 +11,15 @@
|
||||
import { AppState, AppStateStatus, Platform } from 'react-native';
|
||||
import * as BackgroundFetch from 'expo-background-fetch';
|
||||
import * as TaskManager from 'expo-task-manager';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { sseService } from './sseService';
|
||||
import {
|
||||
triggerVibration,
|
||||
vibrateOnMessage,
|
||||
setVibrationConfig,
|
||||
getVibrationConfig,
|
||||
setVibrationEnabled,
|
||||
} from './messageVibrationService';
|
||||
export { triggerVibration, vibrateOnMessage, setVibrationConfig, getVibrationConfig, setVibrationEnabled };
|
||||
|
||||
// 后台任务名称
|
||||
const BACKGROUND_FETCH_TASK = 'background-fetch-keepalive';
|
||||
@@ -21,27 +28,8 @@ const REALTIME_KEEPALIVE_TASK = 'realtime-keepalive';
|
||||
// 后台任务间隔(Android 最小 15 分钟,iOS 最小 15 分钟)
|
||||
const BACKGROUND_INTERVAL = 15; // 15 分钟
|
||||
|
||||
// 震动配置
|
||||
interface VibrationConfig {
|
||||
enabled: boolean; // 是否启用震动
|
||||
onChatMessage: boolean; // 私聊消息震动
|
||||
onGroupMessage: boolean; // 群聊消息震动
|
||||
onNotification: boolean; // 系统通知震动
|
||||
style: Haptics.ImpactFeedbackStyle; // 震动样式
|
||||
}
|
||||
|
||||
// 默认震动配置
|
||||
const defaultVibrationConfig: VibrationConfig = {
|
||||
enabled: true,
|
||||
onChatMessage: true,
|
||||
onGroupMessage: true,
|
||||
onNotification: true,
|
||||
style: Haptics.ImpactFeedbackStyle.Light,
|
||||
};
|
||||
|
||||
// 后台服务状态
|
||||
let isInitialized = false;
|
||||
let vibrationConfig: VibrationConfig = { ...defaultVibrationConfig };
|
||||
let appStateSubscription: any = null;
|
||||
|
||||
// 定义后台任务
|
||||
@@ -73,96 +61,13 @@ TaskManager.defineTask(REALTIME_KEEPALIVE_TASK, async () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 触发震动反馈
|
||||
* @param type 震动类型
|
||||
*/
|
||||
export async function triggerVibration(
|
||||
type: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error' = 'light'
|
||||
): Promise<void> {
|
||||
if (!vibrationConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'light':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
break;
|
||||
case 'medium':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
break;
|
||||
case 'heavy':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
||||
break;
|
||||
case 'success':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
break;
|
||||
case 'warning':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
break;
|
||||
case 'error':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[BackgroundService] 震动反馈失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到消息时触发震动
|
||||
* @param messageType 消息类型
|
||||
*/
|
||||
export async function vibrateOnMessage(messageType: 'chat' | 'group_message' | 'notification'): Promise<void> {
|
||||
if (!vibrationConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (messageType) {
|
||||
case 'chat':
|
||||
if (vibrationConfig.onChatMessage) {
|
||||
await triggerVibration('light');
|
||||
}
|
||||
break;
|
||||
case 'group_message':
|
||||
if (vibrationConfig.onGroupMessage) {
|
||||
await triggerVibration('light');
|
||||
}
|
||||
break;
|
||||
case 'notification':
|
||||
if (vibrationConfig.onNotification) {
|
||||
await triggerVibration('medium');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置震动设置
|
||||
*/
|
||||
export function setVibrationConfig(config: Partial<VibrationConfig>): void {
|
||||
vibrationConfig = { ...vibrationConfig, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前震动配置
|
||||
*/
|
||||
export function getVibrationConfig(): VibrationConfig {
|
||||
return { ...vibrationConfig };
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用震动
|
||||
*/
|
||||
export function setVibrationEnabled(enabled: boolean): void {
|
||||
vibrationConfig.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册后台任务
|
||||
*/
|
||||
async function registerBackgroundTasks(): Promise<void> {
|
||||
if (Platform.OS === 'web') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 检查是否已注册
|
||||
const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_FETCH_TASK);
|
||||
@@ -192,6 +97,9 @@ async function registerBackgroundTasks(): Promise<void> {
|
||||
* 取消后台任务
|
||||
*/
|
||||
async function unregisterBackgroundTasks(): Promise<void> {
|
||||
if (Platform.OS === 'web') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK);
|
||||
await BackgroundFetch.unregisterTaskAsync(REALTIME_KEEPALIVE_TASK);
|
||||
@@ -227,6 +135,12 @@ export async function initBackgroundService(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
setupAppStateListener();
|
||||
isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查后台任务状态
|
||||
const status = await BackgroundFetch.getStatusAsync();
|
||||
@@ -275,6 +189,13 @@ export async function checkBackgroundStatus(): Promise<{
|
||||
isInitialized: boolean;
|
||||
registeredTasks: string[];
|
||||
}> {
|
||||
if (Platform.OS === 'web') {
|
||||
return {
|
||||
isAvailable: false,
|
||||
isInitialized,
|
||||
registeredTasks: [],
|
||||
};
|
||||
}
|
||||
const status = await BackgroundFetch.getStatusAsync();
|
||||
const registeredTasks = await TaskManager.getRegisteredTasksAsync();
|
||||
|
||||
|
||||
@@ -27,6 +27,54 @@ import {
|
||||
CursorPaginationResponse,
|
||||
} from '../types/dto';
|
||||
|
||||
function asGroupId(raw: unknown): string {
|
||||
return String(raw);
|
||||
}
|
||||
|
||||
function normalizeGroup(g: GroupResponse): GroupResponse {
|
||||
return { ...g, id: asGroupId(g.id) };
|
||||
}
|
||||
|
||||
function normalizeMember(m: GroupMemberResponse): GroupMemberResponse {
|
||||
return { ...m, group_id: asGroupId(m.group_id) };
|
||||
}
|
||||
|
||||
function normalizeAnnouncement(a: GroupAnnouncementResponse): GroupAnnouncementResponse {
|
||||
return { ...a, group_id: asGroupId(a.group_id) };
|
||||
}
|
||||
|
||||
function normalizeGroupListResponse(r: GroupListResponse): GroupListResponse {
|
||||
return { ...r, list: r.list.map(normalizeGroup) };
|
||||
}
|
||||
|
||||
function normalizeMemberListResponse(r: GroupMemberListResponse): GroupMemberListResponse {
|
||||
return { ...r, list: r.list.map(normalizeMember) };
|
||||
}
|
||||
|
||||
function normalizeAnnouncementListResponse(
|
||||
r: GroupAnnouncementListResponse
|
||||
): GroupAnnouncementListResponse {
|
||||
return { ...r, list: r.list.map(normalizeAnnouncement) };
|
||||
}
|
||||
|
||||
function normalizeGroupCursor(
|
||||
r: CursorPaginationResponse<GroupResponse>
|
||||
): CursorPaginationResponse<GroupResponse> {
|
||||
return { ...r, list: r.list.map(normalizeGroup) };
|
||||
}
|
||||
|
||||
function normalizeMemberCursor(
|
||||
r: CursorPaginationResponse<GroupMemberResponse>
|
||||
): CursorPaginationResponse<GroupMemberResponse> {
|
||||
return { ...r, list: r.list.map(normalizeMember) };
|
||||
}
|
||||
|
||||
function normalizeAnnouncementCursor(
|
||||
r: CursorPaginationResponse<GroupAnnouncementResponse>
|
||||
): CursorPaginationResponse<GroupAnnouncementResponse> {
|
||||
return { ...r, list: r.list.map(normalizeAnnouncement) };
|
||||
}
|
||||
|
||||
// 群组服务类(纯 API 层)
|
||||
// SQLite/内存缓存编排由 groupManager 统一处理
|
||||
class GroupService {
|
||||
@@ -35,27 +83,27 @@ class GroupService {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
return response.data;
|
||||
return normalizeGroupListResponse(response.data);
|
||||
}
|
||||
|
||||
async fetchGroupFromApi(id: number | string): Promise<GroupResponse> {
|
||||
const response = await api.get<GroupResponse>(`/groups/${encodeURIComponent(String(id))}`);
|
||||
return response.data;
|
||||
async fetchGroupFromApi(id: string): Promise<GroupResponse> {
|
||||
const response = await api.get<GroupResponse>(`/groups/${encodeURIComponent(id)}`);
|
||||
return normalizeGroup(response.data);
|
||||
}
|
||||
|
||||
async fetchMembersFromApi(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
page = 1,
|
||||
pageSize = 50
|
||||
): Promise<GroupMemberListResponse> {
|
||||
const response = await api.get<GroupMemberListResponse>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/members`,
|
||||
`/groups/${encodeURIComponent(groupId)}/members`,
|
||||
{
|
||||
page,
|
||||
page_size: pageSize,
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
return normalizeMemberListResponse(response.data);
|
||||
}
|
||||
|
||||
// ==================== 群组管理 ====================
|
||||
@@ -66,7 +114,7 @@ class GroupService {
|
||||
*/
|
||||
async createGroup(data: CreateGroupRequest): Promise<GroupResponse> {
|
||||
const response = await api.post<GroupResponse>('/groups', data);
|
||||
return response.data;
|
||||
return normalizeGroup(response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +129,7 @@ class GroupService {
|
||||
* 获取群组详情
|
||||
* GET /api/v1/groups/:id
|
||||
*/
|
||||
async getGroup(id: number | string): Promise<GroupResponse> {
|
||||
async getGroup(id: string): Promise<GroupResponse> {
|
||||
return this.fetchGroupFromApi(id);
|
||||
}
|
||||
|
||||
@@ -89,27 +137,30 @@ class GroupService {
|
||||
* 获取当前用户在群组中的成员信息
|
||||
* GET /api/v1/groups/:id/me
|
||||
*/
|
||||
async getMyMemberInfo(groupId: number | string): Promise<MyMemberInfoResponse> {
|
||||
async getMyMemberInfo(groupId: string): Promise<MyMemberInfoResponse> {
|
||||
const response = await api.get<MyMemberInfoResponse>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/me`
|
||||
`/groups/${encodeURIComponent(groupId)}/me`
|
||||
);
|
||||
return response.data;
|
||||
return {
|
||||
...response.data,
|
||||
member: normalizeMember(response.data.member),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解散群组
|
||||
* DELETE /api/v1/groups/:id
|
||||
*/
|
||||
async dissolveGroup(id: number | string): Promise<void> {
|
||||
await api.delete(`/groups/${encodeURIComponent(String(id))}`);
|
||||
async dissolveGroup(id: string): Promise<void> {
|
||||
await api.delete(`/groups/${encodeURIComponent(id)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转让群主
|
||||
* POST /api/v1/groups/:id/transfer
|
||||
*/
|
||||
async transferOwner(id: number | string, data: TransferOwnerRequest): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(id))}/transfer`, data);
|
||||
async transferOwner(id: string, data: TransferOwnerRequest): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(id)}/transfer`, data);
|
||||
}
|
||||
|
||||
// ==================== 成员管理 ====================
|
||||
@@ -118,25 +169,25 @@ class GroupService {
|
||||
* 邀请成员加入群组
|
||||
* POST /api/v1/groups/:id/invitations
|
||||
*/
|
||||
async inviteMembers(groupId: number | string, data: InviteMembersRequest): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(groupId))}/invitations`, data);
|
||||
async inviteMembers(groupId: string, data: InviteMembersRequest): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(groupId)}/invitations`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请加入群组
|
||||
* POST /api/v1/groups/:id/join-requests
|
||||
*/
|
||||
async joinGroup(groupId: number | string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(groupId))}/join-requests`);
|
||||
async joinGroup(groupId: string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(groupId)}/join-requests`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应群邀请/申请
|
||||
* POST /api/v1/groups/:id/join-requests/respond
|
||||
*/
|
||||
async respondInvite(groupId: number | string, data: HandleGroupRequestAction): Promise<void> {
|
||||
async respondInvite(groupId: string, data: HandleGroupRequestAction): Promise<void> {
|
||||
await api.post(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/join-requests/respond`,
|
||||
`/groups/${encodeURIComponent(groupId)}/join-requests/respond`,
|
||||
data
|
||||
);
|
||||
}
|
||||
@@ -145,9 +196,9 @@ class GroupService {
|
||||
* 审批主动加群申请
|
||||
* POST /api/v1/groups/:id/join-requests/handle
|
||||
*/
|
||||
async reviewJoinRequest(groupId: number | string, data: HandleGroupRequestAction): Promise<void> {
|
||||
async reviewJoinRequest(groupId: string, data: HandleGroupRequestAction): Promise<void> {
|
||||
await api.post(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/join-requests/handle`,
|
||||
`/groups/${encodeURIComponent(groupId)}/join-requests/handle`,
|
||||
data
|
||||
);
|
||||
}
|
||||
@@ -156,15 +207,15 @@ class GroupService {
|
||||
* 退出群组
|
||||
* POST /api/v1/groups/:id/leave
|
||||
*/
|
||||
async leaveGroup(groupId: number | string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(groupId))}/leave`);
|
||||
async leaveGroup(groupId: string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(groupId)}/leave`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组成员列表
|
||||
* GET /api/v1/groups/:id/members
|
||||
*/
|
||||
async getMembers(groupId: number | string, page = 1, pageSize = 50): Promise<GroupMemberListResponse> {
|
||||
async getMembers(groupId: string, page = 1, pageSize = 50): Promise<GroupMemberListResponse> {
|
||||
return this.fetchMembersFromApi(groupId, page, pageSize);
|
||||
}
|
||||
|
||||
@@ -172,8 +223,8 @@ class GroupService {
|
||||
* 移除群成员(踢人)
|
||||
* POST /api/v1/groups/:id/members/kick
|
||||
*/
|
||||
async removeMember(groupId: number | string, userId: string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(groupId))}/members/kick`, {
|
||||
async removeMember(groupId: string, userId: string): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(groupId)}/members/kick`, {
|
||||
user_id: userId,
|
||||
reject_add_request: false,
|
||||
});
|
||||
@@ -184,12 +235,12 @@ class GroupService {
|
||||
* PUT /api/v1/groups/:id/members/:user_id/admin
|
||||
*/
|
||||
async setMemberRole(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
userId: string,
|
||||
data: SetRoleRequest
|
||||
): Promise<void> {
|
||||
await api.put(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/members/${encodeURIComponent(userId)}/admin`,
|
||||
`/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}/admin`,
|
||||
{
|
||||
enable: data.role === 'admin',
|
||||
}
|
||||
@@ -200,8 +251,8 @@ class GroupService {
|
||||
* 设置群昵称
|
||||
* PUT /api/v1/groups/:id/members/me/nickname
|
||||
*/
|
||||
async setNickname(groupId: number | string, data: SetNicknameRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(String(groupId))}/members/me/nickname`, data);
|
||||
async setNickname(groupId: string, data: SetNicknameRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(groupId)}/members/me/nickname`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,11 +263,11 @@ class GroupService {
|
||||
* @param duration 禁言时长(秒),0表示解除禁言
|
||||
*/
|
||||
async muteMember(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
userId: string,
|
||||
duration: number = 0
|
||||
): Promise<void> {
|
||||
await api.post(`/groups/${encodeURIComponent(String(groupId))}/members/ban`, {
|
||||
await api.post(`/groups/${encodeURIComponent(groupId)}/members/ban`, {
|
||||
user_id: userId,
|
||||
duration: duration,
|
||||
});
|
||||
@@ -228,8 +279,8 @@ class GroupService {
|
||||
* 设置全员禁言
|
||||
* PUT /api/v1/groups/:id/ban
|
||||
*/
|
||||
async setMuteAll(groupId: number | string, data: SetMuteAllRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(String(groupId))}/ban`, {
|
||||
async setMuteAll(groupId: string, data: SetMuteAllRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(groupId)}/ban`, {
|
||||
enable: data.mute_all,
|
||||
});
|
||||
}
|
||||
@@ -238,16 +289,16 @@ class GroupService {
|
||||
* 设置加群方式
|
||||
* PUT /api/v1/groups/:id/join-type
|
||||
*/
|
||||
async setJoinType(groupId: number | string, data: SetJoinTypeRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(String(groupId))}/join-type`, data);
|
||||
async setJoinType(groupId: string, data: SetJoinTypeRequest): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(groupId)}/join-type`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置群名称
|
||||
* PUT /api/v1/groups/:id/name
|
||||
*/
|
||||
async setGroupName(groupId: number | string, groupName: string): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(String(groupId))}/name`, {
|
||||
async setGroupName(groupId: string, groupName: string): Promise<void> {
|
||||
await api.put(`/groups/${encodeURIComponent(groupId)}/name`, {
|
||||
group_name: groupName,
|
||||
});
|
||||
}
|
||||
@@ -256,14 +307,14 @@ class GroupService {
|
||||
* 设置群头像
|
||||
* PUT /api/v1/groups/:id/avatar
|
||||
*/
|
||||
async setGroupAvatar(groupId: number | string, avatarUrl: string): Promise<GroupResponse> {
|
||||
async setGroupAvatar(groupId: string, avatarUrl: string): Promise<GroupResponse> {
|
||||
const response = await api.put<GroupResponse>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/avatar`,
|
||||
`/groups/${encodeURIComponent(groupId)}/avatar`,
|
||||
{
|
||||
avatar: avatarUrl,
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
return normalizeGroup(response.data);
|
||||
}
|
||||
|
||||
// ==================== 群公告 ====================
|
||||
@@ -273,14 +324,14 @@ class GroupService {
|
||||
* POST /api/v1/groups/:id/announcements
|
||||
*/
|
||||
async createAnnouncement(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
data: CreateAnnouncementRequest
|
||||
): Promise<GroupAnnouncementResponse> {
|
||||
const response = await api.post<GroupAnnouncementResponse>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/announcements`,
|
||||
`/groups/${encodeURIComponent(groupId)}/announcements`,
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
return normalizeAnnouncement(response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,19 +339,19 @@ class GroupService {
|
||||
* GET /api/v1/groups/:id/announcements
|
||||
*/
|
||||
async getAnnouncements(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
page = 1,
|
||||
pageSize = 20
|
||||
): Promise<GroupAnnouncementListResponse> {
|
||||
try {
|
||||
const response = await api.get<GroupAnnouncementListResponse>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/announcements`,
|
||||
`/groups/${encodeURIComponent(groupId)}/announcements`,
|
||||
{
|
||||
page,
|
||||
page_size: pageSize,
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
return normalizeAnnouncementListResponse(response.data);
|
||||
} catch (error) {
|
||||
console.error('获取群公告列表失败:', error);
|
||||
return {
|
||||
@@ -317,9 +368,9 @@ class GroupService {
|
||||
* 删除群公告
|
||||
* DELETE /api/v1/groups/:id/announcements/:announcement_id
|
||||
*/
|
||||
async deleteAnnouncement(groupId: number | string, announcementId: number): Promise<void> {
|
||||
async deleteAnnouncement(groupId: string, announcementId: number): Promise<void> {
|
||||
await api.delete(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/announcements/${encodeURIComponent(String(announcementId))}`
|
||||
`/groups/${encodeURIComponent(groupId)}/announcements/${encodeURIComponent(String(announcementId))}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -335,7 +386,7 @@ class GroupService {
|
||||
): Promise<CursorPaginationResponse<GroupResponse>> {
|
||||
try {
|
||||
const response = await api.get<CursorPaginationResponse<GroupResponse>>('/groups/cursor', params);
|
||||
return response.data;
|
||||
return normalizeGroupCursor(response.data);
|
||||
} catch (error) {
|
||||
console.error('获取群组列表失败:', error);
|
||||
return {
|
||||
@@ -354,15 +405,15 @@ class GroupService {
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getGroupMembersCursor(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<GroupMemberResponse>> {
|
||||
try {
|
||||
const response = await api.get<CursorPaginationResponse<GroupMemberResponse>>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/members/cursor`,
|
||||
`/groups/${encodeURIComponent(groupId)}/members/cursor`,
|
||||
params
|
||||
);
|
||||
return response.data;
|
||||
return normalizeMemberCursor(response.data);
|
||||
} catch (error) {
|
||||
console.error('获取群组成员列表失败:', error);
|
||||
return {
|
||||
@@ -381,15 +432,15 @@ class GroupService {
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getGroupAnnouncementsCursor(
|
||||
groupId: number | string,
|
||||
groupId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<GroupAnnouncementResponse>> {
|
||||
try {
|
||||
const response = await api.get<CursorPaginationResponse<GroupAnnouncementResponse>>(
|
||||
`/groups/${encodeURIComponent(String(groupId))}/announcements/cursor`,
|
||||
`/groups/${encodeURIComponent(groupId)}/announcements/cursor`,
|
||||
params
|
||||
);
|
||||
return response.data;
|
||||
return normalizeAnnouncementCursor(response.data);
|
||||
} catch (error) {
|
||||
console.error('获取群公告列表失败:', error);
|
||||
return {
|
||||
|
||||
88
src/services/messageVibrationService.ts
Normal file
88
src/services/messageVibrationService.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as Haptics from 'expo-haptics';
|
||||
|
||||
interface VibrationConfig {
|
||||
enabled: boolean;
|
||||
onChatMessage: boolean;
|
||||
onGroupMessage: boolean;
|
||||
onNotification: boolean;
|
||||
style: Haptics.ImpactFeedbackStyle;
|
||||
}
|
||||
|
||||
const defaultVibrationConfig: VibrationConfig = {
|
||||
enabled: true,
|
||||
onChatMessage: true,
|
||||
onGroupMessage: true,
|
||||
onNotification: true,
|
||||
style: Haptics.ImpactFeedbackStyle.Light,
|
||||
};
|
||||
|
||||
let vibrationConfig: VibrationConfig = { ...defaultVibrationConfig };
|
||||
|
||||
export async function triggerVibration(
|
||||
type: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error' = 'light'
|
||||
): Promise<void> {
|
||||
if (!vibrationConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'light':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
break;
|
||||
case 'medium':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
break;
|
||||
case 'heavy':
|
||||
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
||||
break;
|
||||
case 'success':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||
break;
|
||||
case 'warning':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
||||
break;
|
||||
case 'error':
|
||||
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[VibrationService] 震动反馈失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function vibrateOnMessage(messageType: 'chat' | 'group_message' | 'notification'): Promise<void> {
|
||||
if (!vibrationConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (messageType) {
|
||||
case 'chat':
|
||||
if (vibrationConfig.onChatMessage) {
|
||||
await triggerVibration('light');
|
||||
}
|
||||
break;
|
||||
case 'group_message':
|
||||
if (vibrationConfig.onGroupMessage) {
|
||||
await triggerVibration('light');
|
||||
}
|
||||
break;
|
||||
case 'notification':
|
||||
if (vibrationConfig.onNotification) {
|
||||
await triggerVibration('medium');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function setVibrationConfig(config: Partial<VibrationConfig>): void {
|
||||
vibrationConfig = { ...vibrationConfig, ...config };
|
||||
}
|
||||
|
||||
export function getVibrationConfig(): VibrationConfig {
|
||||
return { ...vibrationConfig };
|
||||
}
|
||||
|
||||
export function setVibrationEnabled(enabled: boolean): void {
|
||||
vibrationConfig.enabled = enabled;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import EventSource from 'react-native-sse';
|
||||
import { api, SSE_URL } from './api';
|
||||
import { MessageCategory, SystemMessageType, SystemMessageExtraData, MessageSegment } from '../types/dto';
|
||||
import { systemNotificationService } from './systemNotificationService';
|
||||
import { vibrateOnMessage } from './backgroundService';
|
||||
import { vibrateOnMessage } from './messageVibrationService';
|
||||
|
||||
export type WSMessageType =
|
||||
| 'chat'
|
||||
@@ -62,7 +62,7 @@ export interface WSRecallMessage {
|
||||
|
||||
export interface WSNotificationMessage {
|
||||
type: 'notification';
|
||||
id: number | string;
|
||||
id: string;
|
||||
sender_id?: string;
|
||||
receiver_id?: string;
|
||||
content: string;
|
||||
@@ -74,7 +74,7 @@ export interface WSNotificationMessage {
|
||||
|
||||
export interface WSAnnouncementMessage {
|
||||
type: 'announcement';
|
||||
id: number | string;
|
||||
id: string;
|
||||
sender_id?: string;
|
||||
receiver_id?: string;
|
||||
content: string;
|
||||
@@ -86,7 +86,7 @@ export interface WSAnnouncementMessage {
|
||||
|
||||
export interface WSGroupMentionMessage {
|
||||
type: 'group_mention';
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
conversation_id: string;
|
||||
message_id: string;
|
||||
from_user_id: string;
|
||||
@@ -98,7 +98,7 @@ export interface WSGroupMentionMessage {
|
||||
export interface WSGroupChatMessage {
|
||||
type: 'group_message';
|
||||
conversation_id: string;
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
id: string;
|
||||
sender_id: string;
|
||||
seq: number;
|
||||
@@ -108,7 +108,7 @@ export interface WSGroupChatMessage {
|
||||
|
||||
export interface WSGroupTypingMessage {
|
||||
type: 'group_typing';
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
user_id: string;
|
||||
is_typing: boolean;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ export type GroupNoticeType = 'member_join' | 'member_leave' | 'member_removed'
|
||||
export interface WSGroupNoticeMessage {
|
||||
type: 'group_notice';
|
||||
notice_type: GroupNoticeType;
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
data: {
|
||||
user_id?: string;
|
||||
operator_id?: string;
|
||||
@@ -132,7 +132,7 @@ export interface WSGroupNoticeMessage {
|
||||
|
||||
export interface WSGroupReadMessage {
|
||||
type: 'group_read';
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
conversation_id: string;
|
||||
user_id: string;
|
||||
seq: number;
|
||||
@@ -140,7 +140,7 @@ export interface WSGroupReadMessage {
|
||||
|
||||
export interface WSGroupRecallMessage {
|
||||
type: 'group_recall';
|
||||
group_id: number | string;
|
||||
group_id: string;
|
||||
conversation_id: string;
|
||||
message_id: string;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class SSEService {
|
||||
const gm: WSGroupChatMessage = {
|
||||
type: 'group_message',
|
||||
conversation_id: m.conversation_id,
|
||||
group_id: m.group_id || '',
|
||||
group_id: String(m.group_id ?? ''),
|
||||
id: m.id,
|
||||
sender_id: m.sender_id,
|
||||
seq: Number(m.seq || 0),
|
||||
@@ -301,7 +301,7 @@ class SSEService {
|
||||
if (detailType === 'group') {
|
||||
const m: WSGroupReadMessage = {
|
||||
type: 'group_read',
|
||||
group_id: payload.group_id || '',
|
||||
group_id: String(payload.group_id ?? ''),
|
||||
conversation_id: payload.conversation_id,
|
||||
user_id: payload.user_id,
|
||||
seq: Number(payload.seq || 0),
|
||||
@@ -324,7 +324,7 @@ class SSEService {
|
||||
if (detailType === 'group') {
|
||||
const m: WSGroupTypingMessage = {
|
||||
type: 'group_typing',
|
||||
group_id: payload.group_id || '',
|
||||
group_id: String(payload.group_id ?? ''),
|
||||
user_id: payload.user_id,
|
||||
is_typing: payload.is_typing !== false,
|
||||
};
|
||||
@@ -346,7 +346,7 @@ class SSEService {
|
||||
if (detailType === 'group') {
|
||||
const m: WSGroupRecallMessage = {
|
||||
type: 'group_recall',
|
||||
group_id: payload.group_id || '',
|
||||
group_id: String(payload.group_id ?? ''),
|
||||
conversation_id: payload.conversation_id,
|
||||
message_id: payload.message_id,
|
||||
};
|
||||
@@ -366,7 +366,7 @@ class SSEService {
|
||||
const m: WSGroupNoticeMessage = {
|
||||
type: 'group_notice',
|
||||
notice_type: payload.notice_type,
|
||||
group_id: payload.group_id,
|
||||
group_id: String(payload.group_id ?? ''),
|
||||
data: payload.data || {},
|
||||
timestamp: payload.timestamp || Date.now(),
|
||||
message_id: payload.message_id,
|
||||
@@ -379,7 +379,7 @@ class SSEService {
|
||||
if (eventName === 'system_notification') {
|
||||
const m: WSNotificationMessage = {
|
||||
type: 'notification',
|
||||
id: payload.id || '',
|
||||
id: String(payload.id ?? ''),
|
||||
content: payload.content || '',
|
||||
created_at: payload.created_at || new Date().toISOString(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user