Merge pull request #6671 from BlueWallet/receietab

Receietab
This commit is contained in:
Overtorment 2024-06-10 20:48:34 +01:00 committed by GitHub
commit 5041ac4841
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 275 additions and 118 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

@ -1,6 +1,6 @@
PODS:
- boost (1.76.0)
- BugsnagReactNative (7.23.0):
- BugsnagReactNative (7.24.0):
- React-Core
- BVLinearGradient (2.8.3):
- React-Core
@ -767,7 +767,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
BugsnagReactNative: 079e8ede687b76bd8b661acd55bc5c888af56dc7
BugsnagReactNative: 65729c7ee7d9f61b8dbd9f046d920bb38a43931a
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54

View File

@ -150,6 +150,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",

View File

@ -56,7 +56,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';
@ -66,6 +65,7 @@ import SignVerifyStackRoot from './SignVerifyStack';
import ViewEditMultisigCosignersStackRoot from './ViewEditMultisigCosignersStack';
import WalletExportStack from './WalletExportStack';
import WalletXpubStackRoot from './WalletXpubStack';
import PaymentCodeStackRoot from './PaymentCodeStack';
const DetailViewStackScreensStack = () => {
const theme = useTheme();
@ -208,6 +208,8 @@ const DetailViewStackScreensStack = () => {
closeButtonPosition: CloseButtonPosition.Right,
})(theme)}
/>
<DetailViewStack.Screen name="PaymentCodeListRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen
name="LnurlPaySuccess"
component={LnurlPaySuccess}
@ -362,8 +364,6 @@ const DetailViewStackScreensStack = () => {
statusBarHidden: true,
}}
/>
<DetailViewStack.Screen name="PaymentCodeRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen
name="ReorderWallets"
component={ReorderWalletsStackRoot}

View File

@ -1,4 +1,5 @@
import { LightningTransaction } from '../class/wallets/types';
import { SendDetailsParams } from './SendDetailsStackParamList';
export type DetailViewStackParamList = {
UnlockWithScreen: undefined;
@ -30,7 +31,11 @@ export type DetailViewStackParamList = {
Success: undefined;
WalletAddresses: { walletID: string };
AddWalletRoot: undefined;
SendDetailsRoot: undefined;
SendDetailsRoot: {
screen: string;
params: SendDetailsParams;
merge: boolean;
};
LNDCreateInvoiceRoot: undefined;
ScanLndInvoiceRoot: {
screen: string;
@ -94,6 +99,12 @@ export type DetailViewStackParamList = {
animatedQRCodeData?: Record<string, any>;
};
};
PaymentCodeRoot: undefined;
PaymentCodeListRoot: {
screen: 'PaymentCodeList';
params: {
paymentCode: string;
walletID: string;
};
};
ReorderWallets: undefined;
};

View File

@ -2,15 +2,8 @@ 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 />

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,27 +1,10 @@
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 */;
};
};
import { PaymentCodesListComponent } from './LazyLoadPaymentCodeStack';
import { PaymentCodeStackParamList } from './PaymentCodeStackParamList';
const Stack = createNativeStackNavigator<PaymentCodeStackParamList>();
@ -29,12 +12,7 @@ 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 })(theme)}
/>
<Stack.Navigator screenOptions={{ headerShadowVisible: false }} initialRouteName="PaymentCodesList">
<Stack.Screen
name="PaymentCodesList"
component={PaymentCodesListComponent}

View File

@ -0,0 +1,17 @@
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 */;
};
};

View File

@ -7,6 +7,7 @@ import {
CoinControlComponent,
ConfirmComponent,
CreateTransactionComponent,
PaymentCodesListComponent,
PsbtMultisigComponent,
PsbtMultisigQRCodeComponent,
PsbtWithHardwareWalletComponent,
@ -74,6 +75,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="PaymentCodeList"
component={PaymentCodesListComponent}
options={navigationStyle({ title: loc.bip47.contacts })(theme)}
/>
</Stack.Navigator>
);
};

View File

@ -1,20 +1,27 @@
import { Psbt } from 'bitcoinjs-lib';
import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } from '../class/wallets/types';
import { CreateTransactionTarget, CreateTransactionUtxo, TWallet, LightningTransaction } from '../class/wallets/types';
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
export type SendDetailsStackParamList = {
SendDetails: {
memo: string;
export type SendDetailsParams = {
memo?: string;
address?: string;
amount?: number;
amountSats?: number;
unit?: BitcoinUnit;
noRbf?: boolean;
walletID: string;
launchedBy?: string;
isEditable?: boolean;
uri?: string;
addRecipientParams?: {
address: string;
walletID: string;
amount: number;
amountSats: number;
unit: BitcoinUnit;
noRbf: boolean;
launchedBy: string;
isEditable: boolean;
uri: string;
amount?: number;
memo?: string;
};
};
export type SendDetailsStackParamList = {
SendDetails: SendDetailsParams;
Confirm: {
fee: number;
memo?: string;
@ -69,6 +76,9 @@ export type SendDetailsStackParamList = {
walletID: string;
onUTXOChoose: (u: CreateTransactionUtxo[]) => void;
};
PaymentCodeList: {
walletID: string;
};
ScanQRCodeRoot: {
screen: string;
params: {
@ -84,3 +94,127 @@ export type SendDetailsStackParamList = {
};
};
};
export type DetailViewStackParamList = {
UnlockWithScreen: undefined;
WalletsList: undefined;
WalletTransactions: { walletID: string; walletType: string };
LDKOpenChannelRoot: undefined;
LdkInfo: undefined;
WalletDetails: { walletID: string };
LdkViewLogs: undefined;
TransactionDetails: { transactionId: string };
TransactionStatus: { hash?: string; walletID?: string };
CPFP: { transactionId: string };
RBFBumpFee: { transactionId: string };
RBFCancel: { transactionId: string };
SelectWallet: undefined;
LNDViewInvoice: { invoice: LightningTransaction; walletID: string };
LNDViewAdditionalInvoiceInformation: { invoiceId: string };
LNDViewAdditionalInvoicePreImage: { invoiceId: string };
Broadcast: undefined;
IsItMyAddress: undefined;
GenerateWord: undefined;
LnurlPay: undefined;
LnurlPaySuccess: {
paymentHash: string;
justPaid: boolean;
fromWalletID: string;
};
LnurlAuth: undefined;
Success: undefined;
WalletAddresses: { walletID: string };
AddWalletRoot: undefined;
SendDetailsRoot: {
screen: string;
params: {
walletID: string;
address?: string;
amount?: number;
amountSats?: number;
unit?: BitcoinUnit;
noRbf?: boolean;
launchedBy?: string;
isEditable?: boolean;
uri?: string;
addRecipientParams?: {
address?: string;
amount?: number;
memo?: string;
};
memo?: string;
};
merge: boolean;
};
LNDCreateInvoiceRoot: undefined;
ScanLndInvoiceRoot: {
screen: string;
params: {
paymentHash: string;
fromWalletID: string;
justPaid: boolean;
};
};
AztecoRedeemRoot: undefined;
WalletExportRoot: undefined;
ExportMultisigCoordinationSetupRoot: undefined;
Settings: undefined;
Currency: undefined;
GeneralSettings: undefined;
PlausibleDeniability: undefined;
Licensing: undefined;
NetworkSettings: undefined;
About: undefined;
DefaultView: undefined;
ElectrumSettings: undefined;
EncryptStorage: undefined;
Language: undefined;
LightningSettings: {
url?: string;
};
NotificationSettings: undefined;
SelfTest: undefined;
ReleaseNotes: undefined;
Tools: undefined;
SettingsPrivacy: undefined;
ViewEditMultisigCosignersRoot: { walletID: string; cosigners: string[] };
WalletXpubRoot: undefined;
SignVerifyRoot: {
screen: 'SignVerify';
params: {
walletID: string;
address: string;
};
};
ReceiveDetailsRoot: {
screen: 'ReceiveDetails';
params: {
walletID: string;
address: string;
};
};
ScanQRCodeRoot: {
screen: string;
params: {
isLoading: false;
cameraStatusGranted?: boolean;
backdoorPressed?: boolean;
launchedBy?: string;
urTotal?: number;
urHave?: number;
backdoorText?: string;
onDismiss?: () => void;
showFileImportButton: true;
backdoorVisible?: boolean;
animatedQRCodeData?: Record<string, any>;
};
};
PaymentCodeListRoot: {
screen: 'PaymentCodeList';
params: {
paymentCode: string;
walletID: string;
};
};
ReorderWallets: undefined;
};

View File

@ -77,6 +77,7 @@ type RouteProps = RouteProp<SendDetailsStackParamList, 'SendDetails'>;
const SendDetails = () => {
const { wallets, setSelectedWalletID, sleep, txMetadata, saveToDisk } = useStorage();
const navigation = useExtendedNavigation<NavigationProps>();
const setParams = navigation.setParams;
const route = useRoute<RouteProps>();
const name = route.name;
const routeParams = route.params;
@ -157,6 +158,24 @@ 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];
});
}
setParams({ addRecipientParams: undefined });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [routeParams.addRecipientParams]);
useEffect(() => {
// decode route params
const currentAddress = addresses[scrollIndex.current];
@ -194,16 +213,17 @@ const SendDetails = () => {
}
} else if (routeParams.address) {
const { amount, amountSats, unit = BitcoinUnit.BTC } = routeParams;
setAddresses(addrs => {
if (currentAddress) {
// @ts-ignore: needs fix
setAddresses(value => {
if (currentAddress && currentAddress.address && routeParams.address) {
currentAddress.address = routeParams.address;
addrs[scrollIndex.current] = currentAddress;
return [...addrs];
value[scrollIndex.current] = currentAddress;
return [...value];
} else {
return [...addrs, { address: routeParams.address, key: String(Math.random()), amount, amountSats }];
return [...value, { address: routeParams.address, key: String(Math.random()), amount, amountSats }];
}
});
if (routeParams.memo?.trim().length > 0) {
if (routeParams.memo && routeParams.memo?.trim().length > 0) {
setTransactionMemo(routeParams.memo);
}
setUnits(u => {
@ -875,6 +895,12 @@ const SendDetails = () => {
});
};
const handleInsertContact = () => {
if (!wallet) return;
setOptionsVisible(false);
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
};
const handlePsbtSign = async () => {
setIsLoading(true);
setOptionsVisible(false);
@ -945,15 +971,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 }]);
}
@ -1561,6 +1597,7 @@ const SendDetails = () => {
export default SendDetails;
SendDetails.actionKeys = {
InsertContact: 'InsertContact',
SignPSBT: 'SignPSBT',
SendMax: 'SendMax',
AddRecipient: 'AddRecipient',
@ -1574,6 +1611,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

@ -4,7 +4,7 @@ import { RouteProp, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import assert from 'assert';
import createHash from 'create-hash';
import { SectionList, StyleSheet, Text, View } from 'react-native';
import { SectionList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import { satoshiToLocalCurrency } from '../../blue_modules/currency';
import { BlueLoading } from '../../BlueComponents';
@ -20,15 +20,17 @@ 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 { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
interface DataSection {
title: string;
data: string[];
}
enum Actions {
pay,
rename,
@ -66,13 +68,16 @@ function onlyUnique(value: any, index: number, self: any[]) {
return self.indexOf(value) === index;
}
type PaymentCodeListRouteProp = RouteProp<PaymentCodeStackParamList, 'PaymentCodesList'>;
type PaymentCodesListNavigationProp = NativeStackNavigationProp<PaymentCodeStackParamList, 'PaymentCodesList'>;
type PaymentCodeListModalProp = RouteProp<DetailViewStackParamList, 'PaymentCodeListRoot'>;
type PaymentCodesListModalNavigationProp = NativeStackNavigationProp<DetailViewStackParamList, 'PaymentCodeListRoot'>;
type PaymentCodeListRouteProp = RouteProp<SendDetailsStackParamList, 'PaymentCodeList'>;
type PaymentCodesListNavigationProp = NativeStackNavigationProp<SendDetailsStackParamList, 'PaymentCodeList'>;
export default function PaymentCodesList() {
const route = useRoute<PaymentCodeListRouteProp>();
const navigation = useExtendedNavigation<PaymentCodesListNavigationProp>();
const { walletID } = route.params;
const { params } = useRoute<PaymentCodeListModalProp & PaymentCodeListRouteProp>();
const navigation = useExtendedNavigation<PaymentCodesListModalNavigationProp & PaymentCodesListNavigationProp>();
const { walletID } = params;
const { wallets, txMetadata, counterpartyMetadata, saveToDisk } = useStorage();
const [reload, setReload] = useState<number>(0);
const [data, setData] = useState<DataSection[]>([]);
@ -144,14 +149,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,
});
};
@ -160,6 +164,22 @@ export default function PaymentCodesList() {
const displayName = shortenContactName(counterpartyMetadata?.[pc]?.label ?? pc);
const isFirstRouteInStack = navigation.getState().index === 0;
if (isFirstRouteInStack) {
return (
<TouchableOpacity onPress={() => _navigateToSend(pc)}>
<View style={styles.contactRowContainer}>
<View style={[styles.circle, { backgroundColor: '#' + color }]} />
<View style={styles.contactRowBody}>
<Text style={[styles.contactRowNameText, { color: colors.labelText }]}>{displayName}</Text>
</View>
</View>
<View style={styles.stick} />
</TouchableOpacity>
);
}
return (
<ToolTipMenu
actions={toolTipActions}
@ -185,7 +205,7 @@ export default function PaymentCodesList() {
await _addContact(newPc);
} catch (error: any) {
presentAlert({ message: error.message });
console.debug(error.message);
} finally {
setIsLoading(false);
}