feat: add navigation with tab bar and screen layouts
Expo Router file-based navigation with glass tab bar, 3 tabs (Créations, Explorer, Profil), Phosphor icons, modal picker, and fade transitions.
This commit is contained in:
242
app/(tabs)/profile.tsx
Normal file
242
app/(tabs)/profile.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
import { ScrollView, StyleSheet, Switch, Text, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||
import { GlassCard } from "@/components/glass";
|
||||
import { useSettingsStore } from "@/stores/settings.store";
|
||||
import { useWallpaperStore } from "@/stores/wallpaper.store";
|
||||
import { colors, typography, spacing, layout } from "@/theme";
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const settings = useSettingsStore();
|
||||
const wallpaperCount = useWallpaperStore((s) => s.savedWallpapers.length);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={[styles.container, { paddingTop: insets.top + spacing.md }]}
|
||||
contentContainerStyle={styles.content}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Animated.View entering={FadeInDown.duration(500)}>
|
||||
<GlassCard variant="subtle" radius="lg" padding={16}>
|
||||
<Text style={typography.title}>Profil</Text>
|
||||
<Text style={[typography.caption, { marginTop: 2 }]}>
|
||||
Préférences & réglages
|
||||
</Text>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* Stats */}
|
||||
<Animated.View entering={FadeInDown.delay(100).duration(400)} style={styles.section}>
|
||||
<GlassCard variant="medium" radius="lg">
|
||||
<Text style={typography.label}>Statistiques</Text>
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.stat}>
|
||||
<Text style={typography.title}>{wallpaperCount}</Text>
|
||||
<Text style={typography.caption}>Créations</Text>
|
||||
</View>
|
||||
<View style={styles.statDivider} />
|
||||
<View style={styles.stat}>
|
||||
<Text style={typography.title}>5</Text>
|
||||
<Text style={typography.caption}>Animations</Text>
|
||||
</View>
|
||||
</View>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* Settings */}
|
||||
<Animated.View entering={FadeInDown.delay(200).duration(400)} style={styles.section}>
|
||||
<GlassCard variant="medium" radius="lg">
|
||||
<Text style={[typography.label, { marginBottom: 16 }]}>Réglages</Text>
|
||||
|
||||
<SettingRow
|
||||
label="Retour haptique"
|
||||
description="Vibrations au toucher"
|
||||
value={settings.hapticFeedback}
|
||||
onToggle={settings.setHapticFeedback}
|
||||
/>
|
||||
|
||||
<SettingRow
|
||||
label="Depth map auto"
|
||||
description="Générer automatiquement (V1.5)"
|
||||
value={settings.autoDepthMap}
|
||||
onToggle={settings.setAutoDepthMap}
|
||||
/>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* Export Quality */}
|
||||
<Animated.View entering={FadeInDown.delay(300).duration(400)} style={styles.section}>
|
||||
<GlassCard variant="medium" radius="lg">
|
||||
<Text style={[typography.label, { marginBottom: 16 }]}>
|
||||
Qualité d'export
|
||||
</Text>
|
||||
{(["low", "medium", "high"] as const).map((q) => (
|
||||
<QualityOption
|
||||
key={q}
|
||||
label={q === "low" ? "Économique" : q === "medium" ? "Standard" : "Maximum"}
|
||||
description={
|
||||
q === "low"
|
||||
? "15 FPS, compression forte"
|
||||
: q === "medium"
|
||||
? "24 FPS, bon compromis"
|
||||
: "30 FPS, qualité maximale"
|
||||
}
|
||||
selected={settings.exportQuality === q}
|
||||
onPress={() => settings.setExportQuality(q)}
|
||||
/>
|
||||
))}
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
{/* About */}
|
||||
<Animated.View entering={FadeInDown.delay(400).duration(400)} style={styles.section}>
|
||||
<GlassCard variant="subtle" radius="lg">
|
||||
<Text style={typography.label}>À propos</Text>
|
||||
<Text style={[typography.bodySecondary, { marginTop: 8 }]}>
|
||||
Lively v1.0.0
|
||||
</Text>
|
||||
<Text style={[typography.caption, { marginTop: 4 }]}>
|
||||
Fonds d'écran animés depuis votre galerie
|
||||
</Text>
|
||||
</GlassCard>
|
||||
</Animated.View>
|
||||
|
||||
<View style={{ height: layout.tabBarHeight + 40 }} />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingRow({
|
||||
label,
|
||||
description,
|
||||
value,
|
||||
onToggle,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
value: boolean;
|
||||
onToggle: (v: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<View style={styles.settingRow}>
|
||||
<View style={styles.settingText}>
|
||||
<Text style={typography.body}>{label}</Text>
|
||||
<Text style={typography.caption}>{description}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onToggle}
|
||||
trackColor={{
|
||||
false: "rgba(255,255,255,0.1)",
|
||||
true: "rgba(255,255,255,0.3)",
|
||||
}}
|
||||
thumbColor={value ? colors.text.primary : colors.text.tertiary}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function QualityOption({
|
||||
label,
|
||||
description,
|
||||
selected,
|
||||
onPress,
|
||||
}: {
|
||||
label: string;
|
||||
description: string;
|
||||
selected: boolean;
|
||||
onPress: () => void;
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[styles.qualityRow, selected && styles.qualitySelected]}
|
||||
onTouchEnd={onPress}
|
||||
>
|
||||
<View style={styles.settingText}>
|
||||
<Text style={[typography.body, selected && { color: colors.text.primary }]}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text style={typography.caption}>{description}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.radio,
|
||||
selected && styles.radioSelected,
|
||||
]}
|
||||
>
|
||||
{selected && <View style={styles.radioDot} />}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background,
|
||||
paddingHorizontal: layout.screenPadding,
|
||||
},
|
||||
content: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
section: {
|
||||
marginTop: spacing.md,
|
||||
},
|
||||
statsRow: {
|
||||
flexDirection: "row",
|
||||
marginTop: 16,
|
||||
alignItems: "center",
|
||||
},
|
||||
stat: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
},
|
||||
statDivider: {
|
||||
width: 1,
|
||||
height: 40,
|
||||
backgroundColor: colors.glass.border,
|
||||
},
|
||||
settingRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.glass.highlight,
|
||||
},
|
||||
settingText: {
|
||||
flex: 1,
|
||||
marginRight: 16,
|
||||
},
|
||||
qualityRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 12,
|
||||
marginBottom: 4,
|
||||
},
|
||||
qualitySelected: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.06)",
|
||||
},
|
||||
radio: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
borderWidth: 1.5,
|
||||
borderColor: colors.text.tertiary,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
radioSelected: {
|
||||
borderColor: colors.text.primary,
|
||||
},
|
||||
radioDot: {
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.text.primary,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user