feat(kvot): debitera AI-skanning PER BILD + fixa dubbeldebitering
Johans beslut: en skanning debiteras per bild (6 foton = 6 skanningar), streckkod gratis. consumeAiScan tar nu units=imageCount och kollar att hela ryms. Refunden ger tillbaka lika manga (antal bilder) vid misslyckat jobb. Appen kapar antalet foton till kvarvarande kvot och gar till paywall om 0 (servern ar dock den slutgiltiga grinden). Hittade + fixade en PRE-EXISTING bugg pa vagen: kvoten drogs bade vid skapande (consumeAiScan) OCH vid slutford analys (recordAiUsage aiScans:1) -> varje LYCKAD skanning kostade 2 enheter, dolt av att appen bara uppdaterade raknaren vid start. Nu dras kvoten exakt en gang, vid skapandet. Enbildsanvandare far alltsa nu sina fulla 5 gratis-skanningar (var tidigare ~2,5).
This commit is contained in:
@@ -44,17 +44,17 @@ const MULTI_PHOTO_TYPES = new Set(["fridge", "freezer", "pantry", "ingredients"]
|
||||
const MAX_PHOTOS = 6; // matchar createScanInputSchema.imageCount.max
|
||||
|
||||
/** Fråga om användaren vill ta fler bilder i samma skanning. */
|
||||
function askAddMore(count: number): Promise<boolean> {
|
||||
function askAddMore(count: number, maxCount: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (count >= MAX_PHOTOS) {
|
||||
Alert.alert("Klart", `Max ${MAX_PHOTOS} bilder – analyserar.`, [
|
||||
if (count >= maxCount) {
|
||||
Alert.alert("Klart", `Max ${maxCount} bilder den här skanningen – analyserar.`, [
|
||||
{ text: "OK", onPress: () => resolve(false) },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
Alert.alert(
|
||||
`Bild ${count} tagen`,
|
||||
"Ta gärna fler vinklar av samma plats – appen listar varje vara en gång.",
|
||||
"Ta gärna fler vinklar av samma plats – appen listar varje vara en gång. Varje bild räknas som en skanning.",
|
||||
[
|
||||
{ text: "Ta en till", onPress: () => resolve(true) },
|
||||
{ text: `Analysera (${count})`, style: "default", onPress: () => resolve(false) },
|
||||
@@ -63,13 +63,17 @@ function askAddMore(count: number): Promise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Fånga en eller flera bilder. Kamera → loop med "ta en till"; galleri → flerval. */
|
||||
async function capturePhotos(multi: boolean): Promise<string[]> {
|
||||
/**
|
||||
* Fånga en eller flera bilder. Kamera → loop med "ta en till"; galleri → flerval.
|
||||
* maxCount kapar antalet till kvarvarande kvot (varje bild = en skanning).
|
||||
*/
|
||||
async function capturePhotos(multi: boolean, maxCount: number): Promise<string[]> {
|
||||
const permission = await ImagePicker.requestCameraPermissionsAsync();
|
||||
const useCamera = permission.granted;
|
||||
const uris: string[] = [];
|
||||
const cap = Math.max(1, maxCount);
|
||||
|
||||
if (!multi) {
|
||||
if (!multi || cap === 1) {
|
||||
const picked = useCamera
|
||||
? await ImagePicker.launchCameraAsync({ quality: 0.7 })
|
||||
: await ImagePicker.launchImageLibraryAsync({ quality: 0.7 });
|
||||
@@ -83,19 +87,19 @@ async function capturePhotos(multi: boolean): Promise<string[]> {
|
||||
const picked = await ImagePicker.launchImageLibraryAsync({
|
||||
quality: 0.7,
|
||||
allowsMultipleSelection: true,
|
||||
selectionLimit: MAX_PHOTOS,
|
||||
selectionLimit: cap,
|
||||
});
|
||||
if (!picked.canceled) for (const a of picked.assets ?? []) uris.push(a.uri);
|
||||
if (!picked.canceled) for (const a of (picked.assets ?? []).slice(0, cap)) uris.push(a.uri);
|
||||
return uris;
|
||||
}
|
||||
|
||||
// Kamera: ta en bild i taget, fråga om fler mellan varje.
|
||||
while (uris.length < MAX_PHOTOS) {
|
||||
// Kamera: ta en bild i taget, fråga om fler mellan varje (upp till kvot-taket).
|
||||
while (uris.length < cap) {
|
||||
const picked = await ImagePicker.launchCameraAsync({ quality: 0.7 });
|
||||
const asset = picked.assets?.[0];
|
||||
if (picked.canceled || !asset) break;
|
||||
uris.push(asset.uri);
|
||||
const more = await askAddMore(uris.length);
|
||||
const more = await askAddMore(uris.length, cap);
|
||||
if (!more) break;
|
||||
}
|
||||
return uris;
|
||||
@@ -131,9 +135,21 @@ export default function ScanScreen() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Varje bild = en skanning. Kapa antalet foton till kvarvarande kvot, och
|
||||
// skicka till paywall direkt om kvoten redan är slut (servern är dock den
|
||||
// som slutgiltigt räknar – appens tak är bara för att slippa blockering mitt i).
|
||||
const remaining = entitlements.data
|
||||
? entitlements.data.aiScansPerMonth - entitlements.data.aiScansUsedThisMonth
|
||||
: null;
|
||||
if (remaining != null && remaining <= 0) {
|
||||
router.push("/paywall");
|
||||
return;
|
||||
}
|
||||
const cap = remaining != null ? Math.min(MAX_PHOTOS, remaining) : MAX_PHOTOS;
|
||||
|
||||
// 1. Ta bild(er) – flera vinklar tillåtna för kyl/frys/skafferi/ingredienser.
|
||||
const multi = MULTI_PHOTO_TYPES.has(scanType);
|
||||
const uris = await capturePhotos(multi);
|
||||
const uris = await capturePhotos(multi, cap);
|
||||
if (uris.length === 0) return; // avbruten
|
||||
|
||||
// Visa laddning först nu (kamera/dialoger är egna vyer).
|
||||
|
||||
Reference in New Issue
Block a user