mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
mobile: add support for multipart uploads
Files >100MB fail currently using simple file upload methods.
This commit is contained in:
@@ -17,7 +17,7 @@ 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 { isImage, RequestOptions } from "@notesnook/core";
|
||||
import { isImage, RequestOptions, hosts } from "@notesnook/core";
|
||||
import { PermissionsAndroid, Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -32,6 +32,133 @@ import {
|
||||
getUploadedFileSize
|
||||
} from "./utils";
|
||||
import Upload from "@ammarahmed/react-native-upload";
|
||||
import { CloudUploader } from "react-native-nitro-cloud-uploader";
|
||||
|
||||
// Upload constants
|
||||
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
const MINIMUM_MULTIPART_FILE_SIZE = 25 * 1024 * 1024; // 25MB
|
||||
|
||||
interface InitiateMultipartResponse {
|
||||
uploadId: string;
|
||||
parts: string[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function initiateMultipartUpload(
|
||||
filename: string,
|
||||
fileSize: number,
|
||||
headers: Record<string, string>
|
||||
): Promise<InitiateMultipartResponse> {
|
||||
const totalParts = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
|
||||
const url = `${hosts.API_HOST}/s3/multipart?name=${filename}&parts=${totalParts}&uploadId=`;
|
||||
const response = await fetch(url, { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to initiate multipart upload: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
if (!data.uploadId || !data.parts) {
|
||||
throw new Error("Failed to initiate multipart upload: invalid response.");
|
||||
}
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Initiated multipart upload for ${filename} with upload ID: ${data.uploadId}`
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function multipartUploadFile(
|
||||
filename: string,
|
||||
filePath: string,
|
||||
fileSize: number,
|
||||
requestOptions: RequestOptions,
|
||||
cancelToken: { cancel: (reason?: string) => Promise<void> }
|
||||
): Promise<Response> {
|
||||
const { headers } = requestOptions;
|
||||
|
||||
try {
|
||||
const uploadData = await initiateMultipartUpload(
|
||||
filename,
|
||||
fileSize,
|
||||
headers
|
||||
);
|
||||
const { uploadId, parts } = uploadData;
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Starting upload for ${filename} with ${parts.length} parts`
|
||||
);
|
||||
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
await CloudUploader.cancelUpload(uploadId);
|
||||
};
|
||||
|
||||
CloudUploader.addListener("upload-progress", (event) => {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.bytesUploaded || 0,
|
||||
event.totalBytes || fileSize,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.bytesUploaded}/${
|
||||
event.totalBytes || fileSize
|
||||
}, chunk: ${event.chunkIndex}, progress: ${event.progress}`
|
||||
);
|
||||
});
|
||||
// CloudUploader handles chunking and uploading all parts internally
|
||||
const result = await CloudUploader.startUpload(
|
||||
filename,
|
||||
filePath,
|
||||
parts,
|
||||
3, // maxParallel
|
||||
true // showNotification
|
||||
);
|
||||
|
||||
CloudUploader.removeListener("upload-progress");
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error("Failed to upload multipart file");
|
||||
}
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Multipart upload completed for ${filename} with upload ID: ${uploadId}`
|
||||
);
|
||||
|
||||
const response = await fetch(`${hosts.API_HOST}/s3/multipart`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
Key: filename,
|
||||
UploadId: uploadId,
|
||||
PartETags: result.etags.map((etag, index) => ({
|
||||
partNumber: index + 1,
|
||||
etag: etag
|
||||
}))
|
||||
}),
|
||||
headers: { ...headers, "Content-Type": "application/json" }
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
DatabaseLogger.error(error, "Multipart upload failed", { filename });
|
||||
CloudUploader.removeListener("upload-progress");
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFile(
|
||||
filename: string,
|
||||
@@ -96,72 +223,111 @@ export async function uploadFile(
|
||||
}
|
||||
}
|
||||
|
||||
const upload = Upload.create({
|
||||
customUploadId: filename,
|
||||
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
|
||||
url: url,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
...headers,
|
||||
"content-type": "application/octet-stream"
|
||||
},
|
||||
appGroup: IOS_APPGROUPID,
|
||||
notification: {
|
||||
filename:
|
||||
attachmentInfo && isImage(attachmentInfo?.mimeType)
|
||||
? "image"
|
||||
: attachmentInfo?.filename || "file",
|
||||
enabled: true,
|
||||
enableRingTone: true,
|
||||
autoClear: true
|
||||
}
|
||||
}).onChange((event) => {
|
||||
switch (event.status) {
|
||||
case "running":
|
||||
case "pending":
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.uploadedBytes || 0,
|
||||
event.totalBytes || fileInfo.size,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.uploadedBytes}/${
|
||||
event.totalBytes || fileInfo.size
|
||||
}`
|
||||
);
|
||||
break;
|
||||
case "completed":
|
||||
console.log("Upload completed");
|
||||
break;
|
||||
}
|
||||
});
|
||||
const result = await upload.start();
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
upload.cancel();
|
||||
};
|
||||
let uploaded = false;
|
||||
|
||||
const status = result.responseCode || 0;
|
||||
const uploaded = status >= 200 && status < 300;
|
||||
// Use multipart upload for files larger than MINIMUM_MULTIPART_FILE_SIZE
|
||||
if (fileInfo.size >= MINIMUM_MULTIPART_FILE_SIZE) {
|
||||
DatabaseLogger.info(
|
||||
`Using multipart upload for large file: ${filename} (${fileInfo.size} bytes)`
|
||||
);
|
||||
const result = await multipartUploadFile(
|
||||
filename,
|
||||
filePath,
|
||||
fileInfo.size,
|
||||
requestOptions,
|
||||
cancelToken
|
||||
);
|
||||
const status = result.status || 0;
|
||||
uploaded = status >= 200 && status < 300;
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(await result.text())}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Use single-part upload for smaller files
|
||||
DatabaseLogger.info(
|
||||
`Using single-part upload for file: ${filename} (${fileInfo.size} bytes)`
|
||||
);
|
||||
const upload = Upload.create({
|
||||
customUploadId: filename,
|
||||
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
|
||||
url: url,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
...headers,
|
||||
"content-type": "application/octet-stream"
|
||||
},
|
||||
appGroup: IOS_APPGROUPID,
|
||||
notification: {
|
||||
filename:
|
||||
attachmentInfo && isImage(attachmentInfo?.mimeType)
|
||||
? "image"
|
||||
: attachmentInfo?.filename || "file",
|
||||
enabled: true,
|
||||
enableRingTone: true,
|
||||
autoClear: true
|
||||
}
|
||||
}).onChange((event) => {
|
||||
switch (event.status) {
|
||||
case "running":
|
||||
case "pending":
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.uploadedBytes || 0,
|
||||
event.totalBytes || fileInfo.size,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.uploadedBytes}/${
|
||||
event.totalBytes || fileInfo.size
|
||||
}`
|
||||
);
|
||||
break;
|
||||
case "completed":
|
||||
DatabaseLogger.info("Upload completed");
|
||||
break;
|
||||
}
|
||||
});
|
||||
const result = await upload.start();
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
upload.cancel();
|
||||
};
|
||||
|
||||
const status = result.responseCode || 0;
|
||||
uploaded = status >= 200 && status < 300;
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(result.error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(result.error)}`
|
||||
if (uploaded) {
|
||||
attachmentInfo = await db.attachments.attachment(filename);
|
||||
if (!attachmentInfo) return false;
|
||||
await checkUpload(
|
||||
filename,
|
||||
requestOptions.chunkSize,
|
||||
attachmentInfo.size
|
||||
);
|
||||
DatabaseLogger.info(`File upload status: ${filename}, success`);
|
||||
}
|
||||
attachmentInfo = await db.attachments.attachment(filename);
|
||||
if (!attachmentInfo) return false;
|
||||
await checkUpload(filename, requestOptions.chunkSize, attachmentInfo.size);
|
||||
DatabaseLogger.info(`File upload status: ${filename}, ${status}`);
|
||||
|
||||
return uploaded;
|
||||
} catch (e) {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
@@ -45,6 +45,65 @@ PODS:
|
||||
- MMKV (1.3.14):
|
||||
- MMKVCore (~> 1.3.14)
|
||||
- MMKVCore (1.3.14)
|
||||
- NitroCloudUploader (1.0.9):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- NitroModules
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- NitroModules (0.32.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- pop (1.0.12)
|
||||
- RCT-Folly (2024.11.18.00):
|
||||
- boost
|
||||
@@ -2216,7 +2275,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- react-native-upload (6.29.0):
|
||||
- react-native-upload (6.31.0):
|
||||
- React
|
||||
- react-native-view-shot (4.0.3):
|
||||
- boost
|
||||
@@ -3404,6 +3463,8 @@ DEPENDENCIES:
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- MMKV
|
||||
- NitroCloudUploader (from `../node_modules/react-native-nitro-cloud-uploader`)
|
||||
- NitroModules (from `../node_modules/react-native-nitro-modules`)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
|
||||
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
|
||||
@@ -3560,6 +3621,10 @@ EXTERNAL SOURCES:
|
||||
hermes-engine:
|
||||
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
|
||||
:tag: hermes-2025-09-01-RNv0.82.0-265ef62ff3eb7289d17e366664ac0da82303e101
|
||||
NitroCloudUploader:
|
||||
:path: "../node_modules/react-native-nitro-cloud-uploader"
|
||||
NitroModules:
|
||||
:path: "../node_modules/react-native-nitro-modules"
|
||||
RCT-Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
|
||||
RCTDeprecation:
|
||||
@@ -3819,6 +3884,8 @@ SPEC CHECKSUMS:
|
||||
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
|
||||
MMKV: 7b5df6a8bf785c6705cc490c541b9d8a957c4a64
|
||||
MMKVCore: 3f40b896e9ab522452df9df3ce983471aa2449ba
|
||||
NitroCloudUploader: 62489381ed6b472d87c8c6b62629136ebd2342d1
|
||||
NitroModules: f964d2f3f5b392d0c0303737085cc30e375dc43f
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
|
||||
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a
|
||||
@@ -3879,7 +3946,7 @@ SPEC CHECKSUMS:
|
||||
react-native-share-extension: fdc6aaab51591a2d445df239c446aaa3a99658ec
|
||||
react-native-sodium: 066f76e46c9be13e9260521e3fa994937c4cdab4
|
||||
react-native-theme-switch-animation: 449d6db7a760f55740505e7403ae8061debc9a7e
|
||||
react-native-upload: 1459a4be625dda39e4364d9e7d859477a3000f2a
|
||||
react-native-upload: 6285965d20879bcfcf7953f05eb785ba03a60e40
|
||||
react-native-view-shot: 6c008e58f4720de58370848201c5d4a082c6d4ca
|
||||
react-native-webview: 654f794a7686b47491cf43aa67f7f428bea00eed
|
||||
React-NativeModulesApple: 46690a0fe94ec28fc6fc686ec797b911d251ded0
|
||||
|
||||
42
apps/mobile/package-lock.json
generated
42
apps/mobile/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.5",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.8",
|
||||
"@ammarahmed/react-native-upload": "^6.29.0",
|
||||
"@ammarahmed/react-native-upload": "^6.31.0",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.2.1",
|
||||
@@ -100,6 +100,8 @@
|
||||
"react-native-mmkv-storage": "^12.0.1",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-nitro-cloud-uploader": "^1.0.9",
|
||||
"react-native-nitro-modules": "^0.32.0",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation-locker": "^1.7.0",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
@@ -465,20 +467,22 @@
|
||||
},
|
||||
"../../servers/themes": {
|
||||
"name": "@notesnook/themes-server",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@orama/orama": "^1.0.8",
|
||||
"@sagi.io/workers-kv": "^0.0.15",
|
||||
"@trpc/server": "10.45.2",
|
||||
"async-mutex": "0.5.0",
|
||||
"cors": "^2.8.5",
|
||||
"react": "18.3.1",
|
||||
"util": "^0.12.5",
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@types/cors": "^2.8.13",
|
||||
"react": "18.3.1",
|
||||
"esbuild": "0.21.5",
|
||||
"vite-node": "2.1.8"
|
||||
}
|
||||
},
|
||||
@@ -531,9 +535,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-upload": {
|
||||
"version": "6.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.29.0.tgz",
|
||||
"integrity": "sha512-k2FJAVR4Nohx4VhfgHuXzLXQpgEnco/PAv9R080X7oY3O12DTgY0ruDvOFwvQGFW0PDIHlXfLfUiMq2kZc/k1A==",
|
||||
"version": "6.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.31.0.tgz",
|
||||
"integrity": "sha512-GwMDd2IND3nuS4l13pQPP8zA/GJoxQKz0pxCGUoWj65fbPuZPnuUA2Rpf4jZzLlgY62vdjI/TqmX+rPiwwCceQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@@ -17714,6 +17718,30 @@
|
||||
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-nitro-cloud-uploader": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native-nitro-cloud-uploader/-/react-native-nitro-cloud-uploader-1.0.9.tgz",
|
||||
"integrity": "sha512-J96JC/yp0DWeyS512zQFYbw2KGiengWs0IAGGoV6NCAystluBJlVO4WqgiCiy/nd3bFidy/+uteC+P5h28j0vw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"example"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-nitro-modules": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-nitro-modules": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.32.0.tgz",
|
||||
"integrity": "sha512-nLDsmi/H9/cLcYxArYxcTDL641JiWnAH3u2LmsFETgyqSEHvukswDTdmX6AHBRIAwXu/iUDppZyjsaRpTG6WMg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-notification-sounds": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.5",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.8",
|
||||
"@ammarahmed/react-native-upload": "^6.29.0",
|
||||
"@ammarahmed/react-native-upload": "^6.31.0",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.2.1",
|
||||
@@ -116,6 +116,8 @@
|
||||
"react-native-mmkv-storage": "^12.0.1",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-nitro-cloud-uploader": "^1.0.9",
|
||||
"react-native-nitro-modules": "^0.32.0",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation-locker": "^1.7.0",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
index 9ea903a..e023649 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<!-- Required for background uploads -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
|
||||
<!-- Keep CPU awake during uploads -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
index f93d4dc..f95a537 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
@@ -388,7 +388,7 @@ class NitroCloudUploader(
|
||||
)
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, 100, "Upload complete!", isComplete = true)
|
||||
+// showNotification(uploadId, 100, "Upload complete!", isComplete = true)
|
||||
}
|
||||
|
||||
// ✅ Stop foreground service on success
|
||||
@@ -414,7 +414,7 @@ class NitroCloudUploader(
|
||||
println("❌ Upload failed: ${e.message}")
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, -1, "Upload failed", isComplete = true)
|
||||
+// showNotification(uploadId, -1, "Upload failed", isComplete = true)
|
||||
}
|
||||
|
||||
// ✅ Stop foreground service on failure
|
||||
@@ -458,7 +458,7 @@ class NitroCloudUploader(
|
||||
)
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, 0, "Starting upload...")
|
||||
+// showNotification(uploadId, 0, "Starting upload...")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -672,7 +672,7 @@ class NitroCloudUploader(
|
||||
|
||||
if (showNotification) {
|
||||
val progressPercent = (progress * 100).toInt()
|
||||
- showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
|
||||
+// showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
|
||||
|
||||
// ✅ Update foreground service notification
|
||||
val ctx = appContext // Cache to avoid smart cast issues
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
index 466c212..e261329 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
@@ -134,7 +134,7 @@ class UploadForegroundService : Service() {
|
||||
startForeground(
|
||||
NOTIFICATION_ID,
|
||||
notification,
|
||||
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
@@ -165,6 +165,7 @@ class UploadForegroundService : Service() {
|
||||
|
||||
isServiceStarted = false
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
+ notificationManager.cancel(NOTIFICATION_ID);
|
||||
stopSelf()
|
||||
println("✅ Foreground service stopped")
|
||||
}
|
||||
@@ -201,7 +202,7 @@ class UploadForegroundService : Service() {
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
- .setContentTitle("Cloud Uploader")
|
||||
+ .setContentTitle("Uploading file")
|
||||
.setContentText(message)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
Reference in New Issue
Block a user