Initial scaffold: Expo SDK 56, expo-router, expo-sqlite, NativeWind
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import {
|
||||
getIncidents,
|
||||
createIncident,
|
||||
updateIncident,
|
||||
deleteIncident,
|
||||
} from "@/lib/db";
|
||||
import type { Incident, IncidentDraft } from "@/types/incident";
|
||||
|
||||
export function useIncidents() {
|
||||
const [incidents, setIncidents] = useState<Incident[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const refresh = useCallback(async (limit = 10) => {
|
||||
setLoading(true);
|
||||
const data = await getIncidents(limit);
|
||||
setIncidents(data);
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
const add = useCallback(async (draft: IncidentDraft) => {
|
||||
const created = await createIncident(draft);
|
||||
setIncidents((prev) => [created, ...prev]);
|
||||
return created;
|
||||
}, []);
|
||||
|
||||
const update = useCallback(
|
||||
async (id: string, updates: Partial<IncidentDraft>) => {
|
||||
await updateIncident(id, updates);
|
||||
setIncidents((prev) =>
|
||||
prev.map((inc) =>
|
||||
inc.id === id ? { ...inc, ...updates, updatedAt: new Date().toISOString() } : inc
|
||||
)
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const remove = useCallback(async (id: string) => {
|
||||
await deleteIncident(id);
|
||||
setIncidents((prev) => prev.filter((inc) => inc.id !== id));
|
||||
}, []);
|
||||
|
||||
return { incidents, loading, refresh, add, update, remove };
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
|
||||
export interface Settings {
|
||||
homeView: "dashboard" | "capture";
|
||||
inputMode: "form" | "voice";
|
||||
onboardingDone: boolean;
|
||||
}
|
||||
|
||||
const DEFAULTS: Settings = {
|
||||
homeView: "dashboard",
|
||||
inputMode: "form",
|
||||
onboardingDone: false,
|
||||
};
|
||||
|
||||
export function useSettings() {
|
||||
const [settings, setSettings] = useState<Settings>(DEFAULTS);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const [hv, im, od] = await Promise.all([
|
||||
SecureStore.getItemAsync("pref_home_view"),
|
||||
SecureStore.getItemAsync("pref_input_mode"),
|
||||
SecureStore.getItemAsync("onboarding_done"),
|
||||
]);
|
||||
setSettings({
|
||||
homeView: (hv as Settings["homeView"]) ?? DEFAULTS.homeView,
|
||||
inputMode: (im as Settings["inputMode"]) ?? DEFAULTS.inputMode,
|
||||
onboardingDone: od === "true",
|
||||
});
|
||||
setReady(true);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return { settings, ready };
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import {
|
||||
ExpoSpeechRecognitionModule,
|
||||
useSpeechRecognitionEvent,
|
||||
} from "expo-speech-recognition";
|
||||
|
||||
export type VoiceState = "idle" | "listening" | "processing" | "error";
|
||||
|
||||
export function useVoice() {
|
||||
const [state, setState] = useState<VoiceState>("idle");
|
||||
const [transcript, setTranscript] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useSpeechRecognitionEvent("start", () => setState("listening"));
|
||||
|
||||
useSpeechRecognitionEvent("end", () => {
|
||||
setState((s) => (s === "listening" ? "processing" : s));
|
||||
});
|
||||
|
||||
useSpeechRecognitionEvent("result", (event) => {
|
||||
const best = event.results[0]?.transcript ?? "";
|
||||
setTranscript(best);
|
||||
if (event.isFinal) setState("idle");
|
||||
});
|
||||
|
||||
useSpeechRecognitionEvent("error", (event) => {
|
||||
setError(event.error ?? "Voice recognition failed");
|
||||
setState("error");
|
||||
});
|
||||
|
||||
const start = useCallback(async (lang = "fr-FR") => {
|
||||
setError(null);
|
||||
setTranscript("");
|
||||
const { granted } =
|
||||
await ExpoSpeechRecognitionModule.requestPermissionsAsync();
|
||||
if (!granted) {
|
||||
setError("Microphone permission denied");
|
||||
setState("error");
|
||||
return;
|
||||
}
|
||||
ExpoSpeechRecognitionModule.start({ lang, interimResults: true });
|
||||
}, []);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
ExpoSpeechRecognitionModule.stop();
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setTranscript("");
|
||||
setError(null);
|
||||
setState("idle");
|
||||
}, []);
|
||||
|
||||
return { state, transcript, error, start, stop, reset };
|
||||
}
|
||||
Reference in New Issue
Block a user