Files
native/src/screens/FilesScreen.tsx
T
Linus Rath cf3f919fa2 feat: enhance SettingsScreen with dynamic localization and theming
- Integrated useColors hook for dynamic theming in SettingsScreen.
- Updated tab labels and titles to use localized strings.
- Refactored styles to utilize theme palette for consistent styling.
- Improved locale subscription to re-render labels on language change.

feat: implement useSheetDrag hook for bottom sheet gesture handling

- Added useSheetDrag hook to manage drag-to-dismiss functionality for bottom sheets.
- Configured pan responder for handling drag gestures and dismissing the sheet based on distance and velocity.

refactor: restructure color and token management

- Introduced useColors and useResolvedTheme hooks for better theme management.
- Separated light and dark color palettes for improved maintainability.
- Updated tokens to reflect new theming structure and ensure consistency across components.
2026-04-27 13:30:45 +02:00

179 lines
4.4 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react';
import {
ActivityIndicator,
FlatList,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { FileText, Folder, HardDrive } from 'lucide-react-native';
import { getFileNodes } from '../api/files';
import type { FileNode } from '../api/types';
import { spacing, typography, type ThemePalette } from '../theme/tokens';
import { useColors } from '../theme/colors';
function formatFileSize(bytes?: number): string {
if (bytes == null) return '';
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
export default function FilesScreen() {
const c = useColors();
const styles = React.useMemo(() => makeStyles(c), [c]);
const [files, setFiles] = useState<FileNode[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadFiles = useCallback(async () => {
setLoading(true);
setError(null);
try {
const nodes = await getFileNodes(null);
setFiles(nodes);
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to load files');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void loadFiles();
}, [loadFiles]);
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color={c.primary} />
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>{error}</Text>
<TouchableOpacity style={styles.retryButton} onPress={loadFiles}>
<Text style={styles.retryText}>Retry</Text>
</TouchableOpacity>
</View>
);
}
if (files.length === 0) {
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Files</Text>
</View>
<View style={styles.center}>
<HardDrive size={48} color={c.textMuted} />
<Text style={styles.emptyText}>No files yet</Text>
</View>
</View>
);
}
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Files</Text>
</View>
<FlatList
data={files}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.fileRow}>
{item.type === 'directory' ? (
<Folder size={20} color={c.primary} />
) : (
<FileText size={20} color={c.textMuted} />
)}
<View style={styles.fileInfo}>
<Text style={styles.fileName} numberOfLines={1}>
{item.name}
</Text>
{item.size != null && (
<Text style={styles.fileSize}>{formatFileSize(item.size)}</Text>
)}
</View>
</View>
)}
/>
</View>
);
}
function makeStyles(c: ThemePalette) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: c.background,
},
header: {
paddingTop: 60,
paddingHorizontal: spacing.md,
paddingBottom: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: c.border,
},
title: {
fontSize: typography.h2.fontSize,
fontWeight: '700' as const,
color: c.text,
},
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: spacing.sm,
},
emptyText: {
color: c.textMuted,
fontSize: typography.body.fontSize,
marginTop: spacing.sm,
},
errorText: {
color: c.error,
fontSize: typography.body.fontSize,
textAlign: 'center',
paddingHorizontal: spacing.lg,
},
retryButton: {
marginTop: spacing.md,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
backgroundColor: c.primary,
borderRadius: 8,
},
retryText: {
color: c.primaryForeground,
fontWeight: '600' as const,
},
fileRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.sm,
paddingHorizontal: spacing.md,
borderBottomWidth: 1,
borderBottomColor: c.border,
gap: spacing.sm,
},
fileInfo: {
flex: 1,
},
fileName: {
fontSize: typography.body.fontSize,
color: c.text,
},
fileSize: {
fontSize: typography.caption.fontSize,
color: c.textMuted,
marginTop: 2,
},
});
}