Retry image support (#108)

* add dependencies

* adding profile image componeent

* upgrading to img check componenet

* creating storage functions

* adding storage endpionts

* adding rn-fast-image

* creating cache image context

* using fast imgae for profile images

* updating upload function

* adding images

* fixing new share placement icon

* fixing parellel quering

* get contact image from saved cache

* cache image function for adding contact half modal

* gets contact from cache not cache obj due to non active updates

* finalizing image uploads

* fix load missunderstanding  null image

* fixed style inconsistancy
This commit is contained in:
Blake Kaufman
2025-05-19 16:21:10 -04:00
committed by GitHub
parent e4fbaba7fc
commit 87ec3d4ede
22 changed files with 789 additions and 270 deletions
+6 -3
View File
@@ -64,6 +64,7 @@ import getDeepLinkUser from './app/components/admin/homeComponents/contacts/inte
import {navigationRef} from './navigation/navigationService';
import {GlobalConbinedTxContextProvider} from './context-store/combinedTransactionsContext';
import BreezTest from './app/screens/breezTest';
import {ImageCacheProvider} from './context-store/imageCache';
const Stack = createNativeStackNavigator();
@@ -84,10 +85,12 @@ function App(): JSX.Element {
<PushNotificationManager>
<LiquidEventProvider>
<LightningEventProvider>
{/* <Suspense
<ImageCacheProvider>
{/* <Suspense
fallback={<FullLoadingScreen text={'Loading Page'} />}> */}
<ResetStack />
{/* </Suspense> */}
<ResetStack />
{/* </Suspense> */}
</ImageCacheProvider>
</LightningEventProvider>
</LiquidEventProvider>
</PushNotificationManager>
+1 -1
View File
@@ -92,7 +92,7 @@ android {
versionCode 26
versionName "0.4.5"
ndk {
abiFilters 'arm64-v8a', 'x86_64', // Removing these two version since liquid_sdk is not supported 'x86', 'armeabi-v7a', // Exclude riscv64
abiFilters 'arm64-v8a', 'x86_64' // Removing these two version since liquid_sdk is not supported 'x86', 'armeabi-v7a', // Exclude riscv64
}
// Add a build config field to read the Breez API key
// from system
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -26,6 +26,8 @@ import {useKeysContext} from '../../../../../context-store/keys';
import {keyboardNavigate} from '../../../../functions/customNavigation';
import {useGlobalThemeContext} from '../../../../../context-store/theme';
import sha256Hash from '../../../../functions/hash';
import ContactProfileImage from './internalComponents/profileImage';
import {getCachedProfileImage} from '../../../../functions/cachedImage';
export default function AddContactsHalfModal(props) {
const {contactsPrivateKey} = useKeysContext();
@@ -40,25 +42,44 @@ export default function AddContactsHalfModal(props) {
const debouncedSearch = useDebounce(async term => {
const results = await searchUsers(term);
const newUsers = results
.map((savedContact, id) => {
if (!savedContact) {
return false;
}
if (
savedContact.uniqueName ===
globalContactsInformation.myProfile.uniqueName
)
return false;
if (!savedContact.receiveAddress) return false;
return savedContact;
})
.filter(Boolean);
const newUsers = (
await Promise.all(
results.map(async savedContact => {
if (!savedContact) return false;
if (
savedContact.uniqueName ===
globalContactsInformation.myProfile.uniqueName
)
return false;
if (!savedContact?.uuid) return false;
let responseData;
if (
savedContact.hasProfileImage ||
typeof savedContact.hasProfileImage === 'boolean'
) {
responseData = await getCachedProfileImage(savedContact.uuid);
console.log(responseData, 'response');
}
if (!responseData) return savedContact;
else
return {
...savedContact,
...responseData,
};
}),
)
).filter(Boolean);
console.log(newUsers, 'test');
unstable_batchedUpdates(() => {
setIsSearching(false);
setUsers(newUsers);
});
}, 500);
}, 800);
console.log(users);
const handleSearch = term => {
setSearchInput(term);
@@ -192,20 +213,37 @@ export default function AddContactsHalfModal(props) {
/>
</ScrollView>
) : (
<FlatList
key={sha256Hash(users.join('') + `${isSearching}`)}
showsVerticalScrollIndicator={false}
data={users}
renderItem={({item}) => (
<ContactListItem
savedContact={item}
contactsPrivateKey={contactsPrivateKey}
<>
{users.length ? (
<FlatList
key={sha256Hash(users.join('') + `${isSearching}`)}
showsVerticalScrollIndicator={false}
data={users}
renderItem={({item}) => (
<ContactListItem
savedContact={item}
contactsPrivateKey={contactsPrivateKey}
theme={theme}
darkModeType={darkModeType}
/>
)}
keyExtractor={item => item?.uniqueName}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="none"
/>
) : (
<ThemeText
styles={{textAlign: 'center', marginTop: 20}}
content={
isSearching
? ''
: searchInput
? 'No profiles match this search'
: 'Start typing to search for a profile'
}
/>
)}
keyExtractor={item => item?.uniqueName}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="none"
/>
</>
)}
</View>
</View>
@@ -215,6 +253,7 @@ export default function AddContactsHalfModal(props) {
function ContactListItem(props) {
const {textColor, backgroundOffset} = GetThemeColors();
const navigate = useNavigation();
const newContact = {
...props.savedContact,
isFavorite: false,
@@ -235,13 +274,14 @@ function ContactListItem(props) {
style={[
styles.contactListLetterImage,
{
borderColor: textColor,
backgroundColor: backgroundOffset,
},
]}>
<ThemeText
styles={{includeFontPadding: false}}
content={newContact.uniqueName[0].toUpperCase()}
<ContactProfileImage
updated={newContact.updated}
uri={newContact.localUri}
darkModeType={props.darkModeType}
theme={props.theme}
/>
</View>
<View>
@@ -287,12 +327,12 @@ const styles = StyleSheet.create({
},
contactListLetterImage: {
height: 30,
width: 30,
borderRadius: 15,
height: 40,
width: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
overflow: 'hidden',
marginRight: 10,
},
});
@@ -27,25 +27,23 @@ import {useGlobalThemeContext} from '../../../../../context-store/theme';
import {useAppStatus} from '../../../../../context-store/appStatus';
import {useKeysContext} from '../../../../../context-store/keys';
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
import {ANDROIDSAFEAREA, KEYBOARDTIMEOUT} from '../../../../constants/styles';
import {keyboardNavigate} from '../../../../functions/customNavigation';
import {crashlyticsLogReport} from '../../../../functions/crashlyticsLogs';
import ContactProfileImage from './internalComponents/profileImage';
import {useImageCache} from '../../../../../context-store/imageCache';
export default function ContactsPage({navigation}) {
const {masterInfoObject} = useGlobalContextProvider();
const {cache} = useImageCache();
const {isConnectedToTheInternet} = useAppStatus();
const {theme, darkModeType} = useGlobalThemeContext();
const {
decodedAddedContacts,
globalContactsInformation,
myProfileImage,
contactsMessags,
} = useGlobalContacts();
const {decodedAddedContacts, globalContactsInformation, contactsMessags} =
useGlobalContacts();
const [inputText, setInputText] = useState('');
const hideUnknownContacts = masterInfoObject.hideUnknownContacts;
const tabsNavigate = navigation.navigate;
const navigate = useNavigation();
const {backgroundOffset} = GetThemeColors();
const {backgroundOffset, backgroundColor} = GetThemeColors();
const myProfile = globalContactsInformation.myProfile;
const didEditProfile = globalContactsInformation.myProfile.didEditProfile;
@@ -65,7 +63,13 @@ export default function ContactsPage({navigation}) {
return decodedAddedContacts
.filter(contact => contact.isFavorite)
.map((contact, id) => {
return <PinnedContactElement key={contact.uuid} contact={contact} />;
return (
<PinnedContactElement
cache={cache}
key={contact.uuid}
contact={contact}
/>
);
});
}, [decodedAddedContacts, contactsMessags]);
@@ -87,7 +91,9 @@ export default function ContactsPage({navigation}) {
return (earliset_B || 0) - (earliset_A || 0);
})
.map((contact, id) => {
return <ContactElement key={contact.uuid} contact={contact} />;
return (
<ContactElement cache={cache} key={contact.uuid} contact={contact} />
);
});
}, [decodedAddedContacts, inputText, hideUnknownContacts, contactsMessags]);
@@ -143,19 +149,11 @@ export default function ContactsPage({navigation}) {
...styles.profileImageContainer,
backgroundColor: backgroundOffset,
}}>
<Image
source={
myProfileImage
? {uri: myProfileImage}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
myProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
}
<ContactProfileImage
updated={cache[masterInfoObject?.uuid]?.updated}
uri={cache[masterInfoObject?.uuid]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
</TouchableOpacity>
@@ -166,11 +164,14 @@ export default function ContactsPage({navigation}) {
).length !== 0 && myProfile.didEditProfile ? (
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingTop: 10, paddingBottom: 10}}
contentContainerStyle={{
paddingTop: pinnedContacts.length ? 0 : 10,
paddingBottom: 10,
}}
style={{flex: 1, overflow: 'hidden'}}
stickyHeaderIndices={[pinnedContacts.length ? 1 : 0]}>
{pinnedContacts.length != 0 && (
<View style={{height: 130}}>
<View style={{height: 120}}>
<ScrollView
showsHorizontalScrollIndicator={false}
horizontal
@@ -183,6 +184,7 @@ export default function ContactsPage({navigation}) {
placeholderText={'Search added contacts'}
inputText={inputText}
setInputText={setInputText}
containerStyles={{width: '100%', backgroundColor}}
/>
{contactElements}
</ScrollView>
@@ -288,19 +290,19 @@ function PinnedContactElement(props) {
position: 'relative',
},
]}>
<Image
source={
contact.profileImage
? {uri: contact.profileImage}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
<ContactProfileImage
updated={
contact.isLNURL
? new Date().toISOString()
: props.cache[contact.uuid]?.updated
}
style={
contact.profileImage
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
uri={
contact.isLNURL
? contact.profileImage
: props.cache[contact.uuid]?.localUri
}
darkModeType={darkModeType}
theme={theme}
/>
</View>
@@ -393,19 +395,19 @@ export function ContactElement(props) {
position: 'relative',
},
]}>
<Image
source={
contact.profileImage
? {uri: contact.profileImage}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
<ContactProfileImage
updated={
contact.isLNURL
? new Date().toISOString()
: props.cache[contact.uuid]?.updated
}
style={
contact.profileImage
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
uri={
contact.isLNURL
? contact.profileImage
: props.cache[contact.uuid]?.localUri
}
darkModeType={darkModeType}
theme={theme}
/>
</View>
<View
@@ -661,9 +663,8 @@ const styles = StyleSheet.create({
},
pinnedContact: {
height: 'auto',
margin: 5,
// height: 'auto',
marginHorizontal: 5,
alignItems: 'center',
},
pinnedContactsContainer: {
@@ -30,10 +30,6 @@ import {isValidUniqueName} from '../../../../../db';
import CustomButton from '../../../../functions/CustomElements/button';
import {useGlobalContacts} from '../../../../../context-store/globalContacts';
import GetThemeColors from '../../../../hooks/themeColors';
import {
removeLocalStorageItem,
setLocalStorageItem,
} from '../../../../functions/localStorage';
import {getImageFromLibrary} from '../../../../functions/imagePickerWrapper';
import {useGlobalThemeContext} from '../../../../../context-store/theme';
import {useKeysContext} from '../../../../../context-store/keys';
@@ -43,6 +39,15 @@ import {INSET_WINDOW_WIDTH} from '../../../../constants/theme';
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
import {keyboardGoBack} from '../../../../functions/customNavigation';
import {useTranslation} from 'react-i18next';
import * as ImageManipulator from 'expo-image-manipulator';
import ContactProfileImage from './internalComponents/profileImage';
import FullLoadingScreen from '../../../../functions/CustomElements/loadingScreen';
import {
deleteDatabaseImage,
setDatabaseIMG,
} from '../../../../../db/photoStorage';
import {useImageCache} from '../../../../../context-store/imageCache';
import {useContactImage} from '../../../../hooks/useContactImage';
export default function EditMyProfilePage(props) {
const navigate = useNavigation();
@@ -127,16 +132,16 @@ function InnerContent({
}) {
const {contactsPrivateKey, publicKey} = useKeysContext();
const {theme, darkModeType} = useGlobalThemeContext();
const {cache, refreshCache, removeProfileImageFromCache} = useImageCache();
const {backgroundOffset, textInputColor, textInputBackground, textColor} =
GetThemeColors();
const {
decodedAddedContacts,
globalContactsInformation,
toggleGlobalContactsInformation,
setMyProfileImage,
myProfileImage,
} = useGlobalContacts();
const {t} = useTranslation();
const [isAddingImage, setIsAddingImage] = useState(false);
const nameRef = useRef(null);
const uniquenameRef = useRef(null);
@@ -162,6 +167,7 @@ function InnerContent({
uniquename: '',
receiveAddress: '',
});
const [hasImage, setHasImage] = useState(false);
const [isKeyboardActive, setIsKeyboardActive] = useState(false);
const paddingBottom = Platform.select({
ios: insets.bottom,
@@ -212,6 +218,9 @@ function InnerContent({
selectedAddedContactUniqueName,
]);
const myProfileImage = cache[myContact?.uuid];
const selectedAddedContactImage = useContactImage(selectedAddedContact?.uuid);
return (
<View style={styles.innerContainer}>
<ScrollView
@@ -223,20 +232,23 @@ function InnerContent({
...CENTER,
}}>
<TouchableOpacity
activeOpacity={
(isEditingMyProfile || selectedAddedContact.isLNURL) &&
!isAddingImage
? 0.2
: 1
}
onPress={() => {
if (
(!selectedAddedContact?.profileImage && !isEditingMyProfile) ||
(!myProfileImage && isEditingMyProfile)
) {
if (!isEditingMyProfile && !selectedAddedContact.isLNURL) return;
if (isAddingImage) return;
if (!hasImage) {
addProfilePicture();
return;
}
navigate.navigate('AddOrDeleteContactImage', {
addPhoto: addProfilePicture,
deletePhoto: deleteProfilePicture,
hasImage:
(selectedAddedContact?.profileImage && !isEditingMyProfile) ||
(myProfileImage && isEditingMyProfile),
hasImage: hasImage,
});
}}>
<View
@@ -246,38 +258,38 @@ function InnerContent({
backgroundColor: backgroundOffset,
},
]}>
<Image
source={
(selectedAddedContact?.profileImage && !isEditingMyProfile) ||
(myProfileImage && isEditingMyProfile)
? {
uri: isEditingMyProfile
? myProfileImage
: selectedAddedContact?.profileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
(selectedAddedContact?.profileImage && !isEditingMyProfile) ||
(myProfileImage && isEditingMyProfile)
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
}
/>
</View>
<View style={styles.selectFromPhotos}>
<Image
source={
(selectedAddedContact?.profileImage && !isEditingMyProfile) ||
(myProfileImage && isEditingMyProfile)
? ICONS.xSmallIconBlack
: ICONS.ImagesIconDark
}
style={{width: 20, height: 20}}
/>
{isAddingImage ? (
<FullLoadingScreen showText={false} />
) : (
<ContactProfileImage
updated={
isEditingMyProfile
? myProfileImage?.updated
: selectedAddedContact.isLNURL
? new Date().toISOString()
: selectedAddedContactImage?.updated
}
uri={
isEditingMyProfile
? myProfileImage?.localUri
: selectedAddedContact.isLNURL
? selectedAddedContact.profileImage
: selectedAddedContactImage?.localUri
}
darkModeType={darkModeType}
theme={theme}
setHasImage={setHasImage}
/>
)}
</View>
{(isEditingMyProfile || selectedAddedContact.isLNURL) && (
<View style={styles.selectFromPhotos}>
<Image
source={hasImage ? ICONS.xSmallIconBlack : ICONS.ImagesIconDark}
style={{width: 20, height: 20}}
/>
</View>
)}
</TouchableOpacity>
<TouchableOpacity
@@ -661,7 +673,7 @@ function InnerContent({
}
async function addProfilePicture() {
const imagePickerResponse = await getImageFromLibrary();
const imagePickerResponse = await getImageFromLibrary({quality: 1});
const {didRun, error, imgURL} = imagePickerResponse;
if (!didRun) return;
if (error) {
@@ -670,8 +682,19 @@ function InnerContent({
}
if (isEditingMyProfile) {
setMyProfileImage(imgURL.uri);
setLocalStorageItem('myProfileImage', imgURL.uri);
const response = await uploadProfileImage({imgURL: imgURL});
if (!response) return;
toggleGlobalContactsInformation(
{
myProfile: {
...globalContactsInformation.myProfile,
hasProfileImage: true,
},
addedContacts: globalContactsInformation.addedContacts,
},
true,
);
return;
}
@@ -709,11 +732,63 @@ function InnerContent({
true,
);
}
async function uploadProfileImage({imgURL, removeImage}) {
try {
setIsAddingImage(true);
if (!removeImage) {
const resized = ImageManipulator.ImageManipulator.manipulate(
imgURL.uri,
).resize({width: 350});
const image = await resized.renderAsync();
const savedImage = await image.saveAsync({
compress: 0.4,
format: ImageManipulator.SaveFormat.WEBP,
});
const response = await setDatabaseIMG(
globalContactsInformation.myProfile.uuid,
{uri: savedImage.uri},
);
if (response) {
await refreshCache(
globalContactsInformation.myProfile.uuid,
response,
);
return true;
} else throw new Error('Unable to save image');
} else {
await deleteDatabaseImage(globalContactsInformation.myProfile.uuid);
await removeProfileImageFromCache(
globalContactsInformation.myProfile.uuid,
);
return true;
}
} catch (err) {
console.log(err);
navigate.navigate('ErrorScreen', {errorMessage: err.message});
return false;
} finally {
setIsAddingImage(false);
}
}
async function deleteProfilePicture() {
try {
if (isEditingMyProfile) {
setMyProfileImage('');
removeLocalStorageItem('myProfileImage');
const response = await uploadProfileImage({removeImage: true});
console.log(response);
if (!response) return;
toggleGlobalContactsInformation(
{
myProfile: {
...globalContactsInformation.myProfile,
hasProfileImage: false,
},
addedContacts: globalContactsInformation.addedContacts,
},
true,
);
return;
}
if (fromInitialAdd) {
@@ -30,6 +30,8 @@ import {useGlobalThemeContext} from '../../../../../context-store/theme';
import {useAppStatus} from '../../../../../context-store/appStatus';
import {useKeysContext} from '../../../../../context-store/keys';
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
import ContactProfileImage from './internalComponents/profileImage';
import {useContactImage} from '../../../../hooks/useContactImage';
export default function ExpandedContactsPage(props) {
const navigate = useNavigation();
@@ -37,7 +39,6 @@ export default function ExpandedContactsPage(props) {
const {isConnectedToTheInternet} = useAppStatus();
const {theme, darkModeType} = useGlobalThemeContext();
const {
textColor,
backgroundOffset,
backgroundColor,
textInputColor,
@@ -51,7 +52,6 @@ export default function ExpandedContactsPage(props) {
} = useGlobalContacts();
const insets = useSafeAreaInsets();
const currentTime = new Date();
const isInitialRender = useRef(true);
const selectedUUID = props?.route?.params?.uuid || props?.uuid;
const myProfile = globalContactsInformation?.myProfile;
@@ -60,7 +60,7 @@ export default function ExpandedContactsPage(props) {
decodedAddedContacts.filter(contact => contact?.uuid === selectedUUID),
[decodedAddedContacts, selectedUUID],
);
const imageData = useContactImage(selectedContact.uuid);
const contactTransactions = contactsMessags[selectedUUID]?.messages || []; //selectedContact?.transactions;
useHandleBackPressNew();
console.log(selectedContact);
@@ -104,22 +104,6 @@ export default function ExpandedContactsPage(props) {
lightsOutIcon={ICONS.arrow_small_left_white}
/>
</TouchableOpacity>
{!selectedContact?.isLNURL && selectedContact?.uniqueName && (
<TouchableOpacity
style={{marginRight: 5}}
onPress={() => {
Share.share({
title: 'Blitz Contact',
message: `https://blitz-wallet.com/u/${selectedContact?.uniqueName}`,
});
}}>
<ThemeImage
darkModeIcon={ICONS.share}
lightModeIcon={ICONS.share}
lightsOutIcon={ICONS.shareWhite}
/>
</TouchableOpacity>
)}
{selectedContact && (
<TouchableOpacity
style={{marginRight: 5}}
@@ -211,29 +195,52 @@ export default function ExpandedContactsPage(props) {
/>
) : (
<>
<View
style={[
styles.profileImage,
{
// borderColor: COLORS.darkModeText,
backgroundColor: backgroundOffset,
},
]}>
<Image
source={
selectedContact.profileImage
? {uri: selectedContact.profileImage}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
selectedContact.profileImage
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
}
/>
</View>
<TouchableOpacity
activeOpacity={
!selectedContact?.isLNURL && selectedContact?.uniqueName ? 0.2 : 1
}
onPress={() => {
if (selectedContact?.isLNURL || !selectedContact?.uniqueName)
return;
Share.share({
title: 'Blitz Contact',
message: `https://blitz-wallet.com/u/${selectedContact?.uniqueName}`,
});
}}
style={{...CENTER}}>
<View
style={[
styles.profileImage,
{
backgroundColor: backgroundOffset,
},
]}>
<ContactProfileImage
updated={
selectedContact.isLNURL
? new Date().toISOString()
: imageData?.updated
}
uri={
selectedContact.isLNURL
? selectedContact.profileImage
: imageData?.localUri
}
darkModeType={darkModeType}
theme={theme}
/>
</View>
{!selectedContact?.isLNURL && selectedContact?.uniqueName && (
<View style={styles.selectFromPhotos}>
<ThemeImage
styles={{width: 20, height: 20}}
darkModeIcon={ICONS.shareBlack}
lightModeIcon={ICONS.shareBlack}
lightsOutIcon={ICONS.shareBlack}
/>
</View>
)}
</TouchableOpacity>
<ThemeText
styles={styles.profileName}
content={selectedContact.name || selectedContact.uniqueName}
@@ -408,4 +415,16 @@ const styles = StyleSheet.create({
marginBottom: 'auto',
marginTop: 'auto',
},
selectFromPhotos: {
width: 30,
height: 30,
borderRadius: 20,
backgroundColor: COLORS.darkModeText,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
right: 12.5,
bottom: 12.5,
zIndex: 2,
},
});
@@ -0,0 +1,54 @@
import React, {useState, useEffect} from 'react';
import {ICONS} from '../../../../../constants';
import FastImage from 'react-native-fast-image';
import customUUID from '../../../../../functions/customUUID';
export default function ContactProfileImage({
priority = FastImage.priority.high,
resizeMode = FastImage.resizeMode.cover,
uri,
darkModeType,
theme,
setHasImage,
updated,
}) {
const [loadError, setLoadError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const fallbackIcon = darkModeType && theme ? ICONS.userWhite : ICONS.userIcon;
const customURI = `${uri}?v=${
updated ? new Date(updated).getTime() : customUUID()
}`;
return (
<FastImage
onLoad={() => {
setIsLoading(false);
if (setHasImage) {
console.log('On load has image', !!customURI);
setHasImage(!!uri);
}
}}
onError={() => {
setLoadError(true);
if (setHasImage) {
console.log('on error has image', !!customURI);
setHasImage(false);
}
}}
style={
!loadError && uri && !isLoading
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
}
source={
!loadError && uri && !isLoading
? {
uri: customURI,
priority: priority,
}
: fallbackIcon
}
resizeMode={resizeMode}
/>
);
}
@@ -7,10 +7,13 @@ import {useGlobalContacts} from '../../../../../../context-store/globalContacts'
import GetThemeColors from '../../../../../hooks/themeColors';
import {ThemeText} from '../../../../../functions/CustomElements';
import {useGlobalThemeContext} from '../../../../../../context-store/theme';
import ContactProfileImage from './profileImage';
import {useImageCache} from '../../../../../../context-store/imageCache';
export default function ProfilePageTransactions({transaction, currentTime}) {
const profileInfo = transaction;
const transactionData = transaction.transaction;
const {cache} = useImageCache();
const {theme, darkModeType} = useGlobalThemeContext();
const {textColor, backgroundOffset} = GetThemeColors();
@@ -47,6 +50,7 @@ export default function ProfilePageTransactions({transaction, currentTime}) {
timeDifferenceHours={timeDifferenceHours}
timeDifferenceDays={timeDifferenceDays}
profileInfo={profileInfo}
cache={cache}
/>
) : (
<View style={{...styles.transactionContainer}}>
@@ -55,21 +59,11 @@ export default function ProfilePageTransactions({transaction, currentTime}) {
...styles.selectImage,
backgroundColor: backgroundOffset,
}}>
<Image
source={
profileInfo.selectedProfileImage
? {
uri: profileInfo.selectedProfileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
profileInfo.selectedProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '60%', height: '60%'}
}
<ContactProfileImage
updated={cache[profileInfo.contactUUID]?.updated}
uri={cache[profileInfo.contactUUID]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
@@ -130,10 +124,10 @@ function ConfirmedOrSentTransaction({
timeDifferenceHours,
timeDifferenceDays,
profileInfo,
cache,
}) {
const {masterInfoObject} = useGlobalContextProvider();
const {theme, darkModeType} = useGlobalThemeContext();
const {myProfileImage} = useGlobalContacts();
const {textColor, backgroundOffset} = GetThemeColors();
const didDeclinePayment = txParsed.isRedeemed != null && !txParsed.isRedeemed;
@@ -166,21 +160,11 @@ function ConfirmedOrSentTransaction({
bottom: 0,
left: 0,
}}>
<Image
source={
myProfileImage
? {
uri: myProfileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
myProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '60%', height: '60%'}
}
<ContactProfileImage
updated={cache[masterInfoObject.uuid]?.updated}
uri={cache[masterInfoObject.uuid]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
<View
@@ -191,21 +175,11 @@ function ConfirmedOrSentTransaction({
top: 0,
right: 0,
}}>
<Image
source={
profileInfo.selectedProfileImage
? {
uri: profileInfo.selectedProfileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
profileInfo.selectedProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '60%', height: '60%'}
}
<ContactProfileImage
updated={cache[profileInfo.contactUUID]?.updated}
uri={cache[profileInfo.contactUUID]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
</>
@@ -221,21 +195,11 @@ function ConfirmedOrSentTransaction({
backgroundColor: backgroundOffset,
}}>
<Image
source={
profileInfo.selectedProfileImage
? {
uri: profileInfo.selectedProfileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
profileInfo.selectedProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '60%', height: '60%'}
}
<ContactProfileImage
updated={cache[profileInfo.contactUUID]?.updated}
uri={cache[profileInfo.contactUUID]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
)}
@@ -9,7 +9,7 @@ import {
} from 'react-native';
import {CENTER, COLORS, ICONS, SIZES} from '../../../../constants';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import {useCallback, useMemo, useState} from 'react';
import {useCallback, useEffect, useMemo, useState} from 'react';
import {GlobalThemeView, ThemeText} from '../../../../functions/CustomElements';
import {useGlobalContacts} from '../../../../../context-store/globalContacts';
import GetThemeColors from '../../../../hooks/themeColors';
@@ -21,21 +21,23 @@ import {useGlobalThemeContext} from '../../../../../context-store/theme';
import {useAppStatus} from '../../../../../context-store/appStatus';
import useHandleBackPressNew from '../../../../hooks/useHandleBackPressNew';
import MaxHeap from '../../../../functions/minHeap';
import ContactProfileImage from './internalComponents/profileImage';
import {useImageCache} from '../../../../../context-store/imageCache';
export default function MyContactProfilePage({navigation}) {
const {isConnectedToTheInternet} = useAppStatus();
const {cache} = useImageCache();
const {theme, darkModeType} = useGlobalThemeContext();
const {
globalContactsInformation,
myProfileImage,
decodedAddedContacts,
contactsMessags,
} = useGlobalContacts();
const {globalContactsInformation, decodedAddedContacts, contactsMessags} =
useGlobalContacts();
const {backgroundOffset, textInputBackground, textInputColor} =
GetThemeColors();
const navigate = useNavigation();
const currentTime = new Date();
const [showList, setShowList] = useState(false);
const myContact = globalContactsInformation.myProfile;
useFocusEffect(
useCallback(() => {
setShowList(true);
@@ -46,8 +48,6 @@ export default function MyContactProfilePage({navigation}) {
}, []),
);
const myContact = globalContactsInformation.myProfile;
const createdPayments = useMemo(() => {
const messageHeap = new MaxHeap();
const MAX_MESSAGES = 50;
@@ -151,21 +151,11 @@ export default function MyContactProfilePage({navigation}) {
backgroundColor: backgroundOffset,
},
]}>
<Image
source={
myProfileImage
? {
uri: myProfileImage,
}
: darkModeType && theme
? ICONS.userWhite
: ICONS.userIcon
}
style={
myProfileImage
? {width: '100%', aspectRatio: 1}
: {width: '50%', height: '50%'}
}
<ContactProfileImage
updated={cache[myContact.uuid]?.updated}
uri={cache[myContact.uuid]?.localUri}
darkModeType={darkModeType}
theme={theme}
/>
</View>
<View style={styles.scanProfileImage}>
@@ -243,6 +233,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 15,
},
innerContainer: {
+2
View File
@@ -148,6 +148,7 @@ import navigationIcon from '../assets/icons/navigation.png';
import navigationIconFill from '../assets/icons/navigation_fill.png';
import navigationIconWhite from '../assets/icons/navigation_white.png';
import navigationIconFillWhite from '../assets/icons/navigation_fill_white.png';
import shareBlack from '../assets/icons/shareBlack.png';
export default {
Xcircle,
XcircleLight,
@@ -300,4 +301,5 @@ export default {
navigationIconFill,
navigationIconWhite,
navigationIconFillWhite,
shareBlack,
};
+2 -1
View File
@@ -37,7 +37,7 @@ const QUICK_PAY_STORAGE_KEY = 'FAST_PAY_SETTINGS';
const LOGIN_SECUITY_MODE_KEY = 'LOGIN_SECURITY_MODE';
const MIGRATE_ECASH_STORAGE_KEY = 'MIGRATE_ECASH';
const POINT_OF_SALE_PAYOUT_DESCRIPTION = 'Blitz Tips Payout';
const BLITZ_PROFILE_IMG_STORAGE_REF = 'profile_pictures';
const BLITZ_DEFAULT_PAYMENT_DESCRIPTION = 'Blitz Wallet';
const CHATGPT_INPUT_COST = 10 / 1000000;
@@ -95,4 +95,5 @@ export {
LIQUID_NON_BITCOIN_DRAIN_LIMIT,
POINT_OF_SALE_PAYOUT_DESCRIPTION,
BLITZ_GOAL_USER_COUNT,
BLITZ_PROFILE_IMG_STORAGE_REF,
};
+10 -4
View File
@@ -1,9 +1,12 @@
import {StyleSheet, View} from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import {useGlobalContacts} from '../../../context-store/globalContacts';
import {CENTER, COLORS, ICONS} from '../../constants';
import GetThemeColors from '../../hooks/themeColors';
import {useGlobalContextProvider} from '../../../context-store/context';
import {useImageCache} from '../../../context-store/imageCache';
export default function QrCodeWrapper({
QRData = 'No data available',
outerContainerStyle,
@@ -13,8 +16,11 @@ export default function QrCodeWrapper({
logoMargin = 5,
logoBorderRadius = 50,
}) {
const {myProfileImage} = useGlobalContacts();
const {cache} = useImageCache();
const {masterInfoObject} = useGlobalContextProvider();
const {backgroundOffset} = GetThemeColors();
const image = cache[masterInfoObject.uuid]?.localUri;
return (
<View
style={{
@@ -29,8 +35,8 @@ export default function QrCodeWrapper({
value={QRData}
color={COLORS.lightModeText}
backgroundColor={COLORS.darkModeText}
logo={myProfileImage || ICONS.logoWithPadding}
logoSize={myProfileImage ? 70 : 50}
logo={!!image ? image : ICONS.logoWithPadding}
logoSize={!!image ? 70 : 50}
logoMargin={logoMargin}
logoBorderRadius={logoBorderRadius}
logoBackgroundColor={COLORS.darkModeText}
@@ -37,6 +37,8 @@ export default function CustomSearchInput({
<>
<View style={{...styles.inputContainer, ...containerStyles}}>
<TextInput
autoComplete="off"
autoCorrect={false}
keyboardAppearance={theme ? 'dark' : 'light'}
placeholder={placeholderText || ''}
placeholderTextColor={
+41
View File
@@ -0,0 +1,41 @@
import * as FileSystem from 'expo-file-system';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {BLITZ_PROFILE_IMG_STORAGE_REF} from '../constants';
import {getStorage} from '@react-native-firebase/storage';
const FILE_DIR = FileSystem.cacheDirectory + 'profileImages/';
const CACHE_KEY = uuid => `${BLITZ_PROFILE_IMG_STORAGE_REF}/${uuid}`;
export async function getCachedProfileImage(uuid) {
try {
const key = `${CACHE_KEY(uuid)}`;
const ref = getStorage().ref(
`${BLITZ_PROFILE_IMG_STORAGE_REF}/${uuid}.jpg`,
);
const metadata = await ref.getMetadata();
const updated = metadata.updated;
// Check for cached image info
const cacheEntry = await AsyncStorage.getItem(key);
const parsed = cacheEntry ? JSON.parse(cacheEntry) : null;
if (parsed?.updated === updated) {
const exists = await FileSystem.getInfoAsync(parsed.localUri);
if (exists.exists)
return {localUri: parsed.localUri, updated: parsed?.updated};
}
const url = await ref.getDownloadURL();
await FileSystem.makeDirectoryAsync(FILE_DIR, {intermediates: true});
const localUri = `${FILE_DIR}${uuid}.jpg`;
await FileSystem.downloadAsync(url, localUri);
const newEntry = {localUri, updated};
await AsyncStorage.setItem(key, JSON.stringify(newEntry));
return {localUri, updated};
} catch (e) {
console.error('Error caching profile image', e);
return null;
}
}
+32
View File
@@ -0,0 +1,32 @@
import {useEffect, useState} from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {BLITZ_PROFILE_IMG_STORAGE_REF} from '../constants';
export function useContactImage(uuid) {
const [uri, setUri] = useState({});
useEffect(() => {
async function load() {
const keys = await AsyncStorage.getAllKeys();
const imgKeys = keys.filter(k =>
k.startsWith(BLITZ_PROFILE_IMG_STORAGE_REF),
);
const stores = await AsyncStorage.multiGet(imgKeys);
const initialCache = {};
stores.forEach(([key, value]) => {
if (value) {
const uuid = key.replace(BLITZ_PROFILE_IMG_STORAGE_REF + '/', '');
const parsed = JSON.parse(value);
initialCache[uuid] = parsed;
}
});
console.log(initialCache, uuid);
if (initialCache[uuid]?.localUri) {
setUri(initialCache[uuid]);
}
}
load();
}, [uuid]);
return uri;
}
@@ -202,6 +202,9 @@ export default function RestoreWallet({navigation: {reset}, route: {params}}) {
]}>
<ThemeText styles={styles.numberText} content={`${item1}.`} />
<TextInput
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
keyboardAppearance={theme ? 'dark' : 'light'}
ref={ref => (keyRefs.current[item1] = ref)}
value={inputedKey[`key${item1}`]}
+140
View File
@@ -0,0 +1,140 @@
import React, {
createContext,
useContext,
useState,
useEffect,
useRef,
} from 'react';
import {getStorage} from '@react-native-firebase/storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as FileSystem from 'expo-file-system';
import {useGlobalContacts} from './globalContacts';
import {useAppStatus} from './appStatus';
const BLITZ_PROFILE_IMG_STORAGE_REF = 'profile_pictures';
const FILE_DIR = FileSystem.cacheDirectory + 'profile_images/';
const ImageCacheContext = createContext();
export function ImageCacheProvider({children}) {
const [cache, setCache] = useState({});
const {didGetToHomepage} = useAppStatus();
const {decodedAddedContacts} = useGlobalContacts();
const didRunContextCacheCheck = useRef(null);
console.log(cache, 'imgaes cache');
useEffect(() => {
(async () => {
try {
const keys = await AsyncStorage.getAllKeys();
const imgKeys = keys.filter(k =>
k.startsWith(BLITZ_PROFILE_IMG_STORAGE_REF),
);
const stores = await AsyncStorage.multiGet(imgKeys);
const initialCache = {};
stores.forEach(([key, value]) => {
if (value) {
const uuid = key.replace(BLITZ_PROFILE_IMG_STORAGE_REF + '/', '');
const parsed = JSON.parse(value);
initialCache[uuid] = parsed;
}
});
setCache(initialCache);
} catch (e) {
console.error('Error loading image cache from storage', e);
}
})();
}, []);
useEffect(() => {
if (!didGetToHomepage) return;
if (didRunContextCacheCheck.current) return;
didRunContextCacheCheck.current = true;
console.log(decodedAddedContacts, 'DECIN FUNC');
async function refreshContactsImages() {
for (let index = 0; index < decodedAddedContacts.length; index++) {
const element = decodedAddedContacts[index];
await refreshCache(element.uuid);
}
}
refreshContactsImages();
}, [decodedAddedContacts, didGetToHomepage]);
async function refreshCache(uuid, hasdownloadURL) {
try {
console.log('Refreshing image for', uuid);
const key = `${BLITZ_PROFILE_IMG_STORAGE_REF}/${uuid}`;
let url;
let metadata;
let updated;
if (!hasdownloadURL) {
const reference = getStorage().ref(
`${BLITZ_PROFILE_IMG_STORAGE_REF}/${uuid}.jpg`,
);
metadata = await reference.getMetadata();
updated = metadata.updated;
const cached = cache[uuid];
if (cached && cached.updated === updated) {
const fileInfo = await FileSystem.getInfoAsync(cached.localUri);
if (fileInfo.exists) return;
}
url = await reference.getDownloadURL();
} else {
url = hasdownloadURL;
updated = new Date().toISOString();
}
const localUri = `${FILE_DIR}${uuid}.jpg`;
await FileSystem.makeDirectoryAsync(FILE_DIR, {intermediates: true});
await FileSystem.downloadAsync(url, localUri);
const newCacheEntry = {
uri: localUri,
localUri,
updated,
};
await AsyncStorage.setItem(key, JSON.stringify(newCacheEntry));
setCache(prev => ({...prev, [uuid]: newCacheEntry}));
return newCacheEntry;
} catch (err) {
console.log('Error refreshing image cache', err);
}
}
async function removeProfileImageFromCache(uuid) {
try {
console.log('Deleting profile image', uuid);
const key = `${BLITZ_PROFILE_IMG_STORAGE_REF}/${uuid}`;
const newCacheEntry = {
uri: null,
localUri: null,
updated: new Date().getTime(),
};
await AsyncStorage.setItem(key, JSON.stringify(newCacheEntry));
setCache(prev => ({...prev, [uuid]: newCacheEntry}));
return newCacheEntry;
} catch (err) {
console.log('Error refreshing image cache', err);
}
}
return (
<ImageCacheContext.Provider
value={{cache, refreshCache, removeProfileImageFromCache}}>
{children}
</ImageCacheContext.Provider>
);
}
export function useImageCache() {
return useContext(ImageCacheContext);
}
+33
View File
@@ -0,0 +1,33 @@
import {getStorage} from '@react-native-firebase/storage';
import {BLITZ_PROFILE_IMG_STORAGE_REF} from '../app/constants';
export async function setDatabaseIMG(publicKey, imgURL) {
try {
const reference = getStorage().ref(
`${BLITZ_PROFILE_IMG_STORAGE_REF}/${publicKey}.jpg`,
);
await reference.putFile(imgURL.uri);
const downloadURL = await reference.getDownloadURL();
return downloadURL;
} catch (err) {
console.log('set database image error', err);
return false;
}
}
export async function deleteDatabaseImage(publicKey) {
try {
const reference = getStorage().ref(
`${BLITZ_PROFILE_IMG_STORAGE_REF}/${publicKey}.jpg`,
);
await reference.delete();
return true;
} catch (err) {
console.log('delete profime imgage error', err);
if (err.message.includes('No object exists at the desired reference')) {
return true;
}
return false;
}
}
+67
View File
@@ -1212,6 +1212,9 @@ PODS:
- ExpoModulesCore
- EXConstants (17.0.8):
- ExpoModulesCore
- EXImageLoader (5.0.0):
- ExpoModulesCore
- React-Core
- EXNotifications (0.29.14):
- ExpoModulesCore
- Expo (52.0.42):
@@ -1226,6 +1229,10 @@ PODS:
- ExpoModulesCore
- ExpoFont (13.0.4):
- ExpoModulesCore
- ExpoImageManipulator (13.0.6):
- EXImageLoader
- ExpoModulesCore
- SDWebImageWebPCoder
- ExpoKeepAwake (14.0.3):
- ExpoModulesCore
- ExpoLocalAuthentication (15.0.2):
@@ -1284,6 +1291,9 @@ PODS:
- Firebase/Messaging (11.10.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.10.0)
- Firebase/Storage (11.10.0):
- Firebase/CoreOnly
- FirebaseStorage (~> 11.10.0)
- FirebaseAppCheckInterop (11.11.0)
- FirebaseAuth (11.10.0):
- FirebaseAppCheckInterop (~> 11.0)
@@ -1366,6 +1376,13 @@ PODS:
- nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (11.11.0)
- FirebaseStorage (11.10.0):
- FirebaseAppCheckInterop (~> 11.0)
- FirebaseAuthInterop (~> 11.0)
- FirebaseCore (~> 11.10.0)
- FirebaseCoreExtension (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GTMSessionFetcher/Core (< 5.0, >= 3.4)
- fmt (11.0.2)
- glog (0.3.5)
- GoogleDataTransport (10.1.0):
@@ -1493,6 +1510,18 @@ PODS:
- hermes-engine/Pre-built (0.77.2)
- KeychainAccess (4.2.2)
- leveldb-library (1.22.6)
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
- libwebp/sharpyuv (= 1.5.0)
- libwebp/webp (= 1.5.0)
- libwebp/demux (1.5.0):
- libwebp/webp
- libwebp/mux (1.5.0):
- libwebp/demux
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- lottie-ios (4.5.0)
- lottie-react-native (7.1.0):
- DoubleConversion
@@ -3219,6 +3248,10 @@ PODS:
- Yoga
- RNDeviceInfo (14.0.4):
- React-Core
- RNFastImage (8.6.3):
- React-Core
- SDWebImage (~> 5.11.1)
- SDWebImageWebPCoder (~> 0.8.4)
- RNFBApp (21.13.0):
- Firebase/CoreOnly (= 11.10.0)
- React-Core
@@ -3244,6 +3277,10 @@ PODS:
- FirebaseCoreExtension
- React-Core
- RNFBApp
- RNFBStorage (21.13.0):
- Firebase/Storage (= 11.10.0)
- React-Core
- RNFBApp
- RNGestureHandler (2.25.0):
- DoubleConversion
- glog
@@ -3476,6 +3513,12 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- SDWebImage (5.11.1):
- SDWebImage/Core (= 5.11.1)
- SDWebImage/Core (5.11.1)
- SDWebImageWebPCoder (0.8.5):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10)
- SocketRocket (0.7.1)
- UMAppLoader (5.0.1)
- VisionCamera (4.6.4):
@@ -3497,6 +3540,7 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EXApplication (from `../node_modules/expo-application/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
- EXNotifications (from `../node_modules/expo-notifications/ios`)
- Expo (from `../node_modules/expo`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
@@ -3504,6 +3548,7 @@ DEPENDENCIES:
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`)
- ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
@@ -3590,12 +3635,14 @@ DEPENDENCIES:
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFastImage (from `../node_modules/react-native-fast-image`)
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBAuth (from `../node_modules/@react-native-firebase/auth`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- "RNFBFirestore (from `../node_modules/@react-native-firebase/firestore`)"
- "RNFBFunctions (from `../node_modules/@react-native-firebase/functions`)"
- "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)"
- "RNFBStorage (from `../node_modules/@react-native-firebase/storage`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNQrGenerator (from `../node_modules/rn-qr-generator`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
@@ -3630,6 +3677,7 @@ SPEC REPOS:
- FirebaseRemoteConfigInterop
- FirebaseSessions
- FirebaseSharedSwift
- FirebaseStorage
- GoogleDataTransport
- GoogleUtilities
- "gRPC-C++"
@@ -3637,12 +3685,15 @@ SPEC REPOS:
- GTMSessionFetcher
- KeychainAccess
- leveldb-library
- libwebp
- lottie-ios
- nanopb
- OpenSSL-Universal
- PromisesObjC
- PromisesSwift
- RecaptchaInterop
- SDWebImage
- SDWebImageWebPCoder
- SocketRocket
- ZXingObjC
@@ -3659,6 +3710,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-application/ios"
EXConstants:
:path: "../node_modules/expo-constants/ios"
EXImageLoader:
:path: "../node_modules/expo-image-loader/ios"
EXNotifications:
:path: "../node_modules/expo-notifications/ios"
Expo:
@@ -3673,6 +3726,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-file-system/ios"
ExpoFont:
:path: "../node_modules/expo-font/ios"
ExpoImageManipulator:
:path: "../node_modules/expo-image-manipulator/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
ExpoLocalAuthentication:
@@ -3840,6 +3895,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNDeviceInfo:
:path: "../node_modules/react-native-device-info"
RNFastImage:
:path: "../node_modules/react-native-fast-image"
RNFBApp:
:path: "../node_modules/@react-native-firebase/app"
RNFBAuth:
@@ -3852,6 +3909,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/functions"
RNFBMessaging:
:path: "../node_modules/@react-native-firebase/messaging"
RNFBStorage:
:path: "../node_modules/@react-native-firebase/storage"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNQrGenerator:
@@ -3882,6 +3941,7 @@ SPEC CHECKSUMS:
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
EXApplication: 4c72f6017a14a65e338c5e74fca418f35141e819
EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b
EXImageLoader: e5da974e25b13585c196b658a440720c075482d5
EXNotifications: 9d3d17e52c95f377750d4a2ae553a716313dd4aa
Expo: e8f11c8e0290deca7be9254569e23f884b95a777
ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516
@@ -3889,6 +3949,7 @@ SPEC CHECKSUMS:
ExpoCrypto: e97e864c8d7b9ce4a000bca45dddb93544a1b2b4
ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655
ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188
ExpoImageManipulator: 4aca9cc1d84e31f62a7e8075c19026d3ad62f852
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
ExpoLocalAuthentication: eb2be9c7bcdc68e9434d4be4bb5aa5cf7943e816
ExpoModulesCore: bcee92d3a2c68c408b2d8da43e3094109340dc17
@@ -3916,6 +3977,7 @@ SPEC CHECKSUMS:
FirebaseRemoteConfigInterop: 85bdce8babed7814816496bb6f082bc05b0a45e1
FirebaseSessions: 9b3b30947b97a15370e0902ee7a90f50ef60ead6
FirebaseSharedSwift: b1d32c3b29a911dc174bcf363f2f70bda9509d2f
FirebaseStorage: e83d1b9c8a5318d46ccfb2955f0d98095e0bf598
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
@@ -3926,6 +3988,7 @@ SPEC CHECKSUMS:
hermes-engine: 8eb265241fa1d7095d3a40d51fd90f7dce68217c
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: 34cff61bca6246919b9ee8d443ce5b869a7f05ac
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
@@ -4002,17 +4065,21 @@ SPEC CHECKSUMS:
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
RNCAsyncStorage: 923af351d21a33b6c57ed32ac9c25317ddbb0dd4
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87
RNFBApp: 40dddac677bdc020c7669ffc52ed849930b8c694
RNFBAuth: 1af20a72de76b03656c15b792e93c503dd23e5fb
RNFBCrashlytics: 468a67446894a5234a0fd47b99b3263298546729
RNFBFirestore: 8ab4210b7ed0ce05b92c182071a5d191a7ae957b
RNFBFunctions: 4675adfe6256ac50c9f6808263ad4935a1de5711
RNFBMessaging: f0760e86d85a314e39281301b4ea3e337ef12d96
RNFBStorage: 82226bb12b3d77cc37506d23b5457aeccc79619e
RNGestureHandler: 96fc2de36a25cf6bece93e030c2ed9a5e95d33dd
RNQrGenerator: afacf12b55dfba0e3aaca963eec23691e8426431
RNReanimated: b9822df9ff2671c696b1612aa88f27fab168f1fe
RNScreens: 04304337930029a5d0db297b22eb0f07adc31a2c
RNSVG: ca46052a4a0216157b3155e571f82d3b2d875452
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
UMAppLoader: 7e7e0eaa7854ffd652c00a68c443afb28c3bedba
VisionCamera: f56eaedde0d3fa095143b78374d29e89e71735f9
+3
View File
@@ -32,6 +32,7 @@
"@react-native-firebase/firestore": "^21.13.0",
"@react-native-firebase/functions": "^21.13.0",
"@react-native-firebase/messaging": "^21.13.0",
"@react-native-firebase/storage": "21.13.0",
"@react-navigation/bottom-tabs": "^7.3.5",
"@react-navigation/drawer": "^7.3.4",
"@react-navigation/native": "^7.1.6",
@@ -47,6 +48,7 @@
"expo-clipboard": "~7.0.1",
"expo-crypto": "~14.0.2",
"expo-file-system": "~18.0.12",
"expo-image-manipulator": "~13.0.6",
"expo-local-authentication": "~15.0.2",
"expo-network": "~7.0.5",
"expo-notifications": "~0.29.14",
@@ -68,6 +70,7 @@
"react-native-country-picker-modal": "^2.0.0",
"react-native-device-info": "^14.0.4",
"react-native-email-link": "^1.16.1",
"react-native-fast-image": "^8.6.3",
"react-native-gesture-handler": "^2.25.0",
"react-native-get-random-values": "^1.11.0",
"react-native-image-picker": "^8.2.0",
+42
View File
@@ -3610,6 +3610,15 @@ __metadata:
languageName: node
linkType: hard
"@react-native-firebase/storage@npm:21.13.0":
version: 21.13.0
resolution: "@react-native-firebase/storage@npm:21.13.0"
peerDependencies:
"@react-native-firebase/app": 21.13.0
checksum: a54b8e267e7e95ba6b746d320dc29ea9175049470e13eaa2804807fce3810dac989e5123bb70889a2b0b765c6ed0046af6ff8b8db5da9628f539ea064499c917
languageName: node
linkType: hard
"@react-native/assets-registry@npm:0.77.2":
version: 0.77.2
resolution: "@react-native/assets-registry@npm:0.77.2"
@@ -4683,6 +4692,7 @@ __metadata:
"@react-native-firebase/firestore": ^21.13.0
"@react-native-firebase/functions": ^21.13.0
"@react-native-firebase/messaging": ^21.13.0
"@react-native-firebase/storage": 21.13.0
"@react-native/babel-preset": 0.77.2
"@react-native/eslint-config": 0.77.2
"@react-native/metro-config": 0.77.2
@@ -4709,6 +4719,7 @@ __metadata:
expo-clipboard: ~7.0.1
expo-crypto: ~14.0.2
expo-file-system: ~18.0.12
expo-image-manipulator: ~13.0.6
expo-local-authentication: ~15.0.2
expo-network: ~7.0.5
expo-notifications: ~0.29.14
@@ -4733,6 +4744,7 @@ __metadata:
react-native-device-info: ^14.0.4
react-native-dotenv: ^3.4.11
react-native-email-link: ^1.16.1
react-native-fast-image: ^8.6.3
react-native-gesture-handler: ^2.25.0
react-native-get-random-values: ^1.11.0
react-native-image-picker: ^8.2.0
@@ -7829,6 +7841,26 @@ __metadata:
languageName: node
linkType: hard
"expo-image-loader@npm:~5.0.0":
version: 5.0.0
resolution: "expo-image-loader@npm:5.0.0"
peerDependencies:
expo: "*"
checksum: 7741b4b926124a1f85e51b0b8c8c9adc37fccf0654eaa0e715cb55cffc716bdc149c0421120f9bec69a69643d3328ec8562899b4aa37a0fdcecfb6fe3cf6f985
languageName: node
linkType: hard
"expo-image-manipulator@npm:~13.0.6":
version: 13.0.6
resolution: "expo-image-manipulator@npm:13.0.6"
dependencies:
expo-image-loader: ~5.0.0
peerDependencies:
expo: "*"
checksum: 5e2c9e0e7f1a57f5aa24429854956db97d38b83e90fba6d6f3c4801dec087222f4845332a3e974bdef93acf6b378a8dff45fd7ba9b47fcbc5291e368a1712742
languageName: node
linkType: hard
"expo-keep-awake@npm:~14.0.3":
version: 14.0.3
resolution: "expo-keep-awake@npm:14.0.3"
@@ -12590,6 +12622,16 @@ __metadata:
languageName: node
linkType: hard
"react-native-fast-image@npm:^8.6.3":
version: 8.6.3
resolution: "react-native-fast-image@npm:8.6.3"
peerDependencies:
react: ^17 || ^18
react-native: ">=0.60.0"
checksum: 29289cb6b2eae0983c8922b22e2d9de3be07322bb7991c5def19f95eadefaedb0e308ff0b38cc1d0444e8bd4fe94a7621a99a2d3d9298100bcb60b3144677234
languageName: node
linkType: hard
"react-native-gesture-handler@npm:^2.25.0":
version: 2.25.0
resolution: "react-native-gesture-handler@npm:2.25.0"