ADD: Insert Contact

This commit is contained in:
Marcos Rodriguez Velez 2024-06-07 09:37:45 -04:00
parent f10b066206
commit 7332b686fa
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
13 changed files with 125 additions and 149 deletions

View file

@ -107,13 +107,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
}
};
const handleOnPaymentCodeButtonPressed = () => {
navigation.navigate('PaymentCodeRoot', {
screen: 'PaymentCodesList',
params: { walletID: wallet.getID() },
});
};
const onPressMenuItem = (id: string) => {
if (id === 'walletBalanceVisibility') {
handleBalanceVisibility();
@ -247,11 +240,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</ToolTipMenu>
)}
{wallet.allowBIP47() && wallet.isBIP47Enabled() && (
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={handleOnPaymentCodeButtonPressed}>
<Text style={styles.manageFundsButtonText}>{loc.bip47.contacts}</Text>
</TouchableOpacity>
)}
{wallet.type === LightningLdkWallet.type && (
<TouchableOpacity
style={styles.manageFundsButton}

View file

@ -126,7 +126,8 @@
"maxSatsFull": "Maximum amount is {max} sats or {currency}",
"minSats": "Minimal amount is {min} sats",
"minSatsFull": "Minimal amount is {min} sats or {currency}",
"qrcode_for_the_address": "QR Code for the address"
"qrcode_for_the_address": "QR Code for the address",
"bip47_explanation": "Payment codes are an universal address that avoids disclosing your wallet addresses. Not all services will support them."
},
"send": {
"provided_address_is_invoice": "This address appears to be for a Lightning invoice. Please, go to your Lightning wallet in order to make a payment for this invoice.",
@ -148,6 +149,7 @@
"create_to": "To",
"create_tx_size": "Transaction Size",
"create_verify": "Verify on coinb.in",
"details_insert_contact": "Insert Contact",
"details_add_rec_add": "Add Recipient",
"details_add_rec_rem": "Remove Recipient",
"details_address": "Address",
@ -620,7 +622,6 @@
},
"bip47": {
"payment_code": "Payment Code",
"my_payment_code": "My Payment Code",
"contacts": "Contacts",
"purpose": "Reusable and shareable code (BIP47)",
"pay_this_contact": "Pay this contact",

View file

@ -62,7 +62,6 @@ import {
} from './LazyLoadSettingsStack';
import LDKOpenChannelRoot from './LDKOpenChannelStack';
import LNDCreateInvoiceRoot from './LNDCreateInvoiceStack';
import PaymentCodeStackRoot from './PaymentCodeStack';
import ReceiveDetailsStackRoot from './ReceiveDetailsStack';
import ReorderWalletsStackRoot from './ReorderWalletsStack';
import ScanLndInvoiceRoot from './ScanLndInvoiceStack';
@ -376,8 +375,6 @@ const DetailViewStackScreensStack = () => {
statusBarHidden: true,
}}
/>
<DetailViewStack.Screen name="PaymentCodeRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen
name="ReorderWallets"
component={ReorderWalletsStackRoot}

View file

@ -30,7 +30,26 @@ export type DetailViewStackParamList = {
Success: undefined;
WalletAddresses: { walletID: string };
AddWalletRoot: undefined;
SendDetailsRoot: undefined;
SendDetailsRoot: {
screen: string;
params: {
walletID: string;
address?: string;
amount?: number;
amountSats?: number;
uni?: string;
noRbf?: boolean;
launchedBy?: string;
isEditable?: boolean;
uri?: string;
addRecipientParams?: {
address?: string;
amount?: number;
memo?: string;
};
};
merge: boolean;
};
LNDCreateInvoiceRoot: undefined;
ScanLndInvoiceRoot: {
screen: string;

View file

@ -1,18 +0,0 @@
import React, { lazy, Suspense } from 'react';
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
const PaymentCode = lazy(() => import('../screen/wallets/PaymentCode'));
const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList'));
export const PaymentCodeComponent = () => (
<Suspense fallback={<LazyLoadingIndicator />}>
<PaymentCode />
</Suspense>
);
export const PaymentCodesListComponent = () => (
<Suspense fallback={<LazyLoadingIndicator />}>
<PaymentCodesList />
</Suspense>
);

View file

@ -11,6 +11,7 @@ const PsbtMultisigQRCode = lazy(() => import('../screen/send/psbtMultisigQRCode'
const Success = lazy(() => import('../screen/send/success'));
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet'));
const CoinControl = lazy(() => import('../screen/send/coinControl'));
const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList'));
// Export each component with its lazy loader and optional configurations
export const SendDetailsComponent = () => (
@ -58,3 +59,9 @@ export const CoinControlComponent = () => (
<CoinControl />
</Suspense>
);
export const PaymentCodesListComponent = () => (
<Suspense fallback={<LazyLoadingIndicator />}>
<PaymentCodesList />
</Suspense>
);

View file

@ -1,47 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import navigationStyle from '../components/navigationStyle';
import { useTheme } from '../components/themes';
import loc from '../loc'; // Assuming 'loc' is used for localization
import { PaymentCodeComponent, PaymentCodesListComponent } from './LazyLoadPaymentCodeStack';
import { BitcoinUnit } from '../models/bitcoinUnits';
export type PaymentCodeStackParamList = {
PaymentCode: { paymentCode: string };
PaymentCodesList: {
memo: string;
address: string;
walletID: string;
amount: number;
amountSats: number;
unit: BitcoinUnit;
noRbf: boolean;
launchedBy: string;
isEditable: boolean;
uri: string /* payjoin uri */;
};
};
const Stack = createNativeStackNavigator<PaymentCodeStackParamList>();
const PaymentCodeStackRoot = () => {
const theme = useTheme();
return (
<Stack.Navigator screenOptions={{ headerShadowVisible: false }} initialRouteName="PaymentCode">
<Stack.Screen
name="PaymentCode"
component={PaymentCodeComponent}
options={navigationStyle({ title: loc.bip47.payment_code, closeButton: true })(theme)}
/>
<Stack.Screen
name="PaymentCodesList"
component={PaymentCodesListComponent}
options={navigationStyle({ title: loc.bip47.contacts, closeButton: true })(theme)}
/>
</Stack.Navigator>
);
};
export default PaymentCodeStackRoot;

View file

@ -8,6 +8,7 @@ import {
CoinControlComponent,
ConfirmComponent,
CreateTransactionComponent,
PaymentCodesListComponent,
PsbtMultisigComponent,
PsbtMultisigQRCodeComponent,
PsbtWithHardwareWalletComponent,
@ -72,6 +73,11 @@ const SendDetailsStack = () => {
options={navigationStyle({ title: loc.wallets.select_wallet })(theme)}
/>
<Stack.Screen name="CoinControl" component={CoinControlComponent} options={navigationStyle({ title: loc.cc.header })(theme)} />
<Stack.Screen
name="PaymentCodesList"
component={PaymentCodesListComponent}
options={navigationStyle({ title: loc.bip47.contacts })(theme)}
/>
</Stack.Navigator>
);
};

View file

@ -3,6 +3,27 @@ import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } from '../clas
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
export type SendDetailsStackParamList = {
SendDetailsRoot: {
screen: string;
params: {
memo?: string;
address?: string;
walletID: string;
amount?: number;
amountSats?: number;
unit?: BitcoinUnit;
noRbf?: boolean;
launchedBy?: string;
isEditable?: boolean;
uri?: string;
addRecipientParams?: {
address: string;
amount?: number;
memo?: string;
};
};
merge: boolean;
};
SendDetails: {
memo: string;
address: string;
@ -14,6 +35,11 @@ export type SendDetailsStackParamList = {
launchedBy: string;
isEditable: boolean;
uri: string;
addRecipientParams?: {
address: string;
amount?: number;
memo?: string;
};
};
Confirm: {
fee: number;
@ -70,6 +96,9 @@ export type SendDetailsStackParamList = {
walletID: string;
onUTXOChoose: (u: CreateTransactionUtxo[]) => void;
};
PaymentCodesList: {
walletID: string;
};
ScanQRCodeRoot: {
screen: string;
params: {

View file

@ -87,6 +87,9 @@ const ReceiveDetails = () => {
modalButton: {
backgroundColor: colors.modalButton,
},
tip: {
backgroundColor: colors.ballOutgoingExpired,
},
});
useEffect(() => {
@ -264,9 +267,6 @@ const ReceiveDetails = () => {
title={loc.receive.details_setAmount}
onPress={showCustomAmountModal}
/>
<View style={styles.share}>
<Button onPress={handleShareButtonPressed} title={loc.receive.details_share} />
</View>
</BlueCard>
</View>
{renderCustomAmountModal()}
@ -277,7 +277,6 @@ const ReceiveDetails = () => {
const obtainWalletAddress = useCallback(async () => {
console.log('receive/details - componentDidMount');
wallet.setUserHasSavedExport(true);
await saveToDisk();
let newAddress;
if (address) {
setAddressBIP21Encoded(address);
@ -445,13 +444,11 @@ const ReceiveDetails = () => {
{!qrValue && <Text>{loc.bip47.not_found}</Text>}
{qrValue && (
<>
<View style={[styles.tip, stylesHook.tip]}>
<Text style={{ color: colors.foregroundColor }}>{loc.receive.bip47_explanation}</Text>
</View>
<QRCodeComponent value={qrValue} />
<CopyTextToClipboard text={qrValue} truncated={false} />
<View style={styles.share}>
<BlueCard>
<Button onPress={handleShareButtonPressed} title={loc.receive.details_share} />
</BlueCard>
</View>
</>
)}
</View>
@ -469,6 +466,11 @@ const ReceiveDetails = () => {
/>
</View>
{renderTabContent()}
<View style={styles.share}>
<BlueCard>
<Button onPress={handleShareButtonPressed} title={loc.receive.details_share} />
</BlueCard>
</View>
{address !== undefined && showAddress && (
<HandOffComponent title={loc.send.details_address} type={HandOffComponent.activityTypes.ReceiveOnchain} userInfo={{ address }} />
)}
@ -550,6 +552,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
tip: {
marginHorizontal: 16,
borderRadius: 12,
padding: 16,
marginVertical: 24,
},
});
export default ReceiveDetails;

View file

@ -157,6 +157,22 @@ const SendDetails = () => {
};
}, []);
// handle selecting a contact (payment code list)
useEffect(() => {
if (routeParams.addRecipientParams) {
setAddresses(addrs => {
if (routeParams.addRecipientParams?.amount) {
addrs[scrollIndex.current].amount = routeParams.addRecipientParams?.amount;
addrs[scrollIndex.current].amountSats = btcToSatoshi(routeParams.addRecipientParams?.amount);
}
if (routeParams.addRecipientParams?.address) {
addrs[scrollIndex.current].address = routeParams.addRecipientParams?.address;
}
return [...addrs];
});
}
}, [routeParams.addRecipientParams]);
useEffect(() => {
// decode route params
const currentAddress = addresses[scrollIndex.current];
@ -195,7 +211,7 @@ const SendDetails = () => {
} else if (routeParams.address) {
const { amount, amountSats, unit = BitcoinUnit.BTC } = routeParams;
setAddresses(addrs => {
if (currentAddress) {
if (currentAddress && currentAddress.address && routeParams.address) {
currentAddress.address = routeParams.address;
addrs[scrollIndex.current] = currentAddress;
return [...addrs];
@ -872,6 +888,12 @@ const SendDetails = () => {
});
};
const handleInsertContact = () => {
if (!wallet) return;
setOptionsVisible(false);
navigation.navigate('PaymentCodesList', { walletID: wallet.getID() });
};
const handlePsbtSign = async () => {
setIsLoading(true);
setOptionsVisible(false);
@ -942,15 +964,25 @@ const SendDetails = () => {
importTransactionMultisigScanQr();
} else if (id === SendDetails.actionKeys.CoinControl) {
handleCoinControl();
} else if (id === SendDetails.actionKeys.InsertContact) {
handleInsertContact();
}
};
const headerRightActions = () => {
const actions = [];
if (isEditable) {
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) {
actions.push([
{ id: SendDetails.actionKeys.InsertContact, text: loc.send.details_insert_contact, icon: SendDetails.actionIcons.InsertContact },
]);
}
actions.push([{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]);
if (Number(wallet?.getBalance()) > 0) {
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
actions.push([{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]);
}
if (wallet?.type === HDSegwitBech32Wallet.type) {
actions.push([{ id: SendDetails.actionKeys.AllowRBF, text: loc.send.details_adv_fee_bump, menuStateOn: isTransactionReplaceable }]);
}
@ -1549,6 +1581,7 @@ const SendDetails = () => {
export default SendDetails;
SendDetails.actionKeys = {
InsertContact: 'InsertContact',
SignPSBT: 'SignPSBT',
SendMax: 'SendMax',
AddRecipient: 'AddRecipient',
@ -1562,6 +1595,7 @@ SendDetails.actionKeys = {
};
SendDetails.actionIcons = {
InsertContact: { iconType: 'SYSTEM', iconValue: 'at.badge.plus' },
SignPSBT: { iconType: 'SYSTEM', iconValue: 'signature' },
SendMax: 'SendMax',
AddRecipient: { iconType: 'SYSTEM', iconValue: 'person.badge.plus' },

View file

@ -1,36 +0,0 @@
import { useRoute } from '@react-navigation/native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import CopyTextToClipboard from '../../components/CopyTextToClipboard';
import QRCodeComponent from '../../components/QRCodeComponent';
import loc from '../../loc';
import { PaymentCodeStackParamList } from '../../navigation/PaymentCodeStack';
type Props = NativeStackScreenProps<PaymentCodeStackParamList, 'PaymentCode'>;
export default function PaymentCode() {
const route = useRoute();
const { paymentCode } = route.params as Props['route']['params'];
return (
<View style={styles.container}>
{!paymentCode && <Text>{loc.bip47.not_found}</Text>}
{paymentCode && (
<>
<QRCodeComponent value={paymentCode} />
<CopyTextToClipboard text={paymentCode} truncated={false} />
</>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});

View file

@ -7,7 +7,7 @@ import createHash from 'create-hash';
import { SectionList, StyleSheet, Text, View } from 'react-native';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import { satoshiToLocalCurrency } from '../../blue_modules/currency';
import { BlueButtonLink, BlueLoading } from '../../BlueComponents';
import { BlueLoading } from '../../BlueComponents';
import { HDSegwitBech32Wallet } from '../../class';
import { ContactList } from '../../class/contact-list';
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
@ -20,10 +20,10 @@ import confirm from '../../helpers/confirm';
import prompt from '../../helpers/prompt';
import loc, { formatBalance } from '../../loc';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { PaymentCodeStackParamList } from '../../navigation/PaymentCodeStack';
import SafeArea from '../../components/SafeArea';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { useStorage } from '../../hooks/context/useStorage';
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
interface DataSection {
title: string;
@ -66,8 +66,8 @@ function onlyUnique(value: any, index: number, self: any[]) {
return self.indexOf(value) === index;
}
type PaymentCodeListRouteProp = RouteProp<PaymentCodeStackParamList, 'PaymentCodesList'>;
type PaymentCodesListNavigationProp = NativeStackNavigationProp<PaymentCodeStackParamList, 'PaymentCodesList'>;
type PaymentCodeListRouteProp = RouteProp<SendDetailsStackParamList, 'PaymentCodesList'>;
type PaymentCodesListNavigationProp = NativeStackNavigationProp<SendDetailsStackParamList, 'PaymentCodesList'>;
export default function PaymentCodesList() {
const route = useRoute<PaymentCodeListRouteProp>();
@ -145,14 +145,13 @@ export default function PaymentCodesList() {
};
const _navigateToSend = (pc: string) => {
// @ts-ignore idk how to fix
navigation.navigate('SendDetailsRoot', {
screen: 'SendDetails',
params: {
memo: '',
address: pc,
walletID,
address: pc,
},
merge: true,
});
};
@ -179,16 +178,6 @@ export default function PaymentCodesList() {
);
};
const navigateToPaymentCodes = () => {
const foundWallet = wallets.find(w => w.getID() === walletID) as unknown as AbstractHDElectrumWallet;
// @ts-ignore idk how to fix
navigation.navigate('PaymentCodeRoot', {
screen: 'PaymentCode',
params: { paymentCode: foundWallet.getBIP47PaymentCode() },
});
};
const onAddContactPress = async () => {
try {
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, true, 'plain-text');
@ -301,7 +290,6 @@ export default function PaymentCodesList() {
</View>
)}
<BlueButtonLink title={loc.bip47.my_payment_code} onPress={navigateToPaymentCodes} />
<Button title={loc.bip47.add_contact} onPress={onAddContactPress} />
</SafeArea>
);