feat(scan-detail): wire 'Ajouter ma position' button to GPS persist
useScanDetail :
- Nouvelle méthode setLocation(coords) qui persiste latitude / longitude
(top-level pour MapScreen.hasLocation), locationCapturedAt et
l'objet location.{latitude, longitude} dans AsyncStorage + state local
ScanDetailScreen :
- handleAddLocation appelle requestAndGetLocation() (useScanLocation hook
existant) puis setLocation()
- État addingLocation : bouton désactivé + opacity 0.6 + ActivityIndicator
spinner à la place de l'icône MapPin + label 'Localisation en cours...'
- Toast success / error + hapticSuccess en cas de succès
- La carte location bascule automatiquement vers l'affichage des coords
une fois persisté → la plante apparaît aussi sur MapScreen et
SearchScreen mode Map
i18n :
- myPlants.toasts.locationAdded
- myPlants.detail.locating
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05ea1df6ff
commit
bdbdcd7b85
|
|
@ -58,6 +58,44 @@ export function useScanDetail(scanId: string) {
|
||||||
[scanId],
|
[scanId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setLocation = useCallback(
|
||||||
|
async (coords: { latitude: number; longitude: number }) => {
|
||||||
|
const capturedAt = new Date().toISOString();
|
||||||
|
const all = await storage.get<ScanRecord[]>(storage.KEYS.SCAN_HISTORY);
|
||||||
|
if (!all) return;
|
||||||
|
const updated = all.map((s) =>
|
||||||
|
s.id === scanId
|
||||||
|
? {
|
||||||
|
...s,
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
locationCapturedAt: capturedAt,
|
||||||
|
location: {
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: s,
|
||||||
|
);
|
||||||
|
await storage.set(storage.KEYS.SCAN_HISTORY, updated);
|
||||||
|
setScan((prev) =>
|
||||||
|
prev
|
||||||
|
? {
|
||||||
|
...prev,
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
locationCapturedAt: capturedAt,
|
||||||
|
location: {
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: prev,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[scanId],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scan,
|
scan,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -65,6 +103,7 @@ export function useScanDetail(scanId: string) {
|
||||||
toggleFavorite,
|
toggleFavorite,
|
||||||
deleteScan,
|
deleteScan,
|
||||||
renameScan,
|
renameScan,
|
||||||
|
setLocation,
|
||||||
refetch: load,
|
refetch: load,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,8 @@
|
||||||
"favorited": "Added to favorites",
|
"favorited": "Added to favorites",
|
||||||
"unfavorited": "Removed from favorites",
|
"unfavorited": "Removed from favorites",
|
||||||
"deleted": "Scan deleted",
|
"deleted": "Scan deleted",
|
||||||
"renamed": "Name updated"
|
"renamed": "Name updated",
|
||||||
|
"locationAdded": "Location added to the plant"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"healthy": "Healthy",
|
"healthy": "Healthy",
|
||||||
|
|
@ -330,6 +331,7 @@
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
"noLocation": "No location recorded",
|
"noLocation": "No location recorded",
|
||||||
"addLocation": "Add my location",
|
"addLocation": "Add my location",
|
||||||
|
"locating": "Getting location...",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"shareConfirmTitle": "Share this scan?",
|
"shareConfirmTitle": "Share this scan?",
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,8 @@
|
||||||
"favorited": "Ajouté aux favoris",
|
"favorited": "Ajouté aux favoris",
|
||||||
"unfavorited": "Retiré des favoris",
|
"unfavorited": "Retiré des favoris",
|
||||||
"deleted": "Scan supprimé",
|
"deleted": "Scan supprimé",
|
||||||
"renamed": "Nom mis à jour"
|
"renamed": "Nom mis à jour",
|
||||||
|
"locationAdded": "Position ajoutée à la plante"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"healthy": "Saine",
|
"healthy": "Saine",
|
||||||
|
|
@ -330,6 +331,7 @@
|
||||||
"location": "Localisation",
|
"location": "Localisation",
|
||||||
"noLocation": "Aucune localisation enregistrée",
|
"noLocation": "Aucune localisation enregistrée",
|
||||||
"addLocation": "Ajouter ma position",
|
"addLocation": "Ajouter ma position",
|
||||||
|
"locating": "Localisation en cours...",
|
||||||
"share": "Partager",
|
"share": "Partager",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"shareConfirmTitle": "Partager ce scan ?",
|
"shareConfirmTitle": "Partager ce scan ?",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import { toast } from "sonner-native";
|
||||||
import { Text } from "@/components/ui/text";
|
import { Text } from "@/components/ui/text";
|
||||||
import { EditNameBottomSheet } from "@/components/my-plants/EditNameBottomSheet";
|
import { EditNameBottomSheet } from "@/components/my-plants/EditNameBottomSheet";
|
||||||
import { useScanDetail } from "@/hooks/useScanDetail";
|
import { useScanDetail } from "@/hooks/useScanDetail";
|
||||||
|
import { useScanLocation } from "@/hooks/useScanLocation";
|
||||||
import { getCepageById } from "@/utils/cepages";
|
import { getCepageById } from "@/utils/cepages";
|
||||||
import { hapticSuccess } from "@/services/haptics";
|
import { hapticSuccess } from "@/services/haptics";
|
||||||
import { colors } from "@/theme/colors";
|
import { colors } from "@/theme/colors";
|
||||||
|
|
@ -96,9 +97,18 @@ export default function ScanDetailScreen({ route }: Props) {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { scan, loading, error, toggleFavorite, deleteScan, renameScan } =
|
const {
|
||||||
useScanDetail(scanId);
|
scan,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
toggleFavorite,
|
||||||
|
deleteScan,
|
||||||
|
renameScan,
|
||||||
|
setLocation,
|
||||||
|
} = useScanDetail(scanId);
|
||||||
const [editingName, setEditingName] = useState(false);
|
const [editingName, setEditingName] = useState(false);
|
||||||
|
const [addingLocation, setAddingLocation] = useState(false);
|
||||||
|
const { requestAndGetLocation } = useScanLocation();
|
||||||
|
|
||||||
// Entry animation
|
// Entry animation
|
||||||
const contentY = useSharedValue(30);
|
const contentY = useSharedValue(30);
|
||||||
|
|
@ -185,6 +195,26 @@ export default function ScanDetailScreen({ route }: Props) {
|
||||||
toast.success(t("myPlants.toasts.renamed"));
|
toast.success(t("myPlants.toasts.renamed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAddLocation() {
|
||||||
|
if (addingLocation) return;
|
||||||
|
setAddingLocation(true);
|
||||||
|
try {
|
||||||
|
const coords = await requestAndGetLocation();
|
||||||
|
if (!coords) {
|
||||||
|
toast.error(t("location.permissionDenied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await setLocation({
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
});
|
||||||
|
hapticSuccess();
|
||||||
|
toast.success(t("myPlants.toasts.locationAdded"));
|
||||||
|
} finally {
|
||||||
|
setAddingLocation(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleToggleFavorite() {
|
async function handleToggleFavorite() {
|
||||||
await toggleFavorite();
|
await toggleFavorite();
|
||||||
hapticSuccess();
|
hapticSuccess();
|
||||||
|
|
@ -426,17 +456,26 @@ export default function ScanDetailScreen({ route }: Props) {
|
||||||
{t("myPlants.detail.noLocation")}
|
{t("myPlants.detail.noLocation")}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.addLocationBtn}
|
style={[
|
||||||
onPress={() =>
|
styles.addLocationBtn,
|
||||||
console.warn(
|
addingLocation && { opacity: 0.6 },
|
||||||
"[ScanDetail] add location — to be implemented in prompt 3",
|
]}
|
||||||
)
|
onPress={handleAddLocation}
|
||||||
}
|
disabled={addingLocation}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<MapPin size={14} color={colors.primary[700]} />
|
{addingLocation ? (
|
||||||
|
<ActivityIndicator
|
||||||
|
size="small"
|
||||||
|
color={colors.primary[700]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MapPin size={14} color={colors.primary[700]} />
|
||||||
|
)}
|
||||||
<Text style={styles.addLocationText}>
|
<Text style={styles.addLocationText}>
|
||||||
{t("myPlants.detail.addLocation")}
|
{addingLocation
|
||||||
|
? t("myPlants.detail.locating")
|
||||||
|
: t("myPlants.detail.addLocation")}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue