Grapevine_Disease_Detection/VinEye/src/screens/SettingsScreen.tsx
Yanis 06be3483d7 chore(dev): mock seed for testing the Map without scanning
Adds a "Add mock plants" entry under a new Developer section in the
Settings screen, gated by __DEV__ so it never ships in release. It
calls useHistory.seedTestData() which prepends 5 fake ScanRecords
spread across Bordeaux / Bourgogne / Champagne so all the map
features (region chips, markers, rename) can be exercised without
having to actually walk into a vineyard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:32:25 +02:00

345 lines
8.3 KiB
TypeScript

import {
View,
ScrollView,
StyleSheet,
Platform,
Alert,
TouchableOpacity,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useNavigation } from "@react-navigation/native";
import { useTranslation } from "react-i18next";
import { Ionicons } from "@expo/vector-icons";
import i18n from "@/i18n";
import { toast } from "sonner-native";
import { Text } from "@/components/ui/text";
import { colors } from "@/theme/colors";
import { useGameProgress } from "@/hooks/useGameProgress";
import { useHistory } from "@/hooks/useHistory";
interface MenuItem {
icon: string;
label: string;
rightText?: string;
rightColor?: string;
danger?: boolean;
onPress?: () => void;
}
export default function SettingsScreen() {
const { t } = useTranslation();
const navigation = useNavigation();
const { resetProgress } = useGameProgress();
const { clearHistory, seedTestData } = useHistory();
async function handleSeed() {
await seedTestData();
toast.success(t("settings.seedDone"));
}
function handleLanguageToggle() {
const newLang = i18n.language === "fr" ? "en" : "fr";
i18n.changeLanguage(newLang);
}
function handleReset() {
Alert.alert(t("common.confirm"), t("profile.resetConfirm"), [
{ text: t("common.cancel"), style: "cancel" },
{
text: t("profile.resetData"),
style: "destructive",
onPress: async () => {
await resetProgress();
await clearHistory();
},
},
]);
}
const generalItems: MenuItem[] = [
{
icon: "person-outline",
label: t("settings.editProfile"),
},
{
icon: "globe-outline",
label: t("profile.language"),
rightText: i18n.language === "fr" ? "Français" : "English",
onPress: handleLanguageToggle,
},
{
icon: "notifications-outline",
label: t("common.notifications"),
},
{
icon: "shield-outline",
label: t("settings.privacy"),
},
];
const appItems: MenuItem[] = [
{
icon: "diamond-outline",
label: t("settings.premiumStatus"),
rightText: t("settings.inactive"),
rightColor: "#F97316",
},
{
icon: "color-palette-outline",
label: t("settings.appearance"),
},
{
icon: "help-circle-outline",
label: t("settings.helpCenter"),
},
{
icon: "document-text-outline",
label: t("settings.terms"),
},
];
const devItems: MenuItem[] = __DEV__
? [
{
icon: "flask-outline",
label: t("settings.seedTestData"),
onPress: handleSeed,
},
]
: [];
const dangerItems: MenuItem[] = [
{
icon: "trash-outline",
label: t("profile.resetData"),
danger: true,
onPress: handleReset,
},
];
const renderMenuGroup = (items: MenuItem[]) => (
<View style={styles.menuCard}>
{items.map((item, index) => (
<View key={item.label}>
{index > 0 && <View style={styles.divider} />}
<TouchableOpacity
style={styles.menuRow}
activeOpacity={0.5}
onPress={item.onPress}
>
<View
style={[
styles.iconBox,
{ backgroundColor: item.danger ? "#FEF2F2" : "#F8F9FA" },
]}
>
<Ionicons
name={item.icon as any}
size={20}
color={item.danger ? "#EF4444" : "#636E72"}
/>
</View>
<Text
style={[styles.menuLabel, item.danger && styles.menuLabelDanger]}
>
{item.label}
</Text>
<View style={styles.menuRight}>
{item.rightText && (
<Text
style={[
styles.menuRightText,
item.rightColor && { color: item.rightColor },
]}
>
{item.rightText}
</Text>
)}
<Ionicons name="chevron-forward" size={14} color="#D1D1D6" />
</View>
</TouchableOpacity>
</View>
))}
</View>
);
return (
<SafeAreaView style={styles.safe} edges={["top"]}>
{/* Header épuré style Bumble/Apple */}
<View style={styles.header}>
<TouchableOpacity
onPress={() => navigation.goBack()}
style={styles.backBtn}
>
<Ionicons name="chevron-back" size={24} color="#1A1A1A" />
</TouchableOpacity>
<Text style={styles.headerTitle}>{t("common.settings")}</Text>
<View style={{ width: 44 }} />
</View>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
<Text style={styles.sectionLabel}>{t("settings.general")}</Text>
{renderMenuGroup(generalItems)}
<Text style={styles.sectionLabel}>{t("settings.app")}</Text>
{renderMenuGroup(appItems)}
{/* Banner Referral plus "Flat" et moderne */}
<TouchableOpacity style={styles.referCard} activeOpacity={0.9}>
<View style={styles.referContent}>
<Text style={styles.referTitle}>Refer a friend</Text>
<Text style={styles.referBody}>
Get $50 per successful referral
</Text>
</View>
<View style={styles.referIconWrap}>
<Ionicons name="gift" size={28} color="#FFFFFF" />
</View>
</TouchableOpacity>
{devItems.length > 0 && (
<>
<Text style={styles.sectionLabel}>{t("settings.developer")}</Text>
{renderMenuGroup(devItems)}
</>
)}
{renderMenuGroup(dangerItems)}
<Text style={styles.versionText}>VinEye Version 1.0.0</Text>
<View style={{ height: 40 }} />
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: {
flex: 1,
backgroundColor: "#F8F9FB", // Gris encore plus clair/bleuté
},
header: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
paddingVertical: 10,
backgroundColor: "transparent", // Pas de démarcation brutale
},
backBtn: {
width: 44,
height: 44,
alignItems: "center",
justifyContent: "center",
borderRadius: 14,
backgroundColor: "#FFFFFF",
borderWidth: 1,
borderColor: "#F0F0F0",
},
headerTitle: {
fontSize: 18,
fontWeight: "600", // Pas de Bold 900 ici, juste Medium/SemiBold
color: "#1A1A1A",
letterSpacing: -0.4,
},
scrollContent: {
paddingHorizontal: 20,
paddingTop: 10,
},
sectionLabel: {
fontSize: 12,
fontWeight: "500",
color: "#A0A0A0",
marginBottom: 12,
marginLeft: 4,
textTransform: "uppercase",
letterSpacing: 1,
},
menuCard: {
backgroundColor: "#FFFFFF",
borderRadius: 24,
marginBottom: 20,
borderWidth: 1,
borderColor: "#F2F2F2",
},
menuRow: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 16,
},
iconBox: {
width: 36,
height: 36,
borderRadius: 10,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
},
menuLabel: {
flex: 1,
fontSize: 15,
fontWeight: "400", // On reste sur du Regular
color: "#2D3436",
},
menuLabelDanger: {
color: "#EF4444",
},
menuRight: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
menuRightText: {
fontSize: 14,
color: "#B2B2B2",
},
divider: {
height: 1,
backgroundColor: "#F8F9FA",
marginLeft: 60, // Aligné avec le texte, pas l'icône
},
referCard: {
backgroundColor: "#F97316",
borderRadius: 24,
padding: 20,
marginBottom: 20,
flexDirection: "row",
alignItems: "center",
},
referContent: {
flex: 1,
},
referTitle: {
fontSize: 17,
fontWeight: "700",
color: "#FFFFFF",
},
referBody: {
fontSize: 13,
color: "rgba(255,255,255,0.7)",
marginTop: 2,
},
referIconWrap: {
width: 50,
height: 50,
borderRadius: 15,
backgroundColor: "rgba(255,255,255,0.2)",
alignItems: "center",
justifyContent: "center",
},
versionText: {
textAlign: "center",
fontSize: 12,
color: "#D1D1D6",
marginTop: 10,
},
});