Merge branch 'master' into opsrn

This commit is contained in:
marcosrdz 2020-12-04 08:29:45 -05:00
commit 024e744f35
82 changed files with 2305 additions and 2220 deletions

View File

@ -8,7 +8,6 @@ import {
Alert,
Animated,
Dimensions,
FlatList,
Image,
InputAccessoryView,
Keyboard,
@ -458,17 +457,15 @@ export class BlueWalletNavigationHeader extends Component {
}
}
export const BlueButtonLinkHook = props => {
export const BlueButtonLink = props => {
const { colors } = useTheme();
return (
<TouchableOpacity
style={{
minHeight: 60,
minWidth: 100,
height: 60,
justifyContent: 'center',
}}
onPress={props.onPress}
{...props}
>
<Text style={{ color: colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{props.title}</Text>
@ -476,23 +473,6 @@ export const BlueButtonLinkHook = props => {
);
};
export class BlueButtonLink extends Component {
render() {
return (
<TouchableOpacity
style={{
minHeight: 60,
minWidth: 100,
justifyContent: 'center',
}}
{...this.props}
>
<Text style={{ color: BlueCurrentTheme.colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{this.props.title}</Text>
</TouchableOpacity>
);
}
}
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
Alert.alert(
loc.wallets.details_title,
@ -505,88 +485,6 @@ export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure
);
};
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => {
let headerRight;
const { colors, closeImage } = useTheme();
if (withNavigationCloseButton) {
headerRight = () => (
<TouchableOpacity
style={{ width: 40, height: 40, padding: 14 }}
onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
>
<Image style={{ alignSelf: 'center' }} source={closeImage} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: colors.foregroundColor,
},
headerRight,
headerBackTitleVisible: false,
headerTintColor: colors.foregroundColor,
};
};
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => {
let headerRight;
if (withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center' }}
onPress={advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueCurrentTheme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: BlueCurrentTheme.colors.foregroundColor,
},
headerTintColor: BlueCurrentTheme.colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center', paddingHorizontal: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image style={{}} source={BlueCurrentTheme.closeImage} />
</TouchableOpacity>
),
headerRight,
headerBackTitle: null,
};
};
export const BluePrivateBalance = () => {
return Platform.select({
ios: (
@ -675,11 +573,9 @@ export const SafeBlueArea = props => {
return <SafeAreaView forceInset={{ horizontal: 'always' }} style={{ flex: 1, backgroundColor: colors.background }} {...props} />;
};
export class BlueCard extends Component {
render() {
return <View {...this.props} style={{ padding: 20 }} />;
}
}
export const BlueCard = props => {
return <View {...props} style={{ padding: 20 }} />;
};
export const BlueText = props => {
const { colors } = useTheme();
@ -760,122 +656,111 @@ export const BlueFormLabel = props => {
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20 }} />;
};
export class BlueFormInput extends Component {
render() {
return (
<Input
{...this.props}
inputStyle={{ color: BlueCurrentTheme.colors.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
borderColor: BlueCurrentTheme.colors.inputBorderColor,
borderBottomColor: BlueCurrentTheme.colors.inputBorderColor,
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
}}
/>
);
}
}
export const BlueFormInput = props => {
const { colors } = useTheme();
return (
<Input
{...props}
inputStyle={{ color: colors.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
borderColor: colors.inputBorderColor,
borderBottomColor: colors.inputBorderColor,
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: colors.inputBackgroundColor,
}}
/>
);
};
export class BlueFormMultiInput extends Component {
constructor(props) {
super(props);
this.state = {
selection: { start: 0, end: 0 },
};
}
export const BlueFormMultiInput = props => {
const { colors } = useTheme();
render() {
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
style={{
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
color: BlueCurrentTheme.colors.foregroundColor,
textAlignVertical: 'top',
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...this.props}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
}
}
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
style={{
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
backgroundColor: colors.inputBackgroundColor,
color: colors.foregroundColor,
textAlignVertical: 'top',
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...props}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
};
export class BlueHeader extends Component {
render() {
return (
export const BlueHeader = props => {
return (
<Header
{...props}
backgroundColor="transparent"
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
/>
);
};
export const BlueHeaderDefaultSub = props => {
const { colors } = useTheme();
return (
<SafeAreaView style={{ backgroundColor: colors.brandingColor }}>
<Header
{...this.props}
backgroundColor="transparent"
backgroundColor={colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
/>
);
}
}
export class BlueHeaderDefaultSub extends Component {
render() {
return (
<SafeAreaView style={{ backgroundColor: BlueCurrentTheme.colors.brandingColor }}>
<Header
backgroundColor={BlueCurrentTheme.colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: BlueCurrentTheme.colors.foregroundColor,
}}
>
{this.props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (this.props.onClose) this.props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: colors.foregroundColor,
}}
>
{props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (props.onClose) props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
</TouchableOpacity>
}
{...this.props}
/>
</SafeAreaView>
);
}
}
</View>
</TouchableOpacity>
}
{...props}
/>
</SafeAreaView>
);
};
export const BlueHeaderDefaultSubHooks = props => {
const { colors } = useTheme();
@ -938,11 +823,9 @@ export const BlueHeaderDefaultMain = props => {
);
};
export class BlueSpacing extends Component {
render() {
return <View {...this.props} style={{ height: 60 }} />;
}
}
export const BlueSpacing = props => {
return <View {...props} style={{ height: 60 }} />;
};
export const BlueSpacing40 = props => {
return <View {...props} style={{ height: 50 }} />;
@ -972,12 +855,6 @@ export const BlueSpacing10 = props => {
return <View {...props} style={{ height: 10, opacity: 0 }} />;
};
export class BlueList extends Component {
render() {
return <FlatList {...this.props} />;
}
}
export class BlueUseAllFundsButton extends Component {
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
static propTypes = {
@ -1110,19 +987,9 @@ export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
}
}
export class BlueLoading extends Component {
render() {
return (
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
<ActivityIndicator />
</View>
);
}
}
export const BlueLoadingHook = () => {
export const BlueLoading = props => {
return (
<View style={{ flex: 1, paddingTop: 200 }}>
<View style={{ flex: 1, paddingTop: 200 }} {...props}>
<ActivityIndicator />
</View>
);
@ -1421,93 +1288,6 @@ export const BlueReceiveButtonIcon = props => {
);
};
export class BlueScanButton extends Component {
render() {
return (
<TouchableOpacity {...this.props} style={{ flex: 1 }}>
<View
style={{
flex: 1,
minWidth: 130,
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
minWidth: 24,
minHeight: 30,
backgroundColor: 'transparent',
alignItems: 'center',
marginBottom: -15,
marginLeft: -8,
}}
>
<Image resizeMode="stretch" source={BlueCurrentTheme.scanImage} />
</View>
<Text
style={{
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: sendReceiveScanButtonFontSize,
fontWeight: '600',
left: 5,
backgroundColor: 'transparent',
}}
>
{loc.send.details_scan}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export class BlueSendButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props} testID="SendButton" style={{ flex: 1 }}>
<View
style={{
flex: 1,
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
alignItems: 'center',
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
<View
style={{
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '225deg' }],
marginRight: 8,
}}
>
<Icon
{...this.props}
name="arrow-down"
size={sendReceiveScanButtonFontSize}
type="font-awesome"
color={BlueCurrentTheme.colors.buttonAlternativeTextColor}
/>
</View>
<Text
style={{
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: sendReceiveScanButtonFontSize,
fontWeight: '500',
backgroundColor: 'transparent',
}}
>
{loc.send.header}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();

View File

@ -2,6 +2,7 @@ import React from 'react';
import { createStackNavigator, TransitionPresets } from '@react-navigation/stack';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Platform, useWindowDimensions, Dimensions } from 'react-native';
import { useTheme } from '@react-navigation/native';
import Settings from './screen/settings/settings';
import About from './screen/settings/about';
@ -75,6 +76,7 @@ import DrawerList from './screen/wallets/drawerList';
import { isTablet } from 'react-native-device-info';
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
import PsbtMultisigQRCode from './screen/send/psbtMultisigQRCode';
const defaultScreenOptions =
Platform.OS === 'ios'
@ -106,133 +108,191 @@ const defaultStackScreenOptions =
};
const WalletsStack = createStackNavigator();
const WalletsRoot = () => (
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions} />
<WalletsStack.Screen name="TransactionDetails" component={TransactionDetails} options={TransactionDetails.navigationOptions} />
<WalletsStack.Screen name="TransactionStatus" component={TransactionStatus} options={TransactionStatus.navigationOptions} />
<WalletsStack.Screen name="HodlHodl" component={HodlHodl} options={HodlHodl.navigationOptions} />
<WalletsStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions} />
<WalletsStack.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions} />
<WalletsStack.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions} />
<WalletsStack.Screen name="Settings" component={Settings} options={Settings.navigationOptions} />
<WalletsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<WalletsStack.Screen name="Currency" component={Currency} options={Currency.navigationOptions} />
<WalletsStack.Screen name="About" component={About} options={About.navigationOptions} />
<WalletsStack.Screen name="ReleaseNotes" component={ReleaseNotes} options={ReleaseNotes.navigationOptions} />
<WalletsStack.Screen name="Selftest" component={Selftest} options={Selftest.navigationOptions} />
<WalletsStack.Screen name="Licensing" component={Licensing} options={Licensing.navigationOptions} />
<WalletsStack.Screen name="DefaultView" component={DefaultView} options={DefaultView.navigationOptions} />
<WalletsStack.Screen name="Language" component={Language} options={Language.navigationOptions} />
<WalletsStack.Screen name="EncryptStorage" component={EncryptStorage} options={EncryptStorage.navigationOptions} />
<WalletsStack.Screen name="GeneralSettings" component={GeneralSettings} options={GeneralSettings.navigationOptions} />
<WalletsStack.Screen name="NetworkSettings" component={NetworkSettings} options={NetworkSettings.navigationOptions} />
<WalletsStack.Screen name="NotificationSettings" component={NotificationSettings} options={NotificationSettings.navigationOptions} />
<WalletsStack.Screen name="PlausibleDeniability" component={PlausibleDeniability} options={PlausibleDeniability.navigationOptions} />
<WalletsStack.Screen name="LightningSettings" component={LightningSettings} options={LightningSettings.navigationOptions} />
<WalletsStack.Screen name="ElectrumSettings" component={ElectrumSettings} options={ElectrumSettings.navigationOptions} />
<WalletsStack.Screen name="SettingsPrivacy" component={SettingsPrivacy} options={SettingsPrivacy.navigationOptions} />
<WalletsStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} />
<WalletsStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions}
/>
<WalletsStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions}
/>
<WalletsStack.Screen name="HodlHodlViewOffer" component={HodlHodlViewOffer} options={HodlHodlViewOffer.navigationOptions} />
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={Broadcast.navigationOptions} />
<WalletsStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions} />
<WalletsStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions} />
</WalletsStack.Navigator>
);
const WalletsRoot = () => {
const theme = useTheme();
return (
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions(theme)} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions(theme)} />
<WalletsStack.Screen name="TransactionDetails" component={TransactionDetails} options={TransactionDetails.navigationOptions(theme)} />
<WalletsStack.Screen name="TransactionStatus" component={TransactionStatus} options={TransactionStatus.navigationOptions(theme)} />
<WalletsStack.Screen name="HodlHodl" component={HodlHodl} options={HodlHodl.navigationOptions(theme)} />
<WalletsStack.Screen name="HodlHodlViewOffer" component={HodlHodlViewOffer} options={HodlHodlViewOffer.navigationOptions(theme)} />
<WalletsStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions(theme)} />
<WalletsStack.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions(theme)} />
<WalletsStack.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions(theme)} />
<WalletsStack.Screen name="Settings" component={Settings} options={Settings.navigationOptions(theme)} />
<WalletsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<WalletsStack.Screen name="Currency" component={Currency} options={Currency.navigationOptions(theme)} />
<WalletsStack.Screen name="About" component={About} options={About.navigationOptions(theme)} />
<WalletsStack.Screen name="ReleaseNotes" component={ReleaseNotes} options={ReleaseNotes.navigationOptions(theme)} />
<WalletsStack.Screen name="Selftest" component={Selftest} options={Selftest.navigationOptions(theme)} />
<WalletsStack.Screen name="Licensing" component={Licensing} options={Licensing.navigationOptions(theme)} />
<WalletsStack.Screen name="DefaultView" component={DefaultView} options={DefaultView.navigationOptions(theme)} />
<WalletsStack.Screen name="Language" component={Language} options={Language.navigationOptions(theme)} />
<WalletsStack.Screen name="EncryptStorage" component={EncryptStorage} options={EncryptStorage.navigationOptions(theme)} />
<WalletsStack.Screen name="GeneralSettings" component={GeneralSettings} options={GeneralSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="NetworkSettings" component={NetworkSettings} options={NetworkSettings.navigationOptions(theme)} />
<WalletsStack.Screen
name="NotificationSettings"
component={NotificationSettings}
options={NotificationSettings.navigationOptions(theme)}
/>
<WalletsStack.Screen
name="PlausibleDeniability"
component={PlausibleDeniability}
options={PlausibleDeniability.navigationOptions(theme)}
/>
<WalletsStack.Screen name="LightningSettings" component={LightningSettings} options={LightningSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="ElectrumSettings" component={ElectrumSettings} options={ElectrumSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="SettingsPrivacy" component={SettingsPrivacy} options={SettingsPrivacy.navigationOptions(theme)} />
<WalletsStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions(theme)} />
<WalletsStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions(theme)}
/>
<WalletsStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions(theme)}
/>
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={Broadcast.navigationOptions(theme)} />
<WalletsStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions(theme)} />
<WalletsStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions(theme)} />
<WalletsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
</WalletsStack.Navigator>
);
};
const AddWalletStack = createStackNavigator();
const AddWalletRoot = () => (
<AddWalletStack.Navigator screenOptions={defaultStackScreenOptions}>
<AddWalletStack.Screen name="AddWallet" component={AddWallet} options={AddWallet.navigationOptions} />
<AddWalletStack.Screen name="ImportWallet" component={ImportWallet} options={ImportWallet.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackup" component={PleaseBackup} options={PleaseBackup.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackupLNDHub" component={PleaseBackupLNDHub} options={PleaseBackupLNDHub.navigationOptions} />
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions} />
<AddWalletStack.Screen name="WalletsAddMultisig" component={WalletsAddMultisig} options={WalletsAddMultisig.navigationOptions} />
<AddWalletStack.Screen
name="WalletsAddMultisigStep2"
component={WalletsAddMultisigStep2}
options={WalletsAddMultisigStep2.navigationOptions}
/>
</AddWalletStack.Navigator>
);
const AddWalletRoot = () => {
const theme = useTheme();
return (
<AddWalletStack.Navigator screenOptions={defaultStackScreenOptions}>
<AddWalletStack.Screen name="AddWallet" component={AddWallet} options={AddWallet.navigationOptions(theme)} />
<AddWalletStack.Screen name="ImportWallet" component={ImportWallet} options={ImportWallet.navigationOptions(theme)} />
<AddWalletStack.Screen name="PleaseBackup" component={PleaseBackup} options={PleaseBackup.navigationOptions(theme)} />
<AddWalletStack.Screen
name="PleaseBackupLNDHub"
component={PleaseBackupLNDHub}
options={PleaseBackupLNDHub.navigationOptions(theme)}
/>
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions(theme)} />
<AddWalletStack.Screen
name="WalletsAddMultisig"
component={WalletsAddMultisig}
options={WalletsAddMultisig.navigationOptions(theme)}
/>
<AddWalletStack.Screen
name="WalletsAddMultisigStep2"
component={WalletsAddMultisigStep2}
options={WalletsAddMultisigStep2.navigationOptions(theme)}
/>
</AddWalletStack.Navigator>
);
};
// CreateTransactionStackNavigator === SendDetailsStack
const SendDetailsStack = createStackNavigator();
const SendDetailsRoot = () => (
<SendDetailsStack.Navigator screenOptions={defaultStackScreenOptions}>
<SendDetailsStack.Screen name="SendDetails" component={SendDetails} options={SendDetails.navigationOptions} />
<SendDetailsStack.Screen name="Confirm" component={Confirm} options={Confirm.navigationOptions} />
<SendDetailsStack.Screen
name="PsbtWithHardwareWallet"
component={PsbtWithHardwareWallet}
options={PsbtWithHardwareWallet.navigationOptions}
/>
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
<SendDetailsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<SendDetailsStack.Screen name="CoinControl" component={CoinControl} options={CoinControl.navigationOptions} />
</SendDetailsStack.Navigator>
);
const SendDetailsRoot = () => {
const theme = useTheme();
return (
<SendDetailsStack.Navigator screenOptions={defaultStackScreenOptions}>
<SendDetailsStack.Screen name="SendDetails" component={SendDetails} options={SendDetails.navigationOptions(theme)} />
<SendDetailsStack.Screen name="Confirm" component={Confirm} options={Confirm.navigationOptions(theme)} />
<SendDetailsStack.Screen
name="PsbtWithHardwareWallet"
component={PsbtWithHardwareWallet}
options={PsbtWithHardwareWallet.navigationOptions(theme)}
/>
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions(theme)} />
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions(theme)} />
<SendDetailsStack.Screen
name="PsbtMultisigQRCode"
component={PsbtMultisigQRCode}
options={PsbtMultisigQRCode.navigationOptions(theme)}
/>
<SendDetailsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<SendDetailsStack.Screen name="CoinControl" component={CoinControl} options={CoinControl.navigationOptions(theme)} />
</SendDetailsStack.Navigator>
);
};
const LNDCreateInvoiceStack = createStackNavigator();
const LNDCreateInvoiceRoot = () => (
<LNDCreateInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<LNDCreateInvoiceStack.Screen name="LNDCreateInvoice" component={LNDCreateInvoice} options={LNDCreateInvoice.navigationOptions} />
<LNDCreateInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<LNDCreateInvoiceStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} />
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions}
/>
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions}
/>
</LNDCreateInvoiceStack.Navigator>
);
const LNDCreateInvoiceRoot = () => {
const theme = useTheme();
return (
<LNDCreateInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<LNDCreateInvoiceStack.Screen
name="LNDCreateInvoice"
component={LNDCreateInvoice}
options={LNDCreateInvoice.navigationOptions(theme)}
/>
<LNDCreateInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<LNDCreateInvoiceStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions(theme)} />
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions(theme)}
/>
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions(theme)}
/>
</LNDCreateInvoiceStack.Navigator>
);
};
// LightningScanInvoiceStackNavigator === ScanLndInvoiceStack
const ScanLndInvoiceStack = createStackNavigator();
const ScanLndInvoiceRoot = () => (
<ScanLndInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<ScanLndInvoiceStack.Screen name="ScanLndInvoice" component={ScanLndInvoice} options={ScanLndInvoice.navigationOptions} />
<ScanLndInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<ScanLndInvoiceStack.Screen name="Success" component={Success} options={{ headerShown: false, gestureEnabled: false }} />
<ScanLndInvoiceStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions} />
<ScanLndInvoiceStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions} />
</ScanLndInvoiceStack.Navigator>
);
const ScanLndInvoiceRoot = () => {
const theme = useTheme();
return (
<ScanLndInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<ScanLndInvoiceStack.Screen name="ScanLndInvoice" component={ScanLndInvoice} options={ScanLndInvoice.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="Success" component={Success} options={{ headerShown: false, gestureEnabled: false }} />
<ScanLndInvoiceStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions(theme)} />
</ScanLndInvoiceStack.Navigator>
);
};
const AztecoRedeemStack = createStackNavigator();
const AztecoRedeemRoot = () => (
<AztecoRedeemStack.Navigator screenOptions={defaultStackScreenOptions}>
<AztecoRedeemStack.Screen name="AztecoRedeem" component={AztecoRedeem} options={AztecoRedeem.navigationOptions} />
<AztecoRedeemStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
</AztecoRedeemStack.Navigator>
);
const AztecoRedeemRoot = () => {
const theme = useTheme();
return (
<AztecoRedeemStack.Navigator screenOptions={defaultStackScreenOptions}>
<AztecoRedeemStack.Screen name="AztecoRedeem" component={AztecoRedeem} options={AztecoRedeem.navigationOptions(theme)} />
<AztecoRedeemStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
</AztecoRedeemStack.Navigator>
);
};
const ScanQRCodeStack = createStackNavigator();
const ScanQRCodeRoot = () => (
@ -256,18 +316,26 @@ const UnlockWithScreenRoot = () => (
);
const HodlHodlLoginStack = createStackNavigator();
const HodlHodlLoginRoot = () => (
<HodlHodlLoginStack.Navigator name="HodlHodlLoginRoot" screenOptions={defaultStackScreenOptions}>
<HodlHodlLoginStack.Screen name="HodlHodlLogin" component={HodlHodlLogin} options={HodlHodlLogin.navigationOptions} />
</HodlHodlLoginStack.Navigator>
);
const HodlHodlLoginRoot = () => {
const theme = useTheme();
return (
<HodlHodlLoginStack.Navigator name="HodlHodlLoginRoot" screenOptions={defaultStackScreenOptions}>
<HodlHodlLoginStack.Screen name="HodlHodlLogin" component={HodlHodlLogin} options={HodlHodlLogin.navigationOptions(theme)} />
</HodlHodlLoginStack.Navigator>
);
};
const ReorderWalletsStack = createStackNavigator();
const ReorderWalletsStackRoot = () => (
<ReorderWalletsStack.Navigator name="ReorderWalletsRoot" screenOptions={defaultStackScreenOptions}>
<ReorderWalletsStack.Screen name="ReorderWallets" component={ReorderWallets} options={ReorderWallets.navigationOptions} />
</ReorderWalletsStack.Navigator>
);
const ReorderWalletsStackRoot = () => {
const theme = useTheme();
return (
<ReorderWalletsStack.Navigator name="ReorderWalletsRoot" screenOptions={defaultStackScreenOptions}>
<ReorderWalletsStack.Screen name="ReorderWallets" component={ReorderWallets} options={ReorderWallets.navigationOptions(theme)} />
</ReorderWalletsStack.Navigator>
);
};
const Drawer = createDrawerNavigator();
function DrawerRoot() {
@ -287,32 +355,48 @@ function DrawerRoot() {
}
const ReceiveDetailsStack = createStackNavigator();
const ReceiveDetailsStackRoot = () => (
<ReceiveDetailsStack.Navigator name="ReceiveDetailsRoot" screenOptions={defaultStackScreenOptions} initialRouteName="ReceiveDetails">
<RootStack.Screen name="ReceiveDetails" component={ReceiveDetails} options={ReceiveDetails.navigationOptions} />
</ReceiveDetailsStack.Navigator>
);
const ReceiveDetailsStackRoot = () => {
const theme = useTheme();
return (
<ReceiveDetailsStack.Navigator name="ReceiveDetailsRoot" screenOptions={defaultStackScreenOptions} initialRouteName="ReceiveDetails">
<RootStack.Screen name="ReceiveDetails" component={ReceiveDetails} options={ReceiveDetails.navigationOptions(theme)} />
</ReceiveDetailsStack.Navigator>
);
};
const WalletXpubStack = createStackNavigator();
const WalletXpubStackRoot = () => (
<WalletXpubStack.Navigator name="WalletXpubRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletXpub">
<WalletXpubStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions} />
</WalletXpubStack.Navigator>
);
const WalletXpubStackRoot = () => {
const theme = useTheme();
return (
<WalletXpubStack.Navigator name="WalletXpubRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletXpub">
<WalletXpubStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions(theme)} />
</WalletXpubStack.Navigator>
);
};
const WalletExportStack = createStackNavigator();
const WalletExportStackRoot = () => (
<WalletExportStack.Navigator name="WalletExportRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletExport">
<WalletExportStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions} />
</WalletExportStack.Navigator>
);
const WalletExportStackRoot = () => {
const theme = useTheme();
return (
<WalletExportStack.Navigator name="WalletExportRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletExport">
<WalletExportStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions(theme)} />
</WalletExportStack.Navigator>
);
};
const LappBrowserStack = createStackNavigator();
const LappBrowserStackRoot = () => (
<LappBrowserStack.Navigator name="LappBrowserRoot" screenOptions={defaultStackScreenOptions} initialRouteName="LappBrowser">
<LappBrowserStack.Screen name="LappBrowser" component={LappBrowser} options={LappBrowser.navigationOptions} />
</LappBrowserStack.Navigator>
);
const LappBrowserStackRoot = () => {
const theme = useTheme();
return (
<LappBrowserStack.Navigator name="LappBrowserRoot" screenOptions={defaultStackScreenOptions} initialRouteName="LappBrowser">
<LappBrowserStack.Screen name="LappBrowser" component={LappBrowser} options={LappBrowser.navigationOptions(theme)} />
</LappBrowserStack.Navigator>
);
};
const InitStack = createStackNavigator();
const InitRoot = () => (
@ -329,47 +413,51 @@ const InitRoot = () => (
);
const RootStack = createStackNavigator();
const Navigation = () => (
<RootStack.Navigator mode="modal" screenOptions={defaultScreenOptions} initialRouteName="LoadingScreenRoot">
{/* stacks */}
<RootStack.Screen name="WalletsRoot" component={WalletsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false }} />
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlLoginRoot" component={HodlHodlLoginRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlMyContracts" component={HodlHodlMyContracts} options={HodlHodlMyContracts.navigationOptions} />
<RootStack.Screen name="HodlHodlWebview" component={HodlHodlWebview} options={HodlHodlWebview.navigationOptions} />
const Navigation = () => {
const theme = useTheme();
{/* screens */}
<RootStack.Screen name="WalletExportRoot" component={WalletExportStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ExportMultisigCoordinationSetup"
component={ExportMultisigCoordinationSetup}
options={ExportMultisigCoordinationSetup.navigationOptions}
/>
<RootStack.Screen
name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosigners}
options={ViewEditMultisigCosigners.navigationOptions}
/>
<RootStack.Screen name="WalletXpubRoot" component={WalletXpubStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions} />
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions} />
<RootStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
<RootStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LappBrowserRoot" component={LappBrowserStackRoot} options={{ headerShown: false }} />
return (
<RootStack.Navigator mode="modal" screenOptions={defaultScreenOptions} initialRouteName="LoadingScreenRoot">
{/* stacks */}
<RootStack.Screen name="WalletsRoot" component={WalletsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false }} />
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlLoginRoot" component={HodlHodlLoginRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlMyContracts" component={HodlHodlMyContracts} options={HodlHodlMyContracts.navigationOptions(theme)} />
<RootStack.Screen name="HodlHodlWebview" component={HodlHodlWebview} options={HodlHodlWebview.navigationOptions(theme)} />
<RootStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeRoot}
options={{
...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
headerShown: false,
}}
/>
</RootStack.Navigator>
);
{/* screens */}
<RootStack.Screen name="WalletExportRoot" component={WalletExportStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ExportMultisigCoordinationSetup"
component={ExportMultisigCoordinationSetup}
options={ExportMultisigCoordinationSetup.navigationOptions(theme)}
/>
<RootStack.Screen
name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosigners}
options={ViewEditMultisigCosigners.navigationOptions(theme)}
/>
<RootStack.Screen name="WalletXpubRoot" component={WalletXpubStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions(theme)} />
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions(theme)} />
<RootStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
<RootStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LappBrowserRoot" component={LappBrowserStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeRoot}
options={{
...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
headerShown: false,
}}
/>
</RootStack.Navigator>
);
};
export default InitRoot;

View File

@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "5.6.7"
versionName "5.6.8"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type

View File

@ -82,7 +82,7 @@ class DeeplinkSchemaMatch {
},
]);
} else if (action === 'openReceive') {
completionHandler(['LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { fromWallet: wallet } }]);
completionHandler(['LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID: wallet.getID() } }]);
}
}
}

View File

@ -1,7 +1,7 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React, { Component } from 'react';
import { Text } from 'react-native-elements';
import { Dimensions, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Dimensions, LayoutAnimation, StyleSheet, TouchableOpacity, View } from 'react-native';
import { encodeUR } from 'bc-ur/dist';
import QRCode from 'react-native-qrcode-svg';
import { BlueCurrentTheme } from '../components/themes';
@ -107,6 +107,7 @@ export class DynamicQRCode extends Component {
<TouchableOpacity
style={animatedQRCodeStyle.qrcodeContainer}
onPress={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState(prevState => ({ hideControls: !prevState.hideControls }));
}}
>

View File

@ -0,0 +1,92 @@
import React from 'react';
import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
button: {
minWidth: 40,
height: 40,
justifyContent: 'center',
paddingHorizontal: 14,
},
});
const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, formatter) => {
return theme => ({ navigation, route }) => {
let headerRight = null;
if (closeButton) {
const handleClose = closeButtonFunc
? () => closeButtonFunc({ navigation, route })
: () => {
Keyboard.dismiss();
navigation.goBack(null);
};
headerRight = () => (
<TouchableOpacity style={styles.button} onPress={handleClose}>
<Image source={theme.closeImage} />
</TouchableOpacity>
);
}
let options = {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: theme.colors.foregroundColor,
},
headerRight,
headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor,
...opts,
};
if (formatter) {
options = formatter(options, { theme, navigation, route });
}
return options;
};
};
export default navigationStyle;
export const navigationStyleTx = (opts, formatter) => {
return theme => ({ navigation, route }) => {
let options = {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: theme.colors.foregroundColor,
},
// headerBackTitle: null,
headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={styles.button}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image source={theme.closeImage} />
</TouchableOpacity>
),
...opts,
};
if (formatter) {
options = formatter(options, { theme, navigation, route });
}
return options;
};
};

File diff suppressed because one or more lines are too long

View File

@ -1513,7 +1513,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1556,7 +1556,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1597,7 +1597,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
@ -1636,7 +1636,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
@ -1675,7 +1675,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
@ -1717,7 +1717,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1757,7 +1757,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1800,7 +1800,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1841,7 +1841,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
@ -1885,7 +1885,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1926,7 +1926,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
@ -1969,7 +1969,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2111,7 +2111,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -2151,7 +2151,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}";
@ -2185,7 +2185,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
@ -2221,7 +2221,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@ -157,6 +157,14 @@
<string>₩</string>
<key>locale</key>
<string>ko-KR</string>
</dict>
<dict>
<key>endPointKey</key>
<string>LBP</string>
<key>symbol</key>
<string>ل.ل.</string>
<key>locale</key>
<string>ar-LB</string>
</dict>
<dict>
<key>endPointKey</key>

View File

@ -1,3 +1,26 @@
v5.6.7
======
* ADD: coincontrol
* ADD: Handle fiat rate from alternate sources
* ADD: new languages: Bulgarian, Polish, Welsh
* ADD: UYU currency
* FIX: PayJoin is now BIP compliant
* FIX: better support for BRD (aka bread) wallet with segwit
* FIX: Disregarding curent denomination on send screen, scanning address always resets it to BTC
* FIX: import *.txn file with txhex - extra newline characted prevented it from being recognized (closes #2161)
* FIX: locale pt_BR, cs_CZ, sl_SI, es_ES, nl_NL, fi_FI, ru
* FIX: translate message if Bitcoin address or LN invoice is in clipboard
* FIX: Styling for large screens
* FIX: exclude change address from recipients for Confirm screen
* FIX: Dont show loading indicator on launch and onsnapitem
* FIX: Show alert if storage access is denied
* FIX: When wallet card has balance but no txs it displays 'pull to refresh'
* FIX: broken wallet->send->longtap send btn->choose photo
* FIX: Use system color on widgets
* FIX: hide provide entropy button when creating Lightning or MS wallet
* FIX: Can't paste in address block while building tx
v5.6.6
======
@ -64,12 +87,3 @@ v5.6.1
* FIX: locales pt_BR, pt_PT, ru, sl_SI, ja_JP
* FIX: add margin for RTL languages
* FIX: Missing (NT) before $ sign
v.5.6.0
=======
* FIX: some transactions displayed with 0 value
* FIX: PSBT with HW wallets flow
* FIX: rare crash on watch-only receive button
* FIX: RBF cancel style
* REF: updated languages sl_SI, de_DE, fi_FI, es_ES

View File

@ -75,6 +75,7 @@
"item_rating": "{rating} trades",
"item_rating_no": "No rating",
"login": "Login",
"logout": "logout",
"mycont": "My contracts",
"offer_accept": "Accept offer",
"offer_account_finish": "Looks like you didn't finish setting up account on HodlHodl, would you like to finish setup now?",
@ -145,6 +146,8 @@
"header": "Receive"
},
"send": {
"broadcast_success_screen_msg": "Success! You transaction has been broadcasted!",
"broadcast_success_screen_open": "Open link in explorer",
"broadcastButton": "BROADCAST",
"broadcastError": "error",
"broadcastNone": "Input transaction hash",
@ -314,6 +317,7 @@
"details_inputs": "Inputs",
"details_outputs": "Outputs",
"details_received": "Received",
"transaction_note_saved":"Transaction note has been successfully saved.",
"details_show_in_block_explorer": "View in block explorer",
"details_title": "Transaction",
"details_to": "Output",
@ -403,6 +407,7 @@
"take_photo": "Take Photo",
"xpub_copiedToClipboard": "Copied to clipboard.",
"pull_to_refresh": "pull to refresh",
"warning_do_not_disclose": "Warning! Do not disclose",
"xpub_title": "wallet XPUB"
},
"multisig": {

View File

@ -107,12 +107,13 @@
"lndViewInvoice": {
"additional_info": "Lisäinformaatio",
"for": "Kenelle:",
"lightning_invoice": "Lightning-lasku",
"has_been_paid": "Tämä lasku on maksettu",
"open_direct_channel": "Avaa suora kanava tällä solmulla:",
"please_pay": "Ole hyvä ja maksa",
"preimage": "Alkukuva",
"sats": "sats",
"wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut"
"wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut."
},
"plausibledeniability": {
"create_fake_storage": "Luo Salattu tallennustila",
@ -313,6 +314,7 @@
"details_inputs": "Syötteet",
"details_outputs": "Ulostulot",
"details_received": "Vastaanotettu",
"transaction_note_saved":"Siirtotapahtumailmoitus on tallennettu.",
"details_show_in_block_explorer": "Näytä lohkoketjuselaimessa",
"details_title": "Siirtotapahtuma",
"details_to": "Ulostulo",
@ -455,6 +457,10 @@
"view_edit_cosigners": "Tarkastele/muokkaa allekirjoittajia",
"this_cosigner_is_already_imported": "Tämä allekirjoittaja on jo tuotu.",
"export_signed_psbt": "Vie Allekirjoitettu PSBT",
"input_fp": "Syötä sormenjälki",
"input_fp_explain": "ohita käyttääksesi oletusarvoa (00000000)",
"input_path": "Syöte johtamisen polku",
"input_path_explain": "ohita käyttääksesi oletusarvoa ({default})",
"view_edit_cosigners_title": "Muokkaa Allekirjoittajia"
},
"cc": {

View File

@ -107,12 +107,13 @@
"lndViewInvoice": {
"additional_info": "Dodatne Informacije",
"for": "Za:",
"lightning_invoice": "Lightning Račun",
"has_been_paid": "Ta račun je bil plačan",
"open_direct_channel": "Odpri neposreden kanal s tem vozliščem:",
"please_pay": "Prosim plačajte",
"preimage": "Preimage",
"sats": "sats",
"wasnt_paid_and_expired": "Ta račun ni bil plačan in je potekel"
"wasnt_paid_and_expired": "Ta račun ni bil plačan in je potekel."
},
"plausibledeniability": {
"create_fake_storage": "Ustvari Šifrirano shrambo",
@ -146,7 +147,7 @@
"send": {
"broadcastButton": "Objavi v omrežju",
"broadcastError": "napaka",
"broadcastNone": "Vnesite zgoščeno vrednost transakcije",
"broadcastNone": "Vnesite zgoščeno vrednost transakcije (hash)",
"broadcastPending": "v teku",
"broadcastSuccess": "uspešno",
"confirm_header": "Potrditev",
@ -313,6 +314,7 @@
"details_inputs": "Vhodi",
"details_outputs": "Izhodi",
"details_received": "Prejeto",
"transaction_note_saved":"Opomba transakcije je bila uspešno shranjena.",
"details_show_in_block_explorer": "Prikaži v raziskovalcu blokov",
"details_title": "Transakcija",
"details_to": "Izhod",
@ -358,7 +360,7 @@
"details_display": "prikaži na seznamu denarnic",
"details_export_backup": "Izvozi / varnostna kopija",
"details_marketplace": "Tržnica",
"details_master_fingerprint": "Master fingerprint",
"details_master_fingerprint": "Glavni prstni odtis (fingerprint)",
"details_no_cancel": "Ne, prekliči",
"details_save": "Shrani",
"details_show_xpub": "Prikaži XPUB denarnice",
@ -455,6 +457,10 @@
"view_edit_cosigners": "Prikaži/uredi sopodpisnike",
"this_cosigner_is_already_imported": "Ta sopodpisnik je že uvožen",
"export_signed_psbt": "Izvozi podpisano PSBT",
"input_fp": "Vnesite xfp (master key fingerprint)",
"input_fp_explain": "preskoči in uporabi privzetega (00000000)",
"input_path": "Vnesite pot izpeljave (derivation path)",
"input_path_explain": "preskoči in uporabi privzeto ({default})",
"view_edit_cosigners_title": "Urejanje sopodpisnikov"
},
"cc": {

View File

@ -27,6 +27,7 @@ export const FiatUnit = Object.freeze({
JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP', source: FiatUnitSource.CoinDesk },
KES: { endPointKey: 'KES', symbol: 'Ksh', locale: 'en-KE', source: FiatUnitSource.CoinDesk },
KRW: { endPointKey: 'KRW', symbol: '₩', locale: 'ko-KR', source: FiatUnitSource.CoinDesk },
LBP: { endPointKey: 'LBP', symbol: 'ل.ل.', locale: 'ar-LB', source: FiatUnitSource.CoinDesk },
MXN: { endPointKey: 'MXN', symbol: '$', locale: 'es-MX', source: FiatUnitSource.CoinDesk },
MYR: { endPointKey: 'MYR', symbol: 'RM', locale: 'ms-MY', source: FiatUnitSource.CoinDesk },
NGN: { endPointKey: 'NGN', symbol: '₦', locale: 'en-NG', source: FiatUnitSource.CoinDesk },

51
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.6.7",
"version": "5.6.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -6475,9 +6475,9 @@
}
},
"@react-navigation/drawer": {
"version": "5.10.2",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.10.2.tgz",
"integrity": "sha512-P9Sf9csztbdJAZpz3FhYg5TFZXxRwSyv5xIgFyT55F1KqB4ocRjrtj+AsIoOzdfQ7wC0nHJacOjp9noDk6QHqw==",
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.11.2.tgz",
"integrity": "sha512-xCD/Q9Yne5CYsvKr+eMMNx4riQJMy5ghi5dEnvntsdJFx2Lq+UETyvFeTH7iXVXo2JOOxNddMvZJrds0B7+nCg==",
"requires": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
@ -6529,9 +6529,9 @@
}
},
"@react-navigation/stack": {
"version": "5.11.1",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-5.11.1.tgz",
"integrity": "sha512-wxGxnQnktf0ByicDAVAQnf6bazC7FynvPYY3o5Zf31i1Ucb+xJcSesDbl5wyeaW1YGiCfFs/K8fUVko3K7fxQA==",
"version": "5.12.6",
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-5.12.6.tgz",
"integrity": "sha512-pf9AigAIVtCQuCpZAZqBux4kNqQwj98ngvd6JEryFrqTQ1CYsUH6jfpQE7SKyHggVRFSQVMf24aCgwtRixBvjw==",
"requires": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
@ -8380,12 +8380,19 @@
}
},
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz",
"integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
},
"dependencies": {
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
}
}
},
"buffer-alloc": {
@ -18382,6 +18389,15 @@
"yargs": "^13.2.4"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -19261,6 +19277,17 @@
"integrity": "sha512-ToyFSYuJp0/DQ7bXd/vTwF5m3yhNSRngIpVlBFBDccDZQmp2qIo0exrObCNlJLOOHb38dil726hM+GKjR1/60w==",
"requires": {
"buffer": "^5.4.3"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"react-native-tooltip": {

View File

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.6.7",
"version": "5.6.8",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.10.4",
@ -73,9 +73,9 @@
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/push-notification-ios": "1.7.1",
"@react-native-community/slider": "3.0.3",
"@react-navigation/drawer": "5.10.2",
"@react-navigation/drawer": "5.11.2",
"@react-navigation/native": "5.8.2",
"@react-navigation/stack": "5.11.1",
"@react-navigation/stack": "5.12.6",
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
"@sentry/react-native": "1.9.0",
"amplitude-js": "7.3.0",
@ -90,7 +90,7 @@
"bip39": "2.6.0",
"bitcoinjs-lib": "5.2.0",
"bolt11": "1.2.7",
"buffer": "5.6.0",
"buffer": "6.0.1",
"buffer-reverse": "1.0.1",
"coinselect": "3.1.12",
"crypto-js": "3.1.9-1",

View File

@ -1,4 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Ionicons from 'react-native-vector-icons/Ionicons';
import {
TouchableOpacity,
ActivityIndicator,
@ -12,9 +14,9 @@ import {
StyleSheet,
} from 'react-native';
import { WebView } from 'react-native-webview';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import Ionicons from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types';
import { SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Notifications from '../../blue_modules/notifications';
let processedInvoices = {};
@ -517,8 +519,8 @@ Browser.propTypes = {
}),
};
Browser.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
Browser.navigationOptions = navigationStyle({
closeButton: true,
title: 'Lapp Browser',
headerLeft: null,
});

View File

@ -1,38 +1,402 @@
/* global alert */
import React, { Component } from 'react';
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
View,
TextInput,
KeyboardAvoidingView,
Keyboard,
StatusBar,
TouchableWithoutFeedback,
TouchableOpacity,
Text,
StyleSheet,
Image,
Keyboard,
KeyboardAvoidingView,
StatusBar,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native';
import {
BlueNavigationStyle,
BlueButton,
BlueBitcoinAmount,
BlueDismissKeyboardInputAccessory,
BlueAlertWalletExportReminder,
} from '../../BlueComponents';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import PropTypes from 'prop-types';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import * as NavigationService from '../../NavigationService';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueAlertWalletExportReminder, BlueBitcoinAmount, BlueButton, BlueDismissKeyboardInputAccessory } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import * as NavigationService from '../../NavigationService';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import loc, { formatBalanceWithoutSuffix, formatBalancePlain } from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import Lnurl from '../../class/lnurl';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Notifications from '../../blue_modules/notifications';
const currency = require('../../blue_modules/currency');
const LNDCreateInvoice = () => {
const { wallets, saveToDisk, setSelectedWallet } = useContext(BlueStorageContext);
const { walletID, uri } = useRoute().params;
const wallet = useRef(
wallets.find(item => item.getID() === walletID) || wallets.find(item => item.type === LightningCustodianWallet.type),
);
const { name } = useRoute();
const { colors } = useTheme();
const { navigate, dangerouslyGetParent, goBack, pop, setParams } = useNavigation();
const [unit, setUnit] = useState(wallet.current.getPreferredBalanceUnit());
const [amount, setAmount] = useState();
const [renderWalletSelectionButtonHidden, setRenderWalletSelectionButtonHidden] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [description, setDescription] = useState('');
const [lnurlParams, setLNURLParams] = useState();
const styleHooks = StyleSheet.create({
scanRoot: {
backgroundColor: colors.scanLabel,
},
scanClick: {
color: colors.inverseForegroundColor,
},
walletNameText: {
color: colors.buttonAlternativeTextColor,
},
walletNameBalance: {
color: colors.buttonAlternativeTextColor,
},
walletNameSats: {
color: colors.buttonAlternativeTextColor,
},
root: {
backgroundColor: colors.elevated,
},
amount: {
backgroundColor: colors.elevated,
},
fiat: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
});
useEffect(() => {
// console.log(params)
Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
return () => {
Keyboard.removeListener('keyboardDidShow', _keyboardDidShow);
Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
};
}, []);
const renderReceiveDetails = async () => {
try {
wallet.current.setUserHasSavedExport(true);
await saveToDisk();
if (uri) {
processLnurl(uri);
}
} catch (e) {
console.log(e);
}
setIsLoading(false);
};
useEffect(() => {
if (wallet.current && wallet.current.getID() !== walletID) {
const newWallet = wallets.find(w => w.getID() === walletID);
if (newWallet) {
wallet.current = newWallet;
setSelectedWallet(newWallet.getID());
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
useFocusEffect(
useCallback(() => {
if (wallet.current) {
setSelectedWallet(walletID);
if (wallet.current.getUserHasSavedExport()) {
renderReceiveDetails();
} else {
BlueAlertWalletExportReminder({
onSuccess: () => renderReceiveDetails(),
onFailure: () => {
dangerouslyGetParent().pop();
NavigationService.navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID,
},
});
},
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet]),
[],
);
const _keyboardDidShow = () => {
setRenderWalletSelectionButtonHidden(true);
};
const _keyboardDidHide = () => {
setRenderWalletSelectionButtonHidden(false);
};
const createInvoice = async () => {
setIsLoading(true);
try {
let invoiceAmount = amount;
switch (unit) {
case BitcoinUnit.SATS:
invoiceAmount = parseInt(invoiceAmount); // basically nop
break;
case BitcoinUnit.BTC:
invoiceAmount = currency.btcToSatoshi(invoiceAmount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
// trying to fetch cached sat equivalent for this fiat amount
invoiceAmount = BlueBitcoinAmount.getCachedSatoshis(invoiceAmount) || currency.btcToSatoshi(currency.fiatToBTC(invoiceAmount));
break;
}
const invoiceRequest = await wallet.current.addInvoice(invoiceAmount, description);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
// lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid
/** @type LightningCustodianWallet */
const decoded = await wallet.current.decodeInvoice(invoiceRequest);
await Notifications.tryToObtainPermissions();
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
// send to lnurl-withdraw callback url if that exists
if (lnurlParams) {
const { callback, k1 } = lnurlParams;
const callbackUrl = callback + (callback.indexOf('?') !== -1 ? '&' : '?') + 'k1=' + k1 + '&pr=' + invoiceRequest;
const resp = await fetch(callbackUrl, { method: 'GET' });
if (resp.status >= 300) {
const text = await resp.text();
throw new Error(text);
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
}
setTimeout(async () => {
// wallet object doesnt have this fresh invoice in its internals, so we refetch it and only then save
await wallet.current.fetchUserInvoices(1);
await saveToDisk();
}, 1000);
navigate('LNDViewInvoice', {
invoice: invoiceRequest,
walletID,
isModal: true,
});
} catch (Err) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
setIsLoading(false);
alert(Err.message);
}
};
const processLnurl = async data => {
setIsLoading(true);
if (!wallet) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
return goBack();
}
// decoding the lnurl
const url = Lnurl.getUrlFromLnurl(data);
// calling the url
try {
const resp = await fetch(url, { method: 'GET' });
if (resp.status >= 300) {
throw new Error('Bad response from server');
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
if (reply.tag === Lnurl.TAG_PAY_REQUEST) {
// we are here by mistake. user wants to SEND to lnurl-pay, but he is on a screen that creates
// invoices (including through lnurl-withdraw)
navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: walletID,
},
});
return;
}
if (reply.tag !== Lnurl.TAG_WITHDRAW_REQUEST) {
throw new Error('Unsupported lnurl');
}
// amount that comes from lnurl is always in sats
let amount = (reply.maxWithdrawable / 1000).toString();
const sats = amount;
switch (unit) {
case BitcoinUnit.SATS:
// nop
break;
case BitcoinUnit.BTC:
amount = currency.satoshiToBTC(amount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
amount = formatBalancePlain(amount, BitcoinUnit.LOCAL_CURRENCY);
BlueBitcoinAmount.setCachedSatoshis(amount, sats);
break;
}
// setting the invoice creating screen with the parameters
setLNURLParams({
k1: reply.k1,
callback: reply.callback,
fixed: reply.minWithdrawable === reply.maxWithdrawable,
min: (reply.minWithdrawable || 0) / 1000,
max: reply.maxWithdrawable / 1000,
});
setAmount(amount);
setDescription(reply.defaultDescription);
setIsLoading(false);
} catch (Err) {
Keyboard.dismiss();
setIsLoading(false);
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert(Err.message);
}
};
const renderCreateButton = () => {
return (
<View style={styles.createButton}>
{isLoading ? (
<ActivityIndicator />
) : (
<BlueButton disabled={!(amount > 0)} onPress={createInvoice} title={loc.send.details_create} />
)}
</View>
);
};
const navigateToScanQRCode = () => {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: processLnurl,
launchedBy: name,
},
});
Keyboard.dismiss();
};
const renderScanClickable = () => {
return (
<TouchableOpacity disabled={isLoading} onPress={navigateToScanQRCode} style={[styles.scanRoot, styleHooks.scanRoot]}>
<Image style={{}} source={require('../../img/scan-white.png')} />
<Text style={[styles.scanClick, styleHooks.scanClick]}>{loc.send.details_scan}</Text>
</TouchableOpacity>
);
};
const navigateToSelectWallet = () => {
navigate('SelectWallet', { onWalletSelect: onWalletSelect, chainType: Chain.OFFCHAIN });
};
const renderWalletSelectionButton = () => {
if (renderWalletSelectionButtonHidden) return;
return (
<View style={styles.walletRoot}>
{!isLoading && (
<TouchableOpacity style={styles.walletChooseWrap} onPress={navigateToSelectWallet}>
<Text style={styles.walletChooseText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={styles.walletNameWrap}>
<TouchableOpacity style={styles.walletNameTouch} onPress={navigateToSelectWallet}>
<Text style={[styles.walletNameText, styleHooks.walletNameText]}>{wallet.current.getLabel()}</Text>
<Text style={[styles.walletNameBalance, styleHooks.walletNameBalance]}>
{formatBalanceWithoutSuffix(wallet.current.getBalance(), BitcoinUnit.SATS, false)}
</Text>
<Text style={[styles.walletNameSats, styleHooks.walletNameSats]}>{BitcoinUnit.SATS}</Text>
</TouchableOpacity>
</View>
</View>
);
};
const onWalletSelect = selectedWallet => {
setParams({ walletID: selectedWallet.getID() });
pop();
};
if (wallet.current === undefined || !walletID) {
return (
<View style={styles.error}>
<Text>System error: Source wallet not found (this should never happen)</Text>
</View>
);
}
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={[styles.root, styleHooks.root]}>
<StatusBar barStyle="light-content" />
<View style={[styles.amount, styleHooks.amount]}>
<KeyboardAvoidingView behavior="position">
<BlueBitcoinAmount
isLoading={isLoading}
amount={amount}
onAmountUnitChange={setUnit}
onChangeText={text => {
if (lnurlParams) {
// in this case we prevent the user from changing the amount to < min or > max
const { min, max } = lnurlParams;
const nextAmount = parseInt(text);
if (nextAmount < min) {
text = min.toString();
} else if (nextAmount > max) {
text = max.toString();
}
}
setAmount(text);
}}
disabled={isLoading || (lnurlParams && lnurlParams.fixed)}
unit={unit}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
<View style={[styles.fiat, styleHooks.fiat]}>
<TextInput
onChangeText={setDescription}
placeholder={loc.receive.details_label}
value={description}
numberOfLines={1}
placeholderTextColor="#81868e"
style={styles.fiat2}
editable={!isLoading}
onSubmitEditing={Keyboard.dismiss}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
{lnurlParams ? null : renderScanClickable()}
</View>
<BlueDismissKeyboardInputAccessory />
{renderCreateButton()}
</KeyboardAvoidingView>
</View>
{renderWalletSelectionButton()}
</View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
createButton: {
marginHorizontal: 16,
@ -44,7 +408,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: BlueCurrentTheme.colors.scanLabel,
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
@ -52,7 +415,6 @@ const styles = StyleSheet.create({
},
scanClick: {
marginLeft: 4,
color: BlueCurrentTheme.colors.inverseForegroundColor,
},
walletRoot: {
marginBottom: 16,
@ -78,18 +440,15 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
walletNameText: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 14,
},
walletNameBalance: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 14,
fontWeight: '600',
marginLeft: 8,
marginRight: 4,
},
walletNameSats: {
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: 11,
fontWeight: '600',
textAlignVertical: 'bottom',
@ -102,19 +461,14 @@ const styles = StyleSheet.create({
root: {
flex: 1,
justifyContent: 'space-between',
backgroundColor: BlueCurrentTheme.colors.elevated,
},
amount: {
flex: 1,
backgroundColor: BlueCurrentTheme.colors.elevated,
},
fiat: {
flexDirection: 'row',
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
minHeight: 44,
height: 44,
marginHorizontal: 20,
@ -130,377 +484,10 @@ const styles = StyleSheet.create({
},
});
export default class LNDCreateInvoice extends Component {
static contextType = BlueStorageContext;
constructor(props, context) {
super(props);
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
/** @type LightningCustodianWallet */
let fromWallet;
if (props.route.params.fromWallet) fromWallet = props.route.params.fromWallet;
export default LNDCreateInvoice;
// fallback to first wallet if it exists
if (!fromWallet) {
const lightningWallets = context.wallets.filter(item => item.type === LightningCustodianWallet.type);
if (lightningWallets.length > 0) {
fromWallet = lightningWallets[0];
console.warn('warning: using ln wallet index 0');
}
}
this.state = {
fromWallet,
amount: '',
unit: fromWallet.preferredBalanceUnit,
description: '',
lnurl: '',
lnurlParams: null,
isLoading: true,
renderWalletSelectionButtonHidden: false,
};
}
renderReceiveDetails = async () => {
try {
this.state.fromWallet.setUserHasSavedExport(true);
await this.context.saveToDisk();
if (this.props.route.params.uri) {
this.processLnurl(this.props.route.params.uri);
}
} catch (e) {
console.log(e);
}
this.setState({ isLoading: false });
};
componentDidMount() {
console.log('lnd/lndCreateInvoice mounted');
this.context.setSelectedWallet(this.state.fromWallet.getID());
if (this.state.fromWallet.getUserHasSavedExport()) {
this.renderReceiveDetails();
} else {
BlueAlertWalletExportReminder({
onSuccess: this.renderReceiveDetails,
onFailure: () => {
this.props.navigation.dangerouslyGetParent().pop();
this.props.navigation.navigate('WalletExportRoot', {
screen: 'WalletExportRoot',
params: {
walletID: this.state.fromWallet.getID(),
},
});
},
});
}
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow = () => {
this.setState({ renderWalletSelectionButtonHidden: true });
};
_keyboardDidHide = () => {
this.setState({ renderWalletSelectionButtonHidden: false });
};
async createInvoice() {
this.setState({ isLoading: true }, async () => {
try {
let amount = this.state.amount;
switch (this.state.unit) {
case BitcoinUnit.SATS:
amount = parseInt(amount); // basically nop
break;
case BitcoinUnit.BTC:
amount = currency.btcToSatoshi(amount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
// trying to fetch cached sat equivalent for this fiat amount
amount = BlueBitcoinAmount.getCachedSatoshis(amount) || currency.btcToSatoshi(currency.fiatToBTC(amount));
break;
}
const invoiceRequest = await this.state.fromWallet.addInvoice(amount, this.state.description);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
// lets decode payreq and subscribe groundcontrol so we can receive push notification when our invoice is paid
/** @type LightningCustodianWallet */
const fromWallet = this.state.fromWallet;
const decoded = await fromWallet.decodeInvoice(invoiceRequest);
await Notifications.tryToObtainPermissions();
Notifications.majorTomToGroundControl([], [decoded.payment_hash], []);
// send to lnurl-withdraw callback url if that exists
if (this.state.lnurlParams) {
const { callback, k1 } = this.state.lnurlParams;
const callbackUrl = callback + (callback.indexOf('?') !== -1 ? '&' : '?') + 'k1=' + k1 + '&pr=' + invoiceRequest;
const resp = await fetch(callbackUrl, { method: 'GET' });
if (resp.status >= 300) {
const text = await resp.text();
throw new Error(text);
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
}
setTimeout(async () => {
// wallet object doesnt have this fresh invoice in its internals, so we refetch it and only then save
await fromWallet.fetchUserInvoices(1);
await this.context.saveToDisk();
}, 1000);
this.props.navigation.navigate('LNDViewInvoice', {
invoice: invoiceRequest,
walletID: this.state.fromWallet.getID(),
isModal: true,
});
} catch (Err) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
this.setState({ isLoading: false });
alert(Err.message);
}
});
}
processLnurl = data => {
this.setState({ isLoading: true }, async () => {
if (!this.state.fromWallet) {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
return this.props.navigation.goBack();
}
// decoding the lnurl
const url = Lnurl.getUrlFromLnurl(data);
// calling the url
try {
const resp = await fetch(url, { method: 'GET' });
if (resp.status >= 300) {
throw new Error('Bad response from server');
}
const reply = await resp.json();
if (reply.status === 'ERROR') {
throw new Error('Reply from server: ' + reply.reason);
}
if (reply.tag === Lnurl.TAG_PAY_REQUEST) {
// we are here by mistake. user wants to SEND to lnurl-pay, but he is on a screen that creates
// invoices (including through lnurl-withdraw)
this.props.navigation.navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPay',
params: {
lnurl: data,
fromWalletID: this.state.fromWallet.getID(),
},
});
return;
}
if (reply.tag !== Lnurl.TAG_WITHDRAW_REQUEST) {
throw new Error('Unsupported lnurl');
}
// amount that comes from lnurl is always in sats
let amount = (reply.maxWithdrawable / 1000).toString();
const sats = amount;
switch (this.state.unit) {
case BitcoinUnit.SATS:
// nop
break;
case BitcoinUnit.BTC:
amount = currency.satoshiToBTC(amount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
amount = formatBalancePlain(amount, BitcoinUnit.LOCAL_CURRENCY);
BlueBitcoinAmount.setCachedSatoshis(amount, sats);
break;
}
// setting the invoice creating screen with the parameters
this.setState({
isLoading: false,
lnurlParams: {
k1: reply.k1,
callback: reply.callback,
fixed: reply.minWithdrawable === reply.maxWithdrawable,
min: (reply.minWithdrawable || 0) / 1000,
max: reply.maxWithdrawable / 1000,
},
amount,
description: reply.defaultDescription,
});
} catch (Err) {
Keyboard.dismiss();
this.setState({ isLoading: false });
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert(Err.message);
}
});
};
renderCreateButton = () => {
return (
<View style={styles.createButton}>
{this.state.isLoading ? (
<ActivityIndicator />
) : (
<BlueButton disabled={!(this.state.amount > 0)} onPress={() => this.createInvoice()} title={loc.send.details_create} />
)}
</View>
);
};
renderScanClickable = () => {
return (
<TouchableOpacity
disabled={this.state.isLoading}
onPress={() => {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: this.processLnurl,
launchedBy: this.props.route.name,
},
});
Keyboard.dismiss();
}}
style={styles.scanRoot}
>
<Image style={{}} source={require('../../img/scan-white.png')} />
<Text style={styles.scanClick}>{loc.send.details_scan}</Text>
</TouchableOpacity>
);
};
renderWalletSelectionButton = () => {
if (this.state.renderWalletSelectionButtonHidden) return;
return (
<View style={styles.walletRoot}>
{!this.state.isLoading && (
<TouchableOpacity
style={styles.walletChooseWrap}
onPress={() =>
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
}
>
<Text style={styles.walletChooseText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={styles.walletNameWrap}>
<TouchableOpacity
style={styles.walletNameTouch}
onPress={() =>
this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect, chainType: Chain.OFFCHAIN })
}
>
<Text style={styles.walletNameText}>{this.state.fromWallet.getLabel()}</Text>
<Text style={styles.walletNameBalance}>
{formatBalanceWithoutSuffix(this.state.fromWallet.getBalance(), BitcoinUnit.SATS, false)}
</Text>
<Text style={styles.walletNameSats}>{BitcoinUnit.SATS}</Text>
</TouchableOpacity>
</View>
</View>
);
};
onWalletSelect = wallet => {
this.setState({ fromWallet: wallet }, () => {
this.context.setSelectedWallet(wallet.getID());
this.props.navigation.pop();
});
};
render() {
if (!this.state.fromWallet) {
return (
<View style={styles.error}>
<Text>System error: Source wallet not found (this should never happen)</Text>
</View>
);
}
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={styles.root}>
<StatusBar barStyle="light-content" />
<View style={styles.amount}>
<KeyboardAvoidingView behavior="position">
<BlueBitcoinAmount
isLoading={this.state.isLoading}
amount={this.state.amount}
onAmountUnitChange={unit => {
this.setState({ unit });
}}
onChangeText={text => {
if (this.state.lnurlParams) {
// in this case we prevent the user from changing the amount to < min or > max
const { min, max } = this.state.lnurlParams;
const nextAmount = parseInt(text);
if (nextAmount < min) {
text = min.toString();
} else if (nextAmount > max) {
text = max.toString();
}
}
this.setState({ amount: text });
}}
disabled={this.state.isLoading || (this.state.lnurlParams && this.state.lnurlParams.fixed)}
unit={this.state.unit}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
<View style={styles.fiat}>
<TextInput
onChangeText={text => this.setState({ description: text })}
placeholder={loc.receive.details_label}
value={this.state.description}
numberOfLines={1}
placeholderTextColor="#81868e"
style={styles.fiat2}
editable={!this.state.isLoading}
onSubmitEditing={Keyboard.dismiss}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
{this.state.lnurlParams ? null : this.renderScanClickable()}
</View>
<BlueDismissKeyboardInputAccessory />
{this.renderCreateButton()}
</KeyboardAvoidingView>
</View>
{this.renderWalletSelectionButton()}
</View>
</TouchableWithoutFeedback>
);
}
}
LNDCreateInvoice.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
dangerouslyGetParent: PropTypes.func,
navigate: PropTypes.func,
pop: PropTypes.func,
}),
route: PropTypes.shape({
name: PropTypes.string,
params: PropTypes.shape({
uri: PropTypes.string,
fromWallet: PropTypes.shape({}),
}),
}),
};
LNDCreateInvoice.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
LNDCreateInvoice.navigationOptions = navigationStyle({
closeButton: true,
headerTitle: loc.receive.header,
headerLeft: null,
});

View File

@ -1,22 +1,15 @@
/* global alert */
import React, { useContext, useEffect, useState } from 'react';
import { View, Share, StyleSheet } from 'react-native';
import {
BlueLoading,
BlueCopyTextToClipboard,
SafeBlueArea,
BlueButton,
BlueNavigationStyle,
BlueText,
BlueSpacing20,
} from '../../BlueComponents';
import QRCode from 'react-native-qrcode-svg';
import loc from '../../loc';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueButton, BlueCopyTextToClipboard, BlueLoading, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const LNDViewAdditionalInvoiceInformation = () => {
// state = { walletInfo: undefined };
const { walletID } = useRoute().params;
const { wallets } = useContext(BlueStorageContext);
const wallet = wallets.find(w => w.getID() === walletID);
@ -123,7 +116,6 @@ const styles = StyleSheet.create({
export default LNDViewAdditionalInvoiceInformation;
LNDViewAdditionalInvoiceInformation.navigationOptions = () => ({
...BlueNavigationStyle(),
LNDViewAdditionalInvoiceInformation.navigationOptions = navigationStyle({
title: loc.lndViewInvoice.additional_info,
});

View File

@ -1,10 +1,12 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { BlueCopyTextToClipboard, SafeBlueArea, BlueNavigationStyle, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import QRCode from 'react-native-qrcode-svg';
import loc from '../../loc';
import { useRoute, useTheme } from '@react-navigation/native';
import { BlueCopyTextToClipboard, SafeBlueArea, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
const LNDViewAdditionalInvoicePreImage = () => {
// state = { walletInfo: undefined };
const { colors } = useTheme();
@ -59,7 +61,6 @@ const styles = StyleSheet.create({
export default LNDViewAdditionalInvoicePreImage;
LNDViewAdditionalInvoicePreImage.navigationOptions = () => ({
...BlueNavigationStyle(),
LNDViewAdditionalInvoicePreImage.navigationOptions = navigationStyle({
title: loc.lndViewInvoice.additional_info,
});

View File

@ -1,6 +1,11 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { View, Text, StatusBar, ScrollView, BackHandler, TouchableOpacity, StyleSheet, useWindowDimensions } from 'react-native';
import Share from 'react-native-share';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import QRCode from 'react-native-qrcode-svg';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import {
BlueLoading,
BlueText,
@ -8,16 +13,12 @@ import {
BlueButton,
SecondButton,
BlueCopyTextToClipboard,
BlueNavigationStyle,
BlueSpacing20,
BlueTextCentered,
} from '../../BlueComponents';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import QRCode from 'react-native-qrcode-svg';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { SuccessView } from '../send/success';
@ -356,23 +357,22 @@ const styles = StyleSheet.create({
},
});
export default LNDViewInvoice;
LNDViewInvoice.navigationOptions = navigationStyle(
{
title: loc.lndViewInvoice.lightning_invoice,
closeButton: true,
closeButtonFunc: ({ navigation }) => navigation.dangerouslyGetParent().pop(),
},
(options, { theme, navigation, route }) => {
return route.params.isModal === true
? {
headerLeft: null,
gestureEnabled: false,
}
: {
headerRight: null,
};
},
);
LNDViewInvoice.navigationOptions = ({ navigation, route }) =>
route.params.isModal === true
? {
...BlueNavigationStyle(navigation, true, () => navigation.dangerouslyGetParent().pop()),
title: loc.lndViewInvoice.lightning_invoice,
headerLeft: null,
headerStyle: {
...BlueNavigationStyle().headerStyle,
},
gestureEnabled: false,
}
: {
...BlueNavigationStyle(),
title: loc.lndViewInvoice.lightning_invoice,
headerStyle: {
...BlueNavigationStyle().headerStyle,
},
};
export default LNDViewInvoice;

View File

@ -1,26 +1,27 @@
/* global alert */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import AsyncStorage from '@react-native-community/async-storage';
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Icon } from 'react-native-elements';
import navigationStyle from '../../components/navigationStyle';
import {
BlueBitcoinAmount,
BlueButton,
BlueCard,
BlueDismissKeyboardInputAccessory,
BlueLoading,
BlueNavigationStyle,
BlueSpacing20,
BlueText,
SafeBlueArea,
} from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import Lnurl from '../../class/lnurl';
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { Icon } from 'react-native-elements';
import Biometric from '../../class/biometrics';
import PropTypes from 'prop-types';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const currency = require('../../blue_modules/currency');
@ -260,10 +261,9 @@ const styles = StyleSheet.create({
},
});
LnurlPay.navigationOptions = ({ navigation, route }) => {
return {
...BlueNavigationStyle(navigation, true, () => navigation.dangerouslyGetParent().popToTop()),
title: '',
headerLeft: null,
};
};
LnurlPay.navigationOptions = navigationStyle({
title: '',
closeButton: true,
closeButtonFunc: ({ navigation }) => navigation.dangerouslyGetParent().popToTop(),
headerLeft: null,
});

View File

@ -1,19 +1,12 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import LottieView from 'lottie-react-native';
import { View, Text, Linking, StyleSheet, Image, ScrollView } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import { Icon } from 'react-native-elements';
import {
BlueButton,
BlueButtonLink,
BlueNavigationStyle,
SafeBlueArea,
BlueCard,
BlueLoading,
BlueText,
BlueSpacing20,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import { BlueButton, BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Lnurl from '../../class/lnurl';
import loc from '../../loc';
@ -201,10 +194,9 @@ const styles = StyleSheet.create({
},
});
LnurlPaySuccess.navigationOptions = ({ navigation, route }) => {
return {
...BlueNavigationStyle(navigation, true, () => navigation.dangerouslyGetParent().popToTop()),
title: '',
headerLeft: null,
};
};
LnurlPaySuccess.navigationOptions = navigationStyle({
title: '',
closeButton: true,
closeButtonFunc: ({ navigation }) => navigation.dangerouslyGetParent().popToTop(),
headerLeft: null,
});

View File

@ -12,21 +12,22 @@ import {
StyleSheet,
} from 'react-native';
import PropTypes from 'prop-types';
import { Icon } from 'react-native-elements';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import {
BlueButton,
SafeBlueArea,
BlueCard,
BlueDismissKeyboardInputAccessory,
BlueNavigationStyle,
BlueAddressInput,
BlueBitcoinAmount,
BlueLoading,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import Lnurl from '../../class/lnurl';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { Icon } from 'react-native-elements';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import Biometric from '../../class/biometrics';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
@ -468,8 +469,8 @@ ScanLndInvoice.propTypes = {
}),
};
ScanLndInvoice.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
ScanLndInvoice.navigationOptions = navigationStyle({
closeButton: true,
title: loc.send.header,
headerLeft: null,
});

View File

@ -1,11 +1,13 @@
/* global alert */
import React, { useContext, useState } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20 } from '../BlueComponents';
import { useNavigation, useTheme } from '@react-navigation/native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import navigationStyle from '../components/navigationStyle';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueSpacing20 } from '../BlueComponents';
import loc from '../loc';
import { BlueStorageContext } from '../blue_modules/storage-context';
import { useNavigation, useTheme } from '@react-navigation/native';
const prompt = require('../blue_modules/prompt');
const styles = StyleSheet.create({
@ -85,7 +87,6 @@ const PlausibleDeniability = () => {
export default PlausibleDeniability;
PlausibleDeniability.navigationOptions = () => ({
...BlueNavigationStyle(),
PlausibleDeniability.navigationOptions = navigationStyle({
title: loc.plausibledeniability.title,
});

View File

@ -3,8 +3,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Keyboard, Text, TouchableOpacity, StatusBar, TouchableWithoutFeedback, View, StyleSheet } from 'react-native';
import { Icon } from 'react-native-elements';
import { BlueButton, BlueCreateTxNavigationStyle, BlueLoading, BlueSpacing, BlueText } from '../../BlueComponents';
import { BlueButton, BlueLoading, BlueSpacing, BlueText } from '../../BlueComponents';
import { navigationStyleTx } from '../../components/navigationStyle';
import loc from '../../loc';
import { PlaceholderWallet } from '../../class';
import Azteco from '../../class/azteco';
@ -183,7 +184,6 @@ AztecoRedeem.propTypes = {
}),
};
AztecoRedeem.navigationOptions = ({ navigation }) => ({
...BlueCreateTxNavigationStyle(navigation),
AztecoRedeem.navigationOptions = navigationStyleTx({
title: loc.azteco.title,
});

View File

@ -16,18 +16,18 @@ import Share from 'react-native-share';
import Handoff from 'react-native-handoff';
import {
BlueLoadingHook,
BlueLoading,
BlueCopyTextToClipboard,
BlueButton,
SecondButton,
BlueButtonLinkHook,
BlueButtonLink,
is,
BlueBitcoinAmount,
BlueText,
BlueSpacing20,
BlueAlertWalletExportReminder,
BlueNavigationStyle,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import BottomModal from '../../components/BottomModal';
import Privacy from '../../Privacy';
import { Chain, BitcoinUnit } from '../../models/bitcoinUnits';
@ -161,7 +161,7 @@ const ReceiveDetails = () => {
<BlueCopyTextToClipboard text={isCustom ? bip21encoded : address} />
</View>
<View style={styles.share}>
<BlueButtonLinkHook title={loc.receive.details_setAmount} onPress={showCustomAmountModal} />
<BlueButtonLink title={loc.receive.details_setAmount} onPress={showCustomAmountModal} />
<View>
<SecondButton onPress={handleShareButtonPressed} title={loc.receive.details_share} />
</View>
@ -345,13 +345,13 @@ const ReceiveDetails = () => {
url={`https://blockstream.info/address/${address}`}
/>
)}
{showAddress ? renderReceiveDetails() : <BlueLoadingHook />}
{showAddress ? renderReceiveDetails() : <BlueLoading />}
</View>
);
};
ReceiveDetails.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
ReceiveDetails.navigationOptions = navigationStyle({
closeButton: true,
title: loc.receive.header,
headerLeft: null,
});

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react';
import { ScrollView, View, StyleSheet } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueLoadingHook } from '../BlueComponents';
import PropTypes from 'prop-types';
import { ScrollView, View, StyleSheet } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueLoading } from '../BlueComponents';
import navigationStyle from '../components/navigationStyle';
import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet, HDSegwitBech32Wallet } from '../class';
import { BlueCurrentTheme } from '../components/themes';
const bitcoin = require('bitcoinjs-lib');
@ -210,7 +211,7 @@ export default class Selftest extends Component {
render() {
if (this.state.isLoading) {
return <BlueLoadingHook />;
return <BlueLoading />;
}
return (
@ -259,7 +260,6 @@ Selftest.propTypes = {
}),
};
Selftest.navigationOptions = () => ({
...BlueNavigationStyle(),
Selftest.navigationOptions = navigationStyle({
title: 'Self test',
});

View File

@ -7,7 +7,7 @@ import ImagePicker from 'react-native-image-picker';
import { decodeUR, extractSingleWorkload } from 'bc-ur';
import { useNavigation, useRoute, useIsFocused, useTheme } from '@react-navigation/native';
import loc from '../../loc';
import { BlueLoadingHook, BlueText, BlueButton, BlueSpacing40 } from '../../BlueComponents';
import { BlueLoading, BlueText, BlueButton, BlueSpacing40 } from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import { openPrivacyDesktopSettings } from '../../class/camera';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
@ -231,7 +231,7 @@ const ScanQRCode = () => {
return isLoading ? (
<View style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</View>
) : (
<View style={styles.root}>
@ -330,8 +330,4 @@ const ScanQRCode = () => {
);
};
ScanQRCode.navigationOptions = {
headerShown: false,
};
export default ScanQRCode;

View File

@ -2,8 +2,10 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { ActivityIndicator, Linking, StyleSheet, View, KeyboardAvoidingView, Platform, Text, TextInput } from 'react-native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import loc from '../../loc';
import { HDSegwitBech32Wallet } from '../../class';
import navigationStyle from '../../components/navigationStyle';
import {
SafeBlueArea,
BlueCard,
@ -13,7 +15,6 @@ import {
BlueFormLabel,
BlueTextCentered,
BlueBigCheckmark,
BlueNavigationStyle,
} from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import BlueElectrum from '../../blue_modules/BlueElectrum';
@ -110,8 +111,7 @@ const Broadcast = () => {
};
export default Broadcast;
Broadcast.navigationOptions = () => ({
...BlueNavigationStyle(),
Broadcast.navigationOptions = navigationStyle({
title: loc.send.create_broadcast,
});
@ -180,10 +180,10 @@ function SuccessScreen({ tx }) {
<View style={styles.broadcastResultWrapper}>
<BlueBigCheckmark />
<BlueSpacing20 />
<BlueTextCentered>Success! You transaction has been broadcasted!</BlueTextCentered>
<BlueTextCentered>{loc.send.broadcast_success_screen_msg}</BlueTextCentered>
<BlueSpacing10 />
<Text style={styles.link} onPress={() => Linking.openURL(`https://blockstream.info/tx/${tx}`)}>
Open link in explorer
{loc.send.broadcast_success_screen_open}
</Text>
</View>
</BlueCard>

View File

@ -16,9 +16,10 @@ import {
} from 'react-native';
import { useRoute, useTheme, useNavigation } from '@react-navigation/native';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import loc, { formatBalance } from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { BlueNavigationStyle, SafeBlueArea, BlueSpacing10, BlueSpacing20, BlueButton, BlueListItem } from '../../BlueComponents';
import { SafeBlueArea, BlueSpacing10, BlueSpacing20, BlueButton, BlueListItem } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import BottomModal from '../../components/BottomModal';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -35,7 +36,15 @@ const debounce = (func, wait) => {
};
};
const Output = ({ item: { address, txid, value, vout }, oMemo, frozen, change = false, full = false, onPress }) => {
const Output = ({
item: { address, txid, value, vout },
balanceUnit = BitcoinUnit.BTC,
oMemo,
frozen,
change = false,
full = false,
onPress,
}) => {
const { colors } = useTheme();
const { txMetadata } = useContext(BlueStorageContext);
const cs = useColorScheme();
@ -43,7 +52,7 @@ const Output = ({ item: { address, txid, value, vout }, oMemo, frozen, change =
const fullId = `${txid}:${vout}`;
const shortId = `${address.substring(0, 9)}...${address.substr(address.length - 9)}`;
const color = `#${txid.substring(0, 6)}`;
const amount = formatBalanceWithoutSuffix(value, BitcoinUnit.BTC, true);
const amount = formatBalance(value, balanceUnit, true);
const oStyles = StyleSheet.create({
containerFull: { paddingHorizontal: 0 },
@ -102,6 +111,7 @@ Output.propTypes = {
value: PropTypes.number.isRequired,
vout: PropTypes.number.isRequired,
}),
balanceUnit: PropTypes.string,
oMemo: PropTypes.string,
frozen: PropTypes.bool,
change: PropTypes.bool,
@ -148,7 +158,7 @@ const OutputModalContent = ({ output, wallet, onUseCoin }) => {
return (
<>
<Output item={output} full />
<Output item={output} balanceUnit={wallet.getPreferredBalanceUnit()} full />
<BlueSpacing20 />
<TextInput
testID="OutputMemo"
@ -223,7 +233,16 @@ const CoinControl = () => {
const renderItem = p => {
const { memo, frozen } = wallet.getUTXOMetadata(p.item.txid, p.item.vout);
const change = wallet.addressIsChange(p.item.address);
return <Output item={p.item} oMemo={memo} frozen={frozen} change={change} onPress={() => handleChoose(p.item)} />;
return (
<Output
balanceUnit={wallet.getPreferredBalanceUnit()}
item={p.item}
oMemo={memo}
frozen={frozen}
change={change}
onPress={() => handleChoose(p.item)}
/>
);
};
if (loading) {
@ -290,8 +309,7 @@ const styles = StyleSheet.create({
},
});
CoinControl.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
CoinControl.navigationOptions = navigationStyle({
title: loc.cc.header,
});

View File

@ -1,13 +1,15 @@
/* global alert */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ActivityIndicator, FlatList, TouchableOpacity, StyleSheet, Switch, View } from 'react-native';
import { Text } from 'react-native-elements';
import { PayjoinClient } from 'payjoin-client';
import PayjoinTransaction from '../../class/payjoin-transaction';
import { BlueButton, BlueText, SafeBlueArea, BlueCard, BlueSpacing40, BlueNavigationStyle } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PayjoinTransaction from '../../class/payjoin-transaction';
import { BlueButton, BlueText, SafeBlueArea, BlueCard, BlueSpacing40 } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import Biometric from '../../class/biometrics';
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
@ -91,14 +93,15 @@ export default class Confirm extends Component {
amount = formatBalanceWithoutSuffix(amount, BitcoinUnit.BTC, false);
this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID());
this.props.navigation.navigate('Success', {
fee: Number(this.state.fee),
amount,
dismissModal: () => this.props.navigation.dangerouslyGetParent().pop(),
});
this.setState({ isLoading: false });
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep to make sure network propagates
this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID());
} catch (error) {
ReactNativeHapticFeedback.trigger('notificationError', {
ignoreAndroidSystemSettings: false,
@ -323,7 +326,6 @@ Confirm.propTypes = {
}),
};
Confirm.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
Confirm.navigationOptions = navigationStyle({
title: loc.send.confirm_header,
});

View File

@ -20,13 +20,14 @@ import Clipboard from '@react-native-community/clipboard';
import { Icon } from 'react-native-elements';
import Share from 'react-native-share';
import RNFS from 'react-native-fs';
import isCatalyst from 'react-native-is-catalyst';
import { BlueNavigationStyle, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
import { SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Privacy from '../../Privacy';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import isCatalyst from 'react-native-is-catalyst';
const currency = require('../../blue_modules/currency');
export default class SendCreate extends Component {
@ -246,21 +247,25 @@ SendCreate.propTypes = {
}),
};
SendCreate.navigationOptions = ({ navigation, route }) => {
let headerRight;
if (route.params.exportTXN) {
headerRight = () => (
<TouchableOpacity style={styles.export} onPress={route.params.exportTXN}>
<Icon size={22} name="share-alternative" type="entypo" color={BlueCurrentTheme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
...BlueNavigationStyle(),
SendCreate.navigationOptions = navigationStyle(
{
title: loc.send.create_details,
headerRight,
};
};
},
(options, { theme, navigation, route }) => {
let headerRight;
if (route.params.exportTXN) {
headerRight = () => (
<TouchableOpacity style={styles.export} onPress={route.params.exportTXN}>
<Icon size={22} name="share-alternative" type="entypo" color={BlueCurrentTheme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
...options,
headerRight,
};
},
);

View File

@ -20,8 +20,12 @@ import {
} from 'react-native';
import { Icon } from 'react-native-elements';
import AsyncStorage from '@react-native-community/async-storage';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import RNFS from 'react-native-fs';
import BigNumber from 'bignumber.js';
import * as bitcoin from 'bitcoinjs-lib';
import {
BlueCreateTxNavigationStyle,
BlueButton,
BlueBitcoinAmount,
BlueAddressInput,
@ -31,11 +35,7 @@ import {
BlueListItem,
BlueText,
} from '../../BlueComponents';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import BigNumber from 'bignumber.js';
import RNFS from 'react-native-fs';
import * as bitcoin from 'bitcoinjs-lib';
import { navigationStyleTx } from '../../components/navigationStyle';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { HDSegwitBech32Wallet, LightningCustodianWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
@ -205,6 +205,11 @@ const styles = StyleSheet.create({
feeValue: {
color: BlueCurrentTheme.colors.feeValue,
},
advancedOptions: {
minWidth: 40,
height: 40,
justifyContent: 'center',
},
});
export default class SendDetails extends Component {
@ -647,7 +652,7 @@ export default class SendDetails extends Component {
this.props.navigation.navigate('PsbtMultisig', {
memo: this.state.memo,
psbtBase64: psbt.toBase64(),
walletId: wallet.getID(),
walletID: wallet.getID(),
});
this.setState({ isLoading: false });
return;
@ -923,7 +928,6 @@ export default class SendDetails extends Component {
} else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
// plain text file with txhex ready to broadcast
const file = (await RNFS.readFile(res.uri, 'ascii')).replace('\n', '').replace('\r', '');
console.warn(JSON.stringify(file));
this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo,
fromWallet: this.state.fromWallet,
@ -976,7 +980,7 @@ export default class SendDetails extends Component {
this.props.navigation.navigate('PsbtMultisig', {
memo: this.state.memo,
psbtBase64: psbt.toBase64(),
walletId: fromWallet.getID(),
walletID: fromWallet.getID(),
});
} catch (error) {
alert(loc.send.problem_with_psbt + ': ' + error.message);
@ -1470,7 +1474,28 @@ SendDetails.propTypes = {
}),
};
SendDetails.navigationOptions = ({ navigation, route }) => ({
...BlueCreateTxNavigationStyle(navigation, route.params.withAdvancedOptionsMenuButton, route.params.advancedOptionsMenuButtonAction),
title: loc.send.header,
});
SendDetails.navigationOptions = navigationStyleTx(
{
title: loc.send.header,
},
(options, { theme, navigation, route }) => {
let headerRight;
if (route.params.withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity
style={styles.advancedOptions}
onPress={route.params.advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={theme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
...options,
headerRight,
};
},
);

View File

@ -1,23 +1,17 @@
/* global alert */
import React, { useContext, useState } from 'react';
import { FlatList, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { BlueButton, BlueButtonLink, BlueCard, BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import { SquareButton } from '../../components/SquareButton';
import { getSystemName } from 'react-native-device-info';
import loc from '../../loc';
import React, { useContext, useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Icon } from 'react-native-elements';
import ImagePicker from 'react-native-image-picker';
import ScanQRCode from './ScanQRCode';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueButton, BlueCard, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const bitcoin = require('bitcoinjs-lib');
const currency = require('../../blue_modules/currency');
const fs = require('../../blue_modules/fs');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const BigNumber = require('bignumber.js');
const currency = require('../../blue_modules/currency');
const shortenAddress = addr => {
return addr.substr(0, Math.floor(addr.length / 2) - 1) + '\n' + addr.substr(Math.floor(addr.length / 2) - 1, addr.length);
@ -25,21 +19,21 @@ const shortenAddress = addr => {
const PsbtMultisig = () => {
const { wallets } = useContext(BlueStorageContext);
const navigation = useNavigation();
const route = useRoute();
const { navigate, setParams } = useNavigation();
const { colors } = useTheme();
const [flatListHeight, setFlatListHeight] = useState(0);
const walletId = route.params.walletId;
const psbtBase64 = route.params.psbtBase64;
const memo = route.params.memo;
const { walletID, psbtBase64, memo, receivedPSBTBase64 } = useRoute().params;
/** @type MultisigHDWallet */
const wallet = wallets.find(w => w.getID() === walletID);
const [psbt, setPsbt] = useState(bitcoin.Psbt.fromBase64(psbtBase64));
const [isModalVisible, setIsModalVisible] = useState(false);
const data = new Array(wallet.getM());
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
whitespace: {
color: colors.elevated,
},
textBtc: {
color: colors.buttonAlternativeTextColor,
},
@ -52,18 +46,12 @@ const PsbtMultisig = () => {
textDestination: {
color: colors.foregroundColor,
},
modalContentShort: {
backgroundColor: colors.elevated,
},
textFiat: {
color: colors.alternativeTextColor,
},
provideSignatureButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
exportButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
provideSignatureButtonText: {
color: colors.buttonTextColor,
},
@ -83,8 +71,7 @@ const PsbtMultisig = () => {
color: colors.msSuccessBG,
},
});
/** @type MultisigHDWallet */
const wallet = wallets.find(w => w.getID() === walletId);
let destination = [];
let totalSat = 0;
const targets = [];
@ -98,23 +85,22 @@ const PsbtMultisig = () => {
destination = shortenAddress(destination.join(', '));
const totalBtc = new BigNumber(totalSat).dividedBy(100000000).toNumber();
const totalFiat = currency.satoshiToLocalCurrency(totalSat);
const fileName = `${Date.now()}.psbt`;
const howManySignaturesWeHave = () => {
return wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
};
const getFee = () => {
return wallet.calculateFeeFromPsbt(psbt);
};
const _renderItem = el => {
if (el.index >= howManySignaturesWeHave()) return _renderItemUnsigned(el);
if (el.index >= howManySignaturesWeHave) return _renderItemUnsigned(el);
else return _renderItemSigned(el);
};
const navigateToPSBTMultisigQRCode = () => {
navigate('PsbtMultisigQRCode', { walletID, psbtBase64, isShowOpenScanner: isConfirmEnabled() });
};
const _renderItemUnsigned = el => {
const renderProvideSignature = el.index === howManySignaturesWeHave();
const renderProvideSignature = el.index === howManySignaturesWeHave;
return (
<View testID="ItemUnsigned">
<View style={styles.itemUnsignedWrapper}>
@ -133,9 +119,7 @@ const PsbtMultisig = () => {
<TouchableOpacity
testID="ProvideSignature"
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
onPress={() => {
setIsModalVisible(true);
}}
onPress={navigateToPSBTMultisigQRCode}
>
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
{loc.multisig.provide_signature}
@ -162,31 +146,25 @@ const PsbtMultisig = () => {
);
};
const _combinePSBT = receivedPSBTBase64 => {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (receivedPSBTBase64) {
_combinePSBT();
setParams({ receivedPSBTBase64: undefined });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [receivedPSBTBase64]);
const _combinePSBT = () => {
const receivedPSBT = bitcoin.Psbt.fromBase64(receivedPSBTBase64);
try {
const newPsbt = psbt.combine(receivedPSBT);
navigation.dangerouslyGetParent().pop();
setPsbt(newPsbt);
setIsModalVisible(false);
} catch (error) {
alert(error);
}
};
const onBarScanned = ret => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
_combinePSBT(ret.data);
}
};
const onConfirm = () => {
try {
psbt.finalizeAllInputs();
@ -195,7 +173,7 @@ const PsbtMultisig = () => {
try {
const tx = psbt.extractTransaction().toHex();
const satoshiPerByte = Math.round(getFee() / (tx.length / 2));
navigation.navigate('Confirm', {
navigate('Confirm', {
fee: new BigNumber(getFee()).dividedBy(100000000).toNumber(),
memo: memo,
fromWallet: wallet,
@ -208,83 +186,20 @@ const PsbtMultisig = () => {
}
};
const openScanner = () => {
if (isDesktop) {
ImagePicker.launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
}
},
);
} else {
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: onBarScanned,
showFileImportButton: true,
},
});
}
};
const exportPSBT = async () => {
await fs.writeFileAndExport(fileName, psbt.toBase64());
};
const howManySignaturesWeHave = wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
const isConfirmEnabled = () => {
return howManySignaturesWeHave() >= wallet.getM();
};
const renderDynamicQrCode = () => {
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
<DynamicQRCode value={psbt.toHex()} capacity={666} />
{!isConfirmEnabled() && (
<>
<BlueSpacing20 />
<SquareButton
testID="CosignedScanOrImportFile"
style={[styles.exportButton, stylesHook.exportButton]}
onPress={openScanner}
title={loc.multisig.scan_or_import_file}
/>
</>
)}
<BlueSpacing20 />
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
<BlueSpacing20 />
<BlueButtonLink title={loc._.cancel} onPress={() => setIsModalVisible(false)} />
</View>
</ScrollView>
</SafeBlueArea>
);
return howManySignaturesWeHave >= wallet.getM();
};
const destinationAddress = () => {
// eslint-disable-next-line prefer-const
let destinationAddressView = [];
const whitespace = '_';
const destinations = Object.entries(destination.split(','));
for (const [index, address] of destinations) {
if (index > 1) {
destinationAddressView.push(
<View style={styles.destionationTextContainer} key={`end-${index}`}>
<View style={styles.destinationTextContainer} key={`end-${index}`}>
<Text numberOfLines={0} style={[styles.textDestinationFirstFour, stylesHook.textFiat]}>
and {destinations.length - 2} more...
</Text>
@ -297,13 +212,15 @@ const PsbtMultisig = () => {
const lastFour = currentAddress.substring(currentAddress.length - 5, currentAddress.length);
const middle = currentAddress.split(firstFour)[1].split(lastFour)[0];
destinationAddressView.push(
<View style={styles.destionationTextContainer} key={`${currentAddress}-${index}`}>
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
{firstFour}
<Text> </Text>
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
<Text> </Text>
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
<View style={styles.destinationTextContainer} key={`${currentAddress}-${index}`}>
<Text style={styles.textAlignCenter}>
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
{firstFour}
<Text style={stylesHook.whitespace}>{whitespace}</Text>
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
<Text style={stylesHook.whitespace}>{whitespace}</Text>
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
</Text>
</Text>
</View>,
);
@ -338,13 +255,10 @@ const PsbtMultisig = () => {
</View>
);
if (isModalVisible) return renderDynamicQrCode();
const onLayout = e => {
setFlatListHeight(e.nativeEvent.layout.height);
};
const data = new Array(wallet.getM());
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<View style={styles.container}>
@ -367,9 +281,7 @@ const PsbtMultisig = () => {
<TouchableOpacity
testID="ExportSignedPsbt"
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
onPress={() => {
setIsModalVisible(true);
}}
onPress={navigateToPSBTMultisigQRCode}
>
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
{loc.multisig.export_signed_psbt}
@ -422,7 +334,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'center',
},
destionationTextContainer: {
destinationTextContainer: {
flexDirection: 'row',
marginBottom: 4,
paddingHorizontal: 60,
@ -438,6 +350,9 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
fontSize: 30,
},
textAlignCenter: {
textAlign: 'center',
},
textDestinationFirstFour: {
fontSize: 14,
},
@ -447,21 +362,6 @@ const styles = StyleSheet.create({
fontSize: 14,
flexWrap: 'wrap',
},
modalContentShort: {
marginLeft: 20,
marginRight: 20,
},
copyToClipboard: {
justifyContent: 'center',
alignItems: 'center',
},
exportButton: {
height: 48,
borderRadius: 8,
flex: 1,
justifyContent: 'center',
paddingHorizontal: 16,
},
provideSignatureButton: {
marginTop: 24,
marginLeft: 40,
@ -503,8 +403,7 @@ const styles = StyleSheet.create({
},
});
PsbtMultisig.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
PsbtMultisig.navigationOptions = navigationStyle({
title: loc.multisig.header,
});

View File

@ -0,0 +1,148 @@
/* global alert */
import React, { useState } from 'react';
import { ActivityIndicator, Platform, ScrollView, StyleSheet, View } from 'react-native';
import { getSystemName } from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueSpacing20, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import { SquareButton } from '../../components/SquareButton';
import loc from '../../loc';
import ScanQRCode from './ScanQRCode';
const bitcoin = require('bitcoinjs-lib');
const fs = require('../../blue_modules/fs');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const PsbtMultisigQRCode = () => {
const { navigate } = useNavigation();
const { colors } = useTheme();
const { psbtBase64, isShowOpenScanner } = useRoute().params;
const [isLoading, setIsLoading] = useState(false);
const psbt = bitcoin.Psbt.fromBase64(psbtBase64);
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
modalContentShort: {
backgroundColor: colors.elevated,
},
exportButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
});
const fileName = `${Date.now()}.psbt`;
const onBarScanned = ret => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
navigate('PsbtMultisig', { receivedPSBTBase64: ret.data });
}
};
const openScanner = () => {
if (isDesktop) {
ImagePicker.launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
}
},
);
} else {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: onBarScanned,
showFileImportButton: true,
},
});
}
};
const exportPSBT = () => {
setIsLoading(true);
setTimeout(() => fs.writeFileAndExport(fileName, psbt.toBase64()).finally(() => setIsLoading(false)), 10);
};
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
<DynamicQRCode value={psbt.toHex()} capacity={666} />
{!isShowOpenScanner && (
<>
<BlueSpacing20 />
<SquareButton
testID="CosignedScanOrImportFile"
style={[styles.exportButton, stylesHook.exportButton]}
onPress={openScanner}
title={loc.multisig.scan_or_import_file}
/>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
)}
</View>
</ScrollView>
</SafeBlueArea>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1,
justifyContent: 'space-between',
},
modalContentShort: {
marginLeft: 20,
marginRight: 20,
},
copyToClipboard: {
justifyContent: 'center',
alignItems: 'center',
},
exportButton: {
height: 48,
borderRadius: 8,
flex: 1,
justifyContent: 'center',
paddingHorizontal: 16,
},
});
PsbtMultisigQRCode.navigationOptions = navigationStyle({
title: loc.multisig.header,
});
export default PsbtMultisigQRCode;

View File

@ -15,27 +15,28 @@ import {
} from 'react-native';
import ImagePicker from 'react-native-image-picker';
import Clipboard from '@react-native-community/clipboard';
import Share from 'react-native-share';
import { getSystemName } from 'react-native-device-info';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import DocumentPicker from 'react-native-document-picker';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import isCatalyst from 'react-native-is-catalyst';
import RNFS from 'react-native-fs';
import {
SecondButton,
BlueText,
SafeBlueArea,
BlueCard,
BlueNavigationStyle,
BlueSpacing20,
BlueCopyToClipboardButton,
DynamicQRCode,
} from '../../BlueComponents';
import Share from 'react-native-share';
import { getSystemName } from 'react-native-device-info';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
import ScanQRCode from './ScanQRCode';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Notifications from '../../blue_modules/notifications';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import isCatalyst from 'react-native-is-catalyst';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
/** @type {AppStorage} */
const bitcoin = require('bitcoinjs-lib');
@ -336,8 +337,7 @@ const PsbtWithHardwareWallet = () => {
export default PsbtWithHardwareWallet;
PsbtWithHardwareWallet.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
PsbtWithHardwareWallet.navigationOptions = navigationStyle({
title: loc.send.header,
});

View File

@ -15,7 +15,7 @@ const Success = () => {
};
const { colors } = useTheme();
const { dangerouslyGetParent } = useNavigation();
const { amount = 0, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop } = useRoute().params;
const { amount, fee, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop } = useRoute().params;
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
@ -76,10 +76,12 @@ export const SuccessView = ({ amount, amountUnit, fee, invoiceDescription, shoul
<View style={styles.root}>
<BlueCard style={styles.amount}>
<View style={styles.view}>
<>
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
</>
{amount && (
<>
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
</>
)}
</View>
{fee > 0 && (
<Text style={styles.feeText}>

View File

@ -1,6 +1,8 @@
import React, { useContext, useEffect, useState } from 'react';
import { ScrollView, Platform, TouchableWithoutFeedback, TouchableOpacity, StyleSheet } from 'react-native';
import { BlueLoading, BlueText, BlueSpacing20, BlueListItem, BlueNavigationStyle, BlueCard } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueLoading, BlueText, BlueSpacing20, BlueListItem, BlueCard } from '../../BlueComponents';
import { useNavigation, useTheme } from '@react-navigation/native';
import HandoffSettings from '../../class/handoff';
import loc from '../../loc';
@ -88,8 +90,7 @@ const GeneralSettings = () => {
);
};
GeneralSettings.navigationOptions = () => ({
...BlueNavigationStyle(),
GeneralSettings.navigationOptions = navigationStyle({
title: loc.settings.general,
});

View File

@ -1,7 +1,9 @@
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueListItem, BlueNavigationStyle } from '../../BlueComponents';
import { useNavigation, useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueListItem } from '../../BlueComponents';
import loc from '../../loc';
const NetworkSettings = () => {
@ -36,8 +38,9 @@ const NetworkSettings = () => {
</SafeBlueArea>
);
};
NetworkSettings.navigationOptions = () => ({
...BlueNavigationStyle(),
NetworkSettings.navigationOptions = navigationStyle({
title: loc.settings.network,
});
export default NetworkSettings;

View File

@ -1,7 +1,9 @@
import React, { useContext, useEffect, useState } from 'react';
import { ScrollView, TouchableWithoutFeedback, StyleSheet, Linking } from 'react-native';
import { BlueText, BlueSpacing20, BlueListItem, BlueNavigationStyle, BlueCard } from '../../BlueComponents';
import { useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { BlueText, BlueSpacing20, BlueListItem, BlueCard } from '../../BlueComponents';
import loc from '../../loc';
import BlueClipboard from '../../blue_modules/clipboard';
import DeviceQuickActions from '../../class/quick-actions';
@ -100,8 +102,7 @@ const styles = StyleSheet.create({
},
});
SettingsPrivacy.navigationOptions = () => ({
...BlueNavigationStyle(),
SettingsPrivacy.navigationOptions = navigationStyle({
title: loc.settings.privacy,
});

View File

@ -1,17 +1,11 @@
import React from 'react';
import { ScrollView, Linking, Image, View, Text, StyleSheet, useWindowDimensions } from 'react-native';
import { useNavigation, useTheme } from '@react-navigation/native';
import {
BlueTextCentered,
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueListItem,
BlueNavigationStyle,
} from '../../BlueComponents';
import { getApplicationName, getVersion, getBundleId, getBuildNumber } from 'react-native-device-info';
import Rate, { AndroidMarket } from 'react-native-rate';
import { BlueButton, BlueCard, BlueListItem, BlueSpacing20, BlueTextCentered, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
const About = () => {
@ -193,8 +187,7 @@ const About = () => {
);
};
About.navigationOptions = () => ({
...BlueNavigationStyle(),
About.navigationOptions = navigationStyle({
headerTitle: loc.settings.about,
});
export default About;

View File

@ -1,10 +1,11 @@
import React, { useState, useEffect } from 'react';
import { FlatList, ActivityIndicator, View, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueListItem, BlueText, BlueCard, BlueNavigationStyle } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueListItem, BlueText, BlueCard } from '../../BlueComponents';
import { FiatUnit, FiatUnitSource } from '../../models/fiatUnit';
import loc from '../../loc';
import { useTheme } from '@react-navigation/native';
const currency = require('../../blue_modules/currency');
const data = Object.values(FiatUnit);
@ -82,15 +83,8 @@ const Currency = () => {
);
};
Currency.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};
Currency.navigationOptions = () => ({
...BlueNavigationStyle(),
Currency.navigationOptions = navigationStyle({
title: loc.settings.currency,
});
export default Currency;

View File

@ -1,7 +1,9 @@
import React, { useContext, useEffect, useState } from 'react';
import { View, TouchableWithoutFeedback, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { SafeBlueArea, BlueCard, BlueNavigationStyle, BlueListItem, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueCard, BlueListItem, BlueText } from '../../BlueComponents';
import OnAppLaunch from '../../class/on-app-launch';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -78,8 +80,7 @@ const DefaultView = () => {
);
};
DefaultView.navigationOptions = () => ({
...BlueNavigationStyle(),
DefaultView.navigationOptions = navigationStyle({
title: loc.settings.default_title,
});

View File

@ -1,24 +1,17 @@
/* global alert */
import React, { Component } from 'react';
import { View, TextInput, StyleSheet } from 'react-native';
import { AppStorage } from '../../class';
import AsyncStorage from '@react-native-community/async-storage';
import { ScrollView } from 'react-native-gesture-handler';
import {
BlueLoading,
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueNavigationStyle,
BlueButtonLink,
} from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import PropTypes from 'prop-types';
import loc from '../../loc';
import { View, TextInput, StyleSheet } from 'react-native';
import DefaultPreference from 'react-native-default-preference';
import RNWidgetCenter from 'react-native-widget-center';
import AsyncStorage from '@react-native-community/async-storage';
import { ScrollView } from 'react-native-gesture-handler';
import { AppStorage } from '../../class';
import navigationStyle from '../../components/navigationStyle';
import { BlueButton, BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import loc from '../../loc';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
export default class ElectrumSettings extends Component {
@ -219,8 +212,7 @@ ElectrumSettings.propTypes = {
}),
};
ElectrumSettings.navigationOptions = () => ({
...BlueNavigationStyle(),
ElectrumSettings.navigationOptions = navigationStyle({
title: loc.settings.electrum_settings,
});

View File

@ -3,19 +3,20 @@ import React, { useEffect, useState, useCallback, useContext } from 'react';
import { ScrollView, Alert, Platform, TouchableOpacity, TouchableWithoutFeedback, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { colors } from 'react-native-elements';
import navigationStyle from '../../components/navigationStyle';
import {
BlueLoadingHook,
BlueLoading,
SafeBlueArea,
BlueSpacing20,
BlueCard,
BlueListItem,
BlueHeaderDefaultSubHooks,
BlueText,
BlueNavigationStyle,
} from '../../BlueComponents';
import Biometric from '../../class/biometrics';
import loc from '../../loc';
import { colors } from 'react-native-elements';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const prompt = require('../../blue_modules/prompt');
@ -149,7 +150,7 @@ const EncryptStorage = () => {
return isLoading ? (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</SafeBlueArea>
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
@ -202,7 +203,6 @@ const EncryptStorage = () => {
};
export default EncryptStorage;
EncryptStorage.navigationOptions = () => ({
...BlueNavigationStyle(),
EncryptStorage.navigationOptions = navigationStyle({
headerTitle: loc.settings.encrypt_title,
});

View File

@ -1,6 +1,8 @@
import React, { useState, useEffect, useCallback } from 'react';
import { FlatList, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueListItem, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueListItem, BlueCard, BlueLoading, BlueText } from '../../BlueComponents';
import { AvailableLanguages } from '../../loc/languages';
import loc from '../../loc';
@ -40,7 +42,7 @@ const Language = () => {
);
return isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.flex}>
<FlatList style={styles.flex} keyExtractor={(_item, index) => `${index}`} data={AvailableLanguages} renderItem={renderItem} />
@ -51,8 +53,7 @@ const Language = () => {
);
};
Language.navigationOptions = () => ({
...BlueNavigationStyle(),
Language.navigationOptions = navigationStyle({
headerTitle: loc.settings.language,
});

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20, BlueLoadingHook } from '../../BlueComponents';
/** @type {AppStorage} */
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueCard, BlueText, BlueSpacing20, BlueLoading } from '../../BlueComponents';
const styles = StyleSheet.create({
root: {
@ -17,7 +17,7 @@ const Licensing = () => {
}, []);
return isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
<ScrollView>
@ -51,8 +51,7 @@ const Licensing = () => {
);
};
Licensing.navigationOptions = () => ({
...BlueNavigationStyle(),
Licensing.navigationOptions = navigationStyle({
title: 'License',
});

View File

@ -3,18 +3,11 @@ import React, { useState, useEffect, useCallback } from 'react';
import { View, TextInput, Linking, StyleSheet } from 'react-native';
import { Button } from 'react-native-elements';
import { useTheme, useNavigation, useRoute } from '@react-navigation/native';
import { AppStorage } from '../../class';
import AsyncStorage from '@react-native-community/async-storage';
import {
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueNavigationStyle,
BlueLoadingHook,
BlueText,
BlueButtonLink,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueButton, BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { AppStorage } from '../../class';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
@ -130,14 +123,14 @@ const LightningSettings = () => {
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} />
<BlueSpacing20 />
{isLoading ? <BlueLoadingHook /> : <BlueButton onPress={save} title={loc.settings.save} />}
{isLoading ? <BlueLoading /> : <BlueButton onPress={save} title={loc.settings.save} />}
</BlueCard>
</SafeBlueArea>
);
};
LightningSettings.navigationOptions = () => ({
...BlueNavigationStyle(),
LightningSettings.navigationOptions = navigationStyle({
title: loc.settings.lightning_settings,
});
export default LightningSettings;

View File

@ -1,19 +1,12 @@
/* global alert */
import React, { useCallback, useEffect, useState } from 'react';
import { ScrollView, TouchableWithoutFeedback, StyleSheet, Linking, View, TextInput } from 'react-native';
import {
BlueLoading,
BlueText,
BlueSpacing20,
BlueListItem,
BlueNavigationStyle,
BlueCard,
BlueButton,
BlueCopyToClipboardButton,
} from '../../BlueComponents';
import { useTheme } from '@react-navigation/native';
import loc from '../../loc';
import { Button } from 'react-native-elements';
import navigationStyle from '../../components/navigationStyle';
import { BlueButton, BlueCard, BlueCopyToClipboardButton, BlueListItem, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import Notifications from '../../blue_modules/notifications';
@ -163,8 +156,7 @@ const NotificationSettings = () => {
);
};
NotificationSettings.navigationOptions = () => ({
...BlueNavigationStyle(),
NotificationSettings.navigationOptions = navigationStyle({
title: loc.settings.notifications,
});

View File

@ -1,7 +1,8 @@
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueCard, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import { useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
import loc from '../../loc';
const ReleaseNotes = () => {
@ -25,8 +26,7 @@ const ReleaseNotes = () => {
);
};
ReleaseNotes.navigationOptions = () => ({
...BlueNavigationStyle(),
ReleaseNotes.navigationOptions = navigationStyle({
title: loc.settings.about_release_notes,
});

View File

@ -1,7 +1,8 @@
import React from 'react';
import { ScrollView, TouchableOpacity, StyleSheet, StatusBar } from 'react-native';
import { BlueListItem, BlueNavigationStyle, BlueHeaderDefaultSubHooks } from '../../BlueComponents';
import { useNavigation } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { BlueListItem, BlueHeaderDefaultSubHooks } from '../../BlueComponents';
import loc from '../../loc';
const styles = StyleSheet.create({
@ -47,7 +48,6 @@ const Settings = () => {
};
export default Settings;
Settings.navigationOptions = () => ({
...BlueNavigationStyle(),
Settings.navigationOptions = navigationStyle({
headerTitle: '',
});

View File

@ -5,16 +5,9 @@ import { ActivityIndicator, View, TextInput, TouchableOpacity, Linking, ScrollVi
import Clipboard from '@react-native-community/clipboard';
import { Text } from 'react-native-elements';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import {
BlueSpacing20,
BlueReplaceFeeSuggestions,
BlueButton,
SafeBlueArea,
BlueCard,
BlueText,
BlueSpacing,
BlueNavigationStyle,
} from '../../BlueComponents';
import { BlueButton, BlueCard, BlueReplaceFeeSuggestions, BlueSpacing, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueCurrentTheme } from '../../components/themes';
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
import loc from '../../loc';
@ -117,6 +110,7 @@ export default class CPFP extends Component {
onSuccessBroadcast() {
this.context.txMetadata[this.state.newTxid] = { memo: 'Child pays for parent (CPFP)' };
Notifications.majorTomToGroundControl([], [], [this.state.newTxid]);
this.context.sleep(4000).then(() => this.context.fetchAndSaveWalletTransactions(this.state.wallet.getID()));
this.props.navigation.navigate('Success', { onDonePressed: () => this.props.navigation.popToTop(), amount: undefined });
}
@ -249,7 +243,6 @@ CPFP.propTypes = {
}),
}),
};
CPFP.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
CPFP.navigationOptions = navigationStyle({
title: loc.transactions.cpfp_title,
});

View File

@ -2,7 +2,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ActivityIndicator, View, ScrollView, StyleSheet } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueSpacing20, SafeBlueArea, BlueText } from '../../BlueComponents';
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
import CPFP from './CPFP';
import loc from '../../loc';
@ -65,6 +66,7 @@ export default class RBFBumpFee extends CPFP {
if (this.context.txMetadata[this.state.txid]) {
this.context.txMetadata[this.state.newTxid] = this.context.txMetadata[this.state.txid];
}
this.context.sleep(4000).then(() => this.context.fetchAndSaveWalletTransactions(this.state.wallet.getID()));
this.props.navigation.navigate('Success', { onDonePressed: () => this.props.navigation.popToTop(), amount: undefined });
}
@ -115,8 +117,6 @@ RBFBumpFee.propTypes = {
}),
}),
};
RBFBumpFee.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
RBFBumpFee.navigationOptions = navigationStyle({
title: loc.transactions.rbf_title,
});

View File

@ -1,8 +1,9 @@
/* global alert */
import React from 'react';
import { ActivityIndicator, View, StyleSheet, ScrollView } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { ActivityIndicator, View, StyleSheet, ScrollView } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
import CPFP from './CPFP';
import loc from '../../loc';
@ -75,6 +76,7 @@ export default class RBFCancel extends CPFP {
} else {
this.context.txMetadata[this.state.newTxid].memo = 'Cancelled transaction';
}
this.context.sleep(4000).then(() => this.context.fetchAndSaveWalletTransactions(this.state.wallet.getID()));
this.props.navigation.navigate('Success', { onDonePressed: () => this.props.navigation.popToTop(), amount: undefined });
}
@ -126,7 +128,6 @@ RBFCancel.propTypes = {
}),
};
RBFCancel.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
RBFCancel.navigationOptions = navigationStyle({
title: loc.transactions.cancel_title,
});

View File

@ -1,23 +1,231 @@
/* global alert */
import React, { Component } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { View, ScrollView, TouchableOpacity, Text, TextInput, Linking, StatusBar, StyleSheet, Keyboard } from 'react-native';
import {
SafeBlueArea,
BlueCard,
BlueText,
BlueLoading,
BlueSpacing20,
BlueCopyToClipboardButton,
BlueNavigationStyle,
} from '../../BlueComponents';
import HandoffSettings from '../../class/handoff';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import Handoff from 'react-native-handoff';
import PropTypes from 'prop-types';
import { BlueCard, BlueCopyToClipboardButton, BlueLoading, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import HandoffSettings from '../../class/handoff';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const dayjs = require('dayjs');
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function arrDiff(a1, a2) {
const ret = [];
for (const v of a2) {
if (a1.indexOf(v) === -1) {
ret.push(v);
}
}
return ret;
}
const TransactionsDetails = () => {
const { setOptions } = useNavigation();
const { hash } = useRoute().params;
const { saveToDisk, txMetadata, wallets, getTransactions } = useContext(BlueStorageContext);
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [from, setFrom] = useState();
const [to, setTo] = useState();
const [isLoading, setIsLoading] = useState(true);
const [tx, setTX] = useState();
const [memo, setMemo] = useState();
const { colors } = useTheme();
const stylesHooks = StyleSheet.create({
rowCaption: {
color: colors.foregroundColor,
},
txId: {
color: colors.foregroundColor,
},
txLink: {
color: colors.alternativeTextColor2,
},
saveText: {
color: colors.alternativeTextColor2,
},
memoTextInput: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
});
useEffect(() => {
setOptions({
headerRight: () => (
<TouchableOpacity disabled={isLoading} style={styles.save} onPress={handleOnSaveButtonTapped}>
<Text style={stylesHooks.saveText}>{loc.wallets.details_save}</Text>
</TouchableOpacity>
),
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
backgroundColor: colors.customHeader,
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors, isLoading, memo]);
useEffect(() => {
let foundTx = {};
let from = [];
let to = [];
for (const tx of getTransactions()) {
if (tx.hash === hash) {
foundTx = tx;
for (const input of foundTx.inputs) {
from = from.concat(input.addresses);
}
for (const output of foundTx.outputs) {
if (output.addresses) to = to.concat(output.addresses);
if (output.scriptPubKey && output.scriptPubKey.addresses) to = to.concat(output.scriptPubKey.addresses);
}
}
}
for (const w of wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
}
}
}
if (txMetadata[foundTx.hash]) {
if (txMetadata[foundTx.hash].memo) {
setMemo(txMetadata[foundTx.hash].memo);
}
}
setTX(foundTx);
setFrom(from);
setTo(to);
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash]);
useEffect(() => {
HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleOnSaveButtonTapped = () => {
Keyboard.dismiss();
txMetadata[tx.hash] = { memo };
saveToDisk().then(_success => alert(loc.transactions.transaction_note_saved));
};
const handleOnOpenTransactionOnBlockExporerTapped = () => {
const url = `https://blockstream.info/tx/${tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
};
if (isLoading || !tx) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
{isHandOffUseEnabled && (
<Handoff title={`Bitcoin Transaction ${tx.hash}`} type="io.bluewallet.bluewallet" url={`https://blockstream.info/tx/${tx.hash}`} />
)}
<StatusBar barStyle="default" />
<ScrollView style={styles.scroll}>
<BlueCard>
<View>
<TextInput
placeholder={loc.send.details_note_placeholder}
value={memo}
placeholderTextColor="#81868e"
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
onChangeText={setMemo}
/>
<BlueSpacing20 />
</View>
{from && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{from.filter(onlyUnique).join(', ')}</BlueText>
</>
)}
{to && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{arrDiff(from, to.filter(onlyUnique)).join(', ')}</BlueText>
</>
)}
{tx.fee && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.send.create_fee}</BlueText>
<BlueText style={styles.rowValue}>{tx.fee + ' sats'}</BlueText>
</>
)}
{tx.hash && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.txId, stylesHooks.txId]}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={tx.hash} />
</View>
<BlueText style={styles.txHash}>{tx.hash}</BlueText>
<TouchableOpacity onPress={handleOnOpenTransactionOnBlockExporerTapped}>
<BlueText style={[styles.txLink, stylesHooks.txLink]}>{loc.transactions.details_show_in_block_explorer}</BlueText>
</TouchableOpacity>
</>
)}
{tx.received && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_received}</BlueText>
<BlueText style={styles.rowValue}>{dayjs(tx.received).format('MM/DD/YYYY h:mm A')}</BlueText>
</>
)}
{tx.block_height > 0 && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_block}</BlueText>
<BlueText style={styles.rowValue}>{tx.block_height}</BlueText>
</>
)}
{tx.inputs && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_inputs}</BlueText>
<BlueText style={styles.rowValue}>{tx.inputs.length}</BlueText>
</>
)}
{tx.outputs.length > 0 && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_outputs}</BlueText>
<BlueText style={styles.rowValue}>{tx.outputs.length}</BlueText>
</>
)}
</BlueCard>
</ScrollView>
</SafeBlueArea>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
@ -35,7 +243,6 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '500',
marginBottom: 4,
color: BlueCurrentTheme.colors.foregroundColor,
},
rowValue: {
marginBottom: 26,
@ -44,7 +251,6 @@ const styles = StyleSheet.create({
txId: {
fontSize: 16,
fontWeight: '500',
color: BlueCurrentTheme.colors.foregroundColor,
},
txHash: {
marginBottom: 8,
@ -52,23 +258,16 @@ const styles = StyleSheet.create({
},
txLink: {
marginBottom: 26,
color: BlueCurrentTheme.colors.alternativeTextColor2,
},
save: {
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'center',
},
saveText: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
},
memoTextInput: {
flexDirection: 'row',
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
minHeight: 44,
height: 44,
alignItems: 'center',
@ -79,221 +278,8 @@ const styles = StyleSheet.create({
},
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
export default TransactionsDetails;
function arrDiff(a1, a2) {
const ret = [];
for (const v of a2) {
if (a1.indexOf(v) === -1) {
ret.push(v);
}
}
return ret;
}
export default class TransactionsDetails extends Component {
static contextType = BlueStorageContext;
constructor(props, context) {
super(props);
const hash = props.route.params.hash;
let foundTx = {};
let from = [];
let to = [];
for (const tx of context.getTransactions()) {
if (tx.hash === hash) {
foundTx = tx;
for (const input of foundTx.inputs) {
from = from.concat(input.addresses);
}
for (const output of foundTx.outputs) {
if (output.addresses) to = to.concat(output.addresses);
if (output.scriptPubKey && output.scriptPubKey.addresses) to = to.concat(output.scriptPubKey.addresses);
}
}
}
let wallet = false;
for (const w of context.wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
wallet = w;
}
}
}
let memo = '';
if (context.txMetadata[foundTx.hash]) {
if (context.txMetadata[foundTx.hash].memo) {
memo = context.txMetadata[foundTx.hash].memo;
}
}
this.state = {
isLoading: true,
tx: foundTx,
from,
to,
wallet,
isHandOffUseEnabled: false,
memo,
};
}
async componentDidMount() {
console.log('transactions/details - componentDidMount');
this.props.navigation.setParams({ handleOnSaveButtonTapped: this.handleOnSaveButtonTapped });
const isHandOffUseEnabled = await HandoffSettings.isHandoffUseEnabled();
this.setState({
isLoading: false,
isHandOffUseEnabled,
});
}
handleOnSaveButtonTapped = () => {
Keyboard.dismiss();
this.context.txMetadata[this.state.tx.hash] = { memo: this.state.memo };
this.context.saveToDisk().then(_success => alert('Transaction note has been successfully saved.'));
};
handleOnMemoChangeText = value => {
this.setState({ memo: value });
};
render() {
if (this.state.isLoading || !('tx' in this.state)) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
{this.state.isHandOffUseEnabled && (
<Handoff
title={`Bitcoin Transaction ${this.state.tx.hash}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/tx/${this.state.tx.hash}`}
/>
)}
<StatusBar barStyle="default" />
<ScrollView style={styles.scroll}>
<BlueCard>
<View>
<TextInput
placeholder={loc.send.details_note_placeholder}
value={this.state.memo}
placeholderTextColor="#81868e"
style={styles.memoTextInput}
onChangeText={this.handleOnMemoChangeText}
/>
<BlueSpacing20 />
</View>
{'from' in this.state && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.rowCaption}>{loc.transactions.details_from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{this.state.from.filter(onlyUnique).join(', ')}</BlueText>
</>
)}
{'to' in this.state && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.rowCaption}>{loc.transactions.details_to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{arrDiff(this.state.from, this.state.to.filter(onlyUnique)).join(', ')}</BlueText>
</>
)}
{'fee' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.send.create_fee}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.fee + ' sats'}</BlueText>
</>
)}
{'hash' in this.state.tx && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.txId}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.tx.hash} />
</View>
<BlueText style={styles.txHash}>{this.state.tx.hash}</BlueText>
<TouchableOpacity
onPress={() => {
const url = `https://blockstream.info/tx/${this.state.tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}}
>
<BlueText style={styles.txLink}>{loc.transactions.details_show_in_block_explorer}</BlueText>
</TouchableOpacity>
</>
)}
{'received' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_received}</BlueText>
<BlueText style={styles.rowValue}>{dayjs(this.state.tx.received).format('MM/DD/YYYY h:mm A')}</BlueText>
</>
)}
{'block_height' in this.state.tx && this.state.tx.block_height > 0 && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_block}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.block_height}</BlueText>
</>
)}
{'inputs' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_inputs}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.inputs.length}</BlueText>
</>
)}
{'outputs' in this.state.tx && this.state.tx.outputs.length > 0 && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_outputs}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.outputs.length}</BlueText>
</>
)}
</BlueCard>
</ScrollView>
</SafeBlueArea>
);
}
}
TransactionsDetails.propTypes = {
route: PropTypes.shape({
name: PropTypes.string,
params: PropTypes.shape({
hash: PropTypes.string,
}),
}),
navigation: PropTypes.shape({
setParams: PropTypes.func,
}),
};
TransactionsDetails.navigationOptions = ({ navigation, route }) => ({
...BlueNavigationStyle(),
TransactionsDetails.navigationOptions = navigationStyle({
title: loc.transactions.details_title,
headerStyle: {
...BlueNavigationStyle().headerStyle,
backgroundColor: BlueCurrentTheme.colors.customHeader,
},
headerRight: () => (
<TouchableOpacity disabled={route.params.isLoading === true} style={styles.save} onPress={route.params.handleOnSaveButtonTapped}>
<Text style={styles.saveText}>{loc.wallets.details_save}</Text>
</TouchableOpacity>
),
});

View File

@ -1,32 +1,362 @@
import React, { Component } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { View, ActivityIndicator, Text, TouchableOpacity, StyleSheet, StatusBar } from 'react-native';
import {
BlueButton,
SafeBlueArea,
BlueTransactionOutgoingIcon,
BlueTransactionPendingIcon,
BlueTransactionIncomingIcon,
BlueCard,
BlueText,
BlueLoading,
BlueSpacing20,
BlueNavigationStyle,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import { HDSegwitBech32Transaction } from '../../class';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { Icon } from 'react-native-elements';
import Handoff from 'react-native-handoff';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import {
BlueButton,
BlueCard,
BlueLoading,
BlueSpacing10,
BlueSpacing20,
BlueText,
BlueTransactionIncomingIcon,
BlueTransactionOutgoingIcon,
BlueTransactionPendingIcon,
SafeBlueArea,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { HDSegwitBech32Transaction } from '../../class';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import HandoffSettings from '../../class/handoff';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const buttonStatus = Object.freeze({
possible: 1,
unknown: 2,
notPossible: 3,
});
const TransactionsStatus = () => {
const { setSelectedWallet, wallets, txMetadata, getTransactions } = useContext(BlueStorageContext);
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const { hash } = useRoute().params;
const { navigate, setOptions } = useNavigation();
const { colors } = useTheme();
const wallet = useRef();
const [isCPFPPossible, setIsCPFPPossible] = useState();
const [isRBFBumpFeePossible, setIsRBFBumpFeePossible] = useState();
const [isRBFCancelPossible, setIsRBFCancelPossible] = useState();
const [tx, setTX] = useState();
const [isLoading, setIsLoading] = useState(true);
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
value: {
color: colors.alternativeTextColor2,
},
valueUnit: {
color: colors.alternativeTextColor2,
},
iconRoot: {
backgroundColor: colors.success,
},
confirmations: {
backgroundColor: colors.lightButton,
},
});
useEffect(() => {
setIsCPFPPossible(buttonStatus.unknown);
setIsRBFBumpFeePossible(buttonStatus.unknown);
setIsRBFCancelPossible(buttonStatus.unknown);
}, []);
useEffect(() => {
setOptions({
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
backgroundColor: colors.customHeader,
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors]);
useEffect(() => {
for (const w of wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
wallet.current = w;
break;
}
}
}
for (const tx of getTransactions()) {
if (tx.hash === hash) {
setTX(tx);
break;
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash]);
const initialState = async () => {
try {
await checkPossibilityOfCPFP();
await checkPossibilityOfRBFBumpFee();
await checkPossibilityOfRBFCancel();
} catch (e) {
setIsCPFPPossible(buttonStatus.notPossible);
setIsRBFBumpFeePossible(buttonStatus.notPossible);
setIsRBFCancelPossible(buttonStatus.notPossible);
}
setIsLoading(false);
};
useEffect(() => {
initialState();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tx]);
useEffect(() => {
if (wallet) {
setSelectedWallet(wallet.current.getID());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet]);
useEffect(() => {
console.log('transactions/details - useEffect');
HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
}, []);
const checkPossibilityOfCPFP = async () => {
if (!wallet.current.allowRBF()) {
return setIsCPFPPossible(buttonStatus.notPossible);
}
const cpfbTx = new HDSegwitBech32Transaction(null, tx.hash, wallet.current);
if ((await cpfbTx.isToUsTransaction()) && (await cpfbTx.getRemoteConfirmationsNum()) === 0) {
return setIsCPFPPossible(buttonStatus.possible);
} else {
return setIsCPFPPossible(buttonStatus.notPossible);
}
};
const checkPossibilityOfRBFBumpFee = async () => {
if (!wallet.current.allowRBF()) {
return setIsRBFBumpFeePossible(buttonStatus.notPossible);
}
const rbfTx = new HDSegwitBech32Transaction(null, tx.hash, wallet.current);
if (
(await rbfTx.isOurTransaction()) &&
(await rbfTx.getRemoteConfirmationsNum()) === 0 &&
(await rbfTx.isSequenceReplaceable()) &&
(await rbfTx.canBumpTx())
) {
return setIsRBFBumpFeePossible(buttonStatus.possible);
} else {
return setIsRBFBumpFeePossible(buttonStatus.notPossible);
}
};
const checkPossibilityOfRBFCancel = async () => {
if (!wallet.current.allowRBF()) {
return setIsRBFCancelPossible(buttonStatus.notPossible);
}
const rbfTx = new HDSegwitBech32Transaction(null, tx.hash, wallet.current);
if (
(await rbfTx.isOurTransaction()) &&
(await rbfTx.getRemoteConfirmationsNum()) === 0 &&
(await rbfTx.isSequenceReplaceable()) &&
(await rbfTx.canCancelTx())
) {
return setIsRBFCancelPossible(buttonStatus.possible);
} else {
return setIsRBFCancelPossible(buttonStatus.notPossible);
}
};
const navigateToRBFBumpFee = () => {
navigate('RBFBumpFee', {
txid: tx.hash,
wallet: wallet.current,
});
};
const navigateToRBFCancel = () => {
navigate('RBFCancel', {
txid: tx.hash,
wallet: wallet.current,
});
};
const navigateToCPFP = () => {
navigate('CPFP', {
txid: tx.hash,
wallet: wallet.current,
});
};
const navigateToTransactionDetials = () => {
navigate('TransactionDetails', { hash: tx.hash });
};
const renderCPFP = () => {
if (isCPFPPossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
<BlueSpacing20 />
</>
);
} else if (isCPFPPossible === buttonStatus.possible) {
return (
<>
<BlueButton onPress={navigateToCPFP} title={loc.transactions.status_bump} />
<BlueSpacing10 />
</>
);
}
};
const renderRBFCancel = () => {
if (isRBFCancelPossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
</>
);
} else if (isRBFCancelPossible === buttonStatus.possible) {
return (
<>
<TouchableOpacity style={styles.cancel}>
<Text onPress={navigateToRBFCancel} style={styles.cancelText}>
{loc.transactions.status_cancel}
</Text>
</TouchableOpacity>
<BlueSpacing10 />
</>
);
}
};
const renderRBFBumpFee = () => {
if (isRBFBumpFeePossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
<BlueSpacing20 />
</>
);
} else if (isRBFBumpFeePossible === buttonStatus.possible) {
return (
<>
<BlueButton onPress={navigateToRBFBumpFee} title={loc.transactions.status_bump} />
<BlueSpacing10 />
</>
);
}
};
const renderTXMetadata = () => {
if (txMetadata[tx.hash]) {
if (txMetadata[tx.hash].memo) {
return (
<View style={styles.memo}>
<Text style={styles.memoText}>{txMetadata[tx.hash].memo}</Text>
<BlueSpacing20 />
</View>
);
}
}
};
if (isLoading || !tx) {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={[styles.root, stylesHook.root]}>
<BlueLoading />
</SafeBlueArea>
);
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={[styles.root, stylesHook.root]}>
{isHandOffUseEnabled && (
<Handoff title={`Bitcoin Transaction ${tx.hash}`} type="io.bluewallet.bluewallet" url={`https://blockstream.info/tx/${tx.hash}`} />
)}
<StatusBar barStyle="default" />
<View style={styles.container}>
<BlueCard>
<View style={styles.center}>
<Text style={[styles.value, stylesHook.value]}>
{formatBalanceWithoutSuffix(tx.value, wallet.current.preferredBalanceUnit, true)}{' '}
{wallet.current.preferredBalanceUnit !== BitcoinUnit.LOCAL_CURRENCY && (
<Text style={[styles.valueUnit, stylesHook.valueUnit]}>{wallet.current.preferredBalanceUnit}</Text>
)}
</Text>
</View>
{renderTXMetadata()}
<View style={[styles.iconRoot, stylesHook.iconRoot]}>
<View>
<Icon name="check" size={50} type="font-awesome" color={colors.successCheck} />
</View>
<View style={[styles.iconWrap, styles.margin]}>
{(() => {
if (!tx.confirmations) {
return (
<View style={styles.icon}>
<BlueTransactionPendingIcon />
</View>
);
} else if (tx.value < 0) {
return (
<View style={styles.icon}>
<BlueTransactionOutgoingIcon />
</View>
);
} else {
return (
<View style={styles.icon}>
<BlueTransactionIncomingIcon />
</View>
);
}
})()}
</View>
</View>
{tx.fee && (
<View style={styles.fee}>
<BlueText style={styles.feeText}>
{loc.send.create_fee.toLowerCase()} {formatBalanceWithoutSuffix(tx.fee, wallet.current.preferredBalanceUnit, true)}{' '}
{wallet.current.preferredBalanceUnit !== BitcoinUnit.LOCAL_CURRENCY && wallet.current.preferredBalanceUnit}
</BlueText>
</View>
)}
<View style={[styles.confirmations, stylesHook.confirmations]}>
<Text style={styles.confirmationsText}>{tx.confirmations > 6 ? '6+' : tx.confirmations} confirmations</Text>
</View>
</BlueCard>
<View style={styles.actions}>
{renderCPFP()}
{renderRBFBumpFee()}
{renderRBFCancel()}
<TouchableOpacity style={styles.details} onPress={navigateToTransactionDetials}>
<Text style={styles.detailsText}>{loc.send.create_details.toLowerCase()}</Text>
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
</View>
</View>
</SafeBlueArea>
);
};
export default TransactionsStatus;
const styles = StyleSheet.create({
root: {
flex: 1,
@ -39,12 +369,10 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
value: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 36,
fontWeight: '600',
},
valueUnit: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 16,
fontWeight: '600',
},
@ -57,7 +385,6 @@ const styles = StyleSheet.create({
fontSize: 14,
},
iconRoot: {
backgroundColor: BlueCurrentTheme.colors.success,
width: 120,
height: 120,
borderRadius: 60,
@ -93,7 +420,6 @@ const styles = StyleSheet.create({
},
confirmations: {
borderRadius: 11,
backgroundColor: BlueCurrentTheme.colors.lightButton,
width: 109,
height: 21,
alignSelf: 'center',
@ -130,321 +456,6 @@ const styles = StyleSheet.create({
},
});
export default class TransactionsStatus extends Component {
static contextType = BlueStorageContext;
constructor(props, context) {
super(props);
const hash = props.route.params.hash;
let foundTx = {};
let from = [];
let to = [];
for (const tx of context.getTransactions()) {
if (tx.hash === hash) {
foundTx = tx;
for (const input of foundTx.inputs) {
from = from.concat(input.addresses);
}
for (const output of foundTx.outputs) {
if (output.addresses) to = to.concat(output.addresses);
if (output.scriptPubKey && output.scriptPubKey.addresses) to = to.concat(output.scriptPubKey.addresses);
}
}
}
let wallet = false;
for (const w of context.wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
wallet = w;
break;
}
}
}
this.state = {
isLoading: true,
tx: foundTx,
from,
to,
wallet,
isCPFPpossible: buttonStatus.unknown,
isRBFBumpFeePossible: buttonStatus.unknown,
isRBFCancelPossible: buttonStatus.unknown,
isHandOffUseEnabled: false,
};
}
async componentDidMount() {
console.log('transactions/details - componentDidMount');
const isHandOffUseEnabled = await HandoffSettings.isHandoffUseEnabled();
this.setState({
isLoading: false,
isHandOffUseEnabled,
});
try {
await this.checkPossibilityOfCPFP();
await this.checkPossibilityOfRBFBumpFee();
await this.checkPossibilityOfRBFCancel();
} catch (_) {
this.setState({
isCPFPpossible: buttonStatus.notPossible,
isRBFBumpFeePossible: buttonStatus.notPossible,
isRBFCancelPossible: buttonStatus.notPossible,
});
}
this.context.setSelectedWallet(this.state.wallet.getID());
}
async checkPossibilityOfCPFP() {
if (!this.state.wallet.allowRBF()) {
return this.setState({ isCPFPpossible: buttonStatus.notPossible });
}
const tx = new HDSegwitBech32Transaction(null, this.state.tx.hash, this.state.wallet);
if ((await tx.isToUsTransaction()) && (await tx.getRemoteConfirmationsNum()) === 0) {
return this.setState({ isCPFPpossible: buttonStatus.possible });
} else {
return this.setState({ isCPFPpossible: buttonStatus.notPossible });
}
}
async checkPossibilityOfRBFBumpFee() {
if (!this.state.wallet.allowRBF()) {
return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible });
}
const tx = new HDSegwitBech32Transaction(null, this.state.tx.hash, this.state.wallet);
if (
(await tx.isOurTransaction()) &&
(await tx.getRemoteConfirmationsNum()) === 0 &&
(await tx.isSequenceReplaceable()) &&
(await tx.canBumpTx())
) {
return this.setState({ isRBFBumpFeePossible: buttonStatus.possible });
} else {
return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible });
}
}
async checkPossibilityOfRBFCancel() {
if (!this.state.wallet.allowRBF()) {
return this.setState({ isRBFCancelPossible: buttonStatus.notPossible });
}
const tx = new HDSegwitBech32Transaction(null, this.state.tx.hash, this.state.wallet);
if (
(await tx.isOurTransaction()) &&
(await tx.getRemoteConfirmationsNum()) === 0 &&
(await tx.isSequenceReplaceable()) &&
(await tx.canCancelTx())
) {
return this.setState({ isRBFCancelPossible: buttonStatus.possible });
} else {
return this.setState({ isRBFCancelPossible: buttonStatus.notPossible });
}
}
render() {
if (this.state.isLoading || !('tx' in this.state)) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
{this.state.isHandOffUseEnabled && (
<Handoff
title={`Bitcoin Transaction ${this.state.tx.hash}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/tx/${this.state.tx.hash}`}
/>
)}
<StatusBar barStyle="default" />
<View style={styles.container}>
<BlueCard>
<View style={styles.center}>
<Text style={styles.value}>
{formatBalanceWithoutSuffix(this.state.tx.value, this.state.wallet.preferredBalanceUnit, true)}{' '}
{this.state.wallet.preferredBalanceUnit !== BitcoinUnit.LOCAL_CURRENCY && (
<Text style={styles.valueUnit}>{this.state.wallet.preferredBalanceUnit}</Text>
)}
</Text>
</View>
{(() => {
if (this.context.txMetadata[this.state.tx.hash]) {
if (this.context.txMetadata[this.state.tx.hash].memo) {
return (
<View style={styles.memo}>
<Text style={styles.memoText}>{this.context.txMetadata[this.state.tx.hash].memo}</Text>
<BlueSpacing20 />
</View>
);
}
}
})()}
<View style={styles.iconRoot}>
<View>
<Icon name="check" size={50} type="font-awesome" color={BlueCurrentTheme.colors.successCheck} />
</View>
<View style={[styles.iconWrap, styles.margin]}>
{(() => {
if (!this.state.tx.confirmations) {
return (
<View style={styles.icon}>
<BlueTransactionPendingIcon />
</View>
);
} else if (this.state.tx.value < 0) {
return (
<View style={styles.icon}>
<BlueTransactionOutgoingIcon />
</View>
);
} else {
return (
<View style={styles.icon}>
<BlueTransactionIncomingIcon />
</View>
);
}
})()}
</View>
</View>
{'fee' in this.state.tx && (
<View style={styles.fee}>
<BlueText style={styles.feeText}>
{loc.send.create_fee.toLowerCase()}{' '}
{formatBalanceWithoutSuffix(this.state.tx.fee, this.state.wallet.preferredBalanceUnit, true)}{' '}
{this.state.wallet.preferredBalanceUnit !== BitcoinUnit.LOCAL_CURRENCY && this.state.wallet.preferredBalanceUnit}
</BlueText>
</View>
)}
<View style={styles.confirmations}>
<Text style={styles.confirmationsText}>
{this.state.tx.confirmations > 6 ? '6+' : this.state.tx.confirmations} confirmations
</Text>
</View>
</BlueCard>
<View style={styles.actions}>
{(() => {
if (this.state.isCPFPpossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
<BlueSpacing20 />
</>
);
} else if (this.state.isCPFPpossible === buttonStatus.possible) {
return (
<>
<BlueButton
onPress={() =>
this.props.navigation.navigate('CPFP', {
txid: this.state.tx.hash,
wallet: this.state.wallet,
})
}
title={loc.transactions.status_bump}
/>
<BlueSpacing20 />
</>
);
}
})()}
{(() => {
if (this.state.isRBFBumpFeePossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
<BlueSpacing20 />
</>
);
} else if (this.state.isRBFBumpFeePossible === buttonStatus.possible) {
return (
<>
<BlueButton
onPress={() =>
this.props.navigation.navigate('RBFBumpFee', {
txid: this.state.tx.hash,
wallet: this.state.wallet,
})
}
title={loc.transactions.status_bump}
/>
</>
);
}
})()}
{(() => {
if (this.state.isRBFCancelPossible === buttonStatus.unknown) {
return (
<>
<ActivityIndicator />
</>
);
} else if (this.state.isRBFCancelPossible === buttonStatus.possible) {
return (
<>
<TouchableOpacity style={styles.cancel}>
<Text
onPress={() =>
this.props.navigation.navigate('RBFCancel', {
txid: this.state.tx.hash,
wallet: this.state.wallet,
})
}
style={styles.cancelText}
>
{loc.transactions.status_cancel}
</Text>
</TouchableOpacity>
</>
);
}
})()}
<TouchableOpacity
style={styles.details}
onPress={() => this.props.navigation.navigate('TransactionDetails', { hash: this.state.tx.hash })}
>
<Text style={styles.detailsText}>{loc.send.create_details.toLowerCase()}</Text>
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
</View>
</View>
</SafeBlueArea>
);
}
}
TransactionsStatus.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
hash: PropTypes.string,
}),
}),
}),
route: PropTypes.shape({
params: PropTypes.object,
}),
};
TransactionsStatus.navigationOptions = () => ({
...BlueNavigationStyle(),
TransactionsStatus.navigationOptions = navigationStyle({
title: '',
headerStyle: {
...BlueNavigationStyle().headerStyle,
backgroundColor: BlueCurrentTheme.colors.customHeader,
},
});

View File

@ -21,10 +21,10 @@ import {
VaultButton,
BlueFormLabel,
BlueButton,
BlueNavigationStyle,
BlueButtonLinkHook,
BlueButtonLink,
BlueSpacing20,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { HDSegwitBech32Wallet, SegwitP2SHWallet, HDSegwitP2SHWallet, LightningCustodianWallet, AppStorage } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { useTheme, useNavigation } from '@react-navigation/native';
@ -189,7 +189,7 @@ const WalletsAdd = () => {
A(A.ENUM.CREATED_WALLET);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
navigate('PleaseBackupLNDHub', {
wallet,
walletID: wallet.getID(),
});
};
@ -310,7 +310,7 @@ const WalletsAdd = () => {
}
})()}
{isAdvancedOptionsEnabled && selectedWalletType === ButtonSelected.ONCHAIN && !isLoading && (
<BlueButtonLinkHook style={styles.import} title={entropyButtonText} onPress={navigateToEntropy} />
<BlueButtonLink style={styles.import} title={entropyButtonText} onPress={navigateToEntropy} />
)}
<BlueSpacing20 />
<View style={styles.createButton}>
@ -321,7 +321,7 @@ const WalletsAdd = () => {
)}
</View>
{!isLoading && (
<BlueButtonLinkHook
<BlueButtonLink
testID="ImportWallet"
style={styles.import}
title={loc.wallets.add_import_wallet}
@ -334,8 +334,8 @@ const WalletsAdd = () => {
);
};
WalletsAdd.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
WalletsAdd.navigationOptions = navigationStyle({
closeButton: true,
headerTitle: loc.wallets.add_title,
headerLeft: null,
});

View File

@ -5,7 +5,8 @@ import { Icon } from 'react-native-elements';
import { useNavigation, useTheme } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { BlueButton, BlueListItem, BlueNavigationStyle, BlueSpacing20 } from '../../BlueComponents';
import { BlueButton, BlueListItem, BlueSpacing20 } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import BottomModal from '../../components/BottomModal';
import { MultisigHDWallet } from '../../class';
import loc from '../../loc';
@ -344,8 +345,7 @@ WalletsAddMultisig.getCurrentFormatReadable = f => {
}
};
WalletsAddMultisig.navigationOptions = () => ({
...BlueNavigationStyle(),
WalletsAddMultisig.navigationOptions = navigationStyle({
headerTitle: null,
});

View File

@ -13,35 +13,36 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import { Icon } from 'react-native-elements';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { getSystemName } from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker';
import QRCode from 'react-native-qrcode-svg';
import Clipboard from '@react-native-community/clipboard';
import showPopupMenu from 'react-native-popup-menu-android';
import ToolTip from 'react-native-tooltip';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import {
BlueButton,
BlueButtonLinkHook,
BlueButtonLink,
BlueFormMultiInput,
BlueLoadingHook,
BlueNavigationStyle,
BlueLoading,
BlueSpacing10,
BlueSpacing20,
BlueSpacing40,
BlueTextCentered,
} from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import navigationStyle from '../../components/navigationStyle';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import loc from '../../loc';
import { getSystemName } from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker';
import ScanQRCode from '../send/ScanQRCode';
import QRCode from 'react-native-qrcode-svg';
import { SquareButton } from '../../components/SquareButton';
import BottomModal from '../../components/BottomModal';
import MultipleStepsListItem, {
MultipleStepsListItemButtohType,
MultipleStepsListItemDashType,
} from '../../components/MultipleStepsListItem';
import Clipboard from '@react-native-community/clipboard';
import showPopupMenu from 'react-native-popup-menu-android';
import ToolTip from 'react-native-tooltip';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const prompt = require('../../blue_modules/prompt');
@ -290,6 +291,13 @@ const WalletsAddMultisigStep2 = () => {
if (cosignersCopy.length === n) setIsOnCreateButtonEnabled(true);
setIsProvideMnemonicsModalVisible(false);
setIsLoading(false);
setImportText('');
};
const isValidMnemonicSeed = mnemonicSeed => {
const hd = new HDSegwitBech32Wallet();
hd.setSecret(mnemonicSeed);
return hd.validateMnemonic();
};
const onBarScanned = ret => {
@ -298,6 +306,9 @@ const WalletsAddMultisigStep2 = () => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (isValidMnemonicSeed(ret.data)) {
setIsProvideMnemonicsModalVisible(true);
setImportText(ret.data);
} else {
let cosigner = new MultisigCosigner(ret.data);
if (!cosigner.isValid()) return alert(loc.multisig.invalid_cosigner);
@ -572,7 +583,7 @@ const WalletsAddMultisigStep2 = () => {
) : (
<BlueButton disabled={importText.trim().length === 0} title={loc.wallets.import_do_import} onPress={useMnemonicPhrase} />
)}
<BlueButtonLinkHook disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
<BlueButtonLink disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</KeyboardAvoidingView>
</BottomModal>
@ -620,7 +631,7 @@ const WalletsAddMultisigStep2 = () => {
);
};
const footer = isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<View style={styles.buttonBottom}>
<BlueButton title={loc.multisig.create} onPress={onCreate} disabled={!isOnCreateButtonEnabled} />
@ -754,8 +765,7 @@ const styles = StyleSheet.create({
qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' },
});
WalletsAddMultisigStep2.navigationOptions = () => ({
...BlueNavigationStyle(),
WalletsAddMultisigStep2.navigationOptions = navigationStyle({
headerTitle: null,
});

View File

@ -1,13 +1,16 @@
import React, { Component } from 'react';
import { StyleSheet, StatusBar, Linking, Platform } from 'react-native';
import { BlueNavigationStyle, BlueLoading, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { StyleSheet, StatusBar, Linking, Platform } from 'react-native';
import { WebView } from 'react-native-webview';
import { LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import InAppBrowser from 'react-native-inappbrowser-reborn';
import { BlueLoading, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import * as NavigationService from '../../NavigationService';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const currency = require('../../blue_modules/currency');
const styles = StyleSheet.create({
root: {
flex: 1,
@ -105,8 +108,8 @@ BuyBitcoin.propTypes = {
}),
};
BuyBitcoin.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
BuyBitcoin.navigationOptions = navigationStyle({
closeButton: true,
title: '',
headerLeft: null,
});

View File

@ -16,7 +16,8 @@ import {
StatusBar,
PermissionsAndroid,
} from 'react-native';
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoadingHook } from '../../BlueComponents';
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueText, BlueLoading } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import { HDLegacyBreadwalletWallet } from '../../class/wallets/hd-legacy-breadwallet-wallet';
import { HDLegacyP2PKHWallet } from '../../class/wallets/hd-legacy-p2pkh-wallet';
@ -368,7 +369,7 @@ const WalletDetails = () => {
return isLoading ? (
<View style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</View>
) : (
<SafeBlueArea style={styles.root}>
@ -525,8 +526,7 @@ const WalletDetails = () => {
);
};
WalletDetails.navigationOptions = () => ({
...BlueNavigationStyle(),
WalletDetails.navigationOptions = navigationStyle({
headerTitle: loc.wallets.details_title,
});

View File

@ -1,18 +1,16 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { StatusBar, View, TouchableOpacity, StyleSheet, Alert, useWindowDimensions } from 'react-native';
import { StatusBar, View, StyleSheet, Alert, useWindowDimensions } from 'react-native';
import { DrawerContentScrollView } from '@react-navigation/drawer';
import { BlueNavigationStyle, BlueHeaderDefaultMain } from '../../BlueComponents';
import WalletsCarousel from '../../components/WalletsCarousel';
import { Icon } from 'react-native-elements';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PropTypes from 'prop-types';
import { PlaceholderWallet } from '../../class';
import WalletImport from '../../class/wallet-import';
import * as NavigationService from '../../NavigationService';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import { useTheme } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { BlueHeaderDefaultMain } from '../../BlueComponents';
import WalletsCarousel from '../../components/WalletsCarousel';
import { PlaceholderWallet } from '../../class';
import WalletImport from '../../class/wallet-import';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const DrawerList = props => {
@ -131,6 +129,7 @@ const DrawerList = props => {
};
export default DrawerList;
const styles = StyleSheet.create({
contentContainerCustomStyle: {
paddingRight: 10,
@ -157,22 +156,3 @@ DrawerList.propTypes = {
params: PropTypes.object,
}),
};
DrawerList.navigationOptions = ({ navigation }) => {
return {
...BlueNavigationStyle(navigation, true),
title: '',
headerStyle: {
backgroundColor: BlueCurrentTheme.colors.customHeader,
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerRight: () => (
<TouchableOpacity testID="SettingsButton" style={styles.headerTouch} onPress={() => NavigationService.navigate('Settings')}>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueCurrentTheme.colors.foregroundColor} />
</TouchableOpacity>
),
};
};

View File

@ -1,12 +1,14 @@
import React, { useState, useCallback, useContext } from 'react';
import React, { useState, useCallback, useContext, useRef } from 'react';
import { useWindowDimensions, InteractionManager, ScrollView, ActivityIndicator, StatusBar, View, StyleSheet } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText, BlueCopyTextToClipboard, BlueCard } from '../../BlueComponents';
import { useTheme, useNavigation, useFocusEffect, useRoute } from '@react-navigation/native';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueCopyTextToClipboard, BlueCard } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Privacy from '../../Privacy';
import Biometric from '../../class/biometrics';
import { LegacyWallet, LightningCustodianWallet, SegwitBech32Wallet, SegwitP2SHWallet, WatchOnlyWallet } from '../../class';
import loc from '../../loc';
import { useTheme, useNavigation, useFocusEffect, useRoute } from '@react-navigation/native';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const styles = StyleSheet.create({
@ -38,7 +40,7 @@ const styles = StyleSheet.create({
const WalletExport = () => {
const { wallets, saveToDisk } = useContext(BlueStorageContext);
const { walletID } = useRoute().params;
const wallet = wallets.find(w => w.getID() === walletID);
const wallet = useRef(wallets.find(w => w.getID() === walletID));
const [isLoading, setIsLoading] = useState(true);
const { goBack } = useNavigation();
const { colors } = useTheme();
@ -55,6 +57,7 @@ const WalletExport = () => {
},
type: { ...styles.type, color: colors.foregroundColor },
secret: { ...styles.secret, color: colors.foregroundColor },
warning: { ...styles.secret, color: colors.failedColor },
};
useFocusEffect(
@ -76,14 +79,14 @@ const WalletExport = () => {
return () => {
task.cancel();
Privacy.disableBlur();
wallet.setUserHasSavedExport(true);
wallet.current.setUserHasSavedExport(true);
saveToDisk();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [goBack, wallet]),
}, [goBack, walletID]),
);
return isLoading ? (
return isLoading && wallet ? (
<View style={stylesHook.loading}>
<ActivityIndicator />
</View>
@ -92,14 +95,14 @@ const WalletExport = () => {
<StatusBar barStyle="light-content" />
<ScrollView contentContainerStyle={styles.scrollViewContent}>
<View>
<BlueText style={stylesHook.type}>{wallet.typeReadable}</BlueText>
<BlueText style={stylesHook.type}>{wallet.current.typeReadable}</BlueText>
</View>
{(() => {
if ([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type)) {
if ([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.current.type)) {
return (
<BlueCard>
<BlueText>{wallet.getAddress()}</BlueText>
<BlueText>{wallet.current.getAddress()}</BlueText>
</BlueCard>
);
}
@ -107,7 +110,7 @@ const WalletExport = () => {
<BlueSpacing20 />
<View style={styles.activeQrcode}>
<QRCode
value={wallet.getSecret()}
value={wallet.current.getSecret()}
logo={require('../../img/qr-code.png')}
size={height > width ? width - 40 : width / 2}
logoSize={70}
@ -117,19 +120,20 @@ const WalletExport = () => {
ecl="H"
/>
</View>
{wallet.type !== WatchOnlyWallet.type && <BlueText style={stylesHook.warning}>{loc.wallets.warning_do_not_disclose}</BlueText>}
<BlueSpacing20 />
{wallet.type === LightningCustodianWallet.type || wallet.type === WatchOnlyWallet.type ? (
<BlueCopyTextToClipboard text={wallet.getSecret()} />
{wallet.current.type === LightningCustodianWallet.type || wallet.current.type === WatchOnlyWallet.type ? (
<BlueCopyTextToClipboard text={wallet.current.getSecret()} />
) : (
<BlueText style={stylesHook.secret}>{wallet.getSecret()}</BlueText>
<BlueText style={stylesHook.secret}>{wallet.current.getSecret()}</BlueText>
)}
</ScrollView>
</SafeBlueArea>
);
};
WalletExport.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
WalletExport.navigationOptions = navigationStyle({
closeButton: true,
title: loc.wallets.export_title,
headerLeft: null,
});

View File

@ -1,11 +1,13 @@
import React, { useCallback, useContext, useState } from 'react';
import { ActivityIndicator, InteractionManager, ScrollView, StatusBar, StyleSheet, View } from 'react-native';
import { BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import Privacy from '../../Privacy';
import Biometric from '../../class/biometrics';
import loc from '../../loc';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { SquareButton } from '../../components/SquareButton';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const fs = require('../../blue_modules/fs');
@ -118,8 +120,8 @@ const styles = StyleSheet.create({
},
});
ExportMultisigCoordinationSetup.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
ExportMultisigCoordinationSetup.navigationOptions = navigationStyle({
closeButton: true,
title: loc.multisig.export_coordination_setup,
headerLeft: null,
});

View File

@ -22,7 +22,8 @@ import {
} from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import { BlueButtonLink, BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import { BlueButtonLink, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import * as NavigationService from '../../NavigationService';
import { BlueCurrentTheme } from '../../components/themes';
@ -881,21 +882,25 @@ HodlHodl.propTypes = {
}),
};
HodlHodl.navigationOptions = ({ navigation, route }) => ({
...BlueNavigationStyle(navigation, true),
title: '',
headerStyle: {
...BlueNavigationStyle(navigation, true).headerStyle,
backgroundColor: BlueCurrentTheme.colors.customHeader,
HodlHodl.navigationOptions = navigationStyle(
{
title: '',
},
headerRight: () => {
return route.params.displayLoginButton ? (
<BlueButtonLink title={loc.hodl.login} onPress={route.params.handleLoginPress} style={styles.marginHorizontal20} />
) : (
<BlueButtonLink title={loc.hodl.mycont} onPress={route.params.handleMyContractsPress} style={styles.marginHorizontal20} />
);
},
});
(options, { theme, navigation, route }) => ({
...options,
headerStyle: {
...options.headerStyle,
backgroundColor: theme.colors.customHeader,
},
headerRight: () => {
return route.params.displayLoginButton ? (
<BlueButtonLink title={loc.hodl.login} onPress={route.params.handleLoginPress} style={styles.marginHorizontal20} />
) : (
<BlueButtonLink title={loc.hodl.mycont} onPress={route.params.handleMyContractsPress} style={styles.marginHorizontal20} />
);
},
}),
);
const styles = StyleSheet.create({
grayDropdownText: {

View File

@ -1,7 +1,9 @@
import React, { useRef } from 'react';
import { WebView } from 'react-native-webview';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import { useRoute, useNavigation } from '@react-navigation/native';
import { SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
const url = 'https://accounts.hodlhodl.com/accounts/request_access?attributes=api_key,api_signature_key';
@ -51,8 +53,8 @@ const HodlHodlLogin = () => {
);
};
HodlHodlLogin.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
HodlHodlLogin.navigationOptions = navigationStyle({
closeButton: true,
title: loc.hodl.login,
headerLeft: null,
});

View File

@ -1,5 +1,6 @@
/* global alert */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
Alert,
FlatList,
@ -14,15 +15,8 @@ import {
View,
} from 'react-native';
import {
BlueButton,
BlueCopyTextToClipboard,
BlueLoading,
BlueNavigationStyle,
BlueSpacing10,
BlueSpacing20,
BlueText,
} from '../../BlueComponents';
import { BlueButton, BlueCopyTextToClipboard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import * as NavigationService from '../../NavigationService';
import { BlueCurrentTheme } from '../../components/themes';
@ -35,6 +29,7 @@ export default class HodlHodlMyContracts extends Component {
constructor(props) {
super(props);
props.navigation.setParams({ handleLogout: this.handleLogout });
this.state = {
contracts: [],
isLoading: true,
@ -45,6 +40,11 @@ export default class HodlHodlMyContracts extends Component {
clearInterval(this.state.inverval);
}
handleLogout = () => {
this.context.setHodlHodlApiKey('', '<empty>');
this.props.navigation.navigate('WalletsList');
};
async componentDidMount() {
const hodlApiKey = await this.context.getHodlHodlApiKey();
const hodlApi = new HodlHodlApi(hodlApiKey);
@ -424,35 +424,44 @@ const styles = StyleSheet.create({
},
});
HodlHodlMyContracts.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.hodl.cont_title,
headerStyle: {
backgroundColor: BlueCurrentTheme.colors.elevated,
HodlHodlMyContracts.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
setParams: PropTypes.func,
}),
};
HodlHodlMyContracts.navigationOptions = navigationStyle(
{
closeButton: true,
title: loc.hodl.cont_title,
},
headerRight: () => (
<TouchableOpacity
style={styles.marginRight}
onPress={() => {
Alert.alert(
loc.hodl.are_you_sure_you_want_to_logout,
'',
[
{
text: loc._.ok,
onPress: () => {
this.context.setHodlHodlApiKey('', '<empty>');
navigation.navigate('WalletsList');
(options, { theme, navigation, route }) => ({
...options,
headerStyle: {
backgroundColor: theme.colors.elevated,
},
headerRight: () => (
<TouchableOpacity
style={styles.marginRight}
onPress={() => {
Alert.alert(
loc.hodl.are_you_sure_you_want_to_logout,
'',
[
{
text: loc._.ok,
onPress: route.params.handleLogout,
style: 'default',
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}}
>
<BlueText>logout</BlueText>
</TouchableOpacity>
),
});
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}}
>
<BlueText>{loc.hodl.logout}</BlueText>
</TouchableOpacity>
),
}),
);

View File

@ -1,10 +1,12 @@
/* global alert */
import React, { Component } from 'react';
import { Alert, FlatList, Image, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, View } from 'react-native';
import { BlueButton, BlueLoading, BlueNavigationStyle, BlueSpacing10, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import { Icon } from 'react-native-elements';
import navigationStyle from '../../components/navigationStyle';
import { BlueButton, BlueLoading, BlueSpacing10, SafeBlueArea } from '../../BlueComponents';
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import * as NavigationService from '../../NavigationService';
import { BlueCurrentTheme } from '../../components/themes';
import loc from '../../loc';
@ -380,11 +382,15 @@ const styles = StyleSheet.create({
acceptOfferButtonWrapperWrapper: { marginTop: 24, alignItems: 'center' },
});
HodlHodlViewOffer.navigationOptions = () => ({
...BlueNavigationStyle(),
title: '',
headerStyle: {
...BlueNavigationStyle().headerStyle,
backgroundColor: BlueCurrentTheme.colors.customHeader,
HodlHodlViewOffer.navigationOptions = navigationStyle(
{
title: '',
},
});
(options, { theme }) => ({
...options,
headerStyle: {
...options.headerStyle,
backgroundColor: theme.colors.customHeader,
},
}),
);

View File

@ -1,8 +1,10 @@
import React, { Component } from 'react';
import { WebView } from 'react-native-webview';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
export default class HodlHodlWebview extends Component {
constructor(props) {
super(props);
@ -31,8 +33,8 @@ HodlHodlWebview.propTypes = {
}),
};
HodlHodlWebview.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
HodlHodlWebview.navigationOptions = navigationStyle({
closeButton: true,
title: '',
headerLeft: null,
});

View File

@ -1,6 +1,14 @@
/* global alert */
import React, { useEffect, useState } from 'react';
import { Platform, View, Keyboard, StatusBar, StyleSheet } from 'react-native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import Clipboard from '@react-native-community/clipboard';
import ImagePicker from 'react-native-image-picker';
import { getSystemName } from 'react-native-device-info';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import {
BlueFormMultiInput,
BlueButtonLink,
@ -9,19 +17,12 @@ import {
BlueButton,
SafeBlueArea,
BlueSpacing20,
BlueNavigationStyle,
} from '../../BlueComponents';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import navigationStyle from '../../components/navigationStyle';
import Privacy from '../../Privacy';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import WalletImport from '../../class/wallet-import';
import Clipboard from '@react-native-community/clipboard';
import ActionSheet from '../ActionSheet';
import ImagePicker from 'react-native-image-picker';
import loc from '../../loc';
import { getSystemName } from 'react-native-device-info';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import { presentCameraNotAuthorizedAlert } from '../../class/camera';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
@ -267,8 +268,7 @@ const WalletsImport = () => {
);
};
WalletsImport.navigationOptions = () => ({
...BlueNavigationStyle(),
WalletsImport.navigationOptions = navigationStyle({
title: loc.wallets.import_title,
});
export default WalletsImport;

View File

@ -208,7 +208,7 @@ const WalletsList = () => {
{`${loc.transactions.list_title}${' '}`}
</Text>
{isCatalyst && (
<TouchableOpacity style={style} onPress={refreshTransactions} disabled={isLoading}>
<TouchableOpacity style={style} onPress={() => refreshTransactions(true)} disabled={isLoading}>
<Icon name="refresh" type="font-awesome" color={colors.feeText} />
</TouchableOpacity>
)}

View File

@ -1,8 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { BackHandler } from 'react-native';
import { WebView } from 'react-native-webview';
import { BlueLoading, BlueNavigationStyle } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { BlueLoading } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
export default class Marketplace extends Component {
webview = React.createRef();
@ -75,8 +77,8 @@ Marketplace.propTypes = {
}),
};
Marketplace.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
Marketplace.navigationOptions = navigationStyle({
closeButton: true,
title: 'Marketplace',
headerLeft: null,
});

View File

@ -1,7 +1,9 @@
import React, { useEffect, useState, useCallback, useContext } from 'react';
import { ActivityIndicator, View, BackHandler, Text, ScrollView, StyleSheet, StatusBar } from 'react-native';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText, BlueButton } from '../../BlueComponents';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueButton } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Privacy from '../../Privacy';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -85,14 +87,15 @@ const PleaseBackup = () => {
);
};
PleaseBackup.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
PleaseBackup.navigationOptions = navigationStyle({
closeButton: true,
title: loc.pleasebackup.title,
headerLeft: null,
headerRight: null,
gestureEnabled: false,
swipeEnabled: false,
});
const styles = StyleSheet.create({
flex: {
flex: 1,

View File

@ -1,21 +1,19 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useContext, useEffect } from 'react';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { View, useWindowDimensions, StyleSheet, BackHandler, StatusBar } from 'react-native';
import {
SafeBlueArea,
BlueNavigationStyle,
BlueSpacing20,
BlueCopyTextToClipboard,
BlueButton,
BlueTextCentered,
} from '../../BlueComponents';
import QRCode from 'react-native-qrcode-svg';
import Privacy from '../../Privacy';
import { ScrollView } from 'react-native-gesture-handler';
import { BlueButton, BlueCopyTextToClipboard, BlueSpacing20, BlueTextCentered, SafeBlueArea } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import Privacy from '../../Privacy';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const PleaseBackupLNDHub = () => {
const { wallet } = useRoute().params;
const { wallets } = useContext(BlueStorageContext);
const { walletID } = useRoute().params;
const wallet = wallets.find(w => w.getID() === walletID);
const navigation = useNavigation();
const { colors } = useTheme();
const { height, width } = useWindowDimensions();
@ -77,8 +75,8 @@ const PleaseBackupLNDHub = () => {
);
};
PleaseBackupLNDHub.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
PleaseBackupLNDHub.navigationOptions = navigationStyle({
closeButton: true,
title: loc.pleasebackup.title,
headerLeft: null,
headerRight: null,

View File

@ -3,11 +3,12 @@ import PropTypes from 'prop-types';
import BN from 'bignumber.js';
import { Dimensions, PixelRatio, View, ScrollView, Text, Image, TouchableOpacity, StyleSheet, useWindowDimensions } from 'react-native';
import { Icon } from 'react-native-elements';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueCurrentTheme } from '../../components/themes';
import { FContainer, FButton } from '../../components/FloatButtons';
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueTabs } from '../../BlueComponents';
import { BlueSpacing20, SafeBlueArea, BlueTabs } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
const ENTROPY_LIMIT = 256;
@ -270,15 +271,7 @@ const Entropy = () => {
);
};
Entropy.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};
Entropy.navigationOptions = () => ({
...BlueNavigationStyle(),
Entropy.navigationOptions = navigationStyle({
title: loc.entropy.title,
});

View File

@ -1,13 +1,14 @@
import React, { useEffect, useState, useRef, useContext } from 'react';
import { View, ActivityIndicator, Image, Text, StyleSheet, StatusBar, ScrollView } from 'react-native';
import { BlueNavigationStyle } from '../../BlueComponents';
import SortableList from 'react-native-sortable-list';
import LinearGradient from 'react-native-linear-gradient';
import { PlaceholderWallet, LightningCustodianWallet, MultisigHDWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { useNavigation, useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { PlaceholderWallet, LightningCustodianWallet, MultisigHDWallet } from '../../class';
import WalletGradient from '../../class/wallet-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../../loc';
import { useNavigation, useTheme } from '@react-navigation/native';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const styles = StyleSheet.create({
@ -187,13 +188,14 @@ const ReorderWallets = () => {
);
};
ReorderWallets.navigationOptions = ({ navigation, route }) => ({
...BlueNavigationStyle(
navigation,
true,
route.params && route.params.customCloseButtonFunction ? route.params.customCloseButtonFunction : undefined,
),
headerTitle: loc.wallets.reorder_title,
ReorderWallets.navigationOptions = navigationStyle({
title: loc.wallets.reorder_title,
closeButton: true,
closeButtonFunc: ({ navigation, route }) => {
if (route.params && route.params.customCloseButtonFunction) {
route.params.customCloseButtonFunction();
}
},
headerLeft: null,
});

View File

@ -1,12 +1,14 @@
/* eslint-disable react/prop-types */
import React, { useContext, useEffect, useState } from 'react';
import { View, ActivityIndicator, Image, Text, TouchableOpacity, FlatList, StyleSheet, StatusBar } from 'react-native';
import { SafeBlueArea, BlueText, BlueSpacing20, BluePrivateBalance, BlueNavigationStyle } from '../../BlueComponents';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import WalletGradient from '../../class/wallet-gradient';
import { useRoute, useTheme } from '@react-navigation/native';
import { SafeBlueArea, BlueText, BlueSpacing20, BluePrivateBalance } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import WalletGradient from '../../class/wallet-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../../loc';
import { MultisigHDWallet } from '../../class';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -170,11 +172,8 @@ const SelectWallet = ({ navigation }) => {
}
};
SelectWallet.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
headerRight: null,
headerTitle: loc.wallets.select_wallet,
headerBackTitleVisible: false,
SelectWallet.navigationOptions = navigationStyle({
title: loc.wallets.select_wallet,
});
export default SelectWallet;

View File

@ -23,15 +23,17 @@ import Clipboard from '@react-native-community/clipboard';
import { Icon } from 'react-native-elements';
import Handoff from 'react-native-handoff';
import { useRoute, useNavigation, useTheme, useFocusEffect } from '@react-navigation/native';
import isCatalyst from 'react-native-is-catalyst';
import { Chain } from '../../models/bitcoinUnits';
import { BlueTransactionListItem, BlueWalletNavigationHeader, BlueAlertWalletExportReminder, BlueListItem } from '../../BlueComponents';
import WalletGradient from '../../class/wallet-gradient';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet, WatchOnlyWallet } from '../../class';
import HandoffSettings from '../../class/handoff';
import ActionSheet from '../ActionSheet';
import loc from '../../loc';
import { FContainer, FButton } from '../../components/FloatButtons';
import isCatalyst from 'react-native-is-catalyst';
import BottomModal from '../../components/BottomModal';
import BuyBitcoin from './buyBitcoin';
import { BlueStorageContext } from '../../blue_modules/storage-context';
@ -140,6 +142,15 @@ const WalletTransactions = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
// if balance of the wallet positive and there are no transactions, then
// it'a freshly impoted wallet and we need to refresh transactions
useEffect(() => {
if (dataSource.length === 0 && wallet.current.getBalance() > 0) {
refreshTransactions();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// if description of transaction has been changed we want to show new one
useFocusEffect(
useCallback(() => {
@ -651,7 +662,7 @@ const WalletTransactions = () => {
text={loc.receive.header}
onPress={() => {
if (wallet.current.chain === Chain.OFFCHAIN) {
navigate('LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { fromWallet: wallet.current } });
navigate('LNDCreateInvoiceRoot', { screen: 'LNDCreateInvoice', params: { walletID: wallet.current.getID() } });
} else {
navigate('ReceiveDetailsRoot', { screen: 'ReceiveDetails', params: { walletID: wallet.current.getID() } });
}
@ -684,7 +695,7 @@ const WalletTransactions = () => {
export default WalletTransactions;
WalletTransactions.navigationOptions = ({ navigation, route }) => {
WalletTransactions.navigationOptions = navigationStyle({}, (options, { theme, navigation, route }) => {
return {
headerRight: () => (
<TouchableOpacity
@ -699,7 +710,7 @@ WalletTransactions.navigationOptions = ({ navigation, route }) => {
<Icon name="kebab-horizontal" type="octicon" size={22} color="#FFFFFF" />
</TouchableOpacity>
),
headerTitle: '',
title: '',
headerStyle: {
backgroundColor: WalletGradient.headerColorFor(route.params.walletType),
borderBottomWidth: 0,
@ -710,7 +721,7 @@ WalletTransactions.navigationOptions = ({ navigation, route }) => {
headerTintColor: '#FFFFFF',
headerBackTitleVisible: false,
};
};
});
const styles = StyleSheet.create({
flex: {

View File

@ -22,15 +22,15 @@ import ImagePicker from 'react-native-image-picker';
import {
BlueButton,
BlueButtonLinkHook,
BlueButtonLink,
BlueFormMultiInput,
BlueLoadingHook,
BlueNavigationStyle,
BlueLoading,
BlueSpacing10,
BlueSpacing20,
BlueSpacing40,
BlueTextCentered,
} from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import SquareEnumeratedWords, { SquareEnumeratedWordsContentAlign } from '../../components/SquareEnumeratedWords';
import BottomModal from '../../components/BottomModal';
import { HDSegwitBech32Wallet, MultisigHDWallet } from '../../class';
@ -168,7 +168,7 @@ const ViewEditMultisigCosigners = () => {
</View>
<View style={styles.vaultKeyTextWrapper}>
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex + 1 })}
{loc.formatString(loc.multisig.vault_key, { number: vaultKeyData.keyIndex })}
</Text>
</View>
</View>
@ -441,7 +441,7 @@ const ViewEditMultisigCosigners = () => {
onPress={handleUseMnemonicPhrase}
/>
)}
<BlueButtonLinkHook disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
<BlueButtonLink disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</KeyboardAvoidingView>
</BottomModal>
@ -451,7 +451,7 @@ const ViewEditMultisigCosigners = () => {
if (isLoading)
return (
<SafeAreaView style={[styles.root, stylesHook.root]}>
<BlueLoadingHook />
<BlueLoading />
</SafeAreaView>
);
@ -574,8 +574,8 @@ const styles = StyleSheet.create({
squareButtonWrapper: { height: 50, width: 250 },
});
ViewEditMultisigCosigners.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
ViewEditMultisigCosigners.navigationOptions = navigationStyle({
closeButton: true,
title: loc.multisig.view_edit_cosigners_title,
headerLeft: null,
});

View File

@ -1,11 +1,13 @@
import React, { useCallback, useContext, useState } from 'react';
import { InteractionManager, useWindowDimensions, ActivityIndicator, View, StatusBar, StyleSheet } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle, BlueCopyTextToClipboard } from '../../BlueComponents';
import { useFocusEffect, useRoute, useNavigation, useTheme } from '@react-navigation/native';
import navigationStyle from '../../components/navigationStyle';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueCopyTextToClipboard } from '../../BlueComponents';
import Privacy from '../../Privacy';
import Biometric from '../../class/biometrics';
import loc from '../../loc';
import { useFocusEffect, useRoute, useNavigation, useTheme } from '@react-navigation/native';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const styles = StyleSheet.create({
@ -97,8 +99,8 @@ const WalletXpub = () => {
);
};
WalletXpub.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
WalletXpub.navigationOptions = navigationStyle({
closeButton: true,
title: loc.wallets.xpub_title,
headerLeft: null,
});

View File

@ -1,12 +1,4 @@
vim ios/BlueWallet/Info.plist
vim ios/BlueWalletWatch/Info.plist
vim "ios/BlueWalletWatch Extension/Info.plist"
vim "ios/TodayExtension/Info.plist"
vim ios/BlueWallet.xcodeproj/project.pbxproj
vim ios/WalletInformationWidget/Widgets/MarketWidget/Info.plist
vim ios/WalletInformationWidget/Widgets/WalletInformationAndMarketWidget/Info.plist
vim ios/WalletInformationWidget/Info.plist
vim ios/WalletInformationWidget/Widgets/PriceWidget/Info.plist
vim android/app/build.gradle
vim package.json
vim package-lock.json