Merge pull request #7472 from BlueWallet/cameraki

REF: Upgrade Camera kit
This commit is contained in:
GLaDOS 2025-01-07 17:32:00 +00:00 committed by GitHub
commit 0ee3da9dc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 258 additions and 114 deletions

218
components/CameraScreen.tsx Normal file
View File

@ -0,0 +1,218 @@
import React, { useState, useRef } from 'react';
import { Animated, ImageURISource, SafeAreaView, StatusBar, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit';
import loc from '../loc';
import { Icon } from '@rneui/base';
interface CameraScreenProps {
onCancelButtonPress: () => void;
showImagePickerButton?: boolean;
showFilePickerButton?: boolean;
onImagePickerButtonPress?: () => void;
onFilePickerButtonPress?: () => void;
torchOnImage?: ImageURISource;
torchOffImage?: ImageURISource;
onReadCode?: (event: any) => void;
cameraFlipImage?: ImageURISource;
}
const CameraScreen: React.FC<CameraScreenProps> = ({
onCancelButtonPress,
showImagePickerButton,
showFilePickerButton,
onImagePickerButtonPress,
onFilePickerButtonPress,
torchOnImage,
torchOffImage,
onReadCode,
cameraFlipImage,
}) => {
const cameraRef = useRef<CameraApi>(null);
const [torchMode, setTorchMode] = useState(false);
const [cameraType, setCameraType] = useState(CameraType.Back);
const [zoom, setZoom] = useState<number | undefined>();
const [orientationAnim] = useState(new Animated.Value(3));
const onSwitchCameraPressed = () => {
const direction = cameraType === CameraType.Back ? CameraType.Front : CameraType.Back;
setCameraType(direction);
setZoom(1); // When changing camera type, reset to default zoom for that camera
};
const onSetTorch = () => {
setTorchMode(!torchMode);
};
// Counter-rotate the icons to indicate the actual orientation of the captured photo.
// For this example, it'll behave incorrectly since UI orientation is allowed (and already-counter rotates the entire screen)
// For real phone apps, lock your UI orientation using a library like 'react-native-orientation-locker'
const rotateUi = true;
const uiRotation = orientationAnim.interpolate({
inputRange: [1, 4],
outputRange: ['180deg', '-90deg'],
});
const uiRotationStyle = rotateUi ? { transform: [{ rotate: uiRotation }] } : undefined;
function rotateUiTo(rotationValue: number) {
Animated.timing(orientationAnim, {
toValue: rotationValue,
useNativeDriver: true,
duration: 200,
isInteraction: false,
}).start();
}
return (
<View style={styles.screen}>
<StatusBar hidden />
<SafeAreaView style={styles.topButtons}>
<TouchableOpacity style={styles.topButton} onPress={onSetTorch}>
<Animated.Image
source={torchMode ? torchOnImage : torchOffImage}
resizeMode="contain"
style={[styles.topButtonImg, uiRotationStyle]}
/>
</TouchableOpacity>
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Icon name="image" type="font-awesome" color="#ffffff" />
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</TouchableOpacity>
)}
</View>
</SafeAreaView>
<View style={styles.cameraContainer}>
<Camera
ref={cameraRef}
style={styles.cameraPreview}
cameraType={cameraType}
resetFocusWhenMotionDetected
zoom={zoom}
maxZoom={10}
onZoom={e => {
console.debug('zoom', e.nativeEvent.zoom);
setZoom(e.nativeEvent.zoom);
}}
onReadCode={onReadCode}
torchMode={torchMode ? 'on' : 'off'}
shutterPhotoSound
maxPhotoQualityPrioritization="quality"
onOrientationChange={e => {
// We recommend locking the camera UI to portrait (using a different library)
// and rotating the UI elements counter to the orientation
// However, we include onOrientationChange so you can match your UI to what the camera does
switch (e.nativeEvent.orientation) {
case Orientation.PORTRAIT_UPSIDE_DOWN:
console.debug('orientationChange', 'PORTRAIT_UPSIDE_DOWN');
rotateUiTo(1);
break;
case Orientation.LANDSCAPE_LEFT:
console.debug('orientationChange', 'LANDSCAPE_LEFT');
rotateUiTo(2);
break;
case Orientation.PORTRAIT:
console.debug('orientationChange', 'PORTRAIT');
rotateUiTo(3);
break;
case Orientation.LANDSCAPE_RIGHT:
console.debug('orientationChange', 'LANDSCAPE_RIGHT');
rotateUiTo(4);
break;
default:
console.debug('orientationChange', e.nativeEvent);
break;
}
}}
/>
</View>
<SafeAreaView style={styles.bottomButtons}>
<TouchableOpacity onPress={onCancelButtonPress}>
<Animated.Text style={[styles.backTextStyle, uiRotationStyle]}>{loc._.cancel}</Animated.Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bottomButton} onPress={onSwitchCameraPressed}>
<Animated.Image source={cameraFlipImage as ImageURISource} resizeMode="contain" style={[styles.topButtonImg, uiRotationStyle]} />
</TouchableOpacity>
</SafeAreaView>
</View>
);
};
export default CameraScreen;
const styles = StyleSheet.create({
screen: {
height: '100%',
backgroundColor: '#000000',
},
topButtons: {
margin: 10,
zIndex: 10,
flexDirection: 'row',
justifyContent: 'space-between',
},
topButton: {
backgroundColor: '#222',
width: 44,
height: 44,
borderRadius: 22,
justifyContent: 'center',
alignItems: 'center',
},
topButtonImg: {
margin: 10,
width: 24,
height: 24,
},
cameraContainer: {
justifyContent: 'center',
flex: 1,
},
cameraPreview: {
width: '100%',
height: '100%',
},
bottomButtons: {
margin: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
backTextStyle: {
padding: 10,
color: 'white',
fontSize: 20,
},
rightButtonsContainer: {
flexDirection: 'row',
alignItems: 'center',
},
bottomButton: {
backgroundColor: '#222',
width: 44,
height: 44,
borderRadius: 22,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
},
spacing: {
marginLeft: 20,
},
});

View File

@ -1588,8 +1588,27 @@ PODS:
- React-logger (= 0.75.4)
- React-perflogger (= 0.75.4)
- React-utils (= 0.75.4)
- ReactNativeCameraKit (13.0.0):
- ReactNativeCameraKit (14.1.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RealmJS (20.1.0):
- React
- RNCAsyncStorage (2.1.0):
@ -2258,7 +2277,7 @@ SPEC CHECKSUMS:
React-utils: 02526ea15628a768b8db9517b6017a1785c734d2
ReactCodegen: 8b5341ecb61898b8bd40a73ebc443c6bf2d14423
ReactCommon: 36d48f542b4010786d6b2bcee615fe5f906b7105
ReactNativeCameraKit: f058d47e0b1e55fd819bb55ee16505a2e0ca53db
ReactNativeCameraKit: e72b838dac4ea2da19b7eb5d00b23125072790fd
RealmJS: 9fd51c849eb552ade9f7b11db42a319b4f6cab4c
RNCAsyncStorage: c91d753ede6dc21862c4922cd13f98f7cfde578e
RNCClipboard: dbcf25b8f666b4685c02eeb65be981d30198e505

12
package-lock.json generated
View File

@ -63,7 +63,7 @@
"react-native": "0.75.4",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit": "13.0.0",
"react-native-camera-kit": "14.1.0",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "13.2.0",
@ -20530,12 +20530,12 @@
}
},
"node_modules/react-native-camera-kit": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/react-native-camera-kit/-/react-native-camera-kit-13.0.0.tgz",
"integrity": "sha512-fnkyivCG2xzS+14/doP8pCAYNafYaTyg5J0t+JJltJdgKSHf328OG44Rd+fnbbEOydZxgy/bcuLB24R0kCbynw==",
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/react-native-camera-kit/-/react-native-camera-kit-14.1.0.tgz",
"integrity": "sha512-idkg+Sa2KbGvF6SUqmuAr2U12qBELdiuUJ6fxgB4whUC2AyYHi5jBxiGv6whY/eTB3is7nW1S+TjyM9pEBzNzw==",
"license": "MIT",
"dependencies": {
"lodash": "^4.14.2"
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "*",

View File

@ -59,7 +59,7 @@
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
"ios": "react-native run-ios",
"postinstall": "rn-nodeify --install buffer,events,process,stream,inherits,path,assert,crypto --hack; npm run releasenotes2json; npm run branch2json; npm run patches",
"patches": "patch -p1 < scripts/react-native-camera-kit.patch;",
"patches": "",
"test": "npm run tslint && npm run lint && npm run unit && npm run jest",
"jest": "jest tests/integration/*",
"e2e:debug-build": "detox build -c android.debug",
@ -127,7 +127,7 @@
"react-native": "0.75.4",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit": "13.0.0",
"react-native-camera-kit": "14.1.0",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "13.2.0",

View File

@ -2,9 +2,7 @@ import { useFocusEffect, useIsFocused, useNavigation, useRoute } from '@react-na
import * as bitcoin from 'bitcoinjs-lib';
import createHash from 'create-hash';
import React, { useCallback, useEffect, useState } from 'react';
import { Alert, Image, Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import { CameraScreen } from 'react-native-camera-kit';
import { Icon } from '@rneui/themed';
import { Alert, Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import Base43 from '../../blue_modules/base43';
import * as fs from '../../blue_modules/fs';
import { BlueURDecoder, decodeUR, extractSingleWorkload } from '../../blue_modules/ur';
@ -15,6 +13,7 @@ import { useTheme } from '../../components/themes';
import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr';
import loc from '../../loc';
import { useSettings } from '../../hooks/context/useSettings';
import CameraScreen from '../../components/CameraScreen';
let decoder = false;
@ -23,39 +22,6 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: '#000000',
},
closeTouch: {
width: 40,
height: 40,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
left: 16,
top: 55,
},
closeImage: {
alignSelf: 'center',
},
imagePickerTouch: {
width: 40,
height: 40,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
left: 24,
bottom: 48,
},
filePickerTouch: {
width: 40,
height: 40,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
left: 96,
bottom: 48,
},
openSettingsContainer: {
flex: 1,
justifyContent: 'center',
@ -67,6 +33,9 @@ const styles = StyleSheet.create({
height: 60,
backgroundColor: 'rgba(0,0,0,0.01)',
position: 'absolute',
top: 10,
left: '50%',
transform: [{ translateX: -30 }],
},
backdoorInputWrapper: { position: 'absolute', left: '5%', top: '0%', width: '90%', height: '70%', backgroundColor: 'white' },
progressWrapper: { position: 'absolute', alignSelf: 'center', alignItems: 'center', top: '50%', padding: 8, borderRadius: 8 },
@ -288,7 +257,7 @@ const ScanQRCode = () => {
setIsLoading(false);
};
const showImagePicker = () => {
const onShowImagePickerButtonPress = () => {
if (!isLoading) {
setIsLoading(true);
fs.showImagePickerAndReadImage()
@ -321,29 +290,13 @@ const ScanQRCode = () => {
cameraFlipImage={require('../../img/camera-rotate-solid.png')}
onReadCode={event => onBarCodeRead({ data: event?.nativeEvent?.codeStringValue })}
showFrame={false}
showFilePickerButton={showFileImportButton}
showImagePickerButton={true}
onFilePickerButtonPress={showFilePicker}
onImagePickerButtonPress={onShowImagePickerButtonPress}
onCancelButtonPress={dismiss}
/>
) : null}
<TouchableOpacity accessibilityRole="button" accessibilityLabel={loc._.close} style={styles.closeTouch} onPress={dismiss}>
<Image style={styles.closeImage} source={require('../../img/close-white.png')} />
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={styles.imagePickerTouch}
onPress={showImagePicker}
>
<Icon name="image" type="font-awesome" color="#ffffff" />
</TouchableOpacity>
{showFileImportButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={styles.filePickerTouch}
onPress={showFilePicker}
>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</TouchableOpacity>
)}
{urTotal > 0 && (
<View style={[styles.progressWrapper, stylesHook.progressWrapper]} testID="UrProgressBar">
<BlueText>{loc.wallets.please_continue_scanning}</BlueText>

View File

@ -1,46 +0,0 @@
--- ../node_modules/react-native-camera-kit/android/src/main/java/com/rncamerakit/CKCamera.kt 2023-11-10 11:25:36
+++ ../node_modules/react-native-camera-kit/android/src/main/java/com/rncamerakit/CKCamera.kt 2023-11-10 11:25:42
@@ -180,7 +180,7 @@
orientationListener!!.enable()
val scaleDetector = ScaleGestureDetector(context, object: ScaleGestureDetector.SimpleOnScaleGestureListener() {
- override fun onScale(detector: ScaleGestureDetector?): Boolean {
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
if (zoomMode == "off") return true
val cameraControl = camera?.cameraControl ?: return true
val zoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: return true
--- ../node_modules/react-native-camera-kit/dist/CameraScreen.js 2024-09-01 13:00:57
+++ ../node_modules/react-native-camera-kit/dist/CameraScreen.js 2024-09-01 13:00:46
@@ -61,14 +61,14 @@
</TouchableOpacity>));
}
renderTorchButton() {
- return (!this.isCaptureRetakeMode() && (<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={() => this.onSetTorch()}>
- <Image style={[{ flex: 1, justifyContent: 'center' }, this.props.torchImageStyle]} source={this.state.torchMode ? this.props.torchOnImage : this.props.torchOffImage} resizeMode="contain"/>
+ return (!this.isCaptureRetakeMode() && (<TouchableOpacity style={{ backgroundColor: '#FFFFFF', borderRadius: 20, height: 40, marginHorizontal: 15 }} onPress={() => this.onSetTorch()}>
+ <Image style={[{ width: 40, height: 40, justifyContent: 'center' }, this.props.torchImageStyle]} source={this.state.torchMode ? this.props.torchOnImage : this.props.torchOffImage} resizeMode="contain"/>
</TouchableOpacity>));
}
renderSwitchCameraButton() {
return (this.props.cameraFlipImage &&
- !this.isCaptureRetakeMode() && (<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={() => this.onSwitchCameraPressed()}>
- <Image style={{ flex: 1, justifyContent: 'center' }} source={this.props.cameraFlipImage} resizeMode="contain"/>
+ !this.isCaptureRetakeMode() && (<TouchableOpacity style={{ }} onPress={() => this.onSwitchCameraPressed()}>
+ <Image style={{ width: 40, height: 40, justifyContent: 'center' }} source={this.props.cameraFlipImage} resizeMode="contain"/>
</TouchableOpacity>));
}
renderTopButtons() {
\ No newline at end of file
@@ -228,8 +228,8 @@
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
- paddingTop: 8,
- paddingBottom: 0,
+ paddingTop:44,
+ paddingHorizontal: 16,
},
cameraContainer: Object.assign({}, Platform.select({
android: {
\ No newline at end of file