Skip to content

Commit 4baec97

Browse files
authored
Merge pull request #30 from ac666666666/feat/ac-feature
fix(web): 补全缺失的 web shims 并修复渲染崩溃问题
2 parents e0a845e + 8725a79 commit 4baec97

11 files changed

Lines changed: 201 additions & 14 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
inclusion: always
3+
---
4+
5+
# 提交规范(Conventional Commits)
6+
7+
所有 git commit 必须遵循以下格式:
8+
9+
```
10+
<type>(<scope>): <描述>
11+
12+
[可选正文]
13+
```
14+
15+
## 类型说明
16+
17+
| 类型 | 含义 |
18+
|------|------|
19+
| feat | 新功能 |
20+
| fix | Bug 修复 |
21+
| refactor | 重构(不改变功能) |
22+
| docs | 文档更新 |
23+
| chore | 构建脚本、依赖更新等 |
24+
| style | 代码格式(不影响逻辑) |
25+
| perf | 性能优化 |
26+
27+
## 示例
28+
29+
```
30+
feat(danmaku): 添加弹幕字体大小设置
31+
fix(player): 修复 DASH MPD 解析在 Android 12 上崩溃的问题
32+
docs: 更新 README 快速开始步骤
33+
```

app/_layout.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { useTheme } from '../utils/theme';
1010
import { MiniPlayer } from '../components/MiniPlayer';
1111
import * as Sentry from '@sentry/react-native';
1212
import { ErrorBoundary } from '@sentry/react-native';
13+
import { useFonts } from 'expo-font';
14+
import { Ionicons } from '@expo/vector-icons';
1315

1416
Sentry.init({
1517
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN ?? '',
@@ -24,13 +26,18 @@ function RootLayout() {
2426
const restoreSettings = useSettingsStore(s => s.restore);
2527
const darkMode = useSettingsStore(s => s.darkMode);
2628

29+
const [fontsLoaded] = useFonts({
30+
...Ionicons.font,
31+
});
32+
2733
useEffect(() => {
2834
restore();
2935
loadDownloads();
3036
restoreSettings();
31-
3237
}, []);
3338

39+
if (!fontsLoaded) return null;
40+
3441
return (
3542
<SafeAreaProvider>
3643
<StatusBar style={darkMode ? 'light' : 'dark'} />

metro.config.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
const path = require('path');
2-
const {
3-
getSentryExpoConfig
4-
} = require("@sentry/react-native/metro");
2+
const { getSentryExpoConfig } = require("@sentry/react-native/metro");
53

64
const config = getSentryExpoConfig(__dirname);
75

6+
// Ensure shims directory is watched by Metro
7+
config.watchFolders = [...(config.watchFolders ?? []), path.resolve(__dirname, 'shims')];
8+
89
const originalResolveRequest = config.resolver.resolveRequest;
910

11+
const WEB_SHIMS = {
12+
'react-native-pager-view': 'shims/react-native-pager-view.web.tsx',
13+
'@sentry/react-native': 'shims/sentry-react-native.web.tsx',
14+
'@dr.pogodin/react-native-static-server': 'shims/react-native-static-server.web.ts',
15+
'expo-network': 'shims/expo-network.web.ts',
16+
'expo-intent-launcher': 'shims/expo-intent-launcher.web.ts',
17+
'react-native-video': 'shims/react-native-video.web.tsx',
18+
'expo-file-system': 'shims/expo-file-system.web.ts',
19+
'expo-file-system/legacy': 'shims/expo-file-system.web.ts',
20+
'expo-clipboard': 'shims/expo-clipboard.web.ts',
21+
};
22+
1023
config.resolver.resolveRequest = (context, moduleName, platform) => {
11-
if (platform === 'web' && moduleName === 'react-native-pager-view') {
24+
if (platform === 'web' && WEB_SHIMS[moduleName]) {
1225
return {
13-
filePath: path.resolve(__dirname, 'shims/react-native-pager-view.web.tsx'),
26+
filePath: path.resolve(__dirname, WEB_SHIMS[moduleName]),
1427
type: 'sourceFile',
1528
};
1629
}
@@ -20,4 +33,4 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
2033
return context.resolveRequest(context, moduleName, platform);
2134
};
2235

23-
module.exports = config;
36+
module.exports = config;

shims/expo-clipboard.web.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** Web shim for expo-clipboard - use native browser clipboard API */
2+
export async function setStringAsync(text: string): Promise<void> {
3+
try { await navigator.clipboard.writeText(text); } catch {}
4+
}
5+
export async function getStringAsync(): Promise<string> {
6+
try { return await navigator.clipboard.readText(); } catch { return ''; }
7+
}

shims/expo-file-system.web.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** Web shim for expo-file-system */
2+
export const documentDirectory = '';
3+
export const cacheDirectory = '';
4+
export async function getInfoAsync(_uri: string) { return { exists: false, isDirectory: false }; }
5+
export async function readAsStringAsync(_uri: string) { return ''; }
6+
export async function writeAsStringAsync(_uri: string, _contents: string) {}
7+
export async function deleteAsync(_uri: string) {}
8+
export async function moveAsync(_opts: any) {}
9+
export async function copyAsync(_opts: any) {}
10+
export async function makeDirectoryAsync(_uri: string) {}
11+
export async function getContentUriAsync(_uri: string) { return ''; }
12+
export function createDownloadResumable(_url: string, _fileUri: string, _opts?: any, _cb?: any) {
13+
return { downloadAsync: async () => ({}) };
14+
}

shims/expo-intent-launcher.web.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** Web shim for expo-intent-launcher - no-op for web */
2+
export async function startActivityAsync(_activity: string, _params?: unknown): Promise<unknown> {
3+
return {};
4+
}
5+
export const ActivityAction = {};

shims/expo-network.web.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/** Web shim for expo-network */
2+
export enum NetworkStateType {
3+
NONE = 0, UNKNOWN = 1, CELLULAR = 2, WIFI = 3, BLUETOOTH = 4,
4+
ETHERNET = 5, WIMAX = 6, VPN = 7, OTHER = 8,
5+
}
6+
export async function getNetworkStateAsync() {
7+
return { isConnected: navigator.onLine, isInternetReachable: navigator.onLine, type: NetworkStateType.UNKNOWN };
8+
}
9+
export async function getIpAddressAsync(): Promise<string> { return '0.0.0.0'; }
10+
export async function isAirplaneModeEnabledAsync(): Promise<boolean> { return false; }

shims/react-native-pager-view.web.tsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/**
22
* Web shim for react-native-pager-view.
3-
* eas update exports for web; this replaces the native-only module
4-
* with a simple View-based container that renders the first child only.
3+
* Supports setPage/setPageWithoutAnimation via imperative handle.
54
*/
65
import React from 'react';
76
import { View, type ViewStyle } from 'react-native';
@@ -11,16 +10,35 @@ interface PagerViewProps {
1110
style?: ViewStyle;
1211
initialPage?: number;
1312
scrollEnabled?: boolean;
14-
onPageSelected?: (e: any) => void;
13+
onPageSelected?: (e: { nativeEvent: { position: number } }) => void;
14+
onPageScrollStateChanged?: (e: any) => void;
1515
[key: string]: any;
1616
}
1717

18-
const PagerView = React.forwardRef<View, PagerViewProps>(
19-
({ children, style, initialPage = 0 }, ref) => {
18+
export interface PagerViewHandle {
19+
setPage: (page: number) => void;
20+
setPageWithoutAnimation: (page: number) => void;
21+
}
22+
23+
const PagerView = React.forwardRef<PagerViewHandle, PagerViewProps>(
24+
({ children, style, initialPage = 0, onPageSelected }, ref) => {
25+
const [currentPage, setCurrentPage] = React.useState(initialPage);
2026
const pages = React.Children.toArray(children);
27+
28+
React.useImperativeHandle(ref, () => ({
29+
setPage(page: number) {
30+
setCurrentPage(page);
31+
onPageSelected?.({ nativeEvent: { position: page } });
32+
},
33+
setPageWithoutAnimation(page: number) {
34+
setCurrentPage(page);
35+
onPageSelected?.({ nativeEvent: { position: page } });
36+
},
37+
}));
38+
2139
return (
22-
<View ref={ref} style={[{ flex: 1 }, style]}>
23-
{pages[initialPage] ?? pages[0] ?? null}
40+
<View style={[{ flex: 1 }, style]}>
41+
{pages[currentPage] ?? pages[0] ?? null}
2442
</View>
2543
);
2644
},
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** Web shim for @dr.pogodin/react-native-static-server - no-op for web */
2+
export default class StaticServer {
3+
constructor(_port?: number, _root?: string, _options?: unknown) {}
4+
start(): Promise<string> { return Promise.resolve(''); }
5+
stop(): Promise<void> { return Promise.resolve(); }
6+
isRunning(): boolean { return false; }
7+
get origin(): string { return ''; }
8+
}

shims/react-native-video.web.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/** Web shim for react-native-video - uses HTML5 <video> element */
2+
import React from 'react';
3+
4+
interface VideoProps {
5+
source?: { uri?: string } | number;
6+
style?: React.CSSProperties;
7+
paused?: boolean;
8+
muted?: boolean;
9+
repeat?: boolean;
10+
onLoad?: (data: unknown) => void;
11+
onError?: (error: unknown) => void;
12+
onProgress?: (data: unknown) => void;
13+
onEnd?: () => void;
14+
resizeMode?: string;
15+
[key: string]: unknown;
16+
}
17+
18+
const Video = React.forwardRef<HTMLVideoElement, VideoProps>(
19+
({ source, style, paused, muted, repeat, onLoad, onError, onProgress, onEnd }, ref) => {
20+
const uri = typeof source === 'object' && source !== null ? (source as { uri?: string }).uri : undefined;
21+
return (
22+
<video
23+
ref={ref}
24+
src={uri}
25+
style={style as React.CSSProperties}
26+
autoPlay={!paused}
27+
muted={muted}
28+
loop={repeat}
29+
onLoadedData={onLoad ? () => onLoad({}) : undefined}
30+
onError={onError ? (e) => onError(e) : undefined}
31+
onTimeUpdate={onProgress ? (e) => {
32+
const t = e.currentTarget;
33+
onProgress({ currentTime: t.currentTime, seekableDuration: t.duration });
34+
} : undefined}
35+
onEnded={onEnd}
36+
playsInline
37+
/>
38+
);
39+
}
40+
);
41+
42+
export default Video;

0 commit comments

Comments
 (0)