mrousavy / react-native-vision-camera

📸 A powerful, high-performance React Native Camera library.
https://react-native-vision-camera.com
MIT License
7.56k stars 1.1k forks source link

🐛 onCodeScanned callback executes multiple times #3060

Closed criss02-cs closed 4 months ago

criss02-cs commented 4 months ago

What's happening?

I wanted to scan a QR code, but when I do it the onCodeScanned callback function is executed countless times, and is causing me problems

Reproduceable Code

import React, { JSXElementConstructor, ReactElement, useContext, useEffect, useState } from 'react';
import Footer from '../components/Footer';
import { Text, StyleSheet, TouchableOpacity, Alert, View, ImageBackground, Modal, FlatList, ListRenderItemInfo, Platform, Dimensions } from 'react-native'

import { Camera, useCameraDevice, useCodeScanner, useFrameProcessor } from 'react-native-vision-camera';
import { Images } from '../config';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { faArrowLeft, faCopy, faPencil, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
import Animated from 'react-native-reanimated';
import Header from '../components/Header';
import CircularProgress from 'react-native-circular-progress-indicator';
import { TotpUtils } from '../utils/totp-utils';
import { AuthContext } from '../context/AuthContext';
import { MfaList } from '../prototypes/MFA/MfaList';
import * as Keychain from 'react-native-keychain';
import GBRNMessage from '../utils/GBRNMessage';
import { Colors } from '../utils/Color';
import { cloneDeep } from 'lodash';
import * as CustomModal from 'react-native-modal';
import BarcodeMask from 'react-native-barcode-mask';
import Clipboard from '@react-native-clipboard/clipboard';

const checkCameraPermission = async () => {
    let status = Camera.getCameraPermissionStatus();
    if (status !== 'granted') {
        await Camera.requestCameraPermission();
        status = Camera.getCameraPermissionStatus();
        if (status === 'denied') {
            Alert.alert(
                'Non sarai in grado di scansionare nessun codice se non autorizzi i permessi',
            );
        }
    }
};

const MfaScreen = ({ navigation, route }) => {
    const { userInfo, isComeToForeground, setIsComeToForeground } = useContext(AuthContext);
    useEffect(() => {
        checkCameraPermission();
    }, []);

    const { height, width } = Dimensions.get('window');
    const maskRowHeight = Math.round((height - 300) / 20);
    const maskColWidth = (width - 300) / 2;

    const [secretKeys, setSecretKeys] = useState<MfaList[]>([]);

    // const devices = useCameraDevices();
    const device = useCameraDevice('back')
    const [modalVisible, setModalVisible] = useState(false);
    const [detailVisible, setDetailVisible] = useState(false);
    const [selectedKey, setSelectedKey] = useState<MfaList>(null);
    // const [frameProcessor, barcodes] = useScanBarcodes([BarcodeFormat.QR_CODE], {
    //     checkInverted: true,
    // });
    const codeScanner = useCodeScanner({
        codeTypes: ['qr'],
        onCodeScanned(codes) {
            console.log('qr code rilevato', codes);
            let barcodeValue = codes[0]?.value;
            if (!barcodeValue) return;
            if (!secretKeys.find(x => x.secretKey === barcodeValue)) {
                setModalVisible(false);
                GBRNMessage.notifyMessage('QRCode rilevato');
                setTimeout(() => {
                    if (Platform.OS === 'android') {
                        GBRNMessage.showInputDialog(
                            'GBsoftware authenticator',
                            '',
                            'Inserisci il nome dell\'account',
                            value => addNewSecretKey(value, barcodeValue),
                        )
                    } else {
                        Alert.prompt(
                            'GBsoftware authenticator',
                            'Inserisci il nome dell\'account',
                            (txt) => addNewSecretKey(txt, barcodeValue)

                        )
                    }
                }, 150);
            } else {
                GBRNMessage.notifyMessage('Codice già presente');
                codes = [];
                barcodeValue = '';
            }
        },

    })

    const addNewSecretKey = async (value: string, barcodeValue: string) => {
        await Keychain.setGenericPassword(value, barcodeValue, { service: value, storage: Keychain.STORAGE_TYPE.AES });
        const sharedSecret = totpService.extraxtSharedSecretFromUrl(barcodeValue);
        const rawValue = totpService.getOTP(sharedSecret);
        const sk = cloneDeep(secretKeys);
        sk.push({ account: value, secretKey: barcodeValue, rawValue: rawValue, timeRemaining: 30, epochSnapshot: 0 });
        setSecretKeys(sk);
    }
    const totpService = new TotpUtils();

    useEffect(() => {
        const interval = setInterval(() => {
            refreshToken();
        }, 500);
        return () => clearInterval(interval);
    });

    useEffect(() => {
        getAllSecretKeys();
    }, []);

    const refreshToken = async () => {
        const serviceNames = await Keychain.getAllGenericPasswordServices();
        const sk: MfaList[] = [];
        for (const service of serviceNames) {
            const credentials = await Keychain.getGenericPassword({ service, storage: Keychain.STORAGE_TYPE.AES });
            if (credentials) {
                if (credentials.password && credentials.password.includes('otpauth://')) {
                    const sharedSecret = totpService.extraxtSharedSecretFromUrl(credentials.password);
                    // console.log(credentials.password);
                    const rawValue = totpService.getOTP(sharedSecret);
                    const epoch = Math.round(new Date().getTime() / 1000.0);
                    const countdown = (30 - (epoch % 30));
                    sk.push({
                        account: credentials.username, secretKey: credentials.password,
                        rawValue: rawValue, timeRemaining: countdown, epochSnapshot: 0,
                    });
                }
            }
        }
        setSecretKeys(sk);
    }
    const getAllSecretKeys = async () => {
        try {
            if (secretKeys.length === 0 || isComeToForeground) {
                setIsComeToForeground(false);
                const serviceNames = await Keychain.getAllGenericPasswordServices();
                const sk: MfaList[] = [];
                for (const service of serviceNames) {
                    const credentials = await Keychain.getGenericPassword({ service, storage: Keychain.STORAGE_TYPE.AES });
                    if (credentials) {
                        if (credentials.password && credentials.password.includes('otpauth://')) {
                            const sharedSecret = totpService.extraxtSharedSecretFromUrl(credentials.password);
                            const rawValue = totpService.getOTP(sharedSecret);
                            const epoch = Math.round(new Date().getTime() / 1000.0);
                            const countdown = (30 - (epoch % 30));
                            sk.push({
                                account: credentials.username, secretKey: credentials.password,
                                rawValue: rawValue, timeRemaining: countdown, epochSnapshot: 0,
                            });
                        }
                    }
                }
                setSecretKeys(sk);
            }
        } catch (error) {
            console.log('Errore nel recupero di tutte le chiavi segrete:', error);
        }
    }

    function renderOtp(info: ListRenderItemInfo<MfaList>): ReactElement<any, string | JSXElementConstructor<any>> {
        return (
            <TouchableOpacity style={{ backgroundColor: Colors.bianco, padding: 10, borderRadius: 10, marginBottom: 10 }} onLongPress={() => {
                setSelectedKey(info.item);
                setDetailVisible(true);
            }}>
                <Text style={{ color: Colors.nero, fontFamily: 'Roboto-Bold' }}>{info.item.account}</Text>
                <View style={{ alignItems: 'center', flexDirection: 'row', justifyContent: 'space-between' }}>
                    <Text style={{ color: Colors.arancioneGb, fontFamily: 'Roboto-Bold', fontSize: 25 }}>{info.item.rawValue}</Text>
                    <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 25 }}>
                        <TouchableOpacity style={{
                            alignItems: 'center',
                        }} onPress={() => {
                            Clipboard.setString(info.item.rawValue.replace(' ', ''));
                            GBRNMessage.notifyMessage('Codice copiato correttamente');
                        }}>
                            <FontAwesomeIcon icon={faCopy} color={Colors.nero} size={35} style={{ marginLeft: 5 }} />
                        </TouchableOpacity>
                        <CircularProgress radius={20} value={info.item.timeRemaining} subtitleColor='#000'
                            valueSuffix={''}
                            activeStrokeColor='#F07E00'
                            progressValueColor='#F07E00'
                            titleColor='#000'
                            inActiveStrokeOpacity={0.2}
                            inActiveStrokeWidth={5}
                            maxValue={30}
                        />
                    </View>
                </View>
            </TouchableOpacity>
        )
    }

    return (
        <View style={styles.container}>
            {
                userInfo ?
                    (
                        <Header navigation={navigation} onOpzioniPress={() => { }} />
                    ) : null
            }
            <ImageBackground source={Images.backgroundImage} style={[styles.container]}>
                <View style={{ margin: 10, marginTop: Platform.OS === 'ios' && !userInfo ? 50 : 10 }}>
                    <FlatList
                        data={secretKeys}
                        renderItem={renderOtp}
                    />
                </View>
                <TouchableOpacity style={{
                    position: 'absolute', bottom: 70, right: 20,
                    backgroundColor: Colors.verdeGb, width: 50, height: 50,
                    borderRadius: 25, justifyContent: 'center', alignItems: 'center'
                }} onPress={() => {
                    setModalVisible(true);
                }}>
                    <FontAwesomeIcon icon={faPlus} color={Colors.bianco} size={30} />
                </TouchableOpacity>
                <Modal visible={modalVisible} animationType='slide' onRequestClose={() => setModalVisible(false)}>

                    <Camera device={device} isActive={true} style={StyleSheet.absoluteFill}
                        codeScanner={codeScanner} >
                        <BarcodeMask />

                    </Camera>
                    <TouchableOpacity style={{
                        position: 'absolute', top: 70, left: 0, width: 60, height: 60,
                        borderRadius: 25, justifyContent: 'center', alignItems: 'center', zIndex: 999
                    }} onPress={() => {
                        setModalVisible(false);
                    }}>
                        <FontAwesomeIcon icon={faArrowLeft} color={Colors.bianco} size={40} />
                    </TouchableOpacity>
                    {
                        Platform.OS === 'android' ?
                            (
                                <BarcodeMask />
                            ) : null
                    }
                </Modal>
                <CustomModal.ReactNativeModal isVisible={detailVisible} onBackdropPress={() => setDetailVisible(false)}
                    onBackButtonPress={() => setDetailVisible(false)} animationIn='slideInUp' animationOut='slideOutDown'
                    style={{ margin: 0 }} backdropOpacity={0}>
                    <View style={{ backgroundColor: '#424143', height: '35%', marginTop: 'auto', zIndex: 1000, width: '100%' }}>
                        <View style={{ alignItems: 'center' }}>
                            <TouchableOpacity onPressOut={() => setDetailVisible(false)}>
                                <View style={{
                                    borderBottomColor: '#F07E00',
                                    borderBottomWidth: 5,
                                    borderRadius: 5,
                                    width: 80,
                                    margin: 15,
                                    marginTop: 5
                                }} />
                            </TouchableOpacity>
                        </View>
                        <View style={{ flexDirection: 'row', marginLeft: 15, alignItems: 'center' }}>
                            <View style={{ flex: 1, }}>
                                <Text style={{ color: '#fff', fontFamily: 'Roboto-Bold', fontSize: 18 }} lineBreakMode='middle'>
                                    {selectedKey?.account ?? ''}
                                </Text>
                            </View>
                        </View>
                        <View style={{ alignItems: 'center', width: '100%' }}>
                            <View style={{
                                borderBottomColor: '#F07E00',
                                borderBottomWidth: 5,
                                borderRadius: 5,
                                width: 335,
                                margin: 10,
                                marginTop: 10,
                                marginBottom: 5
                            }} />
                        </View>
                        <View style={{ marginLeft: 20, marginBottom: 20 }}>
                            <TouchableOpacity style={{
                                marginTop: 10, flexDirection: 'row', alignItems: 'center',
                            }} onPress={() => {
                                GBRNMessage.showInputDialog(
                                    'GBsoftware authenticator',
                                    selectedKey.account,
                                    'Inserisci il nome dell\'account',
                                    async txt => {
                                        const response = await Keychain.resetGenericPassword({ service: selectedKey.account });
                                        console.log('Response', response)
                                        if (response) {
                                            const resp = await Keychain.setGenericPassword(txt, selectedKey.secretKey, { service: txt, storage: Keychain.STORAGE_TYPE.AES });
                                            const sk = cloneDeep(secretKeys);
                                            const secretKey = sk.find(x => x.secretKey === selectedKey.secretKey);
                                            secretKey.account = txt;
                                            setSecretKeys(sk);
                                            setDetailVisible(false);
                                        }
                                    }
                                )
                            }}>
                                <Text style={{ color: '#fff', fontFamily: 'Roboto-Bold', marginRight: 5 }}>Modifica</Text>
                                <FontAwesomeIcon icon={faPencil} color={Colors.bianco} style={{ marginLeft: 5 }} />
                            </TouchableOpacity>
                            <TouchableOpacity style={{
                                marginTop: 10, flexDirection: 'row',
                                alignItems: 'center',
                            }} onPress={() => {
                                Keychain.resetGenericPassword({ service: selectedKey.account })
                                    .then(value => {
                                        if (value) {
                                            const s = secretKeys.find(x => x.secretKey === selectedKey.secretKey);
                                            secretKeys.remove(s);
                                            setDetailVisible(false);
                                        }
                                    });
                            }} >
                                <Text style={{ color: '#fff', fontFamily: 'Roboto-Bold', marginRight: 5 }}>Elimina</Text>
                                <FontAwesomeIcon icon={faTrash} color={Colors.bianco} style={{ marginLeft: 5 }} />
                            </TouchableOpacity>
                        </View>
                    </View>
                </CustomModal.ReactNativeModal>
                <Footer style={{ position: 'absolute', bottom: 0 }} onHomePress={() => {
                }} navigation={navigation} onFiltriPress={() => { }} onCartellePress={() => { }} />
            </ImageBackground>
        </View>
    );

};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        // alignItems: 'center',
        // justifyContent: 'center',
        width: '100%',
    },
    wrapper: {
        backgroundColor: '#fff',
        alignItems: 'center',
        //justifyContent: 'center',
        borderRadius: 10,
        width: '90%',
        height: '70%',
        marginTop: -65
    },
    wrapper2: {
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: 10,
        width: '90%',
        height: '65%',
    },
    input: {
        marginTop: 30,
        marginBottom: 12,
        borderWidth: 1,
        borderColor: '#bbb',
        borderRadius: 5,
        paddingHorizontal: 14,
        width: 300,
        color: '#000'
    },
    btnRegistra: { height: 40, backgroundColor: '#72b1e9', justifyContent: 'center', alignSelf: 'flex-end', marginRight: 5, width: 50, alignItems: 'center', borderRadius: 5 },
    btnText: { textAlign: 'center', color: '#fff', fontFamily: 'Roboto-Regular' },
    image: { width: 150, height: 120, margin: 10 },
    textWrapper: { margin: 20, alignItems: 'center', flex: 1 },
    addText: { color: '#000', marginTop: 9, marginLeft: 0, fontSize: 15, fontFamily: 'Roboto-Regular' },
    maskOutter: {
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        alignItems: 'center',
        justifyContent: 'space-around',
        zIndex: 99999999999,
    },
    maskInner: {
        width: 300,
        backgroundColor: 'transparent',
        borderColor: 'white',
        borderWidth: 1,
    },
    maskFrame: {
        backgroundColor: 'rgba(1,1,1,0.6)',
    },
    maskRow: {
        width: '100%',
    },
    maskCenter: { flexDirection: 'row' },
});

export default MfaScreen;

Relevant log output

LOG qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 121.84621095657349, "width": 124.3846321105957, "x": 623.3076667785645, "y": 343.576877117157}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
LOG qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21365213394165, "width": 122.13214874267578, "x": 630.5907440185547, "y": 337.20163106918335}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.2135877609253, "width": 122.13214874267578, "x": 629.5907592773438, "y": 331.2016797065735}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 120.95818519592285, "width": 121.88684463500977, "x": 624.7365760803223, "y": 326.31505966186523}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 120.95818519592285, "width": 121.88684463500977, "x": 620.7365798950195, "y": 327.3150944709778}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 123.4713077545166, "width": 124.38589096069336, "x": 618.684139251709, "y": 328.2746386528015}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 123.4713077545166, "width": 124.38583374023438, "x": 617.684154510498, "y": 329.27467346191406}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21069097518921, "width": 123.11599731445312, "x": 615.8600234985352, "y": 330.41253089904785}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21069097518921, "width": 123.11599731445312, "x": 615.8600234985352, "y": 331.4125657081604}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21719264984131, "width": 124.62993621826172, "x": 614.2736434936523, "y": 330.2107858657837}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21725702285767, "width": 124.62993621826172, "x": 611.2736320495605, "y": 329.21075105667114}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.22086191177368, "width": 125.6649398803711, "x": 606.3043212890625, "y": 326.15026473999023}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.22092628479004, "width": 125.6649398803711, "x": 602.3043251037598, "y": 324.1502594947815}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21616268157959, "width": 125.66001892089844, "x": 598.3986854553223, "y": 319.2714285850525}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.21616268157959, "width": 125.66001892089844, "x": 594.3986892700195, "y": 314.2714476585388}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.22414493560791, "width": 126.93758010864258, "x": 591.1133193969727, "y": 310.20249366760254}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 122.22414493560791, "width": 126.93758010864258, "x": 589.1133499145508, "y": 306.20248317718506}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 123.47504138946533, "width": 125.65893173217773, "x": 585.4245185852051, "y": 298.3001160621643}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 123.47504138946533, "width": 125.65893173217773, "x": 580.4244804382324, "y": 291.3001298904419}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 124.73655939102173, "width": 126.92985534667969, "x": 572.2491073608398, "y": 285.22936820983887}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]
 LOG  qr code rilevato [{"corners": [[Object], [Object], [Object], [Object]], "frame": {"height": 124.73655939102173, "width": 126.92985534667969, "x": 569.249095916748, "y": 281.2293577194214}, "type": "qr", "value": "otpauth://totp/GBinWeb?secret=Y7FTJ2EDSQY4AU3YAZXRHCGFEO4XQ23F"}]

Camera Device

{
  "isMultiCam": false,
  "supportsLowLightBoost": false,
  "maxExposure": 8,
  "minFocusDistance": 12,
  "position": "back",
  "id": "com.apple.avfoundation.avcapturedevice.built-in_video:0",
  "formats": [],
  "maxZoom": 123.75,
  "minExposure": -8,
  "name": "Fotocamera (posteriore)",
  "hasFlash": true,
  "hasTorch": true,
  "neutralZoom": 1,
  "supportsRawCapture": false,
  "supportsFocus": true,
  "physicalDevices": [
    "wide-angle-camera"
  ],
  "hardwareLevel": "full",
  "minZoom": 1,
  "sensorOrientation": "landscape-left"
}

Device

iPhone 12 Pro Max

VisionCamera Version

4.0.5

Can you reproduce this issue in the VisionCamera Example app?

No, I cannot reproduce the issue in the Example app

Additional information

maintenance-hans[bot] commented 4 months ago

Guten Tag, Hans here.

[!NOTE] New features, bugfixes, updates and other improvements are all handled mostly by @mrousavy in his free time. To support @mrousavy, please consider 💖 sponsoring him on GitHub 💖. Sponsored issues will be prioritized.

mrousavy commented 4 months ago

This is intentional. Code coordinates change, hence it's being called multiple times.

criss02-cs commented 4 months ago

But then why don't I have this problem on Android?

mrousavy commented 4 months ago

For me on Android it will also be called multiple times. Maybe not as often as on iOS, but also multiple times.

criss02-cs commented 4 months ago

On my Android device is called one time, but in my iPhone device is called multiple times. There is a way to fix this?

mrousavy commented 4 months ago

Just debounce it if you only care about the result. On Android it should also be called multiple times. I don't have free time to fix this, but maybe I will take a look at this in the future - we'll see.

UmerQur3shi commented 2 days ago

This is intentional. Code coordinates change, hence it's being called multiple times.

Can you please add a timeout prop so that it will wait for given number of seconds before scanning the next code or the same code again?

mrousavy commented 2 days ago

Can you please add a timeout prop so that it will wait for given number of seconds before scanning the next code or the same code again?

No. This is something you can easily do yourself - much easier in JS than in native code.

criss02-cs commented 2 days ago

I tried to set a delay, but still cannot stop multiple values