improving camera lifecycle logic
This commit is contained in:
@@ -41,58 +41,127 @@ export default function CameraModal(props) {
|
||||
const device = useCameraDevice('back');
|
||||
const { t } = useTranslation();
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [isFlashOn, setIsFlashOn] = useState(false);
|
||||
const didScanRef = useRef(false);
|
||||
const didCallImagePicker = useRef(null);
|
||||
|
||||
const isCameraActive = isFocused && !isClosing;
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
crashlyticsLogReport('Loading camera model page');
|
||||
|
||||
if (!hasPermission) {
|
||||
requestPermission();
|
||||
}
|
||||
|
||||
setIsFocused(true);
|
||||
setIsClosing(false);
|
||||
didScanRef.current = false;
|
||||
|
||||
return () => {
|
||||
setIsFocused(false);
|
||||
didScanRef.current = false;
|
||||
};
|
||||
}, [hasPermission, requestPermission]),
|
||||
);
|
||||
|
||||
const handleFinish = useCallback(
|
||||
data => {
|
||||
if (didScanRef.current) return;
|
||||
didScanRef.current = true;
|
||||
|
||||
setIsClosing(true);
|
||||
|
||||
setTimeout(() => {
|
||||
if (navigate.canGoBack()) {
|
||||
navigate.goBack();
|
||||
setTimeout(() => {
|
||||
props.route.params.updateBitcoinAdressFunc(data);
|
||||
}, 100);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
[navigate, props.route.params],
|
||||
);
|
||||
|
||||
const codeScanner = useCodeScanner({
|
||||
codeTypes: ['qr'],
|
||||
onCodeScanned: useCallback(
|
||||
codes => {
|
||||
if (didScanRef.current) return;
|
||||
const [data] = codes;
|
||||
if (data.type !== 'qr') return;
|
||||
|
||||
didScanRef.current = true;
|
||||
onCodeScanned: codes => {
|
||||
if (didScanRef.current || isClosing || codes.length === 0) return;
|
||||
const [data] = codes;
|
||||
if (data.type === 'qr' && data.value) {
|
||||
crashlyticsLogReport('handling scanned barcode');
|
||||
navigate.goBack();
|
||||
|
||||
setTimeout(() => {
|
||||
props.route.params.updateBitcoinAdressFunc(data.value);
|
||||
}, 150);
|
||||
},
|
||||
[navigate, props.route.params],
|
||||
),
|
||||
handleFinish(data.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const format = useCameraFormat(device?.formats?.length ? device : undefined, [
|
||||
{ photoAspectRatio: screenAspectRatio },
|
||||
]);
|
||||
|
||||
const toggleFlash = useCallback(() => {
|
||||
if (!device?.hasTorch) {
|
||||
return;
|
||||
}
|
||||
setIsFlashOn(prev => !prev);
|
||||
}, [device]);
|
||||
|
||||
const dataFromClipboard = async () => {
|
||||
try {
|
||||
const response = await getClipboardText();
|
||||
if (!response.didWork) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: t(response.reason) });
|
||||
return;
|
||||
}
|
||||
handleFinish(response.data);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
const getQRImage = async () => {
|
||||
if (didCallImagePicker.current) return;
|
||||
didCallImagePicker.current = true;
|
||||
|
||||
const imagePickerResponse = await getImageFromLibrary();
|
||||
const { didRun, error, imgURL } = imagePickerResponse;
|
||||
|
||||
if (!didRun) {
|
||||
didCallImagePicker.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
crashlyticsRecordErrorReport(error);
|
||||
didCallImagePicker.current = false;
|
||||
navigate.navigate('ErrorScreen', { errorMessage: t(error) });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await detectQRCode(imgURL.uri);
|
||||
if (response?.values?.length > 0) {
|
||||
handleFinish(response.values[0]);
|
||||
} else {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.qrDecodeError'),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.qrDecodeError'),
|
||||
});
|
||||
} finally {
|
||||
didCallImagePicker.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!hasPermission) {
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true}>
|
||||
<CameraPageNavBar />
|
||||
<View
|
||||
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<View style={styles.centeredContainer}>
|
||||
<ThemeText
|
||||
styles={styles.errorText}
|
||||
content={t('wallet.cameraModal.noCamera')}
|
||||
@@ -105,6 +174,7 @@ export default function CameraModal(props) {
|
||||
</GlobalThemeView>
|
||||
);
|
||||
}
|
||||
|
||||
if (device == null) {
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true}>
|
||||
@@ -123,10 +193,11 @@ export default function CameraModal(props) {
|
||||
codeScanner={codeScanner}
|
||||
style={StyleSheet.absoluteFill}
|
||||
device={device}
|
||||
isActive={isFocused}
|
||||
isActive={isCameraActive}
|
||||
format={format}
|
||||
torch={isFlashOn ? 'on' : 'off'}
|
||||
/>
|
||||
|
||||
<View style={styles.cameraOverlay}>
|
||||
<View style={styles.topOverlay}>
|
||||
<CameraPageNavBar useFullWidth={false} showWhiteImage={true} />
|
||||
@@ -148,17 +219,21 @@ export default function CameraModal(props) {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.middleRow}>
|
||||
<View style={styles.sideOverlay} />
|
||||
<View
|
||||
style={{
|
||||
...styles.qrBoxOutline,
|
||||
borderColor:
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary,
|
||||
}}
|
||||
style={[
|
||||
styles.qrBoxOutline,
|
||||
{
|
||||
borderColor:
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View style={styles.sideOverlay} />
|
||||
</View>
|
||||
|
||||
<View style={styles.bottomOverlay}>
|
||||
{props?.route?.params?.fromPage !== 'addContact' && (
|
||||
<TouchableOpacity
|
||||
@@ -176,111 +251,16 @@ export default function CameraModal(props) {
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
function toggleFlash() {
|
||||
if (!device?.hasTorch) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.noFlash'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsFlashOn(prev => !prev);
|
||||
}
|
||||
|
||||
async function dataFromClipboard() {
|
||||
try {
|
||||
const response = await getClipboardText();
|
||||
if (!response.didWork) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: t(response.reason) });
|
||||
return;
|
||||
}
|
||||
crashlyticsLogReport('handling data from clipboard');
|
||||
navigate.goBack();
|
||||
props.route.params.updateBitcoinAdressFunc(response.data);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function getQRImage() {
|
||||
if (didCallImagePicker.current) return;
|
||||
didCallImagePicker.current = true;
|
||||
const imagePickerResponse = await getImageFromLibrary();
|
||||
const { didRun, error, imgURL } = imagePickerResponse;
|
||||
if (!didRun) {
|
||||
didCallImagePicker.current = false;
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
crashlyticsRecordErrorReport(error);
|
||||
navigate.goBack();
|
||||
setTimeout(
|
||||
() => {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(error),
|
||||
});
|
||||
},
|
||||
Platform.OS === 'android' ? 350 : 50,
|
||||
);
|
||||
didCallImagePicker.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await detectQRCode(imgURL.uri);
|
||||
if (!response) throw new Error('Error detecting invoice');
|
||||
|
||||
if (response.type != 'QRCode') {
|
||||
navigate.goBack();
|
||||
setTimeout(() => {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.sanningResponse'),
|
||||
});
|
||||
}, 150);
|
||||
}
|
||||
if (!response.values.length) {
|
||||
navigate.goBack();
|
||||
setTimeout(() => {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.qrDecodeError'),
|
||||
});
|
||||
}, 150);
|
||||
return;
|
||||
}
|
||||
crashlyticsLogReport('handling code from qr code');
|
||||
navigate.goBack();
|
||||
setTimeout(() => {
|
||||
props.route.params.updateBitcoinAdressFunc(response.values[0]);
|
||||
}, 150);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
navigate.goBack();
|
||||
setTimeout(() => {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('wallet.cameraModal.qrDecodeError'),
|
||||
});
|
||||
}, 150);
|
||||
} finally {
|
||||
didCallImagePicker.current = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centeredContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
errorText: { width: '80%', textAlign: 'center' },
|
||||
backArrow: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
},
|
||||
qrBoxOutline: {
|
||||
width: 250,
|
||||
height: 250,
|
||||
borderWidth: 3,
|
||||
},
|
||||
qrLine: {
|
||||
width: '100%',
|
||||
height: 10,
|
||||
position: 'absolute',
|
||||
},
|
||||
qrBoxOutline: { width: 250, height: 250, borderWidth: 3 },
|
||||
qrVerticalBackground: {
|
||||
width: 250,
|
||||
flexDirection: 'row',
|
||||
@@ -289,6 +269,11 @@ const styles = StyleSheet.create({
|
||||
marginTop: 'auto',
|
||||
...CENTER,
|
||||
},
|
||||
cameraOverlay: { ...StyleSheet.absoluteFillObject, zIndex: 1 },
|
||||
topOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)' },
|
||||
middleRow: { flexDirection: 'row' },
|
||||
sideOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)' },
|
||||
bottomOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)' },
|
||||
pasteBTN: {
|
||||
borderRadius: 8,
|
||||
borderWidth: 2,
|
||||
@@ -298,34 +283,6 @@ const styles = StyleSheet.create({
|
||||
borderColor: COLORS.darkModeText,
|
||||
marginTop: 10,
|
||||
},
|
||||
cameraOverlay: {
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
overlay: {
|
||||
flex: 1,
|
||||
},
|
||||
topOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
},
|
||||
middleRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sideOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
},
|
||||
bottomOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
},
|
||||
pastBTNText: {
|
||||
color: COLORS.darkModeText,
|
||||
includeFontPadding: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ import Reanimated, {
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { navigateToSendUsingClipboard, getQRImage } from '../../functions';
|
||||
import { getQRImage } from '../../functions';
|
||||
import { GlobalThemeView, ThemeText } from '../../functions/CustomElements';
|
||||
import { backArrow } from '../../constants/styles';
|
||||
import FullLoadingScreen from '../../functions/CustomElements/loadingScreen';
|
||||
@@ -38,10 +38,8 @@ import { CameraPageNavBar } from '../../functions/CustomElements/camera/cameraPa
|
||||
import { crashlyticsLogReport } from '../../functions/crashlyticsLogs';
|
||||
import handlePreSendPageParsing from '../../functions/sendBitcoin/handlePreSendPageParsing';
|
||||
import ThemeIcon from '../../functions/CustomElements/themeIcon';
|
||||
import getClipboardText from '../../functions/getClipboardText';
|
||||
|
||||
Reanimated.addWhitelistedNativeProps({
|
||||
zoom: true,
|
||||
});
|
||||
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
||||
|
||||
export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
@@ -57,6 +55,7 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
const zoomOffset = useSharedValue(0);
|
||||
const zoom = useSharedValue(device?.neutralZoom || 1);
|
||||
const [isFlashOn, setIsFlashOn] = useState(false);
|
||||
const [isNavigatingAway, setIsNavigatingAway] = useState(false);
|
||||
const didScanRef = useRef(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -68,10 +67,11 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
const format = useCameraFormat(device?.formats?.length ? device : undefined, [
|
||||
{ photoAspectRatio: screenAspectRatio },
|
||||
]);
|
||||
const isCameraActive = useMemo(
|
||||
() => (navigate.canGoBack() ? isFocused : pageViewPage === 0),
|
||||
[navigate, isFocused, pageViewPage],
|
||||
);
|
||||
|
||||
const isCameraActive = useMemo(() => {
|
||||
const baseActive = navigate.canGoBack() ? isFocused : pageViewPage === 0;
|
||||
return baseActive && !isNavigatingAway;
|
||||
}, [isFocused, pageViewPage, navigate, isNavigatingAway]);
|
||||
|
||||
const qrBoxOutlineStyle = useMemo(
|
||||
() => ({
|
||||
@@ -110,48 +110,62 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
|
||||
const handleInvoice = useCallback(
|
||||
data => {
|
||||
const response = handlePreSendPageParsing(data);
|
||||
setIsNavigatingAway(true);
|
||||
|
||||
if (response.error) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: response.error });
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
const response = handlePreSendPageParsing(data);
|
||||
|
||||
if (response.navigateToWebView) {
|
||||
navigate.navigate('CustomWebView', {
|
||||
headerText: '',
|
||||
webViewURL: response.webViewURL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (response.error) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: response.error });
|
||||
setIsNavigatingAway(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === 'home')
|
||||
navigate.navigate('ConfirmPaymentScreen', {
|
||||
btcAdress: response.btcAdress,
|
||||
});
|
||||
else
|
||||
navigate.replace('ConfirmPaymentScreen', {
|
||||
btcAdress: response.btcAdress,
|
||||
});
|
||||
if (response.navigateToWebView) {
|
||||
navigate.navigate('CustomWebView', {
|
||||
headerText: '',
|
||||
webViewURL: response.webViewURL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === 'home')
|
||||
navigate.navigate('ConfirmPaymentScreen', {
|
||||
btcAdress: response.btcAdress,
|
||||
});
|
||||
else
|
||||
navigate.replace('ConfirmPaymentScreen', {
|
||||
btcAdress: response.btcAdress,
|
||||
});
|
||||
}, 100);
|
||||
},
|
||||
[navigate, from],
|
||||
);
|
||||
|
||||
const handleBarCodeScanned = codes => {
|
||||
if (didScanRef.current || codes.length === 0) return;
|
||||
const [data] = codes;
|
||||
const handleBarCodeScanned = useCallback(
|
||||
codes => {
|
||||
if (didScanRef.current || codes.length === 0 || isNavigatingAway) return;
|
||||
const [data] = codes;
|
||||
|
||||
if (data.type !== 'qr') return;
|
||||
crashlyticsLogReport('Hanlding scanned baracode');
|
||||
didScanRef.current = true;
|
||||
handleInvoice(data.value);
|
||||
};
|
||||
if (data.type !== 'qr') return;
|
||||
crashlyticsLogReport('Hanlding scanned baracode');
|
||||
didScanRef.current = true;
|
||||
handleInvoice(data.value);
|
||||
},
|
||||
[handleInvoice, isNavigatingAway],
|
||||
);
|
||||
|
||||
const codeScanner = useCodeScanner({
|
||||
codeTypes: ['qr'],
|
||||
onCodeScanned: handleBarCodeScanned,
|
||||
});
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setIsNavigatingAway(false);
|
||||
didScanRef.current = false;
|
||||
}, []),
|
||||
);
|
||||
const toggleFlash = useCallback(() => {
|
||||
if (!device?.hasTorch) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
@@ -203,6 +217,17 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
const handlePaste = useCallback(async () => {
|
||||
const response = await getClipboardText();
|
||||
|
||||
if (!response.didWork) {
|
||||
navigate.navigate('ErrorScreen', { errorMessage: t(response.reason) });
|
||||
return;
|
||||
}
|
||||
const clipboardData = response.data?.trim();
|
||||
handleInvoice(clipboardData);
|
||||
}, [navigate, handleInvoice]);
|
||||
|
||||
if (!hasPermission) {
|
||||
return (
|
||||
<GlobalThemeView useStandardWidth={true}>
|
||||
@@ -280,9 +305,7 @@ export default function SendPaymentHome({ pageViewPage, from }) {
|
||||
</View>
|
||||
<View style={styles.overlay}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
navigateToSendUsingClipboard(navigate, 'sendBTCPage', from, t);
|
||||
}}
|
||||
onPress={handlePaste}
|
||||
style={styles.pasteBTN}
|
||||
activeOpacity={0.2}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user