diff --git a/apps/mobile/app/common/filesystem/download-attachment.js b/apps/mobile/app/common/filesystem/download-attachment.js
index e5b82cdcb..444445523 100644
--- a/apps/mobile/app/common/filesystem/download-attachment.js
+++ b/apps/mobile/app/common/filesystem/download-attachment.js
@@ -54,15 +54,10 @@ export async function downloadAllAttachments() {
* @param onProgress
* @returns
*/
-export async function downloadAttachments(
- attachments,
- onProgress,
- canceled,
- groupId
-) {
+export async function downloadAttachments(attachments) {
await createCacheDir();
if (!attachments || !attachments.length) return;
- const result = new Map();
+ const groupId = `download-all-${Date.now()}`;
let outputFolder;
if (Platform.OS === "android") {
@@ -86,92 +81,117 @@ export async function downloadAttachments(
await RNFetchBlob.fs.mkdir(zipSourceFolder);
+ const isCancelled = () => {
+ if (useAttachmentStore.getState().downloading[groupId]?.canceled) {
+ RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
+ useAttachmentStore.getState().setDownloading({
+ groupId,
+ current: 0,
+ total: 0,
+ success: false,
+ message: "Download cancelled",
+ canceled: true
+ });
+ return true;
+ }
+ };
+
for (let i = 0; i < attachments.length; i++) {
+ if (isCancelled()) return;
let attachment = await db.attachments.attachment(attachments[i]);
const hash = attachment.hash;
try {
- if (canceled.current) {
- RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
- return;
- }
- onProgress?.(
- i + 1 / attachments.length,
- `Downloading attachments (${i + 1}/${
- attachments.length
- })... Please wait`
- );
+ useAttachmentStore.getState().setDownloading({
+ groupId: groupId,
+ current: i + 1,
+ total: attachments.length,
+ filename: attachment.hash
+ });
// Download to cache
let uri = await downloadAttachment(hash, false, {
silent: true,
cache: true,
groupId: groupId
});
- if (canceled.current) {
- RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
- return;
- }
+
+ if (isCancelled()) return;
+
if (!uri) throw new Error("Failed to download file");
// Move file to the source folder we will zip eventually and rename the file to it's actual name.
const filePath = `${zipSourceFolder}/${attachment.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
- result.set(hash, {
- filename: attachment.filename,
- status: FileDownloadStatus.Success,
- attachment: attachment
- });
} catch (e) {
- result.set(hash, {
- filename: attachment.filename,
- status: FileDownloadStatus.Fail,
- reason: e
- });
- ToastManager.error(e, "Error downloading attachment");
+ DatabaseLogger.error(e);
}
}
- if (canceled.current) {
- RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
- return;
- }
- if (result?.size) {
- let sub;
- try {
- onProgress?.(0, `Zipping... Please wait`);
- // If all goes well, zip the notesnook-attachments folder in cache.
- sub = subscribe(({ progress }) => {
- onProgress(
- progress,
- `Saving zip file (${(progress * 100).toFixed(1)}%)... Please wait`
- );
+ useAttachmentStore.getState().setDownloading({
+ groupId: groupId,
+ current: 0,
+ total: 0,
+ success: true
+ });
+
+ if (isCancelled()) return;
+
+ let sub;
+ try {
+ useAttachmentStore.getState().setDownloading({
+ current: 0,
+ total: 1,
+ message: "Saving zip file... Please wait",
+ groupId
+ });
+ // If all goes well, zip the notesnook-attachments folder in cache.
+
+ sub = subscribe(({ progress }) => {
+ useAttachmentStore.getState().setDownloading({
+ groupId,
+ current: progress,
+ total: 1,
+ message: `Saving zip file (${(progress * 100).toFixed(
+ 1
+ )}%)... Please wait`
});
- await zip(zipSourceFolder, zipOutputFile);
- sub?.remove();
- onProgress(1, `Saving zip file... Please wait`);
- if (Platform.OS === "android") {
- // Move the zip to user selected directory.
- const file = await ScopedStorage.createFile(
- outputFolder,
- `notesnook-attachments-${Date.now()}.zip`,
- "application/zip"
- );
- await copyFileAsync(`file://${zipOutputFile}`, file.uri);
- }
+ });
+ await zip(zipSourceFolder, zipOutputFile);
+ sub?.remove();
- onProgress?.(1, `Done`);
- releasePermissions(outputFolder);
- } catch (e) {
- releasePermissions(outputFolder);
- sub?.remove();
- ToastManager.error(e, "Error zipping attachments");
- }
- // Remove source & zip file from cache.
- RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
- RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
+ // Move the zip to user selected directory.
+ const file = await ScopedStorage.createFile(
+ outputFolder,
+ `notesnook-attachments-${Date.now()}.zip`,
+ "application/zip"
+ );
+ await copyFileAsync(`file://${zipOutputFile}`, file.uri);
}
- }
- return result;
+ useAttachmentStore.getState().setDownloading({
+ current: 0,
+ total: 0,
+ message: undefined,
+ success: true,
+ groupId
+ });
+ releasePermissions(outputFolder);
+ } catch (e) {
+ useAttachmentStore.getState().setDownloading({
+ current: 0,
+ total: 0,
+ message: undefined,
+ success: true,
+ groupId
+ });
+ releasePermissions(outputFolder);
+ sub?.remove();
+ ToastManager.error(e, "Error zipping attachments");
+ }
+ // Remove source & zip file from cache.
+ RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
+ if (Platform.OS === "android") {
+ RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
+ }
}
export default async function downloadAttachment(
diff --git a/apps/mobile/app/components/attachments/actions.tsx b/apps/mobile/app/components/attachments/actions.tsx
index 2511af137..22b903795 100644
--- a/apps/mobile/app/components/attachments/actions.tsx
+++ b/apps/mobile/app/components/attachments/actions.tsx
@@ -30,6 +30,8 @@ import filesystem from "../../common/filesystem";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import picker from "../../screens/editor/tiptap/picker";
+import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
+import { editorController } from "../../screens/editor/tiptap/utils";
import {
ToastManager,
eSendEvent,
@@ -49,14 +51,12 @@ import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
+import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
-import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
-import { editorController } from "../../screens/editor/tiptap/utils";
-import SheetProvider from "../sheet-provider";
const Actions = ({
attachment,
@@ -88,7 +88,7 @@ const Actions = ({
useAttachmentStore.getState().remove(attachment.hash);
}
downloadAttachment(attachment.hash, false);
- eSendEvent(eCloseSheet, contextId);
+ fwdRef.current?.hide();
},
icon: "download"
},
@@ -149,9 +149,8 @@ const Actions = ({
},
{
name: "Rename",
- onPress: () => {
+ onPress: async () => {
presentDialog({
- context: contextId as any,
input: true,
title: "Rename file",
paragraph: "Enter a new name for the file",
@@ -219,6 +218,7 @@ const Actions = ({
style={{
maxHeight: "100%"
}}
+ keyboardShouldPersistTaps="never"
>
diff --git a/apps/mobile/app/components/attachments/attachment-item.tsx b/apps/mobile/app/components/attachments/attachment-item.tsx
index 357e846ce..44edb2d1f 100644
--- a/apps/mobile/app/components/attachments/attachment-item.tsx
+++ b/apps/mobile/app/components/attachments/attachment-item.tsx
@@ -22,7 +22,6 @@ import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { TouchableOpacity, View } from "react-native";
-import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useDBItem } from "../../hooks/use-db-item";
@@ -44,7 +43,8 @@ export const AttachmentItem = ({
setAttachments,
pressable = true,
hideWhenNotDownloading,
- context
+ context,
+ errorOnly
}: {
id: string | number;
attachments?: VirtualizedGrouping;
@@ -53,9 +53,9 @@ export const AttachmentItem = ({
pressable?: boolean;
hideWhenNotDownloading?: boolean;
context?: string;
+ errorOnly?: boolean;
}) => {
const [attachment] = useDBItem(id, "attachment", attachments);
-
const { colors } = useThemeColors();
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
@@ -67,8 +67,7 @@ export const AttachmentItem = ({
Actions.present(attachment, setAttachments, context);
};
- return hideWhenNotDownloading &&
- (!currentProgress || !currentProgress.value) ? null : (
+ return errorOnly && attachment && !attachment?.failed ? null : (
@@ -96,20 +93,26 @@ export const AttachmentItem = ({
style={{
justifyContent: "center",
alignItems: "center",
- marginLeft: -5
+ marginLeft: -5,
+ borderWidth: 1,
+ borderColor: colors.secondary.border,
+ paddingHorizontal: 2,
+ minWidth: 20,
+ height: 30,
+ borderRadius: 5
}}
>
-
-
- {getFileExtension(attachment.filename).toUpperCase()}
+ {getFileExtension(attachment.filename).toUpperCase() ||
+ attachment.mimeType.split("/")?.[1]?.toUpperCase()}
@@ -120,10 +123,9 @@ export const AttachmentItem = ({
}}
>
- {formatBytes(attachment.size)}{" "}
- {currentProgress?.type
- ? "(" + currentProgress.type + "ing - tap to cancel)"
- : ""}
+ File size: {formatBytes(attachment.size)}
) : null}
diff --git a/apps/mobile/app/components/attachments/download-attachments.tsx b/apps/mobile/app/components/attachments/download-attachments.tsx
deleted file mode 100644
index b66995efd..000000000
--- a/apps/mobile/app/components/attachments/download-attachments.tsx
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
-This file is part of the Notesnook project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-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 .
-*/
-
-import React, { useRef, useState } from "react";
-import { Platform, View } from "react-native";
-import { db } from "../../common/database";
-import { downloadAttachments } from "../../common/filesystem/download-attachment";
-import {
- PresentSheetOptions,
- presentSheet
-} from "../../services/event-manager";
-import { Button } from "../ui/button";
-import Heading from "../ui/typography/heading";
-import Paragraph from "../ui/typography/paragraph";
-import { ProgressBarComponent } from "../ui/svg/lazy";
-import { useThemeColors } from "@notesnook/theme";
-import { FlatList } from "react-native-actions-sheet";
-import { AttachmentItem } from "./attachment-item";
-import { Attachment, VirtualizedGrouping } from "@notesnook/core";
-
-const DownloadAttachments = ({
- close,
- attachments,
- isNote,
- update
-}: {
- attachments: VirtualizedGrouping;
- close?: ((ctx?: string | undefined) => void) | undefined;
- isNote?: boolean;
- update?: (props: PresentSheetOptions) => void;
-}) => {
- const { colors } = useThemeColors();
- const [downloading, setDownloading] = useState(false);
- const [progress, setProgress] = useState({
- value: 0,
- statusText: "Download started... Please wait"
- });
- const [result, setResult] = useState(new Map());
- const canceled = useRef(false);
- const groupId = useRef();
-
- const onDownload = async () => {
- update?.({
- disableClosing: true
- } as PresentSheetOptions);
- setDownloading(true);
- canceled.current = false;
- groupId.current = Date.now().toString();
- const result = await downloadAttachments(
- await attachments.ids(),
- (progress: number, statusText: string) =>
- setProgress({ value: progress, statusText }),
- canceled,
- groupId.current
- );
- if (canceled.current) return;
- setResult(result || new Map());
- setDownloading(false);
- update?.({
- disableClosing: false
- } as PresentSheetOptions);
- };
-
- const cancel = async () => {
- update?.({
- disableClosing: false
- } as PresentSheetOptions);
- canceled.current = true;
- if (!groupId.current) return;
- console.log(groupId.current, "canceling groupId downloads");
- await db.fs().cancel(groupId.current);
- setDownloading(false);
- setResult(new Map());
- groupId.current = undefined;
- };
-
- const failedResults = () => {
- const results = [];
- for (const value of result.values()) {
- if (value.status === 0) results.push(value.attachment);
- }
- return results;
- };
-
- function getResultText() {
- const downloadedAttachmentsCount =
- attachments?.placeholders?.length - failedResults().length;
- if (downloadedAttachmentsCount === 0)
- return "Failed to download all attachments";
- return `Successfully downloaded ${downloadedAttachmentsCount}/${
- attachments?.placeholders.length
- } attachments as a zip file at ${
- Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
- }`;
- }
-
- return (
-
-
- {downloading
- ? "Downloading attachments"
- : result?.size
- ? "Downloaded attachments"
- : "Download attachments"}
-
-
- {downloading ? (
-
- {progress.statusText}
-
- ) : result?.size ? (
-
- {getResultText()}
-
- ) : (
-
- Are you sure you want to download all attachments
- {isNote ? " of this note?" : "?"}
-
- )}
-
- {downloading ? (
-
-
-
- ) : null}
-
-
-
- No downloads in progress.
-
-
- }
- keyExtractor={(index) => "attachment_download" + index}
- renderItem={({ index }) => {
- return (
- {}}
- pressable={false}
- hideWhenNotDownloading={true}
- attachments={attachments}
- />
- );
- }}
- />
-
- {result?.size ? (
-