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],
|
||||
);
|
||||
|
||||
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 {
|
||||
scan,
|
||||
loading,
|
||||
|
|
@ -65,6 +103,7 @@ export function useScanDetail(scanId: string) {
|
|||
toggleFavorite,
|
||||
deleteScan,
|
||||
renameScan,
|
||||
setLocation,
|
||||
refetch: load,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,8 @@
|
|||
"favorited": "Added to favorites",
|
||||
"unfavorited": "Removed from favorites",
|
||||
"deleted": "Scan deleted",
|
||||
"renamed": "Name updated"
|
||||
"renamed": "Name updated",
|
||||
"locationAdded": "Location added to the plant"
|
||||
},
|
||||
"status": {
|
||||
"healthy": "Healthy",
|
||||
|
|
@ -330,6 +331,7 @@
|
|||
"location": "Location",
|
||||
"noLocation": "No location recorded",
|
||||
"addLocation": "Add my location",
|
||||
"locating": "Getting location...",
|
||||
"share": "Share",
|
||||
"delete": "Delete",
|
||||
"shareConfirmTitle": "Share this scan?",
|
||||
|
|
|
|||
|
|
@ -304,7 +304,8 @@
|
|||
"favorited": "Ajouté aux favoris",
|
||||
"unfavorited": "Retiré des favoris",
|
||||
"deleted": "Scan supprimé",
|
||||
"renamed": "Nom mis à jour"
|
||||
"renamed": "Nom mis à jour",
|
||||
"locationAdded": "Position ajoutée à la plante"
|
||||
},
|
||||
"status": {
|
||||
"healthy": "Saine",
|
||||
|
|
@ -330,6 +331,7 @@
|
|||
"location": "Localisation",
|
||||
"noLocation": "Aucune localisation enregistrée",
|
||||
"addLocation": "Ajouter ma position",
|
||||
"locating": "Localisation en cours...",
|
||||
"share": "Partager",
|
||||
"delete": "Supprimer",
|
||||
"shareConfirmTitle": "Partager ce scan ?",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import { toast } from "sonner-native";
|
|||
import { Text } from "@/components/ui/text";
|
||||
import { EditNameBottomSheet } from "@/components/my-plants/EditNameBottomSheet";
|
||||
import { useScanDetail } from "@/hooks/useScanDetail";
|
||||
import { useScanLocation } from "@/hooks/useScanLocation";
|
||||
import { getCepageById } from "@/utils/cepages";
|
||||
import { hapticSuccess } from "@/services/haptics";
|
||||
import { colors } from "@/theme/colors";
|
||||
|
|
@ -96,9 +97,18 @@ export default function ScanDetailScreen({ route }: Props) {
|
|||
const { t, i18n } = useTranslation();
|
||||
const navigation = useNavigation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { scan, loading, error, toggleFavorite, deleteScan, renameScan } =
|
||||
useScanDetail(scanId);
|
||||
const {
|
||||
scan,
|
||||
loading,
|
||||
error,
|
||||
toggleFavorite,
|
||||
deleteScan,
|
||||
renameScan,
|
||||
setLocation,
|
||||
} = useScanDetail(scanId);
|
||||
const [editingName, setEditingName] = useState(false);
|
||||
const [addingLocation, setAddingLocation] = useState(false);
|
||||
const { requestAndGetLocation } = useScanLocation();
|
||||
|
||||
// Entry animation
|
||||
const contentY = useSharedValue(30);
|
||||
|
|
@ -185,6 +195,26 @@ export default function ScanDetailScreen({ route }: Props) {
|
|||
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() {
|
||||
await toggleFavorite();
|
||||
hapticSuccess();
|
||||
|
|
@ -426,17 +456,26 @@ export default function ScanDetailScreen({ route }: Props) {
|
|||
{t("myPlants.detail.noLocation")}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.addLocationBtn}
|
||||
onPress={() =>
|
||||
console.warn(
|
||||
"[ScanDetail] add location — to be implemented in prompt 3",
|
||||
)
|
||||
}
|
||||
style={[
|
||||
styles.addLocationBtn,
|
||||
addingLocation && { opacity: 0.6 },
|
||||
]}
|
||||
onPress={handleAddLocation}
|
||||
disabled={addingLocation}
|
||||
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}>
|
||||
{t("myPlants.detail.addLocation")}
|
||||
{addingLocation
|
||||
? t("myPlants.detail.locating")
|
||||
: t("myPlants.detail.addLocation")}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue