接入原生壳生命周期事件
新增 app.lifecycle HostBridge 能力与 H5 订阅入口 Expo 壳通过 React Native AppState 注入真实前后台状态 Tauri 壳通过主窗口 focus 和 blur 注入真实激活状态 更新壳能力漂移检查、测试和架构文档
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { BackHandler, Linking, Platform, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
AppState,
|
||||
type AppStateStatus,
|
||||
BackHandler,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import type { WebViewMessageEvent } from 'react-native-webview';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
@@ -10,6 +18,7 @@ import {
|
||||
resolveMobileHostCapabilities,
|
||||
} from './src/mobileHostBridge';
|
||||
import { buildMobileShellUrlFromDeepLink } from './src/mobileShellDeepLink';
|
||||
import { lifecyclePayloadFromAppState } from './src/mobileShellLifecycle';
|
||||
import {
|
||||
resolveMobileShellExternalUrl,
|
||||
shouldOpenInMobileShellWebView,
|
||||
@@ -97,6 +106,27 @@ export default function App() {
|
||||
return () => subscription.remove();
|
||||
}, [canGoBack]);
|
||||
|
||||
useEffect(() => {
|
||||
const sendLifecycleEvent = (state: AppStateStatus) => {
|
||||
webViewRef.current?.injectJavaScript(
|
||||
buildHostBridgeMessageScript({
|
||||
bridge: 'GenarrativeHostBridge',
|
||||
version: 1,
|
||||
event: 'app.lifecycle',
|
||||
payload: lifecyclePayloadFromAppState(state),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const subscription = AppState.addEventListener(
|
||||
'change',
|
||||
sendLifecycleEvent,
|
||||
);
|
||||
sendLifecycleEvent(AppState.currentState);
|
||||
|
||||
return () => subscription.remove();
|
||||
}, []);
|
||||
|
||||
const handleMessage = (event: WebViewMessageEvent) => {
|
||||
void handleMobileHostBridgeMessage(event.nativeEvent.data, (response) => {
|
||||
webViewRef.current?.injectJavaScript(
|
||||
|
||||
@@ -75,6 +75,7 @@ for (const capability of iosMobileCapabilities) {
|
||||
const switchCase = `case '${capability}':`;
|
||||
if (
|
||||
capability !== 'host.events' &&
|
||||
capability !== 'app.lifecycle' &&
|
||||
capability !== 'navigation.canGoBack' &&
|
||||
!bridgeSource.includes(switchCase)
|
||||
) {
|
||||
@@ -116,6 +117,8 @@ for (const snippet of [
|
||||
"Linking.addEventListener('url'",
|
||||
'buildMobileShellUrlFromDeepLink',
|
||||
'configureMobileHostBridgeNavigation',
|
||||
'AppState.addEventListener',
|
||||
'app.lifecycle',
|
||||
'navigation.canGoBack',
|
||||
'buildHostBridgeMessageScript',
|
||||
]) {
|
||||
@@ -156,6 +159,7 @@ for (const capability of [
|
||||
'appearance.getColorScheme',
|
||||
'share.open',
|
||||
'share.setTarget',
|
||||
'app.lifecycle',
|
||||
'navigation.openNativePage',
|
||||
'navigation.canGoBack',
|
||||
'app.openExternalUrl',
|
||||
|
||||
@@ -174,6 +174,7 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
expect.arrayContaining([
|
||||
'appearance.getColorScheme',
|
||||
'host.events',
|
||||
'app.lifecycle',
|
||||
'navigation.canGoBack',
|
||||
'app.setBadgeCount',
|
||||
]),
|
||||
|
||||
@@ -48,6 +48,7 @@ export const MOBILE_HOST_CAPABILITIES: HostBridgeCapability[] = [
|
||||
'host.getRuntime',
|
||||
'appearance.getColorScheme',
|
||||
'host.events',
|
||||
'app.lifecycle',
|
||||
'share.open',
|
||||
'share.setTarget',
|
||||
'navigation.openNativePage',
|
||||
|
||||
28
apps/mobile-shell/src/mobileShellLifecycle.test.ts
Normal file
28
apps/mobile-shell/src/mobileShellLifecycle.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { lifecyclePayloadFromAppState } from './mobileShellLifecycle';
|
||||
|
||||
describe('mobileShellLifecycle', () => {
|
||||
test('把 React Native AppState 映射为统一 HostBridge 生命周期状态', () => {
|
||||
expect(lifecyclePayloadFromAppState('active')).toEqual({
|
||||
state: 'active',
|
||||
focused: true,
|
||||
nativeState: 'active',
|
||||
});
|
||||
expect(lifecyclePayloadFromAppState('background')).toEqual({
|
||||
state: 'background',
|
||||
focused: false,
|
||||
nativeState: 'background',
|
||||
});
|
||||
expect(lifecyclePayloadFromAppState('inactive')).toEqual({
|
||||
state: 'inactive',
|
||||
focused: false,
|
||||
nativeState: 'inactive',
|
||||
});
|
||||
expect(lifecyclePayloadFromAppState('unknown')).toEqual({
|
||||
state: 'inactive',
|
||||
focused: false,
|
||||
nativeState: 'unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
13
apps/mobile-shell/src/mobileShellLifecycle.ts
Normal file
13
apps/mobile-shell/src/mobileShellLifecycle.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { AppStateStatus } from 'react-native';
|
||||
|
||||
export function lifecyclePayloadFromAppState(state: AppStateStatus) {
|
||||
return {
|
||||
state: state === 'active'
|
||||
? 'active'
|
||||
: state === 'background'
|
||||
? 'background'
|
||||
: 'inactive',
|
||||
focused: state === 'active',
|
||||
nativeState: state,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user