mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 08:06:13 +00:00
298 lines
10 KiB
TypeScript
298 lines
10 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { View, Text, StyleSheet } from 'react-native';
|
|
import {
|
|
Folder, FileText, FileCode, FileAudio, File, Image as ImageIcon,
|
|
} from 'lucide-react-native';
|
|
import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section';
|
|
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
|
import { useColors } from '../../theme/colors';
|
|
import {
|
|
useSettingsStore,
|
|
type FilesViewMode,
|
|
type FilesSortKey,
|
|
type FilesSortDir,
|
|
} from '../../stores/settings-store';
|
|
import { useLocaleStore } from '../../stores/locale-store';
|
|
|
|
interface FilesPrefs {
|
|
defaultViewMode: FilesViewMode;
|
|
defaultSortKey: FilesSortKey;
|
|
defaultSortDir: FilesSortDir;
|
|
showIcons: boolean;
|
|
coloredIcons: boolean;
|
|
showThumbnails: boolean;
|
|
showHiddenFiles: boolean;
|
|
}
|
|
|
|
interface SampleFile {
|
|
name: string;
|
|
isFolder: boolean;
|
|
size: number;
|
|
modified: string;
|
|
hidden?: boolean;
|
|
}
|
|
|
|
const SAMPLE: SampleFile[] = [
|
|
{ name: 'Documents', isFolder: true, size: 0, modified: '03-10' },
|
|
{ name: 'Photos', isFolder: true, size: 0, modified: '03-14' },
|
|
{ name: 'report.pdf', isFolder: false, size: 245000, modified: '03-15' },
|
|
{ name: 'notes.md', isFolder: false, size: 1200, modified: '03-12' },
|
|
{ name: 'song.mp3', isFolder: false, size: 5200000, modified: '03-01' },
|
|
{ name: '.config', isFolder: false, size: 340, modified: '02-20', hidden: true },
|
|
];
|
|
|
|
function formatSize(b: number) {
|
|
if (b === 0) return '-';
|
|
if (b < 1024) return `${b} B`;
|
|
if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)} KB`;
|
|
return `${(b / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
function getIcon(c: ThemePalette, file: SampleFile, colored: boolean, sz: number) {
|
|
if (file.isFolder) return <Folder size={sz} color={colored ? '#60a5fa' : c.mutedForeground} />;
|
|
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
switch (ext) {
|
|
case 'jpg': case 'png': case 'gif':
|
|
return <ImageIcon size={sz} color={colored ? '#4ade80' : c.mutedForeground} />;
|
|
case 'mp3': case 'wav':
|
|
return <FileAudio size={sz} color={colored ? '#a78bfa' : c.mutedForeground} />;
|
|
case 'pdf':
|
|
return <FileText size={sz} color={colored ? '#f87171' : c.mutedForeground} />;
|
|
case 'md': case 'json': case 'js': case 'ts':
|
|
return <FileCode size={sz} color={colored ? '#fbbf24' : c.mutedForeground} />;
|
|
default:
|
|
return <File size={sz} color={c.mutedForeground} />;
|
|
}
|
|
}
|
|
|
|
function FilesPreview({ prefs }: { prefs: FilesPrefs }) {
|
|
const c = useColors();
|
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
|
const t = useLocaleStore((s) => s.t);
|
|
const files = SAMPLE.filter((f) => {
|
|
if (!prefs.showHiddenFiles && f.hidden) return false;
|
|
return true;
|
|
}).sort((a, b) => {
|
|
if (a.isFolder !== b.isFolder) return a.isFolder ? -1 : 1;
|
|
let cmp = 0;
|
|
if (prefs.defaultSortKey === 'name') cmp = a.name.localeCompare(b.name);
|
|
if (prefs.defaultSortKey === 'size') cmp = a.size - b.size;
|
|
if (prefs.defaultSortKey === 'modified') cmp = a.modified.localeCompare(b.modified);
|
|
return prefs.defaultSortDir === 'desc' ? -cmp : cmp;
|
|
});
|
|
|
|
if (prefs.defaultViewMode === 'grid') {
|
|
return (
|
|
<View style={styles.previewBox}>
|
|
<View style={styles.gridWrap}>
|
|
{files.map((f) => (
|
|
<View key={f.name} style={[styles.gridItem, f.hidden && { opacity: 0.5 }]}>
|
|
{prefs.showIcons ? getIcon(c, f, prefs.coloredIcons, 24) : <View style={{ width: 24, height: 24 }} />}
|
|
<Text style={styles.gridName} numberOfLines={1}>{f.name}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.previewBox}>
|
|
<View style={styles.listHeader}>
|
|
<Text style={[styles.listHeaderText, { flex: 1 }]}>{t('files.name', 'Name')}</Text>
|
|
<Text style={[styles.listHeaderText, { width: 60, textAlign: 'right' }]}>{t('files.size', 'Size')}</Text>
|
|
<Text style={[styles.listHeaderText, { width: 60, textAlign: 'right' }]}>{t('files.modified', 'Modified')}</Text>
|
|
</View>
|
|
{files.map((f) => (
|
|
<View key={f.name} style={[styles.listRow, f.hidden && { opacity: 0.5 }]}>
|
|
{prefs.showIcons && getIcon(c, f, prefs.coloredIcons, 14)}
|
|
<Text
|
|
style={[styles.listName, f.isFolder && { fontWeight: '500' }]}
|
|
numberOfLines={1}
|
|
>
|
|
{f.name}
|
|
</Text>
|
|
<Text style={styles.listMeta}>{formatSize(f.size)}</Text>
|
|
<Text style={styles.listMeta}>{f.modified}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export function FilesSettings() {
|
|
const c = useColors();
|
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
|
const hydrated = useSettingsStore((s) => s.hydrated);
|
|
const hydrate = useSettingsStore((s) => s.hydrate);
|
|
const set = useSettingsStore((s) => s.updateSetting);
|
|
const t = useLocaleStore((s) => s.t);
|
|
|
|
const defaultViewMode = useSettingsStore((s) => s.filesDefaultViewMode);
|
|
const defaultSortKey = useSettingsStore((s) => s.filesDefaultSortKey);
|
|
const defaultSortDir = useSettingsStore((s) => s.filesDefaultSortDir);
|
|
const showIcons = useSettingsStore((s) => s.filesShowIcons);
|
|
const coloredIcons = useSettingsStore((s) => s.filesColoredIcons);
|
|
const showThumbnails = useSettingsStore((s) => s.filesShowThumbnails);
|
|
const showHiddenFiles = useSettingsStore((s) => s.filesShowHiddenFiles);
|
|
|
|
useEffect(() => {
|
|
if (!hydrated) void hydrate();
|
|
}, [hydrated, hydrate]);
|
|
|
|
const prefs: FilesPrefs = {
|
|
defaultViewMode,
|
|
defaultSortKey,
|
|
defaultSortDir,
|
|
showIcons,
|
|
coloredIcons,
|
|
showThumbnails,
|
|
showHiddenFiles,
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View>
|
|
<Text style={styles.previewLabel}>{t('files.preview', 'Preview')}</Text>
|
|
<FilesPreview prefs={prefs} />
|
|
</View>
|
|
|
|
<SettingsSection title={t('files.settings_display', 'Display')}>
|
|
<SettingItem
|
|
label={t('files.settings_default_view', 'Default View')}
|
|
description={t('files.settings_default_view_desc', 'Choose between grid and list layout')}
|
|
>
|
|
<RadioGroup
|
|
value={defaultViewMode}
|
|
onChange={(v) => set('filesDefaultViewMode', v as FilesViewMode)}
|
|
options={[
|
|
{ value: 'list', label: t('files.list_view', 'List') },
|
|
{ value: 'grid', label: t('files.grid_view', 'Grid') },
|
|
]}
|
|
/>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label={t('files.settings_default_sort', 'Default Sort')}
|
|
description={t('files.settings_default_sort_desc', 'Choose the default sorting for files')}
|
|
>
|
|
<RadioGroup
|
|
value={defaultSortKey}
|
|
onChange={(v) => set('filesDefaultSortKey', v as FilesSortKey)}
|
|
options={[
|
|
{ value: 'name', label: t('files.name', 'Name') },
|
|
{ value: 'size', label: t('files.size', 'Size') },
|
|
{ value: 'modified', label: t('files.modified', 'Modified') },
|
|
]}
|
|
/>
|
|
</SettingItem>
|
|
|
|
<SettingItem
|
|
label={t('files.settings_sort_direction', 'Sort Direction')}
|
|
description={t('files.settings_sort_direction_desc', 'Choose ascending or descending order')}
|
|
>
|
|
<RadioGroup
|
|
value={defaultSortDir}
|
|
onChange={(v) => set('filesDefaultSortDir', v as FilesSortDir)}
|
|
options={[
|
|
{ value: 'asc', label: t('files.settings_ascending', 'Ascending') },
|
|
{ value: 'desc', label: t('files.settings_descending', 'Descending') },
|
|
]}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection title={t('files.settings_icons', 'Icons')}>
|
|
<SettingItem
|
|
label={t('files.settings_show_icons', 'Show File Icons')}
|
|
description={t('files.settings_show_icons_desc', 'Display icons next to files and folders')}
|
|
>
|
|
<ToggleSwitch checked={showIcons} onChange={(v) => set('filesShowIcons', v)} />
|
|
</SettingItem>
|
|
<SettingItem
|
|
label={t('files.settings_colored_icons', 'Colored Icons')}
|
|
description={t('files.settings_colored_icons_desc', 'Use colorful icons instead of monochrome')}
|
|
>
|
|
<ToggleSwitch
|
|
checked={coloredIcons}
|
|
onChange={(v) => set('filesColoredIcons', v)}
|
|
disabled={!showIcons}
|
|
/>
|
|
</SettingItem>
|
|
<SettingItem
|
|
label={t('files.settings_show_thumbnails', 'Show Thumbnails')}
|
|
description={t('files.settings_show_thumbnails_desc', 'Display image previews instead of icons for image files')}
|
|
>
|
|
<ToggleSwitch
|
|
checked={showThumbnails}
|
|
onChange={(v) => set('filesShowThumbnails', v)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
|
|
<SettingsSection title={t('files.settings_behavior', 'Behavior')}>
|
|
<SettingItem
|
|
label={t('files.settings_show_hidden', 'Show Hidden Files')}
|
|
description={t('files.settings_show_hidden_desc', 'Display files and folders that start with a dot')}
|
|
>
|
|
<ToggleSwitch
|
|
checked={showHiddenFiles}
|
|
onChange={(v) => set('filesShowHiddenFiles', v)}
|
|
/>
|
|
</SettingItem>
|
|
</SettingsSection>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function makeStyles(c: ThemePalette) {
|
|
return StyleSheet.create({
|
|
container: { gap: spacing.xxxl },
|
|
previewLabel: { ...typography.bodyMedium, color: c.text, marginBottom: spacing.sm },
|
|
previewBox: {
|
|
borderRadius: radius.md,
|
|
borderWidth: 1,
|
|
borderColor: c.border,
|
|
backgroundColor: c.background,
|
|
overflow: 'hidden',
|
|
minHeight: 160,
|
|
},
|
|
listHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.sm,
|
|
paddingVertical: 4,
|
|
paddingHorizontal: spacing.sm,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: c.border,
|
|
backgroundColor: c.muted,
|
|
},
|
|
listHeaderText: { fontSize: 10, fontWeight: '500', color: c.mutedForeground },
|
|
listRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.sm,
|
|
paddingVertical: 6,
|
|
paddingHorizontal: spacing.sm,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: c.border,
|
|
},
|
|
listName: { fontSize: 11, color: c.text, flex: 1 },
|
|
listMeta: { fontSize: 10, color: c.mutedForeground, width: 60, textAlign: 'right' },
|
|
gridWrap: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 6,
|
|
padding: spacing.sm,
|
|
},
|
|
gridItem: {
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: spacing.sm,
|
|
width: 72,
|
|
borderRadius: radius.sm,
|
|
},
|
|
gridName: { fontSize: 9, color: c.text, textAlign: 'center' },
|
|
});
|
|
}
|