add contact filtering to search bar when using @
This commit is contained in:
@@ -364,11 +364,14 @@ const BusinessCard = React.memo(
|
||||
style={[
|
||||
styles.button,
|
||||
{
|
||||
paddingTop: item?.description ? 24 : 0,
|
||||
marginTop: item?.description ? 24 : 0,
|
||||
borderTopColor: backgroundColor,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
]}
|
||||
onPress={handleWebsitePress}
|
||||
activeOpacity={0.8}
|
||||
hitSlop={{ top: 20, bottom: 20, left: 10, right: 10 }}
|
||||
>
|
||||
<View style={styles.buttonCopy}>
|
||||
<ThemeIcon
|
||||
@@ -382,11 +385,7 @@ const BusinessCard = React.memo(
|
||||
/>
|
||||
</View>
|
||||
|
||||
<ThemeIcon
|
||||
size={16}
|
||||
colorOverride={theme ? COLORS.darkModeText : COLORS.lightModeText}
|
||||
iconName="ArrowUpRight"
|
||||
/>
|
||||
<ThemeIcon size={16} iconName="ArrowUpRight" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
@@ -474,8 +473,7 @@ const styles = StyleSheet.create({
|
||||
opacity: 0.85,
|
||||
},
|
||||
button: {
|
||||
paddingTop: 24,
|
||||
borderRadius: 18,
|
||||
paddingTop: 16,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import {
|
||||
FlatList,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {
|
||||
CENTER,
|
||||
CONTENT_KEYBOARD_OFFSET,
|
||||
@@ -35,14 +42,18 @@ import Animated, {
|
||||
withTiming,
|
||||
} from 'react-native-reanimated';
|
||||
import ThemeImage from '../../../../functions/CustomElements/themeImage';
|
||||
import { useProcessedContacts } from '../contacts/contactsPageComponents/hooks';
|
||||
import {
|
||||
useProcessedContacts,
|
||||
useFilteredContacts,
|
||||
} from '../contacts/contactsPageComponents/hooks';
|
||||
import getClipboardText from '../../../../functions/getClipboardText';
|
||||
import { AddContactOverlay } from '../contacts/addContactOverlay';
|
||||
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
|
||||
import CustomButton from '../../../../functions/CustomElements/button';
|
||||
import getReceiveAddressAndContactForContactsPayment from '../contacts/internalComponents/getReceiveAddressAndKindForPayment';
|
||||
import { parse } from 'tldts';
|
||||
import { hasStringAsync } from 'expo-clipboard';
|
||||
import { scheduleOnRN } from 'react-native-worklets';
|
||||
import { KeyboardController } from 'react-native-keyboard-controller';
|
||||
|
||||
const ContactRow = ({
|
||||
contact,
|
||||
@@ -85,17 +96,6 @@ const ContactRow = ({
|
||||
transform: [{ rotate: `${chevronRotation.value * 180}deg` }],
|
||||
}));
|
||||
|
||||
const lnurlDomain = useMemo(() => {
|
||||
try {
|
||||
if (!contact.isLNURL) return '';
|
||||
const parsed = parse(contact.receiveAddress);
|
||||
return parsed.domain;
|
||||
} catch (err) {
|
||||
console.log('error parsing lnurl', err);
|
||||
return '';
|
||||
}
|
||||
}, [contact.isLNURL]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.contactWrapper}
|
||||
@@ -130,21 +130,6 @@ const ContactRow = ({
|
||||
styles={styles.contactName}
|
||||
content={formatDisplayName(contact) || contact.uniqueName || ''}
|
||||
/>
|
||||
{contact.isLNURL && lnurlDomain && (
|
||||
<View
|
||||
style={[
|
||||
styles.lnurlDomainContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeText styles={styles.lnurlDomain} content={lnurlDomain} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Animated.View style={labelFadeStyle}>
|
||||
<ThemeText
|
||||
@@ -153,12 +138,8 @@ const ContactRow = ({
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
<Animated.View style={[{ opacity: HIDDEN_OPACITY }, chevronStyle]}>
|
||||
<ThemeIcon
|
||||
size={20}
|
||||
iconName={'ChevronDown'}
|
||||
colorOverride={textColor}
|
||||
/>
|
||||
<Animated.View style={chevronStyle}>
|
||||
<ThemeIcon size={20} iconName={'ChevronDown'} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -289,6 +270,49 @@ const ContactRow = ({
|
||||
);
|
||||
};
|
||||
|
||||
const FilteredContactItem = ({
|
||||
contact,
|
||||
cache,
|
||||
theme,
|
||||
darkModeType,
|
||||
backgroundOffset,
|
||||
backgroundColor,
|
||||
onPress,
|
||||
}) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.contactRowContainer}
|
||||
onPress={() => onPress(contact)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.contactImageContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ContactProfileImage
|
||||
updated={cache[contact.uuid]?.updated}
|
||||
uri={cache[contact.uuid]?.localUri}
|
||||
darkModeType={darkModeType}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.nameContainer}>
|
||||
<ThemeText
|
||||
CustomEllipsizeMode={'tail'}
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.contactName}
|
||||
content={formatDisplayName(contact) || contact.uniqueName || ''}
|
||||
/>
|
||||
</View>
|
||||
<ThemeIcon size={20} iconName={'ChevronRight'} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default function HalfModalSendOptions({
|
||||
setIsKeyboardActive,
|
||||
isKeyboardActive,
|
||||
@@ -302,6 +326,7 @@ export default function HalfModalSendOptions({
|
||||
const [inputError, setInputError] = useState('');
|
||||
const [expandedContact, setExpandedContact] = useState(null);
|
||||
const [showAddContact, setShowAddContact] = useState(false);
|
||||
const [noInputMounted, setNoInputMounted] = useState(true);
|
||||
const [visibleCount, setVisibleCount] = useState(8);
|
||||
const [scrollViewHeight, setScrollViewHeight] = useState(0);
|
||||
const [showPasteButton, setShowPasteButton] = useState(true);
|
||||
@@ -319,12 +344,22 @@ export default function HalfModalSendOptions({
|
||||
useGlobalContacts();
|
||||
const { t } = useTranslation();
|
||||
const { backgroundColor, backgroundOffset, textColor } = GetThemeColors();
|
||||
const isContactInputMode = inputText.startsWith('@');
|
||||
|
||||
const contactInfoList = useProcessedContacts(
|
||||
decodedAddedContacts,
|
||||
contactsMessags,
|
||||
);
|
||||
|
||||
const filteredContacts = useFilteredContacts(
|
||||
isContactInputMode ? contactInfoList : [],
|
||||
isContactInputMode ? inputText.trim() : '',
|
||||
false,
|
||||
);
|
||||
const contactSearchUsername = useMemo(() => {
|
||||
return inputText.startsWith('@') ? inputText.slice(1).trim() : '';
|
||||
}, [inputText]);
|
||||
|
||||
const contentOpacity = useSharedValue(1);
|
||||
const contentTranslateX = useSharedValue(0);
|
||||
const inputModeProgress = useSharedValue(0);
|
||||
@@ -347,9 +382,14 @@ export default function HalfModalSendOptions({
|
||||
}, [showAddContact]);
|
||||
|
||||
useEffect(() => {
|
||||
inputModeProgress.value = withTiming(isInputMode ? 1 : 0, {
|
||||
duration: 220,
|
||||
});
|
||||
if (isInputMode) {
|
||||
inputModeProgress.value = withTiming(1, { duration: 220 }, finished => {
|
||||
if (finished) scheduleOnRN(setNoInputMounted, false);
|
||||
});
|
||||
} else {
|
||||
setNoInputMounted(true);
|
||||
inputModeProgress.value = withTiming(0, { duration: 220 });
|
||||
}
|
||||
}, [isInputMode]);
|
||||
|
||||
const contentStyle = useAnimatedStyle(() => ({
|
||||
@@ -357,11 +397,11 @@ export default function HalfModalSendOptions({
|
||||
transform: [{ translateX: contentTranslateX.value }],
|
||||
}));
|
||||
|
||||
const belowInputStyle = useAnimatedStyle(() => ({
|
||||
const noInputPageStyle = useAnimatedStyle(() => ({
|
||||
opacity: 1 - inputModeProgress.value,
|
||||
}));
|
||||
|
||||
const continueButtonStyle = useAnimatedStyle(() => ({
|
||||
const inputPageStyle = useAnimatedStyle(() => ({
|
||||
opacity: inputModeProgress.value,
|
||||
}));
|
||||
|
||||
@@ -398,6 +438,7 @@ export default function HalfModalSendOptions({
|
||||
const normalized = input.startsWith('@')
|
||||
? input.slice(1).toLowerCase()
|
||||
: input.toLowerCase();
|
||||
if (!normalized) return;
|
||||
const matchedContact = decodedAddedContacts.find(
|
||||
c => c.uniqueName?.toLowerCase() === normalized,
|
||||
);
|
||||
@@ -779,244 +820,342 @@ export default function HalfModalSendOptions({
|
||||
visibleCount,
|
||||
]);
|
||||
|
||||
const handleFilteredContactPress = useCallback(
|
||||
async contact => {
|
||||
handleBackPressFunction(async () => {
|
||||
navigate.replace('SendAndRequestPage', {
|
||||
selectedContact: contact,
|
||||
paymentType: 'send',
|
||||
imageData: cache[contact.uuid],
|
||||
endReceiveType: 'BTC',
|
||||
selectedPaymentMethod: 'BTC',
|
||||
});
|
||||
});
|
||||
},
|
||||
[navigate, globalContactsInformation, cache, t, handleBackPressFunction],
|
||||
);
|
||||
|
||||
const renderFilteredContact = useCallback(
|
||||
({ item: { contact } }) => (
|
||||
<FilteredContactItem
|
||||
contact={contact}
|
||||
cache={cache}
|
||||
theme={theme}
|
||||
darkModeType={darkModeType}
|
||||
backgroundOffset={backgroundOffset}
|
||||
backgroundColor={backgroundColor}
|
||||
onPress={handleFilteredContactPress}
|
||||
/>
|
||||
),
|
||||
[
|
||||
cache,
|
||||
theme,
|
||||
darkModeType,
|
||||
backgroundOffset,
|
||||
backgroundColor,
|
||||
handleFilteredContactPress,
|
||||
],
|
||||
);
|
||||
|
||||
const filteredContactKeyExtractor = useCallback(
|
||||
({ contact }) => contact.uuid,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Animated.View style={[styles.mainContent, contentStyle]}>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
scrollEnabled={!isInputMode}
|
||||
contentContainerStyle={{
|
||||
...styles.innerContainer,
|
||||
paddingBottom: bottomPadding,
|
||||
}}
|
||||
stickyHeaderIndices={[4]}
|
||||
onScroll={e => {
|
||||
scrollOffsetRef.current = e.nativeEvent.contentOffset.y;
|
||||
}}
|
||||
scrollEventThrottle={16}
|
||||
onLayout={e => {
|
||||
const h = e.nativeEvent.layout.height;
|
||||
scrollViewHeightRef.current = h;
|
||||
setScrollViewHeight(h);
|
||||
}}
|
||||
{/* Search Input with Clipboard Icon */}
|
||||
<View
|
||||
style={[
|
||||
styles.searchContainer,
|
||||
{
|
||||
backgroundColor: backgroundColor,
|
||||
maxHeight: Math.round(scrollViewHeight - 50),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* Search Input with Clipboard Icon */}
|
||||
<View
|
||||
style={[
|
||||
styles.searchContainer,
|
||||
{
|
||||
backgroundColor: backgroundColor,
|
||||
maxHeight: Math.round(scrollViewHeight - 50),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CustomSearchInput
|
||||
textInputRef={textInputRef}
|
||||
placeholderText={t('wallet.halfModal.inputPlaceholder')}
|
||||
textInputMultiline={true}
|
||||
inputText={inputText}
|
||||
setInputText={setInputText}
|
||||
onBlurFunction={onBlurFunction}
|
||||
onFocusFunction={onFocusFunction}
|
||||
textInputStyles={{
|
||||
paddingRight: showPasteButton || inputText.trim() ? 40 : 10,
|
||||
}}
|
||||
returnKeyType="go"
|
||||
onSubmitEditingFunction={handleManualInputSubmit}
|
||||
/>
|
||||
{inputText.trim() ? (
|
||||
<TouchableOpacity
|
||||
onPress={onTrimFunction}
|
||||
style={styles.clipboardButton}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType
|
||||
? COLORS.lightModeText
|
||||
: COLORS.primary
|
||||
}
|
||||
size={20}
|
||||
iconName={'X'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : showPasteButton ? (
|
||||
<TouchableOpacity
|
||||
onPress={handleClipboardPaste}
|
||||
style={styles.clipboardButton}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType
|
||||
? COLORS.lightModeText
|
||||
: COLORS.primary
|
||||
}
|
||||
size={20}
|
||||
iconName={'Clipboard'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<Animated.View
|
||||
style={belowInputStyle}
|
||||
pointerEvents={isInputMode ? 'none' : 'auto'}
|
||||
>
|
||||
{/* Scan QR Code Button */}
|
||||
<CustomSearchInput
|
||||
textInputRef={textInputRef}
|
||||
placeholderText={t('wallet.halfModal.inputPlaceholder')}
|
||||
textInputMultiline={true}
|
||||
inputText={inputText}
|
||||
setInputText={setInputText}
|
||||
onBlurFunction={onBlurFunction}
|
||||
onFocusFunction={onFocusFunction}
|
||||
textInputStyles={{
|
||||
paddingRight: showPasteButton || inputText.trim() ? 40 : 10,
|
||||
}}
|
||||
returnKeyType="go"
|
||||
onSubmitEditingFunction={handleManualInputSubmit}
|
||||
/>
|
||||
{inputText.trim() ? (
|
||||
<TouchableOpacity
|
||||
style={[styles.scanButton, { marginBottom: 0 }]}
|
||||
onPress={handleCameraScan}
|
||||
onPress={onTrimFunction}
|
||||
style={styles.clipboardButton}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.scanIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary
|
||||
}
|
||||
size={24}
|
||||
iconName={'ScanQrCode'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.scanTextContainer}>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonText}
|
||||
content={t('wallet.halfModal.scanQrCode')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonSubtext}
|
||||
content={t('wallet.halfModal.tapToScanQr')}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Scan Image Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.scanButton}
|
||||
onPress={handleImageScan}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.scanIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType ? COLORS.darkModeText : COLORS.primary
|
||||
}
|
||||
size={24}
|
||||
iconName={'Image'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.scanTextContainer}>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonText}
|
||||
content={t('wallet.halfModal.images')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonSubtext}
|
||||
content={t('wallet.halfModal.tapToScan')}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Divider */}
|
||||
<View
|
||||
style={[
|
||||
styles.divider,
|
||||
{
|
||||
borderColor:
|
||||
theme && darkModeType
|
||||
? 'rgba(255, 255, 255, 0.1)'
|
||||
: 'rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : backgroundColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.sectionHeader}
|
||||
content={t('wallet.halfModal.addressBook', {
|
||||
context: 'send',
|
||||
})}
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType ? COLORS.lightModeText : COLORS.primary
|
||||
}
|
||||
size={20}
|
||||
iconName={'X'}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
) : showPasteButton ? (
|
||||
<TouchableOpacity
|
||||
onPress={handleClipboardPaste}
|
||||
style={styles.clipboardButton}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType ? COLORS.lightModeText : COLORS.primary
|
||||
}
|
||||
size={20}
|
||||
iconName={'Clipboard'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* Address Book Section */}
|
||||
{decodedAddedContacts.length > 0 ? (
|
||||
contactElements
|
||||
) : (
|
||||
<View style={styles.emptyContactsContainer}>
|
||||
<ThemeIcon iconName={'UsersRound'} />
|
||||
<ThemeText
|
||||
styles={styles.emptyTitle}
|
||||
content={t('wallet.halfModal.noAddedContactsTitle')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.emptySubtext}
|
||||
content={t('wallet.halfModal.noAddedContactsDesc')}
|
||||
/>
|
||||
<CustomButton
|
||||
buttonStyles={{ width: '100%' }}
|
||||
textContent={t('contacts.editMyProfilePage.addContactBTN')}
|
||||
actionFunction={() => setShowAddContact(true)}
|
||||
<View style={styles.pageContainer}>
|
||||
{/* Page: No Input */}
|
||||
{noInputMounted && (
|
||||
<Animated.View
|
||||
style={[styles.page, noInputPageStyle]}
|
||||
pointerEvents={isInputMode ? 'none' : 'auto'}
|
||||
>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
contentContainerStyle={{
|
||||
...styles.innerContainer,
|
||||
paddingBottom: bottomPadding,
|
||||
}}
|
||||
stickyHeaderIndices={[3]}
|
||||
onScroll={e => {
|
||||
scrollOffsetRef.current = e.nativeEvent.contentOffset.y;
|
||||
}}
|
||||
scrollEventThrottle={16}
|
||||
onLayout={e => {
|
||||
const h = e.nativeEvent.layout.height;
|
||||
scrollViewHeightRef.current = h;
|
||||
setScrollViewHeight(h);
|
||||
}}
|
||||
>
|
||||
{/* Scan QR Code Button */}
|
||||
<TouchableOpacity
|
||||
style={[styles.scanButton, { marginBottom: 0 }]}
|
||||
onPress={handleCameraScan}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.scanIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType
|
||||
? COLORS.darkModeText
|
||||
: COLORS.primary
|
||||
}
|
||||
size={24}
|
||||
iconName={'ScanQrCode'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.scanTextContainer}>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonText}
|
||||
content={t('wallet.halfModal.scanQrCode')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonSubtext}
|
||||
content={t('wallet.halfModal.tapToScanQr')}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Scan Image Button */}
|
||||
<TouchableOpacity
|
||||
style={styles.scanButton}
|
||||
onPress={handleImageScan}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.scanIconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundColor
|
||||
: backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeIcon
|
||||
colorOverride={
|
||||
theme && darkModeType
|
||||
? COLORS.darkModeText
|
||||
: COLORS.primary
|
||||
}
|
||||
size={24}
|
||||
iconName={'Image'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.scanTextContainer}>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonText}
|
||||
content={t('wallet.halfModal.images')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.scanButtonSubtext}
|
||||
content={t('wallet.halfModal.tapToScan')}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Divider */}
|
||||
<View
|
||||
style={[
|
||||
styles.divider,
|
||||
{
|
||||
borderColor:
|
||||
theme && darkModeType
|
||||
? 'rgba(255, 255, 255, 0.1)'
|
||||
: 'rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundOffset
|
||||
: backgroundColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.sectionHeader}
|
||||
content={t('wallet.halfModal.addressBook', {
|
||||
context: 'send',
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Address Book Section */}
|
||||
{decodedAddedContacts.length > 0 ? (
|
||||
contactElements
|
||||
) : (
|
||||
<View style={styles.emptyContactsContainer}>
|
||||
<ThemeIcon iconName={'UsersRound'} />
|
||||
<ThemeText
|
||||
styles={styles.emptyTitle}
|
||||
content={t('wallet.halfModal.noAddedContactsTitle')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.emptySubtext}
|
||||
content={t('wallet.halfModal.noAddedContactsDesc')}
|
||||
/>
|
||||
<CustomButton
|
||||
buttonStyles={{ width: '100%' }}
|
||||
textContent={t(
|
||||
'contacts.editMyProfilePage.addContactBTN',
|
||||
)}
|
||||
actionFunction={() => setShowAddContact(true)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
{/* Page: Input */}
|
||||
{isInputMode && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.page,
|
||||
inputPageStyle,
|
||||
{ paddingBottom: isKeyboardActive ? 0 : bottomPadding },
|
||||
]}
|
||||
>
|
||||
<View style={styles.inputPageContent}>
|
||||
{isContactInputMode ? (
|
||||
filteredContacts.length > 0 ? (
|
||||
<FlatList
|
||||
data={filteredContacts}
|
||||
renderItem={renderFilteredContact}
|
||||
keyExtractor={filteredContactKeyExtractor}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
) : contactSearchUsername ? (
|
||||
<View style={styles.noContactContainer}>
|
||||
<ThemeIcon iconName={'SearchX'} />
|
||||
<ThemeText
|
||||
styles={styles.emptyTitle}
|
||||
content={t('wallet.halfModal.noContactHead', {
|
||||
username: contactSearchUsername,
|
||||
})}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.emptySubtext}
|
||||
content={t('wallet.halfModal.noContactDesc', {
|
||||
username: contactSearchUsername,
|
||||
})}
|
||||
/>
|
||||
<CustomButton
|
||||
buttonStyles={{ ...CENTER, marginTop: 'auto' }}
|
||||
textContent={`${t(
|
||||
'constants.pay',
|
||||
)} ${contactSearchUsername}`}
|
||||
actionFunction={handleManualInputSubmit}
|
||||
/>
|
||||
</View>
|
||||
) : null
|
||||
) : (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={KeyboardController.dismiss}
|
||||
>
|
||||
<View style={styles.inputActionContainer}>
|
||||
{!!inputError && (
|
||||
<View style={styles.inputErrorContainer}>
|
||||
<ThemeIcon size={20} iconName={'TriangleAlert'} />
|
||||
<ThemeText
|
||||
styles={styles.inputError}
|
||||
content={inputError}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={styles.inputModeButton}
|
||||
textContent={
|
||||
inputText?.trim()
|
||||
? t('constants.continue')
|
||||
: t('constants.back')
|
||||
}
|
||||
actionFunction={
|
||||
inputText?.trim()
|
||||
? handleManualInputSubmit
|
||||
: blurKeyboard
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
|
||||
{isInputMode && (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.continueButtonWrapper,
|
||||
continueButtonStyle,
|
||||
{ paddingBottom: isKeyboardActive ? 0 : bottomPadding },
|
||||
]}
|
||||
pointerEvents={isInputMode ? 'auto' : 'none'}
|
||||
>
|
||||
{!!inputError && (
|
||||
<View style={styles.inputErrorContainer}>
|
||||
<ThemeIcon size={20} iconName={'TriangleAlert'} />
|
||||
<ThemeText styles={styles.inputError} content={inputError} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={styles.inputModeButton}
|
||||
textContent={
|
||||
inputText?.trim()
|
||||
? t('constants.continue')
|
||||
: t('constants.back')
|
||||
}
|
||||
actionFunction={
|
||||
inputText?.trim() ? handleManualInputSubmit : blurKeyboard
|
||||
}
|
||||
/>
|
||||
</Animated.View>
|
||||
)}
|
||||
</Animated.View>
|
||||
)}
|
||||
</View>
|
||||
</Animated.View>
|
||||
|
||||
<AddContactOverlay
|
||||
@@ -1036,6 +1175,26 @@ const styles = StyleSheet.create({
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
},
|
||||
pageContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
page: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
inputPageContent: {
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
alignSelf: 'center',
|
||||
paddingTop: 12,
|
||||
flex: 1,
|
||||
},
|
||||
noContactContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
inputActionContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
innerContainer: {
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
flexGrow: 1,
|
||||
@@ -1063,10 +1222,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
|
||||
searchContainer: {
|
||||
width: '100%',
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 15,
|
||||
...CENTER,
|
||||
},
|
||||
searchInputContainer: {
|
||||
justifyContent: 'flex-start',
|
||||
@@ -1122,11 +1282,6 @@ const styles = StyleSheet.create({
|
||||
includeFontPadding: false,
|
||||
fontSize: SIZES.smedium,
|
||||
},
|
||||
continueButtonWrapper: {
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
alignSelf: 'center',
|
||||
paddingTop: 12,
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Schnell bezahlen",
|
||||
"createInvoice": "Rechnung erstellen",
|
||||
"invoiceDescription": "Fordern Sie eine Zahlung über einen Link oder QR-Code an.",
|
||||
"poolsDescription": "Sammeln Sie Beiträge von einer Gruppe für ein gemeinsames Ziel."
|
||||
"poolsDescription": "Sammeln Sie Beiträge von einer Gruppe für ein gemeinsames Ziel.",
|
||||
"noContactHead": "{{username}} nicht gefunden",
|
||||
"noContactDesc": "Füge {{username}} zu deinen Kontakten hinzu, um sie hier in Zukunft zu finden, oder fahre fort, um jetzt eine Zahlung zu senden."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Überspringen",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Quick Pay",
|
||||
"createInvoice": "Create Invoice",
|
||||
"invoiceDescription": "Request payment using a link or QR code.",
|
||||
"poolsDescription": "Collect contributions from a group toward a shared goal."
|
||||
"poolsDescription": "Collect contributions from a group toward a shared goal.",
|
||||
"noContactHead": "{{username}} not found",
|
||||
"noContactDesc": "Add {{username}} to your contacts to find them here in the future, or continue to send a payment now."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Skip",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Pago rápido",
|
||||
"createInvoice": "Crear factura",
|
||||
"invoiceDescription": "Solicita un pago mediante un enlace o código QR.",
|
||||
"poolsDescription": "Reúne contribuciones de un grupo para un objetivo compartido."
|
||||
"poolsDescription": "Reúne contribuciones de un grupo para un objetivo compartido.",
|
||||
"noContactHead": "{{username}} no encontrado",
|
||||
"noContactDesc": "Agrega a {{username}} a tus contactos para encontrarlo aquí en el futuro, o continúa para enviar un pago ahora."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Omitir",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Paiement rapide",
|
||||
"createInvoice": "Créer une facture",
|
||||
"invoiceDescription": "Demandez un paiement via un lien ou un code QR.",
|
||||
"poolsDescription": "Collectez des contributions d’un groupe pour un objectif commun."
|
||||
"poolsDescription": "Collectez des contributions d’un groupe pour un objectif commun.",
|
||||
"noContactHead": "{{username}} introuvable",
|
||||
"noContactDesc": "Ajoutez {{username}} à vos contacts pour le retrouver ici à l’avenir, ou continuez pour envoyer un paiement maintenant."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Passer",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Pagamento rapido",
|
||||
"createInvoice": "Crea fattura",
|
||||
"invoiceDescription": "Richiedi un pagamento tramite un link o un codice QR.",
|
||||
"poolsDescription": "Raccogli contributi da un gruppo per un obiettivo condiviso."
|
||||
"poolsDescription": "Raccogli contributi da un gruppo per un obiettivo condiviso.",
|
||||
"noContactHead": "{{username}} non trovato",
|
||||
"noContactDesc": "Aggiungi {{username}} ai tuoi contatti per trovarlo qui in futuro, oppure continua per inviare un pagamento adesso."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Salta",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Endereço Blitz",
|
||||
"createInvoice": "Criar Fatura",
|
||||
"invoiceDescription": "Escolha a moeda e quantia desejada",
|
||||
"poolsDescription": "Colete pagamentos para um objetivo"
|
||||
"poolsDescription": "Colete pagamentos para um objetivo",
|
||||
"noContactHead": "{{username}} não encontrado",
|
||||
"noContactDesc": "Adicione {{username}} aos seus contatos para encontrá-lo aqui no futuro, ou continue para enviar um pagamento agora."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Pular",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Быстрая оплата",
|
||||
"createInvoice": "Создать счёт",
|
||||
"invoiceDescription": "Запросите оплату с помощью ссылки или QR-кода.",
|
||||
"poolsDescription": "Собирайте взносы от группы для общей цели."
|
||||
"poolsDescription": "Собирайте взносы от группы для общей цели.",
|
||||
"noContactHead": "{{username}} не найден",
|
||||
"noContactDesc": "Добавьте {{username}} в свои контакты, чтобы находить здесь в будущем, или продолжите, чтобы отправить платёж сейчас."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Пропустить",
|
||||
|
||||
@@ -1000,7 +1000,9 @@
|
||||
"payMe": "Snabb betalning",
|
||||
"createInvoice": "Skapa faktura",
|
||||
"invoiceDescription": "Begär betalning via en länk eller QR-kod.",
|
||||
"poolsDescription": "Samla in bidrag från en grupp till ett gemensamt mål."
|
||||
"poolsDescription": "Samla in bidrag från en grupp till ett gemensamt mål.",
|
||||
"noContactHead": "{{username}} hittades inte",
|
||||
"noContactDesc": "Lägg till {{username}} i dina kontakter för att hitta dem här i framtiden, eller fortsätt för att skicka en betalning nu."
|
||||
},
|
||||
"payLinks": {
|
||||
"skip": "Hoppa över",
|
||||
|
||||
Reference in New Issue
Block a user