refactor(database): migrate to new modular database layer and unify data access
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 12m51s
Frontend CI / build-android-apk (push) Successful in 1h1m26s

- Remove legacy database.ts, LocalDataSource.ts, and MessageRepository.ts
- Create new src/database/ module with messageRepository, userCacheRepository, conversationRepository, and groupCacheRepository
- Update all consumers to import from @/database instead of services/database
- Add web platform blur handling for modal components to fix focus issues
- Flatten SystemMessageItem and NotificationsScreen styles for consistent design
- Add draggable slider in ChatSettingsScreen and dynamic font size support
- Introduce 9 new chat color themes
- Add profile screens for about, terms, and privacy policy with navigation routes
- Add policy links to login and registration screens
- Fix post share URL format from /posts/ to /post/
This commit is contained in:
lafay
2026-04-04 08:01:45 +08:00
parent 189b977fac
commit 82c2970a85
76 changed files with 3382 additions and 2000 deletions

View File

@@ -1,10 +1,3 @@
/**
* GroupManager - 蝢斤<E89DA2>蝞∠<E89D9E><E288A0><EFBFBD><E79285><EFBFBD><E59A97><EFBFBD><EFBFBD><EFBFBD>嚗?
*
* 雿輻鍂 Zustand store 餈𥡝<E9A488><F0A5A19D><EFBFBD><E59786><EFBFBD>?
* 靽脲<E99DBD>銝擧唂 API <20><><EFBFBD><EFBFBD>𤾸<EFBFBD>摰?
*/
import type {
GroupListResponse,
GroupMemberListResponse,
@@ -12,15 +5,7 @@ import type {
GroupResponse,
} from '../../types/dto';
import { groupService } from '../../services/groupService';
import {
getAllGroupsCache,
getGroupCache,
getGroupMembersCache,
saveGroupCache,
saveGroupMembersCache,
saveGroupsCache,
saveUsersCache,
} from '../../services/database';
import { groupCacheRepository, userCacheRepository } from '@/database';
import { useGroupManagerStore } from './groupStore';
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
import {
@@ -33,8 +18,6 @@ import {
GROUP_MEMBER_LIST_PAGE_SIZE,
} from '../groupListSources';
// ==================== 颲<>𨭌<EFBFBD>賣㺭 ====================
function buildMembersKey(groupId: string, page: number, pageSize: number): string {
return `members:${groupId}:${page}:${pageSize}`;
}
@@ -63,14 +46,7 @@ function buildMemberListResponse(
};
}
// ==================== GroupManager 蝐?====================
class GroupManager {
// ==================== 蝢斤<E89DA2><E696A4>𡑒”<F0A19192><EFBFBD> ====================
/**
* <20><EFBFBD>蝢斤<E89DA2><E696A4>𡑒”
*/
async getGroups(
page = 1,
pageSize = GROUP_LIST_PAGE_SIZE,
@@ -79,20 +55,17 @@ class GroupManager {
const store = useGroupManagerStore.getState();
const entry = store.groupsListEntry;
// 蝚砌<E89D9A>憿萎<E686BF><E8908E><EFBFBD><E39787><EFBFBD><EFBFBD>摮?
if (page === 1 && !forceRefresh && entry && !isCacheExpired(entry)) {
return buildGroupListResponse(entry.data, pageSize);
}
// 蝚砌<E89D9A>憿萎<E686BF>蝻枏<E89DBB><EFBFBD><E9A488>嚗𡁜<E59A97><F0A1819C><EFBFBD><E595A3>?
if (page === 1 && !forceRefresh && entry && isCacheExpired(entry)) {
this.refreshGroupsInBackground(page, pageSize);
return buildGroupListResponse(entry.data, pageSize);
}
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
if (page === 1 && !forceRefresh) {
const localGroups = await getAllGroupsCache();
const localGroups = await groupCacheRepository.getAll();
if (localGroups.length > 0) {
const newEntry = createCacheEntry(localGroups, DEFAULT_TTL.GROUP);
store.setGroupsListEntry(newEntry);
@@ -101,7 +74,6 @@ class GroupManager {
}
}
// <20>𤏸絲霂瑟<E99C82>
return this.dedupe(`groups:list:${page}:${pageSize}`, async () => {
const store = useGroupManagerStore.getState();
const response = await groupService.getGroups(page, pageSize);
@@ -109,7 +81,7 @@ class GroupManager {
if (page === 1) {
store.setGroupsList(groups);
}
await saveGroupsCache(groups).catch(() => {});
await groupCacheRepository.saveBatch(groups).catch(() => {});
return response;
});
}
@@ -122,16 +94,13 @@ class GroupManager {
if (page === 1) {
store.setGroupsList(groups);
}
saveGroupsCache(groups).catch(() => {});
groupCacheRepository.saveBatch(groups).catch(() => {});
return response;
}).catch((error) => {
console.error('[GroupManager] refreshGroupsInBackground error:', error);
});
}
/**
* <20>𥕦遣蝢斤<E89DA2><E696A4>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C><EFBFBD> Sources 璅<E79285>嚗?
*/
createGroupListSource(
kind: RemoteGroupListSourceKind = 'cursor',
pageSize: number = GROUP_LIST_PAGE_SIZE
@@ -139,29 +108,21 @@ class GroupManager {
return createRemoteGroupListSource(kind, pageSize);
}
// ==================== 蝢斤<E89DA2>霂行<E99C82><E8A18C><EFBFBD> ====================
/**
* <20><EFBFBD>蝢斤<E89DA2>霂行<E99C82>
*/
async getGroup(groupId: string, forceRefresh = false): Promise<GroupResponse> {
const store = useGroupManagerStore.getState();
const entry = store.groupsMap.get(groupId);
// <20><EFBFBD>蝻枏<E89DBB>
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return entry.data;
}
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C><EFBFBD><E595A3>?
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshGroupInBackground(groupId);
return entry.data;
}
// 撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
if (!forceRefresh) {
const localGroup = await getGroupCache(groupId);
const localGroup = await groupCacheRepository.get(groupId);
if (localGroup) {
store.setGroup(groupId, localGroup);
this.refreshGroupInBackground(groupId);
@@ -169,12 +130,11 @@ class GroupManager {
}
}
// <20>𤏸絲霂瑟<E99C82>
return this.dedupe(`groups:detail:${groupId}`, async () => {
const store = useGroupManagerStore.getState();
const group = await groupService.getGroup(groupId);
store.setGroup(groupId, group);
await saveGroupCache(group).catch(() => {});
await groupCacheRepository.save(group).catch(() => {});
return group;
});
}
@@ -184,18 +144,13 @@ class GroupManager {
const store = useGroupManagerStore.getState();
const group = await groupService.getGroup(groupId);
store.setGroup(groupId, group);
saveGroupCache(group).catch(() => {});
groupCacheRepository.save(group).catch(() => {});
return group;
}).catch((error) => {
console.error('[GroupManager] refreshGroupInBackground error:', error);
});
}
// ==================== 蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC><EFBFBD> ====================
/**
* <20><EFBFBD>蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”
*/
async getMembers(
groupId: string,
page = 1,
@@ -206,20 +161,17 @@ class GroupManager {
const store = useGroupManagerStore.getState();
const entry = store.membersMap.get(key);
// <20><EFBFBD>蝻枏<E89DBB>
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return buildMemberListResponse(entry.data, page, pageSize);
}
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C><EFBFBD><E595A3>?
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshMembersInBackground(groupId, page, pageSize);
return buildMemberListResponse(entry.data, page, pageSize);
}
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
if (page === 1 && !forceRefresh) {
const localMembers = await getGroupMembersCache(groupId);
const localMembers = await groupCacheRepository.getMembers(groupId);
if (localMembers.length > 0) {
store.setMembers(groupId, localMembers, page, pageSize);
this.refreshMembersInBackground(groupId, page, pageSize);
@@ -227,16 +179,15 @@ class GroupManager {
}
}
// <20>𤏸絲霂瑟<E99C82>
return this.dedupe(`groups:members:${key}`, async () => {
const store = useGroupManagerStore.getState();
const response = await groupService.getMembers(groupId, page, pageSize);
const members = response.list || [];
store.setMembers(groupId, members, page, pageSize);
if (page === 1) {
await saveGroupMembersCache(groupId, members).catch(() => {});
await groupCacheRepository.saveMembers(groupId, members).catch(() => {});
}
await saveUsersCache(
await userCacheRepository.saveBatch(
members
.map((member) => member.user)
.filter((user): user is NonNullable<typeof user> => Boolean(user))
@@ -257,9 +208,9 @@ class GroupManager {
const members = response.list || [];
store.setMembers(groupId, members, page, pageSize);
if (page === 1) {
saveGroupMembersCache(groupId, members).catch(() => {});
groupCacheRepository.saveMembers(groupId, members).catch(() => {});
}
saveUsersCache(
userCacheRepository.saveBatch(
members
.map((member) => member.user)
.filter((user): user is NonNullable<typeof user> => Boolean(user))
@@ -270,9 +221,6 @@ class GroupManager {
});
}
/**
* <20>𥕦遣蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C><EFBFBD> Sources 璅<E79285>嚗?
*/
createGroupMemberListSource(
groupId: string,
kind: RemoteGroupListSourceKind = 'cursor',
@@ -281,11 +229,6 @@ class GroupManager {
return createRemoteGroupMemberListSource(groupId, kind, pageSize);
}
// ==================== 蝻枏<E89DBB>蝞∠<E89D9E> ====================
/**
* 雿輻<E99BBF>摮睃仃<E79D83>?
*/
invalidate(groupId?: string): void {
if (!groupId) {
useGroupManagerStore.getState().invalidateAll();
@@ -294,15 +237,10 @@ class GroupManager {
useGroupManagerStore.getState().invalidateGroup(groupId);
}
/**
* 皜<><EFBFBD><E88684><EFBFBD>㗇㺭<E39787>?
*/
clear(): void {
useGroupManagerStore.getState().reset();
}
// ==================== 霂瑟<E99C82><E7919F><EFBFBD> ====================
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
const store = useGroupManagerStore.getState();
const pending = store.getPendingRequest<T>(key);
@@ -316,11 +254,8 @@ class GroupManager {
}
}
// ==================== <20><EFBFBD>撖澆枂 ====================
export const groupManager = new GroupManager();
// 撖澆枂蝐颱誑<E9A2B1><EFBFBD>蝐餃<E89D90><EFBFBD><E79289><EFBFBD>瘚贝<E7989A>
export { GroupManager };
export default groupManager;
export default groupManager;