Animations FadeInDown.springify().damping(16) avec stagger 60ms entre sections du HomeScreen : - SearchHeader (delay 0) - SearchSection (delay 60) - RecentScans (delay 120) - FrequentDiseasesHorizontal (delay 200) - PracticalGuides (delay 280) Skeleton loading states : - LargeDiseaseCardCompactSkeleton : nouveau, simule la structure compact (badge + icon + title + desc + footer) — utilisé dans FrequentDiseasesHorizontal en remplacement de CarouselCardSkeleton - ScanListItemSkeleton : nouveau, simule image 64x64 + name + status pill + time + confidence tile — utilisé dans RecentScans - RecentScans / PracticalGuides : nouveau style cardLoading sans shadow/elevation Android (qui ne respecte pas l'opacité de FadeInDown → flash "rectangle blanc + ombre" pendant l'anim). iOS shadow conservé. isLoading propagé depuis useDiseases / useGuides / useHistory vers les sections concernées. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { View, StyleSheet, Platform } from "react-native";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { ScanListItem } from "@/components/my-plants/ScanListItem";
|
|
import SectionHeader from "@/components/home/components/homeheader";
|
|
import HomeCta from "@/components/home/HomeCta";
|
|
import { ScanListItemSkeleton } from "@/components/ui/Skeleton";
|
|
import { useHistory } from "@/hooks/useHistory";
|
|
import type { RootStackParamList } from "@/types/navigation";
|
|
|
|
type Nav = NativeStackNavigationProp<RootStackParamList>;
|
|
|
|
const MAX_RECENT = 3;
|
|
|
|
export default function RecentScans() {
|
|
const { t } = useTranslation();
|
|
const navigation = useNavigation<Nav>();
|
|
const { history, isLoading, toggleFavorite, deleteScan } = useHistory();
|
|
|
|
// Loading + pas encore de cache → skeleton
|
|
if (isLoading && history.length === 0) {
|
|
return (
|
|
<View className="mb-6 mx-5 gap-3">
|
|
<SectionHeader
|
|
title={t("home.recentScans")}
|
|
onViewAll={() => navigation.navigate("Main", { screen: "MyPlants" })}
|
|
/>
|
|
<View style={styles.cardLoading}>
|
|
<ScanListItemSkeleton showSeparator />
|
|
<ScanListItemSkeleton showSeparator />
|
|
<ScanListItemSkeleton />
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (history.length === 0) {
|
|
return <HomeCta />;
|
|
}
|
|
|
|
const recent = history.slice(0, MAX_RECENT);
|
|
|
|
return (
|
|
<View className="mb-6 mx-5 gap-3">
|
|
<SectionHeader
|
|
title={t("home.recentScans")}
|
|
onViewAll={() => navigation.navigate("Main", { screen: "MyPlants" })}
|
|
/>
|
|
<View style={styles.card}>
|
|
{recent.map((scan, index) => (
|
|
<ScanListItem
|
|
key={scan.id}
|
|
scan={scan}
|
|
onPress={() => navigation.navigate("ScanDetail", { scanId: scan.id })}
|
|
onToggleFavorite={() => toggleFavorite(scan.id)}
|
|
onDelete={() => deleteScan(scan.id)}
|
|
grouped
|
|
showSeparator={index < recent.length - 1}
|
|
/>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
backgroundColor: "#FFFFFF",
|
|
borderRadius: 16,
|
|
overflow: "hidden",
|
|
borderWidth: 1,
|
|
borderColor: "#F0F0F0",
|
|
...Platform.select({
|
|
ios: {
|
|
shadowColor: "#000",
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.04,
|
|
shadowRadius: 8,
|
|
},
|
|
android: { elevation: 2 },
|
|
}),
|
|
},
|
|
// Loading: pas de shadow / elevation → évite le flash "rectangle blanc + ombre"
|
|
// sur Android avant que les skeletons ne fadent in via FadeInDown du parent.
|
|
cardLoading: {
|
|
backgroundColor: "#FFFFFF",
|
|
borderRadius: 16,
|
|
overflow: "hidden",
|
|
borderWidth: 1,
|
|
borderColor: "#F0F0F0",
|
|
},
|
|
});
|