接入移动壳安全区布局
Expo 移动壳使用 SafeAreaProvider 和 SafeAreaView 包裹 WebView 新增安全区边界配置和测试 补充移动壳安全区依赖检查和架构文档
This commit is contained in:
@@ -7,8 +7,8 @@ import {
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||
import type { WebViewMessageEvent } from 'react-native-webview';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
shouldOpenInMobileShellWebView,
|
||||
} from './src/mobileShellNavigation';
|
||||
import { subscribeMobileNetworkStatus } from './src/mobileShellNetwork';
|
||||
import { MOBILE_SHELL_SAFE_AREA_EDGES } from './src/mobileShellSafeArea';
|
||||
import { buildMobileShellUrl } from './src/mobileShellUrl';
|
||||
|
||||
const defaultWebUrl = 'http://127.0.0.1:3000/';
|
||||
@@ -165,32 +166,34 @@ export default function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
<StatusBar style="auto" />
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
source={{ uri: webUrl }}
|
||||
javaScriptEnabled
|
||||
domStorageEnabled
|
||||
originWhitelist={[allowedWebOrigin]}
|
||||
onMessage={handleMessage}
|
||||
onShouldStartLoadWithRequest={handleShouldStartLoad}
|
||||
onNavigationStateChange={(event) => {
|
||||
setCanGoBack(event.canGoBack);
|
||||
webViewRef.current?.injectJavaScript(
|
||||
buildHostBridgeMessageScript({
|
||||
bridge: 'GenarrativeHostBridge',
|
||||
version: 1,
|
||||
event: 'navigation.canGoBack',
|
||||
payload: {
|
||||
canGoBack: event.canGoBack,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}}
|
||||
setSupportMultipleWindows={false}
|
||||
/>
|
||||
</View>
|
||||
<SafeAreaProvider>
|
||||
<SafeAreaView style={styles.root} edges={MOBILE_SHELL_SAFE_AREA_EDGES}>
|
||||
<StatusBar style="auto" />
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
source={{ uri: webUrl }}
|
||||
javaScriptEnabled
|
||||
domStorageEnabled
|
||||
originWhitelist={[allowedWebOrigin]}
|
||||
onMessage={handleMessage}
|
||||
onShouldStartLoadWithRequest={handleShouldStartLoad}
|
||||
onNavigationStateChange={(event) => {
|
||||
setCanGoBack(event.canGoBack);
|
||||
webViewRef.current?.injectJavaScript(
|
||||
buildHostBridgeMessageScript({
|
||||
bridge: 'GenarrativeHostBridge',
|
||||
version: 1,
|
||||
event: 'navigation.canGoBack',
|
||||
payload: {
|
||||
canGoBack: event.canGoBack,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}}
|
||||
setSupportMultipleWindows={false}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"expo-status-bar": "^56.0.4",
|
||||
"react": "^19.0.0",
|
||||
"react-native": "^0.86.0",
|
||||
"react-native-safe-area-context": "^5.8.0",
|
||||
"react-native-webview": "^13.16.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -124,6 +124,9 @@ for (const snippet of [
|
||||
'subscribeMobileNetworkStatus',
|
||||
'navigation.canGoBack',
|
||||
'buildHostBridgeMessageScript',
|
||||
'SafeAreaProvider',
|
||||
'SafeAreaView',
|
||||
'MOBILE_SHELL_SAFE_AREA_EDGES',
|
||||
]) {
|
||||
if (!appSource.includes(snippet)) {
|
||||
throw new Error(`mobile shell App missing ${snippet}`);
|
||||
@@ -137,6 +140,7 @@ for (const dependency of [
|
||||
'expo-network',
|
||||
'expo-notifications',
|
||||
'expo-sharing',
|
||||
'react-native-safe-area-context',
|
||||
]) {
|
||||
if (!packageConfig.dependencies?.[dependency]) {
|
||||
throw new Error(`mobile shell package missing ${dependency}`);
|
||||
|
||||
14
apps/mobile-shell/src/mobileShellSafeArea.test.ts
Normal file
14
apps/mobile-shell/src/mobileShellSafeArea.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { MOBILE_SHELL_SAFE_AREA_EDGES } from './mobileShellSafeArea';
|
||||
|
||||
describe('mobile shell safe area', () => {
|
||||
test('protects the WebView from every device edge', () => {
|
||||
expect(MOBILE_SHELL_SAFE_AREA_EDGES).toEqual([
|
||||
'top',
|
||||
'right',
|
||||
'bottom',
|
||||
'left',
|
||||
]);
|
||||
});
|
||||
});
|
||||
6
apps/mobile-shell/src/mobileShellSafeArea.ts
Normal file
6
apps/mobile-shell/src/mobileShellSafeArea.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const MOBILE_SHELL_SAFE_AREA_EDGES = [
|
||||
'top',
|
||||
'right',
|
||||
'bottom',
|
||||
'left',
|
||||
] as const;
|
||||
@@ -38,6 +38,7 @@
|
||||
- 2026-06-18 移动图片导入:Expo 壳开始声明并实现 `file.importImage`,通过 `expo-image-picker` 请求相册权限并打开系统相册选择器,只允许 `image/png`、`image/jpeg`、`image/webp` 且单次不超过 10 MiB;成功只回传清洗后的文件名、MIME、base64 内容和字节数,不暴露设备本地 URI,用户取消返回 `cancelled` 并由 H5 facade 归为 `false`。
|
||||
- 2026-06-18 移动图片拍摄导入:Expo 壳新增 `file.captureImage` HostBridge capability,通过 `expo-image-picker` 请求相机权限并打开系统相机拍摄图片,沿用 `file.importImage` 的 MIME、体积、base64 和文件名清洗规则,成功回传 `action=captured`,不暴露设备本地 URI,也不请求麦克风权限;Tauri 壳不声明该能力,不伪造桌面拍摄。
|
||||
- 2026-06-18 H5 图片上传接入宿主导入:`CreativeImageInputPanel` 在 `native_app` 且声明 `file.importImage` / `file.captureImage` 时,主图上传和描述参考图上传可分别调用 `importHostImageFile()` / `captureHostImageFile()`,并把宿主返回的 base64 图片转换为现有 `File` 回调;浏览器、小程序和未声明能力的裁剪壳继续走原生 `<input type="file">` 路径,不新增玩法侧上传分叉。
|
||||
- 2026-06-18 移动壳安全区:Expo 壳根布局使用 `react-native-safe-area-context` 的 `SafeAreaProvider` 与四边 `SafeAreaView` 保护 WebView,避免 H5 主站内容贴进 iOS 刘海、底部 Home Indicator、Android 状态栏或横屏边缘;该能力属于宿主壳布局保护,不新增 H5 占位 UI,不改变玩法 runtime 或 HostBridge capability。
|
||||
- 2026-06-18 桌面图片拖入接入主图槽位:`CreativeImageInputPanel` 在桌面壳声明 `file.imageDropped` 时订阅宿主拖入事件,只在拖入坐标命中当前主图卡片且未被上层元素遮挡时消费事件,避免窗口级拖入被多个创作面板同时接收;成功后仍转换为现有 `File` 上传回调。
|
||||
- 2026-06-18 H5 背景音乐接入宿主生命周期:`useBackgroundMusic` 通过 `useHostLifecycleActive()` 消费 `subscribeHostAppLifecycle()` 的归一结果,宿主进入后台、inactive 或桌面窗口失焦时降低音量并暂停音频循环,同时 `suspend` WebAudio context;回到 `active + focused` 且用户原本开启音乐时再恢复播放,不改变用户音量设置。
|
||||
- 2026-06-18 固定玩法音频接入宿主生命周期:前端新增 `useHostLifecycleActive()` 统一消费 `subscribeHostAppLifecycle()`,`useBackgroundMusic`、拼图运行态和抓大鹅运行态都只依赖该归一状态判断音频可播放性;宿主 inactive、background 或窗口失焦时暂停 `<audio>` / WebAudio,回到 `active + focused` 后仅在运行态仍在播放、音源存在且用户音乐音量大于 0 时恢复,不改变用户音量设置。
|
||||
|
||||
@@ -270,6 +270,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:H5 账号状态刷新开始消费 `app.reloadWebView`。用户登录成功、退出登录或其它身份边界变化需要整页重新初始化时,`AuthGate` 会优先请求 Expo 壳刷新当前 WebView;宿主未声明或刷新失败时再回退浏览器刷新,避免在移动壳内绕过受控容器刷新入口。
|
||||
|
||||
2026-06-18 追加:移动壳根布局接入 `react-native-safe-area-context`,用 `SafeAreaProvider` 和四边 `SafeAreaView` 承接 iOS 刘海、底部 Home Indicator、Android 状态栏和横屏边缘安全区;WebView 仍加载同一 H5 主站,不改变 H5 路由、玩法 runtime 或 HostBridge capability。该能力属于宿主壳布局保护,不在 H5 内补额外占位 UI。
|
||||
|
||||
### Phase 3:Tauri 桌面壳 MVP
|
||||
|
||||
- 新增 `apps/desktop-shell/`。
|
||||
|
||||
17
package-lock.json
generated
17
package-lock.json
generated
@@ -32,6 +32,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-native": "^0.86.0",
|
||||
"react-native-safe-area-context": "^5.8.0",
|
||||
"react-native-webview": "^13.16.1",
|
||||
"three": "^0.184.0",
|
||||
"vite": "^6.2.0"
|
||||
@@ -9975,6 +9976,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.8.0.tgz",
|
||||
"integrity": "sha512-t+ZsAVzY/wWzzx34vqGbo3/as9EEESJdbyZNL7Yg5EYX+toYMtMqFoDDCvqZUi35eeGVsXc6pAaEk4edMwbuCQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-webview": {
|
||||
"version": "13.16.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz",
|
||||
@@ -19870,6 +19881,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-safe-area-context": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.8.0.tgz",
|
||||
"integrity": "sha512-t+ZsAVzY/wWzzx34vqGbo3/as9EEESJdbyZNL7Yg5EYX+toYMtMqFoDDCvqZUi35eeGVsXc6pAaEk4edMwbuCQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "13.16.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz",
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-native": "^0.86.0",
|
||||
"react-native-safe-area-context": "^5.8.0",
|
||||
"react-native-webview": "^13.16.1",
|
||||
"three": "^0.184.0",
|
||||
"vite": "^6.2.0"
|
||||
|
||||
Reference in New Issue
Block a user