feat(mobile): in-app document viewer (#667)

* Keep the phone logged in

* Added document sheet

* Make button outline

* Fix colors

* Design accroding to base design

* Design accroding to base design

* Added download and share

* Added view document screen

* Added view document screen

* Screen is launching

* fix toolbar issue

* Fix the button

* fix all the conflicts

* revert unncessary bloat

* remove packages

* pnpnm i

* Fixed some issues

* Added error state

* added header in loading and error state

* Removed duplication  in render

* added correct name of app

* Update apps/mobile/app.json

Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>

---------

Co-authored-by: jibraniqbal666 <jibran.iqbal@protonmail.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
This commit is contained in:
Jibran Iqbal
2025-12-12 17:27:36 +01:00
committed by GitHub
parent cf91515cfe
commit 9d9be949b0
9 changed files with 367 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
{ {
"expo": { "expo": {
"name": "mobile", "name": "Papra",
"slug": "mobile", "slug": "papra",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./src/assets/images/icon.png", "icon": "./src/assets/images/icon.png",
@@ -19,7 +19,8 @@
"monochromeImage": "./src/assets/images/android-icon-monochrome.png" "monochromeImage": "./src/assets/images/android-icon-monochrome.png"
}, },
"edgeToEdgeEnabled": true, "edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false "predictiveBackGestureEnabled": false,
"package": "app.papra.android"
}, },
"web": { "web": {
"output": "static", "output": "static",

View File

@@ -11,6 +11,14 @@ export default function RootLayout() {
<Stack.Screen name="auth/login" options={{ headerShown: false }} /> <Stack.Screen name="auth/login" options={{ headerShown: false }} />
<Stack.Screen name="auth/signup" options={{ headerShown: false }} /> <Stack.Screen name="auth/signup" options={{ headerShown: false }} />
<Stack.Screen name="(with-organizations)" options={{ headerShown: false }} /> <Stack.Screen name="(with-organizations)" options={{ headerShown: false }} />
<Stack.Screen
name="document/view"
options={{
headerShown: false,
presentation: 'modal',
animation: 'slide_from_bottom',
}}
/>
</Stack> </Stack>
<StatusBar style="auto" /> <StatusBar style="auto" />
</ApiProvider> </ApiProvider>

View File

@@ -0,0 +1,3 @@
import DocumentViewScreen from '@/modules/documents-actions/screens/document-view.screen';
export default DocumentViewScreen;

View File

@@ -7,8 +7,8 @@
"scripts": { "scripts": {
"dev": "pnpm start", "dev": "pnpm start",
"start": "expo start", "start": "expo start",
"android": "expo start --android", "android": "expo run:android",
"ios": "expo start --ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
@@ -48,6 +48,7 @@
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-native": "0.81.5", "react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0", "react-native-gesture-handler": "~2.28.0",
"react-native-pdf": "^7.0.3",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0", "react-native-screens": "~4.16.0",

View File

@@ -2,6 +2,7 @@ import type { CoerceDates } from '@/modules/api/api.models';
import type { Document } from '@/modules/documents/documents.types'; import type { Document } from '@/modules/documents/documents.types';
import type { ThemeColors } from '@/modules/ui/theme.constants'; import type { ThemeColors } from '@/modules/ui/theme.constants';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { router } from 'expo-router';
import * as Sharing from 'expo-sharing'; import * as Sharing from 'expo-sharing';
import { import {
Modal, Modal,
@@ -21,14 +22,12 @@ type DocumentActionSheetProps = {
visible: boolean; visible: boolean;
document: CoerceDates<Document> | undefined; document: CoerceDates<Document> | undefined;
onClose: () => void; onClose: () => void;
onView: () => void;
}; };
export function DocumentActionSheet({ export function DocumentActionSheet({
visible, visible,
document, document,
onClose, onClose,
onView,
}: DocumentActionSheetProps) { }: DocumentActionSheetProps) {
const themeColors = useThemeColor(); const themeColors = useThemeColor();
const styles = createStyles({ themeColors }); const styles = createStyles({ themeColors });
@@ -64,6 +63,17 @@ export function DocumentActionSheet({
}); });
}; };
const handleView = async () => {
onClose();
router.push({
pathname: '/(app)/document/view',
params: {
documentId: document.id,
organizationId: document.organizationId,
},
});
};
const handleDownloadAndShare = async () => { const handleDownloadAndShare = async () => {
const baseUrl = await configLocalStorage.getApiServerBaseUrl(); const baseUrl = await configLocalStorage.getApiServerBaseUrl();
@@ -166,9 +176,9 @@ export function DocumentActionSheet({
{isViewable && ( {isViewable && (
<TouchableOpacity <TouchableOpacity
style={styles.actionButton} style={styles.actionButton}
onPress={() => { onPress={async () => {
onClose(); onClose();
onView(); await handleView();
}} }}
activeOpacity={0.7} activeOpacity={0.7}
> >

View File

@@ -0,0 +1,257 @@
import type { CoerceDates } from '@/modules/api/api.models';
import type { Document } from '@/modules/documents/documents.types';
import type { ThemeColors } from '@/modules/ui/theme.constants';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useQuery } from '@tanstack/react-query';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React from 'react';
import {
ActivityIndicator,
Image,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Pdf from 'react-native-pdf';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useApiClient, useAuthClient } from '@/modules/api/providers/api.provider';
import { configLocalStorage } from '@/modules/config/config.local-storage';
import { fetchDocument, fetchDocumentFile } from '@/modules/documents/documents.services';
import { useAlert } from '@/modules/ui/providers/alert-provider';
import { useThemeColor } from '@/modules/ui/providers/use-theme-color';
type DocumentFile = {
uri: string;
doc: CoerceDates<Document>;
};
export default function DocumentViewScreen() {
const router = useRouter();
const params = useLocalSearchParams<{ documentId: string; organizationId: string }>();
const themeColors = useThemeColor();
const styles = createStyles({ themeColors });
const { showAlert } = useAlert();
const apiClient = useApiClient();
const authClient = useAuthClient();
const { documentId, organizationId } = params;
const documentQuery = useQuery({
queryKey: ['organizations', organizationId, 'documents', documentId],
queryFn: async () => {
if (organizationId == null || documentId == null) {
throw new Error('Organization ID and Document ID are required');
}
return fetchDocument({ organizationId, documentId, apiClient });
},
enabled: organizationId != null && documentId != null,
});
const documentFileQuery = useQuery({
queryKey: ['organizations', organizationId, 'documents', documentId, 'file'],
queryFn: async () => {
if (documentQuery.data == null) {
throw new Error('Document not loaded');
}
const baseUrl = await configLocalStorage.getApiServerBaseUrl();
if (baseUrl == null) {
throw new Error('Base URL not found');
}
const fileUri = await fetchDocumentFile({
document: documentQuery.data.document,
organizationId,
baseUrl,
authClient,
});
return {
uri: fileUri,
doc: documentQuery.data.document,
} as DocumentFile;
},
enabled: documentQuery.isSuccess && documentQuery.data != null,
});
const renderHeader = (documentName: string) => {
return (
<View style={styles.header}>
<TouchableOpacity
style={styles.backButton}
onPress={() => router.back()}
>
<MaterialCommunityIcons
name="close"
size={24}
color={themeColors.foreground}
/>
</TouchableOpacity>
<Text style={styles.headerTitle} numberOfLines={1}>
{documentName}
</Text>
<View style={styles.headerSpacer} />
</View>
);
};
const renderDocumentFile = (file: DocumentFile) => {
if (file.doc.mimeType.startsWith('image/')) {
return (
<Image
source={{ uri: file.uri }}
style={styles.pdfViewer}
/>
);
}
if (file.doc.mimeType.startsWith('application/pdf')) {
return (
<Pdf
source={{ uri: file.uri, cache: true }}
style={styles.pdfViewer}
onError={(error) => {
console.error('PDF error:', error);
showAlert({
title: 'Error',
message: 'Failed to load PDF',
});
}}
enablePaging={true}
horizontal={false}
enableAnnotationRendering={true}
fitPolicy={0}
spacing={10}
/>
);
}
return <View style={styles.pdfViewer} />;
};
const isLoading = documentQuery.isLoading || documentFileQuery.isLoading;
const error = documentQuery.error ?? documentFileQuery.error;
const documentFile = documentFileQuery.data;
const documentName = documentFile?.doc.name ?? 'Document';
return (
<SafeAreaView style={styles.container}>
{renderHeader(documentName)}
{isLoading
? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={themeColors.primary} />
<Text style={styles.loadingText}>Loading document...</Text>
</View>
)
: error != null
? (
<View style={styles.errorContainer}>
<MaterialCommunityIcons
name="file-pdf-box"
size={64}
color={themeColors.mutedForeground}
/>
<Text style={styles.errorText}>Failed to load document</Text>
<TouchableOpacity
style={styles.errorButton}
onPress={() => {
void documentQuery.refetch();
}}
>
<Text style={styles.errorButtonText}>Retry</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.errorButton}
onPress={() => router.back()}
>
<Text style={styles.errorButtonText}>Go Back</Text>
</TouchableOpacity>
</View>
)
: documentFile != null
? (
<View style={styles.pdfContainer}>
{renderDocumentFile(documentFile)}
</View>
)
: null}
</SafeAreaView>
);
}
function createStyles({ themeColors }: { themeColors: ThemeColors }) {
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: themeColors.background,
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 24,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: themeColors.border,
},
backButton: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: themeColors.secondaryBackground,
justifyContent: 'center',
alignItems: 'flex-start',
},
headerTitle: {
flex: 1,
fontSize: 18,
fontWeight: 'bold',
color: themeColors.foreground,
marginHorizontal: 16,
},
headerSpacer: {
width: 40,
},
pdfContainer: {
flex: 1,
backgroundColor: themeColors.background,
},
pdfViewer: {
flex: 1,
width: '100%',
height: '100%',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: themeColors.mutedForeground,
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 32,
},
errorText: {
fontSize: 18,
color: themeColors.foreground,
marginTop: 16,
marginBottom: 24,
},
errorButton: {
paddingHorizontal: 24,
paddingVertical: 16,
backgroundColor: themeColors.secondaryBackground,
borderRadius: 12,
marginTop: 16,
},
errorButtonText: {
fontSize: 16,
fontWeight: '600',
color: themeColors.primary,
},
});
}

View File

@@ -76,6 +76,24 @@ export async function fetchOrganizationDocuments({
} }
} }
export async function fetchDocument({
organizationId,
documentId,
apiClient,
}: {
organizationId: string;
documentId: string;
apiClient: ApiClient;
}) {
const { document } = await apiClient<{ document: Document }>({
method: 'GET',
path: `/api/organizations/${organizationId}/documents/${documentId}`,
});
return {
document: coerceDates(document),
};
}
export async function fetchDocumentFile({ export async function fetchDocumentFile({
document, document,
organizationId, organizationId,

View File

@@ -85,7 +85,6 @@ export function DocumentsListScreen() {
visible={true} visible={true}
document={onDocumentActionSheet} document={onDocumentActionSheet}
onClose={() => setOnDocumentActionSheet(undefined)} onClose={() => setOnDocumentActionSheet(undefined)}
onView={() => {}}
/> />
)} )}
<View style={styles.header}> <View style={styles.header}>

84
pnpm-lock.yaml generated
View File

@@ -225,6 +225,9 @@ importers:
react-native-gesture-handler: react-native-gesture-handler:
specifier: ~2.28.0 specifier: ~2.28.0
version: 2.28.0(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) version: 2.28.0(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-pdf:
specifier: ^7.0.3
version: 7.0.3(react-native-blob-util@0.23.2(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-reanimated: react-native-reanimated:
specifier: ~4.1.1 specifier: ~4.1.1
version: 4.1.5(@babel/core@7.28.4)(react-native-worklets@0.5.1(@babel/core@7.28.4)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) version: 4.1.5(@babel/core@7.28.4)(react-native-worklets@0.5.1(@babel/core@7.28.4)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -4046,6 +4049,9 @@ packages:
resolution: {integrity: sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==} resolution: {integrity: sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==}
engines: {node: '>= 20.19.4'} engines: {node: '>= 20.19.4'}
'@react-native/normalize-color@2.1.0':
resolution: {integrity: sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==}
'@react-native/normalize-colors@0.74.89': '@react-native/normalize-colors@0.74.89':
resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==} resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==}
@@ -5874,6 +5880,9 @@ packages:
bare-events: bare-events:
optional: true optional: true
base-64@0.1.0:
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
base-64@1.0.0: base-64@1.0.0:
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
@@ -6403,6 +6412,9 @@ packages:
crypt@0.0.2: crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
crypto-random-string@2.0.0: crypto-random-string@2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -6560,6 +6572,9 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
deprecated-react-native-prop-types@2.3.0:
resolution: {integrity: sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==}
dequal@2.0.3: dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -7824,9 +7839,6 @@ packages:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
get-tsconfig@4.10.0:
resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
get-tsconfig@4.10.1: get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
@@ -10091,6 +10103,12 @@ packages:
react-is@19.2.0: react-is@19.2.0:
resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==} resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==}
react-native-blob-util@0.23.2:
resolution: {integrity: sha512-ZsUUFQYyZ7BI57c31XdPCkPlteoH7+PvcVy2w6wh1OPSUWGtKL79pj7fa6MepMX0v87fn0V9Heq0n6OjEpLdCw==}
peerDependencies:
react: '*'
react-native: '*'
react-native-gesture-handler@2.28.0: react-native-gesture-handler@2.28.0:
resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==} resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==}
peerDependencies: peerDependencies:
@@ -10103,6 +10121,13 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
react-native-pdf@7.0.3:
resolution: {integrity: sha512-zDtF6CGXPAfGptQZqX7LQK3CVQrIGsD+rYuBnMK0sVmd8mrq7ciwmWXINT+d92emMtZ7+PLnx1IQZIdsh0fphA==}
peerDependencies:
react: '*'
react-native: '*'
react-native-blob-util: '>=0.13.7'
react-native-reanimated@4.1.5: react-native-reanimated@4.1.5:
resolution: {integrity: sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==} resolution: {integrity: sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==}
peerDependencies: peerDependencies:
@@ -11264,11 +11289,6 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.20.3:
resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
engines: {node: '>=18.0.0'}
hasBin: true
tsx@4.20.6: tsx@4.20.6:
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@@ -14046,7 +14066,7 @@ snapshots:
'@esbuild-kit/esm-loader@2.6.5': '@esbuild-kit/esm-loader@2.6.5':
dependencies: dependencies:
'@esbuild-kit/core-utils': 3.3.2 '@esbuild-kit/core-utils': 3.3.2
get-tsconfig: 4.10.0 get-tsconfig: 4.10.1
'@esbuild/aix-ppc64@0.19.12': '@esbuild/aix-ppc64@0.19.12':
optional: true optional: true
@@ -16421,6 +16441,8 @@ snapshots:
'@react-native/js-polyfills@0.81.5': {} '@react-native/js-polyfills@0.81.5': {}
'@react-native/normalize-color@2.1.0': {}
'@react-native/normalize-colors@0.74.89': {} '@react-native/normalize-colors@0.74.89': {}
'@react-native/normalize-colors@0.76.2': {} '@react-native/normalize-colors@0.76.2': {}
@@ -18705,6 +18727,8 @@ snapshots:
bare-events: 2.5.4 bare-events: 2.5.4
optional: true optional: true
base-64@0.1.0: {}
base-64@1.0.0: {} base-64@1.0.0: {}
base64-js@1.5.1: {} base64-js@1.5.1: {}
@@ -19274,6 +19298,8 @@ snapshots:
crypt@0.0.2: {} crypt@0.0.2: {}
crypto-js@4.2.0: {}
crypto-random-string@2.0.0: {} crypto-random-string@2.0.0: {}
css-in-js-utils@3.1.0: css-in-js-utils@3.1.0:
@@ -19406,6 +19432,12 @@ snapshots:
depd@2.0.0: {} depd@2.0.0: {}
deprecated-react-native-prop-types@2.3.0:
dependencies:
'@react-native/normalize-color': 2.1.0
invariant: 2.2.4
prop-types: 15.8.1
dequal@2.0.3: {} dequal@2.0.3: {}
destr@2.0.5: {} destr@2.0.5: {}
@@ -21054,10 +21086,6 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
get-tsconfig@4.10.0:
dependencies:
resolve-pkg-maps: 1.0.0
get-tsconfig@4.10.1: get-tsconfig@4.10.1:
dependencies: dependencies:
resolve-pkg-maps: 1.0.0 resolve-pkg-maps: 1.0.0
@@ -21545,7 +21573,7 @@ snapshots:
jiti: 2.0.0-beta.3 jiti: 2.0.0-beta.3
jiti-v1: jiti@1.21.7 jiti-v1: jiti@1.21.7
pathe: 1.1.2 pathe: 1.1.2
tsx: 4.20.3 tsx: 4.20.6
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -22724,7 +22752,7 @@ snapshots:
metro-transform-plugins@0.83.3: metro-transform-plugins@0.83.3:
dependencies: dependencies:
'@babel/core': 7.28.4 '@babel/core': 7.28.4
'@babel/generator': 7.28.3 '@babel/generator': 7.28.5
'@babel/template': 7.27.2 '@babel/template': 7.27.2
'@babel/traverse': 7.28.4 '@babel/traverse': 7.28.4
flow-enums-runtime: 0.0.6 flow-enums-runtime: 0.0.6
@@ -22755,7 +22783,7 @@ snapshots:
metro-transform-worker@0.83.3: metro-transform-worker@0.83.3:
dependencies: dependencies:
'@babel/core': 7.28.4 '@babel/core': 7.28.4
'@babel/generator': 7.28.3 '@babel/generator': 7.28.5
'@babel/parser': 7.28.5 '@babel/parser': 7.28.5
'@babel/types': 7.28.5 '@babel/types': 7.28.5
flow-enums-runtime: 0.0.6 flow-enums-runtime: 0.0.6
@@ -24019,6 +24047,13 @@ snapshots:
react-is@19.2.0: {} react-is@19.2.0: {}
react-native-blob-util@0.23.2(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
base-64: 0.1.0
glob: 10.4.5
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0)
react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@egjs/hammerjs': 2.0.17 '@egjs/hammerjs': 2.0.17
@@ -24032,6 +24067,14 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0) react-native: 0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0)
react-native-pdf@7.0.3(react-native-blob-util@0.23.2(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
crypto-js: 4.2.0
deprecated-react-native-prop-types: 2.3.0
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0)
react-native-blob-util: 0.23.2(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-reanimated@4.1.5(@babel/core@7.28.4)(react-native-worklets@0.5.1(@babel/core@7.28.4)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): react-native-reanimated@4.1.5(@babel/core@7.28.4)(react-native-worklets@0.5.1(@babel/core@7.28.4)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.2(typescript@5.9.3))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@babel/core': 7.28.4 '@babel/core': 7.28.4
@@ -24522,7 +24565,7 @@ snapshots:
rolldown-plugin-dts@0.15.6(rolldown@1.0.0-beta.31)(typescript@5.9.3): rolldown-plugin-dts@0.15.6(rolldown@1.0.0-beta.31)(typescript@5.9.3):
dependencies: dependencies:
'@babel/generator': 7.28.3 '@babel/generator': 7.28.5
'@babel/parser': 7.28.5 '@babel/parser': 7.28.5
'@babel/types': 7.28.5 '@babel/types': 7.28.5
ast-kit: 2.1.1 ast-kit: 2.1.1
@@ -25563,13 +25606,6 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tsx@4.20.3:
dependencies:
esbuild: 0.25.10
get-tsconfig: 4.10.1
optionalDependencies:
fsevents: 2.3.3
tsx@4.20.6: tsx@4.20.6:
dependencies: dependencies:
esbuild: 0.25.10 esbuild: 0.25.10