重构应用程序架构,添加 OpenIM SDK 初始化和引导逻辑
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import OpenIMSDK from "@openim/rn-client-sdk";
|
|
||||||
import {
|
import {
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
@@ -6,11 +5,11 @@ import {
|
|||||||
} from "@react-navigation/native";
|
} from "@react-navigation/native";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import { StatusBar } from "expo-status-bar";
|
import { StatusBar } from "expo-status-bar";
|
||||||
import RNFS from "react-native-fs";
|
|
||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
import "../global.css";
|
import "../global.css";
|
||||||
|
|
||||||
import { useColorScheme } from "@/hooks/use-color-scheme";
|
import { useColorScheme } from "@/hooks/use-color-scheme";
|
||||||
|
import { AppBootstrapProvider } from "@/providers/app-bootstrap-provider";
|
||||||
|
|
||||||
export const unstable_settings = {
|
export const unstable_settings = {
|
||||||
anchor: "(tabs)",
|
anchor: "(tabs)",
|
||||||
@@ -19,18 +18,8 @@ export const unstable_settings = {
|
|||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
|
|
||||||
RNFS.mkdir(RNFS.DocumentDirectoryPath + "/tmp");
|
|
||||||
|
|
||||||
OpenIMSDK.initSDK({
|
|
||||||
apiAddr: "https://openim-api.riwsan.com/api",
|
|
||||||
wsAddr: "wss://openim-api.riwsan.com/msg_gateway",
|
|
||||||
dataDir: RNFS.DocumentDirectoryPath + "/tmp",
|
|
||||||
logFilePath: RNFS.DocumentDirectoryPath + "/tmp",
|
|
||||||
logLevel: 5,
|
|
||||||
isLogStandardOutput: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<AppBootstrapProvider>
|
||||||
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
@@ -41,5 +30,6 @@ export default function RootLayout() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</AppBootstrapProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
44
docs/architecture.md
Normal file
44
docs/architecture.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Project Architecture
|
||||||
|
|
||||||
|
This project uses Expo Router and follows a route-first + feature-module structure.
|
||||||
|
|
||||||
|
## Directory Design
|
||||||
|
|
||||||
|
- `app/`: Routing layer only (Expo Router pages and layouts).
|
||||||
|
- `features/`: Business modules grouped by domain.
|
||||||
|
- `providers/`: App-level providers and bootstrap orchestration.
|
||||||
|
- `components/`: Reusable presentational components.
|
||||||
|
- `hooks/`: Shared generic hooks (cross-feature).
|
||||||
|
- `constants/`: Shared constants and theme tokens.
|
||||||
|
|
||||||
|
## Current Module Split
|
||||||
|
|
||||||
|
- `app/_layout.tsx`
|
||||||
|
- Root navigation composition only.
|
||||||
|
- Theme provider and route stack config.
|
||||||
|
- No SDK side effects.
|
||||||
|
|
||||||
|
- `providers/app-bootstrap-provider.tsx`
|
||||||
|
- Central app bootstrap orchestration.
|
||||||
|
- Triggers feature bootstrap hooks.
|
||||||
|
|
||||||
|
- `features/im/openim-bootstrap.ts`
|
||||||
|
- OpenIM SDK initialization and listener registration.
|
||||||
|
- One-time initialization guard.
|
||||||
|
- Local data directory creation and reuse.
|
||||||
|
|
||||||
|
- `features/im/hooks/use-openim-bootstrap.ts`
|
||||||
|
- React lifecycle bridge for IM bootstrap.
|
||||||
|
|
||||||
|
## Why This Structure
|
||||||
|
|
||||||
|
- Keeps routing files focused on navigation.
|
||||||
|
- Moves SDK side effects out of render paths.
|
||||||
|
- Makes IM bootstrap testable and reusable.
|
||||||
|
- Scales better when adding `features/chat`, `features/contact`, and `features/conversation`.
|
||||||
|
|
||||||
|
## Next Suggested Refactors
|
||||||
|
|
||||||
|
1. Move IM API wrappers into `features/im/services/`.
|
||||||
|
2. Add `features/chat/hooks/use-chat-list.ts` and `features/chat/hooks/use-messages.ts`.
|
||||||
|
3. Keep `app/(tabs)` as route shells and move business UI into `features/*/screens`.
|
||||||
11
features/im/hooks/use-openim-bootstrap.ts
Normal file
11
features/im/hooks/use-openim-bootstrap.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { bootstrapOpenIM } from "@/features/im/openim-bootstrap";
|
||||||
|
|
||||||
|
export function useOpenIMBootstrap() {
|
||||||
|
useEffect(() => {
|
||||||
|
bootstrapOpenIM().catch((error) => {
|
||||||
|
console.error("OpenIM bootstrap failed", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
38
features/im/openim-bootstrap.ts
Normal file
38
features/im/openim-bootstrap.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import OpenIMSDK, { MessageItem } from "@openim/rn-client-sdk";
|
||||||
|
import RNFS from "react-native-fs";
|
||||||
|
|
||||||
|
const OPENIM_DIR = `${RNFS.DocumentDirectoryPath}/openim`;
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
let ensureDirPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
function ensureOpenIMDir() {
|
||||||
|
if (!ensureDirPromise) {
|
||||||
|
ensureDirPromise = RNFS.mkdir(OPENIM_DIR).then(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ensureDirPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function bootstrapOpenIM() {
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureOpenIMDir();
|
||||||
|
|
||||||
|
OpenIMSDK.initSDK({
|
||||||
|
apiAddr: "https://openim-api.riwsan.com/api",
|
||||||
|
wsAddr: "wss://openim-api.riwsan.com/msg_gateway",
|
||||||
|
dataDir: OPENIM_DIR,
|
||||||
|
logFilePath: OPENIM_DIR,
|
||||||
|
logLevel: 5,
|
||||||
|
isLogStandardOutput: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
OpenIMSDK.on("onRecvNewMessages", (messages: MessageItem[]) => {
|
||||||
|
console.log("onRecvNewMessages", messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
9
providers/app-bootstrap-provider.tsx
Normal file
9
providers/app-bootstrap-provider.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
import { useOpenIMBootstrap } from "@/features/im/hooks/use-openim-bootstrap";
|
||||||
|
|
||||||
|
export function AppBootstrapProvider({ children }: PropsWithChildren) {
|
||||||
|
useOpenIMBootstrap();
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user