Files
com.blitzwallet/app/components/admin/homeComponents/contacts/sendAndRequestPage.js
T
Blake KaufmanandGitHub 6760060083 Simplify confirm split payment (#778)
* fix small style issues

* fix touch gap

* fixing podfile

* creating standardized amountIn function

* show tx ids not amounts

* use standardized amount in

* use standardized amount in

* fixed no payment information crash

* downgrade rn and react-native-quick-crypto

* only show margin if two elements exist

* adding backup amount

* wait till hompage to call analytics data

* fix restore bug

* updating firebase

* fix check logic

* wrap to handle thrown execptions

* use utils package directly

* adding error handling

* group notification payments insted of handling them sequentually

* handle payment requset on confirm page

* improving split payment ux

* adding translations

* adding firebase sdk warning
2026-04-19 11:43:00 -04:00

1072 lines
34 KiB
JavaScript

import { StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native';
import {
CENTER,
CONTENT_KEYBOARD_OFFSET,
HIDE_IN_APP_PURCHASE_ITEMS,
ICONS,
QUICK_PAY_STORAGE_KEY,
SATSPERBITCOIN,
USDB_TOKEN_ID,
} from '../../../../constants';
import { useNavigation } from '@react-navigation/native';
import { useGlobalContextProvider } from '../../../../../context-store/context';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { publishMessage } from '../../../../functions/messaging/publishMessage';
import {
CustomKeyboardAvoidingView,
ThemeText,
} from '../../../../functions/CustomElements';
import CustomNumberKeyboard from '../../../../functions/CustomElements/customNumberKeyboard';
import CustomButton from '../../../../functions/CustomElements/button';
import FormattedSatText from '../../../../functions/CustomElements/satTextDisplay';
import { useGlobalContactsInfo } from '../../../../../context-store/globalContacts';
import CustomSearchInput from '../../../../functions/CustomElements/searchInput';
import customUUID from '../../../../functions/customUUID';
import FormattedBalanceInput from '../../../../functions/CustomElements/formattedBalanceInput';
import { useNodeContext } from '../../../../../context-store/nodeContext';
import { useAppStatus } from '../../../../../context-store/appStatus';
import { useKeysContext } from '../../../../../context-store/keys';
import { crashlyticsLogReport } from '../../../../functions/crashlyticsLogs';
import convertTextInputValue from '../../../../functions/textInputConvertValue';
import { useServerTimeOnly } from '../../../../../context-store/serverTime';
import { useTranslation } from 'react-i18next';
import CustomSettingsTopBar from '../../../../functions/CustomElements/settingsTopBar';
import GetThemeColors from '../../../../hooks/themeColors';
import {
COLORS,
HIDDEN_OPACITY,
INSET_WINDOW_WIDTH,
SIZES,
WINDOWWIDTH,
} from '../../../../constants/theme';
import { useGlobalThemeContext } from '../../../../../context-store/theme';
import ThemeImage from '../../../../functions/CustomElements/themeImage';
import fetchBackend from '../../../../../db/handleBackend';
import { getDataFromCollection } from '../../../../../db';
import { Image } from 'expo-image';
import loadNewFiatData from '../../../../functions/saveAndUpdateFiatData';
import giftCardPurchaseAmountTracker from '../../../../functions/apps/giftCardPurchaseTracker';
import DropdownMenu from '../../../../functions/CustomElements/dropdownMenu';
import { useSparkWallet } from '../../../../../context-store/sparkContext';
import getReceiveAddressAndContactForContactsPayment from './internalComponents/getReceiveAddressAndKindForPayment';
import calculateProgressiveBracketFee from '../../../../functions/spark/calculateSupportFee';
import { useActiveCustodyAccount } from '../../../../../context-store/activeAccount';
import NavBarWithBalance from '../../../../functions/CustomElements/navWithBalance';
import { sparkPaymenWrapper } from '../../../../functions/spark/payments';
import { receiveSparkLightningPayment } from '../../../../functions/spark';
import { useGlobalInsets } from '../../../../../context-store/insetsProvider';
import EmojiQuickBar from '../../../../functions/CustomElements/emojiBar';
import usePaymentMethodSelection from '../../../../hooks/usePaymentMethodSelection';
import { useUserBalanceContext } from '../../../../../context-store/userBalanceContext';
import { useFlashnet } from '../../../../../context-store/flashnetContext';
import {
dollarsToSats,
satsToDollars,
} from '../../../../functions/spark/flashnet';
import ChoosePaymentMethod from '../sendBitcoin/components/choosePaymentMethodContainer';
import displayCorrectDenomination from '../../../../functions/displayCorrectDenomination';
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
import usePaymentValidation from '../sendBitcoin/functions/paymentValidation';
import { InputTypes } from 'bitcoin-address-parser';
import usePaymentInputDisplay from '../../../../hooks/usePaymentInputDisplay';
import ContactProfileImage from './internalComponents/profileImage';
const MAX_SEND_OPTIONS = [
{ label: '25%', value: '25' },
{ label: '50%', value: '50' },
{ label: '75%', value: '75' },
{ label: '100%', value: '100' },
];
export default function SendAndRequestPage(props) {
const {
selectedRequestMethod = 'BTC',
selectedPaymentMethod = 'BTC',
endReceiveType = 'BTC',
selectedContact,
paymentType, // 'send' or 'request'
imageData,
cardInfo: giftOption,
} = props.route.params || {};
const navigate = useNavigation();
const { dollarBalanceSat, dollarBalanceToken, bitcoinBalance } =
useUserBalanceContext();
const { poolInfoRef, swapLimits, swapUSDPriceDollars } = useFlashnet();
const { masterInfoObject } = useGlobalContextProvider();
const { sparkInformation } = useSparkWallet();
const { contactsPrivateKey, publicKey } = useKeysContext();
const { isConnectedToTheInternet } = useAppStatus();
const { fiatStats } = useNodeContext();
const { globalContactsInformation } = useGlobalContactsInfo();
const getServerTime = useServerTimeOnly();
const [amountValue, setAmountValue] = useState('');
const [isDescriptionFocused, setIsDescriptionFocused] = useState(false);
const [descriptionValue, setDescriptionValue] = useState(
giftOption?.memo || '',
);
const [isLoading, setIsLoading] = useState(false);
const { bottomPadding } = useGlobalInsets();
const { theme, darkModeType } = useGlobalThemeContext();
const { backgroundOffset, textColor, backgroundColor } = GetThemeColors();
const { t } = useTranslation();
const poolInfoRefSnapshotRef = useRef(poolInfoRef);
const prefSelectedPaymentInfo = useRef({
selectedPaymentMethod,
selectedRequestMethod,
});
const descriptionRef = useRef(null);
useEffect(() => {
if (typeof giftOption?.memo !== 'string') return;
setDescriptionValue(giftOption.memo);
}, [giftOption?.memo]);
const paymentMode =
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod
: selectedRequestMethod;
// Determine if we're in USD mode
const isUSDMode =
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod === 'USD'
: selectedRequestMethod === 'USD';
const [userSetInputDenomination, setUserSetInputDenomination] =
useState(null);
const inputDenomination = userSetInputDenomination
? userSetInputDenomination
: paymentMode === 'USD'
? 'fiat'
: masterInfoObject.userBalanceDenomination !== 'fiat'
? 'sats'
: 'fiat';
const {
primaryDisplay,
secondaryDisplay,
conversionFiatStats,
convertDisplayToSats,
convertSatsToDisplay,
getNextDenomination,
convertForToggle,
} = usePaymentInputDisplay({
paymentMode,
inputDenomination,
fiatStats,
usdFiatStats: { coin: 'USD', value: swapUSDPriceDollars },
masterInfoObject,
});
const displayAmount =
paymentType !== 'Gift' ? amountValue : convertSatsToDisplay(amountValue);
// Calculate sat amount
const convertedSendAmount =
paymentType === 'Gift'
? Number(amountValue)
: convertDisplayToSats(amountValue);
const min_usd_swap_amount = useMemo(() => {
return Math.round(
dollarsToSats(
swapLimits.usd,
poolInfoRefSnapshotRef.current.currentPriceAInB,
),
);
}, [poolInfoRefSnapshotRef.current.currentPriceAInB, swapLimits]);
const paymentValidation = usePaymentValidation({
paymentInfo: {
sendAmount: convertedSendAmount,
paymentNetwork:
giftOption || selectedContact?.isLNURL ? 'lightning' : 'spark',
isLNURLPayment: selectedContact?.isLNURL,
data: {
expectedReceive: endReceiveType === 'BTC' ? 'sats' : 'tokens',
expectedToken: endReceiveType === 'BTC' ? null : USDB_TOKEN_ID,
},
decodedInput: {
tpye:
giftOption || selectedContact?.isLNURL ? InputTypes.BOLT11 : 'spark',
data: { amountMsat: convertedSendAmount * 1000 },
},
},
convertedSendAmount,
paymentFee: 0, //need to calculate swap fee,
determinePaymentMethod: selectedPaymentMethod,
selectedPaymentMethod,
bitcoinBalance: bitcoinBalance,
dollarBalanceSat: dollarBalanceSat,
min_usd_swap_amount: min_usd_swap_amount,
swapLimits: swapLimits,
isUsingLRC20: false,
canEditAmount: true,
t,
masterInfoObject,
fiatStats,
inputDenomination: primaryDisplay.denomination,
primaryDisplay,
conversionFiatStats,
sparkInformation,
});
const handleSelectPaymentMethod = useCallback(() => {
if (paymentType === 'send' || paymentType === 'Gift') {
navigate.navigate('CustomHalfModal', {
wantedContent: 'SelectPaymentMethod',
selectedPaymentMethod: selectedPaymentMethod,
fromPage: 'SendAndRequestPage',
});
} else {
navigate.navigate('CustomHalfModal', {
wantedContent: 'SelectContactRequestCurrency',
selectedRecieveOption: selectedRequestMethod,
});
}
}, [navigate, selectedPaymentMethod, selectedRequestMethod]);
useEffect(() => {
if (
prefSelectedPaymentInfo.current.selectedPaymentMethod !==
selectedPaymentMethod ||
prefSelectedPaymentInfo.current.selectedRequestMethod !==
selectedRequestMethod
) {
setAmountValue('');
setUserSetInputDenomination(null);
prefSelectedPaymentInfo.current = {
selectedPaymentMethod,
selectedRequestMethod,
};
}
}, [selectedPaymentMethod, selectedRequestMethod]);
useEffect(() => {
if (!giftOption) {
setAmountValue('');
return;
}
const totalSats = Math.round(
giftOption.selectedDenomination * giftOption.satsPerDollar,
);
const localfiatSatsPerDollar =
(primaryDisplay.forceFiatStats?.value || fiatStats.value) /
SATSPERBITCOIN;
setAmountValue(
String(
primaryDisplay.denomination !== 'fiat'
? totalSats
: Math.round(localfiatSatsPerDollar * totalSats),
),
);
}, [giftOption]);
const canProceed =
paymentType === 'request' ? !!amountValue : paymentValidation.canProceed;
const handleDenominationToggle = () => {
if (isDescriptionFocused) return;
if (paymentType === 'Gift') {
const nextDenom = getNextDenomination();
setUserSetInputDenomination(nextDenom);
} else {
const nextDenom = getNextDenomination();
setUserSetInputDenomination(nextDenom);
setAmountValue(convertForToggle(amountValue, convertTextInputValue));
}
};
const handleSubmit = useCallback(async () => {
try {
if (!canProceed) {
const error = paymentValidation.getErrorMessage(
paymentValidation.primaryError,
);
navigate.navigate('ErrorScreen', {
errorMessage: error,
});
return;
}
if (!isConnectedToTheInternet) {
navigate.navigate('ErrorScreen', {
errorMessage: t('errormessages.nointernet'),
});
return;
}
crashlyticsLogReport('Submitting send and request payment');
setIsLoading(true);
const sendingAmountMsat = convertedSendAmount * 1000;
const contactMessage = descriptionValue;
const myProfileMessage = !!descriptionValue
? descriptionValue
: t('contacts.sendAndRequestPage.profileMessage', {
name: selectedContact.name || selectedContact.uniqueName,
});
const payingContactMessage = !!descriptionValue
? descriptionValue
: {
usingTranslation: true,
type: 'paid',
name:
globalContactsInformation.myProfile.name ||
globalContactsInformation.myProfile.uniqueName,
};
const currentTime = getServerTime();
const UUID = customUUID();
let sendObject = {};
if (globalContactsInformation.myProfile.uniqueName) {
sendObject['senderProfileSnapshot'] = {
uniqueName: globalContactsInformation.myProfile.uniqueName,
};
}
if (giftOption) {
const retrivedContact = await getDataFromCollection(
'blitzWalletUsers',
selectedContact.uuid,
);
if (!retrivedContact) {
navigate.navigate('ErrorScreen', {
errorMessage: 'errormessages.fullDeeplinkError',
});
return;
}
if (!retrivedContact.enabledGiftCards) {
navigate.navigate('ErrorScreen', {
errorMessage: t(
'contacts.sendAndRequestPage.giftCardappVersionError',
),
});
return;
}
const postData = {
type: 'buyGiftCard',
productId: giftOption.id, //string
cardValue: giftOption.selectedDenomination, //number
quantity: Number(1), //number
};
const response = await fetchBackend(
'theBitcoinCompanyV3',
postData,
contactsPrivateKey,
publicKey,
);
if (response.result) {
const { amount, invoice, orderId, uuid } = response.result;
const fiatRates = await (fiatStats.coin?.toLowerCase() === 'usd'
? Promise.resolve({ didWork: true, fiatRateResponse: fiatStats })
: loadNewFiatData(
'usd',
contactsPrivateKey,
publicKey,
masterInfoObject,
));
const USDBTCValue = fiatRates.didWork
? fiatRates.fiatRateResponse
: { coin: 'USD', value: 100_000 };
const sendingAmountSat = amount;
const isOverDailyLimit = await giftCardPurchaseAmountTracker({
sendingAmountSat: sendingAmountSat,
USDBTCValue: USDBTCValue,
testOnly: true,
});
if (isOverDailyLimit.shouldBlock) {
navigate.navigate('ErrorScreen', {
errorMessage: isOverDailyLimit.reason,
});
return;
}
sendObject['amountMsat'] = amount;
sendObject['description'] = descriptionValue || '';
sendObject['uuid'] = UUID;
sendObject['isRequest'] = false;
sendObject['isRedeemed'] = null;
sendObject['wasSeen'] = null;
sendObject['didSend'] = null;
sendObject['giftCardInfo'] = {
amount,
invoice,
orderId,
uuid,
logo: giftOption.logo,
name: giftOption.name,
};
navigate.navigate('ConfirmPaymentScreen', {
btcAdress: invoice,
comingFromAccept: true,
enteredPaymentInfo: {
fromContacts: true,
amount: amount,
description:
descriptionValue ||
t('contacts.sendAndRequestPage.giftCardDescription', {
name: selectedContact.name || selectedContact.uniqueName,
giftCardName: giftOption.name,
}),
},
contactInfo: {
imageData,
name: selectedContact.name || selectedContact.uniqueName,
uniqueName: selectedContact.uniqueName,
uuid: selectedContact.uuid,
},
preSelectedPaymentMethod: selectedPaymentMethod,
selectedPaymentMethod: selectedPaymentMethod,
fromPage: 'contacts',
publishMessageFunc: () => {
giftCardPurchaseAmountTracker({
sendingAmountSat: sendingAmountSat,
USDBTCValue: USDBTCValue,
});
publishMessage({
toPubKey: selectedContact.uuid,
fromPubKey: globalContactsInformation.myProfile.uuid,
data: sendObject,
globalContactsInformation,
selectedContact,
isLNURLPayment: false,
privateKey: contactsPrivateKey,
retrivedContact,
currentTime,
masterInfoObject,
});
},
});
} else {
navigate.navigate('ErrorScreen', {
errorMessage: t('contacts.sendAndRequestPage.cardDetailsError'),
});
}
return;
}
const {
receiveAddress,
retrivedContact,
didWork,
error,
formattedPayingContactMessage,
} = await getReceiveAddressAndContactForContactsPayment({
sendingAmountSat: convertedSendAmount,
selectedContact,
myProfileMessage,
payingContactMessage,
onlyGetContact: paymentType !== 'send',
});
if (!didWork) {
navigate.navigate('ErrorScreen', {
errorMessage: error,
useTranslationString: true,
});
return;
}
sendObject['amountMsat'] = sendingAmountMsat;
sendObject['uuid'] = UUID;
sendObject['wasSeen'] = null;
sendObject['didSend'] = null;
sendObject['isRedeemed'] = null;
if (paymentType === 'send') {
sendObject['description'] = contactMessage;
sendObject['isRequest'] = false;
sendObject['paymentDenomination'] = endReceiveType;
sendObject['amountDollars'] =
endReceiveType === 'USD'
? satsToDollars(
convertedSendAmount,
poolInfoRefSnapshotRef.current.currentPriceAInB,
).toFixed(2)
: null;
navigate.navigate('ConfirmPaymentScreen', {
btcAdress: receiveAddress,
comingFromAccept: true,
enteredPaymentInfo: {
fromContacts: true,
amount: convertedSendAmount,
description: myProfileMessage,
endReceiveType: endReceiveType,
},
contactInfo: {
imageData,
name: selectedContact.name || selectedContact.uniqueName,
isLNURLPayment: selectedContact?.isLNURL,
payingContactMessage: formattedPayingContactMessage, //handles remote tx description
uniqueName: retrivedContact?.contacts?.myProfile?.uniqueName,
uuid: selectedContact.uuid,
},
preSelectedPaymentMethod: selectedPaymentMethod,
selectedPaymentMethod: selectedPaymentMethod,
fromPage: 'contacts',
publishMessageFunc: txid =>
publishMessage({
toPubKey: selectedContact.uuid,
fromPubKey: globalContactsInformation.myProfile.uuid,
data: {
...sendObject,
txid,
name:
globalContactsInformation.myProfile.name ||
globalContactsInformation.myProfile.uniqueName,
},
globalContactsInformation,
selectedContact,
isLNURLPayment: selectedContact?.isLNURL,
privateKey: contactsPrivateKey,
retrivedContact,
currentTime,
masterInfoObject,
}),
});
} else {
sendObject['amountDollars'] =
selectedRequestMethod === 'USD'
? satsToDollars(
convertedSendAmount,
poolInfoRefSnapshotRef.current.currentPriceAInB,
).toFixed(2)
: null;
sendObject['description'] = descriptionValue;
sendObject['isRequest'] = true;
sendObject['paymentDenomination'] = selectedRequestMethod;
await publishMessage({
toPubKey: selectedContact.uuid,
fromPubKey: globalContactsInformation.myProfile.uuid,
data: sendObject,
globalContactsInformation,
selectedContact,
isLNURLPayment: selectedContact?.isLNURL,
privateKey: contactsPrivateKey,
retrivedContact,
currentTime,
masterInfoObject,
});
navigate.goBack();
}
} catch (err) {
console.log(err, 'publishing message error');
navigate.navigate('ErrorScreen', {
errorMessage: selectedContact.isLNURL
? t('errormessages.contactInvoiceGenerationError')
: t('errormessages.invoiceRetrivalError'),
});
} finally {
setIsLoading(false);
}
}, [
isConnectedToTheInternet,
convertedSendAmount,
canProceed,
selectedContact,
navigate,
contactsPrivateKey,
descriptionValue,
paymentType,
globalContactsInformation,
getServerTime,
giftOption,
masterInfoObject,
fiatStats,
imageData,
paymentValidation,
selectedPaymentMethod,
primaryDisplay,
isUSDMode,
inputDenomination,
]);
const handleEmoji = newDescription => {
setDescriptionValue(newDescription);
};
const memorizedKeyboardStyle = useMemo(() => {
return {
paddingBottom: !isDescriptionFocused ? bottomPadding : 0,
};
}, [isDescriptionFocused]);
return (
<CustomKeyboardAvoidingView globalThemeViewStyles={memorizedKeyboardStyle}>
<View
style={[
styles.replacementContainer,
isDescriptionFocused ? { flexShrink: 1 } : { flexGrow: 1 },
]}
>
<CustomSettingsTopBar
label={
paymentType === 'Gift'
? t('constants.gift')
: paymentType === 'send'
? t('constants.send')
: t('constants.request')
}
containerStyles={{ marginBottom: 0 }}
/>
{paymentType === 'request' && (
<ThemeText
styles={styles.requestTypeIndicator}
content={
selectedRequestMethod === 'BTC'
? t('constants.bitcoin_upper')
: t('constants.dollars_upper')
}
/>
)}
<View style={styles.identityBadge}>
<TouchableOpacity
activeOpacity={0.7}
style={styles.splitPayContainer}
onPress={() =>
navigate.navigate('AddFriendsToSplit', {
paymentType,
selectedContact,
paymentCurrency: isUSDMode ? 'USD' : 'BTC',
})
}
>
<View
style={[
styles.contactListLetterImage,
{ backgroundColor: backgroundOffset },
]}
>
<ContactProfileImage
updated={imageData?.updated}
uri={imageData?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
<View style={[styles.splitPayIcon]}>
<ThemeIcon size={25} iconName={'CirclePlus'} />
</View>
</TouchableOpacity>
<View style={styles.identityTextContainer}>
<ThemeText
styles={styles.contactName}
content={
selectedContact?.name || selectedContact?.uniqueName || ''
}
/>
</View>
</View>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={[
styles.scrollViewContainer,
paymentType === 'Gift' && { justifyContent: 'flex-start' },
{
opacity:
!isDescriptionFocused || paymentType === 'Gift'
? 1
: HIDDEN_OPACITY,
},
]}
>
<TouchableOpacity
activeOpacity={1}
onPress={handleDenominationToggle}
>
<FormattedBalanceInput
maxWidth={0.9}
amountValue={displayAmount || 0}
inputDenomination={primaryDisplay.denomination}
forceCurrency={primaryDisplay.forceCurrency}
forceFiatStats={primaryDisplay.forceFiatStats}
/>
<FormattedSatText
containerStyles={{
...styles.convertedAmount,
opacity: !amountValue ? HIDDEN_OPACITY : 1,
}}
neverHideBalance={true}
globalBalanceDenomination={secondaryDisplay.denomination}
forceCurrency={secondaryDisplay.forceCurrency}
forceFiatStats={secondaryDisplay.forceFiatStats}
balance={convertedSendAmount}
/>
</TouchableOpacity>
{giftOption && (
<>
<View style={styles.giftAmountContainer}>
<TouchableOpacity
onPress={() =>
navigate.navigate('SelectGiftCardForContacts', {
selectedContact,
imageData,
})
}
style={[
styles.pill,
{
backgroundColor: theme
? backgroundOffset
: COLORS.darkModeText,
},
]}
>
<View
style={[
styles.logoContainer,
{
backgroundColor: theme
? COLORS.darkModeText
: backgroundColor,
},
]}
>
<Image
style={styles.cardLogo}
source={{ uri: giftOption.logo }}
contentFit="contain"
/>
</View>
<ThemeText
CustomNumberOfLines={1}
styles={styles.pillText}
content={t('contacts.sendAndRequestPage.giftCardText', {
giftName: giftOption.name,
})}
/>
<View style={[styles.editButton]}>
<ThemeIcon
colorOverride={textColor}
size={18}
iconName={'SquarePen'}
/>
</View>
</TouchableOpacity>
<View style={styles.memoSection}>
<ThemeText
styles={styles.memoLabel}
content={t('contacts.sendAndRequestPage.giftMessage')}
/>
<CustomSearchInput
textInputMultiline={true}
placeholderText={t(
'contacts.sendAndRequestPage.giftMessagePlaceholder',
)}
onFocusFunction={() => setIsDescriptionFocused(true)}
onBlurFunction={() => setIsDescriptionFocused(false)}
inputText={descriptionValue}
setInputText={setDescriptionValue}
maxLength={500}
textInputStyles={{ minHeight: 100 }}
textAlignVertical="top"
/>
</View>
{paymentType === 'Gift' && (
<View>
<ThemeText
styles={styles.memoLabel}
content={t(
'contacts.sendAndRequestPage.paymentSourceHeader',
)}
/>
<ChoosePaymentMethod
theme={theme}
darkModeType={darkModeType}
determinePaymentMethod={
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod
: selectedRequestMethod
}
handleSelectPaymentMethod={handleSelectPaymentMethod}
bitcoinBalance={sparkInformation.balance}
dollarBalanceToken={dollarBalanceToken}
masterInfoObject={masterInfoObject}
fiatStats={fiatStats}
uiState={
paymentType === 'send' || paymentType === 'Gift'
? 'SELECT_INLINE'
: 'CONTACT_REQUEST'
}
t={t}
selectedMethod={
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod
: selectedRequestMethod
}
containerStyles={{ width: '100%', marginTop: 0 }}
/>
</View>
)}
</View>
</>
)}
</ScrollView>
{paymentType !== 'Gift' && (
<View style={styles.inputAndGiftContainer}>
{paymentType === 'send' && (
<ChoosePaymentMethod
theme={theme}
darkModeType={darkModeType}
determinePaymentMethod={
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod
: selectedRequestMethod
}
handleSelectPaymentMethod={handleSelectPaymentMethod}
bitcoinBalance={sparkInformation.balance}
dollarBalanceToken={dollarBalanceToken}
masterInfoObject={masterInfoObject}
fiatStats={fiatStats}
uiState={
paymentType === 'send' || paymentType === 'Gift'
? 'SELECT_INLINE'
: 'CONTACT_REQUEST'
}
t={t}
selectedMethod={
paymentType === 'send' || paymentType === 'Gift'
? selectedPaymentMethod
: selectedRequestMethod
}
containerStyles={{ width: '100%', marginBottom: 8 }}
/>
)}
<CustomSearchInput
onFocusFunction={() => {
setIsDescriptionFocused(true);
}}
onBlurFunction={() => {
setIsDescriptionFocused(false);
}}
textInputRef={descriptionRef}
placeholderText={t('constants.paymentDescriptionPlaceholder')}
editable={paymentType === 'send' ? true : !!convertedSendAmount}
containerStyles={styles.descriptionInput}
setInputText={setDescriptionValue}
inputText={descriptionValue}
textInputMultiline={true}
textAlignVertical={'center'}
maxLength={149}
/>
</View>
)}
{!isDescriptionFocused && (
<View>
{paymentType !== 'Gift' && (
<CustomNumberKeyboard
showDot={primaryDisplay.denomination === 'fiat'}
frompage="sendContactsPage"
setInputValue={setAmountValue}
usingForBalance={true}
fiatStats={conversionFiatStats}
/>
)}
<CustomButton
buttonStyles={{
...styles.button,
opacity: canProceed ? 1 : HIDDEN_OPACITY,
}}
useLoading={isLoading}
actionFunction={handleSubmit}
textContent={
paymentType === 'send' || paymentType === 'Gift'
? t('constants.review')
: t('constants.request')
}
/>
</View>
)}
</View>
{isDescriptionFocused && (
<EmojiQuickBar
description={descriptionValue}
onEmojiSelect={handleEmoji}
/>
)}
</CustomKeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
replacementContainer: {
flexGrow: 1,
width: WINDOWWIDTH,
...CENTER,
},
requestTypeIndicator: { textAlign: 'center', opacity: HIDDEN_OPACITY },
identityBadge: {
alignItems: 'center',
alignSelf: 'center',
marginTop: 26,
gap: 10,
marginBottom: CONTENT_KEYBOARD_OFFSET,
},
splitPayContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 5,
paddingHorizontal: 5,
},
splitPayIcon: {
width: 25,
height: 25,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
right: -25,
},
contactListLetterImage: {
height: 60,
width: 60,
borderRadius: 30,
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
identityTextContainer: {
flexDirection: 'column',
gap: 2,
},
contactName: {
fontSize: SIZES.medium,
includeFontPadding: false,
},
scrollViewContainer: {
paddingTop: 5,
paddingBottom: 20,
alignItems: 'center',
// justifyContent: 'center',
flexGrow: 1,
},
convertedAmount: {
marginBottom: 16,
},
inputAndGiftContainer: {
width: INSET_WINDOW_WIDTH,
alignSelf: 'center',
},
giftAmountContainer: {
width: INSET_WINDOW_WIDTH,
marginTop: 20,
gap: 20,
},
giftAmountText: {
textAlign: 'center',
},
giftContainer: {
marginTop: CONTENT_KEYBOARD_OFFSET,
paddingVertical: 10,
paddingHorizontal: 16,
borderRadius: 8,
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'flex-end',
},
giftText: {
marginRight: 8,
includeFontPadding: false,
},
button: {
width: 'auto',
...CENTER,
},
pill: {
width: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
borderRadius: 8,
gap: 16,
alignSelf: 'center',
position: 'relative',
},
logoContainer: {
width: 48,
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: COLORS.darkModeText,
borderRadius: 12,
padding: 6,
},
cardLogo: {
width: '100%',
height: '100%',
maxWidth: 80,
maxHeight: 80,
borderRadius: 8,
},
pillText: {
flexShrink: 1,
includeFontPadding: false,
fontSize: SIZES.medium,
marginRight: 20,
},
editButton: {
width: 32,
height: 32,
alignItems: 'center',
justifyContent: 'center',
marginLeft: 'auto',
},
editIcon: {
width: 18,
height: 18,
},
memoSection: {},
memoLabel: {
marginBottom: 10,
opacity: 0.8,
fontSize: SIZES.smedium,
},
memoContainer: {
justifyContent: 'center',
},
memoText: {
lineHeight: 22,
letterSpacing: 0.3,
fontSize: SIZES.medium,
},
descriptionInput: {
marginTop: 8,
},
});