diff --git a/Navigation.ts b/Navigation.ts index eff4cf4c5c..7c7789a04e 100644 --- a/Navigation.ts +++ b/Navigation.ts @@ -50,6 +50,7 @@ import PointOfSaleRecon from './views/Settings/PointOfSaleRecon'; import PointOfSaleReconExport from './views/Settings/PointOfSaleReconExport'; import PaymentsSettings from './views/Settings/PaymentsSettings'; import InvoicesSettings from './views/Settings/InvoicesSettings'; +import Satscard from './views/Settings/Satscard'; // Routing import Routing from './views/Routing/Routing'; @@ -246,6 +247,8 @@ const AppScenes = { }, PointOfSaleReconExport: { screen: PointOfSaleReconExport + Satscard: { + screen: Satscard }, PaymentsSettings: { screen: PaymentsSettings diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 776f382283..049fedbcd1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -261,6 +261,8 @@ PODS: - React-Core - react-native-nfc-manager (3.13.0): - React + - react-native-qrcode-local-image (1.0.4): + - React - react-native-randombytes (3.5.3): - React - react-native-restart (0.0.24): @@ -339,8 +341,6 @@ PODS: - React-perflogger (= 0.70.6) - ReactNativeCameraKit (13.0.0): - React-Core - - RemobileReactNativeQrcodeLocalImage (1.0.4): - - React - RNCClipboard (1.9.0): - React-Core - RNCMaskedView (0.1.11): @@ -396,6 +396,7 @@ DEPENDENCIES: - react-native-hce (from `../node_modules/react-native-hce`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`) + - "react-native-qrcode-local-image (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - react-native-randombytes (from `../node_modules/react-native-randombytes`) - react-native-restart (from `../node_modules/react-native-restart`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -414,7 +415,6 @@ DEPENDENCIES: - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - ReactNativeCameraKit (from `../node_modules/react-native-camera-kit`) - - "RemobileReactNativeQrcodeLocalImage (from `../node_modules/@remobile/react-native-qrcode-local-image`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" @@ -495,6 +495,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-picker" react-native-nfc-manager: :path: "../node_modules/react-native-nfc-manager" + react-native-qrcode-local-image: + :path: "../node_modules/@remobile/react-native-qrcode-local-image" react-native-randombytes: :path: "../node_modules/react-native-randombytes" react-native-restart: @@ -531,8 +533,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" ReactNativeCameraKit: :path: "../node_modules/react-native-camera-kit" - RemobileReactNativeQrcodeLocalImage: - :path: "../node_modules/@remobile/react-native-qrcode-local-image" RNCClipboard: :path: "../node_modules/@react-native-clipboard/clipboard" RNCMaskedView: @@ -589,6 +589,7 @@ SPEC CHECKSUMS: react-native-hce: 677aa1c41edf183523bb7b1014d106a08631ff88 react-native-image-picker: 8cb4280e2c1efc3daeb2d9d597f9429a60472e40 react-native-nfc-manager: 001c3a98e68efd57b7fb04b1837ab06ca7243238 + react-native-qrcode-local-image: 35ccb306e4265bc5545f813e54cc830b5d75bcfc react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23 react-native-restart: 45c8dca02491980f2958595333cbccd6877cb57e react-native-safe-area-context: 25260c5d0b9c53fd7aa88e569e2edae72af1f6a3 @@ -607,7 +608,6 @@ SPEC CHECKSUMS: React-runtimeexecutor: 15437b576139df27635400de0599d9844f1ab817 ReactCommon: 349be31adeecffc7986a0de875d7fb0dcf4e251c ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb - RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa RNCClipboard: 99fc8ad669a376b756fbc8098ae2fd05c0ed0668 RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489 RNCPicker: 0bf8ef8f7800524f32d2bb2a8bcadd53eda0ecd1 diff --git a/package.json b/package.json index 97c2d724f3..9639c2791a 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@react-native-picker/picker": "2.4.8", "@react-navigation/bottom-tabs": "5.11.11", "@react-navigation/native": "6.0.16", - "@remobile/react-native-qrcode-local-image": "github:BlueWallet/react-native-qrcode-local-image#b8baa79", + "@remobile/react-native-qrcode-local-image": "github:BlueWallet/react-native-qrcode-local-image#master", "@tradle/react-native-http": "2.0.1", "@types/react-native-snap-carousel": "3.8.5", "assert": "1.5.0", @@ -42,6 +42,7 @@ "browserify-zlib": "0.1.4", "bs58check": "2.1.2", "buffer": "5.6.0", + "cktap-protocol-react-native": "git+https://github.com/bithyve/cktap-protocol-react-native.git#main", "console-browserify": "1.2.0", "constants-browserify": "1.0.0", "create-hash": "1.2.0", diff --git a/views/Settings/Satscard.tsx b/views/Settings/Satscard.tsx new file mode 100644 index 0000000000..979978ece1 --- /dev/null +++ b/views/Settings/Satscard.tsx @@ -0,0 +1,589 @@ +import React, { useRef } from 'react'; +import { + Animated, + Platform, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View +} from 'react-native'; +import { ButtonGroup, Header, Icon } from 'react-native-elements'; +import { inject, observer } from 'mobx-react'; + +import { localeString } from './../../utils/LocaleUtils'; +import { themeColor } from './../../utils/ThemeUtils'; + +import DropdownSetting from './../../components/DropdownSetting'; +import Switch from '../../components/Switch'; +import TextInput from '../../components/TextInput'; + +import Button from '../../components/Button'; +import CollapsedQR from '../../components/CollapsedQR'; +import KeyValue from '../../components/KeyValue'; +import { ErrorMessage } from '../../components/SuccessErrorMessage'; + +import { CKTapCard } from 'cktap-protocol-react-native'; + +import SettingsStore, { DEFAULT_FIAT } from './../../stores/SettingsStore'; + +interface SatscardProps { + navigation: any; + SettingsStore: SettingsStore; +} + +interface SatscardState { + selectedIndex: number; + card: CKTapCard; + nfcStatus: string; + cardStatus: any; + pubkey: string; + privkey: string; + tapsignerError: boolean; + error: string; + fadeAnimation: any; + slotStatus: string; + cvv: string; + selectedSlot: number; + selectedSlotIndex: number; + selectedSlotStatus: string; +} + +interface CardStatus { + card_ident: string; + active_slot: number; + birth_height: number; + is_tapsigner: boolean; + applet_version: string; + num_slots: number; +} + +@inject('SettingsStore') +@observer +export default class Satscard extends React.Component< + SatscardProps, + SatscardState +> { + constructor(props) { + super(props); + const card = new CKTapCard(); + this.state = { + cvv: '744902', + selectedIndex: 0, + selectedSlot: 0, + card, + nfcStatus: 'inactive', + cardStatus: null, + pubkey: '', + privkey: '', + slotStatus: '', + tapsignerError: false, + error: '', + fadeAnimation: new Animated.Value(1) + }; + + Animated.loop( + Animated.sequence([ + Animated.timing(this.state.fadeAnimation, { + toValue: 0, + duration: 500, + delay: 1000, + useNativeDriver: true + }), + Animated.timing(this.state.fadeAnimation, { + toValue: 1, + duration: 500, + useNativeDriver: true + }) + ]) + ).start(); + } + + renderSeparator = () => ( + + ); + + render() { + const { navigation } = this.props; + const { + cvv, + card, + cardStatus, + nfcStatus, + pubkey, + privkey, + slotStatus, + selectedIndex, + tapsignerError, + error, + selectedSlot, + selectedSlotIndex, + selectedSlotStatus + } = this.state; + + const BackButton = () => ( + + navigation.navigate('Settings', { + refresh: true + }) + } + color={themeColor('text')} + underlayColor="transparent" + /> + ); + + const infoButton = () => ( + + Info + + ); + + const addressButton = () => ( + + Address + + ); + + const slotButton = (index: number) => ( + + {index.toString()} + + ); + + const ClearButton = () => ( + + this.setState({ + nfcStatus: 'received', + pubkey: '', + cardStatus: null, + error: '' + }) + } + color={themeColor('text')} + underlayColor="transparent" + /> + ); + + const AwaitMessage = () => ( + + + Awaiting NFC tap + + + ); + + const buttons = [{ element: infoButton }, { element: addressButton }]; + const slotButtons = + cardStatus && cardStatus.num_slots + ? [...Array(cardStatus.num_slots)].map((_, i) => { + return slotButton(i); + }) + : null; + + const awaitNfc = () => { + this.setState({ + nfcStatus: 'awaiting', + error: '' + }); + }; + + return ( + +
} + centerComponent={{ + text: 'Satscard', + style: { + color: themeColor('text'), + fontFamily: 'Lato-Regular' + } + }} + rightComponent={cardStatus ? : null} + backgroundColor={themeColor('background')} + containerStyle={{ + borderBottomWidth: 0 + }} + /> + + {tapsignerError && ( + + )} + {error && ( + this.setState({ error: '' })} + > + + + )} + {cardStatus && ( + { + this.setState({ selectedIndex }); + }} + selectedIndex={selectedIndex} + buttons={buttons} + selectedButtonStyle={{ + backgroundColor: themeColor('highlight'), + borderRadius: 12 + }} + containerStyle={{ + backgroundColor: themeColor('secondary'), + borderRadius: 12, + borderColor: themeColor('secondary') + }} + innerBorderStyle={{ + color: themeColor('secondary') + }} + /> + )} + {selectedIndex === 0 && cardStatus && ( + <> + {nfcStatus === 'awaiting' && } + {false && ( + + )} + + + + + + + { + if (text.length === 7) return; + this.setState({ + cvv: text + }); + }} + /> +