mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
181 Commits
v2.4.10
...
2.5.2-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e95960b45 | ||
|
|
131ecfee89 | ||
|
|
dfccd640b0 | ||
|
|
14d27bb277 | ||
|
|
dd470297c3 | ||
|
|
1e690664f1 | ||
|
|
d68eaa7a41 | ||
|
|
791119fcbc | ||
|
|
87b82865b2 | ||
|
|
ad145c3499 | ||
|
|
5a228ce5fe | ||
|
|
8cb3cb6b19 | ||
|
|
94cc3a6853 | ||
|
|
163dfac5d7 | ||
|
|
efd0d6c2c3 | ||
|
|
742dc56f3c | ||
|
|
19f2d8b0a7 | ||
|
|
8c9d98ab88 | ||
|
|
75ae5d9603 | ||
|
|
8a2453429d | ||
|
|
b54ca08fc8 | ||
|
|
2be801ba3b | ||
|
|
f37cd3fe63 | ||
|
|
1224e48bc8 | ||
|
|
780bebe141 | ||
|
|
f707b43efb | ||
|
|
92e9cd740f | ||
|
|
20bef5e70c | ||
|
|
c9b45875a0 | ||
|
|
f180b79a3e | ||
|
|
438a3c2686 | ||
|
|
64d23d3de0 | ||
|
|
e251c6f71c | ||
|
|
40cb6ae8bf | ||
|
|
06aaa7e2c2 | ||
|
|
ab29a01649 | ||
|
|
49e7bdf202 | ||
|
|
2e0f9d8524 | ||
|
|
092b26be57 | ||
|
|
3525feffcf | ||
|
|
6cb5a5e473 | ||
|
|
0992d4eeab | ||
|
|
828cd6be35 | ||
|
|
1b1a962ec2 | ||
|
|
0a88502e59 | ||
|
|
50ff39845a | ||
|
|
1cb34d0783 | ||
|
|
bbb43bc8b4 | ||
|
|
1521a7d120 | ||
|
|
3b6a2cb9de | ||
|
|
0deaed4ccd | ||
|
|
8ebb58b70c | ||
|
|
f691d113d2 | ||
|
|
a57eaf9be6 | ||
|
|
c5a264270c | ||
|
|
e75c62e6e1 | ||
|
|
6c3583c9e5 | ||
|
|
58ba453f57 | ||
|
|
7f5d5fcb82 | ||
|
|
e218194402 | ||
|
|
c36f532bbb | ||
|
|
b3b7bd44ef | ||
|
|
c85242a610 | ||
|
|
58f7a206d9 | ||
|
|
25bf4fee82 | ||
|
|
c2ab5ded47 | ||
|
|
ad6a0ebfab | ||
|
|
cb9e6578c1 | ||
|
|
17a1475ded | ||
|
|
2cee5a4d89 | ||
|
|
337f88c373 | ||
|
|
53d5073690 | ||
|
|
9cc9660b48 | ||
|
|
e6162b2580 | ||
|
|
abc14d2d1f | ||
|
|
fa11b5fabb | ||
|
|
d904916863 | ||
|
|
0cfc2e1609 | ||
|
|
3742e923c9 | ||
|
|
792a254513 | ||
|
|
17928ba182 | ||
|
|
2da0f164c6 | ||
|
|
4eabe79e7e | ||
|
|
89dd8f5b6c | ||
|
|
b4d3662897 | ||
|
|
675e1e2c6f | ||
|
|
0f7f3bae7e | ||
|
|
c917d95a7b | ||
|
|
3ae9001504 | ||
|
|
cc97b8beed | ||
|
|
954bdebcef | ||
|
|
04b62f4e53 | ||
|
|
a2dcb67b36 | ||
|
|
cc9141c981 | ||
|
|
85484b1762 | ||
|
|
1a0c94896a | ||
|
|
068a99463d | ||
|
|
b413315777 | ||
|
|
7227513783 | ||
|
|
083e4e1150 | ||
|
|
09098410a5 | ||
|
|
2c9f5df530 | ||
|
|
3ad346362b | ||
|
|
e176b1bf76 | ||
|
|
b368232598 | ||
|
|
bcd27e10c1 | ||
|
|
af9b9b7083 | ||
|
|
ea1565048a | ||
|
|
59b081704b | ||
|
|
2761222b36 | ||
|
|
571f15b556 | ||
|
|
283efa6e41 | ||
|
|
b3b1b7470e | ||
|
|
a9e59b17dc | ||
|
|
22d946b677 | ||
|
|
6a748c3f98 | ||
|
|
520296106a | ||
|
|
3b727e6f64 | ||
|
|
b77711a53d | ||
|
|
aebb2d414b | ||
|
|
e0e505371c | ||
|
|
b9040a9358 | ||
|
|
68f10ce308 | ||
|
|
8a475f654f | ||
|
|
1043b7df55 | ||
|
|
8383e09243 | ||
|
|
df58a6cfc4 | ||
|
|
a9df577478 | ||
|
|
e021d93893 | ||
|
|
24e13f488e | ||
|
|
6f88b70937 | ||
|
|
a90f957aa4 | ||
|
|
748953b3eb | ||
|
|
874c892986 | ||
|
|
ab678fa3a9 | ||
|
|
c2351ab2d3 | ||
|
|
3fd3e02256 | ||
|
|
d2fd26abd5 | ||
|
|
dd84b9ca2f | ||
|
|
269f083dce | ||
|
|
e18867d3f1 | ||
|
|
e221882f8e | ||
|
|
153ee4bce3 | ||
|
|
a2ecdab61c | ||
|
|
eb089e6211 | ||
|
|
1981c3a2f8 | ||
|
|
68b3ebe318 | ||
|
|
8a0b6264d2 | ||
|
|
239174c3e7 | ||
|
|
db1503d828 | ||
|
|
7b1cbba9e7 | ||
|
|
2c3c193629 | ||
|
|
899e56e9cd | ||
|
|
9c9582ae12 | ||
|
|
b918667b63 | ||
|
|
878c901e06 | ||
|
|
fdd3897a60 | ||
|
|
c958657b01 | ||
|
|
e7175f48a0 | ||
|
|
4e1862d653 | ||
|
|
1d5ac37222 | ||
|
|
32d027fff6 | ||
|
|
9434993373 | ||
|
|
2bd0f1a304 | ||
|
|
7f873f240e | ||
|
|
a0229729b7 | ||
|
|
dd368a4170 | ||
|
|
6d3e985c9d | ||
|
|
15858db21b | ||
|
|
b69a94a96f | ||
|
|
73b965eb7c | ||
|
|
5738f48360 | ||
|
|
de74b8c2cd | ||
|
|
2d07d79057 | ||
|
|
5f6a38c8b1 | ||
|
|
3496e6bfa9 | ||
|
|
fc5b62ba61 | ||
|
|
9d0a206240 | ||
|
|
ee6bbca18c | ||
|
|
e6d5472514 | ||
|
|
1e5f13087b |
@@ -20,6 +20,7 @@ const SCOPES = [
|
||||
"logger",
|
||||
"theme",
|
||||
"core",
|
||||
"fs",
|
||||
"clipper",
|
||||
"config",
|
||||
"ci",
|
||||
|
||||
@@ -31,14 +31,6 @@ import { TipManager } from "./services/tip-manager";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
import { View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
NetInfo.configure({
|
||||
reachabilityUrl: "https://notesnook.com",
|
||||
reachabilityTest: (response) => {
|
||||
if (!response) return false;
|
||||
return response?.status >= 200 && response?.status < 300;
|
||||
}
|
||||
});
|
||||
|
||||
SettingsService.init();
|
||||
SettingsService.checkOrientation();
|
||||
|
||||
@@ -22,10 +22,17 @@ import "react-native-get-random-values";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import { generateSecureRandom } from "react-native-securerandom";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { MMKV } from "./mmkv";
|
||||
|
||||
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
|
||||
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
|
||||
const IOS_KEYCHAIN_UPGRAGE_KEY = "keychain-ios:upgraded";
|
||||
|
||||
const KEYSTORE_CONFIG = Platform.select({
|
||||
ios: {
|
||||
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
||||
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
||||
accessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
|
||||
service: IOS_KEYCHAIN_SERVICE_NAME
|
||||
},
|
||||
android: {}
|
||||
});
|
||||
@@ -39,12 +46,27 @@ export async function deriveCryptoKey(name, data) {
|
||||
credentials.key,
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
|
||||
return credentials.key;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function upgradeIOSKeychain(username, password) {
|
||||
if (Platform.OS !== "ios") return;
|
||||
if (!MMKV.getBool(IOS_KEYCHAIN_UPGRAGE_KEY)) {
|
||||
await Keychain.setInternetCredentials(
|
||||
"notesnook",
|
||||
username,
|
||||
password,
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
console.log("IOS KEYCHAIN MIGRATION COMPLETED!");
|
||||
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCryptoKey(_name) {
|
||||
try {
|
||||
if (await Keychain.hasInternetCredentials("notesnook")) {
|
||||
@@ -52,6 +74,9 @@ export async function getCryptoKey(_name) {
|
||||
"notesnook",
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
// upgrades ios keychain to use accessGroups
|
||||
// so we have access to keychain in share extension.
|
||||
await upgradeIOSKeychain(credentials.username, credentials.password);
|
||||
return credentials.password;
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import {
|
||||
decrypt,
|
||||
deriveCryptoKey,
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Dimensions, Platform } from "react-native";
|
||||
import ImageResizer from "@bam.tech/react-native-image-resizer";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
/**
|
||||
* Scale down & compress images to screen width
|
||||
* for loading in editor.
|
||||
|
||||
@@ -17,25 +17,169 @@ 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 Sodium from "@ammarahmed/react-native-sodium";
|
||||
import {
|
||||
getFileNameWithExtension,
|
||||
isImage,
|
||||
isDocument
|
||||
} from "@notesnook/core/utils/filename";
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { subscribe, zip } from "react-native-zip-archive";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { presentSheet, ToastEvent } from "../../services/event-manager";
|
||||
import { ToastEvent, presentSheet } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir } from "./utils";
|
||||
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
|
||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||
|
||||
export const FileDownloadStatus = {
|
||||
Success: 1,
|
||||
Fail: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Download all user's attachments
|
||||
* @returns
|
||||
*/
|
||||
export function downloadAllAttachments() {
|
||||
const attachments = db.attachments.all;
|
||||
return downloadAttachments(attachments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads provided attachments to a .zip file
|
||||
* on user's device.
|
||||
* @param attachments
|
||||
* @param onProgress
|
||||
* @returns
|
||||
*/
|
||||
export async function downloadAttachments(
|
||||
attachments,
|
||||
onProgress,
|
||||
canceled,
|
||||
groupId
|
||||
) {
|
||||
if (!attachments || !attachments.length) return;
|
||||
const result = new Map();
|
||||
|
||||
let outputFolder;
|
||||
if (Platform.OS === "android") {
|
||||
// Ask the user to select a directory to store the file
|
||||
let file = await ScopedStorage.openDocumentTree(true);
|
||||
outputFolder = file.uri;
|
||||
if (!outputFolder) return;
|
||||
} else {
|
||||
outputFolder = await Storage.checkAndCreateDir("/downloads/");
|
||||
}
|
||||
|
||||
// Create the folder to zip;
|
||||
const zipSourceFolder = `${cacheDir}/notesnook-attachments`;
|
||||
const zipOutputFile =
|
||||
Platform.OS === "ios"
|
||||
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
|
||||
: `${cacheDir}/notesnook-attachments.zip`;
|
||||
if (await RNFetchBlob.fs.exists(zipSourceFolder))
|
||||
await RNFetchBlob.fs.unlink(zipSourceFolder);
|
||||
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
||||
|
||||
for (let i = 0; i < attachments.length; i++) {
|
||||
let attachment = attachments[i];
|
||||
const hash = attachment.metadata.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`
|
||||
);
|
||||
// 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 (!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.metadata.filename}`;
|
||||
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
|
||||
result.set(hash, {
|
||||
filename: attachment.metadata.filename,
|
||||
status: FileDownloadStatus.Success
|
||||
});
|
||||
} catch (e) {
|
||||
result.set(hash, {
|
||||
filename: attachment.metadata.filename,
|
||||
status: FileDownloadStatus.Fail,
|
||||
reason: e
|
||||
});
|
||||
console.log("Error downloading attachment", 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`
|
||||
);
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
onProgress?.(1, `Done`);
|
||||
releasePermissions(outputFolder);
|
||||
} catch (e) {
|
||||
releasePermissions(outputFolder);
|
||||
sub?.remove();
|
||||
console.log("Error zipping attachments", e);
|
||||
}
|
||||
// Remove source & zip file from cache.
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
||||
if (Platform.OS === "android") {
|
||||
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function downloadAttachment(
|
||||
hash,
|
||||
global = true,
|
||||
options = {
|
||||
silent: false,
|
||||
cache: false
|
||||
cache: false,
|
||||
throwError: false,
|
||||
groupId: undefined
|
||||
}
|
||||
) {
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
@@ -55,8 +199,13 @@ export default async function downloadAttachment(
|
||||
}
|
||||
|
||||
try {
|
||||
await db.fs.downloadFile(
|
||||
console.log(
|
||||
"starting download attachment",
|
||||
attachment.metadata.hash,
|
||||
options.groupId
|
||||
);
|
||||
await db.fs.downloadFile(
|
||||
options.groupId || attachment.metadata.hash,
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (
|
||||
@@ -99,7 +248,8 @@ export default async function downloadAttachment(
|
||||
|
||||
if (
|
||||
attachment.dateUploaded &&
|
||||
!attachment.metadata?.type?.startsWith("image")
|
||||
!isImage(attachment.metadata?.type) &&
|
||||
!isDocument(attachment.metadata?.type)
|
||||
) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
@@ -131,5 +281,8 @@ export default async function downloadAttachment(
|
||||
.catch(console.log);
|
||||
}
|
||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
||||
if (options.throwError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import hosts from "@notesnook/core/utils/constants";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
|
||||
@@ -19,18 +19,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
import { db } from "../database";
|
||||
import { compressToBase64 } from "./compress";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
let exists =
|
||||
(await RNFetchBlob.fs.exists(path)) ||
|
||||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
|
||||
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const isPng = /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
@@ -39,7 +50,8 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
key,
|
||||
{
|
||||
...cipherData,
|
||||
hash: filename
|
||||
hash: filename,
|
||||
appGroupId: IOS_APPGROUPID
|
||||
},
|
||||
cipherData.outputType === "base64"
|
||||
? isPng || isJpeg
|
||||
|
||||
@@ -17,10 +17,13 @@ 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 RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir } from "./utils";
|
||||
import { isImage, isDocument } from "@notesnook/core/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
@@ -35,9 +38,19 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
||||
const uploadUrl = await res.text();
|
||||
if (!uploadUrl) throw new Error("Unable to resolve upload url");
|
||||
let uploadFilePath = `${cacheDir}/${filename}`;
|
||||
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
uploadFilePath = appGroupPath;
|
||||
}
|
||||
let request = RNFetchBlob.config({
|
||||
IOSBackgroundTask: true
|
||||
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
|
||||
})
|
||||
.fetch(
|
||||
"PUT",
|
||||
@@ -45,7 +58,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
{
|
||||
"content-type": ""
|
||||
},
|
||||
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
|
||||
RNFetchBlob.wrap(uploadFilePath)
|
||||
)
|
||||
.uploadProgress((sent, total) => {
|
||||
useAttachmentStore
|
||||
@@ -63,7 +76,10 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
if (result) {
|
||||
let attachment = db.attachments.attachment(filename);
|
||||
if (!attachment) return result;
|
||||
if (!attachment.metadata.type.startsWith("image/")) {
|
||||
if (
|
||||
!isImage(attachment.metadata.type) &&
|
||||
!isDocument(attachment.metadata?.type)
|
||||
) {
|
||||
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ 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 RNFetchBlob from "rn-fetch-blob";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
|
||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
|
||||
@@ -62,3 +64,22 @@ export function cancelable(operation) {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function copyFileAsync(source, dest) {
|
||||
return new Promise((resolve) => {
|
||||
ScopedStorage.copyFile(source, dest, (e, r) => {
|
||||
console.log(e, r);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function releasePermissions(path) {
|
||||
if (Platform.OS === "ios") return;
|
||||
const uris = await ScopedStorage.getPersistedUriPermissions();
|
||||
for (let uri of uris) {
|
||||
if (path.startsWith(uri)) {
|
||||
await ScopedStorage.releasePersistableUriPermission(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,15 +102,21 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
if (res.failed) {
|
||||
db.attachments.markAsFailed(attachment.id, res.failed);
|
||||
setFailed(res.failed);
|
||||
ToastEvent.show({
|
||||
heading: "File check failed with error: " + res.failed,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
} else {
|
||||
setFailed(null);
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
ToastEvent.show({
|
||||
heading: "File check passed",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
ToastEvent.show({
|
||||
heading: "File check passed",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
setAttachments([...db.attachments.all]);
|
||||
setLoading({
|
||||
name: null
|
||||
|
||||
@@ -22,7 +22,6 @@ 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 { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { formatBytes } from "../../utils";
|
||||
import { SIZE } from "../../utils/size";
|
||||
@@ -36,24 +35,27 @@ function getFileExtension(filename) {
|
||||
var ext = /^.+\.([^.]+)$/.exec(filename);
|
||||
return ext == null ? "" : ext[1];
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {any} param0
|
||||
* @returns
|
||||
*/
|
||||
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
|
||||
export const AttachmentItem = ({
|
||||
attachment,
|
||||
encryption,
|
||||
setAttachments,
|
||||
pressable = true,
|
||||
hideWhenNotDownloading
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||
attachment,
|
||||
encryption
|
||||
);
|
||||
const encryptionProgress = useAttachmentStore(
|
||||
(state) => state.encryptionProgress
|
||||
);
|
||||
|
||||
const onPress = () => {
|
||||
if (!pressable) return;
|
||||
Actions.present(attachment, setAttachments, attachment.metadata.hash);
|
||||
};
|
||||
return (
|
||||
|
||||
return hideWhenNotDownloading &&
|
||||
(!currentProgress || !currentProgress.value) ? null : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={onPress}
|
||||
@@ -116,22 +118,22 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
{attachment.metadata.filename}
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
{formatBytes(attachment.length)}{" "}
|
||||
{currentProgress?.type
|
||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||
: ""}
|
||||
</Paragraph>
|
||||
{!hideWhenNotDownloading ? (
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
{formatBytes(attachment.length)}{" "}
|
||||
{currentProgress?.type
|
||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||
: ""}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{currentProgress ||
|
||||
(encryptionProgress && encryptionProgress !== "0.00") ||
|
||||
encryption ? (
|
||||
{currentProgress ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => {
|
||||
if (encryption) return;
|
||||
if (encryption || !pressable) return;
|
||||
db.fs.cancel(attachment.metadata.hash);
|
||||
setCurrentProgress(null);
|
||||
}}
|
||||
@@ -144,13 +146,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
>
|
||||
<ProgressCircleComponent
|
||||
size={SIZE.xxl}
|
||||
progress={
|
||||
encryptionProgress
|
||||
? encryptionProgress
|
||||
: currentProgress?.value
|
||||
? currentProgress?.value / 100
|
||||
: 0
|
||||
}
|
||||
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
|
||||
showsText
|
||||
textStyle={{
|
||||
fontSize: 10
|
||||
|
||||
273
apps/mobile/app/components/attachments/download-attachments.js
Normal file
273
apps/mobile/app/components/attachments/download-attachments.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 { 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 { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
|
||||
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
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
|
||||
});
|
||||
setDownloading(true);
|
||||
canceled.current = false;
|
||||
groupId.current = Date.now().toString();
|
||||
const result = await downloadAttachments(
|
||||
attachments,
|
||||
(progress, statusText) => setProgress({ value: progress, statusText }),
|
||||
canceled,
|
||||
groupId.current
|
||||
);
|
||||
if (canceled.current) return;
|
||||
setResult(result || new Map());
|
||||
setDownloading(false);
|
||||
update({
|
||||
disableClosing: false
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = async () => {
|
||||
update({
|
||||
disableClosing: false
|
||||
});
|
||||
canceled.current = true;
|
||||
console.log(groupId.current, "canceling groupId downloads");
|
||||
await db.fs.cancel(groupId.current);
|
||||
setDownloading(false);
|
||||
groupId.current = null;
|
||||
};
|
||||
|
||||
const successResults = () => {
|
||||
const results = [];
|
||||
for (let [key, value] of result.entries()) {
|
||||
if (value.status === 1) results.push(db.attachments.attachment(key));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const failedResults = () => {
|
||||
const results = [];
|
||||
for (let [key, value] of result.entries()) {
|
||||
if (value.status === 0) results.push(db.attachments.attachment(key));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
function getResultText() {
|
||||
const downloadedAttachmentsCount =
|
||||
attachments.length - failedResults().length;
|
||||
if (downloadedAttachmentsCount === 0)
|
||||
return "Failed to download all attachments";
|
||||
return `Successfully downloaded ${downloadedAttachmentsCount}/${
|
||||
attachments.length
|
||||
} attachments as a zip file at ${
|
||||
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
|
||||
}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Heading>
|
||||
{downloading
|
||||
? "Downloading attachments"
|
||||
: result?.size
|
||||
? "Downloaded attachments"
|
||||
: "Download attachments"}
|
||||
</Heading>
|
||||
|
||||
{downloading ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{progress.statusText}
|
||||
</Paragraph>
|
||||
) : result?.size ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{getResultText()}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Are you sure you want to download all attachments
|
||||
{isNote ? " of this note?" : "?"}
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
{downloading ? (
|
||||
<View
|
||||
style={{
|
||||
width: 200,
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
height={5}
|
||||
width={null}
|
||||
animated={true}
|
||||
useNativeDriver
|
||||
progress={progress.value ? progress.value / attachments.length : 0}
|
||||
unfilledColor={colors.nav}
|
||||
color={colors.accent}
|
||||
borderWidth={0}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
style={{
|
||||
maxHeight: 300,
|
||||
width: "100%",
|
||||
minHeight: 60,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 5,
|
||||
marginVertical: 12
|
||||
}}
|
||||
data={downloading ? attachments : []}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 60
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon}>No downloads in progress.</Paragraph>
|
||||
</View>
|
||||
}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => {
|
||||
return (
|
||||
<AttachmentItem
|
||||
attachment={item}
|
||||
setAttachments={() => {}}
|
||||
pressable={false}
|
||||
hideWhenNotDownloading={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{result?.size ? (
|
||||
<Button
|
||||
style={{
|
||||
width: 250,
|
||||
borderRadius: 100,
|
||||
marginTop: 20
|
||||
}}
|
||||
onPress={close}
|
||||
type="accent"
|
||||
title="Done"
|
||||
/>
|
||||
) : !downloading ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
marginTop: 20
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 100,
|
||||
marginRight: 5
|
||||
}}
|
||||
onPress={close}
|
||||
type="grayBg"
|
||||
title="No"
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 100,
|
||||
marginLeft: 5
|
||||
}}
|
||||
onPress={onDownload}
|
||||
type="accent"
|
||||
title="Yes"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Button
|
||||
style={{
|
||||
width: 250,
|
||||
borderRadius: 100,
|
||||
marginTop: 20
|
||||
}}
|
||||
onPress={cancel}
|
||||
type="error"
|
||||
title="Cancel"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
DownloadAttachments.present = (context, attachments, isNote) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref, close, update) => (
|
||||
<DownloadAttachments
|
||||
close={close}
|
||||
attachments={attachments}
|
||||
isNote={isNote}
|
||||
update={update}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
export default DownloadAttachments;
|
||||
@@ -18,44 +18,60 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import DownloadAttachments from "./download-attachments";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
isAudio,
|
||||
isDocument,
|
||||
isImage,
|
||||
isVideo
|
||||
} from "@notesnook/core/utils/filename";
|
||||
|
||||
export const AttachmentDialog = ({ data }) => {
|
||||
export const AttachmentDialog = ({ note }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [note, setNote] = useState(data);
|
||||
|
||||
const [attachments, setAttachments] = useState(
|
||||
data
|
||||
? db.attachments.ofNote(data.id, "all")
|
||||
note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])]
|
||||
);
|
||||
|
||||
const attachmentSearchValue = useRef();
|
||||
const searchTimer = useRef();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [currentFilter, setCurrentFilter] = useState("all");
|
||||
|
||||
const onChangeText = (text) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
|
||||
attachmentSearchValue.current = text;
|
||||
if (
|
||||
!attachmentSearchValue.current ||
|
||||
attachmentSearchValue.current === ""
|
||||
) {
|
||||
setAttachments([...db.attachments.all]);
|
||||
setAttachments([...attachments]);
|
||||
}
|
||||
clearTimeout(searchTimer.current);
|
||||
searchTimer.current = setTimeout(() => {
|
||||
let results = db.lookup.attachments(
|
||||
db.attachments.all,
|
||||
attachments,
|
||||
attachmentSearchValue.current
|
||||
);
|
||||
if (results.length === 0) return;
|
||||
@@ -67,6 +83,77 @@ export const AttachmentDialog = ({ data }) => {
|
||||
<AttachmentItem setAttachments={setAttachments} attachment={item} />
|
||||
);
|
||||
|
||||
const onCheck = async () => {
|
||||
setLoading(true);
|
||||
const checkedAttachments = [];
|
||||
for (let attachment of attachments) {
|
||||
let result = await filesystem.checkAttachment(attachment.metadata.hash);
|
||||
if (result.failed) {
|
||||
await db.attachments.markAsFailed(
|
||||
attachment.metadata.hash,
|
||||
result.failed
|
||||
);
|
||||
} else {
|
||||
await db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
checkedAttachments.push(
|
||||
db.attachments.attachment(attachment.metadata.hash)
|
||||
);
|
||||
setAttachments([...checkedAttachments]);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const attachmentTypes = [
|
||||
{
|
||||
title: "All",
|
||||
filterBy: "all"
|
||||
},
|
||||
{
|
||||
title: "Images",
|
||||
filterBy: "images"
|
||||
},
|
||||
{
|
||||
title: "Documents",
|
||||
filterBy: "documents"
|
||||
},
|
||||
{
|
||||
title: "Video",
|
||||
filterBy: "video"
|
||||
},
|
||||
{
|
||||
title: "Audio",
|
||||
filterBy: "audio"
|
||||
}
|
||||
];
|
||||
|
||||
const filterAttachments = (type) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
isDocument;
|
||||
switch (type) {
|
||||
case "all":
|
||||
return attachments;
|
||||
case "images":
|
||||
return attachments.filter((attachment) =>
|
||||
isImage(attachment.metadata.type)
|
||||
);
|
||||
case "video":
|
||||
return attachments.filter((attachment) =>
|
||||
isVideo(attachment.metadata.type)
|
||||
);
|
||||
case "audio":
|
||||
return attachments.filter((attachment) =>
|
||||
isAudio(attachment.metadata.type)
|
||||
);
|
||||
case "documents":
|
||||
return attachments.filter((attachment) =>
|
||||
isDocument(attachment.metadata.type)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -75,43 +162,71 @@ export const AttachmentDialog = ({ data }) => {
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
title={note ? "Attachments" : "Manage attachments"}
|
||||
paragraph="Tap on an attachment to view properties"
|
||||
button={{
|
||||
title: "Check all",
|
||||
type: "grayAccent",
|
||||
loading: loading,
|
||||
onPress: async () => {
|
||||
setLoading(true);
|
||||
for (let attachment of attachments) {
|
||||
let result = await filesystem.checkAttachment(
|
||||
attachment.metadata.hash
|
||||
<SheetProvider context="attachments-list" />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading>Attachments</Heading>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
style={{
|
||||
height: 40,
|
||||
width: 40,
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
name="check-all"
|
||||
customStyle={{
|
||||
height: 40,
|
||||
width: 40,
|
||||
marginRight: 10
|
||||
}}
|
||||
color={colors.pri}
|
||||
size={SIZE.lg}
|
||||
onPress={onCheck}
|
||||
/>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
name="download"
|
||||
customStyle={{
|
||||
height: 40,
|
||||
width: 40
|
||||
}}
|
||||
color={colors.pri}
|
||||
onPress={() => {
|
||||
DownloadAttachments.present(
|
||||
"attachments-list",
|
||||
attachments,
|
||||
!!note
|
||||
);
|
||||
if (result.failed) {
|
||||
db.attachments.markAsFailed(
|
||||
attachment.metadata.hash,
|
||||
result.failed
|
||||
);
|
||||
} else {
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
setAttachments([...db.attachments.all]);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Seperator />
|
||||
<Input
|
||||
placeholder="Filter attachments by filename, type or hash"
|
||||
onChangeText={onChangeText}
|
||||
onSubmit={() => {
|
||||
onChangeText(attachmentSearchValue.current);
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
{!note ? (
|
||||
<Input
|
||||
placeholder="Filter attachments by filename, type or hash"
|
||||
onChangeText={onChangeText}
|
||||
onSubmit={() => {
|
||||
onChangeText(attachmentSearchValue.current);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
keyboardDismissMode="none"
|
||||
@@ -119,6 +234,45 @@ export const AttachmentDialog = ({ data }) => {
|
||||
maxToRenderPerBatch={10}
|
||||
initialNumToRender={10}
|
||||
windowSize={5}
|
||||
stickyHeaderIndices={[0]}
|
||||
ListHeaderComponent={
|
||||
<ScrollView
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 50,
|
||||
flexDirection: "row",
|
||||
backgroundColor: colors.bg
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
minWidth: "100%"
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{attachmentTypes.map((item) => (
|
||||
<Button
|
||||
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
|
||||
key={item.title}
|
||||
title={
|
||||
item.title +
|
||||
` (${filterAttachments(item.filterBy)?.length || 0})`
|
||||
}
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
borderBottomWidth: 1,
|
||||
flexGrow: 1,
|
||||
borderBottomColor:
|
||||
currentFilter !== item.filterBy
|
||||
? "transparent"
|
||||
: colors.accent
|
||||
}}
|
||||
onPress={() => {
|
||||
setCurrentFilter(item.filterBy);
|
||||
setAttachments(filterAttachments(item.filterBy));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
@@ -162,6 +316,6 @@ export const AttachmentDialog = ({ data }) => {
|
||||
|
||||
AttachmentDialog.present = (note) => {
|
||||
presentSheet({
|
||||
component: () => <AttachmentDialog data={note} />
|
||||
component: () => <AttachmentDialog note={note} />
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
@@ -75,7 +75,7 @@ const AuthModal = () => {
|
||||
return !visible ? null : (
|
||||
<BaseDialog
|
||||
overlayOpacity={0}
|
||||
statusBarTranslucent={false}
|
||||
statusBarTranslucent={true}
|
||||
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
|
||||
visible={true}
|
||||
onClose={close}
|
||||
@@ -84,6 +84,7 @@ const AuthModal = () => {
|
||||
background={colors.bg}
|
||||
transparent={false}
|
||||
animated={false}
|
||||
avoidKeyboardResize
|
||||
>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
<Signup
|
||||
@@ -101,7 +102,7 @@ const AuthModal = () => {
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: Platform.OS === "ios" ? insets.top : 0,
|
||||
top: insets.top,
|
||||
zIndex: 999,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
@@ -126,11 +127,11 @@ const AuthModal = () => {
|
||||
|
||||
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
|
||||
<Button
|
||||
title="Skip for now"
|
||||
title="Skip"
|
||||
onPress={() => {
|
||||
hideAuth();
|
||||
}}
|
||||
iconSize={20}
|
||||
iconSize={16}
|
||||
type="gray"
|
||||
iconPosition="right"
|
||||
icon="chevron-right"
|
||||
|
||||
@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
@@ -30,13 +29,12 @@ import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { SVG } from "./background";
|
||||
import { hideAuth } from "./common";
|
||||
import { ForgotPassword } from "./forgot-password";
|
||||
import { useLogin } from "./use-login";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
const LoginSteps = {
|
||||
emailAuth: 1,
|
||||
@@ -64,6 +62,7 @@ export const Login = ({ changeMode }) => {
|
||||
await sleep(500);
|
||||
Progress.present();
|
||||
});
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
useEffect(() => {
|
||||
async () => {
|
||||
@@ -82,62 +81,70 @@ export const Login = ({ changeMode }) => {
|
||||
<>
|
||||
<ForgotPassword />
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 10,
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
height: "100%",
|
||||
minHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<SvgView
|
||||
src={SVG(colors.night ? colors.icon : "black")}
|
||||
height={700}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: 15
|
||||
flexGrow: 0.8,
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.nav,
|
||||
borderBottomWidth: 1,
|
||||
marginBottom: 12,
|
||||
borderBottomColor: colors.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: "center"
|
||||
marginBottom: 25,
|
||||
marginTop: 10
|
||||
}}
|
||||
size={30}
|
||||
color={colors.heading}
|
||||
extraBold
|
||||
size={SIZE.xxl}
|
||||
>
|
||||
Welcome back!
|
||||
Login to your {"\n"}account
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: "underline",
|
||||
textAlign: "center",
|
||||
marginTop: 5
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(1);
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{"Don't have an account? Sign up"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab
|
||||
@@ -147,10 +154,10 @@ export const Login = ({ changeMode }) => {
|
||||
: focused
|
||||
? "100%"
|
||||
: "99.9%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1,
|
||||
alignSelf: "center"
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 20,
|
||||
flexGrow: 1
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -217,28 +224,22 @@ export const Login = ({ changeMode }) => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginTop: 25,
|
||||
alignSelf: "center"
|
||||
marginTop: 25
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
width: 250,
|
||||
borderRadius: 100
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
login();
|
||||
}}
|
||||
style={{
|
||||
width: 250,
|
||||
borderRadius: 100
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
type="accent"
|
||||
title={
|
||||
loading
|
||||
? null
|
||||
: step === LoginSteps.emailAuth
|
||||
? "Login"
|
||||
: "Continue"
|
||||
}
|
||||
title={!loading ? "Continue" : null}
|
||||
/>
|
||||
|
||||
{step === LoginSteps.passwordAuth && (
|
||||
@@ -261,9 +262,34 @@ export const Login = ({ changeMode }) => {
|
||||
type="errorShade"
|
||||
/>
|
||||
)}
|
||||
|
||||
{!loading ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(1);
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: 12,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
|
||||
Don't have an account?{" "}
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
style={{ color: colors.accent }}
|
||||
>
|
||||
Sign up
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Dimensions, View } from "react-native";
|
||||
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
@@ -32,11 +31,10 @@ import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { SVG } from "./background";
|
||||
import { hideAuth } from "./common";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
export const Signup = ({ changeMode, trial }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -50,6 +48,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current || !confirmPassword.current) {
|
||||
@@ -96,69 +95,77 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 10,
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
height: "100%",
|
||||
minHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: "hidden"
|
||||
flexGrow: 1,
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.nav,
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%"
|
||||
}}
|
||||
>
|
||||
<SvgView
|
||||
src={SVG(colors.night ? colors.icon : "black")}
|
||||
height={700}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Heading
|
||||
extraBold
|
||||
style={{
|
||||
marginBottom: 25,
|
||||
marginTop: 10
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
>
|
||||
Create your {"\n"}account
|
||||
</Heading>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={30}
|
||||
color={colors.heading}
|
||||
>
|
||||
Create your account
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: "underline",
|
||||
textAlign: "center"
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(0);
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
Already have an account? Log in
|
||||
</Paragraph>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab ? "50%" : "100%",
|
||||
padding: 12,
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1,
|
||||
alignSelf: "center"
|
||||
alignSelf: "center",
|
||||
flexGrow: 0.5
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -217,37 +224,13 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
validationType="confirmPassword"
|
||||
customValidator={() => password.current}
|
||||
placeholder="Confirm password"
|
||||
marginBottom={5}
|
||||
marginBottom={12}
|
||||
onSubmit={signup}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 25,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
width: 250,
|
||||
borderRadius: 100
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
signup();
|
||||
}}
|
||||
type="accent"
|
||||
title={loading ? null : "Agree and continue"}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
alignSelf: "center",
|
||||
marginBottom: 20
|
||||
marginBottom: 25
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
@@ -263,7 +246,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
}}
|
||||
color={colors.accent}
|
||||
>
|
||||
terms of service{" "}
|
||||
Terms of Service{" "}
|
||||
</Paragraph>
|
||||
and{" "}
|
||||
<Paragraph
|
||||
@@ -276,11 +259,46 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
}}
|
||||
color={colors.accent}
|
||||
>
|
||||
privacy policy.
|
||||
</Paragraph>
|
||||
Privacy Policy.
|
||||
</Paragraph>{" "}
|
||||
You also agree to recieve marketing emails from us which you can
|
||||
opt-out of from app settings.
|
||||
</Paragraph>
|
||||
|
||||
<Button
|
||||
title={!loading ? "Continue" : null}
|
||||
type="accent"
|
||||
loading={loading}
|
||||
onPress={signup}
|
||||
fontSize={SIZE.md}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
width: 250,
|
||||
borderRadius: 100
|
||||
}}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (loading) return;
|
||||
changeMode(0);
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: 12,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
|
||||
Already have an account?{" "}
|
||||
<Paragraph size={SIZE.xs + 1} style={{ color: colors.accent }}>
|
||||
Login
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ import SheetProvider from "../sheet-provider";
|
||||
import RateAppSheet from "../sheets/rate-app";
|
||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||
import RestoreDataSheet from "../sheets/restore-data";
|
||||
import PDFPreview from "../dialogs/pdf-preview";
|
||||
|
||||
const DialogProvider = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -60,6 +61,7 @@ const DialogProvider = () => {
|
||||
{loading ? null : <Expiring />}
|
||||
<AnnouncementDialog />
|
||||
<SessionExpired />
|
||||
<PDFPreview />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -46,7 +46,8 @@ const BaseDialog = ({
|
||||
animated = true,
|
||||
bounce = true,
|
||||
closeOnTouch = true,
|
||||
useSafeArea = true
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false
|
||||
}) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
|
||||
@@ -94,7 +95,7 @@ const BaseDialog = ({
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
enabled={!floating && Platform.OS === "ios"}
|
||||
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
|
||||
behavior="padding"
|
||||
>
|
||||
<BouncingView
|
||||
|
||||
324
apps/mobile/app/components/dialogs/pdf-preview/index.js
Normal file
324
apps/mobile/app/components/dialogs/pdf-preview/index.js
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import {
|
||||
addOrientationListener,
|
||||
removeOrientationListener
|
||||
} from "react-native-orientation";
|
||||
import Pdf from "react-native-pdf";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../../common/filesystem/utils";
|
||||
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Dialog } from "../../dialog";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
|
||||
const WIN_WIDTH = Dimensions.get("window").width;
|
||||
const WIN_HEIGHT = Dimensions.get("window").height;
|
||||
|
||||
const attachmentSnapshotsKey = "___attachmentsnapshots";
|
||||
const usePDFSnapshot = (attachment) => {
|
||||
const snapshots = useRef(MMKV.getMap(attachmentSnapshotsKey) || {});
|
||||
const snapshot = useRef(snapshots[attachment?.id]);
|
||||
|
||||
function saveSnapshot(ss) {
|
||||
if (!attachment) return;
|
||||
snapshots.current[attachment.id] = ss;
|
||||
MMKV.setMap(attachmentSnapshotsKey, snapshots.current);
|
||||
snapshot.current = snapshots.current[attachment.id];
|
||||
}
|
||||
|
||||
return [snapshot, saveSnapshot];
|
||||
};
|
||||
|
||||
const PDFPreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [pdfSource, setPDFSource] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [width, setWidth] = useState(WIN_WIDTH);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [numPages, setNumPages] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const inputRef = useRef();
|
||||
const pdfRef = useRef();
|
||||
const [attachment, setAttachment] = useState(null);
|
||||
const [password, setPassword] = useState("");
|
||||
const [progress] = useAttachmentProgress(attachment);
|
||||
const [snapshot, saveSnapshot] = usePDFSnapshot(attachment);
|
||||
const snapshotValue = useRef(snapshot.current);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("PDFPreview", open);
|
||||
return () => {
|
||||
eUnSubscribeEvent("PDFPreview", open);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onOrientationChange = (o) => {
|
||||
if (o.includes("LANDSCAPE")) {
|
||||
setWidth(WIN_HEIGHT);
|
||||
} else {
|
||||
setWidth(WIN_WIDTH);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
addOrientationListener(onOrientationChange);
|
||||
return () => {
|
||||
removeOrientationListener(onOrientationChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = async (attachment) => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
setAttachment(attachment);
|
||||
let hash = attachment.metadata.hash;
|
||||
if (!hash) return;
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
});
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
snapshotValue.current = snapshot.current;
|
||||
setPDFSource("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setPDFSource(null);
|
||||
setVisible(false);
|
||||
setPassword("");
|
||||
};
|
||||
|
||||
const onError = async (error) => {
|
||||
if (error?.message === "Password required or incorrect password.") {
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
context: attachment?.metadata?.hash,
|
||||
input: true,
|
||||
inputPlaceholder: "Enter password",
|
||||
positiveText: "Unlock",
|
||||
title: "Decrypt",
|
||||
paragraph: "Please input password to view pdf.",
|
||||
positivePress: (value) => {
|
||||
setTimeout(() => {
|
||||
setPassword(value);
|
||||
});
|
||||
},
|
||||
onClose: () => {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
|
||||
<SheetProvider context={attachment?.metadata?.hash} />
|
||||
<Dialog context={attachment?.metadata?.hash} />
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "black"
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Animated.View
|
||||
exiting={FadeOut}
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
indeterminate={!progress}
|
||||
color={colors.accent}
|
||||
borderColor="transparent"
|
||||
progress={parseInt(progress?.value || "100") / 100}
|
||||
/>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
color={colors.light}
|
||||
>
|
||||
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
|
||||
... Please wait
|
||||
</Paragraph>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 50,
|
||||
marginTop: insets.top,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 12,
|
||||
paddingLeft: 6
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
name="arrow-left"
|
||||
onPress={close}
|
||||
customStyle={{
|
||||
marginRight: 12
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 12
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
color: colors.pri,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.nav,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "OpenSans-Regular"
|
||||
}}
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.light}>/{numPages}</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
name="download"
|
||||
onPress={() => {
|
||||
downloadAttachment(attachment.metadata.hash, false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{pdfSource ? (
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
entering={FadeIn}
|
||||
>
|
||||
<Pdf
|
||||
source={{
|
||||
uri: pdfSource
|
||||
}}
|
||||
ref={pdfRef}
|
||||
onLoadComplete={(numberOfPages) => {
|
||||
setNumPages(numberOfPages);
|
||||
}}
|
||||
onPageChanged={(page) => {
|
||||
setCurrentPage(page);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: page + ""
|
||||
});
|
||||
saveSnapshot({
|
||||
currentPage: page,
|
||||
scale: snapshot?.current?.scale
|
||||
});
|
||||
}}
|
||||
// scale={snapshotValue.current?.scale}
|
||||
// onScaleChanged={(scale) => {
|
||||
// saveSnapshot({
|
||||
// currentPage: snapshot?.current?.currentPage,
|
||||
// scale: scale
|
||||
// });
|
||||
// }}
|
||||
page={snapshotValue?.current?.currentPage}
|
||||
password={password}
|
||||
maxScale={6}
|
||||
onError={onError}
|
||||
onPressLink={(uri) => {
|
||||
console.log(`Link pressed: ${uri}`);
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
width: width,
|
||||
height: Dimensions.get("window").height
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default PDFPreview;
|
||||
File diff suppressed because one or more lines are too long
@@ -42,7 +42,6 @@ import { eOpenAnnouncementDialog } from "../../utils/events";
|
||||
import { getGithubVersion } from "../../utils/github-version";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { SVG } from "../auth/background";
|
||||
import Migrate from "../sheets/migrate";
|
||||
import NewFeature from "../sheets/new-feature/index";
|
||||
import { Update } from "../sheets/update";
|
||||
@@ -50,7 +49,6 @@ import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Walkthrough } from "../walkthroughs";
|
||||
@@ -70,6 +68,7 @@ const Launcher = React.memo(
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const verifying = useRef(false);
|
||||
|
||||
const loadNotes = useCallback(async () => {
|
||||
if (verifyUser) {
|
||||
@@ -88,7 +87,7 @@ const Launcher = React.memo(
|
||||
|
||||
const init = useCallback(async () => {
|
||||
if (!db.isInitialized) {
|
||||
await RNBootSplash.hide({ fade: true });
|
||||
RNBootSplash.hide({ fade: true });
|
||||
DatabaseLogger.info("Initializing database");
|
||||
await db.init();
|
||||
}
|
||||
@@ -131,7 +130,6 @@ const Launcher = React.memo(
|
||||
if (NewFeature.present()) return;
|
||||
if (await checkAppUpdateAvailable()) return;
|
||||
if (await checkForRateAppRequest()) return;
|
||||
if (await checkNeedsBackup()) return;
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
|
||||
if (introCompleted) {
|
||||
@@ -174,23 +172,6 @@ const Launcher = React.memo(
|
||||
return false;
|
||||
};
|
||||
|
||||
const checkNeedsBackup = async () => {
|
||||
return false;
|
||||
// let { nextBackupRequestTime, reminder } = SettingsService.get();
|
||||
// if (reminder === 'off' || !reminder) {
|
||||
// if (nextBackupRequestTime < Date.now()) {
|
||||
// presentSheet({
|
||||
// title: 'Backup & restore',
|
||||
// paragraph: 'Please enable automatic backups to keep your data safe',
|
||||
// component: <SettingsBackupAndRestore isSheet={true} />
|
||||
// });
|
||||
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
};
|
||||
|
||||
const onUnlockBiometrics = useCallback(async () => {
|
||||
if (!(await BiometricService.isBiometryAvailable())) return;
|
||||
if (Platform.OS === "android") {
|
||||
@@ -206,6 +187,9 @@ const Launcher = React.memo(
|
||||
setVerifyUser(false);
|
||||
enabled(false);
|
||||
password.current = null;
|
||||
setTimeout(() => {
|
||||
verifying.current = false;
|
||||
}, 1);
|
||||
}
|
||||
}, [setVerifyUser]);
|
||||
|
||||
@@ -214,7 +198,10 @@ const Launcher = React.memo(
|
||||
}, [init, verifyUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (verifying.current || useUserStore.getState().shouldBlockVerifyUser)
|
||||
return;
|
||||
if (verifyUser && appState === "active") {
|
||||
verifying.current = true;
|
||||
onUnlockBiometrics();
|
||||
}
|
||||
}, [appState, onUnlockBiometrics, verifyUser]);
|
||||
@@ -240,18 +227,10 @@ const Launcher = React.memo(
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
zIndex: 999
|
||||
zIndex: 999,
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -287,14 +266,13 @@ const Launcher = React.memo(
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Unlock to access your notes
|
||||
Unlock your notes
|
||||
</Heading>
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
fontSize: SIZE.md,
|
||||
maxWidth: "90%"
|
||||
}}
|
||||
>
|
||||
@@ -305,8 +283,7 @@ const Launcher = React.memo(
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1
|
||||
backgroundColor: colors.bg
|
||||
}}
|
||||
>
|
||||
{user ? (
|
||||
@@ -346,14 +323,9 @@ const Launcher = React.memo(
|
||||
<Button
|
||||
title="Unlock with Biometrics"
|
||||
width={250}
|
||||
height={45}
|
||||
style={{
|
||||
borderRadius: 100
|
||||
}}
|
||||
onPress={onUnlockBiometrics}
|
||||
icon={"fingerprint"}
|
||||
type={user ? "grayAccent" : "accent"}
|
||||
fontSize={SIZE.md}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -178,16 +178,29 @@ const NoteItem = ({
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<Heading
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
{compactMode ? (
|
||||
<Paragraph
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Heading
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
{item.headline && !compactMode ? (
|
||||
<Paragraph
|
||||
@@ -202,161 +215,219 @@ const NoteItem = ({
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
>
|
||||
{!isTrash ? (
|
||||
<>
|
||||
{item.conflicted ? (
|
||||
<Icon
|
||||
name="alert-circle"
|
||||
{compactMode ? null : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
>
|
||||
{!isTrash ? (
|
||||
<>
|
||||
{item.conflicted ? (
|
||||
<Icon
|
||||
name="alert-circle"
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.red}
|
||||
/>
|
||||
) : null}
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.icon,
|
||||
marginRight: 6
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.red}
|
||||
time={item[dateBy]}
|
||||
updateFrequency={
|
||||
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.icon,
|
||||
marginRight: 6
|
||||
}}
|
||||
time={item[dateBy]}
|
||||
updateFrequency={
|
||||
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
|
||||
}
|
||||
/>
|
||||
|
||||
{attachmentCount > 0 ? (
|
||||
<View
|
||||
{attachmentCount > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="attachment"
|
||||
size={SIZE.md}
|
||||
color={colors.icon}
|
||||
/>
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
{attachmentCount}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{item.pinned ? (
|
||||
<Icon
|
||||
testID="icon-pinned"
|
||||
name="pin-outline"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={
|
||||
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.locked ? (
|
||||
<Icon
|
||||
name="lock"
|
||||
testID="note-locked-icon"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={colors.icon}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.favorite ? (
|
||||
<Icon
|
||||
testID="icon-star"
|
||||
name="star"
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color="orange"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!isTrash && !compactMode && tags
|
||||
? tags.map((item) =>
|
||||
item.id ? (
|
||||
<Button
|
||||
title={"#" + item.alias}
|
||||
key={item.id}
|
||||
height={23}
|
||||
type="gray"
|
||||
textStyle={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
||||
fontSize={SIZE.xs}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
marginRight: 4,
|
||||
zIndex: 10,
|
||||
maxWidth: tags.length > 1 ? 130 : null
|
||||
}}
|
||||
onPress={() => navigateToTag(item)}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
<Icon name="attachment" size={SIZE.md} color={colors.icon} />
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
{attachmentCount}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : null}
|
||||
Deleted on{" "}
|
||||
{item && item.dateDeleted
|
||||
? new Date(item.dateDeleted).toISOString().slice(0, 10)
|
||||
: null}
|
||||
</Paragraph>
|
||||
|
||||
{item.pinned ? (
|
||||
<Icon
|
||||
testID="icon-pinned"
|
||||
name="pin-outline"
|
||||
size={SIZE.sm}
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={
|
||||
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.locked ? (
|
||||
<Icon
|
||||
name="lock"
|
||||
testID="note-locked-icon"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={colors.icon}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.favorite ? (
|
||||
<Icon
|
||||
testID="icon-star"
|
||||
name="star"
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color="orange"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!isTrash && !compactMode && tags
|
||||
? tags.map((item) =>
|
||||
item.id ? (
|
||||
<Button
|
||||
title={"#" + item.alias}
|
||||
key={item.id}
|
||||
height={23}
|
||||
type="gray"
|
||||
textStyle={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
||||
fontSize={SIZE.xs}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
marginRight: 4,
|
||||
zIndex: 10,
|
||||
maxWidth: tags.length > 1 ? 130 : null
|
||||
}}
|
||||
onPress={() => navigateToTag(item)}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
Deleted on{" "}
|
||||
{item && item.dateDeleted
|
||||
? new Date(item.dateDeleted).toISOString().slice(0, 10)
|
||||
: null}
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
>
|
||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<IconButton
|
||||
testID={notesnook.listitem.menu}
|
||||
color={colors.pri}
|
||||
name="dots-horizontal"
|
||||
size={SIZE.xl}
|
||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
||||
customStyle={{
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{item.conflicted ? (
|
||||
<Icon
|
||||
name="alert-circle"
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.red}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.locked ? (
|
||||
<Icon
|
||||
name="lock"
|
||||
testID="note-locked-icon"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={colors.icon}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.favorite ? (
|
||||
<Icon
|
||||
testID="icon-star"
|
||||
name="star-outline"
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color="orange"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.icon,
|
||||
marginRight: 6
|
||||
}}
|
||||
time={item[dateBy]}
|
||||
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
testID={notesnook.listitem.menu}
|
||||
color={colors.pri}
|
||||
name="dots-horizontal"
|
||||
size={SIZE.xl}
|
||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
||||
customStyle={{
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { getFormattedDate } from "../../../utils/time";
|
||||
|
||||
const showActionSheet = (item) => {
|
||||
Properties.present(item);
|
||||
@@ -63,15 +64,28 @@ export const NotebookItem = ({
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
size={SIZE.md}
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
{compactMode ? (
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Heading
|
||||
size={SIZE.md}
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
{isTopic || !item.description || compactMode ? null : (
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
@@ -122,40 +136,51 @@ export const NotebookItem = ({
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
>
|
||||
{isTrash ? (
|
||||
<>
|
||||
{!compactMode ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
>
|
||||
{isTrash ? (
|
||||
<>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{"Deleted on " +
|
||||
new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
||||
</Paragraph>
|
||||
</>
|
||||
) : (
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{"Deleted on " +
|
||||
new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
||||
{getFormattedDate(item[dateBy], "date")}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
||||
</Paragraph>
|
||||
</>
|
||||
) : (
|
||||
)}
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
@@ -163,50 +188,61 @@ export const NotebookItem = ({
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{new Date(item[dateBy]).toDateString().substring(4)}
|
||||
{item && totalNotes > 1
|
||||
? totalNotes + " notes"
|
||||
: totalNotes === 1
|
||||
? totalNotes + " note"
|
||||
: "0 notes"}
|
||||
</Paragraph>
|
||||
)}
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{item && totalNotes > 1
|
||||
? totalNotes + " notes"
|
||||
: totalNotes === 1
|
||||
? totalNotes + " note"
|
||||
: "0 notes"}
|
||||
</Paragraph>
|
||||
|
||||
{item.pinned ? (
|
||||
<Icon
|
||||
name="pin-outline"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
marginTop: 2
|
||||
}}
|
||||
color={colors.accent}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
{item.pinned ? (
|
||||
<Icon
|
||||
name="pin-outline"
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
marginRight: 10,
|
||||
marginTop: 2
|
||||
}}
|
||||
color={colors.accent}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<IconButton
|
||||
color={colors.heading}
|
||||
name="dots-horizontal"
|
||||
testID={notesnook.ids.notebook.menu}
|
||||
size={SIZE.xl}
|
||||
onPress={() => showActionSheet(item)}
|
||||
customStyle={{
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{item && totalNotes > 1
|
||||
? totalNotes + " notes"
|
||||
: totalNotes === 1
|
||||
? totalNotes + " note"
|
||||
: "0 notes"}
|
||||
</Paragraph>
|
||||
<IconButton
|
||||
color={colors.heading}
|
||||
name="dots-horizontal"
|
||||
testID={notesnook.ids.notebook.menu}
|
||||
size={SIZE.xl}
|
||||
onPress={() => showActionSheet(item)}
|
||||
customStyle={{
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -42,11 +42,16 @@ const SelectionWrapper = ({
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
);
|
||||
const listMode = item.type === "notebook" ? notebooksListMode : notesListMode;
|
||||
const listMode =
|
||||
item.type === "notebook" || item.itemType === "notebook"
|
||||
? notebooksListMode
|
||||
: notesListMode;
|
||||
const compactMode =
|
||||
(item.type === "notebook" || item.type === "note") &&
|
||||
(item.type === "notebook" ||
|
||||
item.itemType === "notebook" ||
|
||||
item.itemType === "note" ||
|
||||
item.type === "note") &&
|
||||
listMode === "compact";
|
||||
|
||||
if (item.id !== itemId.current) {
|
||||
itemId.current = item.id;
|
||||
}
|
||||
@@ -77,7 +82,7 @@ const SelectionWrapper = ({
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: compactMode ? 8 : 12,
|
||||
paddingVertical: compactMode ? 4 : 12,
|
||||
borderRadius: isSheet ? 10 : 0,
|
||||
marginBottom: isSheet ? 12 : undefined
|
||||
}}
|
||||
|
||||
@@ -23,6 +23,7 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
|
||||
export const SelectionIcon = ({ item }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -32,6 +33,17 @@ export const SelectionIcon = ({ item }) => {
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
const [selected, setSelected] = useState(false);
|
||||
const notebooksListMode = useSettingStore(
|
||||
(state) => state.settings.notebooksListMode
|
||||
);
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
);
|
||||
const listMode = item.type === "notebook" ? notebooksListMode : notesListMode;
|
||||
|
||||
const compactMode =
|
||||
(item.type === "notebook" || item.type === "note") &&
|
||||
listMode === "compact";
|
||||
|
||||
useEffect(() => {
|
||||
if (selectionMode) {
|
||||
@@ -54,8 +66,8 @@ export const SelectionIcon = ({ item }) => {
|
||||
return selectionMode ? (
|
||||
<View
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
width: compactMode ? 30 : 40,
|
||||
height: compactMode ? 30 : 40,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginRight: 10,
|
||||
@@ -67,7 +79,7 @@ export const SelectionIcon = ({ item }) => {
|
||||
>
|
||||
{selected ? (
|
||||
<Icon
|
||||
size={SIZE.xl}
|
||||
size={compactMode ? SIZE.xl - 2 : SIZE.xl}
|
||||
color={selected ? colors.accent : colors.icon}
|
||||
name={"check"}
|
||||
/>
|
||||
|
||||
@@ -37,7 +37,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { dHeight } from "../../utils";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep, timeConverter } from "../../utils/time";
|
||||
import { getFormattedDate, sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogButtons from "../dialog/dialog-buttons";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
@@ -166,7 +166,7 @@ const MergeConflicts = () => {
|
||||
{isCurrent ? "(This Device)" : "(Incoming)"}
|
||||
</Text>
|
||||
{"\n"}
|
||||
{timeConverter(contentToKeep?.dateEdited)}
|
||||
{getFormattedDate(contentToKeep?.dateEdited)}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { timeConverter, timeSince } from "../../utils/time";
|
||||
import { getFormattedDate, timeSince } from "../../utils/time";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
@@ -63,15 +63,15 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
}, []);
|
||||
|
||||
const getDate = (start, end) => {
|
||||
let _start = timeConverter(start);
|
||||
let _end = timeConverter(end + 60000);
|
||||
if (_start === _end) return _start;
|
||||
let final = _end.lastIndexOf(",");
|
||||
let part = _end.slice(0, final + 1);
|
||||
if (_start.includes(part)) {
|
||||
return _start + " —" + _end.replace(part, "");
|
||||
}
|
||||
return _start + " — " + _end;
|
||||
let _start_date = getFormattedDate(start, "date");
|
||||
let _end_date = getFormattedDate(end + 60000, "date");
|
||||
|
||||
let _start_time = getFormattedDate(start, "time");
|
||||
let _end_time = getFormattedDate(end + 60000, "time");
|
||||
|
||||
return `${_start_date} ${_start_time} - ${
|
||||
_end_date === _start_date ? " " : _end_date + " "
|
||||
}${_end_time}`;
|
||||
};
|
||||
|
||||
const renderItem = useCallback(
|
||||
|
||||
@@ -18,13 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
import { FeatureBlock } from "./feature";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
|
||||
export const CompactFeatures = ({
|
||||
vertical,
|
||||
features = [],
|
||||
maxHeight = 500,
|
||||
maxHeight = 600,
|
||||
scrollRef
|
||||
}) => {
|
||||
let data = vertical
|
||||
@@ -80,14 +80,11 @@ export const CompactFeatures = ({
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal={!vertical}
|
||||
nestedScrollEnabled
|
||||
onMomentumScrollEnd={() => {
|
||||
scrollRef?.current?.handleChildScrollEnd();
|
||||
}}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: maxHeight
|
||||
maxHeight: maxHeight,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{data.map((item) => (
|
||||
|
||||
@@ -41,18 +41,19 @@ export const FeatureBlock = ({
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 10
|
||||
marginBottom: 10,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 10,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
>
|
||||
<Icon color={colors.accent} name="check" size={SIZE.lg} />
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
marginLeft: 5,
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.md}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
{content}
|
||||
</Paragraph>
|
||||
|
||||
@@ -17,10 +17,10 @@ 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 React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -31,6 +31,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { getElevation } from "../../utils";
|
||||
import {
|
||||
eCloseActionSheet,
|
||||
eCloseSheet,
|
||||
eOpenPremiumDialog,
|
||||
eShowGetPremium
|
||||
} from "../../utils/events";
|
||||
@@ -39,12 +40,12 @@ import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [msg, setMsg] = useState(null);
|
||||
const timer = useRef();
|
||||
const keyboard = useKeyboard();
|
||||
|
||||
const open = useCallback(
|
||||
(event) => {
|
||||
@@ -72,7 +73,6 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eShowGetPremium, open);
|
||||
return () => {
|
||||
clearTimeout(timer.current);
|
||||
eUnSubscribeEvent(eShowGetPremium, open);
|
||||
};
|
||||
}, [open]);
|
||||
@@ -80,9 +80,7 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
||||
const onPress = async () => {
|
||||
open(null);
|
||||
eSendEvent(eCloseActionSheet);
|
||||
if (editorState().isFocused) {
|
||||
//tiny.call(EditorWebView, tiny.blur);
|
||||
}
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
};
|
||||
@@ -102,9 +100,13 @@ export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
justifyContent: "space-between",
|
||||
top: offset,
|
||||
top: offset + keyboard.keyboardHeight,
|
||||
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
|
||||
}}
|
||||
onTouchEnd={() => {
|
||||
setMsg(null);
|
||||
clearTimeout(timer.current);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -232,8 +232,7 @@ export const PricingPlans = ({
|
||||
width={250}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 15,
|
||||
borderRadius: 100
|
||||
marginBottom: 15
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@@ -398,8 +397,7 @@ export const PricingPlans = ({
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: 30,
|
||||
marginBottom: 10,
|
||||
borderRadius: 100
|
||||
marginBottom: 10
|
||||
}}
|
||||
/>
|
||||
{Platform.OS !== "ios" &&
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { timeConverter } from "../../utils/time";
|
||||
import { getFormattedDate } from "../../utils/time";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
export const DateMeta = ({ item }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -66,7 +66,7 @@ export const DateMeta = ({ item }) => {
|
||||
{getNameFromKey(key)}
|
||||
</Paragraph>
|
||||
<Paragraph size={SIZE.xs} color={colors.icon}>
|
||||
{timeConverter(item[key])}
|
||||
{getFormattedDate(item[key], "date-time")}
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -191,13 +191,25 @@ Properties.present = (item, buttons = [], isSheet) => {
|
||||
break;
|
||||
case "notebook":
|
||||
props[0] = db.notebooks.notebook(item.id).data;
|
||||
props.push(["edit-notebook", "pin", "add-shortcut", "trash"]);
|
||||
props.push([
|
||||
"edit-notebook",
|
||||
"pin",
|
||||
"add-shortcut",
|
||||
"trash",
|
||||
"default-notebook"
|
||||
]);
|
||||
break;
|
||||
case "topic":
|
||||
props[0] = db.notebooks
|
||||
.notebook(item.notebookId)
|
||||
.topics.topic(item.id)._topic;
|
||||
props.push(["move-notes", "edit-topic", "add-shortcut", "trash"]);
|
||||
props.push([
|
||||
"move-notes",
|
||||
"edit-topic",
|
||||
"add-shortcut",
|
||||
"trash",
|
||||
"default-topic"
|
||||
]);
|
||||
break;
|
||||
case "tag":
|
||||
props[0] = db.tags.tag(item.id);
|
||||
|
||||
@@ -47,7 +47,7 @@ export const Tags = ({ item, close }) => {
|
||||
>
|
||||
<Button
|
||||
onPress={async () => {
|
||||
ManageTagsSheet.present(item);
|
||||
ManageTagsSheet.present([item]);
|
||||
}}
|
||||
buttonType={{
|
||||
text: colors.accent
|
||||
|
||||
@@ -36,6 +36,7 @@ import MoveNoteSheet from "../sheets/add-to";
|
||||
import ExportNotesSheet from "../sheets/export-notes";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
|
||||
export const SelectionHeader = React.memo(() => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -209,6 +210,20 @@ export const SelectionHeader = React.memo(() => {
|
||||
screen === "Notebook" ||
|
||||
screen === "Reminders" ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
await sleep(100);
|
||||
ManageTagsSheet.present(selectedItemsList);
|
||||
}}
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.pri}
|
||||
tooltipText="Manage tags"
|
||||
tooltipPosition={4}
|
||||
name="pound"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
//setSelectionMode(false);
|
||||
@@ -224,6 +239,7 @@ export const SelectionHeader = React.memo(() => {
|
||||
name="plus"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
|
||||
@@ -196,12 +196,11 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
accentColor={data.iconColor || "accent"}
|
||||
accentText="light"
|
||||
type="accent"
|
||||
height={45}
|
||||
height={40}
|
||||
width={250}
|
||||
style={{
|
||||
borderRadius: 100
|
||||
marginBottom: 25
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -309,7 +309,6 @@ export class AddNotebookSheet extends React.Component {
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 24
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
onPress={this.addNewNotebook}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -42,6 +42,7 @@ import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { requestInAppReview } from "../../../services/app-review";
|
||||
|
||||
const ExportNotesSheet = ({ notes, update }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -72,6 +73,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
update({ disableClosing: false });
|
||||
setComplete(true);
|
||||
setExporting(false);
|
||||
requestInAppReview();
|
||||
};
|
||||
|
||||
const actions = [
|
||||
|
||||
@@ -22,7 +22,7 @@ import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import { presentSheet, ToastEvent } from "../../../services/event-manager";
|
||||
import { ToastEvent, presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
@@ -31,9 +31,10 @@ import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
const ManageTagsSheet = (props) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [note, setNote] = useState(props.note);
|
||||
const [notes, setNotes] = useState(props.notes || []);
|
||||
const allTags = useTagStore((state) => state.tags);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [query, setQuery] = useState(null);
|
||||
@@ -42,7 +43,7 @@ const ManageTagsSheet = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
sortTags();
|
||||
}, [allTags, note, query, sortTags]);
|
||||
}, [allTags, notes, query, sortTags]);
|
||||
|
||||
const sortTags = useCallback(() => {
|
||||
let _tags = [...allTags];
|
||||
@@ -51,13 +52,14 @@ const ManageTagsSheet = (props) => {
|
||||
if (query) {
|
||||
_tags = _tags.filter((t) => t.title.startsWith(query));
|
||||
}
|
||||
const tagsMerged = [...notes.map((note) => note.tags || []).flat()];
|
||||
|
||||
if (!note || !note.tags) {
|
||||
if (!tagsMerged || !tagsMerged.length) {
|
||||
setTags(_tags);
|
||||
return;
|
||||
}
|
||||
let noteTags = [];
|
||||
for (let tag of note.tags) {
|
||||
for (let tag of tagsMerged) {
|
||||
let index = _tags.findIndex((t) => t.title === tag);
|
||||
if (index !== -1) {
|
||||
noteTags.push(_tags[index]);
|
||||
@@ -67,7 +69,7 @@ const ManageTagsSheet = (props) => {
|
||||
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
|
||||
let combinedTags = [...noteTags, ..._tags];
|
||||
setTags(combinedTags);
|
||||
}, [allTags, note, query]);
|
||||
}, [allTags, notes, query]);
|
||||
|
||||
useEffect(() => {
|
||||
useTagStore.getState().setTags();
|
||||
@@ -85,15 +87,23 @@ const ManageTagsSheet = (props) => {
|
||||
}
|
||||
|
||||
let tag = _query;
|
||||
setNote({ ...note, tags: note.tags ? [...note.tags, tag] : [tag] });
|
||||
setNotes(
|
||||
notes.map((note) => ({
|
||||
...note,
|
||||
tags: note.tags ? [...note.tags, tag] : [tag]
|
||||
}))
|
||||
);
|
||||
|
||||
setQuery(null);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: ""
|
||||
});
|
||||
try {
|
||||
await db.notes.note(note.id).tag(tag);
|
||||
for (let note of notes) {
|
||||
await db.notes.note(note.id).tag(tag);
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNote(db.notes.note(note.id).data);
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Cannot add tag",
|
||||
@@ -180,84 +190,91 @@ const ManageTagsSheet = (props) => {
|
||||
) : null}
|
||||
|
||||
{tags.map((item) => (
|
||||
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
|
||||
<TagItem
|
||||
key={item.title}
|
||||
tag={item}
|
||||
notes={notes}
|
||||
setNotes={setNotes}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ManageTagsSheet.present = (note) => {
|
||||
ManageTagsSheet.present = (notes) => {
|
||||
presentSheet({
|
||||
component: (ref) => {
|
||||
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
|
||||
return <ManageTagsSheet actionSheetRef={ref} notes={notes} />;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default ManageTagsSheet;
|
||||
|
||||
const TagItem = ({ tag, note, setNote }) => {
|
||||
const TagItem = ({ tag, notes, setNotes }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
const someNotesTagged = notes.some(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
const allNotesTagged = notes.every(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
const onPress = async () => {
|
||||
let prevNote = { ...note };
|
||||
try {
|
||||
if (prevNote.tags.indexOf(tag.title) !== -1) {
|
||||
await db.notes
|
||||
.note(note.id)
|
||||
.untag(prevNote.tags[prevNote.tags.indexOf(tag.title)]);
|
||||
} else {
|
||||
await db.notes.note(note.id).tag(tag.title);
|
||||
for (let note of notes) {
|
||||
try {
|
||||
if (someNotesTagged) {
|
||||
await db.notes
|
||||
.note(note.id)
|
||||
.untag(note.tags[note.tags.indexOf(tag.title)]);
|
||||
} else {
|
||||
await db.notes.note(note.id).tag(tag.title);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNote(db.notes.note(note.id).data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
justifyContent: "flex-start",
|
||||
height: 40
|
||||
}}
|
||||
onPress={onPress}
|
||||
type={
|
||||
note && note.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? "shade"
|
||||
: "grayBg"
|
||||
}
|
||||
type="gray"
|
||||
>
|
||||
<Heading
|
||||
size={SIZE.sm}
|
||||
color={
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? colors.accent
|
||||
: colors.pri
|
||||
<IconButton
|
||||
size={22}
|
||||
customStyle={{
|
||||
marginRight: 5,
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
color={someNotesTagged || allNotesTagged ? colors.accent : colors.icon}
|
||||
testID={
|
||||
allNotesTagged
|
||||
? "check-circle-outline"
|
||||
: someNotesTagged
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
>
|
||||
{"#" + tag.title}
|
||||
</Heading>
|
||||
<Icon
|
||||
name={
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? "minus"
|
||||
: "plus"
|
||||
allNotesTagged
|
||||
? "check-circle-outline"
|
||||
: someNotesTagged
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
color={
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? colors.accent
|
||||
: colors.accent
|
||||
}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
<Paragraph size={SIZE.sm}>{"#" + tag.alias}</Paragraph>
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ import Input from "../../ui/input";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { requestInAppReview } from "../../../services/app-review";
|
||||
|
||||
const PublishNoteSheet = ({ note: item, update }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -66,6 +67,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
setPublishLoading(false);
|
||||
}
|
||||
requestInAppReview();
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Could not publish note",
|
||||
|
||||
@@ -43,7 +43,7 @@ import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import { QRCode } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
|
||||
class RecoveryKeySheet extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -243,7 +243,6 @@ export default function ReminderSheet({
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 24
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
onPress={saveReminder}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -21,21 +21,22 @@ import { EVENTS } from "@notesnook/core/common";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, Platform, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { db } from "../../../common/database";
|
||||
import storage from "../../../common/database/storage";
|
||||
import {
|
||||
ToastEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { initialize } from "../../../stores";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { timeConverter } from "../../../utils/time";
|
||||
import { getFormattedDate } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
@@ -44,7 +45,6 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -251,7 +251,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.sm} style={{ width: "100%", maxWidth: "100%" }}>
|
||||
{timeConverter(item?.lastModified * 1)}
|
||||
{getFormattedDate(item?.lastModified * 1)}
|
||||
</Paragraph>
|
||||
<Paragraph size={SIZE.xs}>
|
||||
{(item.filename || item.name).replace(".nnbackup", "")}
|
||||
|
||||
@@ -65,7 +65,6 @@ import { deleteItems } from "../../../utils/functions";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import { Properties } from "../../properties";
|
||||
import Sort from "../sort";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
|
||||
type ConfigItem = { id: string; type: string };
|
||||
class TopicSheetConfig {
|
||||
|
||||
@@ -302,6 +302,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = value;
|
||||
})
|
||||
.onEnd((event) => {
|
||||
if (locked.value || forcedLock.value) return;
|
||||
if (currentTab.value === 2 && Platform.OS === "android") return;
|
||||
const velocityX =
|
||||
event.velocityX < 0 ? event.velocityX * -1 : event.velocityX;
|
||||
|
||||
@@ -112,14 +112,14 @@ const Input = ({
|
||||
const [secureEntry, setSecureEntry] = useState(true);
|
||||
const [showError, setShowError] = useState(false);
|
||||
const [errorList, setErrorList] = useState({
|
||||
SHORT_PASS: true
|
||||
SHORT_PASS: false
|
||||
});
|
||||
type ErrorKey = keyof typeof errorList;
|
||||
const color = error
|
||||
? colors.red
|
||||
: focus
|
||||
? customColor || colors.accent
|
||||
: colors.nav;
|
||||
: colors.border;
|
||||
|
||||
const validate = async (value: string) => {
|
||||
if (!validationType) return;
|
||||
@@ -187,6 +187,12 @@ const Input = ({
|
||||
onChangeText && onChangeText(value);
|
||||
setShowError(false);
|
||||
validate(value);
|
||||
if (value === "") {
|
||||
setError(false);
|
||||
setErrorList({
|
||||
SHORT_PASS: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
|
||||
@@ -16,17 +16,15 @@ 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 { isReminderActive } from "@notesnook/core/collections/reminders";
|
||||
import React from "react";
|
||||
import {
|
||||
formatReminderTime,
|
||||
isReminderActive
|
||||
} from "@notesnook/core/collections/reminders";
|
||||
import { ViewStyle } from "react-native";
|
||||
|
||||
import { Reminder } from "../../../services/notifications";
|
||||
import { Button, ButtonProps } from "../button";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { getFormattedReminderTime } from "../../../utils/time";
|
||||
import { Button, ButtonProps } from "../button";
|
||||
|
||||
export const ReminderTime = ({
|
||||
checkIsActive = true,
|
||||
@@ -42,7 +40,7 @@ export const ReminderTime = ({
|
||||
} & ButtonProps) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const reminder = props.reminder;
|
||||
const time = !reminder ? undefined : formatReminderTime(reminder);
|
||||
const time = !reminder ? undefined : getFormattedReminderTime(reminder);
|
||||
const isTodayOrTomorrow =
|
||||
(time?.includes("Today") || time?.includes("Tomorrow")) &&
|
||||
!time?.includes("Last");
|
||||
|
||||
@@ -105,6 +105,9 @@ const SheetWrapper = ({
|
||||
defaultOverlayOpacity={overlayOpacity}
|
||||
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
|
||||
keyboardShouldPersistTaps="always"
|
||||
openAnimationConfig={{
|
||||
friction: 9
|
||||
}}
|
||||
ExtraOverlayComponent={
|
||||
<>
|
||||
{overlay}
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { Platform, Text, TextProps } from "react-native";
|
||||
import { Platform, Text, TextProps, ViewStyle } from "react-native";
|
||||
import Animated, {
|
||||
ComplexAnimationBuilder,
|
||||
Layout
|
||||
@@ -30,15 +30,26 @@ interface HeadingProps extends TextProps {
|
||||
size?: number;
|
||||
layout?: ComplexAnimationBuilder;
|
||||
animated?: boolean;
|
||||
extraBold?: boolean;
|
||||
}
|
||||
|
||||
const AnimatedText = Animated.createAnimatedComponent(Text);
|
||||
|
||||
const extraBoldStyle = {
|
||||
fontFamily: Platform.OS === "android" ? "OpenSans-Bold" : undefined,
|
||||
fontWeight: Platform.OS === "ios" ? "800" : undefined
|
||||
};
|
||||
const boldStyle = {
|
||||
fontFamily: Platform.OS === "android" ? "OpenSans-SemiBold" : undefined,
|
||||
fontWeight: Platform.OS === "ios" ? "600" : undefined
|
||||
};
|
||||
|
||||
const Heading = ({
|
||||
color,
|
||||
size = SIZE.xl,
|
||||
style,
|
||||
animated,
|
||||
extraBold,
|
||||
...restProps
|
||||
}: HeadingProps) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -53,11 +64,9 @@ const Heading = ({
|
||||
style={[
|
||||
{
|
||||
fontSize: size || SIZE.xl,
|
||||
color: color || colors.heading,
|
||||
fontFamily:
|
||||
Platform.OS === "android" ? "OpenSans-SemiBold" : undefined,
|
||||
fontWeight: Platform.OS === "ios" ? "600" : undefined
|
||||
color: color || colors.heading
|
||||
},
|
||||
extraBold ? (extraBoldStyle as ViewStyle) : (boldStyle as ViewStyle),
|
||||
style
|
||||
]}
|
||||
></Component>
|
||||
|
||||
@@ -71,6 +71,9 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [notifPinned, setNotifPinned] = useState(null);
|
||||
const alias = item.alias || item.title;
|
||||
const [defaultNotebook, setDefaultNotebook] = useState(
|
||||
db.settings.getDefaultNotebook()
|
||||
);
|
||||
|
||||
const isPublished =
|
||||
item.type === "note" && db.monographs.isPublished(item.id);
|
||||
@@ -381,11 +384,12 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
positivePress: async (value) => {
|
||||
if (!value || value === "" || value.trimStart().length == 0) return;
|
||||
await db.tags.rename(item.id, db.tags.sanitize(value));
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
useTagStore.getState().setTags();
|
||||
useMenuStore.getState().setMenuPins();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
});
|
||||
useRelationStore.getState().update();
|
||||
}, 1);
|
||||
},
|
||||
input: true,
|
||||
defaultValue: alias,
|
||||
@@ -520,7 +524,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
}
|
||||
|
||||
async function showAttachments() {
|
||||
AttachmentDialog.present();
|
||||
AttachmentDialog.present(item);
|
||||
}
|
||||
|
||||
async function exportNote() {
|
||||
@@ -773,6 +777,53 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
func: openHistory
|
||||
},
|
||||
|
||||
{
|
||||
id: "default-notebook",
|
||||
title:
|
||||
defaultNotebook?.id === item.id
|
||||
? "Remove as default"
|
||||
: "Set as default",
|
||||
hidden: item.type !== "notebook",
|
||||
icon: "notebook",
|
||||
func: async () => {
|
||||
if (defaultNotebook?.id === item.id) {
|
||||
await db.settings.setDefaultNotebook();
|
||||
setDefaultNotebook();
|
||||
} else {
|
||||
const notebook = {
|
||||
id: item.id
|
||||
};
|
||||
await db.settings.setDefaultNotebook(notebook);
|
||||
setDefaultNotebook(notebook);
|
||||
}
|
||||
close();
|
||||
},
|
||||
on: defaultNotebook?.topic ? false : defaultNotebook?.id === item.id
|
||||
},
|
||||
{
|
||||
id: "default-topic",
|
||||
title:
|
||||
defaultNotebook?.id === item.id
|
||||
? "Remove as default"
|
||||
: "Set as default",
|
||||
hidden: item.type !== "topic",
|
||||
icon: "bookmark",
|
||||
func: async () => {
|
||||
if (defaultNotebook?.topic === item.id) {
|
||||
await db.settings.setDefaultNotebook();
|
||||
setDefaultNotebook();
|
||||
} else {
|
||||
const notebook = {
|
||||
id: item.notebookId,
|
||||
topic: item.id
|
||||
};
|
||||
await db.settings.setDefaultNotebook(notebook);
|
||||
setDefaultNotebook(notebook);
|
||||
}
|
||||
close();
|
||||
},
|
||||
on: defaultNotebook?.topic === item.id
|
||||
},
|
||||
{
|
||||
id: "disable-reminder",
|
||||
title: !item.disabled ? "Turn off reminder" : "Turn on reminder",
|
||||
|
||||
@@ -17,22 +17,37 @@ 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 NetInfo from "@react-native-community/netinfo";
|
||||
import { EV, EVENTS, SYNC_CHECK_IDS } from "@notesnook/core/common";
|
||||
import { useEffect, useRef } from "react";
|
||||
import notifee from "@notifee/react-native";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
Appearance,
|
||||
AppState,
|
||||
Appearance,
|
||||
Keyboard,
|
||||
Linking,
|
||||
NativeEventEmitter,
|
||||
NativeModules,
|
||||
Platform,
|
||||
Keyboard
|
||||
Platform
|
||||
} from "react-native";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
import { editorController, editorState } from "../screens/editor/tiptap/utils";
|
||||
import {
|
||||
clearAppState,
|
||||
editorController,
|
||||
editorState
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
import {
|
||||
ToastEvent,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../services/event-manager";
|
||||
import {
|
||||
clearMessage,
|
||||
setEmailVerifyMessage,
|
||||
@@ -41,36 +56,23 @@ import {
|
||||
} from "../services/message";
|
||||
import PremiumService from "../services/premium";
|
||||
import SettingsService from "../services/settings";
|
||||
import Sync from "../services/sync";
|
||||
import { initAfterSync } from "../stores";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
import { useNoteStore } from "../stores/use-notes-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/color-scheme";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseSheet,
|
||||
eOnLoadNote,
|
||||
refreshNotesPage
|
||||
} from "../utils/events";
|
||||
import Sync from "../services/sync";
|
||||
import { initAfterSync } from "../stores";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useNoteStore } from "../stores/use-notes-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../services/event-manager";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import { useCallback } from "react";
|
||||
import { clearAppState } from "../screens/editor/tiptap/utils";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import BackupService from "../services/backup";
|
||||
import { sleep } from "../utils/time";
|
||||
import notifee from "@notifee/react-native";
|
||||
|
||||
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
|
||||
export const useAppEvents = () => {
|
||||
@@ -489,7 +491,8 @@ export const useAppEvents = () => {
|
||||
if (
|
||||
SettingsService.get().appLockMode === "background" &&
|
||||
!useSettingStore.getState().requestBiometrics &&
|
||||
!useUserStore.getState().verifyUser
|
||||
!useUserStore.getState().verifyUser &&
|
||||
!useUserStore.getState().shouldBlockVerifyUser
|
||||
) {
|
||||
useUserStore.getState().setVerifyUser(true);
|
||||
if (Platform.OS === "ios") {
|
||||
@@ -561,7 +564,6 @@ export const useAppEvents = () => {
|
||||
await db.initCollections();
|
||||
await db.notes.init();
|
||||
}
|
||||
useNoteStore.getState().setNotes();
|
||||
eSendEvent(refreshNotesPage);
|
||||
MMKV.removeItem("notesAddedFromIntent");
|
||||
initAfterSync();
|
||||
|
||||
@@ -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 { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
|
||||
type AttachmentProgress = {
|
||||
@@ -41,22 +41,26 @@ export const useAttachmentProgress = (
|
||||
: undefined
|
||||
);
|
||||
|
||||
const attachmentProgress = progress?.[attachment.metadata.hash];
|
||||
if (attachmentProgress) {
|
||||
const type = attachmentProgress.type;
|
||||
const loaded =
|
||||
attachmentProgress.type === "download"
|
||||
? attachmentProgress.recieved
|
||||
: attachmentProgress.sent;
|
||||
const value = loaded / attachmentProgress.total;
|
||||
setCurrentProgress({
|
||||
value: value * 100,
|
||||
percent: (value * 100).toFixed(0) + "%",
|
||||
type: type
|
||||
});
|
||||
} else {
|
||||
setCurrentProgress(undefined);
|
||||
}
|
||||
useEffect(() => {
|
||||
const attachmentProgress = progress?.[attachment?.metadata?.hash];
|
||||
if (attachmentProgress) {
|
||||
const type = attachmentProgress.type;
|
||||
const loaded =
|
||||
attachmentProgress.type === "download"
|
||||
? attachmentProgress.recieved
|
||||
: attachmentProgress.sent;
|
||||
const value = loaded / attachmentProgress.total;
|
||||
setCurrentProgress({
|
||||
value: value * 100,
|
||||
percent: (value * 100).toFixed(0) + "%",
|
||||
type: type
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setCurrentProgress(undefined);
|
||||
}, 300);
|
||||
}
|
||||
}, [attachment, progress]);
|
||||
|
||||
return [currentProgress, setCurrentProgress];
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useThemeStore } from "../stores/use-theme-store";
|
||||
import { history } from "../utils";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import Auth from "../components/auth";
|
||||
const NativeStack = createNativeStackNavigator();
|
||||
const IntroStack = createNativeStackNavigator();
|
||||
|
||||
@@ -64,6 +65,7 @@ const IntroStack = createNativeStackNavigator();
|
||||
|
||||
const IntroStackNavigator = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const height = useSettingStore((state) => state.dimensions.height);
|
||||
return (
|
||||
<IntroStack.Navigator
|
||||
screenOptions={{
|
||||
@@ -71,12 +73,14 @@ const IntroStackNavigator = () => {
|
||||
lazy: false,
|
||||
animation: "none",
|
||||
contentStyle: {
|
||||
backgroundColor: colors.bg
|
||||
backgroundColor: colors.bg,
|
||||
minHeight: height
|
||||
}
|
||||
}}
|
||||
initialRouteName={"Intro"}
|
||||
>
|
||||
<NativeStack.Screen name="Intro" component={Intro} />
|
||||
<NativeStack.Screen name="Auth" component={Auth} />
|
||||
<NativeStack.Screen name="AppLock" component={AppLock} />
|
||||
</IntroStack.Navigator>
|
||||
);
|
||||
@@ -120,7 +124,7 @@ const _Tabs = () => {
|
||||
animation: "none",
|
||||
contentStyle: {
|
||||
backgroundColor: colors.bg,
|
||||
height: !introCompleted ? undefined : screenHeight
|
||||
minHeight: !introCompleted ? undefined : screenHeight
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -271,13 +271,13 @@ const _TabsHolder = () => {
|
||||
setTimeout(() => {
|
||||
switch (current) {
|
||||
case "tablet":
|
||||
introCompleted && tabBarRef.current?.goToIndex(0, false);
|
||||
tabBarRef.current?.goToIndex(0, false);
|
||||
break;
|
||||
case "smallTablet":
|
||||
if (!fullscreen) {
|
||||
introCompleted && tabBarRef.current?.closeDrawer(false);
|
||||
tabBarRef.current?.closeDrawer(false);
|
||||
} else {
|
||||
introCompleted && tabBarRef.current?.openDrawer(false);
|
||||
tabBarRef.current?.openDrawer(false);
|
||||
}
|
||||
break;
|
||||
case "mobile":
|
||||
@@ -416,64 +416,66 @@ const _TabsHolder = () => {
|
||||
backgroundColor="transparent"
|
||||
/>
|
||||
|
||||
{deviceMode && widths[deviceMode] ? (
|
||||
<FluidTabs
|
||||
ref={tabBarRef}
|
||||
dimensions={dimensions}
|
||||
widths={!introCompleted ? widths["mobile"] : widths[deviceMode]}
|
||||
enabled={deviceMode !== "tablet" && !fullscreen}
|
||||
onScroll={onScroll}
|
||||
onChangeTab={onChangeTab}
|
||||
onDrawerStateChange={() => true}
|
||||
>
|
||||
<View
|
||||
key="1"
|
||||
style={{
|
||||
height: "100%",
|
||||
width: fullscreen
|
||||
? 0
|
||||
: widths[!introCompleted ? "mobile" : deviceMode]?.a
|
||||
}}
|
||||
>
|
||||
<SideMenu />
|
||||
</View>
|
||||
|
||||
<View
|
||||
key="2"
|
||||
style={{
|
||||
height: "100%",
|
||||
width: fullscreen
|
||||
? 0
|
||||
: widths[!introCompleted ? "mobile" : deviceMode]?.b
|
||||
}}
|
||||
>
|
||||
{deviceMode === "mobile" ? (
|
||||
<Animated.View
|
||||
onTouchEnd={() => {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
animatedOpacity.value = withTiming(0);
|
||||
animatedTranslateY.value = withTiming(-9999);
|
||||
{!introCompleted ? (
|
||||
<NavigationStack />
|
||||
) : (
|
||||
<>
|
||||
{deviceMode && widths[deviceMode] ? (
|
||||
<FluidTabs
|
||||
ref={tabBarRef}
|
||||
dimensions={dimensions}
|
||||
widths={widths[deviceMode]}
|
||||
enabled={deviceMode !== "tablet" && !fullscreen}
|
||||
onScroll={onScroll}
|
||||
onChangeTab={onChangeTab}
|
||||
onDrawerStateChange={() => true}
|
||||
>
|
||||
<View
|
||||
key="1"
|
||||
style={{
|
||||
height: "100%",
|
||||
width: fullscreen ? 0 : widths[deviceMode]?.a
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.2)"
|
||||
},
|
||||
animatedStyle
|
||||
]}
|
||||
ref={overlayRef}
|
||||
/>
|
||||
) : null}
|
||||
>
|
||||
<SideMenu />
|
||||
</View>
|
||||
|
||||
<NavigationStack />
|
||||
</View>
|
||||
<View
|
||||
key="2"
|
||||
style={{
|
||||
height: "100%",
|
||||
width: fullscreen ? 0 : widths[deviceMode]?.b
|
||||
}}
|
||||
>
|
||||
{deviceMode === "mobile" ? (
|
||||
<Animated.View
|
||||
onTouchEnd={() => {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
animatedOpacity.value = withTiming(0);
|
||||
animatedTranslateY.value = withTiming(-9999);
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.2)"
|
||||
},
|
||||
animatedStyle
|
||||
]}
|
||||
ref={overlayRef}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<EditorWrapper key="3" width={widths} dimensions={dimensions} />
|
||||
</FluidTabs>
|
||||
) : null}
|
||||
<NavigationStack />
|
||||
</View>
|
||||
|
||||
<EditorWrapper key="3" width={widths} dimensions={dimensions} />
|
||||
</FluidTabs>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
"validator": "^13.5.2",
|
||||
"zustand": "^3.6.0",
|
||||
"fflate": "^0.7.3",
|
||||
"timeago.js": "4.0.2"
|
||||
"timeago.js": "4.0.2",
|
||||
"react-native-blob-util": "0.17.3",
|
||||
"react-native-swiper-flatlist": "3.2.2"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import { useEditor } from "./tiptap/use-editor";
|
||||
import { useEditorEvents } from "./tiptap/use-editor-events";
|
||||
import { editorController } from "./tiptap/utils";
|
||||
import { useLayoutEffect } from "react";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -128,7 +129,6 @@ const Editor = React.memo(
|
||||
renderKey.current =
|
||||
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
|
||||
editor.setLoading(true);
|
||||
setTimeout(() => editor.setLoading(false), 10);
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -167,6 +167,7 @@ const Editor = React.memo(
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
onContentProcessDidTerminate={onError}
|
||||
setSupportMultipleWindows={false}
|
||||
overScrollMode="never"
|
||||
scrollEnabled={false}
|
||||
@@ -232,6 +233,8 @@ const AppSection = ({
|
||||
|
||||
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
const readonly = useEditorStore((state) => state.readonly);
|
||||
const keyboard = useKeyboard();
|
||||
|
||||
const onPress = async () => {
|
||||
if (editor.note.current) {
|
||||
await db.notes?.note(editor.note.current.id).readonly();
|
||||
@@ -241,7 +244,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
}
|
||||
};
|
||||
|
||||
return readonly && IconButton ? (
|
||||
return readonly && IconButton && !keyboard.keyboardShown ? (
|
||||
<IconButton
|
||||
name="pencil-lock"
|
||||
type="grayBg"
|
||||
@@ -249,7 +252,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
color="accent"
|
||||
customStyle={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
bottom: 60,
|
||||
width: 60,
|
||||
height: 60,
|
||||
right: 12,
|
||||
|
||||
@@ -101,8 +101,9 @@ const EditorOverlay = ({ editorId = "", editor }) => {
|
||||
setTimeout(() => {
|
||||
if (!loadingState.current.startTime) {
|
||||
translateValue.value = 6000;
|
||||
opacity.value = 0;
|
||||
}
|
||||
}, 1000);
|
||||
}, 3000);
|
||||
eSubscribeEvent("loadingNote" + editorId, load);
|
||||
return () => {
|
||||
clearTimers();
|
||||
|
||||
@@ -217,7 +217,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
updateWebclip = async ({ src, hash }: ImageAttributes) => {
|
||||
updateWebclip = async ({ src, hash }: Partial<ImageAttributes>) => {
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.updateWebClip(${JSON.stringify({
|
||||
hash
|
||||
@@ -225,7 +225,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
updateImage = async ({ src, hash }: ImageAttributes) => {
|
||||
updateImage = async ({ src, hash }: Partial<ImageAttributes>) => {
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.updateImage(${JSON.stringify({
|
||||
hash
|
||||
|
||||
@@ -22,7 +22,7 @@ import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { db } from "../../../common/database";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
import { AttachmentItem } from "../../../components/attachments/attachment-item";
|
||||
@@ -34,8 +34,8 @@ import {
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { editorController, editorState } from "./utils";
|
||||
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
|
||||
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
|
||||
import { isImage } from "@notesnook/core/utils/filename";
|
||||
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
presentSheet({
|
||||
@@ -74,7 +74,7 @@ const file = async (fileOptions) => {
|
||||
mode: "import",
|
||||
allowMultiSelection: false
|
||||
};
|
||||
if (Platform.OS == "ios") {
|
||||
if (Platform.OS === "ios") {
|
||||
options.copyTo = "cachesDirectory";
|
||||
}
|
||||
await db.attachments.generateKey();
|
||||
@@ -87,14 +87,9 @@ const file = async (fileOptions) => {
|
||||
}
|
||||
|
||||
file = file[0];
|
||||
if (file.type.startsWith("image")) {
|
||||
ToastEvent.show({
|
||||
title: "Type not supported",
|
||||
message: "Please add images from gallery or camera picker.",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
|
||||
if (file.size > FILE_SIZE_LIMIT) {
|
||||
ToastEvent.show({
|
||||
title: "File too large",
|
||||
@@ -114,7 +109,6 @@ const file = async (fileOptions) => {
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
|
||||
console.log("file uri: ", uri);
|
||||
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
|
||||
showEncryptionSheet(file);
|
||||
@@ -125,12 +119,24 @@ const file = async (fileOptions) => {
|
||||
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
|
||||
return;
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
editorController.current?.commands.insertAttachment({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
type: file.type,
|
||||
size: file.size
|
||||
});
|
||||
if (isImage(file.type)) {
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size,
|
||||
dataurl: await db.attachments.read(hash, "base64"),
|
||||
title: file.name
|
||||
});
|
||||
} else {
|
||||
editorController.current?.commands.insertAttachment({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
size: file.size
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
eSendEvent(eCloseSheet);
|
||||
}, 1000);
|
||||
@@ -255,7 +261,7 @@ const handleImageResponse = async (response, options) => {
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
type: image.type,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
@@ -263,7 +269,7 @@ const handleImageResponse = async (response, options) => {
|
||||
});
|
||||
};
|
||||
|
||||
async function attachFile(uri, hash, type, filename, options) {
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
try {
|
||||
let exists = db.attachments.exists(hash);
|
||||
let encryptionInfo;
|
||||
@@ -282,7 +288,7 @@ async function attachFile(uri, hash, type, filename, options) {
|
||||
let key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
uri: uri,
|
||||
type: "url",
|
||||
type: options.type || "url",
|
||||
hash: hash
|
||||
});
|
||||
encryptionInfo.type = type;
|
||||
|
||||
@@ -50,6 +50,8 @@ export type Settings = {
|
||||
corsProxy: string;
|
||||
fontSize: string;
|
||||
fontFamily: string;
|
||||
dateFormat: string;
|
||||
timeFormat: string;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
|
||||
@@ -130,6 +130,11 @@ export const useEditorEvents = (
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const fullscreen = useSettingStore((state) => state.fullscreen);
|
||||
const corsProxy = useSettingStore((state) => state.settings.corsProxy);
|
||||
const [dateFormat, timeFormat] = useSettingStore((state) => [
|
||||
state.dateFormat,
|
||||
state.timeFormat
|
||||
]);
|
||||
|
||||
const handleBack = useRef<NativeEventSubscription>();
|
||||
const readonly = useEditorStore((state) => state.readonly);
|
||||
const isPremium = useUserStore((state) => state.premium);
|
||||
@@ -173,7 +178,9 @@ export const useEditorEvents = (
|
||||
doubleSpacedLines: doubleSpacedLines,
|
||||
corsProxy: corsProxy,
|
||||
fontSize: defaultFontSize,
|
||||
fontFamily: defaultFontFamily
|
||||
fontFamily: defaultFontFamily,
|
||||
dateFormat: db.settings?.getDateFormat(),
|
||||
timeFormat: db.settings?.getTimeFormat()
|
||||
});
|
||||
}, [
|
||||
fullscreen,
|
||||
@@ -190,7 +197,9 @@ export const useEditorEvents = (
|
||||
noToolbar,
|
||||
corsProxy,
|
||||
defaultFontSize,
|
||||
defaultFontFamily
|
||||
defaultFontFamily,
|
||||
dateFormat,
|
||||
timeFormat
|
||||
]);
|
||||
|
||||
const onBackPress = useCallback(async () => {
|
||||
@@ -349,7 +358,7 @@ export const useEditorEvents = (
|
||||
});
|
||||
return;
|
||||
}
|
||||
ManageTagsSheet.present(editor.note.current);
|
||||
ManageTagsSheet.present([editor.note.current]);
|
||||
break;
|
||||
case EventTypes.tag:
|
||||
if (editorMessage.value) {
|
||||
@@ -398,9 +407,18 @@ export const useEditorEvents = (
|
||||
openLinkInBrowser(editorMessage.value as string);
|
||||
break;
|
||||
|
||||
case EventTypes.previewAttachment:
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
case EventTypes.previewAttachment: {
|
||||
const hash = (editorMessage.value as Attachment)?.hash;
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
if (attachment.metadata.type.startsWith("image/")) {
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
} else {
|
||||
eSendEvent("PDFPreview", attachment);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import { ThemeStore, useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eClearEditor, eOnLoadNote } from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { timeConverter } from "../../../utils/time";
|
||||
import { getFormattedDate } from "../../../utils/time";
|
||||
import { NoteType } from "../../../utils/types";
|
||||
import { onNoteCreated } from "../../notes/common";
|
||||
import Commands from "./commands";
|
||||
import { Content, EditorState, Note, SavePayload } from "./types";
|
||||
import {
|
||||
@@ -80,8 +81,8 @@ export const useEditor = (
|
||||
const lockedSessionId = useRef<string>();
|
||||
|
||||
const postMessage = useCallback(
|
||||
async <T>(type: string, data: T) =>
|
||||
await post(editorRef, sessionIdRef.current, type, data),
|
||||
async <T>(type: string, data: T, waitFor = 300) =>
|
||||
await post(editorRef, sessionIdRef.current, type, data, waitFor),
|
||||
[sessionIdRef]
|
||||
);
|
||||
|
||||
@@ -120,9 +121,13 @@ export const useEditor = (
|
||||
[editorId]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setLoading(false);
|
||||
} else {
|
||||
state.current.ready = false;
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const withTimer = useCallback(
|
||||
(id: string, fn: () => void, duration: number) => {
|
||||
@@ -194,10 +199,7 @@ export const useEditor = (
|
||||
sessionId: isContentInvalid(data) ? null : currentSessionHistoryId
|
||||
};
|
||||
|
||||
if (title) {
|
||||
noteData.title = title;
|
||||
}
|
||||
|
||||
noteData.title = title;
|
||||
if (data) {
|
||||
noteData.content = {
|
||||
data: data,
|
||||
@@ -209,12 +211,19 @@ export const useEditor = (
|
||||
id = await db.notes?.add(noteData);
|
||||
if (!note && id) {
|
||||
currentNote.current = db.notes?.note(id).data as NoteType;
|
||||
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
||||
const defaultNotebook = db.settings?.getDefaultNotebook();
|
||||
if (!state.current.onNoteCreated && defaultNotebook) {
|
||||
onNoteCreated(id, {
|
||||
type: defaultNotebook.topic ? "topic" : "notebook",
|
||||
id: defaultNotebook.id,
|
||||
notebook: defaultNotebook.topic
|
||||
});
|
||||
} else {
|
||||
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
||||
}
|
||||
|
||||
if (!noteData.title) {
|
||||
postMessage(
|
||||
EditorEvents.titleplaceholder,
|
||||
currentNote.current.title
|
||||
);
|
||||
postMessage(EditorEvents.title, currentNote.current.title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +248,7 @@ export const useEditor = (
|
||||
}
|
||||
if (id && sessionIdRef.current === currentSessionId) {
|
||||
note = db.notes?.note(id)?.data as Note;
|
||||
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
|
||||
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
|
||||
|
||||
lastContentChangeTime.current = note.dateEdited;
|
||||
|
||||
@@ -336,7 +345,6 @@ export const useEditor = (
|
||||
) => {
|
||||
state.current.currentlyEditing = true;
|
||||
const editorState = useEditorStore.getState();
|
||||
|
||||
if (item && item.type === "new") {
|
||||
currentNote.current && (await reset());
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
@@ -344,7 +352,7 @@ export const useEditor = (
|
||||
sessionIdRef.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
await commands.focus();
|
||||
if (state.current?.ready) await commands.focus();
|
||||
lastContentChangeTime.current = 0;
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
} else {
|
||||
@@ -358,7 +366,7 @@ export const useEditor = (
|
||||
!currentContent.current?.data ||
|
||||
currentContent.current?.data.length < 50000
|
||||
) {
|
||||
overlay(false);
|
||||
if (state.current.ready) overlay(false);
|
||||
} else {
|
||||
overlay(true);
|
||||
}
|
||||
@@ -370,9 +378,13 @@ export const useEditor = (
|
||||
commands.setSessionId(nextSessionId);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
currentNote.current = item as NoteType;
|
||||
await commands.setStatus(timeConverter(item.dateEdited), "Saved");
|
||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
await postMessage(EditorEvents.html, currentContent.current?.data);
|
||||
await postMessage(
|
||||
EditorEvents.html,
|
||||
currentContent.current?.data,
|
||||
10000
|
||||
);
|
||||
useEditorStore.getState().setReadonly(item.readonly);
|
||||
await commands.setTags(currentNote.current);
|
||||
commands.setSettings();
|
||||
@@ -453,7 +465,7 @@ export const useEditor = (
|
||||
if (note.tags !== currentNote.current.tags) {
|
||||
await commands.setTags(note);
|
||||
}
|
||||
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
|
||||
await commands.setStatus(getFormattedDate(note.dateEdited), "Saved");
|
||||
}
|
||||
|
||||
lock.current = false;
|
||||
@@ -578,6 +590,10 @@ export const useEditor = (
|
||||
lastContentChangeTime.current = Date.now();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
state.current.saveCount = 0;
|
||||
}, [sessionId, loading]);
|
||||
|
||||
const onReady = useCallback(async () => {
|
||||
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
|
||||
eSendEvent("webview_reset");
|
||||
@@ -586,30 +602,24 @@ export const useEditor = (
|
||||
}
|
||||
}, [isDefaultEditor, restoreEditorState]);
|
||||
|
||||
useEffect(() => {
|
||||
state.current.saveCount = 0;
|
||||
(async () => {
|
||||
await commands.setSessionId(sessionIdRef.current);
|
||||
if (sessionIdRef.current) {
|
||||
if (!state.current?.ready) return;
|
||||
await onReady();
|
||||
}
|
||||
})();
|
||||
}, [sessionId, loading, commands, onReady]);
|
||||
|
||||
const onLoad = useCallback(async () => {
|
||||
state.current.ready = true;
|
||||
onReady();
|
||||
postMessage(EditorEvents.theme, theme || useThemeStore.getState().colors);
|
||||
commands.setInsets(
|
||||
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
|
||||
);
|
||||
if (currentNote.current) {
|
||||
loadNote({ ...currentNote.current, forced: true });
|
||||
} else {
|
||||
await commands.setPlaceholder(placeholderTip.current);
|
||||
}
|
||||
commands.setSettings();
|
||||
if (currentNote.current) overlay(true);
|
||||
clearTimeout(timers.current["editor:loaded"]);
|
||||
timers.current["editor:loaded"] = setTimeout(async () => {
|
||||
postMessage(EditorEvents.theme, theme || useThemeStore.getState().colors);
|
||||
commands.setInsets(
|
||||
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
|
||||
);
|
||||
await commands.setSessionId(sessionIdRef.current);
|
||||
await onReady();
|
||||
await commands.setSettings();
|
||||
if (currentNote.current) {
|
||||
loadNote({ ...currentNote.current, forced: true });
|
||||
} else {
|
||||
await commands.setPlaceholder(placeholderTip.current);
|
||||
}
|
||||
state.current.ready = true;
|
||||
}, 300);
|
||||
}, [
|
||||
onReady,
|
||||
postMessage,
|
||||
@@ -617,7 +627,8 @@ export const useEditor = (
|
||||
commands,
|
||||
isDefaultEditor,
|
||||
insets,
|
||||
loadNote
|
||||
loadNote,
|
||||
overlay
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -73,7 +73,8 @@ export async function post<T>(
|
||||
ref: RefObject<WebView>,
|
||||
sessionId: string,
|
||||
type: string,
|
||||
value: T | null = null
|
||||
value: T | null = null,
|
||||
waitFor = 300
|
||||
) {
|
||||
if (!sessionId) {
|
||||
console.warn("post called without sessionId of type:", type);
|
||||
@@ -85,7 +86,7 @@ export async function post<T>(
|
||||
sessionId: sessionId
|
||||
};
|
||||
setImmediate(() => ref.current?.postMessage(JSON.stringify(message)));
|
||||
const response = await getResponse(type);
|
||||
const response = await getResponse(type, waitFor);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,8 @@ type WebviewResponseData = {
|
||||
};
|
||||
|
||||
export const getResponse = async (
|
||||
type: string
|
||||
type: string,
|
||||
waitFor = 300
|
||||
): Promise<WebviewResponseData | false> => {
|
||||
return new Promise((resolve) => {
|
||||
const callback = (data: WebviewResponseData) => {
|
||||
@@ -107,7 +109,7 @@ export const getResponse = async (
|
||||
eSubscribeEvent(type, callback);
|
||||
setTimeout(() => {
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
}, waitFor);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { editorRef } from "../../utils/global-refs";
|
||||
import { ProgressBar } from "./progress";
|
||||
import { editorController, editorState, textInput } from "./tiptap/utils";
|
||||
import { editorController, textInput } from "./tiptap/utils";
|
||||
export const EditorWrapper = ({ width }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
@@ -48,7 +48,6 @@ export const EditorWrapper = ({ width }) => {
|
||||
const keyboard = useKeyboard();
|
||||
|
||||
const onAppStateChanged = async (state) => {
|
||||
if (editorState().movedAway) return;
|
||||
if (state === "active") {
|
||||
editorController.current.onReady();
|
||||
editorController.current.overlay(false);
|
||||
|
||||
@@ -86,7 +86,7 @@ export const setOnFirstSave = (
|
||||
}, 0);
|
||||
};
|
||||
|
||||
async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
export async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
if (!params) return;
|
||||
switch (params.type) {
|
||||
case "notebook": {
|
||||
|
||||
@@ -28,7 +28,7 @@ import React, {
|
||||
import { ActivityIndicator, Linking, Platform, View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { db } from "../../common/database";
|
||||
import Storage from "../../common/database/storage";
|
||||
import DialogHeader from "../../components/dialog/dialog-header";
|
||||
|
||||
@@ -17,33 +17,36 @@ 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 React, { useState } from "react";
|
||||
import { Dimensions, LayoutAnimation, Platform, View } from "react-native";
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { SVG_Z } from "../../components/intro";
|
||||
import { WelcomeNotice } from "../../components/intro/welcome";
|
||||
import { AuthMode } from "../../components/auth";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { PressableButton } from "../../components/ui/pressable";
|
||||
import Seperator from "../../components/ui/seperator";
|
||||
import { SvgView } from "../../components/ui/svg";
|
||||
import { BouncingView } from "../../components/ui/transitions/bouncing-view";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import BiometicService from "../../services/biometrics";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { presentSheet, ToastEvent } from "../../services/event-manager";
|
||||
import {
|
||||
ToastEvent,
|
||||
eSendEvent,
|
||||
presentSheet
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { getElevation } from "../../utils";
|
||||
import { eOpenLoginDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
const AppLock = ({ route }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const appLockMode = useSettingStore((state) => state.settings.appLockMode);
|
||||
const [step, setStep] = useState(0);
|
||||
const welcome = route?.params?.welcome;
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
const modes = [
|
||||
{
|
||||
@@ -76,13 +79,12 @@ const AppLock = ({ route }) => {
|
||||
exiting={!welcome ? undefined : FadeOutUp}
|
||||
entering={!welcome ? undefined : FadeInDown}
|
||||
style={{
|
||||
justifyContent: !welcome ? undefined : "center",
|
||||
height: !welcome ? undefined : "100%",
|
||||
width: !welcome ? undefined : "100%"
|
||||
}}
|
||||
>
|
||||
{step === 0 ? (
|
||||
<>
|
||||
<>
|
||||
{!welcome ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
@@ -125,121 +127,163 @@ const AppLock = ({ route }) => {
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
<Seperator />
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
width: DDS.isTab && welcome ? "50%" : "100%",
|
||||
alignSelf: "center"
|
||||
flexGrow: 1,
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.nav,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%"
|
||||
}}
|
||||
>
|
||||
{modes.map((item) => (
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
type={appLockMode === item.value ? "grayBg" : "transparent"}
|
||||
onPress={async () => {
|
||||
if (
|
||||
!(await BiometicService.isBiometryAvailable()) &&
|
||||
!useUserStore.getState().user
|
||||
) {
|
||||
ToastEvent.show({
|
||||
heading: "Biometrics not enrolled",
|
||||
type: "error",
|
||||
message:
|
||||
"To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone or create an account."
|
||||
});
|
||||
return;
|
||||
}
|
||||
SettingsService.set({ appLockMode: item.value });
|
||||
}}
|
||||
customStyle={{
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
marginTop: 0,
|
||||
marginBottom: 12,
|
||||
borderWidth: 1,
|
||||
borderColor:
|
||||
appLockMode === item.value ? item.activeColor : colors.nav
|
||||
}}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
marginBottom: 10
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
color={
|
||||
appLockMode === item.value ? item.activeColor : colors.pri
|
||||
}
|
||||
style={{ maxWidth: "95%" }}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
<Paragraph
|
||||
color={
|
||||
appLockMode === item.value
|
||||
? item.activeColor
|
||||
: colors.icon
|
||||
}
|
||||
style={{ maxWidth: "95%" }}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
{item.desc}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
))}
|
||||
|
||||
{welcome && (
|
||||
<Button
|
||||
fontSize={SIZE.md}
|
||||
height={45}
|
||||
width={250}
|
||||
onPress={async () => {
|
||||
LayoutAnimation.configureNext({
|
||||
...LayoutAnimation.Presets.linear,
|
||||
delete: {
|
||||
duration: 50,
|
||||
property: "opacity",
|
||||
type: "linear"
|
||||
}
|
||||
});
|
||||
setStep(1);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 24,
|
||||
alignSelf: "center",
|
||||
borderRadius: 100,
|
||||
...getElevation(5),
|
||||
marginTop: 30
|
||||
}}
|
||||
type="accent"
|
||||
title="Next"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<WelcomeNotice />
|
||||
)}
|
||||
|
||||
{welcome && !colors.night ? (
|
||||
<BouncingView
|
||||
<View
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Heading
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
extraBold
|
||||
size={SIZE.xxl}
|
||||
>
|
||||
Protect your notes
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginBottom: 25
|
||||
}}
|
||||
>
|
||||
Choose how you want to secure your notes locally.
|
||||
</Paragraph>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Seperator />
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: DDS.isTab ? -300 : -130,
|
||||
zIndex: -1
|
||||
paddingHorizontal: 12,
|
||||
width: DDS.isTab && welcome ? "50%" : "100%",
|
||||
alignSelf: "center",
|
||||
flexGrow: 1
|
||||
}}
|
||||
animated={false}
|
||||
duration={3000}
|
||||
>
|
||||
<SvgView
|
||||
width={Dimensions.get("window").width}
|
||||
height={Dimensions.get("window").width}
|
||||
src={SVG_Z}
|
||||
/>
|
||||
</BouncingView>
|
||||
) : null}
|
||||
{modes.map((item) => (
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
type={appLockMode === item.value ? "grayBg" : "transparent"}
|
||||
onPress={async () => {
|
||||
if (
|
||||
!(await BiometicService.isBiometryAvailable()) &&
|
||||
!useUserStore.getState().user
|
||||
) {
|
||||
ToastEvent.show({
|
||||
heading: "Biometrics not enrolled",
|
||||
type: "error",
|
||||
message:
|
||||
"To use app lock, you must enable biometrics such as Fingerprint lock or Face ID on your phone or create an account."
|
||||
});
|
||||
return;
|
||||
}
|
||||
SettingsService.set({ appLockMode: item.value });
|
||||
}}
|
||||
customStyle={{
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
marginTop: 0,
|
||||
marginBottom: 12,
|
||||
borderWidth: 1,
|
||||
borderColor:
|
||||
appLockMode === item.value ? item.activeColor : colors.nav
|
||||
}}
|
||||
style={{
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
color={
|
||||
appLockMode === item.value ? item.activeColor : colors.pri
|
||||
}
|
||||
style={{ maxWidth: "95%" }}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{item.title}
|
||||
</Heading>
|
||||
<Paragraph
|
||||
color={
|
||||
appLockMode === item.value ? item.activeColor : colors.icon
|
||||
}
|
||||
style={{ maxWidth: "95%" }}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
{item.desc}
|
||||
</Paragraph>
|
||||
</PressableButton>
|
||||
))}
|
||||
|
||||
{welcome && (
|
||||
<Button
|
||||
fontSize={SIZE.md}
|
||||
width={250}
|
||||
onPress={async () => {
|
||||
eSendEvent(eOpenLoginDialog, AuthMode.welcomeSignup);
|
||||
setTimeout(() => {
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
});
|
||||
Navigation.replace(
|
||||
{
|
||||
name: "Notes"
|
||||
},
|
||||
{
|
||||
canGoBack: false
|
||||
}
|
||||
);
|
||||
}, 1000);
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: 24,
|
||||
alignSelf: "center",
|
||||
...getElevation(5),
|
||||
marginTop: 30,
|
||||
borderRadius: 100
|
||||
}}
|
||||
type="accent"
|
||||
title="Next"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,8 @@ import SoundPicker from "./sound-picker";
|
||||
import { Subscription } from "./subscription";
|
||||
import { TrashIntervalSelector } from "./trash-interval-selector";
|
||||
import { FontSelector } from "./font-selector";
|
||||
import { TitleFormat } from "./title-format";
|
||||
import { DateFormatSelector, TimeFormatSelector } from "./date-format";
|
||||
export const components: { [name: string]: ReactElement } = {
|
||||
colorpicker: <AccentColorPicker />,
|
||||
homeselector: <HomagePageSelector />,
|
||||
@@ -37,5 +39,8 @@ export const components: { [name: string]: ReactElement } = {
|
||||
"sound-picker": <SoundPicker />,
|
||||
licenses: <Licenses />,
|
||||
"trash-interval-selector": <TrashIntervalSelector />,
|
||||
"font-selector": <FontSelector />
|
||||
"font-selector": <FontSelector />,
|
||||
"title-format": <TitleFormat />,
|
||||
"date-format-selector": <DateFormatSelector />,
|
||||
"time-format-selector": <TimeFormatSelector />
|
||||
};
|
||||
|
||||
198
apps/mobile/app/screens/settings/date-format.jsx
Normal file
198
apps/mobile/app/screens/settings/date-format.jsx
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { PressableButton } from "../../components/ui/pressable";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
export const DateFormatSelector = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const menuRef = useRef();
|
||||
const [width, setWidth] = useState(0);
|
||||
const [dateFormat, setDateFormat] = useState(db.settings.getDateFormat());
|
||||
const onChange = (item) => {
|
||||
menuRef.current?.hide();
|
||||
db.settings.setDateFormat(item);
|
||||
setDateFormat(item);
|
||||
useSettingStore.setState({
|
||||
dateFormat: item
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{dateFormat} ({dayjs().format(dateFormat)})
|
||||
</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{DATE_FORMATS.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onPress={async () => {
|
||||
onChange(item);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: dateFormat === item ? colors.nav : "transparent",
|
||||
width: "100%",
|
||||
maxWidth: width
|
||||
}}
|
||||
textStyle={{
|
||||
fontSize: SIZE.md,
|
||||
color: dateFormat === item ? colors.accent : colors.pri
|
||||
}}
|
||||
>
|
||||
{item} ({dayjs().format(item)})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const TimeFormatSelector = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const menuRef = useRef();
|
||||
const [width, setWidth] = useState(0);
|
||||
const [timeFormat, setTimeFormat] = useState(db.settings.getTimeFormat());
|
||||
const onChange = (item) => {
|
||||
menuRef.current?.hide();
|
||||
db.settings.setTimeFormat(item);
|
||||
setTimeFormat(item);
|
||||
useSettingStore.setState({
|
||||
timeFormat: item
|
||||
});
|
||||
};
|
||||
|
||||
const TimeFormats = {
|
||||
"12-hour": "hh:mm A",
|
||||
"24-hour": "HH:mm"
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{timeFormat} ({dayjs().format(TimeFormats[timeFormat])})
|
||||
</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{TIME_FORMATS.map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onPress={async () => {
|
||||
onChange(item);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: timeFormat === item ? colors.nav : "transparent",
|
||||
width: "100%",
|
||||
maxWidth: width
|
||||
}}
|
||||
textStyle={{
|
||||
fontSize: SIZE.md,
|
||||
color: timeFormat === item ? colors.accent : colors.pri
|
||||
}}
|
||||
>
|
||||
{item} ({dayjs().format(TimeFormats[item])})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -18,12 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { LogMessage } from "@streetwriters/logger";
|
||||
import { LogMessage } from "@notesnook/logger";
|
||||
import { format, LogLevel, logManager } from "@notesnook/core/logger";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FlatList, Platform, TouchableOpacity, View } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import Storage from "../../common/database/storage";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
|
||||
@@ -60,8 +60,8 @@ export const Settings = () => {
|
||||
<SettingsStack.Navigator
|
||||
initialRouteName="SettingsHome"
|
||||
screenListeners={{
|
||||
beforeRemove: (e) => {
|
||||
if (e.target?.startsWith("SettingsGroup")) {
|
||||
focus: (e) => {
|
||||
if (e.target?.startsWith("SettingsHome-")) {
|
||||
useNavigationStore.getState().update({ name: "Settings" }, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,10 +268,10 @@ export const LICENSES = [
|
||||
link: "https://github.com/RocketChat/rn-extensions-share"
|
||||
},
|
||||
{
|
||||
name: "rn-fetch-blob",
|
||||
name: "react-native-blob-util",
|
||||
licenseType: "MIT",
|
||||
author: "Joltup",
|
||||
link: "https://github.com/joltup/rn-fetch-blob"
|
||||
author: "RonRadtke",
|
||||
link: "https://github.com/RonRadtke/react-native-blob-util"
|
||||
},
|
||||
{
|
||||
name: "react-native-gzip",
|
||||
|
||||
@@ -22,8 +22,8 @@ import {
|
||||
StackActions,
|
||||
useNavigation
|
||||
} from "@react-navigation/native";
|
||||
import React, { useRef } from "react";
|
||||
import { View, TextInput } from "react-native";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View, TextInput, ActivityIndicator } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
import Input from "../../components/ui/input";
|
||||
@@ -46,10 +46,14 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
const current = item.useHook && item.useHook(item);
|
||||
const isHidden = item.hidden && item.hidden(item.property || current);
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onChangeSettings = () => {
|
||||
const onChangeSettings = async () => {
|
||||
if (loading) return;
|
||||
if (item.modifer) {
|
||||
item.modifer(item.property || current);
|
||||
setLoading(true);
|
||||
await item.modifer(item.property || current);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!item.property) return;
|
||||
@@ -289,12 +293,12 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{item.type === "switch" && item.property && (
|
||||
{item.type === "switch" && !loading && (
|
||||
<ToggleSwitch
|
||||
isOn={
|
||||
item.getter
|
||||
? item.getter(item.property || current)
|
||||
: settings[item.property]
|
||||
: settings[item?.property as never]
|
||||
}
|
||||
onColor={colors.accent}
|
||||
offColor={colors.icon}
|
||||
@@ -303,6 +307,10 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
onToggle={onChangeSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<ActivityIndicator size={SIZE.xxl} color={colors.accent} />
|
||||
) : null}
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -179,17 +179,15 @@ export const settingsGroups: SettingSection[] = [
|
||||
sections: [
|
||||
{
|
||||
id: "enable-2fa",
|
||||
name: "Enable two-factor authentication",
|
||||
name: "Change primary two-factor authentication",
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
MFASheet.present();
|
||||
});
|
||||
},
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return !!(user as User)?.mfa?.isEnabled;
|
||||
},
|
||||
description: "Increased security for your account"
|
||||
description:
|
||||
"Change your current two-factor authentication method"
|
||||
},
|
||||
{
|
||||
id: "2fa-fallback",
|
||||
@@ -241,23 +239,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
},
|
||||
description:
|
||||
"View and save recovery codes for to recover your account"
|
||||
},
|
||||
{
|
||||
id: "disabled-2fa",
|
||||
name: "Disable two-factor authentication",
|
||||
modifer: () => {
|
||||
verifyUser("global", async () => {
|
||||
await db.mfa?.disable();
|
||||
const user = await db.user?.fetchUser();
|
||||
useUserStore.getState().setUser(user);
|
||||
});
|
||||
},
|
||||
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) => {
|
||||
return !(user as User)?.mfa?.isEnabled;
|
||||
},
|
||||
description: "Decreased security for your account"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -524,6 +505,20 @@ export const settingsGroups: SettingSection[] = [
|
||||
description: "Default screen to open on app startup",
|
||||
component: "homeselector"
|
||||
},
|
||||
{
|
||||
id: "date-format",
|
||||
name: "Date format",
|
||||
description: "Set the format for date used across the app",
|
||||
type: "component",
|
||||
component: "date-format-selector"
|
||||
},
|
||||
{
|
||||
id: "time-format",
|
||||
name: "Time format",
|
||||
description: "Set the format for time used across the app",
|
||||
type: "component",
|
||||
component: "time-format-selector"
|
||||
},
|
||||
{
|
||||
id: "clear-trash-interval",
|
||||
type: "component",
|
||||
@@ -531,6 +526,15 @@ export const settingsGroups: SettingSection[] = [
|
||||
description:
|
||||
"Select the duration after which trash items will be cleared",
|
||||
component: "trash-interval-selector"
|
||||
},
|
||||
{
|
||||
id: "default-notebook",
|
||||
name: "Clear default notebook/topic",
|
||||
description: "Clear the default notebook/topic for new notes",
|
||||
modifer: () => {
|
||||
db.settings?.setDefaultNotebook(undefined);
|
||||
},
|
||||
hidden: () => !db.settings?.getDefaultNotebook()
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -588,6 +592,13 @@ export const settingsGroups: SettingSection[] = [
|
||||
icon: "format-font",
|
||||
property: "defaultFontFamily",
|
||||
component: "font-selector"
|
||||
},
|
||||
{
|
||||
id: "title-format",
|
||||
name: "Title format",
|
||||
component: "title-format",
|
||||
description: "Customize the formatting for new note title",
|
||||
type: "component"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -606,6 +617,26 @@ export const settingsGroups: SettingSection[] = [
|
||||
"Contribute towards a better Notesnook. All tracking information is anonymous.",
|
||||
property: "telemetry"
|
||||
},
|
||||
{
|
||||
id: "marketing-emails",
|
||||
type: "switch",
|
||||
name: "Marketing emails",
|
||||
description:
|
||||
"We will send you occasional promotional offers & product updates on your email (sent once every month).",
|
||||
modifer: async () => {
|
||||
try {
|
||||
await db.user?.changeMarketingConsent(
|
||||
!useUserStore.getState().user?.marketingConsent
|
||||
);
|
||||
useUserStore.getState().setUser(await db.user?.fetchUser());
|
||||
} catch (e) {
|
||||
ToastEvent.error(e as Error);
|
||||
}
|
||||
},
|
||||
getter: (current: any) => current?.marketingConsent,
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (current) => !current
|
||||
},
|
||||
{
|
||||
id: "cors-bypass",
|
||||
type: "input",
|
||||
|
||||
63
apps/mobile/app/screens/settings/title-format.tsx
Normal file
63
apps/mobile/app/screens/settings/title-format.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
import Input from "../../components/ui/input";
|
||||
import React from "react";
|
||||
import { TextInput } from "react-native";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
|
||||
export const TitleFormat = () => {
|
||||
const [titleFormat] = useState(db.settings?.getTitleFormat());
|
||||
const inputRef = useRef<TextInput>();
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
onSubmit={(e) => {
|
||||
db.settings?.setTitleFormat(e.nativeEvent.text);
|
||||
}}
|
||||
onChangeText={(text) => {
|
||||
db.settings?.setTitleFormat(text);
|
||||
}}
|
||||
containerStyle={{ marginTop: 6 }}
|
||||
onLayout={() => {
|
||||
inputRef?.current?.setNativeProps({
|
||||
text: titleFormat
|
||||
});
|
||||
}}
|
||||
defaultValue={titleFormat}
|
||||
/>
|
||||
|
||||
<Paragraph style={{ marginTop: 2 }} color={colors.icon} size={SIZE.xs}>
|
||||
Use the following key to format the title:{"\n"}
|
||||
{"\n"}
|
||||
$date$: Current date.{"\n"}
|
||||
$time$: Current time.{"\n"}
|
||||
$timestamp$: Full date and time without any spaces or other symbols.
|
||||
(e.g 202305261253).{"\n"}
|
||||
$count$: Number of notes + 1.{"\n"}
|
||||
$headline$: Use starting line of the note as title.{"\n"}
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
};
|
||||
47
apps/mobile/app/services/app-review.ts
Normal file
47
apps/mobile/app/services/app-review.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import InAppReview from "react-native-in-app-review";
|
||||
import { DatabaseLogger } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import Config from "react-native-config";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
|
||||
const day_ms = 86400000;
|
||||
export function requestInAppReview() {
|
||||
if (Config.GITHUB_RELEASE === "true") return;
|
||||
|
||||
const time = MMKV.getMap<{ timestamp: number }>("requestInAppReview");
|
||||
|
||||
if (time?.timestamp && time?.timestamp + day_ms * 7 > Date.now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (InAppReview.isAvailable()) {
|
||||
useUserStore.getState().setShouldBlockVerifyUser(true);
|
||||
|
||||
InAppReview.RequestInAppReview()
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
DatabaseLogger.error(error);
|
||||
});
|
||||
MMKV.setMap("requestInAppReview", { timestamp: Date.now() });
|
||||
} else {
|
||||
DatabaseLogger.error(new Error("In App Review not available"));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import { Platform } from "react-native";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Share from "react-native-share";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import storage from "../common/database/storage";
|
||||
|
||||
@@ -22,7 +22,7 @@ import { zipSync } from "fflate";
|
||||
import { Platform } from "react-native";
|
||||
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { DatabaseLogger, db } from "../common/database/index";
|
||||
import Storage from "../common/database/storage";
|
||||
import { toTXT } from "../utils";
|
||||
|
||||
@@ -45,6 +45,7 @@ import { sleep } from "../utils/time";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useReminderStore } from "../stores/use-reminder-store";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
|
||||
export type Reminder = {
|
||||
id: string;
|
||||
@@ -193,7 +194,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
case "Hide":
|
||||
unpinQuickNote();
|
||||
break;
|
||||
case "ReplyInput":
|
||||
case "ReplyInput": {
|
||||
displayNotification({
|
||||
title: "Quick note",
|
||||
message: 'Tap on "Take note" to add a note.',
|
||||
@@ -211,9 +212,17 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
data: `<p>${input} </p>`
|
||||
}
|
||||
});
|
||||
await db.sync(false, false);
|
||||
const status = await NetInfo.fetch();
|
||||
if (status.isInternetReachable) {
|
||||
try {
|
||||
await db.sync(false, false);
|
||||
} catch (e) {
|
||||
console.log(e, (e as Error).stack);
|
||||
}
|
||||
}
|
||||
useNoteStore.getState().setNotes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ async function getProducts() {
|
||||
}
|
||||
|
||||
function get() {
|
||||
if (__DEV__ || Config.isTesting) return true;
|
||||
if (__DEV__ || Config.isTesting === "true") return true;
|
||||
|
||||
return SUBSCRIPTION_STATUS.BASIC !== premiumStatus;
|
||||
}
|
||||
@@ -154,9 +154,11 @@ const onUserStatusCheck = async (type) => {
|
||||
};
|
||||
break;
|
||||
case CHECK_IDS.notebookAdd:
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
}, 500);
|
||||
message = {
|
||||
context: "sheet",
|
||||
title: "Get Notesnook Pro",
|
||||
desc: "With Notesnook Pro you can create unlimited notebooks and do so much more! Get it now."
|
||||
};
|
||||
break;
|
||||
case CHECK_IDS.vaultAdd:
|
||||
message = {
|
||||
@@ -177,7 +179,6 @@ const onUserStatusCheck = async (type) => {
|
||||
const showVerifyEmailDialog = () => {
|
||||
presentSheet({
|
||||
title: "Confirm your email",
|
||||
icon: "email",
|
||||
paragraph:
|
||||
"We have sent you an email confirmation link. Please check your email inbox. If you cannot find the email, check your spam folder.",
|
||||
action: async () => {
|
||||
@@ -367,7 +368,7 @@ const features_list = [
|
||||
"Rich note editing experience with markdown, tables, checklists and more"
|
||||
},
|
||||
{
|
||||
content: "Export your notes in Pdf, markdown and html formats"
|
||||
content: "Export your notes in PDF, markdown and html formats"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -386,7 +387,7 @@ const sheet = (context, promo, trial) => {
|
||||
<Seperator />
|
||||
<CompactFeatures
|
||||
scrollRef={ref}
|
||||
maxHeight={300}
|
||||
maxHeight={400}
|
||||
features={features_list}
|
||||
vertical
|
||||
/>
|
||||
|
||||
@@ -36,14 +36,12 @@ export function validatePass(password) {
|
||||
SHORT_PASS: false
|
||||
};
|
||||
|
||||
if (password?.length <= 0) {
|
||||
if (password?.length < 8) {
|
||||
errors.SHORT_PASS = true;
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (password.length >= 8) {
|
||||
} else {
|
||||
errors.SHORT_PASS = false;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ export interface SettingStore extends State {
|
||||
setRequestBiometrics: (requestBiometrics: boolean) => void;
|
||||
insets: Insets;
|
||||
setInsets: (insets: Insets) => void;
|
||||
timeFormat: string;
|
||||
dateFormat: string;
|
||||
}
|
||||
|
||||
const { width, height } = Dimensions.get("window");
|
||||
@@ -170,6 +172,8 @@ export const useSettingStore = create<SettingStore>((set) => ({
|
||||
requestBiometrics: false,
|
||||
setRequestBiometrics: (requestBiometrics) => set({ requestBiometrics }),
|
||||
setInsets: (insets) => set({ insets }),
|
||||
timeFormat: "12-hour",
|
||||
dateFormat: "DD-MM-YYYY",
|
||||
insets: initialWindowMetrics?.insets
|
||||
? initialWindowMetrics.insets
|
||||
: { top: 0, right: 0, left: 0, bottom: 0 }
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface UserStore extends State {
|
||||
setLastSynced: (lastSynced: string) => void;
|
||||
verifyUser: boolean;
|
||||
setVerifyUser: (verified: boolean) => void;
|
||||
shouldBlockVerifyUser: boolean;
|
||||
setShouldBlockVerifyUser: (shouldBlockVerifyUser: boolean) => void;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserStore>((set) => ({
|
||||
@@ -52,5 +54,12 @@ export const useUserStore = create<UserStore>((set) => ({
|
||||
},
|
||||
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
|
||||
setVerifyUser: (verified) => set({ verifyUser: verified }),
|
||||
lastSyncStatus: SyncStatus.Never
|
||||
lastSyncStatus: SyncStatus.Never,
|
||||
shouldBlockVerifyUser: false,
|
||||
setShouldBlockVerifyUser: (shouldBlockVerifyUser) => {
|
||||
set({ shouldBlockVerifyUser });
|
||||
setTimeout(() => {
|
||||
set({ shouldBlockVerifyUser: false });
|
||||
}, 1000);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -18,7 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import { Monographs } from "../screens/notes/monographs";
|
||||
|
||||
export const IOS_APPGROUPID = "group.org.streetwriters.notesnook";
|
||||
export const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
|
||||
export const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
|
||||
|
||||
export const STORE_LINK =
|
||||
Platform.OS === "ios"
|
||||
@@ -139,6 +142,7 @@ export const MenuItemsList = [
|
||||
icon: "text-box-multiple-outline",
|
||||
close: true,
|
||||
func: () => {
|
||||
const Monographs = require("../screens/notes/monographs").Monographs;
|
||||
Monographs.navigate();
|
||||
}
|
||||
},
|
||||
|
||||
122
apps/mobile/app/utils/note-bundle.js
Normal file
122
apps/mobile/app/utils/note-bundle.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import { isImage } from "@notesnook/core/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { db } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { IOS_APPGROUPID } from "./constants";
|
||||
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
try {
|
||||
let exists = db.attachments.exists(hash);
|
||||
let encryptionInfo;
|
||||
if (options?.hash && options.hash !== hash) return false;
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
uri: uri,
|
||||
type: options.type || "url",
|
||||
hash: hash,
|
||||
appGroupId: options.appGroupId
|
||||
});
|
||||
encryptionInfo.type = type;
|
||||
encryptionInfo.filename = filename;
|
||||
encryptionInfo.alg = "xcha-stream";
|
||||
encryptionInfo.size = encryptionInfo.length;
|
||||
encryptionInfo.key = key;
|
||||
if (options?.reupload && exists) await db.attachments.reset(hash);
|
||||
} else {
|
||||
encryptionInfo = { hash: hash };
|
||||
}
|
||||
await db.attachments.add(encryptionInfo, options?.id);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (Platform.OS === "ios") RNFetchBlob.fs.unlink(uri).catch(console.log);
|
||||
console.log("attach file error: ", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function createNotes() {
|
||||
const bundles = MMKV.getArray("shared:noteBundles") || [];
|
||||
console.log("creating notes from note bundles", bundles);
|
||||
|
||||
for (let i = 0; i < bundles.length; i++) {
|
||||
const bundle = bundles[i];
|
||||
const id = await db.notes.add(bundle.note);
|
||||
for (const item of bundle.notebooks) {
|
||||
if (item.type === "notebook") {
|
||||
db.relations.add(item, { id, type: "note" });
|
||||
} else {
|
||||
db.notes.addToNotebook(
|
||||
{
|
||||
id: item.notebookId,
|
||||
topic: item.id
|
||||
},
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of bundle.files) {
|
||||
const uri =
|
||||
Platform.OS === "ios"
|
||||
? `${file.value.replace("file://", "")}`
|
||||
: `${file.value}`;
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "cache"
|
||||
});
|
||||
await attachFile(uri, hash, file.type, file.name, {
|
||||
type: "cache",
|
||||
id: id,
|
||||
appGroupId: IOS_APPGROUPID
|
||||
});
|
||||
let content = ``;
|
||||
if (isImage(file.type)) {
|
||||
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
|
||||
} else {
|
||||
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
|
||||
}
|
||||
const rawContent = await db.content.raw(
|
||||
db.notes.note(id).data?.contentId
|
||||
);
|
||||
await db.notes.add({
|
||||
id: id,
|
||||
content: {
|
||||
type: "tiptap",
|
||||
data: rawContent?.data ? rawContent?.data + content : content
|
||||
},
|
||||
sessionId: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
const _bundles = [...bundles];
|
||||
_bundles.splice(i, 1);
|
||||
MMKV.setArray("shared:noteBundles", _bundles);
|
||||
}
|
||||
}
|
||||
|
||||
export const NoteBundle = {
|
||||
createNotes
|
||||
};
|
||||
@@ -17,6 +17,10 @@ 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 { formatDate } from "@notesnook/core/utils/date";
|
||||
import { db } from "../../common/database";
|
||||
import { formatReminderTime } from "@notesnook/core/collections/reminders";
|
||||
|
||||
export const sleep = (duration: number) =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(true), duration));
|
||||
|
||||
@@ -107,3 +111,21 @@ export const timeConverter = (timestamp: number | undefined | null) => {
|
||||
|
||||
return time;
|
||||
};
|
||||
|
||||
export function getFormattedDate(
|
||||
date: any,
|
||||
type: "time" | "date-time" | "date" = "date-time"
|
||||
) {
|
||||
return formatDate(date, {
|
||||
dateFormat: db.settings?.getDateFormat() as string,
|
||||
timeFormat: db.settings?.getTimeFormat() as string,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
|
||||
export function getFormattedReminderTime(reminder: any, short = false) {
|
||||
return formatReminderTime(reminder, short, {
|
||||
dateFormat: db.settings?.getDateFormat() as string,
|
||||
timeFormat: db.settings?.getTimeFormat() as string
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2045
|
||||
versionCode 2051
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -276,6 +276,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="text/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
@@ -51,8 +56,7 @@
|
||||
android:theme="@style/BootTheme"
|
||||
android:largeHeap="true"
|
||||
android:supportsRtl="false"
|
||||
tools:replace="android:supportsRtl"
|
||||
android:usesCleartextTraffic="true">
|
||||
tools:replace="android:supportsRtl">
|
||||
|
||||
<receiver android:exported="false" android:name=".NoteWidget">
|
||||
<intent-filter>
|
||||
@@ -109,13 +113,17 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/AppThemeB">
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -73,4 +73,6 @@ allprojects {
|
||||
// url("${project(':react-native-background-fetch').projectDir}/libs")
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
- Sync notes created from share extension & notification quick notes without requiring to launch the app
|
||||
- Fix formatting is lost when sharing notes to other apps
|
||||
- Fix editor would be blank after entering the app from background
|
||||
- Small bug fixes and ux improvements
|
||||
- Better compact mode for notes & notebooks in lists
|
||||
- Add all standard date formats
|
||||
- Add new template $timestamp$ for full date & time
|
||||
- Allow changing primary 2fa method in settings
|
||||
- Hide note headlines in share & home widgets
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -7,6 +7,15 @@ import Config from 'react-native-config';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import appJson from './app.json';
|
||||
import Notifications from "../app/services/notifications";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
NetInfo.configure({
|
||||
reachabilityUrl: "https://notesnook.com",
|
||||
reachabilityTest: (response) => {
|
||||
if (!response) return false;
|
||||
return response?.status >= 200 && response?.status < 300;
|
||||
}
|
||||
});
|
||||
|
||||
Notifications.init();
|
||||
|
||||
const appName = appJson.name;
|
||||
|
||||
@@ -33,10 +33,16 @@
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>5</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>10</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
@@ -46,6 +52,7 @@
|
||||
</dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
<string>OpenSans-SemiBold.ttf</string>
|
||||
<string>OpenSans-Regular.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
@@ -57,5 +64,7 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>appGroupId</key>
|
||||
<string>group.org.streetwriters.notesnook</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -6,5 +6,9 @@
|
||||
<array>
|
||||
<string>group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
|
||||
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
|
||||
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
|
||||
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
|
||||
659670B32A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
|
||||
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
|
||||
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
|
||||
65AA857B25E6DDEC00772A01 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857A25E6DDEC00772A01 /* SwiftUI.framework */; };
|
||||
@@ -116,6 +118,7 @@
|
||||
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
|
||||
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
|
||||
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
|
||||
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
|
||||
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
|
||||
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -294,6 +297,7 @@
|
||||
6510E6D62877215700DACAA9 /* build.bundle */,
|
||||
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */,
|
||||
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */,
|
||||
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */,
|
||||
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */,
|
||||
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */,
|
||||
13B07FAE1A68108700A75B9A /* Notesnook */,
|
||||
@@ -538,6 +542,7 @@
|
||||
files = (
|
||||
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */,
|
||||
65D145C529DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
|
||||
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */,
|
||||
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
|
||||
@@ -575,6 +580,7 @@
|
||||
files = (
|
||||
6510E6D82877215700DACAA9 /* build.bundle in Resources */,
|
||||
65D145C629DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
|
||||
659670B32A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
|
||||
6510627727042896009661C3 /* OpenSans-SemiBold.ttf in Resources */,
|
||||
6510627527042893009661C3 /* OpenSans-Regular.ttf in Resources */,
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
|
||||
@@ -993,7 +999,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1067,7 +1073,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1097,7 +1103,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
@@ -1170,7 +1176,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1328,7 +1334,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1340,7 +1346,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1370,7 +1376,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1382,7 +1388,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1411,7 +1417,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1485,7 +1491,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1515,7 +1521,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2037;
|
||||
CURRENT_PROJECT_VERSION = 2043;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1589,7 +1595,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.14;
|
||||
MARKETING_VERSION = 2.5.2;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -151,5 +151,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>appGroupId</key>
|
||||
<string>group.org.streetwriters.notesnook</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -10,5 +10,9 @@
|
||||
<array>
|
||||
<string>group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -11,5 +11,9 @@
|
||||
<array>
|
||||
<string>group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.streetwriters.notesnook</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
PODS:
|
||||
- Base64 (1.1.2)
|
||||
- BEMCheckBox (1.4.1)
|
||||
- boost (1.76.0)
|
||||
- callstack-repack (3.0.0):
|
||||
- callstack-repack (3.2.0):
|
||||
- JWTDecode (~> 3.0)
|
||||
- React-Core
|
||||
- SwiftyRSA
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.69.7)
|
||||
- FBReactNativeSpec (0.69.7):
|
||||
@@ -15,6 +18,7 @@ PODS:
|
||||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- GZIP (1.3.0)
|
||||
- JWTDecode (3.0.1)
|
||||
- MMKV (1.2.13):
|
||||
- MMKVCore (~> 1.2.13)
|
||||
- MMKVCore (1.2.13)
|
||||
@@ -234,9 +238,11 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-begin-background-task (0.1.0):
|
||||
- React
|
||||
- react-native-config (1.4.11):
|
||||
- react-native-config/App (= 1.4.11)
|
||||
- react-native-config/App (1.4.11):
|
||||
- react-native-blob-util (0.17.3):
|
||||
- React-Core
|
||||
- react-native-config (1.5.1):
|
||||
- react-native-config/App (= 1.5.1)
|
||||
- react-native-config/App (1.5.1):
|
||||
- React-Core
|
||||
- react-native-date-picker (4.2.6):
|
||||
- React-Core
|
||||
@@ -244,7 +250,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-fingerprint-scanner (5.0.0):
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.8.0):
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- React-Core
|
||||
- react-native-gzip (1.0.0):
|
||||
- Base64
|
||||
@@ -256,26 +262,32 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-image-resizer (3.0.5):
|
||||
- React-Core
|
||||
- react-native-in-app-review (4.3.3):
|
||||
- React-Core
|
||||
- react-native-keep-awake (1.1.0):
|
||||
- React-Core
|
||||
- react-native-mmkv-storage (0.8.0):
|
||||
- MMKV (= 1.2.13)
|
||||
- React-Core
|
||||
- react-native-netinfo (9.3.7):
|
||||
- react-native-netinfo (9.3.10):
|
||||
- React-Core
|
||||
- react-native-notification-sounds (0.5.5):
|
||||
- React
|
||||
- react-native-orientation (3.1.3):
|
||||
- React
|
||||
- react-native-safe-area-context (4.4.1):
|
||||
- react-native-pdf (6.6.2):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.5.3):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-sodium (1.3.0):
|
||||
- react-native-share-extension (2.5.5):
|
||||
- React
|
||||
- react-native-webview (11.23.1):
|
||||
- react-native-sodium (1.4.1):
|
||||
- React
|
||||
- react-native-webview (11.26.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.7)
|
||||
- React-RCTActionSheet (0.69.7):
|
||||
@@ -343,17 +355,14 @@ PODS:
|
||||
- React-jsi (= 0.69.7)
|
||||
- React-logger (= 0.69.7)
|
||||
- React-perflogger (= 0.69.7)
|
||||
- rn-extensions-share (2.4.0):
|
||||
- RNBootSplash (4.1.4):
|
||||
- React-Core
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- RNCCheckbox (0.5.15):
|
||||
- BEMCheckBox (~> 1.4)
|
||||
- React-Core
|
||||
- RNBootSplash (4.3.2):
|
||||
- RNCClipboard (1.11.2):
|
||||
- React-Core
|
||||
- RNCCheckbox (0.5.12):
|
||||
- React-Core
|
||||
- RNCClipboard (1.11.1):
|
||||
- React-Core
|
||||
- RNCMaskedView (0.2.8):
|
||||
- RNCMaskedView (0.2.9):
|
||||
- React-Core
|
||||
- RNDateTimePicker (6.6.0):
|
||||
- React-Core
|
||||
@@ -365,7 +374,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNFlashList (1.4.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.7.1):
|
||||
- RNGestureHandler (2.10.1):
|
||||
- React-Core
|
||||
- RNIap (7.5.6):
|
||||
- React-Core
|
||||
@@ -405,21 +414,32 @@ PODS:
|
||||
- React-RCTText
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNScreens (3.18.2):
|
||||
- RNScreens (3.20.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSecureRandom (1.0.1):
|
||||
- React
|
||||
- RNShare (7.9.1):
|
||||
- React-Core
|
||||
- RNSVG (12.4.4):
|
||||
- RNSVG (12.5.1):
|
||||
- React-Core
|
||||
- RNTooltips (1.0.3):
|
||||
- pop (~> 1.0)
|
||||
- React
|
||||
- SexyTooltip
|
||||
- RNZipArchive (6.0.9):
|
||||
- React-Core
|
||||
- RNZipArchive/Core (= 6.0.9)
|
||||
- SSZipArchive (~> 2.2)
|
||||
- RNZipArchive/Core (6.0.9):
|
||||
- React-Core
|
||||
- SSZipArchive (~> 2.2)
|
||||
- SexyTooltip (1.2.5):
|
||||
- pop (~> 1.0)
|
||||
- SSZipArchive (2.4.3)
|
||||
- SwiftyRSA (1.7.0):
|
||||
- SwiftyRSA/ObjC (= 1.7.0)
|
||||
- SwiftyRSA/ObjC (1.7.0)
|
||||
- toolbar-android (0.2.1):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
@@ -451,6 +471,7 @@ DEPENDENCIES:
|
||||
- react-native-actions-shortcuts (from `../../node_modules/react-native-actions-shortcuts`)
|
||||
- react-native-background-actions (from `../../node_modules/react-native-background-actions`)
|
||||
- react-native-begin-background-task (from `../../node_modules/react-native-begin-background-task`)
|
||||
- react-native-blob-util (from `../../node_modules/react-native-blob-util`)
|
||||
- react-native-config (from `../../node_modules/react-native-config`)
|
||||
- react-native-date-picker (from `../../node_modules/react-native-date-picker`)
|
||||
- react-native-document-picker (from `../../node_modules/react-native-document-picker`)
|
||||
@@ -460,12 +481,15 @@ DEPENDENCIES:
|
||||
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
|
||||
- react-native-image-picker (from `../../node_modules/react-native-image-picker`)
|
||||
- "react-native-image-resizer (from `../../node_modules/@bam.tech/react-native-image-resizer`)"
|
||||
- react-native-in-app-review (from `../../node_modules/react-native-in-app-review`)
|
||||
- "react-native-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
|
||||
- react-native-mmkv-storage (from `../../node_modules/react-native-mmkv-storage`)
|
||||
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-notification-sounds (from `../../node_modules/react-native-notification-sounds`)
|
||||
- react-native-orientation (from `../../node_modules/react-native-orientation`)
|
||||
- react-native-pdf (from `../../node_modules/react-native-pdf`)
|
||||
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-share-extension (from `../../node_modules/@ammarahmed/react-native-share-extension`)"
|
||||
- "react-native-sodium (from `../../node_modules/@ammarahmed/react-native-sodium`)"
|
||||
- react-native-webview (from `../../node_modules/react-native-webview`)
|
||||
- React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
@@ -480,8 +504,6 @@ DEPENDENCIES:
|
||||
- React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`)
|
||||
- React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`)
|
||||
- ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
|
||||
- rn-extensions-share (from `../../node_modules/rn-extensions-share`)
|
||||
- rn-fetch-blob (from `../../node_modules/rn-fetch-blob`)
|
||||
- RNBootSplash (from `../../node_modules/react-native-bootsplash`)
|
||||
- "RNCCheckbox (from `../../node_modules/@react-native-community/checkbox`)"
|
||||
- "RNCClipboard (from `../../node_modules/@react-native-clipboard/clipboard`)"
|
||||
@@ -502,6 +524,7 @@ DEPENDENCIES:
|
||||
- RNShare (from `../../node_modules/react-native-share`)
|
||||
- RNSVG (from `../../node_modules/react-native-svg`)
|
||||
- RNTooltips (from `../../node_modules/react-native-tooltips`)
|
||||
- RNZipArchive (from `../../node_modules/react-native-zip-archive`)
|
||||
- SexyTooltip (from `https://github.com/ammarahm-ed/SexyTooltip.git`)
|
||||
- "toolbar-android (from `../../node_modules/@react-native-community/toolbar-android`)"
|
||||
- Yoga (from `../../node_modules/react-native/ReactCommon/yoga`)
|
||||
@@ -509,11 +532,15 @@ DEPENDENCIES:
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Base64
|
||||
- BEMCheckBox
|
||||
- fmt
|
||||
- GZIP
|
||||
- JWTDecode
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- pop
|
||||
- SSZipArchive
|
||||
- SwiftyRSA
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
@@ -562,6 +589,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-background-actions"
|
||||
react-native-begin-background-task:
|
||||
:path: "../../node_modules/react-native-begin-background-task"
|
||||
react-native-blob-util:
|
||||
:path: "../../node_modules/react-native-blob-util"
|
||||
react-native-config:
|
||||
:path: "../../node_modules/react-native-config"
|
||||
react-native-date-picker:
|
||||
@@ -580,6 +609,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-image-picker"
|
||||
react-native-image-resizer:
|
||||
:path: "../../node_modules/@bam.tech/react-native-image-resizer"
|
||||
react-native-in-app-review:
|
||||
:path: "../../node_modules/react-native-in-app-review"
|
||||
react-native-keep-awake:
|
||||
:path: "../../node_modules/@sayem314/react-native-keep-awake"
|
||||
react-native-mmkv-storage:
|
||||
@@ -590,8 +621,12 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-notification-sounds"
|
||||
react-native-orientation:
|
||||
:path: "../../node_modules/react-native-orientation"
|
||||
react-native-pdf:
|
||||
:path: "../../node_modules/react-native-pdf"
|
||||
react-native-safe-area-context:
|
||||
:path: "../../node_modules/react-native-safe-area-context"
|
||||
react-native-share-extension:
|
||||
:path: "../../node_modules/@ammarahmed/react-native-share-extension"
|
||||
react-native-sodium:
|
||||
:path: "../../node_modules/@ammarahmed/react-native-sodium"
|
||||
react-native-webview:
|
||||
@@ -620,10 +655,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native/ReactCommon/runtimeexecutor"
|
||||
ReactCommon:
|
||||
:path: "../../node_modules/react-native/ReactCommon"
|
||||
rn-extensions-share:
|
||||
:path: "../../node_modules/rn-extensions-share"
|
||||
rn-fetch-blob:
|
||||
:path: "../../node_modules/rn-fetch-blob"
|
||||
RNBootSplash:
|
||||
:path: "../../node_modules/react-native-bootsplash"
|
||||
RNCCheckbox:
|
||||
@@ -664,6 +695,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-svg"
|
||||
RNTooltips:
|
||||
:path: "../../node_modules/react-native-tooltips"
|
||||
RNZipArchive:
|
||||
:path: "../../node_modules/react-native-zip-archive"
|
||||
SexyTooltip:
|
||||
:git: https://github.com/ammarahm-ed/SexyTooltip.git
|
||||
toolbar-android:
|
||||
@@ -678,14 +711,16 @@ CHECKOUT OPTIONS:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
|
||||
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
callstack-repack: 9e5425dfffeda7ea87b71729c4097141c7d8ce1c
|
||||
callstack-repack: 3e48a96824e0e0411ae1f48749a2ab103aa62a3a
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: 6b7f5692909b4300d50e7359cdefbcd09dd30faa
|
||||
FBReactNativeSpec: f53cf57758c70c6bfba5230b739cd1071e7a6824
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
|
||||
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
|
||||
MMKV: aac95d817a100479445633f2b3ed8961b4ac5043
|
||||
MMKVCore: 3388952ded307e41b3ed8a05892736a236ed1b8e
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
@@ -706,23 +741,27 @@ SPEC CHECKSUMS:
|
||||
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
|
||||
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
|
||||
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
|
||||
react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b
|
||||
react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6
|
||||
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
||||
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
|
||||
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
|
||||
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
|
||||
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
|
||||
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
|
||||
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
|
||||
react-native-keep-awake: acbee258db16483744910f0da3ace39eb9ab47fd
|
||||
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
|
||||
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
|
||||
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
|
||||
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
|
||||
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
|
||||
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
|
||||
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
|
||||
react-native-share-extension: 8fa247810cc533c9d3bab6913f62ce7aec9bcf4a
|
||||
react-native-sodium: f4e3986ddcb73482f8679e534b448a0675d0cf13
|
||||
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
|
||||
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
|
||||
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
|
||||
React-RCTAnimation: 9ced26ad20b96e532ac791a8ab92a7b1ce2266b8
|
||||
@@ -735,29 +774,30 @@ SPEC CHECKSUMS:
|
||||
React-RCTVibration: 600a9f8b3537db360563d50fab3d040c262567d4
|
||||
React-runtimeexecutor: 65cd2782a57e1d59a68aa5d504edf94278578e41
|
||||
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
|
||||
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
|
||||
RNCCheckbox: ed1b4ca295475b41e7251ebae046360a703b6eb5
|
||||
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||
RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a
|
||||
RNBootSplash: de2c568373a9c79a66e9918b8929eb6c9a35246f
|
||||
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
|
||||
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
|
||||
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
|
||||
RNDateTimePicker: 818672afa85519722533d017b832ed09539d9ddb
|
||||
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
|
||||
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
|
||||
RNGestureHandler: 42ec7c28dd02d540ed6c9159c57a98ff016492dc
|
||||
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
|
||||
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
||||
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
|
||||
RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674
|
||||
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
||||
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
|
||||
RNZipArchive: 68a0c6db4b1c103f846f1559622050df254a3ade
|
||||
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
|
||||
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
||||
Yoga: 0b84a956f7393ef1f37f3bb213c516184e4a689d
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@callstack/repack": "^3.0.0",
|
||||
"@callstack/repack": "^3.2.0",
|
||||
"@react-native-clipboard/clipboard": "^1.9.0",
|
||||
"@react-native-community/checkbox": "^0.5.8",
|
||||
"@react-native-community/netinfo": "^9.3.7",
|
||||
@@ -20,7 +20,7 @@
|
||||
"react-native": "0.69.7",
|
||||
"react-native-background-actions": "^2.6.6",
|
||||
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
|
||||
"react-native-bootsplash": "^4.1.4",
|
||||
"react-native-bootsplash": "4.1.4",
|
||||
"react-native-config": "^1.4.6",
|
||||
"react-native-device-info": "^8.4.1",
|
||||
"react-native-document-picker": "^7.1.1",
|
||||
@@ -43,13 +43,10 @@
|
||||
"react-native-screens": "^3.13.1",
|
||||
"react-native-securerandom": "^1.0.1",
|
||||
"react-native-share": "^7.2.0",
|
||||
"@ammarahmed/react-native-sodium": "1.3.0",
|
||||
"@ammarahmed/react-native-sodium": "1.4.1",
|
||||
"react-native-svg": "^12.3.0",
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "^11.14.1",
|
||||
"rn-extensions-share": "^2.4.0",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"react-native-gzip":"1.0.0",
|
||||
"@shopify/flash-list":"1.4.0",
|
||||
"@ammarahmed/notifee-react-native": "7.4.4",
|
||||
@@ -59,7 +56,13 @@
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-actions-shortcuts": "^1.0.1"
|
||||
"react-native-actions-shortcuts": "^1.0.1",
|
||||
"react-native-zip-archive": "6.0.9",
|
||||
"react-native-vector-icons": "9.2.0",
|
||||
"react-native-pdf": "6.6.2",
|
||||
"react-native-blob-util": "0.17.3",
|
||||
"@ammarahmed/react-native-share-extension": "^2.5.5",
|
||||
"react-native-in-app-review": "4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
@@ -98,6 +101,7 @@
|
||||
"react-test-renderer": "18.0.0",
|
||||
"terser-webpack-plugin": "^5.3.5",
|
||||
"ts-jest": "^28.0.7",
|
||||
"webpack": "^5.74.0"
|
||||
"webpack": "^5.74.0",
|
||||
"react-refresh": "0.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user