/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ import React, { Component, forwardRef } from 'react'; import PropTypes from 'prop-types'; import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements'; import { ActivityIndicator, Alert, Animated, Dimensions, Image, InputAccessoryView, Keyboard, KeyboardAvoidingView, PixelRatio, Platform, PlatformColor, SafeAreaView, StyleSheet, Switch, TextInput, TouchableOpacity, View, I18nManager, ImageBackground, } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useTheme } from '@react-navigation/native'; import { BlueCurrentTheme } from './components/themes'; import loc, { formatStringAddTwoWhiteSpaces } from './loc'; const { height, width } = Dimensions.get('window'); const aspectRatio = height / width; let isIpad; if (aspectRatio > 1.6) { isIpad = false; } else { isIpad = true; } // eslint-disable-next-line no-unused-expressions Platform.OS === 'android' ? (ActivityIndicator.defaultProps.color = PlatformColor('?attr/colorControlActivated')) : null; export const BlueButton = props => { const { colors } = useTheme(); let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor || BlueCurrentTheme.colors.mainColor; let fontColor = props.buttonTextColor || colors.buttonTextColor; if (props.disabled === true) { backgroundColor = colors.buttonDisabledBackgroundColor; fontColor = colors.buttonDisabledTextColor; } return ( {props.icon && } {props.title && {props.title}} ); }; export const SecondButton = forwardRef((props, ref) => { const { colors } = useTheme(); let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor; let fontColor = colors.buttonTextColor; if (props.disabled === true) { backgroundColor = colors.buttonDisabledBackgroundColor; fontColor = colors.buttonDisabledTextColor; } return ( {props.icon && } {props.title && {props.title}} ); }); export const BitcoinButton = props => { const { colors } = useTheme(); return ( {loc.wallets.add_bitcoin} {loc.wallets.add_bitcoin_explain} ); }; export const VaultButton = props => { const { colors } = useTheme(); return ( {loc.multisig.multisig_vault} {loc.multisig.multisig_vault_explain} ); }; export const LightningButton = props => { const { colors } = useTheme(); return ( {loc.wallets.add_lightning} {loc.wallets.add_lightning_explain} ); }; /** * TODO: remove this comment once this file gets properly converted to typescript. * * @type {React.FC} */ export const BlueButtonLink = forwardRef((props, ref) => { const { colors } = useTheme(); return ( {props.title} ); }); export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => { Alert.alert( loc.wallets.details_title, loc.pleasebackup.ask, [ { text: loc.pleasebackup.ask_yes, onPress: onSuccess, style: 'cancel' }, { text: loc.pleasebackup.ask_no, onPress: onFailure }, ], { cancelable: false }, ); }; export const BluePrivateBalance = () => { return ( ); }; export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => { return ( Clipboard.setString(stringToCopy)}> {displayText || loc.transactions.details_copy} ); }; export class BlueCopyTextToClipboard extends Component { static propTypes = { text: PropTypes.string, truncated: PropTypes.bool, }; static defaultProps = { text: '', truncated: false, }; constructor(props) { super(props); this.state = { hasTappedText: false, address: props.text }; } static getDerivedStateFromProps(props, state) { if (state.hasTappedText) { return { hasTappedText: state.hasTappedText, address: state.address, truncated: props.truncated }; } else { return { hasTappedText: state.hasTappedText, address: props.text, truncated: props.truncated }; } } copyToClipboard = () => { this.setState({ hasTappedText: true }, () => { Clipboard.setString(this.props.text); this.setState({ address: loc.wallets.xpub_copiedToClipboard }, () => { setTimeout(() => { this.setState({ hasTappedText: false, address: this.props.text }); }, 1000); }); }); }; render() { return ( {this.state.address} ); } } const styleCopyTextToClipboard = StyleSheet.create({ address: { marginVertical: 32, fontSize: 15, color: '#9aa0aa', textAlign: 'center', }, }); export const SafeBlueArea = props => { const { style, ...nonStyleProps } = props; const { colors } = useTheme(); const baseStyle = { flex: 1, backgroundColor: colors.background }; return ; }; export const BlueCard = props => { return ; }; export const BlueText = props => { const { colors } = useTheme(); const style = StyleSheet.compose({ color: colors.foregroundColor, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }, props.style); return ; }; export const BlueTextCentered = props => { const { colors } = useTheme(); return ; }; export const BlueListItem = React.memo(props => { const { colors } = useTheme(); return ( {props.leftAvatar && {props.leftAvatar}} {props.leftIcon && } {props.title} {props.subtitle && ( {props.subtitle} )} {props.rightTitle && ( {props.rightTitle} )} {props.isLoading ? ( ) : ( <> {props.chevron && } {props.rightIcon && } {props.switch && } {props.checkmark && } )} ); }); export const BlueFormLabel = props => { const { colors } = useTheme(); return ( ); }; export const BlueFormInput = props => { const { colors } = useTheme(); return ( ); }; export const BlueFormMultiInput = props => { const { colors } = useTheme(); return ( ); }; export const BlueHeader = props => { return (
); }; export const BlueHeaderDefaultSub = props => { const { colors } = useTheme(); return (
{props.leftText} } {...props} /> ); }; export const BlueHeaderDefaultMain = props => { const { colors } = useTheme(); const { isDrawerList } = props; return ( {props.leftText} ); }; export const BlueSpacing = props => { return ; }; export const BlueSpacing40 = props => { return ; }; export const BlueSpacingVariable = props => { if (isIpad) { return ; } else { return ; } }; export class is { static ipad() { return isIpad; } } export const BlueSpacing20 = props => { const { horizontal = false } = props; return ; }; export const BlueSpacing10 = props => { return ; }; export const BlueDismissKeyboardInputAccessory = () => { const { colors } = useTheme(); BlueDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory'; return Platform.OS !== 'ios' ? null : ( ); }; export const BlueDoneAndDismissKeyboardInputAccessory = props => { const { colors } = useTheme(); BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory'; const onPasteTapped = async () => { const clipboard = await Clipboard.getString(); props.onPasteTapped(clipboard); }; const inputView = ( ); if (Platform.OS === 'ios') { return {inputView}; } else { return {inputView}; } }; export const BlueLoading = props => { return ( ); }; const stylesBlueIcon = StyleSheet.create({ container: { flex: 1, }, box1: { position: 'relative', top: 15, }, box: { alignSelf: 'flex-end', paddingHorizontal: 14, paddingTop: 8, }, boxIncoming: { position: 'relative', }, ball: { width: 30, height: 30, borderRadius: 15, }, ballIncoming: { width: 30, height: 30, borderRadius: 15, transform: [{ rotate: '-45deg' }], justifyContent: 'center', }, ballIncomingWithoutRotate: { width: 30, height: 30, borderRadius: 15, }, ballReceive: { width: 30, height: 30, borderBottomLeftRadius: 15, transform: [{ rotate: '-45deg' }], }, ballOutgoing: { width: 30, height: 30, borderRadius: 15, transform: [{ rotate: '225deg' }], justifyContent: 'center', }, ballOutgoingWithoutRotate: { width: 30, height: 30, borderRadius: 15, }, ballOutgoingExpired: { width: 30, height: 30, borderRadius: 15, justifyContent: 'center', }, ballTransparrent: { width: 30, height: 30, borderRadius: 15, backgroundColor: 'transparent', }, ballDimmed: { width: 30, height: 30, borderRadius: 15, backgroundColor: 'gray', }, }); export const BluePlusIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ball: { backgroundColor: colors.buttonBackgroundColor, }, }); return ( ); }; export const BlueTransactionIncomingIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballIncoming: { backgroundColor: colors.ballReceive, }, }); return ( ); }; export const BlueTransactionPendingIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ball: { backgroundColor: colors.buttonBackgroundColor, }, }); return ( ); }; export const BlueTransactionExpiredIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballOutgoingExpired: { backgroundColor: colors.ballOutgoingExpired, }, }); return ( ); }; export const BlueTransactionOnchainIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballIncoming: { backgroundColor: colors.ballReceive, }, }); return ( ); }; export const BlueTransactionOffchainIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballOutgoingWithoutRotate: { backgroundColor: colors.ballOutgoing, }, }); return ( ); }; export const BlueTransactionOffchainIncomingIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballIncomingWithoutRotate: { backgroundColor: colors.ballReceive, }, }); return ( ); }; export const BlueTransactionOutgoingIcon = props => { const { colors } = useTheme(); const stylesBlueIconHooks = StyleSheet.create({ ballOutgoing: { backgroundColor: colors.ballOutgoing, }, }); return ( ); }; const sendReceiveScanButtonFontSize = PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 ? 22 : PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26); export const BlueReceiveButtonIcon = props => { const { colors } = useTheme(); return ( {formatStringAddTwoWhiteSpaces(loc.receive.header)} ); }; export class BlueReplaceFeeSuggestions extends Component { static propTypes = { onFeeSelected: PropTypes.func.isRequired, transactionMinimum: PropTypes.number.isRequired, }; static defaultProps = { transactionMinimum: 1, }; state = { customFeeValue: '1', }; async componentDidMount() { try { const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey)); if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) { this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST)); } } catch (_) {} const networkFees = await NetworkTransactionFees.recommendedFees(); this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST)); } onFeeSelected = selectedFeeType => { if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) { Keyboard.dismiss(); } if (selectedFeeType === NetworkTransactionFeeType.FAST) { this.props.onFeeSelected(this.state.networkFees.fastestFee); this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee)); } else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) { this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee)); } else if (selectedFeeType === NetworkTransactionFeeType.SLOW) { this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee)); } else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) { this.props.onFeeSelected(Number(this.state.customFeeValue)); } }; onCustomFeeTextChange = customFee => { const customFeeValue = customFee.replace(/[^0-9]/g, ''); this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => { this.onFeeSelected(NetworkTransactionFeeType.CUSTOM); }); }; render() { const { networkFees, selectedFeeType } = this.state; return ( {networkFees && [ { label: loc.send.fee_fast, time: loc.send.fee_10m, type: NetworkTransactionFeeType.FAST, rate: networkFees.fastestFee, active: selectedFeeType === NetworkTransactionFeeType.FAST, }, { label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium), time: loc.send.fee_3h, type: NetworkTransactionFeeType.MEDIUM, rate: networkFees.mediumFee, active: selectedFeeType === NetworkTransactionFeeType.MEDIUM, }, { label: loc.send.fee_slow, time: loc.send.fee_1d, type: NetworkTransactionFeeType.SLOW, rate: networkFees.slowFee, active: selectedFeeType === NetworkTransactionFeeType.SLOW, }, ].map(({ label, type, time, rate, active }, index) => ( this.onFeeSelected(type)} style={[ { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 }, active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor }, ]} > {label} ~{time} {rate} sat/byte ))} this.customTextInput.focus()} style={[ { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 }, selectedFeeType === NetworkTransactionFeeType.CUSTOM && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor, }, ]} > {formatStringAddTwoWhiteSpaces(loc.send.fee_custom)} (this.customTextInput = ref)} maxLength={9} style={{ backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor, borderBottomColor: BlueCurrentTheme.colors.formBorder, borderBottomWidth: 0.5, borderColor: BlueCurrentTheme.colors.formBorder, borderRadius: 4, borderWidth: 1.0, color: '#81868e', flex: 1, marginRight: 10, minHeight: 33, paddingRight: 5, paddingLeft: 5, }} onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)} defaultValue={`${this.props.transactionMinimum}`} placeholder={loc.send.fee_satvbyte} placeholderTextColor="#81868e" inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID} /> sat/byte {loc.formatString(loc.send.fee_replace_minvb, { min: this.props.transactionMinimum })} ); } } export function BlueBigCheckmark({ style }) { const defaultStyles = { backgroundColor: '#ccddf9', width: 120, height: 120, borderRadius: 60, alignSelf: 'center', justifyContent: 'center', marginTop: 0, marginBottom: 0, }; const mergedStyles = { ...defaultStyles, ...style }; return ( ); } const tabsStyles = StyleSheet.create({ root: { flexDirection: 'row', height: 50, borderColor: '#e3e3e3', borderBottomWidth: 1, }, tabRoot: { flex: 1, justifyContent: 'center', alignItems: 'center', borderColor: 'white', borderBottomWidth: 2, }, }); export const BlueTabs = ({ active, onSwitch, tabs }) => ( {tabs.map((Tab, i) => ( onSwitch(i)} style={[ tabsStyles.tabRoot, active === i && { borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor, borderBottomWidth: 2, }, ]} > ))} );