Merge pull request #9099 from streetwriters/fix-file-errors

Fix bugs in file picker, pdf preview, backup restoration and vault unlock with biometrics
This commit is contained in:
Ammar Ahmed
2025-12-26 11:23:39 +05:00
committed by GitHub
12 changed files with 130 additions and 117 deletions

View File

@@ -222,15 +222,21 @@ export async function deleteCacheFileByName(name: string) {
}
export async function deleteDCacheFiles() {
await createCacheDir();
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache") || file.startsWith("NN_")) {
await RNFetchBlob.fs.unlink(file).catch(() => {
/* empty */
});
try {
await createCacheDir();
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (
file.includes("_dcache") ||
file.startsWith("NN_") ||
file.endsWith(".pdf")
) {
await RNFetchBlob.fs.unlink(file).catch(() => {
/* empty */
});
}
}
}
} catch (e) {}
}
export async function getCachePathForFile(filename: string) {

View File

@@ -161,10 +161,10 @@ export async function checkUpload(
size === 0
? `File size is 0.`
: size === -1
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
if (error) throw new Error(error);
}
@@ -189,3 +189,7 @@ export async function checkAndCreateDir(path: string) {
}
return dir;
}
export const santizeUri = (uri: string) => {
return Platform.OS === "ios" ? decodeURI(uri).replace("file:///", "/") : uri;
};

View File

@@ -23,6 +23,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import Orientation from "react-native-orientation-locker";
import Pdf from "react-native-pdf";
import FileViewer from "react-native-file-viewer";
import { MMKV } from "../../../common/database/mmkv";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import { deleteCacheFileByPath, exists } from "../../../common/filesystem/io";
@@ -43,6 +44,7 @@ import SheetProvider from "../../sheet-provider";
import { IconButton } from "../../ui/icon-button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import ReactNativeBlobUtil from "react-native-blob-util";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
@@ -117,8 +119,11 @@ const PDFPreview = () => {
setVisible(false);
return;
}
const path = `${cacheDir}/${uri}`;
let path = `${cacheDir}/${attachment.filename}`;
snapshotValue.current = snapshot.current;
await ReactNativeBlobUtil.fs
.mv(`${cacheDir}/${uri}`, path)
.catch(console.log);
setPDFSource("file://" + path);
setLoading(false);
}, 100);
@@ -127,7 +132,7 @@ const PDFPreview = () => {
);
const close = () => {
deleteCacheFileByPath(pdfSource);
deleteCacheFileByPath(pdfSource.replace("file://", ""));
setPDFSource(null);
setVisible(false);
setPassword("");
@@ -157,7 +162,12 @@ const PDFPreview = () => {
return (
visible && (
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
<BaseDialog
animation="fade"
visible={true}
onRequestClose={close}
useSafeArea={false}
>
<SheetProvider context={attachment?.hash} />
<Dialog context={attachment?.hash} />
@@ -223,45 +233,48 @@ const PDFPreview = () => {
<View
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 12
gap: DefaultAppStyles.GAP_SMALL
}}
>
<TextInput
ref={inputRef}
defaultValue={currentPage + ""}
<View
style={{
color: colors.primary.paragraph,
padding: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
paddingVertical: 0,
height: 25,
backgroundColor: colors.secondary.background,
width: 40,
textAlign: "center",
marginRight: 4,
borderRadius: 3,
fontFamily: "Inter-Regular"
flexDirection: "row",
alignItems: "center"
}}
selectTextOnFocus
keyboardType="decimal-pad"
onSubmitEditing={(event) => {
setCurrentPage(event.nativeEvent.text);
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
}}
blurOnSubmit
/>
<Paragraph color={colors.static.white}>/{numPages}</Paragraph>
</View>
<View
style={{
flexDirection: "row"
}}
>
>
<TextInput
ref={inputRef}
defaultValue={currentPage + ""}
style={{
color: colors.primary.paragraph,
padding: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
paddingVertical: 0,
height: 25,
backgroundColor: colors.secondary.background,
width: 40,
textAlign: "center",
marginRight: 4,
borderRadius: 3,
fontFamily: "Inter-Regular"
}}
selectTextOnFocus
keyboardType="decimal-pad"
onSubmitEditing={(event) => {
setCurrentPage(event.nativeEvent.text);
pdfRef.current?.setPage(
parseInt(event.nativeEvent.text)
);
}}
blurOnSubmit
/>
<Paragraph color={colors.static.white}>
/{numPages}
</Paragraph>
</View>
<IconButton
color={colors.static.white}
name="download"
@@ -269,6 +282,16 @@ const PDFPreview = () => {
downloadAttachment(attachment.hash, false);
}}
/>
<IconButton
color={colors.static.white}
name="open-in-new"
onPress={() => {
FileViewer.open(pdfSource, {
showOpenWithDialog: true,
showAppsSuggestions: true
});
}}
/>
</View>
</View>
{pdfSource ? (

View File

@@ -107,6 +107,7 @@ import { fluidTabsRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import useFeatureManager from "./use-feature-manager";
import { deleteDCacheFiles } from "../common/filesystem/io";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -457,6 +458,7 @@ const initializeDatabase = async (password?: string) => {
Notifications.setupReminders(true);
DatabaseLogger.info("Database initialized");
Notifications.restorePinnedNotes();
deleteDCacheFiles();
}
Walkthrough.init();
};

View File

@@ -22,10 +22,10 @@ import { isFeatureAvailable } from "@notesnook/common";
import { isImage } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import {
pick as pickFile,
DocumentPickerOptions,
DocumentPickerResponse,
keepLocalCopy
keepLocalCopy,
KeepLocalCopyResponse,
pick as pickFile
} from "@react-native-documents/picker";
import { basename } from "pathe";
import { Platform } from "react-native";
@@ -35,31 +35,13 @@ import { DatabaseLogger, db } from "../../../common/database";
import filesystem from "../../../common/filesystem";
import { compressToFile } from "../../../common/filesystem/compress";
import AttachImage from "../../../components/dialogs/attach-image-dialog";
import {
ToastManager,
eSendEvent,
presentSheet
} from "../../../services/event-manager";
import { ToastManager } from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useUserStore } from "../../../stores/use-user-store";
import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
title: strings.encryptingAttachment(),
paragraph: strings.encryptingAttachmentDesc(file.name || ""),
icon: "attachment"
});
};
const santizeUri = (uri: string) => {
uri = decodeURI(uri);
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
return uri;
};
import { santizeUri } from "../../../common/filesystem/utils";
type PickerOptions = {
noteId?: string;
@@ -80,7 +62,7 @@ const file = async (fileOptions: PickerOptions) => {
await db.attachments.generateKey();
let file;
let fileCopyUri;
let fileCopyUri: KeepLocalCopyResponse[0];
let fileName;
try {
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
@@ -97,12 +79,10 @@ const file = async (fileOptions: PickerOptions) => {
});
fileCopyUri = result[0];
} catch (e) {
DatabaseLogger.error(e as Error, "Error picking file");
return;
}
let uri =
Platform.OS === "ios" ? fileCopyUri.sourceUri || file.uri : file.uri;
const featureResult = await isFeatureAvailable("fileSize", file.size || 0);
if (!featureResult.isAllowed) {
ToastManager.show({
@@ -122,8 +102,7 @@ const file = async (fileOptions: PickerOptions) => {
return;
}
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
showEncryptionSheet(file);
let uri = santizeUri(fileCopyUri.localUri);
const hash = await Sodium.hashFile({
uri: uri,
type: "url"
@@ -139,7 +118,8 @@ const file = async (fileOptions: PickerOptions) => {
) {
throw new Error("Failed to attach file");
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
await RNFetchBlob.fs.unlink(uri);
if (
fileOptions.tabId !== undefined &&
@@ -173,10 +153,7 @@ const file = async (fileOptions: PickerOptions) => {
} else {
throw new Error("Failed to attach file, no tabId is set");
}
eSendEvent(eCloseSheet);
} catch (e) {
eSendEvent(eCloseSheet);
ToastManager.show({
heading: (e as Error).message,
type: "error",

View File

@@ -677,7 +677,7 @@ export const useEditor = (
state.current.currentlyEditing = true;
if (!tabLocked) {
await loadContent(item);
} else {
} else if (fluidTabsRef.current?.page() === "editor") {
commands.focus(tabId!);
}

View File

@@ -367,7 +367,6 @@ export const RestoreBackup = () => {
disableAppLockRequests: true
});
const file = await pick();
const fileCopy = await keepLocalCopy({
destination: "cachesDirectory",
files: [
@@ -390,10 +389,7 @@ export const RestoreBackup = () => {
}, 1000);
restoreBackup({
uri:
Platform.OS === "android"
? (("file://" + fileCopy[0].sourceUri) as string)
: (fileCopy[0].sourceUri as string),
uri: fileCopy[0].localUri,
deleteFile: true
});
},

View File

@@ -16,6 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { LegendList } from "@legendapp/list";
import { strings } from "@notesnook/intl";
import {
THEME_COMPATIBILITY_VERSION,
ThemeDefinition,
@@ -29,7 +31,6 @@ import type {
ThemesRouter
} from "@notesnook/themes-server";
import { keepLocalCopy, pick } from "@react-native-documents/picker";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
@@ -40,24 +41,24 @@ import {
TouchableOpacity,
View
} from "react-native";
import ReactNativeBlobUtil from "react-native-blob-util";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { DatabaseLogger, db } from "../../common/database";
import { santizeUri } from "../../common/filesystem/utils";
import SheetProvider from "../../components/sheet-provider";
import { Button } from "../../components/ui/button";
import { IconButton } from "../../components/ui/icon-button";
import Input from "../../components/ui/input";
import { Pressable } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { ToastManager, presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import { getColorLinearShade } from "../../utils/colors";
import { getElevationStyle } from "../../utils/elevation";
import { MenuItemsList } from "../../utils/menu-items";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import { getColorLinearShade } from "../../utils/colors";
import { strings } from "@notesnook/intl";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { LegendList } from "@legendapp/list";
import ReactNativeBlobUtil from "react-native-blob-util";
const THEME_SERVER_URL = "https://themes-api.notesnook.com";
//@ts-ignore
@@ -429,12 +430,16 @@ function ThemeSelector() {
});
if (copiedFile[0].status !== "success") return;
const themeJsonCopiedPath = santizeUri(
copiedFile[0].localUri
);
const themeJson = await ReactNativeBlobUtil.fs.readFile(
copiedFile[0].localUri,
themeJsonCopiedPath,
"utf8"
);
ReactNativeBlobUtil.fs
.unlink(copiedFile[0].localUri)
.unlink(themeJsonCopiedPath)
.catch(() => {});
const json = JSON.parse(themeJson);
const result = validateTheme(json);

View File

@@ -76,11 +76,11 @@ async function getCredentials(title?: string, description?: string) {
const options = Platform.select({
ios: {
fallbackEnabled: false,
description: description
description: description || title || "Unlock"
},
android: {
title: title,
description: description,
description: description || "Unlock",
deviceCredentialAllowed: false
}
});
@@ -132,11 +132,11 @@ async function validateUser(title: string, description?: string) {
Platform.select({
ios: {
fallbackEnabled: false,
description: title
description: title || "Unlock"
},
android: {
title: title,
description: description,
description: description || "Unlock",
deviceCredentialAllowed: false
}
}) as AuthenticateIOS

View File

@@ -1887,7 +1887,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-document-picker (11.0.1):
- react-native-document-picker (11.0.3):
- boost
- DoubleConversion
- fast_float
@@ -2033,7 +2033,7 @@ PODS:
- SocketRocket
- SwiftUIIntrospect (~> 1.0)
- Yoga
- react-native-pdf (6.7.7):
- react-native-pdf (7.0.3):
- boost
- DoubleConversion
- fast_float
@@ -3894,7 +3894,7 @@ SPEC CHECKSUMS:
react-native-blob-util: 7946b7e13acf0da5e849dc2f73fcfebe1d981699
react-native-config: 963b5efabc864cf69412e54b5de49b6a23e4af03
react-native-date-picker: 4f4f40f6e65798038bb4b1bff47890c2be69c2e6
react-native-document-picker: 254467fec90f263dfc4828210daf3e8baa4fcb81
react-native-document-picker: d624d3d9bd9311da87f6f7b64aa44f69927d8543
react-native-fingerprint-scanner: d5e143a361f3f01858e9c45141ddcabc4fd57055
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-gzip: 794e0e964a0d9e1dfd1773fee938adb4d4310e26
@@ -3907,7 +3907,7 @@ SPEC CHECKSUMS:
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
react-native-pager-view: d7d2aa47f54343bf55fdcee3973503dd27c2bd37
react-native-pdf: c586da0d19c14e6d859e62bf957851687fba0f25
react-native-pdf: edc236298f13f1609e42d41e45b8b6ea88ed10f9
react-native-quick-sqlite: 1ed8d3db1e22a8604d006be69f06053382e93bb0
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
react-native-screenguard: 9fc3b4ad5b97783fc0832638fae0dce51272c661

View File

@@ -42,7 +42,7 @@
"@react-native-community/datetimepicker": "^8.4.5",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/toolbar-android": "^0.2.1",
"@react-native-documents/picker": "^11.0.1",
"@react-native-documents/picker": "^11.0.3",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/elements": "^1.3.3",
"@react-navigation/native": "^6.0.10",
@@ -103,7 +103,7 @@
"react-native-notification-sounds": "0.5.5",
"react-native-orientation-locker": "^1.7.0",
"react-native-pager-view": "^8.0.0",
"react-native-pdf": "6.7.7",
"react-native-pdf": "^7.0.3",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-progress": "5.0.0",
"react-native-qrcode-svg": "^6.0.6",
@@ -4853,9 +4853,9 @@
}
},
"node_modules/@react-native-documents/picker": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-11.0.1.tgz",
"integrity": "sha512-aUq4MHGO/f8BslCFFx9OXz9NLLmcLkYAXp5PAEVau31v7obItPpb71Fe84bxpGV6gALIvGlGgSm6W9kEyU4toA==",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-11.0.3.tgz",
"integrity": "sha512-q+vQNT1NtZcFGwsDpTwMKNNcgOEq6foFDGlPQVt8H7KaEjCjC+wTGdXuVSe8JJr2uO9xZR6kJ7aRHReZ4XDRBw==",
"license": "MIT",
"peerDependencies": {
"react": "*",
@@ -17753,9 +17753,9 @@
}
},
"node_modules/react-native-pdf": {
"version": "6.7.7",
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.7.tgz",
"integrity": "sha512-D0ga/eyPsVWSPEBm622sGVZLl3gibxPmfm2cxsLcUrZ4WDSGR5HyGmvvWaR/m9wXEyIbD4J6q9qzuG6yObcSXw==",
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-7.0.3.tgz",
"integrity": "sha512-zDtF6CGXPAfGptQZqX7LQK3CVQrIGsD+rYuBnMK0sVmd8mrq7ciwmWXINT+d92emMtZ7+PLnx1IQZIdsh0fphA==",
"license": "MIT",
"dependencies": {
"crypto-js": "4.2.0",

View File

@@ -58,7 +58,7 @@
"@react-native-community/datetimepicker": "^8.4.5",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/toolbar-android": "^0.2.1",
"@react-native-documents/picker": "^11.0.1",
"@react-native-documents/picker": "^11.0.3",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/elements": "^1.3.3",
"@react-navigation/native": "^6.0.10",
@@ -119,7 +119,7 @@
"react-native-notification-sounds": "0.5.5",
"react-native-orientation-locker": "^1.7.0",
"react-native-pager-view": "^8.0.0",
"react-native-pdf": "6.7.7",
"react-native-pdf": "^7.0.3",
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
"react-native-progress": "5.0.0",
"react-native-qrcode-svg": "^6.0.6",