mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-25 00:42:57 -04:00
Add native context menu to credential list (#880)
This commit is contained in:
committed by
Leendert de Borst
parent
4a35a1a7d3
commit
fbc085439c
@@ -43,7 +43,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
const webApi = useWebApi();
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||
const serviceNameRef = useRef<ValidatedFormFieldRef>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [isSaveDisabled, setIsSaveDisabled] = useState(false);
|
||||
|
||||
const { control, handleSubmit, setValue, watch } = useForm<Credential>({
|
||||
@@ -122,6 +122,13 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
setValue('ServiceUrl', decodedUrl);
|
||||
setValue('ServiceName', serviceName);
|
||||
}
|
||||
|
||||
// On create mode, focus the service name field after a short delay to ensure the component is mounted
|
||||
if (!isEditMode) {
|
||||
setTimeout(() => {
|
||||
serviceNameRef.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}, [id, isEditMode, serviceUrl, loadExistingCredential, setValue, authContext.isOffline, router]);
|
||||
|
||||
/**
|
||||
@@ -217,7 +224,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
|
||||
Keyboard.dismiss();
|
||||
|
||||
setIsLoading(true);
|
||||
setIsSyncing(true);
|
||||
|
||||
// Assemble the credential to save
|
||||
const credentialToSave: Credential = {
|
||||
@@ -308,9 +315,9 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
});
|
||||
}, 200);
|
||||
|
||||
setIsLoading(false);
|
||||
setIsSyncing(false);
|
||||
}
|
||||
}, [isEditMode, id, serviceUrl, router, executeVaultMutation, dbContext.sqliteClient, mode, generateRandomAlias, webApi, watch, setIsSaveDisabled, setIsLoading, isSaveDisabled]);
|
||||
}, [isEditMode, id, serviceUrl, router, executeVaultMutation, dbContext.sqliteClient, mode, generateRandomAlias, webApi, watch, setIsSaveDisabled, setIsSyncing, isSaveDisabled]);
|
||||
|
||||
/**
|
||||
* Generate a random username.
|
||||
@@ -374,7 +381,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
* Delete the credential.
|
||||
*/
|
||||
onPress: async () : Promise<void> => {
|
||||
setIsLoading(true);
|
||||
setIsSyncing(true);
|
||||
|
||||
await executeVaultMutation(async () => {
|
||||
await dbContext.sqliteClient!.deleteCredentialById(id);
|
||||
@@ -389,7 +396,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
});
|
||||
}, 200);
|
||||
|
||||
setIsLoading(false);
|
||||
setIsSyncing(false);
|
||||
|
||||
/*
|
||||
* Navigate back to the root of the navigation stack.
|
||||
@@ -545,7 +552,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: isEditMode ? 'Edit Credential' : 'Add Credential' }} />
|
||||
{(isLoading) && (
|
||||
{(isSyncing) && (
|
||||
<LoadingOverlay status={syncStatus} />
|
||||
)}
|
||||
<KeyboardAvoidingView
|
||||
|
||||
@@ -24,6 +24,8 @@ import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
|
||||
import { useWebApi } from '@/context/WebApiContext';
|
||||
import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
import { ServiceUrlNotice } from '@/components/credentials/ServiceUrlNotice';
|
||||
import LoadingOverlay from '@/components/LoadingOverlay';
|
||||
import { useVaultMutate } from '@/hooks/useVaultMutate';
|
||||
|
||||
/**
|
||||
* Credentials screen.
|
||||
@@ -44,6 +46,8 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
const [refreshing, setRefreshing] = useMinDurationLoading(false, 200);
|
||||
const [serviceUrl, setServiceUrl] = useState<string | null>(null);
|
||||
const insets = useSafeAreaInsets();
|
||||
const { executeVaultMutation, isLoading, syncStatus } = useVaultMutate();
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
|
||||
const authContext = useAuth();
|
||||
const dbContext = useDb();
|
||||
@@ -222,8 +226,12 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
color: colors.textMuted,
|
||||
fontSize: 20,
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
contentContainer: {
|
||||
paddingBottom: Platform.OS === 'ios' ? insets.bottom + 60 : 10,
|
||||
paddingHorizontal: 14,
|
||||
paddingTop: Platform.OS === 'ios' ? 42 : 16,
|
||||
},
|
||||
emptyText: {
|
||||
@@ -270,6 +278,22 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
});
|
||||
}, [navigation, headerButtons]);
|
||||
|
||||
/**
|
||||
* Delete a credential.
|
||||
*/
|
||||
const onCredentialDelete = useCallback(async (credentialId: string) : Promise<void> => {
|
||||
setIsSyncing(true);
|
||||
|
||||
await executeVaultMutation(async () => {
|
||||
await dbContext.sqliteClient!.deleteCredentialById(credentialId);
|
||||
setIsSyncing(false);
|
||||
});
|
||||
|
||||
// Refresh list after deletion with a small delay to ensure feedback is visible.
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
await loadCredentials();
|
||||
}, [dbContext.sqliteClient, executeVaultMutation, loadCredentials]);
|
||||
|
||||
// Handle deep link parameters
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
@@ -280,7 +304,10 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemedContainer>
|
||||
<ThemedContainer style={styles.container}>
|
||||
{(isSyncing) && (
|
||||
<LoadingOverlay status={syncStatus} />
|
||||
)}
|
||||
<CollapsibleHeader
|
||||
title="Credentials"
|
||||
scrollY={scrollY}
|
||||
@@ -354,7 +381,7 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
isLoadingCredentials ? (
|
||||
<SkeletonLoader count={1} height={60} parts={2} />
|
||||
) : (
|
||||
<CredentialCard credential={item} />
|
||||
<CredentialCard credential={item} onCredentialDelete={onCredentialDelete} />
|
||||
)
|
||||
}
|
||||
ListEmptyComponent={
|
||||
@@ -366,6 +393,7 @@ export default function CredentialsScreen() : React.ReactNode {
|
||||
}
|
||||
/>
|
||||
</ThemedView>
|
||||
{isLoading && <LoadingOverlay status={syncStatus || 'Deleting credential...'} />}
|
||||
</ThemedContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { StyleSheet, View, Text, TouchableOpacity, Keyboard } from 'react-native';
|
||||
import { StyleSheet, View, Text, TouchableOpacity, Keyboard, Platform, Alert } from 'react-native';
|
||||
import { router } from 'expo-router';
|
||||
import ContextMenu, { OnPressMenuItemEvent } from 'react-native-context-menu-view';
|
||||
import * as Clipboard from 'expo-clipboard';
|
||||
import Toast from 'react-native-toast-message';
|
||||
|
||||
import { CredentialIcon } from '@/components/credentials/CredentialIcon';
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
@@ -7,12 +10,13 @@ import { Credential } from '@/utils/types/Credential';
|
||||
|
||||
type CredentialCardProps = {
|
||||
credential: Credential;
|
||||
onCredentialDelete?: (credentialId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Credential card component.
|
||||
*/
|
||||
export function CredentialCard({ credential }: CredentialCardProps) : React.ReactNode {
|
||||
export function CredentialCard({ credential, onCredentialDelete }: CredentialCardProps) : React.ReactNode {
|
||||
const colors = useColors();
|
||||
|
||||
/**
|
||||
@@ -50,6 +54,139 @@ export function CredentialCard({ credential }: CredentialCardProps) : React.Reac
|
||||
return returnValue.length > 33 ? returnValue.slice(0, 30) + '...' : returnValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the context menu action when an item is selected.
|
||||
* @param event - The event object containing the selected action details
|
||||
*/
|
||||
const handleContextMenuAction = (event: OnPressMenuItemEvent): void => {
|
||||
const { name } = event.nativeEvent;
|
||||
|
||||
switch (name) {
|
||||
case 'Edit':
|
||||
Keyboard.dismiss();
|
||||
router.push({
|
||||
pathname: '/(tabs)/credentials/add-edit',
|
||||
params: { id: credential.Id }
|
||||
});
|
||||
break;
|
||||
case 'Delete':
|
||||
Keyboard.dismiss();
|
||||
Alert.alert(
|
||||
"Delete Credential",
|
||||
"Are you sure you want to delete this credential? This action cannot be undone.",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel"
|
||||
},
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
/**
|
||||
* Handles the delete credential action.
|
||||
*/
|
||||
onPress: async () : Promise<void> => {
|
||||
if (onCredentialDelete) {
|
||||
await onCredentialDelete(credential.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
break;
|
||||
case 'Copy Username':
|
||||
if (credential.Username) {
|
||||
Clipboard.setStringAsync(credential.Username);
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: 'Username copied to clipboard',
|
||||
position: 'bottom',
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'Copy Email':
|
||||
if (credential.Alias?.Email) {
|
||||
Clipboard.setStringAsync(credential.Alias.Email);
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: 'Email copied to clipboard',
|
||||
position: 'bottom',
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'Copy Password':
|
||||
if (credential.Password) {
|
||||
Clipboard.setStringAsync(credential.Password);
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: 'Password copied to clipboard',
|
||||
position: 'bottom',
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the menu actions for the context menu based on available credential data.
|
||||
* @returns Array of menu action objects with title and icon
|
||||
*/
|
||||
const getMenuActions = (): {
|
||||
title: string;
|
||||
systemIcon: string;
|
||||
destructive?: boolean;
|
||||
}[] => {
|
||||
const actions = [
|
||||
{
|
||||
title: 'Edit',
|
||||
systemIcon: Platform.select({
|
||||
ios: 'pencil',
|
||||
android: 'baseline_edit',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'Delete',
|
||||
systemIcon: Platform.select({
|
||||
ios: 'trash',
|
||||
android: 'baseline_delete',
|
||||
}),
|
||||
destructive: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (credential.Username) {
|
||||
actions.push({
|
||||
title: 'Copy Username',
|
||||
systemIcon: Platform.select({
|
||||
ios: 'person',
|
||||
android: 'baseline_person',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (credential.Alias?.Email) {
|
||||
actions.push({
|
||||
title: 'Copy Email',
|
||||
systemIcon: Platform.select({
|
||||
ios: 'envelope',
|
||||
android: 'baseline_email',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (credential.Password) {
|
||||
actions.push({
|
||||
title: 'Copy Password',
|
||||
systemIcon: Platform.select({
|
||||
ios: 'key',
|
||||
android: 'baseline_key',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
credentialCard: {
|
||||
backgroundColor: colors.accentBackground,
|
||||
@@ -83,25 +220,31 @@ export function CredentialCard({ credential }: CredentialCardProps) : React.Reac
|
||||
});
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.credentialCard}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss();
|
||||
router.push(`/(tabs)/credentials/${credential.Id}`);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
<ContextMenu
|
||||
title="Credential Options"
|
||||
actions={getMenuActions()}
|
||||
onPress={handleContextMenuAction}
|
||||
>
|
||||
<View style={styles.credentialContent}>
|
||||
<CredentialIcon logo={credential.Logo} style={styles.logo} />
|
||||
<View style={styles.credentialInfo}>
|
||||
<Text style={styles.serviceName}>
|
||||
{getCredentialServiceName(credential)}
|
||||
</Text>
|
||||
<Text style={styles.credentialText}>
|
||||
{getCredentialDisplayText(credential)}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.credentialCard}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss();
|
||||
router.push(`/(tabs)/credentials/${credential.Id}`);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.credentialContent}>
|
||||
<CredentialIcon logo={credential.Logo} style={styles.logo} />
|
||||
<View style={styles.credentialInfo}>
|
||||
<Text style={styles.serviceName}>
|
||||
{getCredentialServiceName(credential)}
|
||||
</Text>
|
||||
<Text style={styles.credentialText}>
|
||||
{getCredentialDisplayText(credential)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@@ -196,11 +196,62 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = "<group>"; };
|
||||
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = "<group>"; };
|
||||
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = "<group>"; };
|
||||
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = "<group>"; };
|
||||
CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = "<group>"; };
|
||||
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultStoreKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultStoreKitTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE909812DA548C7008D568F /* Autofill */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = Autofill;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -1157,7 +1208,10 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
@@ -1211,7 +1265,10 @@
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
|
||||
@@ -1565,6 +1565,8 @@ PODS:
|
||||
- Yoga
|
||||
- react-native-aes-gcm-crypto (0.2.2):
|
||||
- React-Core
|
||||
- react-native-context-menu-view (1.19.0):
|
||||
- React
|
||||
- react-native-get-random-values (1.11.0):
|
||||
- React-Core
|
||||
- react-native-quick-crypto (0.7.13):
|
||||
@@ -2277,6 +2279,7 @@ DEPENDENCIES:
|
||||
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
|
||||
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
|
||||
- react-native-aes-gcm-crypto (from `../node_modules/react-native-aes-gcm-crypto`)
|
||||
- react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`)
|
||||
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
|
||||
- react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
@@ -2458,6 +2461,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
|
||||
react-native-aes-gcm-crypto:
|
||||
:path: "../node_modules/react-native-aes-gcm-crypto"
|
||||
react-native-context-menu-view:
|
||||
:path: "../node_modules/react-native-context-menu-view"
|
||||
react-native-get-random-values:
|
||||
:path: "../node_modules/react-native-get-random-values"
|
||||
react-native-quick-crypto:
|
||||
@@ -2603,6 +2608,7 @@ SPEC CHECKSUMS:
|
||||
React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de
|
||||
React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead
|
||||
react-native-aes-gcm-crypto: d572dd7a69f31c539bb8309b3a829bfa3bfad244
|
||||
react-native-context-menu-view: 3a8fb510448efa9d477f645dafa889ef1c78daaa
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-quick-crypto: 361ecda861e23138ce68fea4b820cbc058688fb5
|
||||
react-native-safe-area-context: cd916088cac5300c3266876218377518987b995e
|
||||
|
||||
11
apps/mobile-app/package-lock.json
generated
11
apps/mobile-app/package-lock.json
generated
@@ -40,6 +40,7 @@
|
||||
"react-native": "0.76.9",
|
||||
"react-native-aes-gcm-crypto": "^0.2.2",
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-context-menu-view": "^1.19.0",
|
||||
"react-native-edge-to-edge": "^1.6.0",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
@@ -17849,6 +17850,16 @@
|
||||
"integrity": "sha512-/iOi0S+VVgS1gQGtQgL4ZxUVS4gz6Lav3bgIbtNmr9KbOunnBYzP6/yBe/XxkbpXvasHDwdQnuppOH/nuOBn7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-context-menu-view": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-context-menu-view/-/react-native-context-menu-view-1.19.0.tgz",
|
||||
"integrity": "sha512-RKkDUQuY9cVIb3rJl0ch8+FLH/WnjN6febtOuSif/6F3q7vuMZ8Ie+jmYGtnIbvSxl6lMknAZU61O192ysW3zg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-native": ">=0.60.0-rc.0 <1.0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-edge-to-edge": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"react-native": "0.76.9",
|
||||
"react-native-aes-gcm-crypto": "^0.2.2",
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-context-menu-view": "^1.19.0",
|
||||
"react-native-edge-to-edge": "^1.6.0",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
|
||||
Reference in New Issue
Block a user