Merge branch 'master' into electrumpref

This commit is contained in:
Marcos Rodriguez Velez 2025-01-07 14:48:13 -04:00
commit 2dc3efd391
13 changed files with 372 additions and 239 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

@ -6,6 +6,7 @@ import { WalletCarouselItem } from './WalletsCarousel';
import { TransactionListItem } from './TransactionListItem'; import { TransactionListItem } from './TransactionListItem';
import { useTheme } from './themes'; import { useTheme } from './themes';
import { BitcoinUnit } from '../models/bitcoinUnits'; import { BitcoinUnit } from '../models/bitcoinUnits';
import { TouchableOpacityWrapper } from './ListItem';
enum ItemType { enum ItemType {
WalletSection = 'wallet', WalletSection = 'wallet',
@ -29,11 +30,14 @@ interface ManageWalletsListItemProps {
isDraggingDisabled: boolean; isDraggingDisabled: boolean;
drag?: () => void; drag?: () => void;
isPlaceHolder?: boolean; isPlaceHolder?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
state: { wallets: TWallet[]; searchQuery: string }; state: { wallets: TWallet[]; searchQuery: string };
navigateToWallet: (wallet: TWallet) => void; navigateToWallet: (wallet: TWallet) => void;
renderHighlightedText: (text: string, query: string) => JSX.Element; renderHighlightedText: (text: string, query: string) => JSX.Element;
handleDeleteWallet: (wallet: TWallet) => void; handleDeleteWallet: (wallet: TWallet) => void;
handleToggleHideBalance: (wallet: TWallet) => void; handleToggleHideBalance: (wallet: TWallet) => void;
isActive?: boolean;
} }
interface SwipeContentProps { interface SwipeContentProps {
@ -67,6 +71,9 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
renderHighlightedText, renderHighlightedText,
handleDeleteWallet, handleDeleteWallet,
handleToggleHideBalance, handleToggleHideBalance,
onPressIn,
onPressOut,
isActive,
}) => { }) => {
const { colors } = useTheme(); const { colors } = useTheme();
@ -110,6 +117,10 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
containerStyle={{ backgroundColor: colors.background }} containerStyle={{ backgroundColor: colors.background }}
leftContent={leftContent} leftContent={leftContent}
rightContent={rightContent} rightContent={rightContent}
Component={TouchableOpacityWrapper}
onPressOut={onPressOut}
onPressIn={onPressIn}
style={isActive ? styles.activeItem : undefined}
> >
<ListItem.Content <ListItem.Content
style={{ style={{
@ -121,6 +132,8 @@ const ManageWalletsListItem: React.FC<ManageWalletsListItemProps> = ({
item={item.data} item={item.data}
handleLongPress={isDraggingDisabled ? undefined : drag} handleLongPress={isDraggingDisabled ? undefined : drag}
onPress={onPress} onPress={onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
animationsEnabled={false} animationsEnabled={false}
searchQuery={state.searchQuery} searchQuery={state.searchQuery}
isPlaceHolder={isPlaceHolder} isPlaceHolder={isPlaceHolder}
@ -164,6 +177,9 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
backgroundColor: 'red', backgroundColor: 'red',
}, },
activeItem: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
}); });
export { ManageWalletsListItem, LeftSwipeContent, RightSwipeContent }; export { ManageWalletsListItem, LeftSwipeContent, RightSwipeContent };

View file

@ -172,6 +172,8 @@ interface WalletCarouselItemProps {
searchQuery?: string; searchQuery?: string;
renderHighlightedText?: (text: string, query: string) => JSX.Element; renderHighlightedText?: (text: string, query: string) => JSX.Element;
animationsEnabled?: boolean; animationsEnabled?: boolean;
onPressIn?: () => void;
onPressOut?: () => void;
} }
export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo( export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
@ -186,6 +188,8 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
renderHighlightedText, renderHighlightedText,
animationsEnabled = true, animationsEnabled = true,
isPlaceHolder = false, isPlaceHolder = false,
onPressIn,
onPressOut,
}) => { }) => {
const scaleValue = useRef(new Animated.Value(1.0)).current; const scaleValue = useRef(new Animated.Value(1.0)).current;
const { colors } = useTheme(); const { colors } = useTheme();
@ -203,7 +207,8 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
tension: 100, tension: 100,
}).start(); }).start();
} }
}, [scaleValue, animationsEnabled]); if (onPressIn) onPressIn();
}, [scaleValue, animationsEnabled, onPressIn]);
const onPressedOut = useCallback(() => { const onPressedOut = useCallback(() => {
if (animationsEnabled) { if (animationsEnabled) {
@ -214,7 +219,8 @@ export const WalletCarouselItem: React.FC<WalletCarouselItemProps> = React.memo(
tension: 100, tension: 100,
}).start(); }).start();
} }
}, [scaleValue, animationsEnabled]); if (onPressOut) onPressOut();
}, [scaleValue, animationsEnabled, onPressOut]);
const handlePress = useCallback(() => { const handlePress = useCallback(() => {
onPressedOut(); onPressedOut();

View file

@ -162,7 +162,7 @@
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; }; B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; }; B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -427,7 +427,7 @@
files = ( files = (
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */, C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */, 17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View file

@ -56,7 +56,5 @@
<key>apiKey</key> <key>apiKey</key>
<string>17ba9059f676f1cc4f45d98182388b01</string> <string>17ba9059f676f1cc4f45d98182388b01</string>
</dict> </dict>
<key>WKCompanionAppBundleIdentifier</key>
<string>io.bluewallet.bluewallet</string>
</dict> </dict>
</plist> </plist>

View file

@ -1588,8 +1588,27 @@ PODS:
- React-logger (= 0.75.4) - React-logger (= 0.75.4)
- React-perflogger (= 0.75.4) - React-perflogger (= 0.75.4)
- React-utils (= 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-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): - RealmJS (20.1.0):
- React - React
- RNCAsyncStorage (2.1.0): - RNCAsyncStorage (2.1.0):
@ -2258,7 +2277,7 @@ SPEC CHECKSUMS:
React-utils: 02526ea15628a768b8db9517b6017a1785c734d2 React-utils: 02526ea15628a768b8db9517b6017a1785c734d2
ReactCodegen: 8b5341ecb61898b8bd40a73ebc443c6bf2d14423 ReactCodegen: 8b5341ecb61898b8bd40a73ebc443c6bf2d14423
ReactCommon: 36d48f542b4010786d6b2bcee615fe5f906b7105 ReactCommon: 36d48f542b4010786d6b2bcee615fe5f906b7105
ReactNativeCameraKit: f058d47e0b1e55fd819bb55ee16505a2e0ca53db ReactNativeCameraKit: e72b838dac4ea2da19b7eb5d00b23125072790fd
RealmJS: 9fd51c849eb552ade9f7b11db42a319b4f6cab4c RealmJS: 9fd51c849eb552ade9f7b11db42a319b4f6cab4c
RNCAsyncStorage: c91d753ede6dc21862c4922cd13f98f7cfde578e RNCAsyncStorage: c91d753ede6dc21862c4922cd13f98f7cfde578e
RNCClipboard: dbcf25b8f666b4685c02eeb65be981d30198e505 RNCClipboard: dbcf25b8f666b4685c02eeb65be981d30198e505

View file

@ -9,7 +9,6 @@ export type ScanQRCodeParamList = {
urTotal?: number; urTotal?: number;
urHave?: number; urHave?: number;
backdoorText?: string; backdoorText?: string;
onDismiss?: () => void;
onBarScanned?: (data: string) => void; onBarScanned?: (data: string) => void;
showFileImportButton?: boolean; showFileImportButton?: boolean;
backdoorVisible?: boolean; backdoorVisible?: boolean;

28
package-lock.json generated
View file

@ -63,12 +63,12 @@
"react-native": "0.75.4", "react-native": "0.75.4",
"react-native-biometrics": "3.0.1", "react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", "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-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", "react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "13.2.0", "react-native-device-info": "13.2.0",
"react-native-document-picker": "9.3.1", "react-native-document-picker": "9.3.1",
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627", "react-native-draglist": "github:BlueWallet/react-native-draglist#a4af02f",
"react-native-fs": "2.20.0", "react-native-fs": "2.20.0",
"react-native-gesture-handler": "2.21.2", "react-native-gesture-handler": "2.21.2",
"react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4",
@ -20530,12 +20530,12 @@
} }
}, },
"node_modules/react-native-camera-kit": { "node_modules/react-native-camera-kit": {
"version": "13.0.0", "version": "14.1.0",
"resolved": "https://registry.npmjs.org/react-native-camera-kit/-/react-native-camera-kit-13.0.0.tgz", "resolved": "https://registry.npmjs.org/react-native-camera-kit/-/react-native-camera-kit-14.1.0.tgz",
"integrity": "sha512-fnkyivCG2xzS+14/doP8pCAYNafYaTyg5J0t+JJltJdgKSHf328OG44Rd+fnbbEOydZxgy/bcuLB24R0kCbynw==", "integrity": "sha512-idkg+Sa2KbGvF6SUqmuAr2U12qBELdiuUJ6fxgB4whUC2AyYHi5jBxiGv6whY/eTB3is7nW1S+TjyM9pEBzNzw==",
"license": "MIT", "license": "MIT",
"dependencies": { "engines": {
"lodash": "^4.14.2" "node": ">=18"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
@ -20615,17 +20615,13 @@
} }
} }
}, },
"node_modules/react-native-draggable-flatlist": { "node_modules/react-native-draglist": {
"version": "4.0.1", "version": "3.8.0",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#3a61627474a4e35198ae961310c77fb305507509", "resolved": "git+ssh://git@github.com/BlueWallet/react-native-draglist.git#a4af02fec803b75508a8136e35eca564bbb1d644",
"license": "MIT", "license": "MIT",
"dependencies": {
"@babel/preset-typescript": "^7.17.12"
},
"peerDependencies": { "peerDependencies": {
"react-native": ">=0.64.0", "react": ">=17.0.1",
"react-native-gesture-handler": ">=2.0.0", "react-native": ">=0.64.0"
"react-native-reanimated": ">=2.8.0"
} }
}, },
"node_modules/react-native-fs": { "node_modules/react-native-fs": {

View file

@ -59,7 +59,7 @@
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android", "android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
"ios": "react-native run-ios", "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", "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", "test": "npm run tslint && npm run lint && npm run unit && npm run jest",
"jest": "jest tests/integration/*", "jest": "jest tests/integration/*",
"e2e:debug-build": "detox build -c android.debug", "e2e:debug-build": "detox build -c android.debug",
@ -127,12 +127,12 @@
"react-native": "0.75.4", "react-native": "0.75.4",
"react-native-biometrics": "3.0.1", "react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442", "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-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c", "react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "13.2.0", "react-native-device-info": "13.2.0",
"react-native-document-picker": "9.3.1", "react-native-document-picker": "9.3.1",
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627", "react-native-draglist": "github:BlueWallet/react-native-draglist#a4af02f",
"react-native-fs": "2.20.0", "react-native-fs": "2.20.0",
"react-native-gesture-handler": "2.21.2", "react-native-gesture-handler": "2.21.2",
"react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4", "react-native-handoff": "github:BlueWallet/react-native-handoff#v0.0.4",

View file

@ -2,9 +2,7 @@ import { useFocusEffect, useIsFocused, useNavigation, useRoute } from '@react-na
import * as bitcoin from 'bitcoinjs-lib'; import * as bitcoin from 'bitcoinjs-lib';
import createHash from 'create-hash'; import createHash from 'create-hash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Alert, Image, Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native'; import { Alert, Platform, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import { CameraScreen } from 'react-native-camera-kit';
import { Icon } from '@rneui/themed';
import Base43 from '../../blue_modules/base43'; import Base43 from '../../blue_modules/base43';
import * as fs from '../../blue_modules/fs'; import * as fs from '../../blue_modules/fs';
import { BlueURDecoder, decodeUR, extractSingleWorkload } from '../../blue_modules/ur'; import { BlueURDecoder, decodeUR, extractSingleWorkload } from '../../blue_modules/ur';
@ -15,6 +13,7 @@ import { useTheme } from '../../components/themes';
import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr'; import { isCameraAuthorizationStatusGranted } from '../../helpers/scan-qr';
import loc from '../../loc'; import loc from '../../loc';
import { useSettings } from '../../hooks/context/useSettings'; import { useSettings } from '../../hooks/context/useSettings';
import CameraScreen from '../../components/CameraScreen';
let decoder = false; let decoder = false;
@ -23,39 +22,6 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
backgroundColor: '#000000', 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: { openSettingsContainer: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: 'center',
@ -67,6 +33,9 @@ const styles = StyleSheet.create({
height: 60, height: 60,
backgroundColor: 'rgba(0,0,0,0.01)', backgroundColor: 'rgba(0,0,0,0.01)',
position: 'absolute', position: 'absolute',
top: 10,
left: '50%',
transform: [{ translateX: -30 }],
}, },
backdoorInputWrapper: { position: 'absolute', left: '5%', top: '0%', width: '90%', height: '70%', backgroundColor: 'white' }, 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 }, progressWrapper: { position: 'absolute', alignSelf: 'center', alignItems: 'center', top: '50%', padding: 8, borderRadius: 8 },
@ -89,7 +58,7 @@ const ScanQRCode = () => {
const previousRoute = navigationState.routes[navigationState.routes.length - 2]; const previousRoute = navigationState.routes[navigationState.routes.length - 2];
const defaultLaunchedBy = previousRoute ? previousRoute.name : undefined; const defaultLaunchedBy = previousRoute ? previousRoute.name : undefined;
const { launchedBy = defaultLaunchedBy, onBarScanned, onDismiss, showFileImportButton } = route.params || {}; const { launchedBy = defaultLaunchedBy, onBarScanned, showFileImportButton } = route.params || {};
const scannedCache = {}; const scannedCache = {};
const { colors } = useTheme(); const { colors } = useTheme();
const isFocused = useIsFocused(); const isFocused = useIsFocused();
@ -288,7 +257,7 @@ const ScanQRCode = () => {
setIsLoading(false); setIsLoading(false);
}; };
const showImagePicker = () => { const onShowImagePickerButtonPress = () => {
if (!isLoading) { if (!isLoading) {
setIsLoading(true); setIsLoading(true);
fs.showImagePickerAndReadImage() fs.showImagePickerAndReadImage()
@ -300,16 +269,7 @@ const ScanQRCode = () => {
}; };
const dismiss = () => { const dismiss = () => {
if (launchedBy) { navigation.goBack();
let merge = true;
if (typeof onBarScanned !== 'function') {
merge = false;
}
navigation.navigate({ name: launchedBy, params: {}, merge });
} else {
navigation.goBack();
}
if (onDismiss) onDismiss();
}; };
const render = isLoading ? ( const render = isLoading ? (
@ -330,29 +290,13 @@ const ScanQRCode = () => {
cameraFlipImage={require('../../img/camera-rotate-solid.png')} cameraFlipImage={require('../../img/camera-rotate-solid.png')}
onReadCode={event => onBarCodeRead({ data: event?.nativeEvent?.codeStringValue })} onReadCode={event => onBarCodeRead({ data: event?.nativeEvent?.codeStringValue })}
showFrame={false} showFrame={false}
showFilePickerButton={showFileImportButton}
showImagePickerButton={true}
onFilePickerButtonPress={showFilePicker}
onImagePickerButtonPress={onShowImagePickerButtonPress}
onCancelButtonPress={dismiss}
/> />
) : null} ) : 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 && ( {urTotal > 0 && (
<View style={[styles.progressWrapper, stylesHook.progressWrapper]} testID="UrProgressBar"> <View style={[styles.progressWrapper, stylesHook.progressWrapper]} testID="UrProgressBar">
<BlueText>{loc.wallets.please_continue_scanning}</BlueText> <BlueText>{loc.wallets.please_continue_scanning}</BlueText>

View file

@ -1,13 +1,5 @@
import React, { useEffect, useLayoutEffect, useReducer, useCallback, useMemo, useRef } from 'react'; import React, { useEffect, useLayoutEffect, useReducer, useCallback, useMemo, useRef, useState } from 'react';
import { StyleSheet, TouchableOpacity, Image, Text, Alert, I18nManager, Animated, LayoutAnimation } from 'react-native'; import { StyleSheet, TouchableOpacity, Image, Text, Alert, I18nManager, Animated, LayoutAnimation, FlatList } from 'react-native';
import {
NestableScrollContainer,
ScaleDecorator,
OpacityDecorator,
NestableDraggableFlatList,
RenderItem,
// @ts-expect-error: react-native-draggable-flatlist is not typed
} from 'react-native-draggable-flatlist';
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useFocusEffect, useNavigation } from '@react-navigation/native'; import { useFocusEffect, useNavigation } from '@react-navigation/native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
@ -25,6 +17,7 @@ import prompt from '../../helpers/prompt';
import HeaderRightButton from '../../components/HeaderRightButton'; import HeaderRightButton from '../../components/HeaderRightButton';
import { ManageWalletsListItem } from '../../components/ManageWalletsListItem'; import { ManageWalletsListItem } from '../../components/ManageWalletsListItem';
import { useSettings } from '../../hooks/context/useSettings'; import { useSettings } from '../../hooks/context/useSettings';
import DragList, { DragListRenderItemInfo } from 'react-native-draglist';
enum ItemType { enum ItemType {
WalletSection = 'wallet', WalletSection = 'wallet',
@ -206,21 +199,24 @@ const ManageWallets: React.FC = () => {
color: colors.foregroundColor, color: colors.foregroundColor,
}, },
}; };
const [data, setData] = useState(state.tempOrder);
const listRef = useRef<FlatList<Item> | null>(null);
useEffect(() => { useEffect(() => {
dispatch({ setData(state.tempOrder);
type: SET_INITIAL_ORDER, }, [state.tempOrder]);
payload: { wallets: walletsRef.current, txMetadata },
}); useEffect(() => {
dispatch({ type: SET_INITIAL_ORDER, payload: { wallets: walletsRef.current, txMetadata } });
}, [txMetadata]); }, [txMetadata]);
useEffect(() => { useEffect(() => {
if (debouncedSearchQuery) { if (debouncedSearchQuery) {
dispatch({ type: SET_FILTERED_ORDER, payload: debouncedSearchQuery }); dispatch({ type: SET_FILTERED_ORDER, payload: debouncedSearchQuery });
} else { } else {
dispatch({ type: SET_INITIAL_ORDER, payload: { wallets: walletsRef.current, txMetadata } }); dispatch({ type: SET_TEMP_ORDER, payload: state.order });
} }
}, [debouncedSearchQuery, txMetadata]); }, [debouncedSearchQuery, state.order]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
if (state.searchQuery.length === 0 && !state.isSearchFocused) { if (state.searchQuery.length === 0 && !state.isSearchFocused) {
@ -244,6 +240,7 @@ const ManageWallets: React.FC = () => {
dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: false }); dispatch({ type: SET_IS_SEARCH_FOCUSED, payload: false });
} }
}, [goBack, setWalletsWithNewOrder, state.searchQuery, state.isSearchFocused, state.tempOrder, navigation]); }, [goBack, setWalletsWithNewOrder, state.searchQuery, state.isSearchFocused, state.tempOrder, navigation]);
const hasUnsavedChanges = useMemo(() => { const hasUnsavedChanges = useMemo(() => {
return JSON.stringify(walletsRef.current) !== JSON.stringify(state.tempOrder.map(item => item.data)); return JSON.stringify(walletsRef.current) !== JSON.stringify(state.tempOrder.map(item => item.data));
}, [state.tempOrder]); }, [state.tempOrder]);
@ -319,6 +316,14 @@ const ManageWallets: React.FC = () => {
}, [hasUnsavedChanges, navigation, setIsDrawerShouldHide]), }, [hasUnsavedChanges, navigation, setIsDrawerShouldHide]),
); );
// Ensure the listener is re-added every time there are unsaved changes
useEffect(() => {
if (beforeRemoveListenerRef.current) {
navigation.removeListener('beforeRemove', beforeRemoveListenerRef.current);
navigation.addListener('beforeRemove', beforeRemoveListenerRef.current);
}
}, [hasUnsavedChanges, navigation]);
const renderHighlightedText = useCallback( const renderHighlightedText = useCallback(
(text: string, query: string) => { (text: string, query: string) => {
const parts = text.split(new RegExp(`(${query})`, 'gi')); const parts = text.split(new RegExp(`(${query})`, 'gi'));
@ -425,60 +430,43 @@ const ManageWallets: React.FC = () => {
}, },
[goBack, navigate], [goBack, navigate],
); );
const renderWalletItem = useCallback( const renderItem = useCallback(
({ item, drag, isActive }: RenderItem<Item>) => ( (info: DragListRenderItemInfo<Item>) => {
<ScaleDecorator drag={drag} activeScale={1.1}> const { item, onDragStart, onDragEnd, isActive } = info;
<OpacityDecorator activeOpacity={0.5}> return (
<ManageWalletsListItem <ManageWalletsListItem
item={item} item={item}
isDraggingDisabled={state.searchQuery.length > 0 || state.isSearchFocused} onPressIn={state.isSearchFocused || state.searchQuery.length > 0 ? undefined : onDragStart}
drag={drag} onPressOut={state.isSearchFocused || state.searchQuery.length > 0 ? undefined : onDragEnd}
state={state} isDraggingDisabled={state.searchQuery.length > 0 || state.isSearchFocused}
navigateToWallet={navigateToWallet} state={state}
renderHighlightedText={renderHighlightedText} navigateToWallet={navigateToWallet}
handleDeleteWallet={handleDeleteWallet} renderHighlightedText={renderHighlightedText}
handleToggleHideBalance={handleToggleHideBalance} handleDeleteWallet={handleDeleteWallet}
/> handleToggleHideBalance={handleToggleHideBalance}
</OpacityDecorator> isActive={isActive}
</ScaleDecorator> drag={state.isSearchFocused || state.searchQuery.length > 0 ? undefined : onDragStart}
), />
);
},
[state, navigateToWallet, renderHighlightedText, handleDeleteWallet, handleToggleHideBalance], [state, navigateToWallet, renderHighlightedText, handleDeleteWallet, handleToggleHideBalance],
); );
const renderPlaceholder = useCallback( const onReordered = useCallback(
({ item, drag, isActive }: RenderItem<Item>) => ( (fromIndex: number, toIndex: number) => {
<ManageWalletsListItem const copy = [...state.order];
item={item} const removed = copy.splice(fromIndex, 1);
isDraggingDisabled={state.searchQuery.length > 0 || state.isSearchFocused} copy.splice(toIndex, 0, removed[0]);
state={state} dispatch({ type: SET_TEMP_ORDER, payload: copy });
navigateToWallet={navigateToWallet} dispatch({
renderHighlightedText={renderHighlightedText} type: SET_INITIAL_ORDER,
isPlaceHolder payload: {
handleDeleteWallet={handleDeleteWallet} wallets: copy.filter(item => item.type === ItemType.WalletSection).map(item => item.data as TWallet),
handleToggleHideBalance={handleToggleHideBalance} txMetadata: state.txMetadata,
/> },
), });
[handleDeleteWallet, handleToggleHideBalance, navigateToWallet, renderHighlightedText, state],
);
const onChangeOrder = useCallback(() => {
triggerHapticFeedback(HapticFeedbackTypes.ImpactMedium);
}, []);
const onDragBegin = useCallback(() => {
triggerHapticFeedback(HapticFeedbackTypes.Selection);
}, []);
const onRelease = useCallback(() => {
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
}, []);
const onDragEnd = useCallback(
({ data }: { data: Item[] }) => {
const updatedWallets = data.filter((item): item is WalletItem => item.type === ItemType.WalletSection).map(item => item.data);
dispatch({ type: SET_INITIAL_ORDER, payload: { wallets: updatedWallets, txMetadata: state.txMetadata } });
}, },
[state.txMetadata], [state.order, state.txMetadata],
); );
const keyExtractor = useCallback((item: Item, index: number) => index.toString(), []); const keyExtractor = useCallback((item: Item, index: number) => index.toString(), []);
@ -499,39 +487,21 @@ const ManageWallets: React.FC = () => {
return ( return (
<GestureHandlerRootView style={[{ backgroundColor: colors.background }, styles.root]}> <GestureHandlerRootView style={[{ backgroundColor: colors.background }, styles.root]}>
<NestableScrollContainer contentInsetAdjustmentBehavior="automatic" automaticallyAdjustContentInsets scrollEnabled> <>
{renderHeader} {renderHeader}
<NestableDraggableFlatList <DragList
data={state.tempOrder.filter((item): item is WalletItem => item.type === ItemType.WalletSection)}
extraData={state.tempOrder}
keyExtractor={keyExtractor}
renderItem={renderWalletItem}
onChangeOrder={onChangeOrder}
onDragBegin={onDragBegin}
onPlaceholderIndexChange={onChangeOrder}
onRelease={onRelease}
delayLongPress={150}
useNativeDriver={true}
dragItemOverflow
autoscrollThreshold={1}
renderPlaceholder={renderPlaceholder}
autoscrollSpeed={0.5}
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustContentInsets automaticallyAdjustContentInsets
onDragEnd={onDragEnd} automaticallyAdjustKeyboardInsets
containerStyle={styles.root} automaticallyAdjustsScrollIndicatorInsets
/>
<NestableDraggableFlatList
data={state.tempOrder.filter((item): item is TransactionItem => item.type === ItemType.TransactionSection)}
keyExtractor={keyExtractor}
renderItem={renderWalletItem}
dragItemOverflow
containerStyle={styles.root}
contentInsetAdjustmentBehavior="automatic" contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustContentInsets data={data}
useNativeDriver={true} containerStyle={[{ backgroundColor: colors.background }, styles.root]}
keyExtractor={keyExtractor}
onReordered={onReordered}
renderItem={renderItem}
ref={listRef}
/> />
</NestableScrollContainer> </>
</GestureHandlerRootView> </GestureHandlerRootView>
); );
}; };

View file

@ -38,6 +38,10 @@ import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { useSettings } from '../../hooks/context/useSettings'; import { useSettings } from '../../hooks/context/useSettings';
import { isDesktop } from '../../blue_modules/environment'; import { isDesktop } from '../../blue_modules/environment';
import { useKeyboard } from '../../hooks/useKeyboard'; import { useKeyboard } from '../../hooks/useKeyboard';
import {
DoneAndDismissKeyboardInputAccessory,
DoneAndDismissKeyboardInputAccessoryViewID,
} from '../../components/DoneAndDismissKeyboardInputAccessory';
const staticCache = {}; const staticCache = {};
@ -684,7 +688,16 @@ const WalletsAddMultisigStep2 = () => {
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered> <BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 /> <BlueSpacing20 />
<View style={styles.multiLineTextInput}> <View style={styles.multiLineTextInput}>
<BlueFormMultiInput value={importText} onChangeText={setImportText} /> <BlueFormMultiInput
value={importText}
onChangeText={setImportText}
inputAccessoryViewID={DoneAndDismissKeyboardInputAccessoryViewID}
/>
{Platform.select({
ios: <DoneAndDismissKeyboardInputAccessory />,
android: isVisible && <DoneAndDismissKeyboardInputAccessory />,
})}
<BlueSpacing20 /> <BlueSpacing20 />
</View> </View>
</BottomModal> </BottomModal>

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