This commit is contained in:
2026-05-18 15:59:46 +02:00
parent 2658c37453
commit 26713b13b1
21 changed files with 894 additions and 97 deletions

View File

@@ -11,6 +11,7 @@ import * as ImageManipulator from 'expo-image-manipulator';
import * as Haptics from 'expo-haptics';
import * as AppleAuthentication from 'expo-apple-authentication';
import Constants from 'expo-constants';
import { ShareIntentModule } from 'expo-share-intent';
import { useSafeAnalytics } from '../services/analytics';
import { useApp } from '../context/AppContext';
import { useColors } from '../constants/Colors';
@@ -22,6 +23,7 @@ import { isBackendApiError } from '../services/backend/contracts';
import { createIdempotencyKey } from '../utils/idempotency';
import { AuthService } from '../services/authService';
import { getMockPlantByImage } from '../services/backend/mockCatalog';
import { consumeSharedImageUri, SHARE_INTENT_KEY } from '../utils/shareHandoff';
const HEALTH_CHECK_CREDIT_COST = 2;
const DEMO_SCAN_LIMIT = 5;
@@ -129,7 +131,7 @@ const getBillingCopy = (language: 'de' | 'en' | 'es') => {
};
export default function ScannerScreen() {
const params = useLocalSearchParams<{ mode?: string; plantId?: string; sharedImageUri?: string }>();
const params = useLocalSearchParams<{ mode?: string; plantId?: string; sharedImageKey?: string; sharedImageUri?: string }>();
const posthog = useSafeAnalytics();
const {
isDarkMode,
@@ -160,6 +162,9 @@ export default function ScannerScreen() {
const sharedImageUri = Array.isArray(params.sharedImageUri)
? params.sharedImageUri[0]
: params.sharedImageUri;
const sharedImageKey = Array.isArray(params.sharedImageKey)
? params.sharedImageKey[0]
: params.sharedImageKey;
const hasActiveEntitlement = billingSummary?.entitlement?.plan === 'pro'
&& billingSummary?.entitlement?.status === 'active';
const isDemoMode = !hasActiveEntitlement;
@@ -197,17 +202,10 @@ export default function ScannerScreen() {
};
}, [isExpoGo]);
const hasProcessedSharedImage = useRef(false);
useEffect(() => {
if (!sharedImageUri || hasProcessedSharedImage.current) return;
hasProcessedSharedImage.current = true;
(async () => {
const analysisUri = await resizeForAnalysis(sharedImageUri);
setDemoResultVisible(false);
setSelectedImage(sharedImageUri);
analyzeImage(analysisUri, sharedImageUri);
})();
}, [sharedImageUri]);
const lastProcessedShareToken = useRef<string | null>(null);
const sharedAnalysisInFlightToken = useRef<string | null>(null);
const resizeForAnalysisRef = useRef<(uri: string) => Promise<string>>(async (uri) => uri);
const analyzeImageRef = useRef<(imageUri: string, galleryImageUri?: string) => Promise<void>>(async () => {});
useEffect(() => {
if (!isAnalyzing) {
@@ -256,8 +254,8 @@ export default function ScannerScreen() {
try {
const result = await ImageManipulator.manipulateAsync(
uri,
[{ resize: { width: 768 } }],
{ compress: 0.7, format: ImageManipulator.SaveFormat.JPEG, base64: true },
[{ resize: { width: 1280 } }],
{ compress: 0.9, format: ImageManipulator.SaveFormat.JPEG, base64: true },
);
return result.base64 ? `data:image/jpeg;base64,${result.base64}` : result.uri;
} catch {
@@ -472,6 +470,45 @@ export default function ScannerScreen() {
}
};
useEffect(() => {
resizeForAnalysisRef.current = resizeForAnalysis;
analyzeImageRef.current = analyzeImage;
});
useEffect(() => {
const shareToken = sharedImageKey || sharedImageUri;
if (!shareToken || isLoadingBilling || isAnalyzing) return;
if (lastProcessedShareToken.current === shareToken) return;
if (sharedAnalysisInFlightToken.current) return;
const handoffImageUri = consumeSharedImageUri(sharedImageKey);
const nextSharedImageUri = handoffImageUri || sharedImageUri;
if (!nextSharedImageUri) return;
lastProcessedShareToken.current = shareToken;
sharedAnalysisInFlightToken.current = shareToken;
ShareIntentModule?.clearShareIntent(SHARE_INTENT_KEY);
let cancelled = false;
(async () => {
try {
const analysisUri = await resizeForAnalysisRef.current(nextSharedImageUri);
if (cancelled || sharedAnalysisInFlightToken.current !== shareToken) return;
setDemoResultVisible(false);
setSelectedImage(analysisUri);
await analyzeImageRef.current(analysisUri, nextSharedImageUri);
} finally {
if (sharedAnalysisInFlightToken.current === shareToken) {
sharedAnalysisInFlightToken.current = null;
}
}
})();
return () => {
cancelled = true;
};
}, [sharedImageKey, sharedImageUri, isLoadingBilling, isAnalyzing]);
const takePicture = async () => {
if (!cameraRef.current || isAnalyzing) return;
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);