feat(message): add embedded mode to ChatScreen for desktop dual-column layout
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m37s
Frontend CI / ota-android (push) Successful in 10m43s
Frontend CI / build-android-apk (push) Successful in 40m4s

Replace EmbeddedChat with unified ChatScreen supporting embedded mode through props. Pass conversation data, layout constraints, and navigation callbacks directly to ChatScreen instead of using separate embedded component. Consolidates dual-column layout logic into single component with conditional rendering based on isEmbedded prop.
This commit is contained in:
lafay
2026-04-13 01:52:43 +08:00
parent fe6a03da5d
commit 4f31926eb5
8 changed files with 131 additions and 1095 deletions

View File

@@ -11,10 +11,13 @@ import {
Alert,
ScrollView,
ActivityIndicator,
Platform,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Image } from 'expo-image';
import { Directory, File, Paths } from 'expo-file-system';
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
import { Text } from '../../components/common';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
@@ -47,11 +50,48 @@ export const DataStorageScreen: React.FC = () => {
const [cacheStats, setCacheStats] = useState<CacheStats | null>(null);
const [asyncStorageSize, setAsyncStorageSize] = useState(0);
const [expoImageCacheSize, setExpoImageCacheSize] = useState(0);
const [loading, setLoading] = useState(true);
const [clearing, setClearing] = useState(false);
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const loadExpoImageCacheSize = useCallback(async (): Promise<number> => {
if (Platform.OS === 'web') return 0;
try {
const cacheDir = Paths.cache;
const sdImageCacheDir = new Directory(cacheDir, 'defaultDiskCache');
const expoImageCacheDir = new Directory(cacheDir, 'expo-image');
const scanDirectorySize = async (dir: Directory): Promise<number> => {
let size = 0;
try {
if (!(await dir.exists)) return 0;
const items = dir.list();
for (const item of items) {
try {
if (item instanceof File) {
const info = await item.info();
if (info.exists && info.size) {
size += info.size;
}
} else if (item instanceof Directory) {
size += await scanDirectorySize(item);
}
} catch {}
}
} catch {}
return size;
};
const totalSize = await scanDirectorySize(sdImageCacheDir) + await scanDirectorySize(expoImageCacheDir);
return totalSize;
} catch (error) {
console.warn('读取 expo-image 缓存大小失败:', error);
return 0;
}
}, []);
const loadStats = useCallback(async () => {
try {
setLoading(true);
@@ -59,6 +99,9 @@ export const DataStorageScreen: React.FC = () => {
const stats = mediaCacheManager.getCacheStats();
setCacheStats(stats);
const imageCacheSize = await loadExpoImageCacheSize();
setExpoImageCacheSize(imageCacheSize);
const keys = await AsyncStorage.getAllKeys();
if (keys.length > 0) {
const items = await AsyncStorage.multiGet(keys);
@@ -77,7 +120,7 @@ export const DataStorageScreen: React.FC = () => {
} finally {
setLoading(false);
}
}, []);
}, [loadExpoImageCacheSize]);
useEffect(() => {
loadStats();
@@ -86,7 +129,7 @@ export const DataStorageScreen: React.FC = () => {
const handleClearCache = () => {
Alert.alert(
'清除缓存',
'确定要清除所有媒体缓存吗?此操作不可撤销。',
'确定要清除所有缓存吗?此操作不可撤销。',
[
{ text: '取消', style: 'cancel' },
{
@@ -96,6 +139,8 @@ export const DataStorageScreen: React.FC = () => {
try {
setClearing(true);
await mediaCacheManager.clearAll();
await Image.clearDiskCache();
await Image.clearMemoryCache();
await loadStats();
Alert.alert('完成', '缓存已清除');
} catch (error) {
@@ -139,28 +184,27 @@ export const DataStorageScreen: React.FC = () => {
);
};
const totalSize = (cacheStats?.totalSize ?? 0) + asyncStorageSize;
const totalSize = (cacheStats?.totalSize ?? 0) + asyncStorageSize + expoImageCacheSize;
const storageItems: StorageStatItem[] = useMemo(() => {
if (!cacheStats) return [];
return [
{
icon: 'image-outline',
label: '图片缓存',
value: cacheStats.imageCount > 0 ? `${cacheStats.imageCount} 个文件` : '暂无',
count: cacheStats.imageCount,
value: expoImageCacheSize > 0 ? `${formatBytes(expoImageCacheSize)}` : cacheStats && cacheStats.imageCount > 0 ? `${cacheStats.imageCount} 个文件` : '暂无',
count: expoImageCacheSize > 0 ? undefined : cacheStats?.imageCount,
},
{
icon: 'video-outline',
label: '视频缓存',
value: cacheStats.videoCount > 0 ? `${cacheStats.videoCount} 个文件` : '暂无',
count: cacheStats.videoCount,
value: cacheStats && cacheStats.videoCount > 0 ? `${cacheStats.videoCount} 个文件` : '暂无',
count: cacheStats?.videoCount,
},
{
icon: 'music-note-outline',
label: '音频缓存',
value: cacheStats.audioCount > 0 ? `${cacheStats.audioCount} 个文件` : '暂无',
count: cacheStats.audioCount,
value: cacheStats && cacheStats.audioCount > 0 ? `${cacheStats.audioCount} 个文件` : '暂无',
count: cacheStats?.audioCount,
},
{
icon: 'file-document-outline',
@@ -168,7 +212,7 @@ export const DataStorageScreen: React.FC = () => {
value: asyncStorageSize > 0 ? formatBytes(asyncStorageSize) : '暂无',
},
];
}, [cacheStats, asyncStorageSize]);
}, [cacheStats, asyncStorageSize, expoImageCacheSize]);
const renderContent = () => {
if (loading) {