205 lines
6.5 KiB
TypeScript
205 lines
6.5 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { ActivityIndicator, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
import { useRouter } from 'expo-router';
|
|
import { parseShareIntent, ShareIntentModule } from 'expo-share-intent';
|
|
import * as ExpoLinking from 'expo-linking';
|
|
import { SHARE_INTENT_KEY, storeSharedImageUri } from '../utils/shareHandoff';
|
|
import { resolveSharedImageUri, summarizeShareIntent } from '../utils/shareIntent';
|
|
|
|
const SHARE_INTENT_SCHEME = 'greenlens';
|
|
const SHARE_INTENT_OPTIONS = {
|
|
scheme: SHARE_INTENT_SCHEME,
|
|
};
|
|
const isShareIntentUrl = (url: string | null | undefined) => Boolean(url?.includes(`${SHARE_INTENT_SCHEME}://dataUrl=`));
|
|
|
|
export default function ShareIntentCallbackScreen() {
|
|
const router = useRouter();
|
|
const [isWaiting, setIsWaiting] = React.useState(true);
|
|
const [failureDetails, setFailureDetails] = React.useState<string | null>(null);
|
|
const [previewUri, setPreviewUri] = React.useState<string | null>(null);
|
|
const pendingKeyRef = React.useRef<string | null>(null);
|
|
const getIntentCalledRef = React.useRef(false);
|
|
const settledRef = React.useRef(false);
|
|
const linkingUrl = ExpoLinking.useLinkingURL();
|
|
|
|
useEffect(() => {
|
|
const fallback = setTimeout(() => {
|
|
if (settledRef.current) return;
|
|
setIsWaiting(false);
|
|
setFailureDetails((current) => current || 'Keine Antwort von der Share Extension.');
|
|
}, 15000);
|
|
|
|
return () => clearTimeout(fallback);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const showFailure = (message: string) => {
|
|
settledRef.current = true;
|
|
ShareIntentModule?.clearShareIntent(SHARE_INTENT_KEY);
|
|
setPreviewUri(null);
|
|
setIsWaiting(false);
|
|
setFailureDetails(message);
|
|
};
|
|
|
|
const changeSubscription = ShareIntentModule?.addListener('onChange', async (event) => {
|
|
try {
|
|
setIsWaiting(true);
|
|
setFailureDetails(null);
|
|
const shareIntent = parseShareIntent(event.value, SHARE_INTENT_OPTIONS);
|
|
if (__DEV__) {
|
|
console.debug('[ShareIntentCallback]', summarizeShareIntent(shareIntent));
|
|
}
|
|
const resolved = await resolveSharedImageUri(shareIntent);
|
|
if (!resolved) {
|
|
showFailure('Die Quelle hat keinen nutzbaren Bildanhang oder Bild-Link geliefert.');
|
|
return;
|
|
}
|
|
settledRef.current = true;
|
|
const sharedImageKey = storeSharedImageUri(resolved.uri);
|
|
ShareIntentModule?.clearShareIntent(SHARE_INTENT_KEY);
|
|
if (resolved.requiresConfirmation) {
|
|
pendingKeyRef.current = sharedImageKey;
|
|
setIsWaiting(false);
|
|
setPreviewUri(resolved.uri);
|
|
return;
|
|
}
|
|
router.replace({
|
|
pathname: '/scanner',
|
|
params: { sharedImageKey },
|
|
});
|
|
} catch (error) {
|
|
console.error('[ShareIntentCallback] failed to parse share intent', error);
|
|
showFailure('Die Share-Daten konnten nicht gelesen werden.');
|
|
}
|
|
});
|
|
|
|
const errorSubscription = ShareIntentModule?.addListener('onError', (event) => {
|
|
console.error('[ShareIntentCallback] native error', event.value);
|
|
showFailure(event.value || 'Die Share Extension hat einen Fehler gemeldet.');
|
|
});
|
|
|
|
const url = linkingUrl || ExpoLinking.getLinkingURL();
|
|
if (url && isShareIntentUrl(url) && !getIntentCalledRef.current) {
|
|
getIntentCalledRef.current = true;
|
|
ShareIntentModule?.getShareIntent(url);
|
|
}
|
|
|
|
return () => {
|
|
changeSubscription?.remove();
|
|
errorSubscription?.remove();
|
|
};
|
|
}, [linkingUrl, router]);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{isWaiting ? (
|
|
<ActivityIndicator color="#F4F7F1" size="large" />
|
|
) : previewUri ? (
|
|
<View style={styles.messageBox}>
|
|
<Text style={styles.title}>Pflanze gefunden</Text>
|
|
<Text style={styles.body}>Ist das die Pflanze, die du scannen möchtest?</Text>
|
|
<Image source={{ uri: previewUri }} style={styles.previewImage} resizeMode="cover" />
|
|
<Text style={styles.hint}>
|
|
Wenn das nicht die Pflanze ist, tippe „Scanner öffnen" und teile das Bild direkt in Safari.
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={styles.button}
|
|
onPress={() => {
|
|
if (pendingKeyRef.current) {
|
|
router.replace({ pathname: '/scanner', params: { sharedImageKey: pendingKeyRef.current } });
|
|
}
|
|
}}
|
|
>
|
|
<Text style={styles.buttonText}>Diese Pflanze scannen</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity style={styles.buttonSecondary} onPress={() => router.replace('/scanner')}>
|
|
<Text style={styles.buttonSecondaryText}>Scanner öffnen</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
) : (
|
|
<View style={styles.messageBox}>
|
|
<Text style={styles.title}>Bild konnte nicht geladen werden.</Text>
|
|
<Text style={styles.body}>
|
|
Tippe und halte das Bild in Safari oder Instagram, wähle „Bild teilen" und teile es direkt mit GreenLens.
|
|
</Text>
|
|
{failureDetails ? (
|
|
<Text style={styles.detail}>{failureDetails}</Text>
|
|
) : null}
|
|
<TouchableOpacity style={styles.button} onPress={() => router.replace('/scanner')}>
|
|
<Text style={styles.buttonText}>Scanner öffnen</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: '#111813',
|
|
padding: 24,
|
|
},
|
|
messageBox: {
|
|
alignItems: 'center',
|
|
gap: 14,
|
|
},
|
|
title: {
|
|
color: '#F4F7F1',
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
textAlign: 'center',
|
|
},
|
|
body: {
|
|
color: '#CAD3CB',
|
|
fontSize: 14,
|
|
lineHeight: 20,
|
|
textAlign: 'center',
|
|
},
|
|
detail: {
|
|
color: '#8F9A91',
|
|
fontSize: 12,
|
|
lineHeight: 17,
|
|
textAlign: 'center',
|
|
},
|
|
previewImage: {
|
|
width: '100%',
|
|
aspectRatio: 4 / 3,
|
|
borderRadius: 16,
|
|
backgroundColor: '#1E2820',
|
|
},
|
|
button: {
|
|
marginTop: 8,
|
|
borderRadius: 12,
|
|
backgroundColor: '#D7F5A2',
|
|
paddingHorizontal: 18,
|
|
paddingVertical: 12,
|
|
},
|
|
buttonText: {
|
|
color: '#111813',
|
|
fontSize: 14,
|
|
fontWeight: '800',
|
|
},
|
|
hint: {
|
|
color: '#8F9A91',
|
|
fontSize: 11,
|
|
lineHeight: 16,
|
|
textAlign: 'center',
|
|
marginTop: -6,
|
|
},
|
|
buttonSecondary: {
|
|
marginTop: 4,
|
|
borderRadius: 12,
|
|
paddingHorizontal: 18,
|
|
paddingVertical: 12,
|
|
},
|
|
buttonSecondaryText: {
|
|
color: '#8F9A91',
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
textAlign: 'center',
|
|
},
|
|
});
|