fix: 修复 Android 闪退问题 - SecureStore 异步调用

问题:
- NativeStorage 使用了同步方法调用 expo-secure-store
- expo-secure-store 的正确 API 是异步的(getItemAsync/setItemAsync/deleteItemAsync)
- 在 Android 上同步调用不存在的方法导致应用崩溃

修复:
- 将 Storage 接口改为异步方法
- 使用 SecureStore.getItemAsync/setItemAsync/deleteItemAsync
- removeItem 使用 deleteItemAsync 而不是设置空字符串

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
imeepos
2025-11-12 14:44:42 +08:00
parent 3ebcc24e9b
commit 67f3c0b70e
10 changed files with 101 additions and 89 deletions

View File

@@ -1,10 +1,29 @@
import { ThemedView } from '@/components/themed-view';
import { VideoView, useVideoPlayer } from 'expo-video';
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
import { Platform } from 'react-native';
import { ThemedText } from '@/components/themed-text';
import { getTemplateGeneration } from '@/lib/api/template-runs';
import * as Clipboard from 'expo-clipboard';
import * as FileSystem from 'expo-file-system';
import * as MediaLibrary from 'expo-media-library';
import { router, useLocalSearchParams } from 'expo-router';
import { VideoView, useVideoPlayer } from 'expo-video';
import {
ArrowLeft,
Copy,
Download,
Maximize2,
Plus,
X,
} from 'lucide-react';
import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Dimensions,
Image, Modal, Platform, ScrollView,
StyleSheet, Text,
TouchableOpacity,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
// ResizeMode 兼容映射 - 移除 'stretch'expo-video 不支持
const ResizeMode = {
@@ -12,43 +31,33 @@ const ResizeMode = {
COVER: 'cover' as const,
STRETCH: 'fill' as const, // 使用 'fill' 替代 'stretch'
};
import { router, useLocalSearchParams } from 'expo-router';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import {
Copy,
Download,
Maximize2,
Plus,
X,
ArrowLeft,
} from 'lucide-react';
import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Dimensions,
Image,
ImageStyle,
Modal,
ScrollView,
StyleSheet,
StyleProp,
Text,
TouchableOpacity,
View,
ViewStyle,
FlatList,
} from 'react-native';
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
const HERO_HEIGHT = screenWidth * 0.58;
const VIDEO_HEIGHT = screenHeight * 0.6;
// 原生视频播放组件
const NativeVideoPlayer: React.FC<{ url: string; style?: any }> = ({ url, style }) => {
const player = useVideoPlayer(url, player => {
player.loop = false;
});
return (
<VideoView
player={player}
style={style}
contentFit="contain"
allowsFullscreen
allowsPictureInPicture
/>
);
};
interface ResultWithTemplate {
id: string;
userId: string;
templateId: string;
type: 'TEXT' | 'IMAGE' | 'VIDEO';
type: 'UNKNOWN' | 'TEXT' | 'IMAGE' | 'VIDEO';
resultUrl: string[];
status: 'pending' | 'running' | 'completed' | 'failed';
creditsCost?: number;
@@ -233,7 +242,7 @@ export default function ResultPage() {
if (!result || result.type !== 'TEXT') return;
try {
await navigator.clipboard?.writeText?.(result.resultUrl[0]);
await Clipboard.setStringAsync(result.resultUrl[0]);
Alert.alert('复制成功', '文本已复制到剪贴板');
} catch (error) {
Alert.alert('复制失败', '无法复制文本');
@@ -306,9 +315,10 @@ export default function ResultPage() {
playsInline
/>
) : (
<View style={{ width: '100%', height: VIDEO_HEIGHT }}>
<ThemedText>Video: {url}</ThemedText>
</View>
<NativeVideoPlayer
url={url}
style={{ width: '100%', height: VIDEO_HEIGHT }}
/>
)
) : (
<Image
@@ -435,9 +445,10 @@ export default function ResultPage() {
controls
/>
) : (
<View style={styles.fullscreenMediaContent}>
<ThemedText>Video Player</ThemedText>
</View>
<NativeVideoPlayer
url={fullscreenContent.url}
style={styles.fullscreenMediaContent}
/>
)}
</>
)}