From 075907eae7b375c7bfe95fdeee4eedf41e83e744 Mon Sep 17 00:00:00 2001
From: Janic Duplessis <janicduplessis@gmail.com>
Date: Tue, 23 Jan 2024 18:16:08 -0500
Subject: [PATCH 1/2] Add tap to focus and fix orientation

---
 .../step/IOURequestStepScan/index.native.js   | 56 +++++++++++++++----
 src/styles/index.ts                           | 12 ++++
 2 files changed, 58 insertions(+), 10 deletions(-)

diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.js b/src/pages/iou/request/step/IOURequestStepScan/index.native.js
index 23e30ce25711..d84a2b282a1b 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.js
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.js
@@ -1,7 +1,9 @@
 import lodashGet from 'lodash/get';
 import React, {useCallback, useEffect, useRef, useState} from 'react';
 import {ActivityIndicator, Alert, AppState, View} from 'react-native';
+import {Gesture, GestureDetector} from 'react-native-gesture-handler';
 import {RESULTS} from 'react-native-permissions';
+import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withDelay, withSequence, withSpring, withTiming} from 'react-native-reanimated';
 import {useCameraDevices} from 'react-native-vision-camera';
 import Hand from '@assets/images/hand.svg';
 import Shutter from '@assets/images/shutter.svg';
@@ -66,6 +68,36 @@ function IOURequestStepScan({
 
     const {translate} = useLocalize();
 
+    const focusIndicatorOpacity = useSharedValue(0);
+    const focusIndicatorScale = useSharedValue(2);
+    const focusIndicatorPosition = useSharedValue({x: 0, y: 0});
+
+    const cameraFocusIndicatorAnimatedStyle = useAnimatedStyle(() => ({
+        opacity: focusIndicatorOpacity.value,
+        transform: [{translateX: focusIndicatorPosition.value.x}, {translateY: focusIndicatorPosition.value.y}, {scale: focusIndicatorScale.value}],
+    }));
+
+    const focusCamera = (point) => {
+        if (!camera.current) {
+            return;
+        }
+
+        camera.current.focus(point);
+    };
+
+    const tapGesture = Gesture.Tap()
+        .enabled(device && device.supportsFocus)
+        .onStart((ev) => {
+            const point = {x: ev.x, y: ev.y};
+
+            focusIndicatorOpacity.value = withSequence(withTiming(0.8, {duration: 250}), withDelay(1000, withTiming(0, {duration: 250})));
+            focusIndicatorScale.value = 2;
+            focusIndicatorScale.value = withSpring(1, {damping: 10, stiffness: 200});
+            focusIndicatorPosition.value = point;
+
+            runOnJS(focusCamera)(point);
+        });
+
     useEffect(() => {
         const refreshCameraPermissionStatus = () => {
             CameraPermission.getCameraPermissionStatus()
@@ -255,16 +287,20 @@ function IOURequestStepScan({
             )}
             {cameraPermissionStatus === RESULTS.GRANTED && device != null && (
                 <View style={[styles.cameraView]}>
-                    <View style={styles.flex1}>
-                        <NavigationAwareCamera
-                            ref={camera}
-                            device={device}
-                            style={[styles.flex1]}
-                            zoom={device.neutralZoom}
-                            photo
-                            cameraTabIndex={1}
-                        />
-                    </View>
+                    <GestureDetector gesture={tapGesture}>
+                        <View style={styles.flex1}>
+                            <NavigationAwareCamera
+                                ref={camera}
+                                device={device}
+                                style={[styles.flex1]}
+                                zoom={device.neutralZoom}
+                                photo
+                                cameraTabIndex={1}
+                                orientation="portrait"
+                            />
+                            <Animated.View style={[styles.cameraFocusIndicator, cameraFocusIndicatorAnimatedStyle]} />
+                        </View>
+                    </GestureDetector>
                 </View>
             )}
             <View style={[styles.flexRow, styles.justifyContentAround, styles.alignItemsCenter, styles.pv3]}>
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 9bfe407593df..d1f4fbac8abf 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -855,6 +855,18 @@ const styles = (theme: ThemeColors) =>
             justifyItems: 'center',
         },
 
+        cameraFocusIndicator: {
+            position: 'absolute',
+            left: -32,
+            top: -32,
+            width: 64,
+            height: 64,
+            borderRadius: 32,
+            borderWidth: 2,
+            borderColor: theme.white,
+            pointerEvents: 'none',
+        },
+
         permissionView: {
             paddingVertical: 108,
             paddingHorizontal: 61,

From 3fb005d3195c774a1c5c905bb1b242cb9a3ca229 Mon Sep 17 00:00:00 2001
From: Janic Duplessis <janicduplessis@gmail.com>
Date: Wed, 31 Jan 2024 23:02:27 -0500
Subject: [PATCH 2/2] Ignore warning on android

---
 .../iou/request/step/IOURequestStepScan/index.native.js    | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.js b/src/pages/iou/request/step/IOURequestStepScan/index.native.js
index d84a2b282a1b..9e30b2809670 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.js
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.js
@@ -82,7 +82,12 @@ function IOURequestStepScan({
             return;
         }
 
-        camera.current.focus(point);
+        camera.current.focus(point).catch((ex) => {
+            if (ex.message === '[unknown/unknown] Cancelled by another startFocusAndMetering()') {
+                return;
+            }
+            Log.warn('Error focusing camera', ex);
+        });
     };
 
     const tapGesture = Gesture.Tap()