Lnurl recieve currency (#812)
* adding return key type to CustomSearchInput * fix android crash * adding receive currency type selector * addding lnurlReceiveCurrency to masterinfoobject * creating name and bio regex * remove hardcoded strings * fix receiveAddress not updating * adding auto-focus to savings goal * if using usd use zero amount invoice not lnurl since lnurl will go to usd not btc balance * fix theme * adding translations * adding new edit flow * fixing translation + dup keyboard
This commit is contained in:
@@ -27,7 +27,7 @@ android.enableJetifier=true
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
reactNativeArchitectures=armeabi-v7a,arm64-v8a
|
||||
|
||||
# Use this property to enable support to the new architecture.
|
||||
# This will allow you to use TurboModules and the Fabric render in
|
||||
|
||||
@@ -9,14 +9,17 @@ import {
|
||||
CENTER,
|
||||
COLORS,
|
||||
CONTENT_KEYBOARD_OFFSET,
|
||||
FONT,
|
||||
EMAIL_REGEX,
|
||||
SIZES,
|
||||
VALID_USERNAME_REGEX,
|
||||
VALID_NAME_BIO_REGEX,
|
||||
} from '../../../../constants';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { encriptMessage } from '../../../../functions/messaging/encodingAndDecodingMessages';
|
||||
import { CustomKeyboardAvoidingView } from '../../../../functions/CustomElements';
|
||||
import {
|
||||
CustomKeyboardAvoidingView,
|
||||
ThemeText,
|
||||
} from '../../../../functions/CustomElements';
|
||||
import { isValidUniqueName } from '../../../../../db';
|
||||
import CustomButton from '../../../../functions/CustomElements/button';
|
||||
import { useGlobalContactsInfo } from '../../../../../context-store/globalContacts';
|
||||
@@ -39,6 +42,7 @@ import { useProfileImage } from './hooks/useProfileImage';
|
||||
import EditProfileTextInput from './internalComponents/editProfileTextItems';
|
||||
import { areImagesSame } from './utils/imageComparison';
|
||||
import ThemeIcon from '../../../../functions/CustomElements/themeIcon';
|
||||
import { useGlobalContextProvider } from '../../../../../context-store/context';
|
||||
|
||||
export default function EditMyProfilePage(props) {
|
||||
const navigate = useNavigation();
|
||||
@@ -61,6 +65,29 @@ export default function EditMyProfilePage(props) {
|
||||
const myContact = globalContactsInformation.myProfile;
|
||||
const isFirstTimeEditing = myContact.didEditProfile;
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
return () => {
|
||||
if (isFirstTimeEditing || !isEditingMyProfile) return;
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
didEditProfile: true,
|
||||
},
|
||||
addedContacts: globalContactsInformation.addedContacts,
|
||||
},
|
||||
true,
|
||||
);
|
||||
};
|
||||
}, [
|
||||
isFirstTimeEditing,
|
||||
globalContactsInformation,
|
||||
toggleGlobalContactsInformation,
|
||||
isEditingMyProfile,
|
||||
]),
|
||||
);
|
||||
|
||||
const selectedAddedContact = props.fromInitialAdd
|
||||
? providedContact
|
||||
: decodedAddedContacts.find(
|
||||
@@ -96,21 +123,7 @@ export default function EditMyProfilePage(props) {
|
||||
<CustomSettingsTopBar
|
||||
shouldDismissKeyboard={true}
|
||||
label={fromSettings ? t('contacts.editMyProfilePage.navTitle') : ''}
|
||||
customBackFunction={() => {
|
||||
if (!isFirstTimeEditing) {
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
didEditProfile: true,
|
||||
},
|
||||
addedContacts: globalContactsInformation.addedContacts,
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
keyboardGoBack(navigate);
|
||||
}}
|
||||
customBackFunction={() => keyboardGoBack(navigate)}
|
||||
iconNew="Trash2"
|
||||
leftImageFunction={() =>
|
||||
navigate.navigate('ConfirmActionPage', {
|
||||
@@ -140,18 +153,17 @@ export default function EditMyProfilePage(props) {
|
||||
);
|
||||
}
|
||||
|
||||
// Extracted shared input fields component
|
||||
// ─── Contact-mode input fields ───────────────────────────────────
|
||||
|
||||
function ProfileInputFields({
|
||||
inputs,
|
||||
changeInputText,
|
||||
setIsKeyboardActive,
|
||||
nameRef,
|
||||
uniquenameRef,
|
||||
bioRef,
|
||||
receiveAddressRef,
|
||||
isEditingMyProfile,
|
||||
selectedAddedContact,
|
||||
myContact,
|
||||
theme,
|
||||
darkModeType,
|
||||
textInputColor,
|
||||
@@ -160,6 +172,9 @@ function ProfileInputFields({
|
||||
navigate,
|
||||
t,
|
||||
}) {
|
||||
const hasLNURL = !isEditingMyProfile && selectedAddedContact?.isLNURL;
|
||||
const bioIsLast = !isEditingMyProfile;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditProfileTextInput
|
||||
@@ -176,9 +191,10 @@ function ProfileInputFields({
|
||||
textInputColor={textInputColor}
|
||||
textInputBackground={textInputBackground}
|
||||
textColor={textColor}
|
||||
showDivider={true}
|
||||
/>
|
||||
|
||||
{selectedAddedContact?.isLNURL && (
|
||||
{hasLNURL && (
|
||||
<EditProfileTextInput
|
||||
label={t('contacts.editMyProfilePage.lnurlInputDesc')}
|
||||
placeholder={t('contacts.editMyProfilePage.lnurlInputPlaceholder')}
|
||||
@@ -195,33 +211,7 @@ function ProfileInputFields({
|
||||
textInputColor={textInputColor}
|
||||
textInputBackground={textInputBackground}
|
||||
textColor={textColor}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isEditingMyProfile && (
|
||||
<EditProfileTextInput
|
||||
label={t('contacts.editMyProfilePage.uniqueNameInputDesc')}
|
||||
placeholder={myContact.uniqueName}
|
||||
value={inputs.uniquename}
|
||||
onChangeText={text => changeInputText(text, 'uniquename')}
|
||||
onFocus={() => setIsKeyboardActive(true)}
|
||||
onBlur={() => setIsKeyboardActive(false)}
|
||||
inputRef={uniquenameRef}
|
||||
maxLength={30}
|
||||
theme={theme}
|
||||
darkModeType={darkModeType}
|
||||
textInputColor={textInputColor}
|
||||
textInputBackground={textInputBackground}
|
||||
textColor={textColor}
|
||||
showInfoIcon={true}
|
||||
onInfoPress={() =>
|
||||
navigate.navigate('InformationPopup', {
|
||||
textContent: t(
|
||||
'wallet.receivePages.editLNURLContact.informationMessage',
|
||||
),
|
||||
buttonText: t('constants.understandText'),
|
||||
})
|
||||
}
|
||||
showDivider={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -242,12 +232,130 @@ function ProfileInputFields({
|
||||
textInputColor={textInputColor}
|
||||
textInputBackground={textInputBackground}
|
||||
textColor={textColor}
|
||||
containerStyle={{ marginBottom: 10 }}
|
||||
showDivider={!bioIsLast}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── My profile nav rows ──────────────────────────────────────────────────────
|
||||
|
||||
function MyProfileRows({
|
||||
myContact,
|
||||
backgroundOffset,
|
||||
textColor,
|
||||
navigate,
|
||||
masterInfoObject,
|
||||
t,
|
||||
}) {
|
||||
const receiveCurrencyValue =
|
||||
masterInfoObject.lnurlReceiveCurrency === 'usd'
|
||||
? t('constants.dollars_upper')
|
||||
: t('constants.bitcoin_upper');
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.card,
|
||||
{ backgroundColor: backgroundOffset, marginTop: 12 },
|
||||
]}
|
||||
>
|
||||
{/* Name */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
navigate.navigate('EditProfileFieldPage', { fieldKey: 'name' })
|
||||
}
|
||||
style={styles.navRow}
|
||||
>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.navRowValue}
|
||||
content={t('contacts.editMyProfilePage.nameInputDesc')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.navRowLabel}
|
||||
content={myContact?.name || t('contacts.splitBill.noName')}
|
||||
CustomNumberOfLines={1}
|
||||
CustomEllipsizeMode="tail"
|
||||
/>
|
||||
<ThemeIcon iconName="ChevronRight" size={16} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={[styles.divider, { backgroundColor: textColor }]} />
|
||||
|
||||
{/* Username */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
navigate.navigate('EditProfileFieldPage', { fieldKey: 'uniquename' })
|
||||
}
|
||||
style={styles.navRow}
|
||||
>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.navRowValue}
|
||||
content={t('contacts.editMyProfilePage.uniqueNameInputDesc')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.navRowLabel}
|
||||
content={myContact?.uniqueName || ''}
|
||||
CustomNumberOfLines={1}
|
||||
CustomEllipsizeMode="tail"
|
||||
/>
|
||||
<ThemeIcon iconName="ChevronRight" size={16} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={[styles.divider, { backgroundColor: textColor }]} />
|
||||
|
||||
{/* Bio */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
navigate.navigate('EditProfileFieldPage', { fieldKey: 'bio' })
|
||||
}
|
||||
style={styles.navRow}
|
||||
>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.navRowValue}
|
||||
content={t('contacts.editMyProfilePage.bioInputDesc')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.navRowLabel}
|
||||
content={myContact?.bio || t('constants.noBioSet')}
|
||||
CustomNumberOfLines={1}
|
||||
CustomEllipsizeMode="tail"
|
||||
/>
|
||||
<ThemeIcon iconName="ChevronRight" size={16} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={[styles.divider, { backgroundColor: textColor }]} />
|
||||
|
||||
{/* Lightning Address */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() =>
|
||||
navigate.navigate('CustomHalfModal', {
|
||||
wantedContent: 'lnurlReceiveCurrencySelect',
|
||||
})
|
||||
}
|
||||
style={styles.navRow}
|
||||
>
|
||||
<ThemeText
|
||||
CustomNumberOfLines={1}
|
||||
styles={styles.navRowValue}
|
||||
content={t('contacts.editMyProfilePage.lightningAddress')}
|
||||
/>
|
||||
<ThemeText styles={styles.navRowLabel} content={receiveCurrencyValue} />
|
||||
<ThemeIcon iconName="ChevronRight" size={16} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── InnerContent ─────────────────────────────────────────────────────────────
|
||||
|
||||
function InnerContent({
|
||||
isEditingMyProfile,
|
||||
selectedAddedContact,
|
||||
@@ -260,6 +368,7 @@ function InnerContent({
|
||||
const { cache, refreshCacheObject } = useImageCache();
|
||||
const { backgroundOffset, textInputColor, textInputBackground, textColor } =
|
||||
GetThemeColors();
|
||||
const { masterInfoObject } = useGlobalContextProvider();
|
||||
const {
|
||||
decodedAddedContacts,
|
||||
globalContactsInformation,
|
||||
@@ -273,18 +382,13 @@ function InnerContent({
|
||||
saveProfileImage,
|
||||
} = useProfileImage();
|
||||
|
||||
// Contact-mode refs (unused in myProfile path but kept for contact path)
|
||||
const nameRef = useRef(null);
|
||||
const uniquenameRef = useRef(null);
|
||||
const bioRef = useRef(null);
|
||||
const receiveAddressRef = useRef(null);
|
||||
const didCallImagePicker = useRef(null);
|
||||
const myContact = globalContactsInformation.myProfile;
|
||||
|
||||
const myContactName = myContact?.name || '';
|
||||
const myContactBio = myContact?.bio || '';
|
||||
const myContactUniqueName = myContact?.uniqueName || '';
|
||||
const isFirstTimeEditing = myContact.didEditProfile;
|
||||
|
||||
const selectedAddedContactName = selectedAddedContact?.name || '';
|
||||
const selectedAddedContactBio = selectedAddedContact?.bio || '';
|
||||
const selectedAddedContactUniqueName = selectedAddedContact?.uniqueName || '';
|
||||
@@ -293,25 +397,12 @@ function InnerContent({
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [inputs, setInputs] = useState(() => ({
|
||||
name: isFirstTimeEditing
|
||||
? isEditingMyProfile
|
||||
? myContactName || ''
|
||||
: selectedAddedContactName || ''
|
||||
: '',
|
||||
bio: isFirstTimeEditing
|
||||
? isEditingMyProfile
|
||||
? myContactBio || ''
|
||||
: selectedAddedContactBio || ''
|
||||
: '',
|
||||
uniquename: isFirstTimeEditing
|
||||
? isEditingMyProfile
|
||||
? myContactUniqueName || ''
|
||||
: selectedAddedContactUniqueName || ''
|
||||
: '',
|
||||
name: selectedAddedContactName || '',
|
||||
bio: selectedAddedContactBio || '',
|
||||
uniquename: selectedAddedContactUniqueName || '',
|
||||
receiveAddress: selectedAddedContactReceiveAddress || '',
|
||||
}));
|
||||
|
||||
// Remove the entire useEffect
|
||||
const [tempImage, setTempImage] = useState({
|
||||
uri: null,
|
||||
comparison: null,
|
||||
@@ -340,12 +431,9 @@ function InnerContent({
|
||||
? !!myProfileImage?.localUri
|
||||
: !!selectedAddedContactImage?.localUri;
|
||||
|
||||
// For contact path: full change detection. For myProfile: image only.
|
||||
const hasChangedInfo = isEditingMyProfile
|
||||
? myContactName !== inputs.name ||
|
||||
myContactBio !== inputs.bio ||
|
||||
myContactUniqueName !== inputs.uniquename ||
|
||||
tempImage.uri ||
|
||||
tempImage.shouldDelete
|
||||
? tempImage.uri || tempImage.shouldDelete
|
||||
: selectedAddedContactName !== inputs.name ||
|
||||
selectedAddedContactBio !== inputs.bio ||
|
||||
selectedAddedContactUniqueName !== inputs.uniquename ||
|
||||
@@ -354,22 +442,6 @@ function InnerContent({
|
||||
tempImage.uri ||
|
||||
tempImage.shouldDelete;
|
||||
|
||||
console.log(
|
||||
hasChangedInfo,
|
||||
'has info changed',
|
||||
selectedAddedContactName,
|
||||
inputs.name,
|
||||
selectedAddedContactBio,
|
||||
inputs.bio,
|
||||
selectedAddedContactUniqueName,
|
||||
inputs.uniquename,
|
||||
selectedAddedContactReceiveAddress,
|
||||
inputs.receiveAddress,
|
||||
fromInitialAdd,
|
||||
tempImage.uri,
|
||||
tempImage.shouldDelete,
|
||||
);
|
||||
|
||||
const handleDeleteProfilePicture = () => {
|
||||
setTempImage({
|
||||
uri: null,
|
||||
@@ -406,12 +478,10 @@ function InnerContent({
|
||||
changeInputText,
|
||||
setIsKeyboardActive,
|
||||
nameRef,
|
||||
uniquenameRef,
|
||||
bioRef,
|
||||
receiveAddressRef,
|
||||
isEditingMyProfile,
|
||||
selectedAddedContact,
|
||||
myContact,
|
||||
theme,
|
||||
darkModeType,
|
||||
textInputColor,
|
||||
@@ -425,7 +495,9 @@ function InnerContent({
|
||||
return (
|
||||
<>
|
||||
<View style={styles.hideProfileContainer}>
|
||||
<ProfileInputFields {...inputFieldsProps} />
|
||||
<View style={[styles.card, { backgroundColor: backgroundOffset }]}>
|
||||
<ProfileInputFields {...inputFieldsProps} />
|
||||
</View>
|
||||
</View>
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
@@ -450,6 +522,116 @@ function InnerContent({
|
||||
);
|
||||
}
|
||||
|
||||
// ── myProfile path ──────────────────────────────────────────────────────────
|
||||
if (isEditingMyProfile) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.innerContainer,
|
||||
fromSettings && { maxWidth: MAX_CONTENT_WIDTH, width: '100%' },
|
||||
]}
|
||||
>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
width: fromSettings ? INSET_WINDOW_WIDTH : '100%',
|
||||
...CENTER,
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Profile image */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={!isAddingImage ? 0.2 : 1}
|
||||
onPress={async () => {
|
||||
if (isAddingImage) return;
|
||||
if (Keyboard.isVisible()) {
|
||||
Keyboard.dismiss();
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
}
|
||||
if (!hasImage) {
|
||||
addProfilePicture();
|
||||
return;
|
||||
}
|
||||
navigate.navigate('AddOrDeleteContactImage', {
|
||||
addPhoto: () => addProfilePicture(),
|
||||
deletePhoto: handleDeleteProfilePicture,
|
||||
hasImage: hasImage,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.profileImage,
|
||||
{ backgroundColor: backgroundOffset },
|
||||
]}
|
||||
>
|
||||
{isAddingImage ? (
|
||||
<FullLoadingScreen showText={false} />
|
||||
) : (
|
||||
<ContactProfileImage
|
||||
updated={
|
||||
tempImage.shouldDelete
|
||||
? null
|
||||
: tempImage.uri
|
||||
? tempImage.comparison?.updated
|
||||
: myProfileImage?.updated
|
||||
}
|
||||
uri={
|
||||
tempImage.shouldDelete
|
||||
? null
|
||||
: tempImage.uri
|
||||
? tempImage.comparison?.uri
|
||||
: myProfileImage?.localUri
|
||||
}
|
||||
darkModeType={darkModeType}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.selectFromPhotos}>
|
||||
<ThemeIcon
|
||||
colorOverride={COLORS.lightModeText}
|
||||
size={20}
|
||||
iconName={hasImage ? 'X' : 'Image'}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.sectionHeader}
|
||||
content={t('contacts.editMyProfilePage.aboutYou')}
|
||||
/>
|
||||
|
||||
<MyProfileRows
|
||||
myContact={myContact}
|
||||
backgroundOffset={backgroundOffset}
|
||||
textColor={textColor}
|
||||
navigate={navigate}
|
||||
masterInfoObject={masterInfoObject}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
width: '100%',
|
||||
...CENTER,
|
||||
marginTop: 'auto',
|
||||
marginBottom: bottomPadding,
|
||||
}}
|
||||
actionFunction={saveChanges}
|
||||
useLoading={isSaving}
|
||||
textContent={
|
||||
hasChangedInfo ? t('constants.save') : t('constants.back')
|
||||
}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Contact path (unchanged) ────────────────────────────────────────────────
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -467,6 +649,7 @@ function InnerContent({
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Profile image */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={
|
||||
(isEditingMyProfile || selectedAddedContact.isLNURL) &&
|
||||
@@ -539,8 +722,11 @@ function InnerContent({
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<ProfileInputFields {...inputFieldsProps} />
|
||||
<View style={{ height: 40 }} />
|
||||
{/* Unified settings card */}
|
||||
<View style={[styles.card, { backgroundColor: backgroundOffset }]}>
|
||||
<ProfileInputFields {...inputFieldsProps} />
|
||||
</View>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
width: '100%',
|
||||
@@ -566,183 +752,155 @@ function InnerContent({
|
||||
|
||||
async function saveChanges() {
|
||||
try {
|
||||
if (isAddingImage) return;
|
||||
|
||||
setIsSaving(true);
|
||||
|
||||
// ── myProfile: image only ─────────────────────────────────────────────
|
||||
if (isEditingMyProfile) {
|
||||
if (tempImage.shouldDelete) {
|
||||
await deleteProfilePicture(true, null);
|
||||
} else if (tempImage.uri && tempImage.comparison) {
|
||||
const areImagesTheSame = await areImagesSame(
|
||||
tempImage.comparison?.uri,
|
||||
myProfileImage?.localUri,
|
||||
);
|
||||
if (!areImagesTheSame) {
|
||||
await saveProfileImage(tempImage, true, null);
|
||||
}
|
||||
}
|
||||
keyboardGoBack(navigate);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Contact path (unchanged) ──────────────────────────────────────────
|
||||
if (
|
||||
inputs.name.length >= 30 ||
|
||||
inputs.bio.length >= 150 ||
|
||||
inputs.uniquename.length >= 30 ||
|
||||
(selectedAddedContact?.isLNURL &&
|
||||
inputs.receiveAddress.length >= 200) ||
|
||||
isAddingImage
|
||||
(selectedAddedContact?.isLNURL && inputs.receiveAddress.length >= 200)
|
||||
)
|
||||
return;
|
||||
|
||||
setIsSaving(true);
|
||||
if (
|
||||
!VALID_NAME_BIO_REGEX.test(inputs.name) ||
|
||||
!VALID_NAME_BIO_REGEX.test(inputs.bio)
|
||||
) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t('contacts.editMyProfilePage.invalidCharactersError'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedAddedContact?.isLNURL &&
|
||||
!EMAIL_REGEX.test(inputs.receiveAddress.trim())
|
||||
) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(
|
||||
'contacts.editMyProfilePage.invalidReceiveAddressError',
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// delete or save new image
|
||||
if (tempImage.shouldDelete) {
|
||||
await deleteProfilePicture(isEditingMyProfile, selectedAddedContact);
|
||||
await deleteProfilePicture(false, selectedAddedContact);
|
||||
} else if (tempImage.uri && tempImage.comparison) {
|
||||
const areImagesTheSame = await areImagesSame(
|
||||
tempImage.comparison?.uri,
|
||||
isEditingMyProfile
|
||||
? myProfileImage?.localUri
|
||||
: selectedAddedContactImage?.localUri,
|
||||
selectedAddedContactImage?.localUri,
|
||||
);
|
||||
if (!areImagesTheSame) {
|
||||
await saveProfileImage(
|
||||
tempImage,
|
||||
isEditingMyProfile,
|
||||
selectedAddedContact,
|
||||
);
|
||||
await saveProfileImage(tempImage, false, selectedAddedContact);
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueName =
|
||||
isEditingMyProfile && !isFirstTimeEditing
|
||||
? inputs.uniquename || myContact.uniqueName
|
||||
: inputs.uniquename;
|
||||
|
||||
console.log(selectedAddedContact, 'tt', isEditingMyProfile);
|
||||
|
||||
if (isEditingMyProfile) {
|
||||
if (
|
||||
myContact?.bio === inputs.bio &&
|
||||
myContact?.name === inputs.name &&
|
||||
myContact?.uniqueName === inputs.uniquename &&
|
||||
isFirstTimeEditing
|
||||
) {
|
||||
keyboardGoBack(navigate);
|
||||
} else {
|
||||
if (!VALID_USERNAME_REGEX.test(uniqueName)) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(
|
||||
'contacts.editMyProfilePage.unqiueNameRegexError',
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (myContact?.uniqueName != uniqueName) {
|
||||
const isFreeUniqueName = await isValidUniqueName(
|
||||
'blitzWalletUsers',
|
||||
inputs.uniquename.trim(),
|
||||
);
|
||||
if (!isFreeUniqueName) {
|
||||
navigate.navigate('ErrorScreen', {
|
||||
errorMessage: t(
|
||||
'contacts.editMyProfilePage.usernameAlreadyExistsError',
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
name: inputs.name.trim(),
|
||||
nameLower: inputs.name.trim().toLowerCase(),
|
||||
bio: inputs.bio,
|
||||
uniqueName: uniqueName.trim(),
|
||||
uniqueNameLower: uniqueName.trim().toLowerCase(),
|
||||
didEditProfile: true,
|
||||
},
|
||||
addedContacts: globalContactsInformation.addedContacts,
|
||||
},
|
||||
true,
|
||||
);
|
||||
keyboardGoBack(navigate);
|
||||
if (fromInitialAdd) {
|
||||
let tempContact = JSON.parse(JSON.stringify(selectedAddedContact));
|
||||
tempContact.name = inputs.name.trim();
|
||||
tempContact.nameLower = inputs.name.trim().toLowerCase();
|
||||
tempContact.bio = inputs.bio;
|
||||
tempContact.isAdded = true;
|
||||
tempContact.unlookedTransactions = 0;
|
||||
if (selectedAddedContact.isLNURL) {
|
||||
tempContact.receiveAddress = inputs.receiveAddress;
|
||||
}
|
||||
|
||||
let newAddedContacts = JSON.parse(JSON.stringify(decodedAddedContacts));
|
||||
const isContactInAddedContacts = newAddedContacts.filter(
|
||||
addedContact => addedContact.uuid === tempContact.uuid,
|
||||
).length;
|
||||
|
||||
if (isContactInAddedContacts) {
|
||||
newAddedContacts = newAddedContacts.map(addedContact => {
|
||||
if (addedContact.uuid === tempContact.uuid) {
|
||||
return {
|
||||
...addedContact,
|
||||
name: tempContact.name,
|
||||
nameLower: tempContact.nameLower,
|
||||
bio: tempContact.bio,
|
||||
unlookedTransactions: 0,
|
||||
isAdded: true,
|
||||
};
|
||||
} else return addedContact;
|
||||
});
|
||||
} else newAddedContacts.push(tempContact);
|
||||
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
},
|
||||
addedContacts: encriptMessage(
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
JSON.stringify(newAddedContacts),
|
||||
),
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
selectedAddedContact?.bio === inputs.bio &&
|
||||
selectedAddedContact?.name === inputs.name &&
|
||||
selectedAddedContact?.receiveAddress === inputs.receiveAddress
|
||||
) {
|
||||
keyboardGoBack(navigate);
|
||||
} else {
|
||||
console.log(selectedAddedContact, 'testing');
|
||||
if (fromInitialAdd) {
|
||||
let tempContact = JSON.parse(JSON.stringify(selectedAddedContact));
|
||||
tempContact.name = inputs.name.trim();
|
||||
tempContact.nameLower = inputs.name.trim().toLowerCase();
|
||||
tempContact.bio = inputs.bio;
|
||||
tempContact.isAdded = true;
|
||||
tempContact.unlookedTransactions = 0;
|
||||
if (selectedAddedContact.isLNURL) {
|
||||
tempContact.receiveAddress = inputs.receiveAddress;
|
||||
}
|
||||
let newAddedContacts = [...decodedAddedContacts];
|
||||
const indexOfContact = decodedAddedContacts.findIndex(
|
||||
obj => obj.uuid === selectedAddedContact.uuid,
|
||||
);
|
||||
|
||||
let newAddedContacts = JSON.parse(
|
||||
JSON.stringify(decodedAddedContacts),
|
||||
);
|
||||
const isContactInAddedContacts = newAddedContacts.filter(
|
||||
addedContact => addedContact.uuid === tempContact.uuid,
|
||||
).length;
|
||||
let contact = newAddedContacts[indexOfContact];
|
||||
|
||||
if (isContactInAddedContacts) {
|
||||
newAddedContacts = newAddedContacts.map(addedContact => {
|
||||
if (addedContact.uuid === tempContact.uuid) {
|
||||
return {
|
||||
...addedContact,
|
||||
name: tempContact.name,
|
||||
nameLower: tempContact.nameLower,
|
||||
bio: tempContact.bio,
|
||||
unlookedTransactions: 0,
|
||||
isAdded: true,
|
||||
};
|
||||
} else return addedContact;
|
||||
});
|
||||
} else newAddedContacts.push(tempContact);
|
||||
console.log(tempContact, newAddedContacts);
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
},
|
||||
addedContacts: encriptMessage(
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
JSON.stringify(newAddedContacts),
|
||||
),
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
contact['name'] = inputs.name.trim();
|
||||
contact['nameLower'] = inputs.name.trim().toLowerCase();
|
||||
contact['bio'] = inputs.bio.trim();
|
||||
if (
|
||||
selectedAddedContact?.bio === inputs.bio &&
|
||||
selectedAddedContact?.name === inputs.name &&
|
||||
selectedAddedContact?.receiveAddress === inputs.receiveAddress
|
||||
)
|
||||
keyboardGoBack(navigate);
|
||||
else {
|
||||
let newAddedContacts = [...decodedAddedContacts];
|
||||
const indexOfContact = decodedAddedContacts.findIndex(
|
||||
obj => obj.uuid === selectedAddedContact.uuid,
|
||||
);
|
||||
|
||||
let contact = newAddedContacts[indexOfContact];
|
||||
|
||||
contact['name'] = inputs.name.trim();
|
||||
contact['nameLower'] = inputs.name.trim().toLowerCase();
|
||||
contact['bio'] = inputs.bio.trim();
|
||||
if (
|
||||
selectedAddedContact.isLNURL &&
|
||||
selectedAddedContact?.receiveAddress !== inputs.receiveAddress
|
||||
) {
|
||||
contact['receiveAddress'] = inputs.receiveAddress.trim();
|
||||
}
|
||||
console.log(contact, newAddedContacts);
|
||||
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
},
|
||||
addedContacts: encriptMessage(
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
JSON.stringify(newAddedContacts),
|
||||
),
|
||||
},
|
||||
true,
|
||||
);
|
||||
keyboardGoBack(navigate);
|
||||
selectedAddedContact.isLNURL &&
|
||||
selectedAddedContact?.receiveAddress !== inputs.receiveAddress
|
||||
) {
|
||||
contact['receiveAddress'] = inputs.receiveAddress.trim();
|
||||
}
|
||||
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: {
|
||||
...globalContactsInformation.myProfile,
|
||||
},
|
||||
addedContacts: encriptMessage(
|
||||
contactsPrivateKey,
|
||||
publicKey,
|
||||
JSON.stringify(newAddedContacts),
|
||||
),
|
||||
},
|
||||
true,
|
||||
);
|
||||
keyboardGoBack(navigate);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
@@ -762,6 +920,20 @@ const styles = StyleSheet.create({
|
||||
innerContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
card: {
|
||||
width: '100%',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
marginTop: 16,
|
||||
marginBottom: 20,
|
||||
},
|
||||
sectionHeader: {
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.55,
|
||||
alignSelf: 'flex-start',
|
||||
marginTop: 24,
|
||||
},
|
||||
selectFromPhotos: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
@@ -778,38 +950,50 @@ const styles = StyleSheet.create({
|
||||
width: 150,
|
||||
height: 150,
|
||||
borderRadius: 125,
|
||||
backgroundColor: 'red',
|
||||
...CENTER,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
||||
textInput: {
|
||||
fontSize: SIZES.medium,
|
||||
padding: 10,
|
||||
fontFamily: FONT.Title_Regular,
|
||||
includeFontPadding: false,
|
||||
borderRadius: 8,
|
||||
marginBottom: 10,
|
||||
marginTop: 8,
|
||||
},
|
||||
textInputContainer: { width: '100%' },
|
||||
textInputContainerDescriptionText: {
|
||||
includeFontPadding: false,
|
||||
},
|
||||
usernameRow: {
|
||||
navRow: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'flex-start',
|
||||
|
||||
paddingRight: 10,
|
||||
gap: 5,
|
||||
minHeight: 60,
|
||||
},
|
||||
navRowLabelLine: {
|
||||
marginRight: 'auto',
|
||||
flexShrink: 1,
|
||||
},
|
||||
navRowLabel: {
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.55,
|
||||
flexShrink: 1,
|
||||
marginLeft: 10,
|
||||
},
|
||||
navRowValue: {
|
||||
fontSize: SIZES.medium,
|
||||
includeFontPadding: false,
|
||||
marginRight: 'auto',
|
||||
flexShrink: 1,
|
||||
},
|
||||
copyLinkRow: {
|
||||
paddingVertical: 8,
|
||||
},
|
||||
infoIcon: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginLeft: 5,
|
||||
copyLinkText: {
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.55,
|
||||
flex: 1,
|
||||
marginRight: 4,
|
||||
},
|
||||
divider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
marginHorizontal: 16,
|
||||
opacity: 0.15,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -295,6 +295,7 @@ export default function ExpandedContactsPage(props) {
|
||||
selectedContact?.uniqueName,
|
||||
selectedContact?.bio,
|
||||
selectedContact?.isLNURL,
|
||||
selectedContact?.receiveAddress,
|
||||
imageData?.updated,
|
||||
imageData?.localUri,
|
||||
isConnectedToTheInternet,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ContactsPage from './contactsPage';
|
||||
import EditMyProfilePage from './editMyProfilePage';
|
||||
import EditProfileFieldPage from './internalComponents/editProfileFieldPage';
|
||||
import ExpandedAddContactsPage from './expandedAddContactsPage';
|
||||
import ExpandedContactsPage from './expandedContactPage';
|
||||
// import MyContactProfilePage from './myProfilePage';
|
||||
@@ -13,6 +14,7 @@ import CreateSplitBill from './createSplitBill';
|
||||
export {
|
||||
ExpandedContactsPage,
|
||||
EditMyProfilePage,
|
||||
EditProfileFieldPage,
|
||||
// MyContactProfilePage,
|
||||
SendAndRequestPage,
|
||||
ContactsPage,
|
||||
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import {
|
||||
COLORS,
|
||||
FONT,
|
||||
SIZES,
|
||||
VALID_USERNAME_REGEX,
|
||||
VALID_NAME_BIO_REGEX,
|
||||
} from '../../../../../constants';
|
||||
import { CENTER } from '../../../../../constants';
|
||||
import {
|
||||
CustomKeyboardAvoidingView,
|
||||
ThemeText,
|
||||
} from '../../../../../functions/CustomElements';
|
||||
import CustomSettingsTopBar from '../../../../../functions/CustomElements/settingsTopBar';
|
||||
import ThemeIcon from '../../../../../functions/CustomElements/themeIcon';
|
||||
import CustomButton from '../../../../../functions/CustomElements/button';
|
||||
import GetThemeColors from '../../../../../hooks/themeColors';
|
||||
import { useGlobalThemeContext } from '../../../../../../context-store/theme';
|
||||
import { useGlobalContactsInfo } from '../../../../../../context-store/globalContacts';
|
||||
import { isValidUniqueName } from '../../../../../../db';
|
||||
import CustomSearchInput from '../../../../../functions/CustomElements/searchInput';
|
||||
import { keyboardGoBack } from '../../../../../functions/customNavigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const FIELD_CONFIG = {
|
||||
name: {
|
||||
title: 'contacts.editMyProfilePage.nameInputDesc',
|
||||
description: 'contacts.editProfileFieldPage.nameDescription',
|
||||
maxLength: 30,
|
||||
multiline: false,
|
||||
},
|
||||
uniquename: {
|
||||
title: 'contacts.editMyProfilePage.uniqueNameInputDesc',
|
||||
description: 'contacts.editProfileFieldPage.uniquenameDescription',
|
||||
maxLength: 30,
|
||||
multiline: false,
|
||||
},
|
||||
bio: {
|
||||
title: 'contacts.editMyProfilePage.bioInputDesc',
|
||||
description: 'contacts.editProfileFieldPage.bioDescription',
|
||||
maxLength: 150,
|
||||
multiline: true,
|
||||
},
|
||||
};
|
||||
|
||||
// username validation states
|
||||
const USERNAME_STATE = {
|
||||
IDLE: 'idle',
|
||||
CHECKING: 'checking',
|
||||
AVAILABLE: 'available',
|
||||
TAKEN: 'taken',
|
||||
INVALID: 'invalid',
|
||||
};
|
||||
|
||||
export default function EditProfileFieldPage(props) {
|
||||
const fieldKey = props.route?.params?.fieldKey;
|
||||
const config = FIELD_CONFIG[fieldKey] || FIELD_CONFIG.name;
|
||||
|
||||
const navigate = useNavigation();
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { textInputColor, textColor } = GetThemeColors();
|
||||
const { globalContactsInformation, toggleGlobalContactsInformation } =
|
||||
useGlobalContactsInfo();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const myContact = globalContactsInformation.myProfile;
|
||||
const hasEdited = myContact?.didEditProfile || false;
|
||||
|
||||
const initialValue =
|
||||
fieldKey === 'name'
|
||||
? myContact?.name || ''
|
||||
: fieldKey === 'uniquename'
|
||||
? hasEdited
|
||||
? myContact?.uniqueName || ''
|
||||
: ''
|
||||
: myContact?.bio || '';
|
||||
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [usernameState, setUsernameState] = useState(USERNAME_STATE.IDLE);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isKeyboardActive, setIsKeyboardActive] = useState(false);
|
||||
const debounceRef = useRef(null);
|
||||
const isMountedRef = useRef(true);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const isValidContent =
|
||||
fieldKey === 'uniquename' || VALID_NAME_BIO_REGEX.test(value);
|
||||
const didEdit = initialValue !== value;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => inputRef.current?.focus(), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMountedRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldKey !== 'uniquename') return;
|
||||
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
setUsernameState(USERNAME_STATE.IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VALID_USERNAME_REGEX.test(trimmed)) {
|
||||
setUsernameState(USERNAME_STATE.INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmed.toLowerCase() === myContact?.uniqueName?.toLowerCase()) {
|
||||
setUsernameState(USERNAME_STATE.AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
setUsernameState(USERNAME_STATE.CHECKING);
|
||||
debounceRef.current = setTimeout(async () => {
|
||||
try {
|
||||
const isFree = await isValidUniqueName('blitzWalletUsers', trimmed);
|
||||
if (isMountedRef.current) {
|
||||
setUsernameState(
|
||||
isFree ? USERNAME_STATE.AVAILABLE : USERNAME_STATE.TAKEN,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
if (isMountedRef.current) setUsernameState(USERNAME_STATE.IDLE);
|
||||
}
|
||||
}, 600);
|
||||
|
||||
return () => {
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
};
|
||||
}, [value, fieldKey]);
|
||||
|
||||
const canSave =
|
||||
!isSaving &&
|
||||
isValidContent &&
|
||||
(fieldKey !== 'uniquename' || usernameState === USERNAME_STATE.AVAILABLE);
|
||||
|
||||
async function handleSave() {
|
||||
if (!canSave) {
|
||||
keyboardGoBack(navigate);
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
|
||||
const noChange =
|
||||
fieldKey === 'name'
|
||||
? trimmed === myContact?.name
|
||||
: fieldKey === 'uniquename'
|
||||
? trimmed === myContact?.uniqueName
|
||||
: trimmed === myContact?.bio;
|
||||
|
||||
if (noChange) {
|
||||
keyboardGoBack(navigate);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
let profileUpdate = { ...myContact, didEditProfile: true };
|
||||
|
||||
if (fieldKey === 'name') {
|
||||
profileUpdate.name = trimmed;
|
||||
profileUpdate.nameLower = trimmed.toLowerCase();
|
||||
} else if (fieldKey === 'uniquename') {
|
||||
profileUpdate.uniqueName = trimmed;
|
||||
profileUpdate.uniqueNameLower = trimmed.toLowerCase();
|
||||
} else {
|
||||
profileUpdate.bio = trimmed;
|
||||
}
|
||||
|
||||
toggleGlobalContactsInformation(
|
||||
{
|
||||
myProfile: profileUpdate,
|
||||
addedContacts: globalContactsInformation.addedContacts,
|
||||
},
|
||||
true,
|
||||
);
|
||||
keyboardGoBack(navigate);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
const labelMap = {
|
||||
name: t('contacts.editMyProfilePage.nameInputDesc'),
|
||||
uniquename: t('contacts.editMyProfilePage.uniqueNameInputDesc'),
|
||||
bio: t('contacts.editMyProfilePage.bioInputDesc'),
|
||||
};
|
||||
const fieldLabel = labelMap[fieldKey] || '';
|
||||
|
||||
const statusText =
|
||||
fieldKey === 'uniquename'
|
||||
? usernameState === USERNAME_STATE.TAKEN
|
||||
? t('contacts.editMyProfilePage.usernameAlreadyExistsError')
|
||||
: usernameState === USERNAME_STATE.INVALID
|
||||
? t('contacts.editMyProfilePage.unqiueNameRegexError')
|
||||
: `${value.length} / ${config.maxLength}`
|
||||
: !isValidContent
|
||||
? t('contacts.editMyProfilePage.invalidCharactersError')
|
||||
: `${value.length} / ${config.maxLength}`;
|
||||
|
||||
const statusColor =
|
||||
fieldKey === 'uniquename'
|
||||
? usernameState === USERNAME_STATE.AVAILABLE ||
|
||||
usernameState === USERNAME_STATE.CHECKING ||
|
||||
usernameState === USERNAME_STATE.IDLE
|
||||
? textColor
|
||||
: theme && darkModeType
|
||||
? textColor
|
||||
: COLORS.cancelRed
|
||||
: !isValidContent
|
||||
? theme && darkModeType
|
||||
? textColor
|
||||
: COLORS.cancelRed
|
||||
: textColor;
|
||||
|
||||
return (
|
||||
<CustomKeyboardAvoidingView
|
||||
useTouchableWithoutFeedback={true}
|
||||
useStandardWidth={true}
|
||||
useLocalPadding={true}
|
||||
isKeyboardActive={isKeyboardActive}
|
||||
>
|
||||
<CustomSettingsTopBar label="" />
|
||||
|
||||
<View style={styles.content}>
|
||||
<View style={styles.topSection}>
|
||||
<ThemeText styles={styles.title} content={t(config.title)} />
|
||||
|
||||
{config.description && (
|
||||
<ThemeText
|
||||
styles={styles.description}
|
||||
content={t(config.description)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ThemeText styles={styles.inputLabel} content={fieldLabel} />
|
||||
|
||||
<View style={styles.inputCard}>
|
||||
<CustomSearchInput
|
||||
textInputRef={inputRef}
|
||||
inputText={value}
|
||||
setInputText={setValue}
|
||||
maxLength={config.maxLength}
|
||||
textInputMultiline={config.multiline}
|
||||
textAlignVertical={config.multiline ? 'top' : 'center'}
|
||||
onBlurFunction={() => setIsKeyboardActive(false)}
|
||||
onFocusFunction={() => setIsKeyboardActive(true)}
|
||||
textInputStyles={{ paddingRight: 20 }}
|
||||
placeholderText={
|
||||
fieldKey === 'uniquename' && !hasEdited
|
||||
? myContact?.uniqueName || ''
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{fieldKey === 'uniquename' && (
|
||||
<View style={styles.usernameIcon}>
|
||||
{usernameState === USERNAME_STATE.CHECKING ? (
|
||||
<ActivityIndicator size="small" color={textInputColor} />
|
||||
) : usernameState === USERNAME_STATE.AVAILABLE ? (
|
||||
<ThemeIcon
|
||||
iconName="CircleCheck"
|
||||
size={20}
|
||||
colorOverride={
|
||||
theme && darkModeType ? textInputColor : COLORS.nostrGreen
|
||||
}
|
||||
/>
|
||||
) : usernameState === USERNAME_STATE.INVALID ||
|
||||
usernameState === USERNAME_STATE.TAKEN ? (
|
||||
<ThemeIcon
|
||||
iconName="CircleAlert"
|
||||
size={20}
|
||||
colorOverride={
|
||||
theme && darkModeType ? textInputColor : COLORS.cancelRed
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{statusText && (
|
||||
<ThemeText
|
||||
styles={[
|
||||
styles.statusText,
|
||||
{
|
||||
color: statusColor,
|
||||
opacity:
|
||||
usernameState === USERNAME_STATE.TAKEN ||
|
||||
usernameState === USERNAME_STATE.INVALID ||
|
||||
!isValidContent
|
||||
? 1
|
||||
: 0.55,
|
||||
},
|
||||
]}
|
||||
content={statusText}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
width: '100%',
|
||||
...CENTER,
|
||||
}}
|
||||
actionFunction={handleSave}
|
||||
useLoading={isSaving}
|
||||
textContent={
|
||||
didEdit && canSave ? t('constants.save') : t('constants.back')
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</CustomKeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
content: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
topSection: {
|
||||
flex: 1,
|
||||
},
|
||||
title: {
|
||||
fontSize: SIZES.large,
|
||||
fontWeight: '500',
|
||||
includeFontPadding: false,
|
||||
marginTop: 10,
|
||||
marginBottom: 8,
|
||||
},
|
||||
description: {
|
||||
fontSize: SIZES.smedium,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.6,
|
||||
lineHeight: 22,
|
||||
marginBottom: 16,
|
||||
},
|
||||
inputCard: {
|
||||
borderRadius: 12,
|
||||
marginTop: 8,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
inputLabel: {
|
||||
width: '100%',
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.55,
|
||||
marginBottom: 4,
|
||||
textTransform: 'uppercase',
|
||||
marginTop: 16,
|
||||
},
|
||||
inputRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
textInput: {
|
||||
flex: 1,
|
||||
fontSize: SIZES.medium,
|
||||
fontFamily: FONT.Title_Regular,
|
||||
includeFontPadding: false,
|
||||
padding: 0,
|
||||
},
|
||||
multilineInput: {
|
||||
minHeight: 60,
|
||||
maxHeight: 120,
|
||||
},
|
||||
usernameIcon: {
|
||||
width: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
right: 10,
|
||||
},
|
||||
statusText: {
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
marginTop: 8,
|
||||
},
|
||||
});
|
||||
+56
-50
@@ -48,6 +48,7 @@ export default function EditProfileTextInput({
|
||||
showInfoIcon = false,
|
||||
onInfoPress,
|
||||
containerStyle,
|
||||
showDivider = false,
|
||||
}) {
|
||||
const isOverLimit = value.length >= maxLength;
|
||||
const characterCountColor = isOverLimit
|
||||
@@ -60,32 +61,33 @@ export default function EditProfileTextInput({
|
||||
? theme && darkModeType
|
||||
? textInputColor
|
||||
: COLORS.cancelRed
|
||||
: textInputColor;
|
||||
: textColor;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.textInputContainer, containerStyle]}
|
||||
style={[styles.container, containerStyle]}
|
||||
activeOpacity={1}
|
||||
onPress={() => {
|
||||
inputRef?.current?.focus();
|
||||
}}
|
||||
onPress={() => inputRef?.current?.focus()}
|
||||
>
|
||||
{showInfoIcon ? (
|
||||
<TouchableOpacity onPress={onInfoPress} style={styles.usernameRow}>
|
||||
<ThemeText
|
||||
styles={styles.textInputContainerDescriptionText}
|
||||
content={label}
|
||||
/>
|
||||
<View onPress={onInfoPress}>
|
||||
<ThemeIcon size={20} styles={styles.infoIcon} iconName={'Info'} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={styles.labelRow}>
|
||||
<View style={styles.labelLeft}>
|
||||
<ThemeText styles={styles.label} content={label} />
|
||||
{showInfoIcon && (
|
||||
<TouchableOpacity onPress={onInfoPress} style={styles.infoIconWrap}>
|
||||
<ThemeIcon size={14} iconName="Info" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<ThemeText
|
||||
styles={styles.textInputContainerDescriptionText}
|
||||
content={label}
|
||||
styles={{
|
||||
fontSize: SIZES.xSmall,
|
||||
includeFontPadding: false,
|
||||
color: characterCountColor,
|
||||
opacity: isOverLimit ? 1 : 0.45,
|
||||
}}
|
||||
content={`${value.length} / ${maxLength}`}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<TextInput
|
||||
keyboardAppearance={theme ? 'dark' : 'light'}
|
||||
@@ -101,11 +103,7 @@ export default function EditProfileTextInput({
|
||||
textAlignVertical={multiline ? 'top' : 'center'}
|
||||
style={[
|
||||
styles.textInput,
|
||||
{
|
||||
backgroundColor: textInputBackground,
|
||||
color: inputTextColor,
|
||||
marginTop: showInfoIcon ? 0 : 8,
|
||||
},
|
||||
{ color: inputTextColor },
|
||||
multiline && {
|
||||
minHeight: minHeight || 60,
|
||||
maxHeight: maxHeight || 100,
|
||||
@@ -117,45 +115,53 @@ export default function EditProfileTextInput({
|
||||
onFocus={onFocus}
|
||||
/>
|
||||
|
||||
<ThemeText
|
||||
styles={{
|
||||
textAlign: 'right',
|
||||
color: characterCountColor,
|
||||
}}
|
||||
content={`${value.length} / ${maxLength}`}
|
||||
/>
|
||||
{showDivider && (
|
||||
<View style={[styles.divider, { backgroundColor: textColor }]} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textInputContainer: {
|
||||
container: {
|
||||
width: '100%',
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
},
|
||||
textInputContainerDescriptionText: {
|
||||
labelRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 4,
|
||||
},
|
||||
labelLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
label: {
|
||||
fontSize: SIZES.small,
|
||||
includeFontPadding: false,
|
||||
opacity: 0.55,
|
||||
},
|
||||
infoIconWrap: {
|
||||
marginLeft: 2,
|
||||
opacity: 0.55,
|
||||
},
|
||||
textInput: {
|
||||
fontSize: SIZES.medium,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
fontFamily: FONT.Title_Regular,
|
||||
includeFontPadding: false,
|
||||
borderRadius: 8,
|
||||
marginBottom: 10,
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
},
|
||||
usernameRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'flex-start',
|
||||
paddingRight: 10,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
infoIcon: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginLeft: 5,
|
||||
divider: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 16,
|
||||
right: 16,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
opacity: 0.15,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useToast } from '../../../../../context-store/toastManager';
|
||||
import { copyToClipboard } from '../../../../functions';
|
||||
import QrCodeWrapper from '../../../../functions/CustomElements/QrWrapper';
|
||||
import { useAppStatus } from '../../../../../context-store/appStatus';
|
||||
import { useGlobalContextProvider } from '../../../../../context-store/context';
|
||||
|
||||
// ─── LNURL Banner ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -127,12 +128,16 @@ const LNURLQROverlay = ({
|
||||
visible,
|
||||
onClose,
|
||||
lnurlAddress,
|
||||
|
||||
navigate,
|
||||
masterInfoObject,
|
||||
theme,
|
||||
darkModeType,
|
||||
t,
|
||||
}) => {
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
const overlayOpacity = useSharedValue(0);
|
||||
const overlayTranslateX = useSharedValue(30);
|
||||
const { textColor, backgroundColor, backgroundOffset } = GetThemeColors();
|
||||
|
||||
useEffect(() => {
|
||||
overlayOpacity.value = withTiming(visible ? 1 : 0, { duration: 250 });
|
||||
@@ -163,6 +168,37 @@ const LNURLQROverlay = ({
|
||||
QRData={`${lnurlAddress}`}
|
||||
/>
|
||||
|
||||
<View style={styles.selectionContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
navigate.push('CustomHalfModal', {
|
||||
wantedContent: 'lnurlReceiveCurrencySelect',
|
||||
})
|
||||
}
|
||||
style={[
|
||||
styles.currencyToggle,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeText
|
||||
styles={styles.currencyToggleText}
|
||||
content={
|
||||
masterInfoObject.lnurlReceiveCurrency === 'btc'
|
||||
? t('constants.bitcoin_upper')
|
||||
: t('constants.dollars_upper')
|
||||
}
|
||||
/>
|
||||
<ThemeIcon
|
||||
colorOverride={textColor}
|
||||
size={18}
|
||||
iconName={'ChevronDown'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Back button */}
|
||||
<CustomButton
|
||||
buttonStyles={{
|
||||
@@ -372,6 +408,7 @@ export default function HalfModalReceiveOptions({
|
||||
const { cache } = useImageCache();
|
||||
const { bottomPadding } = useGlobalInsets();
|
||||
const { screenDimensions } = useAppStatus();
|
||||
const { masterInfoObject } = useGlobalContextProvider();
|
||||
const { decodedAddedContacts, contactsMessags, globalContactsInformation } =
|
||||
useGlobalContacts();
|
||||
const { t } = useTranslation();
|
||||
@@ -605,7 +642,7 @@ export default function HalfModalReceiveOptions({
|
||||
backgroundOffset={backgroundOffset}
|
||||
textColor={textColor}
|
||||
onQRPress={() => {
|
||||
setContentHeight(500);
|
||||
setContentHeight(600);
|
||||
setShowLNURLQR(true);
|
||||
}}
|
||||
/>
|
||||
@@ -738,6 +775,8 @@ export default function HalfModalReceiveOptions({
|
||||
backgroundOffset={backgroundOffset}
|
||||
textColor={textColor}
|
||||
t={t}
|
||||
navigate={navigate}
|
||||
masterInfoObject={masterInfoObject}
|
||||
setContentHeight={setContentHeight}
|
||||
/>
|
||||
|
||||
@@ -821,7 +860,6 @@ const styles = StyleSheet.create({
|
||||
// ── QR Overlay ──
|
||||
qrOverlayContent: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
||||
stickyHeaderContainer: {
|
||||
@@ -979,4 +1017,25 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
selectionContainer: {
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
flex: 1,
|
||||
marginVertical: 20,
|
||||
...CENTER,
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
},
|
||||
currencyToggle: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
...CENTER,
|
||||
minHeight: 40,
|
||||
paddingHorizontal: 15,
|
||||
borderRadius: 50,
|
||||
},
|
||||
|
||||
currencyToggleText: {
|
||||
includeFontPadding: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import GetThemeColors from '../../../../hooks/themeColors';
|
||||
import CheckMarkCircle from '../../../../functions/CustomElements/checkMarkCircle';
|
||||
import { CENTER, ICONS } from '../../../../constants';
|
||||
import { COLORS, INSET_WINDOW_WIDTH, SIZES } from '../../../../constants/theme';
|
||||
import { ThemeText } from '../../../../functions/CustomElements';
|
||||
import { useGlobalThemeContext } from '../../../../../context-store/theme';
|
||||
import { useGlobalContextProvider } from '../../../../../context-store/context';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import ThemeImage from '../../../../functions/CustomElements/themeImage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import displayCorrectDenomination from '../../../../functions/displayCorrectDenomination';
|
||||
import { useNodeContext } from '../../../../../context-store/nodeContext';
|
||||
|
||||
export default function LnurlReceiveCurrencySelect({
|
||||
handleBackPressFunction,
|
||||
setContentHeight,
|
||||
}) {
|
||||
const { masterInfoObject, toggleMasterInfoObject } =
|
||||
useGlobalContextProvider();
|
||||
const { fiatStats } = useNodeContext();
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { backgroundOffset, backgroundColor } = GetThemeColors();
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setContentHeight(500);
|
||||
}, []);
|
||||
|
||||
const onSelect = currency => {
|
||||
handleBackPressFunction(() => {
|
||||
toggleMasterInfoObject({ lnurlReceiveCurrency: currency });
|
||||
navigate.goBack();
|
||||
});
|
||||
};
|
||||
|
||||
const currentCurrency =
|
||||
masterInfoObject.lnurlReceiveCurrency === 'usd' ? 'usd' : 'btc';
|
||||
|
||||
return (
|
||||
<View style={styles.innerContainer}>
|
||||
<ThemeText
|
||||
styles={{ fontWeight: 500, fontSize: SIZES.large, marginBottom: 15 }}
|
||||
content={t('contacts.remotePaymentCurrencySelect.title')}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
styles.card,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundColor : backgroundOffset,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => onSelect('btc')}
|
||||
style={styles.optionRow}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType
|
||||
? backgroundOffset
|
||||
: COLORS.bitcoinOrange,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeImage
|
||||
styles={{ width: 25, height: 25 }}
|
||||
lightModeIcon={ICONS.bitcoinIcon}
|
||||
darkModeIcon={ICONS.bitcoinIcon}
|
||||
lightsOutIcon={ICONS.bitcoinIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.textContainer}>
|
||||
<ThemeText
|
||||
styles={styles.optionTitle}
|
||||
content={t('constants.bitcoin_upper')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.optionSubtitle}
|
||||
content={t(
|
||||
'contacts.remotePaymentCurrencySelect.futurePaymentsMessage',
|
||||
{
|
||||
option: t('constants.bitcoin_upper'),
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<CheckMarkCircle
|
||||
isActive={currentCurrency === 'btc'}
|
||||
containerSize={25}
|
||||
switchDarkMode={theme && !darkModeType ? true : false}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.separator} />
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => onSelect('usd')}
|
||||
style={styles.optionRow}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
backgroundColor:
|
||||
theme && darkModeType ? backgroundOffset : COLORS.dollarGreen,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ThemeImage
|
||||
styles={{ width: 25, height: 25 }}
|
||||
lightModeIcon={ICONS.dollarIcon}
|
||||
darkModeIcon={ICONS.dollarIcon}
|
||||
lightsOutIcon={ICONS.dollarIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.textContainer}>
|
||||
<ThemeText
|
||||
styles={styles.optionTitle}
|
||||
content={t('constants.dollars_upper')}
|
||||
/>
|
||||
<ThemeText
|
||||
styles={styles.optionSubtitle}
|
||||
content={t(
|
||||
'contacts.remotePaymentCurrencySelect.futurePaymentsMessage',
|
||||
{
|
||||
option: t('constants.dollars_upper'),
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<CheckMarkCircle
|
||||
isActive={currentCurrency === 'usd'}
|
||||
containerSize={25}
|
||||
switchDarkMode={theme && !darkModeType ? true : false}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ThemeText
|
||||
styles={styles.footnote}
|
||||
content={t('contacts.remotePaymentCurrencySelect.warningMessage', {
|
||||
option: t('constants.dollars_upper'),
|
||||
amount: displayCorrectDenomination({
|
||||
amount: 2000,
|
||||
masterInfoObject: {
|
||||
...masterInfoObject,
|
||||
userBalanceDenomination: 'sats',
|
||||
},
|
||||
fiatStats,
|
||||
}),
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
innerContainer: {
|
||||
flex: 1,
|
||||
width: INSET_WINDOW_WIDTH,
|
||||
...CENTER,
|
||||
},
|
||||
card: {
|
||||
width: '100%',
|
||||
borderRadius: 12,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 5,
|
||||
},
|
||||
optionRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 15,
|
||||
gap: 10,
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginRight: 15,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 24,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
optionTitle: {
|
||||
fontWeight: 500,
|
||||
fontSize: SIZES.large,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
optionSubtitle: {
|
||||
fontSize: SIZES.small,
|
||||
opacity: 0.6,
|
||||
marginTop: 3,
|
||||
},
|
||||
separator: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
opacity: 0.1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
footnote: {
|
||||
marginTop: 15,
|
||||
fontSize: SIZES.small,
|
||||
opacity: 0.5,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useState } from 'react';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '../../../../functions/CustomElements';
|
||||
import { CENTER, COLORS, SIZES } from '../../../../constants';
|
||||
import CustomSearchInput from '../../../../functions/CustomElements/searchInput';
|
||||
import { INSET_WINDOW_WIDTH, WINDOWWIDTH } from '../../../../constants/theme';
|
||||
import { INSET_WINDOW_WIDTH } from '../../../../constants/theme';
|
||||
import CustomButton from '../../../../functions/CustomElements/button';
|
||||
import {
|
||||
keyboardGoBack,
|
||||
@@ -22,12 +22,20 @@ export default function SavingsGoalDescribe(props) {
|
||||
const [isKeyboardActive, setIsKeyboardActive] = useState(false);
|
||||
const navigate = useNavigation();
|
||||
const { t } = useTranslation();
|
||||
const inputRef = useRef(null);
|
||||
const emoji = props?.route?.params?.emoji || '🎯';
|
||||
|
||||
const [goalName, setGoalName] = useState('');
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { textColor } = GetThemeColors();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const timer = setTimeout(() => inputRef.current?.focus(), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, []),
|
||||
);
|
||||
|
||||
const isOverLimit = goalName.length >= 50;
|
||||
const characterCountColor = isOverLimit
|
||||
? theme && darkModeType
|
||||
@@ -54,6 +62,7 @@ export default function SavingsGoalDescribe(props) {
|
||||
/>
|
||||
|
||||
<CustomSearchInput
|
||||
textInputRef={inputRef}
|
||||
containerStyles={styles.inputWrap}
|
||||
placeholderText={t('savings.goalDescribe.placeholder')}
|
||||
setInputText={setGoalName}
|
||||
|
||||
@@ -4,6 +4,7 @@ import CameraModal from './homeComponents/cameraModal';
|
||||
import {
|
||||
ContactsPage,
|
||||
EditMyProfilePage,
|
||||
EditProfileFieldPage,
|
||||
ExpandedContactsPage,
|
||||
// MyContactProfilePage,
|
||||
SendAndRequestPage,
|
||||
@@ -63,6 +64,7 @@ export {
|
||||
HalfModalReceiveOptions,
|
||||
ExpandedContactsPage,
|
||||
EditMyProfilePage,
|
||||
EditProfileFieldPage,
|
||||
// MyContactProfilePage,
|
||||
SendAndRequestPage,
|
||||
ErrorScreen,
|
||||
|
||||
@@ -17,6 +17,7 @@ const IS_BLITZ_URL_REGEX =
|
||||
const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
const VALID_USERNAME_REGEX = /^(?=.*\p{L})[\p{L}\p{N}_]+$/u;
|
||||
const VALID_NAME_BIO_REGEX = /^[^<>{}`\\]*$/u;
|
||||
|
||||
const IS_SPARK_ID =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
@@ -161,6 +162,7 @@ export {
|
||||
MAX_CHANNEL_OPEN_FEE,
|
||||
EMAIL_REGEX,
|
||||
VALID_USERNAME_REGEX,
|
||||
VALID_NAME_BIO_REGEX,
|
||||
BLITZ_RECEIVE_FEE,
|
||||
BLITZ_SEND_FEE,
|
||||
AUTO_CHANNEL_REBALANCE_STORAGE_KEY,
|
||||
|
||||
@@ -72,6 +72,7 @@ import AddGiftQuantityHalfModal from '../../components/admin/homeComponents/gift
|
||||
import SwapFlowHalfModal from '../../components/admin/homeComponents/swaps/swapFlowHalfModal';
|
||||
import TxFilterHalfModal from '../../components/admin/homeComponents/homeLightning/txFilterHalfModal';
|
||||
import PayLinkCurrencySelect from '../../components/admin/homeComponents/payLinks/components/payLinkCurrencySelect';
|
||||
import LnurlReceiveCurrencySelect from '../../components/admin/homeComponents/receiveBitcoin/lnurlReceiveCurrencySelect';
|
||||
import StablecoinAssetPickerHalfModal from './stablecoinAssetPickerHalfModal';
|
||||
import RemoveBudgetHalfModal from '../../components/admin/homeComponents/analytics/removeBudgetHalfModal';
|
||||
import BudgetWarningModal from '../../components/admin/homeComponents/sendBitcoin/components/nearBudgetLimitWarning';
|
||||
@@ -579,6 +580,13 @@ export default function CustomHalfModal(props) {
|
||||
setContentHeight={setContentHeight}
|
||||
/>
|
||||
);
|
||||
case 'lnurlReceiveCurrencySelect':
|
||||
return (
|
||||
<LnurlReceiveCurrencySelect
|
||||
handleBackPressFunction={handleBackPressFunction}
|
||||
setContentHeight={setContentHeight}
|
||||
/>
|
||||
);
|
||||
case 'stablecoinAssetPicker':
|
||||
return (
|
||||
<StablecoinAssetPickerHalfModal
|
||||
|
||||
@@ -26,6 +26,7 @@ export default function CustomSearchInput({
|
||||
autoCapitalize = 'none',
|
||||
editable = true,
|
||||
autoFocus = false,
|
||||
returnKeyType = 'default',
|
||||
}) {
|
||||
const { theme, darkModeType } = useGlobalThemeContext();
|
||||
const { textInputColor, textInputBackground } = GetThemeColors();
|
||||
@@ -139,6 +140,7 @@ export default function CustomSearchInput({
|
||||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={false}
|
||||
editable={editable}
|
||||
returnKeyType={returnKeyType}
|
||||
/>
|
||||
{buttonComponent && buttonComponent}
|
||||
</View>
|
||||
|
||||
@@ -210,6 +210,7 @@ export default async function initializeUserSettingsFromHistory({
|
||||
name: '',
|
||||
pubkey: '',
|
||||
};
|
||||
const lnurlReceiveCurrency = blitzStoredData.lnurlReceiveCurrency || 'btc';
|
||||
|
||||
// let lnurlPubKey = blitzStoredData.lnurlPubKey;
|
||||
|
||||
@@ -404,6 +405,7 @@ export default async function initializeUserSettingsFromHistory({
|
||||
tempObject['nextAccountDerivationIndex'] = nextAccountDerivationIndex;
|
||||
tempObject['currentDerivedPoolIndex'] = currentDerivedPoolIndex;
|
||||
tempObject['monthlyBudget'] = monthlyBudget;
|
||||
tempObject['lnurlReceiveCurrency'] = lnurlReceiveCurrency;
|
||||
|
||||
// store in contacts context
|
||||
tempObject['contacts'] = contacts;
|
||||
|
||||
@@ -84,9 +84,10 @@ export default function ReceivePaymentHome(props) {
|
||||
const [addressState, setAddressState] = useState({
|
||||
selectedRecieveOption: selectedRecieveOption,
|
||||
isReceivingSwap: false,
|
||||
generatedAddress: isUsingAltAccount
|
||||
? ''
|
||||
: `${globalContactsInformation.myProfile.uniqueName}@blitzwalletapp.com`,
|
||||
generatedAddress:
|
||||
isUsingAltAccount || masterInfoObject.lnurlReceiveCurrency === 'usd'
|
||||
? ''
|
||||
: `${globalContactsInformation.myProfile.uniqueName}@blitzwalletapp.com`,
|
||||
isGeneratingInvoice: false,
|
||||
isHoldInvoice: false,
|
||||
minMaxSwapAmount: {
|
||||
@@ -175,7 +176,8 @@ export default function ReceivePaymentHome(props) {
|
||||
!isUsingAltAccount &&
|
||||
endReceiveType === 'BTC' &&
|
||||
!paymentDescription &&
|
||||
!addressState.isHoldInvoice
|
||||
!addressState.isHoldInvoice &&
|
||||
masterInfoObject.lnurlReceiveCurrency !== 'usd'
|
||||
) {
|
||||
setInitialSendAmount(0);
|
||||
setAddressState(prev => ({
|
||||
@@ -472,7 +474,8 @@ function QrCode(props) {
|
||||
!isUsingAltAccount &&
|
||||
endReceiveType === 'BTC' &&
|
||||
!paymentDescription &&
|
||||
!isHoldInvoice;
|
||||
!isHoldInvoice &&
|
||||
masterInfoObject.lnurlReceiveCurrency !== 'usd';
|
||||
|
||||
const qrOpacity = useSharedValue(addressState.generatedAddress ? 1 : 0);
|
||||
const loadingOpacity = useSharedValue(isUsingLnurl ? 0 : 1);
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Dieser Benutzername existiert bereits, bitte wählen Sie einen anderen.",
|
||||
"unableToSaveError": "Profilbild konnte nicht gespeichert werden, bitte versuchen Sie es erneut.",
|
||||
"deleteProfileImageError": "Profilbild konnte nicht gelöscht werden, bitte versuchen Sie es erneut.",
|
||||
"deleteWarning": "Möchten Sie diesen Kontakt wirklich löschen? Nachrichten, die älter als eine Woche sind, können nicht wiederhergestellt werden."
|
||||
"deleteWarning": "Möchten Sie diesen Kontakt wirklich löschen? Nachrichten, die älter als eine Woche sind, können nicht wiederhergestellt werden.",
|
||||
"receiveCurrencyPillTitle": "Empfangswährung",
|
||||
"receiveCurrencyPillDesc": "Legen Sie fest, wie Sie über Ihre Lightning-Adresse Zahlungen empfangen",
|
||||
"invalidCharactersError": "Name und Bio dürfen die Zeichen < > { } ` \\ nicht enthalten",
|
||||
"invalidReceiveAddressError": "Bitte geben Sie eine gültige Lightning-Adresse ein (z. B. benutzer@domain.com)",
|
||||
"aboutYou": "Über dich",
|
||||
"lightningAddress": "Lightning-Adresse"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Empfangswährung",
|
||||
"futurePaymentsMessage": "Alle zukünftigen Zahlungen werden in {{option}} empfangen",
|
||||
"warningMessage": "Bei Auswahl von {{option}} gilt ein Mindestbetrag von {{amount}}. Kleinere Zahlungen werden in BTC empfangen."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "Wie möchten Sie senden?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Bitte fügen Sie eine Beschreibung hinzu.",
|
||||
"noContactsSelected": "Bitte wählen Sie mindestens einen weiteren Kontakt aus"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Ihr Anzeigename ist der Name, unter dem andere Sie sehen. Er kann jederzeit geändert werden.",
|
||||
"uniquenameDescription": "Wenn Sie Ihren Benutzernamen ändern, ändert sich auch Ihre Lightning-Adresse. Zahlungen an Ihren alten Benutzernamen gehen verloren.",
|
||||
"bioDescription": "Ihre Bio ist eine kurze Beschreibung, damit andere Sie besser kennenlernen können."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "Über uns",
|
||||
"language": "Spracheinstellung",
|
||||
"display currency": "Standardwährung",
|
||||
"receive currency": "Lightning-Adresse",
|
||||
"display options": "Anzeige & Layout",
|
||||
"edit contact profile": "Kontaktprofil bearbeiten",
|
||||
"view all swaps": "Netzwerk-Einstellungen",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Sparpool",
|
||||
"show seed phrase": "Seed-Phrase sichern"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Lightning-Adresse",
|
||||
"lnurlPageDesc": "Ihre Lightning-Adresse ermöglicht es jedem, Sie zu bezahlen, ohne dass eine Rechnung nötig ist."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Transaktions-Hash",
|
||||
"paymentId": "Zahlungs-ID",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "This username already exists, please choose another.",
|
||||
"unableToSaveError": "Unable to save profile image, please try again.",
|
||||
"deleteProfileImageError": "Unable to delete profile image, please try again.",
|
||||
"deleteWarning": "Are you sure you want to delete this contact? Messages older than a week cannot be restored."
|
||||
"deleteWarning": "Are you sure you want to delete this contact? Messages older than a week cannot be restored.",
|
||||
"receiveCurrencyPillTitle": "Receive Currency",
|
||||
"receiveCurrencyPillDesc": "Set how you receive via your Lightning address",
|
||||
"invalidCharactersError": "Name and bio cannot contain the characters < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Please enter a valid Lightning address (e.g. user@domain.com)",
|
||||
"aboutYou": "About you",
|
||||
"lightningAddress": "Lightning Address"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Receive currency",
|
||||
"futurePaymentsMessage": "All future payments will be received as {{option}}",
|
||||
"warningMessage": "When selecting {{option}}, there’s a {{amount}} minimum. Smaller payments will be received in Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "What do you want to send?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Please add a description for this split.",
|
||||
"noContactsSelected": "Please select at least one other contact to continue"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Your display name is how others see you. It can be changed anytime.",
|
||||
"uniquenameDescription": "Changing your username also changes your Lightning Address. Payments to your old username will be lost.",
|
||||
"bioDescription": "Your bio is a short description to help others get to know you."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2001,6 +2017,7 @@
|
||||
"language": "Language",
|
||||
"pools": "Pools",
|
||||
"display currency": "Currency",
|
||||
"receive currency": "Lightning Address",
|
||||
"display options": "Display Options",
|
||||
"edit contact profile": "Edit Contact Profile",
|
||||
"view all swaps": "Network Transfers",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"blitzRestore": "Blitz Restore",
|
||||
"point-of-sale": "Point-of-sale"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Lightning address",
|
||||
"lnurlPageDesc": "Your Lightning address lets anyone pay you with no invoice needed."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Transaction Hash",
|
||||
"paymentId": "Payment ID",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Este nombre de usuario ya existe, elige otro.",
|
||||
"unableToSaveError": "No se pudo guardar la imagen de perfil, intenta de nuevo.",
|
||||
"deleteProfileImageError": "No se pudo eliminar la imagen de perfil, intenta de nuevo.",
|
||||
"deleteWarning": "¿Seguro que deseas eliminar este contacto? Los mensajes de más de una semana no se pueden restaurar."
|
||||
"deleteWarning": "¿Seguro que deseas eliminar este contacto? Los mensajes de más de una semana no se pueden restaurar.",
|
||||
"receiveCurrencyPillTitle": "Moneda de recepción",
|
||||
"receiveCurrencyPillDesc": "Configura cómo recibes pagos mediante tu dirección Lightning",
|
||||
"invalidCharactersError": "El nombre y la biografía no pueden contener los caracteres < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Introduce una dirección Lightning válida (p. ej., usuario@dominio.com)",
|
||||
"aboutYou": "Sobre ti",
|
||||
"lightningAddress": "Dirección Lightning"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Moneda de recepción",
|
||||
"futurePaymentsMessage": "Todos los pagos futuros se recibirán en {{option}}",
|
||||
"warningMessage": "Al seleccionar {{option}}, hay un mínimo de {{amount}}. Los pagos más pequeños se recibirán en Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "¿Qué deseas enviar?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Agrega una descripción para esta división.",
|
||||
"noContactsSelected": "Selecciona al menos un contacto para continuar"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Tu nombre para mostrar es como te ven los demás. Puedes cambiarlo en cualquier momento.",
|
||||
"uniquenameDescription": "Cambiar tu nombre de usuario también cambia tu dirección Lightning. Los pagos a tu nombre de usuario anterior se perderán.",
|
||||
"bioDescription": "Tu biografía es una breve descripción para ayudar a otros a conocerte."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "Acerca de",
|
||||
"language": "idioma",
|
||||
"display currency": "Moneda",
|
||||
"receive currency": "Dirección Lightning",
|
||||
"display options": "Opciones de visualización",
|
||||
"edit contact profile": "Editar perfil de contacto",
|
||||
"view all swaps": "Transferencias de red",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Pools",
|
||||
"show seed phrase": "Respaldar billetera"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Dirección Lightning",
|
||||
"lnurlPageDesc": "Tu dirección Lightning permite que cualquiera te pague sin necesidad de una factura."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Hash de transacción",
|
||||
"paymentId": "ID de pago",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Ce nom d'utilisateur existe déjà, veuillez en choisir un autre.",
|
||||
"unableToSaveError": "Impossible d'enregistrer l'image du profil, veuillez réessayer.",
|
||||
"deleteProfileImageError": "Impossible de supprimer l'image du profil, veuillez réessayer.",
|
||||
"deleteWarning": "Êtes-vous sûr de vouloir supprimer ce contact ? Les messages datant de plus d'une semaine ne peuvent pas être restaurés."
|
||||
"deleteWarning": "Êtes-vous sûr de vouloir supprimer ce contact ? Les messages datant de plus d'une semaine ne peuvent pas être restaurés.",
|
||||
"receiveCurrencyPillTitle": "Devise de réception",
|
||||
"receiveCurrencyPillDesc": "Définissez comment vous recevez via votre adresse Lightning",
|
||||
"invalidCharactersError": "Le nom et la bio ne peuvent pas contenir les caractères < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Veuillez saisir une adresse Lightning valide (p. ex. utilisateur@domaine.com)",
|
||||
"aboutYou": "À propos de vous",
|
||||
"lightningAddress": "Adresse Lightning"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Devise de réception",
|
||||
"futurePaymentsMessage": "Tous les paiements futurs seront reçus en {{option}}",
|
||||
"warningMessage": "Lors de la sélection de {{option}}, un minimum de {{amount}} s’applique. Les paiements plus petits seront reçus en Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "Que souhaitez-vous envoyer ?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Veuillez ajouter une description.",
|
||||
"noContactsSelected": "Veuillez sélectionner au moins un contact"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Votre nom d’affichage est la façon dont les autres vous voient. Vous pouvez le modifier à tout moment.",
|
||||
"uniquenameDescription": "Changer votre nom d'utilisateur change aussi votre adresse Lightning. Les paiements envoyés à votre ancien nom d'utilisateur seront perdus.",
|
||||
"bioDescription": "Votre bio est une courte description qui aide les autres à mieux vous connaître."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "A propos de",
|
||||
"language": "Langue",
|
||||
"display currency": "Monnaie",
|
||||
"receive currency": "Adresse Lightning",
|
||||
"display options": "Options d'affichage",
|
||||
"edit contact profile": "Modifier le profil du contact",
|
||||
"view all swaps": "Transferts de réseaux",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Pools",
|
||||
"show seed phrase": "Sauvegarder le portefeuille"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Adresse Lightning",
|
||||
"lnurlPageDesc": "Votre adresse Lightning permet à n’importe qui de vous payer sans facture."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Hachure de la transaction",
|
||||
"paymentId": "Id de paiement",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Questo nome utente esiste già, scegli un altro.",
|
||||
"unableToSaveError": "Impossibile salvare l'immagine del profilo, riprova.",
|
||||
"deleteProfileImageError": "Impossibile eliminare l'immagine del profilo, riprova.",
|
||||
"deleteWarning": "Sei sicuro di voler eliminare questo contatto? I messaggi più vecchi di una settimana non possono essere ripristinati."
|
||||
"deleteWarning": "Sei sicuro di voler eliminare questo contatto? I messaggi più vecchi di una settimana non possono essere ripristinati.",
|
||||
"receiveCurrencyPillTitle": "Valuta di ricezione",
|
||||
"receiveCurrencyPillDesc": "Imposta come ricevere tramite il tuo indirizzo Lightning",
|
||||
"invalidCharactersError": "Nome e bio non possono contenere i caratteri < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Inserisci un indirizzo Lightning valido (es. utente@dominio.com)",
|
||||
"aboutYou": "Su di te",
|
||||
"lightningAddress": "Indirizzo Lightning"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Valuta di ricezione",
|
||||
"futurePaymentsMessage": "Tutti i pagamenti futuri saranno ricevuti in {{option}}",
|
||||
"warningMessage": "Se selezioni {{option}}, è previsto un minimo di {{amount}}. I pagamenti più piccoli saranno ricevuti in Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "Cosa vuoi inviare?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Aggiungi una descrizione.",
|
||||
"noContactsSelected": "Seleziona almeno un contatto"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Il tuo nome visualizzato è il modo in cui gli altri ti vedono. Può essere modificato in qualsiasi momento.",
|
||||
"uniquenameDescription": "Modificare il tuo username cambia anche il tuo indirizzo Lightning. I pagamenti al tuo vecchio username andranno persi.",
|
||||
"bioDescription": "La tua bio è una breve descrizione per aiutare gli altri a conoscerti."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "Informazioni",
|
||||
"language": "Lingua",
|
||||
"display currency": "Valuta",
|
||||
"receive currency": "Indirizzo Lightning",
|
||||
"display options": "Opzioni di Visualizzazione",
|
||||
"edit contact profile": "Modifica Profilo Contatto",
|
||||
"view all swaps": "Trasferimenti di rete",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Pools",
|
||||
"show seed phrase": "Backup portafoglio"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Indirizzo Lightning",
|
||||
"lnurlPageDesc": "Il tuo indirizzo Lightning consente a chiunque di pagarti senza bisogno di una fattura."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Hash Transazione",
|
||||
"paymentId": "ID Pagamento",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Este nome de usuário já existe, escolha outro.",
|
||||
"unableToSaveError": "Não foi possível salvar a imagem do perfil, tente novamente.",
|
||||
"deleteProfileImageError": "Não foi possível excluir a imagem do perfil, tente novamente.",
|
||||
"deleteWarning": "Tem certeza de que deseja excluir este contato? Mensagens com mais de uma semana não podem ser restauradas."
|
||||
"deleteWarning": "Tem certeza de que deseja excluir este contato? Mensagens com mais de uma semana não podem ser restauradas.",
|
||||
"receiveCurrencyPillTitle": "Moeda de recebimento",
|
||||
"receiveCurrencyPillDesc": "Defina como você recebe pelo seu endereço Lightning",
|
||||
"invalidCharactersError": "O nome e a bio não podem conter os caracteres < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Digite um endereço Lightning válido (ex.: usuario@dominio.com)",
|
||||
"aboutYou": "Sobre você",
|
||||
"lightningAddress": "Endereço Lightning"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Moeda de recebimento",
|
||||
"futurePaymentsMessage": "Todos os pagamentos futuros serão recebidos em {{option}}",
|
||||
"warningMessage": "Ao selecionar {{option}}, há um mínimo de {{amount}}. Pagamentos menores serão recebidos em Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "O que você deseja enviar?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Adicione uma descrição.",
|
||||
"noContactsSelected": "Selecione pelo menos um contato"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Seu nome de exibição é como as outras pessoas veem você. Ele pode ser alterado a qualquer momento.",
|
||||
"uniquenameDescription": "Alterar seu nome de usuário também altera seu endereço Lightning. Pagamentos para seu nome de usuário antigo serão perdidos.",
|
||||
"bioDescription": "Sua bio é uma breve descrição para ajudar outras pessoas a conhecer você."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "Sobre",
|
||||
"language": "Idioma",
|
||||
"display currency": "Moeda de exibição",
|
||||
"receive currency": "Endereço Lightning",
|
||||
"display options": "Opções de tela",
|
||||
"edit contact profile": "Editar perfil de contato",
|
||||
"view all swaps": "Transferências de rede",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Vaquinhas",
|
||||
"show seed phrase": "Fazer backup da carteira"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Endereço Lightning",
|
||||
"lnurlPageDesc": "Seu endereço Lightning permite que qualquer pessoa pague você sem precisar de fatura."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Hash da Transação",
|
||||
"paymentId": "ID",
|
||||
|
||||
@@ -1247,7 +1247,18 @@
|
||||
"usernameAlreadyExistsError": "Имя занято, выберите другое.",
|
||||
"unableToSaveError": "Не удалось сохранить фото.",
|
||||
"deleteProfileImageError": "Не удалось удалить фото.",
|
||||
"deleteWarning": "Удалить контакт? Сообщения старше недели нельзя восстановить."
|
||||
"deleteWarning": "Удалить контакт? Сообщения старше недели нельзя восстановить.",
|
||||
"receiveCurrencyPillTitle": "Валюта получения",
|
||||
"receiveCurrencyPillDesc": "Настройте, как вы будете получать платежи через адрес Молнии",
|
||||
"invalidCharactersError": "Имя и био не могут содержать символы < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Введите корректный адрес Молнии (например, user@domain.com)",
|
||||
"aboutYou": "О вас",
|
||||
"lightningAddress": "Lightning-адрес"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Валюта получения",
|
||||
"futurePaymentsMessage": "Все будущие платежи будут получены в {{option}}",
|
||||
"warningMessage": "При выборе {{option}} действует минимальная сумма {{amount}}. Более мелкие платежи будут получены в Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "Что вы хотите отправить?"
|
||||
@@ -1362,6 +1373,11 @@
|
||||
"noDescription": "Добавьте описание.",
|
||||
"noContactsSelected": "Выберите хотя бы один контакт"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Ваше отображаемое имя видят другие пользователи. Его можно изменить в любой момент.",
|
||||
"uniquenameDescription": "Изменение юзернейма также изменит ваш адрес Молнии. Платежи на ваш старый юзернейм будут потеряны.",
|
||||
"bioDescription": "Ваше био — это короткое описание, которое помогает другим узнать вас лучше."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2001,6 +2017,7 @@
|
||||
"about": "О приложении",
|
||||
"language": "Язык",
|
||||
"display currency": "Валюта",
|
||||
"receive currency": "Адрес Молнии",
|
||||
"display options": "Отображение",
|
||||
"edit contact profile": "Редактировать профиль",
|
||||
"view all swaps": "Сетевые переводы",
|
||||
@@ -2026,6 +2043,10 @@
|
||||
"pools": "Пулы",
|
||||
"show seed phrase": "Резервное копирование кошелька"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Адрес Молнии",
|
||||
"lnurlPageDesc": "Ваш адрес Молнии позволяет любому отправить вам платеж без инвойса."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Хэш транзакции",
|
||||
"paymentId": "ID платежа",
|
||||
|
||||
@@ -1246,7 +1246,18 @@
|
||||
"usernameAlreadyExistsError": "Detta användarnamn finns redan, vänligen välj ett annat.",
|
||||
"unableToSaveError": "Det gick inte att spara profilbilden, försök igen.",
|
||||
"deleteProfileImageError": "Det går inte att ta bort profilbilden, försök igen.",
|
||||
"deleteWarning": "Är du säker på att du vill ta bort den här kontakten? Meddelanden som är äldre än en vecka kan inte återställas."
|
||||
"deleteWarning": "Är du säker på att du vill ta bort den här kontakten? Meddelanden som är äldre än en vecka kan inte återställas.",
|
||||
"receiveCurrencyPillTitle": "Mottagningsvaluta",
|
||||
"receiveCurrencyPillDesc": "Ställ in hur du tar emot via din Lightning-adress",
|
||||
"invalidCharactersError": "Namn och bio får inte innehålla tecknen < > { } ` \\",
|
||||
"invalidReceiveAddressError": "Ange en giltig Lightning-adress (t.ex. användare@domän.com)",
|
||||
"aboutYou": "Om dig",
|
||||
"lightningAddress": "Lightning-adress"
|
||||
},
|
||||
"remotePaymentCurrencySelect": {
|
||||
"title": "Mottagningsvaluta",
|
||||
"futurePaymentsMessage": "Alla framtida betalningar kommer att tas emot i {{option}}",
|
||||
"warningMessage": "När du väljer {{option}} finns ett minimum på {{amount}}. Mindre betalningar tas emot i Bitcoin."
|
||||
},
|
||||
"selectCurrencyToSend": {
|
||||
"header": "Vad vill du skicka?"
|
||||
@@ -1361,6 +1372,11 @@
|
||||
"noDescription": "Lägg till en beskrivning.",
|
||||
"noContactsSelected": "Välj minst en kontakt"
|
||||
}
|
||||
},
|
||||
"editProfileFieldPage": {
|
||||
"nameDescription": "Ditt visningsnamn är hur andra ser dig. Det kan ändras när som helst.",
|
||||
"uniquenameDescription": "Om du ändrar ditt användarnamn ändras också din Lightning-adress. Betalningar till ditt gamla användarnamn går förlorade.",
|
||||
"bioDescription": "Din bio är en kort beskrivning som hjälper andra att lära känna dig."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -2000,6 +2016,7 @@
|
||||
"about": "Om",
|
||||
"language": "Språk",
|
||||
"display currency": "Valuta",
|
||||
"receive currency": "Lightning-adress",
|
||||
"display options": "Alternativ för visning",
|
||||
"edit contact profile": "Redigera kontaktprofil",
|
||||
"view all swaps": "Nätverksöverföringar",
|
||||
@@ -2025,6 +2042,10 @@
|
||||
"pools": "Pooler",
|
||||
"show seed phrase": "Säkerhetskopiera plånbok"
|
||||
},
|
||||
"remotePayments": {
|
||||
"lnAddress": "Lightning-adress",
|
||||
"lnurlPageDesc": "Din Lightning-adress gör att vem som helst kan betala dig utan att någon faktura behövs."
|
||||
},
|
||||
"technicalTransactionDetails": {
|
||||
"txHash": "Transaktionens hash",
|
||||
"paymentId": "Betalning Id",
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
AddFriendsToSplit,
|
||||
CreateSplitBill,
|
||||
EditMyProfilePage,
|
||||
EditProfileFieldPage,
|
||||
ExpandedAddContactsPage,
|
||||
ExpandedContactsPage,
|
||||
// MyContactProfilePage,
|
||||
@@ -153,6 +154,7 @@ const SLIDE_FROM_RIGHT_SCREENS = [
|
||||
{ name: 'ReceiveBTC', component: ReceivePaymentHome },
|
||||
// { name: 'MyContactProfilePage', component: MyContactProfilePage },
|
||||
{ name: 'EditMyProfilePage', component: EditMyProfilePage },
|
||||
{ name: 'EditProfileFieldPage', component: EditProfileFieldPage },
|
||||
{ name: 'ExpandedAddContactsPage', component: ExpandedAddContactsPage },
|
||||
{ name: 'SendAndRequestPage', component: SendAndRequestPage },
|
||||
{ name: 'AddFriendsToSplit', component: AddFriendsToSplit },
|
||||
|
||||
Reference in New Issue
Block a user