diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e3f87a037..61db94fed 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -32,6 +32,7 @@ If applicable, add screenshots to help explain your problem. - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Zeus Version [e.g. v0.1.0] + - Clearnet or Tor **Additional context** Add any other context about the problem here. diff --git a/Navigation.ts b/Navigation.ts index e79dce479..620a9d4b0 100644 --- a/Navigation.ts +++ b/Navigation.ts @@ -35,7 +35,7 @@ import Language from './views/Settings/Language'; import Currency from './views/Settings/Currency'; import Theme from './views/Settings/Theme'; import CertInstallInstructions from './views/Settings/CertInstallInstructions'; -import SignMessage from './views/Settings/SignMessage'; +import SignVerifyMessage from './views/Settings/SignVerifyMessage'; import Help from './views/Settings/Help'; // Routing @@ -112,8 +112,8 @@ const AppScenes = { CertInstallInstructions: { screen: CertInstallInstructions }, - SignMessage: { - screen: SignMessage + SignVerifyMessage: { + screen: SignVerifyMessage }, Transaction: { screen: Transaction diff --git a/android/app/build.gradle b/android/app/build.gradle index 3b5872aef..42faca709 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,8 +131,8 @@ android { applicationId "app.zeusln.zeus" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 29 - versionName "0.6.0-alpha4" + versionCode 30 + versionName "0.6.0-beta1" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' } @@ -206,6 +206,9 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0-rc01' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02' implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") + // gif + implementation 'com.facebook.fresco:fresco:2.0.0' + implementation 'com.facebook.fresco:animated-gif:2.0.0' if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; diff --git a/backends/CLightningREST.ts b/backends/CLightningREST.ts index 3da64a70e..ec381ebe2 100644 --- a/backends/CLightningREST.ts +++ b/backends/CLightningREST.ts @@ -138,6 +138,10 @@ export default class CLightningREST extends LND { this.postRequest('/v1/utility/signMessage', { message: message }); + verifyMessage = (data: any) => + this.getRequest( + `/v1/utility/checkMessage/${data.msg}/${data.signature}` + ); supportsMessageSigning = () => true; supportsMPP = () => false; @@ -146,4 +150,5 @@ export default class CLightningREST extends LND { supportsHopPicking = () => false; supportsRouting = () => true; supportsNodeInfo = () => true; + singleFeesEarnedTotal = () => true; } diff --git a/backends/Eclair.ts b/backends/Eclair.ts index d3dacdbc1..1c584b2a0 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -462,7 +462,12 @@ export default class Eclair { signMessage = (message: string) => this.api('signmessage', { - msg: message + msg: Base64Utils.btoa(message) + }); + verifyMessage = (data: any) => + this.api('verifymessage', { + msg: Base64Utils.btoa(data.msg), + sig: data.signature }); supportsMessageSigning = () => true; @@ -475,6 +480,7 @@ export default class Eclair { supportsHopPicking = () => false; supportsRouting = () => true; supportsNodeInfo = () => true; + singleFeesEarnedTotal = () => false; } const mapInvoice = diff --git a/backends/LND.ts b/backends/LND.ts index 21b7bac8a..6c87fa556 100644 --- a/backends/LND.ts +++ b/backends/LND.ts @@ -2,6 +2,7 @@ import RNFetchBlob from 'rn-fetch-blob'; import stores from '../stores/Stores'; import { doTorRequest, RequestMethod } from '../utils/TorUtils'; import OpenChannelRequest from './../models/OpenChannelRequest'; +import Base64Utils from './../utils/Base64Utils'; import VersionUtils from './../utils/VersionUtils'; import { localeString } from './../utils/LocaleUtils'; @@ -201,7 +202,13 @@ export default class LND { getPayments = () => this.getRequest('/v1/payments'); getNewAddress = () => this.getRequest('/v1/newaddress'); openChannel = (data: OpenChannelRequest) => - this.postRequest('/v1/channels', data); + this.postRequest('/v1/channels', { + private: data.private, + local_funding_amount: data.local_funding_amount, + min_confs: data.min_confs, + node_pubkey_string: data.node_pubkey_string, + sat_per_byte: data.sat_per_byte + }); openChannelStream = (data: OpenChannelRequest) => this.wsReq('/v1/channels/stream', 'POST', data); connectPeer = (data: any) => this.postRequest('/v1/peers', data); @@ -233,11 +240,16 @@ export default class LND { getNodeInfo = (urlParams?: Array) => this.getRequest(`/v1/graph/node/${urlParams && urlParams[0]}`); getFees = () => this.getRequest('/v1/fees'); - setFees = (data: any) => { - const request = { ...data }; - request.fee_rate = `${Number(data.fee_rate) / 100}`; - return this.postRequest('/v1/chanpolicy', data); - }; + setFees = (data: any) => + this.postRequest('/v1/chanpolicy', { + base_fee_msat: data.base_fee_msat, + fee_rate: `${Number(data.fee_rate) / 100}`, + chan_point: { + funding_txid_str: data.chan_point.funding_txid_str, + output_index: data.chan_point.output_index + }, + time_lock_delta: data.time_lock_delta + }); getRoutes = (urlParams?: Array) => this.getRequest( `/v1/graph/routes/${urlParams && urlParams[0]}/${ @@ -266,8 +278,15 @@ export default class LND { this.postRequest('/v2/wallet/accounts/import', data); signMessage = (message: string) => this.postRequest('/v1/signmessage', { - msg: message + msg: Base64Utils.btoa(message) + }); + verifyMessage = (data: any) => + this.postRequest('/v1/verifymessage', { + msg: Base64Utils.btoa(data.msg), + signature: data.signature }); + subscribeInvoice = (r_hash: string) => + this.getRequest(`/v2/invoices/subscribe/${r_hash}`); // LndHub createAccount = ( @@ -303,4 +322,5 @@ export default class LND { supportsNodeInfo = () => true; supportsCoinControl = () => this.supports('v0.12.0'); supportsAccounts = () => this.supports('v0.13.0'); + singleFeesEarnedTotal = () => false; } diff --git a/backends/LndHub.ts b/backends/LndHub.ts index 7509c2ceb..2b4363d7e 100644 --- a/backends/LndHub.ts +++ b/backends/LndHub.ts @@ -77,4 +77,5 @@ export default class LndHub extends LND { supportsHopPicking = () => false; supportsRouting = () => false; supportsNodeInfo = () => false; + singleFeesEarnedTotal = () => false; } diff --git a/backends/Spark.ts b/backends/Spark.ts index e3b53c197..2afe87f39 100644 --- a/backends/Spark.ts +++ b/backends/Spark.ts @@ -316,4 +316,5 @@ export default class Spark { supportsCoinControl = () => false; supportsHopPicking = () => false; supportsRouting = () => true; + singleFeesEarnedTotal = () => false; } diff --git a/components/Button.tsx b/components/Button.tsx index 51266de34..2ebce9e65 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -30,14 +30,16 @@ function Button(props: ButtonProps) { ...containerStyle, borderWidth: secondary ? 2 : 0, borderColor: themeColor('highlight'), - alignSelf: 'center' + alignSelf: 'center', + borderRadius: 30 } : { ...containerStyle, borderWidth: secondary ? 2 : 0, borderColor: themeColor('highlight'), alignSelf: 'center', - width: '100%' + borderRadius: 30, + width: '90%' }; return ( @@ -53,12 +55,12 @@ function Button(props: ButtonProps) { title={title} buttonStyle={{ backgroundColor: iconOnly - ? themeColor('background') + ? 'transparent' : tertiary - ? themeColor('text') + ? themeColor('highlight') : secondary ? themeColor('secondary') - : themeColor('highlight') + : themeColor('text') }} titleStyle={{ color: iconOnly diff --git a/components/Channels/Tag.tsx b/components/Channels/Tag.tsx index 830821dd5..cbca56bff 100644 --- a/components/Channels/Tag.tsx +++ b/components/Channels/Tag.tsx @@ -52,7 +52,9 @@ export function Tag({ status }: { status: Status }) { /> {/* TODO: localize */} - {status} + + {status} + ); diff --git a/components/DropdownSetting.tsx b/components/DropdownSetting.tsx index 6d2ba4d7c..68c9972cb 100644 --- a/components/DropdownSetting.tsx +++ b/components/DropdownSetting.tsx @@ -42,7 +42,7 @@ export default class DropdownSetting extends React.Component< return ( {Platform.OS !== 'ios' && ( - + - {loading && ( - - )} - {chanInfo && - chanInfo[channelId] && - chanInfo[channelId].node1_policy && ( - - - - {localeString( - 'views.Channel.initiatingParty' - )} - - - {chanInfo[channelId].node1_pub === nodeId - ? localeString('views.Channel.yourNode') - : peerDisplay || - chanInfo[channelId].node1_pub} - - - - } - /> - - - } - /> - - } - /> - - - {chanInfo[channelId].node1_pub === nodeId && ( - } + {!loading && + chanInfo && + chanInfo[channelId] && + chanInfo[channelId].node1_policy ? ( + + + + {localeString('views.Channel.initiatingParty')} + + + {chanInfo[channelId].node1_pub === nodeId + ? localeString('views.Channel.yourNode') + : peerDisplay || + chanInfo[channelId].node1_pub} + + + + } + /> + + + } + /> + + } + /> + - )} - {chanInfo && + value={`${ + chanInfo[channelId].node1_policy.time_lock_delta + } ${localeString('general.blocks')}`} + /> + + {chanInfo[channelId].node1_pub === nodeId && ( + + )} + + ) : ( + + + + {localeString( + 'components.FeeBreakdown.nowClosed' + )} + + + {peerDisplay} + + + + )} + {!loading && + chanInfo && chanInfo[channelId] && chanInfo[channelId].node2_policy && ( diff --git a/components/FeeTable.tsx b/components/FeeTable.tsx index ded0d7c4e..fdf1667f5 100644 --- a/components/FeeTable.tsx +++ b/components/FeeTable.tsx @@ -5,12 +5,15 @@ * Converted to ReactNative + TypeScript by Evan Kaloudis for Zeus */ import * as React from 'react'; -import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native'; +import { Text, TouchableOpacity, View } from 'react-native'; import { DataTable } from 'react-native-paper'; import isEmpty from 'lodash/isEmpty'; import { inject, observer } from 'mobx-react'; + import Button from './../components/Button'; +import LoadingIndicator from './../components/LoadingIndicator'; + import { themeColor } from './../utils/ThemeUtils'; import FeeUtils from './../utils/FeeUtils'; @@ -155,14 +158,7 @@ export default class FeeTable extends React.Component< } tertiary /> - {!collapsed && loading && ( - - - - )} + {!collapsed && loading && } {!collapsed && !loading && headers && ( ( /> ); -const VALUES = [ - { key: 'No selection', value: 'No selection' }, - { key: 'Select Channel to use', value: 'Select Channel to use' } -]; - -const DEFAULT_TITLE = 'Channels to use'; +const DEFAULT_TITLE = localeString('components.HopPicker.defaultTitle'); const Icon = (balanceImage: any) => ; @@ -70,7 +62,6 @@ export default class ChannelPicker extends React.Component< ChannelPickerState > { state = { - status: 'unselected', channelSelected: null, valueSet: '', showChannelModal: false @@ -109,20 +100,7 @@ export default class ChannelPicker extends React.Component< const { getAmount, units } = UnitsStore; const { settings } = SettingsStore; const { theme, privacy } = settings; - const { lurkerMode } = privacy; - - const pickerValuesAndroid: Array = []; - const pickerValuesIOS: Array = ['Cancel']; - VALUES.forEach((value: { key: string; value: string }) => { - pickerValuesAndroid.push( - - ); - pickerValuesIOS.push(value.key); - }); + const lurkerMode = (privacy && privacy.lurkerMode) || false; return ( @@ -204,30 +182,6 @@ export default class ChannelPicker extends React.Component< return ( <> this.toggleItem( item ) } - titleStyle={{ - color: - channelSelected === - item - ? 'orange' - : themeColor( - 'text' - ) - }} - subtitleStyle={{ - color: - channelSelected === - item - ? 'orange' - : themeColor( - 'secondaryText' - ) - }} - /> + > + {channelSelected === + item + ? theme === 'dark' + ? Icon( + SelectedDark + ) + : Icon( + SelectedLight + ) + : ChannelIcon( + `data:image/png;base64,${data}` + )} + + + {channelTitle} + + + {`${ + !item.isActive + ? `${localeString( + 'views.Wallet.Channels.inactive' + )} | ` + : '' + }${ + item.private + ? `${localeString( + 'views.Wallet.Channels.private' + )} | ` + : '' + }${localeString( + 'views.Wallet.Channels.local' + )}: ${ + units && + localBalanceDisplay + } | ${localeString( + 'views.Wallet.Channels.remote' + )}: ${ + units && + remoteBalanceDisplay + }`} + + + @@ -342,6 +334,7 @@ export default class ChannelPicker extends React.Component< showChannelModal: false }) } + secondary /> @@ -350,90 +343,43 @@ export default class ChannelPicker extends React.Component< - {Platform.OS !== 'ios' && ( - - - {title || DEFAULT_TITLE} - - {valueSet ? ( - this.clearSelection()} - > - - {valueSet} - - - ) : ( - { - if (itemValue === 'No selection') { - this.clearSelection(); - } else if ( - itemValue === 'Select Channel to use' - ) { - this.openPicker(); - } - }} + + + {title || DEFAULT_TITLE} + + {valueSet ? ( + this.clearSelection()}> + - {pickerValuesAndroid} - - )} - - )} - - {Platform.OS === 'ios' && ( - - - {title || DEFAULT_TITLE} - - - ActionSheetIOS.showActionSheetWithOptions( - { - options: pickerValuesIOS, - cancelButtonIndex: 0 - }, - (buttonIndex) => { - if (buttonIndex == 1) { - this.clearSelection(); - } else if (buttonIndex == 2) { - this.openPicker(); - } - } - ) - } - > + {valueSet} + + + ) : ( + this.openPicker()}> - {valueSet ? valueSet : 'No selection'} + {localeString( + 'components.HopPicker.selectChannel' + )} - - )} + )} + ); } diff --git a/components/LayerBalances/LightningSwipeableRow.tsx b/components/LayerBalances/LightningSwipeableRow.tsx index 95f000ff6..25b0b2621 100644 --- a/components/LayerBalances/LightningSwipeableRow.tsx +++ b/components/LayerBalances/LightningSwipeableRow.tsx @@ -17,6 +17,7 @@ import { themeColor } from './../../utils/ThemeUtils'; import Receive from './../../images/SVG/Receive.svg'; import Routing from './../../images/SVG/Routing.svg'; +import Send from './../../images/SVG/Send.svg'; interface LightningSwipeableRowProps { navigation: any; @@ -32,7 +33,7 @@ export default class LightningSwipeableRow extends Component< progress: Animated.AnimatedInterpolation ) => { const trans = progress.interpolate({ - inputRange: [0, 1], + inputRange: [0.25, 1], outputRange: [x, 0] }); const pressHandler = () => { @@ -42,6 +43,8 @@ export default class LightningSwipeableRow extends Component< this.props.navigation.navigate('Receive'); } else if (text === localeString('general.routing')) { this.props.navigation.navigate('Routing'); + } else if (text === localeString('general.send')) { + this.props.navigation.navigate('Send'); } }; @@ -51,10 +54,25 @@ export default class LightningSwipeableRow extends Component< > {text === localeString('general.routing') && ( - + )} {text === localeString('general.receive') && ( - + + )} + {text === localeString('general.send') && ( + )} {text} @@ -69,13 +87,13 @@ export default class LightningSwipeableRow extends Component< {this.renderAction( localeString('general.receive'), - RESTUtils.supportsRouting() ? 150 : 75, + RESTUtils.supportsRouting() ? 200 : 125, progress )} {RESTUtils.supportsRouting() && @@ -84,6 +102,11 @@ export default class LightningSwipeableRow extends Component< 100, progress )} + {this.renderAction( + localeString('general.send'), + RESTUtils.supportsRouting() ? 200 : 125, + progress + )} ); diff --git a/components/LayerBalances/OnchainSwipeableRow.tsx b/components/LayerBalances/OnchainSwipeableRow.tsx index 01813c2f2..fb9af673d 100644 --- a/components/LayerBalances/OnchainSwipeableRow.tsx +++ b/components/LayerBalances/OnchainSwipeableRow.tsx @@ -17,6 +17,7 @@ import { themeColor } from './../../utils/ThemeUtils'; import Coins from './../../images/SVG/Coins.svg'; import Receive from './../../images/SVG/Receive.svg'; +import Send from './../../images/SVG/Send.svg'; interface OnchainSwipeableRowProps { navigation: any; @@ -32,7 +33,7 @@ export default class OnchainSwipeableRow extends Component< progress: Animated.AnimatedInterpolation ) => { const trans = progress.interpolate({ - inputRange: [0, 1], + inputRange: [0.25, 1], outputRange: [x, 0] }); const pressHandler = () => { @@ -42,6 +43,8 @@ export default class OnchainSwipeableRow extends Component< this.props.navigation.navigate('Receive', { selectedIndex: 1 }); } else if (text === localeString('general.coins')) { this.props.navigation.navigate('CoinControl'); + } else if (text === localeString('general.send')) { + this.props.navigation.navigate('Send'); } }; @@ -51,10 +54,25 @@ export default class OnchainSwipeableRow extends Component< > {text === localeString('general.coins') && ( - + )} {text === localeString('general.receive') && ( - + + )} + {text === localeString('general.send') && ( + )} {text} @@ -69,17 +87,22 @@ export default class OnchainSwipeableRow extends Component< {this.renderAction( localeString('general.receive'), - RESTUtils.supportsRouting() ? 150 : 75, + RESTUtils.supportsRouting() ? 200 : 125, progress )} {RESTUtils.supportsCoinControl() && this.renderAction(localeString('general.coins'), 100, progress)} + {this.renderAction( + localeString('general.send'), + RESTUtils.supportsRouting() ? 200 : 125, + progress + )} ); diff --git a/components/LayerBalances/index.tsx b/components/LayerBalances/index.tsx index 7fde2b8f7..d09c9a3cc 100644 --- a/components/LayerBalances/index.tsx +++ b/components/LayerBalances/index.tsx @@ -16,7 +16,7 @@ import UnitsStore from './../../stores/UnitsStore'; import { themeColor } from './../../utils/ThemeUtils'; import OnChain from './../../images/SVG/OnChain.svg'; -import Lightning from './../../images/SVG/Lightning Circle.svg'; +import Lightning from './../../images/SVG/Lightning.svg'; interface LayerBalancesProps { BalanceStore: BalanceStore; diff --git a/components/LoadingIndicator.tsx b/components/LoadingIndicator.tsx new file mode 100644 index 000000000..56ec5b9f8 --- /dev/null +++ b/components/LoadingIndicator.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { Image } from 'react-native'; + +import Loading from './../images/GIF/Loading.gif'; + +interface LoadingIndicatorProps { + size?: number; +} + +function LoadingIndicator(props: LoadingIndicatorProps) { + const { size } = props; + + return ( + + ); +} + +export default LoadingIndicator; diff --git a/components/NodeIdenticon.tsx b/components/NodeIdenticon.tsx new file mode 100644 index 000000000..3bbbb35e1 --- /dev/null +++ b/components/NodeIdenticon.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { Avatar } from 'react-native-elements'; +import Identicon from 'identicon.js'; +import PrivacyUtils from './../utils/PrivacyUtils'; + +const hash = require('object-hash'); + +export const NodeTitle = (selectedNode: any) => { + const displayName = + selectedNode && selectedNode.nickname + ? selectedNode.nickname + : selectedNode && selectedNode.implementation === 'lndhub' + ? selectedNode.lndhubUrl + .replace('https://', '') + .replace('http://', '') + : selectedNode && selectedNode.url + ? selectedNode.url.replace('https://', '').replace('http://', '') + : selectedNode && selectedNode.port + ? `${selectedNode.host}:${selectedNode.port}` + : (selectedNode && selectedNode.host) || 'Unknown'; + + const title = PrivacyUtils.sensitiveValue(displayName, 8); + return title; +}; + +export default function NodeIdenticon({ + selectedNode, + width +}: { + selectedNode: any; + width?: number; +}) { + const title = NodeTitle(selectedNode); + const implementation = PrivacyUtils.sensitiveValue( + (selectedNode && selectedNode.implementation) || 'lnd', + 8 + ); + + const data = new Identicon( + hash.sha1( + selectedNode && selectedNode.implementation === 'lndhub' + ? `${title}-${selectedNode.username}` + : title + ), + 255 + ).toString(); + + const identicon = width ? ( + + ) : ( + + ); + + return identicon; +} diff --git a/components/SetFeesForm.tsx b/components/SetFeesForm.tsx index 0edcf772d..38ba5e145 100644 --- a/components/SetFeesForm.tsx +++ b/components/SetFeesForm.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; -import { StyleSheet, Text, TextInput, View } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import { inject, observer } from 'mobx-react'; import Button from './../components/Button'; +import TextInput from './../components/TextInput'; import { localeString } from './../utils/LocaleUtils'; import { themeColor } from './../utils/ThemeUtils'; @@ -38,11 +39,17 @@ export default class SetFeesForm extends React.Component< constructor(props: any) { super(props); + const { SettingsStore } = props; + const { implementation } = SettingsStore; + this.state = { showNewFeesForm: false, feesSubmitted: false, newBaseFee: props.baseFee || '1', - newFeeRate: props.feeRate || '0.001', + newFeeRate: + props.feeRate || implementation === 'c-lightning-REST' + ? '1' + : '0.001', newTimeLockDelta: props.timeLockDelta || '144' }; } @@ -136,38 +143,38 @@ export default class SetFeesForm extends React.Component< this.setState({ newBaseFee: text }) } - numberOfLines={1} autoCapitalize="none" autoCorrect={false} - style={{ fontSize: 20, color: themeColor('text') }} /> {`${localeString( 'components.SetFeesForm.feeRate' - )} (${localeString('general.percentage')})`} + )} (${ + implementation === 'c-lightning-REST' + ? localeString( + 'components.SetFeesForm.ppmMilliMsat' + ) + : localeString('general.percentage') + })`} this.setState({ newFeeRate: text }) } - numberOfLines={1} autoCapitalize="none" autoCorrect={false} - style={{ fontSize: 20, color: themeColor('text') }} /> @@ -178,17 +185,14 @@ export default class SetFeesForm extends React.Component< this.setState({ newTimeLockDelta: text }) } - numberOfLines={1} autoCapitalize="none" autoCorrect={false} - style={{ fontSize: 20, color: themeColor('text') }} /> diff --git a/components/TextInput.tsx b/components/TextInput.tsx new file mode 100644 index 000000000..c9f3f11db --- /dev/null +++ b/components/TextInput.tsx @@ -0,0 +1,79 @@ +import * as React from 'react'; +import { TextInput as TextInputRN } from 'react-native'; +import { themeColor } from './../utils/ThemeUtils'; + +interface TextInputProps { + placeholder?: string; + value?: string; + onChangeText?: any; + numberOfLines?: number; + style?: any; + placeholderTextColor?: string; + editable?: boolean; + keyboardType?: string; + autoCapitalize?: string; + autoCorrect?: boolean; + multiline?: boolean; +} + +function TextInput(props: TextInputProps) { + const { + placeholder, + value, + onChangeText, + numberOfLines, + style, + placeholderTextColor, + editable, + keyboardType, + autoCapitalize, + autoCorrect, + multiline + } = props; + + const defaultStyle = numberOfLines + ? { + color: themeColor('text'), + fontSize: 20, + width: '100%', + top: 10, + backgroundColor: themeColor('secondary'), + borderRadius: 6, + marginBottom: 20, + paddingLeft: 5 + } + : { + color: themeColor('text'), + fontSize: 20, + width: '100%', + height: 60, + top: 10, + backgroundColor: themeColor('secondary'), + borderRadius: 6, + marginBottom: 20, + paddingLeft: 5 + }; + + return ( + + ); +} + +export default TextInput; diff --git a/components/UTXOPicker.tsx b/components/UTXOPicker.tsx index 33ad918a8..a8f51c0ae 100644 --- a/components/UTXOPicker.tsx +++ b/components/UTXOPicker.tsx @@ -1,27 +1,24 @@ import * as React from 'react'; import { - ActionSheetIOS, - Button, FlatList, Modal, - Platform, StyleSheet, View, Text, TouchableOpacity } from 'react-native'; -import { Picker } from '@react-native-picker/picker'; import { ListItem } from 'react-native-elements'; import remove from 'lodash/remove'; import { inject, observer } from 'mobx-react'; + +import Button from './../components/Button'; + import { localeString } from './../utils/LocaleUtils'; import { themeColor } from './../utils/ThemeUtils'; import stores from './../stores/Stores'; import UTXOsStore from './../stores/UTXOsStore'; -import Bitcoin from './../images/SVG/Bitcoin Circle.svg'; - interface UTXOPickerProps { title?: string; selectedValue?: string | boolean; @@ -31,7 +28,6 @@ interface UTXOPickerProps { } interface UTXOPickerState { - status: string; utxosSelected: string[]; utxosSet: string[]; showUtxoModal: boolean; @@ -39,12 +35,7 @@ interface UTXOPickerState { setBalance: number; } -const VALUES = [ - { key: 'No selection', value: 'No selection' }, - { key: 'Select UTXOs to use', value: 'Select UTXOs to use' } -]; - -const DEFAULT_TITLE = 'UTXOs to use'; +const DEFAULT_TITLE = localeString('components.UTXOPicker.defaultTitle'); @inject('UTXOsStore') @observer @@ -53,7 +44,6 @@ export default class UTXOPicker extends React.Component< UTXOPickerState > { state = { - status: 'unselected', utxosSelected: [], utxosSet: [], showUtxoModal: false, @@ -118,19 +108,6 @@ export default class UTXOPicker extends React.Component< const utxosPicked: string[] = []; utxosSelected.forEach((utxo: string) => utxosPicked.push(utxo)); - const pickerValuesAndroid: Array = []; - const pickerValuesIOS: Array = ['Cancel']; - VALUES.forEach((value: { key: string; value: string }) => { - pickerValuesAndroid.push( - - ); - pickerValuesIOS.push(value.key); - }); - return ( this.toggleItem(item) } @@ -264,6 +234,7 @@ export default class UTXOPicker extends React.Component< showUtxoModal: false }) } + secondary /> @@ -272,92 +243,42 @@ export default class UTXOPicker extends React.Component< - {Platform.OS !== 'ios' && ( - - - {title || DEFAULT_TITLE} - - {utxosSet.length > 0 ? ( - this.clearSelection()} - > - - {this.displayValues()} - - - ) : ( - { - if (itemValue === 'No selection') { - this.clearSelection(); - } else if ( - itemValue === 'Select UTXOs to use' - ) { - this.openPicker(); - } - }} + + + {title || DEFAULT_TITLE} + + {utxosSet.length > 0 ? ( + this.clearSelection()}> + - {pickerValuesAndroid} - - )} - - )} - - {Platform.OS === 'ios' && ( - - - {title || DEFAULT_TITLE} - - - ActionSheetIOS.showActionSheetWithOptions( - { - options: pickerValuesIOS, - cancelButtonIndex: 0 - }, - (buttonIndex) => { - if (buttonIndex == 1) { - this.clearSelection(); - } else if (buttonIndex == 2) { - this.openPicker(); - } - } - ) - } - > + {this.displayValues()} + + + ) : ( + this.openPicker()}> - {utxosSet.length > 0 - ? this.displayValues() - : 'No UTXOs selected'} + {localeString( + 'components.UTXOPicker.selectUTXOs' + )} - - )} + )} + ); } diff --git a/components/WalletHeader.tsx b/components/WalletHeader.tsx index a253db8d0..c6ca66e84 100644 --- a/components/WalletHeader.tsx +++ b/components/WalletHeader.tsx @@ -1,27 +1,16 @@ import React from 'react'; import { Button, Header } from 'react-native-elements'; -import { TouchableOpacity } from 'react-native'; +import { Image, TouchableOpacity, View } from 'react-native'; +import Identicon from 'identicon.js'; +import NodeIdenticon from '../components/NodeIdenticon'; import RESTUtils from '../utils/RESTUtils'; +import PrivacyUtils from '../utils/PrivacyUtils'; import { themeColor } from '../utils/ThemeUtils'; -import NodeOn from '../images/SVG/Node On.svg'; +import Contact from '../images/SVG/Contact.svg'; +import Keysign from '../images/SVG/Keysign.svg'; +import Scan from '../images/SVG/Scan.svg'; import { Body } from './text/Body'; -const SettingsButton = ({ navigation }: { navigation: any }) => ( -