mrousavy / react-native-vision-camera

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

🐛 V3: Unable to resize (width) camera view in Android #1957

Closed ahmettaydinn closed 11 months ago

ahmettaydinn commented 11 months ago

What's happening?

I am building a form on top of which has a barcode scanner. I want the camera to fill the whole width and roughly 1/5 height of the view. However, In Android camera height grows 3 times bigger than the width. I want width bigger than height. I cannot set custom width in Android. In IOS same issue does not take place. I'll leave my component down below. Resize mode does not solve my problem since it fits the camera inside the container in the same proportion (roughly 3x height 1x width) ... I have read this issue but it does not provide necessary or useful solution for my case. ... I'll attach two images representing IOS and Android versions with the same code. My expectation is android being resizable as IOS . (Second one is IOS as it seems) IMG_20231009_180357 WhatsApp Image 2023-10-09 at 18 15 04 ... Thank you for your great contribution on the RN open source btw.

Reproduceable Code

import { useGetExternalSku } from '@sms/mobile'
import React, { useEffect, useState } from 'react'
import { colors } from '@sms/common'
import {
  ActivityIndicator,
  Dimensions,
  StyleSheet,
  TextInput,
  TouchableOpacity,
  View
} from 'react-native'
import { scaleByWidth } from '@common/mobile'
import { PermissionsAndroid } from 'react-native'
import Ionicons from 'react-native-vector-icons/Ionicons'
import {
  useCameraDevice,
  useCameraFormat,
  Camera as VisionCamera
} from 'react-native-vision-camera'
import { useDebounce } from '@common/cross-platform'
import { useCodeScanner } from 'react-native-vision-camera'
import { useIsFocused } from '@react-navigation/native'
import { useAppState } from '@react-native-community/hooks'

const BarcodeInput = ({
  barcode,
  foundation_id,
  value,
  placeholder,
  onChangeText,
  fieldName,
  setScannedBarcode,
  setNameOfESKU,
  setFormattedInlineForm,
  nameOfESKU
}) => {
  const [cameraPermission, setCameraPermission] = useState('denied')

  const isCameraOpen = value.length < 8

  const isFocused = useIsFocused()
  const appState = useAppState()
  const isActive = isFocused && appState === 'active' && isCameraOpen

  const device = useCameraDevice('back')
  const format = useCameraFormat(device, [
    { photoResolution: { width: 1920, height: 1080 } }
  ])

  useEffect(() => {
    onChangeText(barcode, fieldName)
  }, [])

  const debouncedValue = useDebounce(value, 500)

  useGetExternalSku({
    value,
    foundation_id,
    debouncedValue,
    options: {
      onSuccess: data => {
        setFormattedInlineForm(prev => ({
          ...prev,
          ['Ürün Adı']: { ...prev['Ürün Adı'], value: data?.external_sku_name }
        }))
        setNameOfESKU(data?.external_sku_name)
      },
      onError: () => {
        if (nameOfESKU)
          setFormattedInlineForm(prev => ({
            ...prev,
            ['Ürün Adı']: { ...prev['Ürün Adı'], value: '' }
          }))
        setNameOfESKU('not_found')
      }
    }
  })

  const requestCameraPermission = async () => {
    try {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.CAMERA,
        {
          title: 'Barcode Scanner Permission',
          message: 'Barcode Scanner needs access to your camera',
          buttonNeutral: 'Ask Me Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK'
        }
      )
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        setCameraPermission('granted')
      } else {
        console.warn('Camera permission denied')
      }
    } catch (err) {
      console.warn(err)
    }
  }

  const onBarcodeRead = barcodeArg => {
    if (barcodeArg !== barcode) {
      setFormattedInlineForm(prev => ({
        ...prev,
        ['Barkod']: { ...prev['Barkod'], value: `${barcodeArg}` }
      }))
      setScannedBarcode(`${barcodeArg}`)
    }
  }

  const codeScanner = useCodeScanner({
    codeTypes: ['ean-13'],
    onCodeScanned: codes => {
      console.log('codes', codes)
      onBarcodeRead(codes?.[0]?.value)
    }
  })

  useEffect(() => {
    setFormattedInlineForm(prev => ({
      ...prev,
      ['Ürün Adı']: { ...prev['Ürün Adı'], value: `` }
    }))
    setNameOfESKU('')
  }, [barcode])

  useEffect(() => {
    if (Platform.OS !== 'ios') {
      requestCameraPermission()
    }
  }, [])

  // if (Platform.OS !== 'ios') {
  //   if (cameraPermission !== 'granted') {
  //     return (
  //       <View>
  //         <ActivityIndicator />
  //       </View>
  //     )
  //   }
  // }

  function clearBarcode() {
    setFormattedInlineForm(prev => ({
      ...prev,
      ['Barkod']: { ...prev['Barkod'], value: '' }
    }))

    setFormattedInlineForm(prev => ({
      ...prev,
      ['Ürün Adı']: { ...prev['Ürün Adı'], value: `` }
    }))

    setScannedBarcode('')
    setNameOfESKU('')
  }

  return (
    <>
      <View style={styles.container}>
        {
          <View
            style={[
              styles.barcodeScannerContainer,
              { width: Dimensions.get('screen').width }
            ]}
          >
            <VisionCamera
              codeScanner={codeScanner}
              device={device}
              isActive={isActive}
              format={format}
              style={styles.camera}
            />
          </View>
        }
        <View
          style={[
            styles.barcodeInputOuterContainer
            // !isCameraOpen && { paddingTop: scaleByWidth(40) }
          ]}
        >
          <View style={styles.barcodeInputInnerContainer}>
            <TextInput
              style={styles.barcodeInput}
              value={value ?? barcode}
              onChangeText={text => {
                onChangeText(text, fieldName)
                setNameOfESKU('')
                setScannedBarcode('')
                setFormattedInlineForm(prev => ({
                  ...prev,
                  ['Ürün Adı']: { ...prev['Ürün Adı'], value: '' }
                }))
              }}
              placeholder={placeholder ?? ''}
            />
            <TouchableOpacity onPress={clearBarcode}>
              <Ionicons name='barcode-outline' size={36} />
            </TouchableOpacity>
          </View>
        </View>
      </View>
    </>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  barcodeInputInnerContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    borderColor: colors.fgGray,
    borderWidth: 1,
    borderRadius: 3,
    width: Dimensions.get('screen').width * 0.9,
    alignItems: 'center',
    height: scaleByWidth(45)
  },

  barcodeInputOuterContainer: {
    alignItems: 'center',
    justifyContent: 'flex-start'
  },
  barcodeInput: { flex: 1, fontFamily: 'Quicksand', paddingHorizontal: 5 },
  iconStyle: {
    position: 'absolute',
    bottom: scaleByWidth(10),
    left: scaleByWidth(5),
    borderWidth: 0.5,
    padding: 1,
    borderColor: 'white'
  },
  camera: {
    width: '100%',
    height: '100%'
  },
  barcodeScannerContainer: {
    marginBottom: scaleByWidth(10),
    height: scaleByWidth(190)
    // borderWidth: 3,
    // borderColor: 'red'
  }
})

export default BarcodeInput

Relevant log output

I do not have any runtime issue. My concern is on styling of the camera.

Camera Device

{
  "sensorOrientation": "landscape-right",
  "hardwareLevel": "full",
  "maxZoom": 10,
  "minZoom": 1,
  "supportsLowLightBoost": false,
  "neutralZoom": 1,
  "physicalDevices": [
    "wide-angle-camera"
  ],
  "supportsFocus": true,
  "supportsRawCapture": true,
  "isMultiCam": false,
  "name": "BACK (0)",
  "hasFlash": true,
  "hasTorch": true,
  "position": "back",
  "id": "0"
}

Device

Redmi Note 8 Pro (Android 11)

VisionCamera Version

3.3.1

Can you reproduce this issue in the VisionCamera Example app?

Yes, I can reproduce the same issue in the Example app here

Additional information

mrousavy commented 11 months ago

Did you try adding overflow: 'hidden'?

ahmettaydinn commented 11 months ago

Did you try adding overflow: 'hidden'?

Yes I tried, it solves my issue. Might hiding cause any problem in terms of scanning since it might scan in the part which is outside the view? Just out of curiosity, Does this issue happens due to Android native camera behaviour?

mrousavy commented 11 months ago

Gotcha, yea I might just use that as a default prop. This is just how the SurfaceView works under the hood on Android, and no it does not affect any Camera behaviour, this is simply the preview.

Let's leave this open until I fix it on the native side

ahmettaydinn commented 11 months ago

Gotcha, yea I might just use that as a default prop. This is just how the SurfaceView works under the hood on Android, and no it does not affect any Camera behaviour, this is simply the preview.

Let's leave this open until I fix it on the native side

I see, thank you in advance

mrousavy commented 11 months ago

Fixed in https://github.com/mrousavy/react-native-vision-camera/pull/2001