refactor: run prettier on the whole codebase

This commit is contained in:
thecodrr
2022-08-26 16:19:39 +05:00
parent 054c973c99
commit e898ef5d86
570 changed files with 13058 additions and 10818 deletions

View File

@@ -1,6 +1,14 @@
# all
build
coverage
node_modules
dist
native/
public/an.js
# mobile
apps/mobile/native/
# web
apps/web/public/an.js
# editor
packages/editor/styles/

View File

@@ -1,16 +1,16 @@
module.exports = {
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
env: {
browser: true,
es2021: true,
'react-native/react-native': true
"react-native/react-native": true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended'
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
parserOptions: {
ecmaFeatures: {
@@ -18,22 +18,31 @@ module.exports = {
},
ecmaVersion: 12,
es6: true,
sourceType: 'module'
sourceType: "module"
},
plugins: ['react', 'react-native', 'prettier', 'unused-imports', '@typescript-eslint'],
plugins: [
"react",
"react-native",
"prettier",
"unused-imports",
"@typescript-eslint"
],
rules: {
'react/display-name': 0,
'no-unused-vars': 'off',
'react/no-unescaped-entities': 'off',
'unused-imports/no-unused-vars': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'prefer-const': 'off',
'no-empty': 'off',
'react/prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', 'ts', 'tsx'] }],
'prettier/prettier': [
'error',
"react/display-name": 0,
"no-unused-vars": "off",
"react/no-unescaped-entities": "off",
"unused-imports/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": "off",
"prefer-const": "off",
"no-empty": "off",
"react/prop-types": 0,
"react/jsx-filename-extension": [
1,
{ extensions: [".js", ".jsx", "ts", "tsx"] }
],
"prettier/prettier": [
"error",
{},
{
usePrettierrc: true

View File

@@ -28,13 +28,13 @@ jobs:
- uses: actions/setup-node@master
with:
node-version: '16'
node-version: "16"
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: '11'
architecture: 'x64'
java-version: "11"
architecture: "x64"
- name: Install node modules
run: |

View File

@@ -28,13 +28,13 @@ jobs:
- uses: actions/setup-node@master
with:
node-version: '16'
node-version: "16"
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: '11'
architecture: 'x64'
java-version: "11"
architecture: "x64"
- name: Install node modules
run: |

View File

@@ -1,13 +0,0 @@
module.exports = {
arrowParens: 'avoid',
bracketSpacing: true,
jsxBracketSameLine: false,
jsxSingleQuote: false,
quoteProps: 'as-needed',
singleQuote: true,
semi: true,
printWidth: 100,
useTabs: false,
tabWidth: 2,
trailingComma: 'none'
};

View File

@@ -4,8 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Android",
"cwd": "${workspaceFolder}",
@@ -27,4 +25,4 @@
"request": "attach"
}
]
}
}

View File

@@ -1 +1 @@
{}
{}

View File

@@ -2,21 +2,21 @@
* @format
*/
import React from 'react';
import 'react-native';
import React from "react";
import "react-native";
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import Heading from '../app/components/ui/typography/heading';
import Paragraph from '../app/components/ui/typography/paragraph';
import renderer from "react-test-renderer";
import Heading from "../app/components/ui/typography/heading";
import Paragraph from "../app/components/ui/typography/paragraph";
it('Heading renders correctly', done => {
it("Heading renders correctly", (done) => {
let instance = renderer.create(<Heading>Heading</Heading>);
expect(instance.root.props.children).toBe('Heading');
expect(instance.root.props.children).toBe("Heading");
done();
});
it('Paragraph renders correctly', done => {
it("Paragraph renders correctly", (done) => {
let instance = renderer.create(<Paragraph>Paragraph</Paragraph>);
expect(instance.root.props.children).toBe('Paragraph');
expect(instance.root.props.children).toBe("Paragraph");
done();
});

View File

@@ -1,14 +1,14 @@
import React, { useEffect } from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { withErrorBoundry } from './components/exception-handler';
import Launcher from './components/launcher';
import { ApplicationHolder } from './navigation';
import Notifications from './services/notifications';
import SettingsService from './services/settings';
import { TipManager } from './services/tip-manager';
import { useUserStore } from './stores/use-user-store';
import { useAppEvents } from './hooks/use-app-events';
import React, { useEffect } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { withErrorBoundry } from "./components/exception-handler";
import Launcher from "./components/launcher";
import { ApplicationHolder } from "./navigation";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
import { TipManager } from "./services/tip-manager";
import { useUserStore } from "./stores/use-user-store";
import { useAppEvents } from "./hooks/use-app-events";
SettingsService.init();
SettingsService.checkOrientation();
@@ -16,7 +16,7 @@ const App = () => {
useAppEvents();
useEffect(() => {
let { appLockMode } = SettingsService.get();
if (appLockMode && appLockMode !== 'none') {
if (appLockMode && appLockMode !== "none") {
useUserStore.getState().setVerifyUser(true);
}
setTimeout(() => {
@@ -39,4 +39,4 @@ const App = () => {
);
};
export default withErrorBoundry(App, 'App');
export default withErrorBoundry(App, "App");

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,12 @@
import { Platform } from 'react-native';
import { MMKV } from '../database/mmkv';
import { useSettingStore } from '../../stores/use-setting-store';
import { Platform } from "react-native";
import { MMKV } from "../database/mmkv";
import { useSettingStore } from "../../stores/use-setting-store";
const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`;
const baseUrl = `https://analytics.streetwriters.co/api/collect`;
const UA =
Platform.OS === 'ios'
Platform.OS === "ios"
? `Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1`
: `
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
@@ -19,37 +19,37 @@ Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHT
*/
async function canUpdateAnalytics(route, conditions = []) {
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
let eventsList = MMKV.getString('notesnookUserEvents');
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
}
if (eventsList && eventsList[route]) {
console.log('analytics: event already sent', route);
console.log("analytics: event already sent", route);
return false;
}
if (route !== '/welcome') {
if (route !== "/welcome") {
for (let cond of conditions) {
if (!eventsList || !eventsList[cond]) {
console.log('analytics: conditions not met for event', route, cond);
console.log("analytics: conditions not met for event", route, cond);
return false;
}
}
}
console.log('analytics: will send event', route);
console.log("analytics: will send event", route);
return true;
}
async function saveAnalytics(route, value = true) {
let eventsList = MMKV.getString('notesnookUserEvents');
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
} else {
eventsList = {};
}
eventsList[route] = value;
MMKV.setString('notesnookUserEvents', JSON.stringify(eventsList));
MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList));
}
/**
@@ -61,7 +61,12 @@ async function saveAnalytics(route, value = true) {
* @returns
*/
async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = true) {
async function pageView(
route,
prevRoute = "",
conditions = ["/welcome"],
once = true
) {
if (__DEV__) return;
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
let body = {
@@ -70,22 +75,22 @@ async function pageView(route, prevRoute = '', conditions = ['/welcome'], once =
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
hostname: `notesnook-${Platform.OS}`,
language: 'en-US',
screen: '1920x1080'
language: "en-US",
screen: "1920x1080"
},
type: 'pageview'
type: "pageview"
};
try {
let response = await fetch(baseUrl, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
'User-Agent': UA
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});
console.log('analytics: event sent', route);
console.log("analytics: event sent", route);
await saveAnalytics(route);
return await response.text();
} catch (e) {
@@ -99,22 +104,22 @@ async function sendEvent(type, value, once = true) {
let body = {
payload: {
website: WEBSITE_ID,
url: '/',
url: "/",
event_type: type,
event_value: value,
hostname: 'notesnook-android-app',
language: 'en-US',
screen: '1920x1080'
hostname: "notesnook-android-app",
language: "en-US",
screen: "1920x1080"
},
type: 'event'
type: "event"
};
try {
let response = await fetch(baseUrl, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
'User-Agent': UA
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});

View File

@@ -1,8 +1,8 @@
import { Platform } from 'react-native';
import 'react-native-get-random-values';
import * as Keychain from 'react-native-keychain';
import { generateSecureRandom } from 'react-native-securerandom';
import Sodium from 'react-native-sodium';
import { Platform } from "react-native";
import "react-native-get-random-values";
import * as Keychain from "react-native-keychain";
import { generateSecureRandom } from "react-native-securerandom";
import Sodium from "react-native-sodium";
const KEYSTORE_CONFIG = Platform.select({
ios: {
@@ -14,15 +14,23 @@ const KEYSTORE_CONFIG = Platform.select({
export async function deriveCryptoKey(name, data) {
try {
let credentials = await Sodium.deriveKey(data.password, data.salt);
await Keychain.setInternetCredentials('notesnook', name, credentials.key, KEYSTORE_CONFIG);
await Keychain.setInternetCredentials(
"notesnook",
name,
credentials.key,
KEYSTORE_CONFIG
);
return credentials.key;
} catch (e) {}
}
export async function getCryptoKey(name) {
try {
if (await Keychain.hasInternetCredentials('notesnook')) {
let credentials = await Keychain.getInternetCredentials('notesnook', KEYSTORE_CONFIG);
if (await Keychain.hasInternetCredentials("notesnook")) {
let credentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
return credentials.password;
} else {
return null;
@@ -32,7 +40,7 @@ export async function getCryptoKey(name) {
export async function removeCryptoKey(name) {
try {
let result = await Keychain.resetInternetCredentials('notesnook');
let result = await Keychain.resetInternetCredentials("notesnook");
return result;
} catch (e) {}
}
@@ -51,7 +59,7 @@ export async function generateCryptoKey(password, salt) {
let credentials = await Sodium.deriveKey(password, salt || null);
return credentials;
} catch (e) {
console.log('generateCryptoKey: ', e);
console.log("generateCryptoKey: ", e);
}
}
@@ -61,30 +69,32 @@ export function getAlgorithm(base64Variant) {
export async function decrypt(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === '' && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let _data = { ...data };
_data.output = 'plain';
_data.output = "plain";
return await Sodium.decrypt(password, _data);
}
export function parseAlgorithm(alg) {
if (!alg) return {};
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split('-');
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
return {
encryptionAlgorithm: enc,
kdfAlgorithm: kdf,
compressionAlgorithm: compressionAlg,
isCompress: compressed === '1',
isCompress: compressed === "1",
base64_variant: base64variant
};
}
export async function encrypt(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === '' && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let message = {
type: 'plain',
type: "plain",
data: data
};
let result = await Sodium.encrypt(password, message);

View File

@@ -1,12 +1,17 @@
import Database from '@streetwriters/notesnook-core/api/index';
import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger';
import { Platform } from 'react-native';
import { MMKVLoader } from 'react-native-mmkv-storage';
import filesystem from '../filesystem';
import EventSource from '../../utils/sse/even-source-ios';
import AndroidEventSource from '../../utils/sse/event-source';
import Storage, { KV } from './storage';
const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize();
import Database from "@streetwriters/notesnook-core/api/index";
import {
initalize,
logger as dbLogger
} from "@streetwriters/notesnook-core/logger";
import { Platform } from "react-native";
import { MMKVLoader } from "react-native-mmkv-storage";
import filesystem from "../filesystem";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import Storage, { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
console.log(LoggerStorage);
initalize(new KV(LoggerStorage));
export const DatabaseLogger = dbLogger;
@@ -16,18 +21,18 @@ export const DatabaseLogger = dbLogger;
*/
export var db = new Database(
Storage,
Platform.OS === 'ios' ? EventSource : AndroidEventSource,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem
);
db.host(
__DEV__
? {
API_HOST: 'https://api.notesnook.com',
AUTH_HOST: 'https://auth.streetwriters.co',
SSE_HOST: 'https://events.streetwriters.co',
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
ISSUES_HOST: 'https://issues.streetwriters.co'
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: 'http://192.168.10.29:5264',
// AUTH_HOST: 'http://192.168.10.29:8264',
// SSE_HOST: 'http://192.168.10.29:7264',
@@ -35,11 +40,11 @@ db.host(
// ISSUES_HOST: 'http://192.168.10.29:2624'
}
: {
API_HOST: 'https://api.notesnook.com',
AUTH_HOST: 'https://auth.streetwriters.co',
SSE_HOST: 'https://events.streetwriters.co',
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
ISSUES_HOST: 'https://issues.streetwriters.co'
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
}
);

View File

@@ -1,8 +1,10 @@
import { Platform } from 'react-native';
import MMKVStorage, { ProcessingModes } from 'react-native-mmkv-storage';
import { Platform } from "react-native";
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
export const MMKV = new MMKVStorage.Loader()
.setProcessingMode(
Platform.OS === 'ios' ? ProcessingModes.MULTI_PROCESS : ProcessingModes.SINGLE_PROCESS
Platform.OS === "ios"
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.initialize();

View File

@@ -1,5 +1,5 @@
import { Platform } from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
import { Platform } from "react-native";
import RNFetchBlob from "rn-fetch-blob";
import {
decrypt,
deriveCryptoKey,
@@ -9,8 +9,8 @@ import {
getRandomBytes,
hash,
removeCryptoKey
} from './encryption';
import { MMKV } from './mmkv';
} from "./encryption";
import { MMKV } from "./mmkv";
export class KV {
storage = null;
@@ -30,7 +30,10 @@ export class KV {
}
async write(key, data) {
this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data));
this.storage.setString(
key,
typeof data === "string" ? data : JSON.stringify(data)
);
return true;
}
@@ -64,12 +67,12 @@ export class KV {
async getAllKeys() {
let keys = (await this.storage.indexer.getKeys()) || [];
keys = keys.filter(
k =>
k !== 'stringIndex' &&
k !== 'boolIndex' &&
k !== 'mapIndex' &&
k !== 'arrayIndex' &&
k !== 'numberIndex' &&
(k) =>
k !== "stringIndex" &&
k !== "boolIndex" &&
k !== "mapIndex" &&
k !== "arrayIndex" &&
k !== "numberIndex" &&
k !== this.storage.instanceID
);
return keys;
@@ -79,14 +82,14 @@ export class KV {
const DefaultStorage = new KV(MMKV);
async function requestPermission() {
if (Platform.OS === 'ios') return true;
if (Platform.OS === "ios") return true;
return true;
}
async function checkAndCreateDir(path) {
let dir =
Platform.OS === 'ios'
Platform.OS === "ios"
? RNFetchBlob.fs.dirs.DocumentDir + path
: RNFetchBlob.fs.dirs.SDCardDir + '/Notesnook/' + path;
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
try {
let exists = await RNFetchBlob.fs.exists(dir);
@@ -102,10 +105,10 @@ async function checkAndCreateDir(path) {
}
export default {
read: key => DefaultStorage.read(key),
read: (key) => DefaultStorage.read(key),
write: (key, value) => DefaultStorage.write(key, value),
readMulti: keys => DefaultStorage.readMulti(keys),
remove: key => DefaultStorage.remove(key),
readMulti: (keys) => DefaultStorage.readMulti(keys),
remove: (key) => DefaultStorage.remove(key),
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
encrypt,

View File

@@ -1,48 +1,51 @@
import React from 'react';
import { Platform } from 'react-native';
import * as ScopedStorage from 'react-native-scoped-storage';
import Sodium from 'react-native-sodium';
import RNFetchBlob from 'rn-fetch-blob';
import { ShareComponent } from '../../components/sheets/export-notes/share';
import { useAttachmentStore } from '../../stores/use-attachment-store';
import { presentSheet, ToastEvent } from '../../services/event-manager';
import { db } from '../database';
import Storage from '../database/storage';
import { cacheDir, fileCheck } from './utils';
import hosts from '@streetwriters/notesnook-core/utils/constants';
import NetInfo from '@react-native-community/netinfo';
import React from "react";
import { Platform } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";
import Sodium from "react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, fileCheck } from "./utils";
import hosts from "@streetwriters/notesnook-core/utils/constants";
import NetInfo from "@react-native-community/netinfo";
export async function downloadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
console.log('downloading file: ', filename, url);
console.log("downloading file: ", filename, url);
let path = `${cacheDir}/${filename}`;
try {
let exists = await RNFetchBlob.fs.exists(path);
if (exists) {
console.log('file is downloaded');
console.log("file is downloaded");
return true;
}
let res = await fetch(url, {
method: 'GET',
method: "GET",
headers
});
if (!res.ok) throw new Error(`${res.status}: Unable to resolve download url`);
if (!res.ok)
throw new Error(`${res.status}: Unable to resolve download url`);
const downloadUrl = await res.text();
if (!downloadUrl) throw new Error('Unable to resolve download url');
if (!downloadUrl) throw new Error("Unable to resolve download url");
let totalSize = 0;
let request = RNFetchBlob.config({
path: path,
IOSBackgroundTask: true
})
.fetch('GET', downloadUrl, null)
.fetch("GET", downloadUrl, null)
.progress((recieved, total) => {
useAttachmentStore.getState().setProgress(0, total, filename, recieved, 'download');
useAttachmentStore
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
console.log('downloading: ', recieved, total);
console.log("downloading: ", recieved, total);
});
cancelToken.cancel = request.cancel;
@@ -53,21 +56,21 @@ export async function downloadFile(filename, data, cancelToken) {
return status >= 200 && status < 300;
} catch (e) {
ToastEvent.show({
heading: 'Error downloading file',
heading: "Error downloading file",
message: e.message,
type: 'error',
context: 'global'
type: "error",
context: "global"
});
ToastEvent.show({
heading: 'Error downloading file',
heading: "Error downloading file",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log('download file error: ', e, url, headers);
console.log("download file error: ", e, url, headers);
return false;
}
}
@@ -75,24 +78,30 @@ export async function downloadFile(filename, data, cancelToken) {
export async function downloadAttachment(hash, global = true) {
let attachment = db.attachments.attachment(hash);
if (!attachment) {
console.log('attachment not found');
console.log("attachment not found");
return;
}
let folder = {};
if (Platform.OS === 'android') {
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir('/downloads/');
folder.uri = await Storage.checkAndCreateDir("/downloads/");
}
try {
await db.fs.downloadFile(attachment.metadata.hash, attachment.metadata.hash);
if (!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))) return;
await db.fs.downloadFile(
attachment.metadata.hash,
attachment.metadata.hash
);
if (
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
)
return;
let key = await db.attachments.decryptKey(attachment.key);
console.log('attachment key', key);
console.log("attachment key", key);
let info = {
iv: attachment.iv,
salt: attachment.salt,
@@ -108,37 +117,48 @@ export async function downloadAttachment(hash, global = true) {
let fileUri = await Sodium.decryptFile(key, info, false);
ToastEvent.show({
heading: 'Download successful',
message: attachment.metadata.filename + ' downloaded',
type: 'success'
heading: "Download successful",
message: attachment.metadata.filename + " downloaded",
type: "success"
});
if (attachment.dateUploaded) {
console.log('Deleting attachment after download', attachment.dateUploaded);
console.log(
"Deleting attachment after download",
attachment.dateUploaded
);
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
}
if (Platform.OS === 'ios') {
if (Platform.OS === "ios") {
fileUri = folder.uri + `/${attachment.metadata.filename}`;
}
console.log('saved file uri: ', fileUri);
console.log("saved file uri: ", fileUri);
presentSheet({
title: `File downloaded`,
paragraph: `${attachment.metadata.filename} saved to ${
Platform.OS === 'android' ? 'selected path' : 'File Manager/Notesnook/downloads'
Platform.OS === "android"
? "selected path"
: "File Manager/Notesnook/downloads"
}`,
icon: 'download',
icon: "download",
context: global ? null : attachment.metadata.hash,
component: <ShareComponent uri={fileUri} name={attachment.metadata.filename} padding={12} />
component: (
<ShareComponent
uri={fileUri}
name={attachment.metadata.filename}
padding={12}
/>
)
});
return fileUri;
} catch (e) {
console.log('download attachment error: ', e);
console.log("download attachment error: ", e);
if (attachment.dateUploaded) {
console.log('Deleting attachment on error', attachment.dateUploaded);
console.log("Deleting attachment on error", attachment.dateUploaded);
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
@@ -152,26 +172,27 @@ export async function getUploadedFileSize(hash) {
const token = await db.user.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: 'HEAD',
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(attachmentInfo.headers?.get('content-length'));
console.log('contentLength:', contentLength, attachmentInfo.headers);
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
console.log("contentLength:", contentLength, attachmentInfo.headers);
return isNaN(contentLength) ? 0 : contentLength;
}
export async function checkAttachment(hash) {
const internetState = await NetInfo.fetch();
const isInternetReachable = internetState.isConnected && internetState.isInternetReachable;
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
if (!isInternetReachable) return { success: true };
const attachment = db.attachments.attachment(hash);
if (!attachment) return { failed: 'Attachment not found.' };
if (!attachment) return { failed: "Attachment not found." };
try {
const size = await getUploadedFileSize(hash);
if (size <= 0) return { failed: 'File length is 0.' };
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e?.message };
}

View File

@@ -1,7 +1,18 @@
import { downloadAttachment, downloadFile, getUploadedFileSize, checkAttachment } from './download';
import { clearFileStorage, deleteFile, exists, readEncrypted, writeEncrypted } from './io';
import { uploadFile } from './upload';
import { cancelable } from './utils';
import {
downloadAttachment,
downloadFile,
getUploadedFileSize,
checkAttachment
} from "./download";
import {
clearFileStorage,
deleteFile,
exists,
readEncrypted,
writeEncrypted
} from "./io";
import { uploadFile } from "./upload";
import { cancelable } from "./utils";
export default {
readEncrypted,

View File

@@ -1,7 +1,7 @@
import { Platform } from 'react-native';
import Sodium from 'react-native-sodium';
import RNFetchBlob from 'rn-fetch-blob';
import { cacheDir, getRandomId } from './utils';
import { Platform } from "react-native";
import Sodium from "react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { cacheDir, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
let path = `${cacheDir}/${filename}`;
@@ -19,28 +19,28 @@ export async function readEncrypted(filename, key, cipherData) {
},
true
);
console.log('output length: ', output?.length);
console.log("output length: ", output?.length);
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log(e);
console.log('error');
console.log("error");
return false;
}
}
export async function writeEncrypted(filename, { data, type, key }) {
console.log('file input: ', { type, key });
let filepath = cacheDir + `/${getRandomId('imagecache_')}`;
console.log("file input: ", { type, key });
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
console.log(filepath);
await RNFetchBlob.fs.writeFile(filepath, data, 'base64');
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
uri: Platform.OS === 'ios' ? filepath : `file://` + filepath,
type: 'url'
uri: Platform.OS === "ios" ? filepath : `file://` + filepath,
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log('encrypted file output: ', output);
console.log("encrypted file output: ", output);
return {
...output,
alg: `xcha-stream`
@@ -57,7 +57,7 @@ export async function deleteFile(filename, data) {
let { url, headers } = data;
try {
let response = await RNFetchBlob.fetch('DELETE', url, headers);
let response = await RNFetchBlob.fetch("DELETE", url, headers);
let status = response.info().status;
let ok = status >= 200 && status < 300;
if (ok) {
@@ -65,7 +65,7 @@ export async function deleteFile(filename, data) {
}
return ok;
} catch (e) {
console.log('delete file: ', e, url, headers);
console.log("delete file: ", e, url, headers);
return false;
}
}

View File

@@ -1,36 +1,38 @@
import RNFetchBlob from 'rn-fetch-blob';
import { useAttachmentStore } from '../../stores/use-attachment-store';
import { db } from '../database';
import { cacheDir } from './utils';
import RNFetchBlob from "rn-fetch-blob";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir } from "./utils";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
console.log('uploading file: ', filename, headers);
console.log("uploading file: ", filename, headers);
try {
let res = await fetch(url, {
method: 'PUT',
method: "PUT",
headers
});
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');
if (!uploadUrl) throw new Error("Unable to resolve upload url");
let request = RNFetchBlob.config({
IOSBackgroundTask: true
})
.fetch(
'PUT',
"PUT",
uploadUrl,
{
'content-type': ''
"content-type": ""
},
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
)
.uploadProgress((sent, total) => {
useAttachmentStore.getState().setProgress(sent, total, filename, 0, 'upload');
console.log('uploading: ', sent, total);
useAttachmentStore
.getState()
.setProgress(sent, total, filename, 0, "upload");
console.log("uploading: ", sent, total);
});
cancelToken.cancel = request.cancel;
let response = await request;
@@ -42,7 +44,7 @@ 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 (!attachment.metadata.type.startsWith("image/")) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
}
@@ -50,7 +52,7 @@ export async function uploadFile(filename, data, cancelToken) {
return result;
} catch (e) {
useAttachmentStore.getState().remove(filename);
console.log('upload file: ', e, url, headers);
console.log("upload file: ", e, url, headers);
return false;
}
}

View File

@@ -1,25 +1,28 @@
import RNFetchBlob from 'rn-fetch-blob';
import RNFetchBlob from "rn-fetch-blob";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
export function getRandomId(prefix) {
return Math.random()
.toString(36)
.replace('0.', prefix || '');
.replace("0.", prefix || "");
}
export function extractValueFromXmlTag(code, xml) {
if (!xml.includes(code)) return `Unknown ${code}`;
return xml.slice(xml.indexOf(`<${code}>`) + code.length + 2, xml.indexOf(`</${code}>`));
return xml.slice(
xml.indexOf(`<${code}>`) + code.length + 2,
xml.indexOf(`</${code}>`)
);
}
export async function fileCheck(response, totalSize) {
if (totalSize < 1000) {
let text = await response.text();
if (text.startsWith('<?xml')) {
if (text.startsWith("<?xml")) {
let errorJson = {
Code: extractValueFromXmlTag('Code', text),
Message: extractValueFromXmlTag('Message', text)
Code: extractValueFromXmlTag("Code", text),
Message: extractValueFromXmlTag("Message", text)
};
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
}

View File

@@ -3,7 +3,10 @@ function info(context: string, ...logs: any[]) {
}
function error(context: string, ...logs: any[]) {
console.log(`${new Date().toLocaleDateString()}::error::${context}: `, ...logs);
console.log(
`${new Date().toLocaleDateString()}::error::${context}: `,
...logs
);
}
type Logger = {

View File

@@ -1,20 +1,20 @@
import React from 'react';
import { FlatList, View } from 'react-native';
import { useSelectionStore } from '../../stores/use-selection-store';
import { useMessageStore } from '../../stores/use-message-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { allowedOnPlatform, renderItem } from './functions';
import React from "react";
import { FlatList, View } from "react-native";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { allowedOnPlatform, renderItem } from "./functions";
export const Announcement = ({ color }) => {
const colors = useThemeStore(state => state.colors);
const announcements = useMessageStore(state => state.announcements);
const colors = useThemeStore((state) => state.colors);
const announcements = useMessageStore((state) => state.announcements);
let announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore(state => state.selectionMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
return !announcement || selectionMode ? null : (
<View
style={{
backgroundColor: colors.bg,
width: '100%',
width: "100%",
paddingHorizontal: 12,
paddingTop: 12,
paddingBottom: 12
@@ -22,9 +22,9 @@ export const Announcement = ({ color }) => {
>
<View
style={{
width: '100%',
width: "100%",
borderRadius: 10,
overflow: 'hidden',
overflow: "hidden",
backgroundColor: colors.nav,
paddingBottom: 12
}}
@@ -32,12 +32,19 @@ export const Announcement = ({ color }) => {
<View>
<FlatList
style={{
width: '100%',
width: "100%",
marginTop: 12
}}
data={announcement?.body.filter(item => allowedOnPlatform(item.platforms))}
data={announcement?.body.filter((item) =>
allowedOnPlatform(item.platforms)
)}
renderItem={({ item, index }) =>
renderItem({ item: item, index: index, color: colors[color], inline: true })
renderItem({
item: item,
index: index,
color: colors[color],
inline: true
})
}
/>
</View>

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { useThemeStore } from '../../stores/use-theme-store';
import Paragraph from '../ui/typography/paragraph';
import { getStyle } from './functions';
import React from "react";
import { useThemeStore } from "../../stores/use-theme-store";
import Paragraph from "../ui/typography/paragraph";
import { getStyle } from "./functions";
export const Body = ({ text, style = {} }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<Paragraph

View File

@@ -1,30 +1,34 @@
import React from 'react';
import { Linking, View } from 'react-native';
import React from "react";
import { Linking, View } from "react-native";
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
import { eSendEvent, presentSheet } from '../../services/event-manager';
import Sync from '../../services/sync';
import { useThemeStore } from '../../stores/use-theme-store';
import { eCloseAnnouncementDialog, eCloseProgressDialog } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { PricingPlans } from '../premium/pricing-plans';
import SheetProvider from '../sheet-provider';
import { Progress } from '../sheets/progress';
import { Button } from '../ui/button';
import { allowedOnPlatform, getStyle } from './functions';
import { eSendEvent, presentSheet } from "../../services/event-manager";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import {
eCloseAnnouncementDialog,
eCloseProgressDialog
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { PricingPlans } from "../premium/pricing-plans";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import { allowedOnPlatform, getStyle } from "./functions";
export const Cta = ({ actions, style = {}, color, inline }) => {
const colors = useThemeStore(state => state.colors);
let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || [];
const colors = useThemeStore((state) => state.colors);
let buttons =
actions.filter((item) => allowedOnPlatform(item.platforms)) || [];
const onPress = async item => {
const onPress = async (item) => {
if (!inline) {
eSendEvent(eCloseAnnouncementDialog);
await sleep(500);
}
if (item.type === 'link') {
if (item.type === "link") {
Linking.openURL(item.data).catch(console.log);
} else if (item.type === 'promo') {
} else if (item.type === "promo") {
presentSheet({
component: (
<PricingPlans
@@ -36,11 +40,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
/>
)
});
} else if (item.type === 'force-sync') {
} else if (item.type === "force-sync") {
eSendEvent(eCloseProgressDialog);
await sleep(300);
Progress.present();
Sync.run('global', true, true, () => {
Sync.run("global", true, true, () => {
eSendEvent(eCloseProgressDialog);
});
}
@@ -50,7 +54,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
style={{
paddingHorizontal: 12,
...getStyle(style),
flexDirection: inline ? 'row' : 'column'
flexDirection: inline ? "row" : "column"
}}
>
<SheetProvider context="premium_cta" />
@@ -58,20 +62,20 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
{inline ? (
<>
{buttons.length > 0 &&
buttons.slice(0, 1).map(item => (
buttons.slice(0, 1).map((item) => (
<Button
key={item.title}
title={item.title}
fontSize={SIZE.sm}
type="transparent"
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
onPress={() => onPress(item)}
bold
style={{
height: 30,
alignSelf: 'flex-start',
alignSelf: "flex-start",
paddingHorizontal: 0,
marginTop: -6
}}
@@ -89,13 +93,13 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
width={null}
height={30}
style={{
alignSelf: 'flex-start',
alignSelf: "flex-start",
paddingHorizontal: 0,
marginTop: -6,
marginLeft: 12
}}
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
/>
))}
@@ -103,7 +107,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
) : (
<>
{buttons.length > 0 &&
buttons.slice(0, 1).map(item => (
buttons.slice(0, 1).map((item) => (
<Button
key={item.title}
title={item.title}
@@ -134,11 +138,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
width={null}
height={30}
style={{
minWidth: '50%',
minWidth: "50%",
marginTop: 5
}}
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
/>
))}

View File

@@ -1,17 +1,17 @@
import React from 'react';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import Paragraph from '../ui/typography/paragraph';
import { getStyle } from './functions';
import React from "react";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { getStyle } from "./functions";
export const Description = ({ text, style = {}, inline }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<Paragraph
style={{
marginHorizontal: 12,
...getStyle(style),
textAlign: inline ? 'left' : style?.textAlign
textAlign: inline ? "left" : style?.textAlign
}}
size={inline ? SIZE.sm : SIZE.md}
>

View File

@@ -1,18 +1,18 @@
import React, { Fragment } from 'react';
import { View } from 'react-native';
import { allowedPlatforms } from '../../stores/use-message-store';
import { ProFeatures } from '../dialogs/result/pro-features';
import { Body } from './body';
import { Cta } from './cta';
import { Description } from './description';
import { List } from './list';
import { Photo } from './photo';
import { SubHeading } from './subheading';
import { Title } from './title';
import React, { Fragment } from "react";
import { View } from "react-native";
import { allowedPlatforms } from "../../stores/use-message-store";
import { ProFeatures } from "../dialogs/result/pro-features";
import { Body } from "./body";
import { Cta } from "./cta";
import { Description } from "./description";
import { List } from "./list";
import { Photo } from "./photo";
import { SubHeading } from "./subheading";
import { Title } from "./title";
export function allowedOnPlatform(platforms) {
if (!platforms) return true;
return platforms.some(platform => allowedPlatforms.indexOf(platform) > -1);
return platforms.some((platform) => allowedPlatforms.indexOf(platform) > -1);
}
export const margins = {
@@ -21,12 +21,12 @@ export const margins = {
2: 20
};
export const getStyle = style => {
export const getStyle = (style) => {
if (!style) return {};
return {
marginTop: margins[style.marginTop] || 0,
marginBottom: margins[style.marginBottom] || 0,
textAlign: style.textAlign || 'left'
textAlign: style.textAlign || "left"
};
};
@@ -35,8 +35,8 @@ const Features = () => {
<View
style={{
paddingHorizontal: 12,
alignItems: 'center',
width: '100%'
alignItems: "center",
width: "100%"
}}
>
<ProFeatures />

View File

@@ -1,18 +1,24 @@
import React, { useEffect, useState } from 'react';
import { FlatList, View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { useMessageStore } from '../../stores/use-message-store';
import { DDS } from '../../services/device-detection';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { eCloseAnnouncementDialog, eOpenAnnouncementDialog } from '../../utils/events';
import BaseDialog from '../dialog/base-dialog';
import { allowedOnPlatform, renderItem } from './functions';
import React, { useEffect, useState } from "react";
import { FlatList, View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { useMessageStore } from "../../stores/use-message-store";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import {
eCloseAnnouncementDialog,
eOpenAnnouncementDialog
} from "../../utils/events";
import BaseDialog from "../dialog/base-dialog";
import { allowedOnPlatform, renderItem } from "./functions";
export const AnnouncementDialog = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [info, setInfo] = useState(null);
const remove = useMessageStore(state => state.remove);
const remove = useMessageStore((state) => state.remove);
useEffect(() => {
eSubscribeEvent(eOpenAnnouncementDialog, open);
@@ -23,7 +29,7 @@ export const AnnouncementDialog = () => {
};
}, [visible]);
const open = data => {
const open = (data) => {
setInfo(data);
setVisible(true);
};
@@ -46,11 +52,11 @@ export const AnnouncementDialog = () => {
>
<View
style={{
width: DDS.isTab ? 600 : '100%',
width: DDS.isTab ? 600 : "100%",
backgroundColor: colors.bg,
maxHeight: DDS.isTab ? '90%' : '100%',
maxHeight: DDS.isTab ? "90%" : "100%",
borderRadius: DDS.isTab ? 10 : 0,
overflow: 'hidden',
overflow: "hidden",
marginBottom: DDS.isTab ? 20 : 0,
borderTopRightRadius: 10,
borderTopLeftRadius: 10
@@ -58,9 +64,9 @@ export const AnnouncementDialog = () => {
>
<FlatList
style={{
width: '100%'
width: "100%"
}}
data={info?.body.filter(item => allowedOnPlatform(item.platforms))}
data={info?.body.filter((item) => allowedOnPlatform(item.platforms))}
renderItem={renderItem}
/>

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useThemeStore } from '../../stores/use-theme-store';
import Paragraph from '../ui/typography/paragraph';
import { getStyle } from './functions';
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeStore } from "../../stores/use-theme-store";
import Paragraph from "../ui/typography/paragraph";
import { getStyle } from "./functions";
export const List = ({ items, listType, style = {} }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<View
style={{
paddingHorizontal: 12,
paddingLeft: listType === 'ordered' ? 25 : 25,
paddingLeft: listType === "ordered" ? 25 : 25,
...getStyle(style)
}}
>
@@ -21,10 +21,10 @@ export const List = ({ items, listType, style = {} }) => {
key={item.text}
style={{
paddingVertical: 6,
flexDirection: 'row'
flexDirection: "row"
}}
>
{listType === 'ordered' ? (
{listType === "ordered" ? (
<Paragraph
style={{
marginRight: 5

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Image } from 'react-native';
import { getStyle } from './functions';
import React from "react";
import { Image } from "react-native";
import { getStyle } from "./functions";
export const Photo = ({ src, style = {} }) => {
return src ? (
@@ -8,9 +8,9 @@ export const Photo = ({ src, style = {} }) => {
source={{ uri: src }}
resizeMode="cover"
style={{
width: '100%',
width: "100%",
height: 200,
alignSelf: 'center',
alignSelf: "center",
...getStyle(style)
}}
/>

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import Heading from '../ui/typography/heading';
import { getStyle } from './functions';
import React from "react";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import { getStyle } from "./functions";
export const SubHeading = ({ text, style = {} }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<Heading

View File

@@ -1,22 +1,22 @@
import React from 'react';
import { View } from 'react-native';
import { useMessageStore } from '../../stores/use-message-store';
import { SIZE } from '../../utils/size';
import { Button } from '../ui/button';
import Heading from '../ui/typography/heading';
import { getStyle } from './functions';
import React from "react";
import { View } from "react-native";
import { useMessageStore } from "../../stores/use-message-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import { getStyle } from "./functions";
export const Title = ({ text, style = {}, inline }) => {
const announcements = useMessageStore(state => state.announcements);
const announcements = useMessageStore((state) => state.announcements);
let announcement = announcements.length > 0 ? announcements[0] : null;
const remove = useMessageStore(state => state.remove);
const remove = useMessageStore((state) => state.remove);
return inline ? (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: inline ? 5 : 0
}}
>
@@ -25,7 +25,7 @@ export const Title = ({ text, style = {}, inline }) => {
marginHorizontal: 12,
marginTop: 12,
...getStyle(style),
textAlign: inline ? 'left' : style?.textAlign,
textAlign: inline ? "left" : style?.textAlign,
flexShrink: 1
}}
numberOfLines={1}

View File

@@ -1,34 +1,42 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import picker from '../../screens/editor/tiptap/picker';
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
import PremiumService from '../../services/premium';
import { useAttachmentStore } from '../../stores/use-attachment-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { formatBytes } from '../../utils';
import { db } from '../../common/database';
import { eCloseAttachmentDialog, eCloseProgressDialog } from '../../utils/events';
import filesystem from '../../common/filesystem';
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { Dialog } from '../dialog';
import { presentDialog } from '../dialog/functions';
import { openNote } from '../list-items/note/wrapper';
import { DateMeta } from '../properties/date-meta';
import { Button } from '../ui/button';
import { Notice } from '../ui/notice';
import { PressableButton } from '../ui/pressable';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import picker from "../../screens/editor/tiptap/picker";
import {
eSendEvent,
presentSheet,
ToastEvent
} from "../../services/event-manager";
import PremiumService from "../../services/premium";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from "../../utils";
import { db } from "../../common/database";
import {
eCloseAttachmentDialog,
eCloseProgressDialog
} from "../../utils/events";
import filesystem from "../../common/filesystem";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const contextId = attachment.metadata.hash;
const [filename, setFilename] = useState(attachment.metadata.filename);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment);
const [currentProgress, setCurrentProgress] =
useAttachmentProgress(attachment);
const [failed, setFailed] = useState(attachment.failed);
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState({
@@ -37,25 +45,25 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
const actions = [
{
name: 'Download',
name: "Download",
onPress: async () => {
if (currentProgress) {
await db.fs.cancel(attachment.metadata.hash, 'download');
await db.fs.cancel(attachment.metadata.hash, "download");
useAttachmentStore.getState().remove(attachment.metadata.hash);
}
filesystem.downloadAttachment(attachment.metadata.hash, false);
eSendEvent(eCloseProgressDialog, contextId);
},
icon: 'download'
icon: "download"
},
{
name: 'Reupload',
name: "Reupload",
onPress: async () => {
if (!PremiumService.get()) {
ToastEvent.show({
heading: 'Upgrade to pro',
type: 'error',
context: 'local'
heading: "Upgrade to pro",
type: "error",
context: "local"
});
return;
}
@@ -66,13 +74,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
type: attachment.metadata.type
});
},
icon: 'upload'
icon: "upload"
},
{
name: 'Run file check',
name: "Run file check",
onPress: async () => {
setLoading({
name: 'Run file check'
name: "Run file check"
});
let res = await filesystem.checkAttachment(attachment.metadata.hash);
if (res.failed) {
@@ -83,27 +91,27 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
db.attachments.markAsFailed(attachment.id, null);
}
ToastEvent.show({
heading: 'File check passed',
type: 'success',
context: 'local'
heading: "File check passed",
type: "success",
context: "local"
});
setAttachments([...db.attachments.all]);
setLoading({
name: null
});
},
icon: 'file-check'
icon: "file-check"
},
{
name: 'Rename',
name: "Rename",
onPress: () => {
presentDialog({
context: contextId,
input: true,
title: 'Rename file',
paragraph: 'Enter a new name for the file',
title: "Rename file",
paragraph: "Enter a new name for the file",
defaultValue: attachment.metadata.filename,
positivePress: async value => {
positivePress: async (value) => {
if (value && value.length > 0) {
await db.attachments.add({
hash: attachment.metadata.hash,
@@ -113,31 +121,31 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
setAttachments([...db.attachments.all]);
}
},
positiveText: 'Rename'
positiveText: "Rename"
});
},
icon: 'form-textbox'
icon: "form-textbox"
},
{
name: 'Delete',
name: "Delete",
onPress: async () => {
await db.attachments.remove(attachment.metadata.hash, false);
setAttachments([...db.attachments.all]);
eSendEvent(eCloseProgressDialog, contextId);
},
icon: 'delete-outline'
icon: "delete-outline"
}
];
const getNotes = () => {
let allNotes = db.notes.all;
let attachmentNotes = attachment.noteIds?.map(id => {
let index = allNotes?.findIndex(note => id === note.id);
let attachmentNotes = attachment.noteIds?.map((id) => {
let index = allNotes?.findIndex((note) => id === note.id);
if (index !== -1) {
return allNotes[index];
} else {
return {
type: 'notfound',
type: "notfound",
title: `Note with id ${id} does not exist.`,
id: id
};
@@ -157,7 +165,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
nestedScrollEnabled={true}
style={{
maxHeight: '100%'
maxHeight: "100%"
}}
>
<Dialog context={contextId} />
@@ -179,7 +187,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
<View
style={{
flexDirection: 'row',
flexDirection: "row",
marginBottom: 10,
paddingHorizontal: 12
}}
@@ -210,15 +218,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
size={SIZE.xs + 1}
color={colors.icon}
>
{attachment.noteIds.length} note{attachment.noteIds.length > 1 ? 's' : ''}
{attachment.noteIds.length} note
{attachment.noteIds.length > 1 ? "s" : ""}
</Paragraph>
<Paragraph
onPress={() => {
Clipboard.setString(attachment.metadata.hash);
ToastEvent.show({
type: 'success',
heading: 'Attachment hash copied',
context: 'local'
type: "success",
heading: "Attachment hash copied",
context: "local"
});
}}
size={SIZE.xs + 1}
@@ -250,16 +259,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
List of notes:
</Heading>
{notes.map(item => (
{notes.map((item) => (
<PressableButton
onPress={async () => {
if (item.type === 'notfound') {
if (item.type === "notfound") {
ToastEvent.show({
heading: 'Note not found',
heading: "Note not found",
message:
'A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.',
type: 'error',
context: 'local'
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
type: "error",
context: "local"
});
return;
}
@@ -267,11 +276,11 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
await sleep(150);
eSendEvent(eCloseAttachmentDialog);
await sleep(300);
openNote(item, item.type === 'trash');
openNote(item, item.type === "trash");
}}
customStyle={{
paddingVertical: 12,
alignItems: 'flex-start',
alignItems: "flex-start",
paddingHorizontal: 12
}}
@@ -284,13 +293,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
</View>
) : null}
{actions.map(item => (
{actions.map((item) => (
<Button
key={item.name}
buttonType={{
text: item.on
? colors.accent
: item.name === 'Delete' || item.name === 'PermDelete'
: item.name === "Delete" || item.name === "PermDelete"
? colors.errorText
: colors.pri
}}
@@ -298,13 +307,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
title={item.name}
icon={item.icon}
loading={loading?.name === item.name}
type={item.on ? 'shade' : 'gray'}
type={item.on ? "shade" : "gray"}
fontSize={SIZE.sm}
style={{
borderRadius: 0,
justifyContent: 'flex-start',
alignSelf: 'flex-start',
width: '100%'
justifyContent: "flex-start",
alignSelf: "flex-start",
width: "100%"
}}
/>
))}
@@ -329,7 +338,9 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
Actions.present = (attachment, set, context) => {
presentSheet({
context: context,
component: ref => <Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
component: (ref) => (
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
)
});
};

View File

@@ -1,28 +1,31 @@
import React from 'react';
import { TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useAttachmentStore } from '../../stores/use-attachment-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { formatBytes } from '../../utils';
import { db } from '../../common/database';
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
import { SIZE } from '../../utils/size';
import SheetProvider from '../sheet-provider';
import { IconButton } from '../ui/icon-button';
import { ProgressCircleComponent } from '../ui/svg/lazy';
import Paragraph from '../ui/typography/paragraph';
import Actions from './actions';
import React from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from "../../utils";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? '' : ext[1];
return ext == null ? "" : ext[1];
}
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const colors = useThemeStore(state => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment, encryption);
const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const encryptionProgress = encryption
? useAttachmentStore(state => state.encryptionProgress)
? useAttachmentStore((state) => state.encryptionProgress)
: null;
const onPress = () => {
@@ -33,9 +36,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
activeOpacity={0.9}
onPress={onPress}
style={{
flexDirection: 'row',
flexDirection: "row",
marginVertical: 5,
justifyContent: 'space-between',
justifyContent: "space-between",
padding: 12,
paddingVertical: 6,
borderRadius: 5,
@@ -47,14 +50,14 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
<View
style={{
flexShrink: 1,
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
marginLeft: -5
}}
>
@@ -65,7 +68,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
size={6}
color={colors.light}
style={{
position: 'absolute'
position: "absolute"
}}
>
{getFileExtension(attachment.metadata.filename).toUpperCase()}
@@ -81,7 +84,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
<Paragraph
size={SIZE.sm - 1}
style={{
flexWrap: 'wrap',
flexWrap: "wrap",
marginBottom: 2.5
}}
numberOfLines={1}
@@ -92,8 +95,10 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{' '}
{currentProgress?.type ? '(' + currentProgress.type + 'ing - tap to cancel)' : ''}
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
</View>
</View>
@@ -107,7 +112,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
setCurrentProgress(null);
}}
style={{
justifyContent: 'center',
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
@@ -127,7 +132,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
fontSize: 10
}}
color={colors.accent}
formatText={progress => (progress * 100).toFixed(0)}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
@@ -135,7 +140,11 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
) : (
<>
{attachment.failed ? (
<IconButton onPress={onPress} name="alert-circle-outline" color={colors.errorText} />
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.errorText}
/>
) : null}
</>
)}

View File

@@ -1,22 +1,28 @@
import React, { useEffect, useRef, useState } from 'react';
import { View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import { eCloseAttachmentDialog, eOpenAttachmentsDialog } from '../../utils/events';
import filesystem from '../../common/filesystem';
import { SIZE } from '../../utils/size';
import DialogHeader from '../dialog/dialog-header';
import { Toast } from '../toast';
import Input from '../ui/input';
import Seperator from '../ui/seperator';
import SheetWrapper from '../ui/sheet';
import Paragraph from '../ui/typography/paragraph';
import { AttachmentItem } from './attachment-item';
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import {
eCloseAttachmentDialog,
eOpenAttachmentsDialog
} from "../../utils/events";
import filesystem from "../../common/filesystem";
import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header";
import { Toast } from "../toast";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
export const AttachmentDialog = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
const actionSheetRef = useRef();
@@ -34,10 +40,10 @@ export const AttachmentDialog = () => {
};
}, [visible]);
const open = data => {
const open = (data) => {
if (data?.id) {
setNote(data);
let _attachments = db.attachments.ofNote(data.id, 'all');
let _attachments = db.attachments.ofNote(data.id, "all");
setAttachments(_attachments);
} else {
setAttachments([...db.attachments.all]);
@@ -56,18 +62,24 @@ export const AttachmentDialog = () => {
setVisible(false);
};
const onChangeText = text => {
const onChangeText = (text) => {
attachmentSearchValue.current = text;
console.log(attachmentSearchValue.current?.length);
if (!attachmentSearchValue.current || attachmentSearchValue.current === '') {
console.log('resetting all');
if (
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
console.log("resetting all");
setAttachments([...db.attachments.all]);
}
console.log(attachments.length);
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
let results = db.lookup.attachments(db.attachments.all, attachmentSearchValue.current);
console.log('results', results.length, attachments.length);
let results = db.lookup.attachments(
db.attachments.all,
attachmentSearchValue.current
);
console.log("results", results.length, attachments.length);
if (results.length === 0) return;
setAttachments(results);
}, 300);
@@ -88,24 +100,29 @@ export const AttachmentDialog = () => {
<Toast context="local" />
<View
style={{
width: '100%',
alignSelf: 'center',
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<DialogHeader
title={note ? 'Attachments' : 'Manage attachments'}
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: 'Check all',
type: 'grayAccent',
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(attachment.metadata.hash);
let result = await filesystem.checkAttachment(
attachment.metadata.hash
);
if (result.failed) {
db.attachments.markAsFailed(attachment.metadata.hash, result.failed);
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
@@ -139,12 +156,14 @@ export const AttachmentDialog = () => {
<View
style={{
height: 150,
justifyContent: 'center',
alignItems: 'center'
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>{note ? `No attachments on this note` : `No attachments`}</Paragraph>
<Paragraph>
{note ? `No attachments on this note` : `No attachments`}
</Paragraph>
</View>
}
ListFooterComponent={
@@ -155,7 +174,7 @@ export const AttachmentDialog = () => {
/>
}
data={attachments}
keyExtractor={item => item.id}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
@@ -163,12 +182,12 @@ export const AttachmentDialog = () => {
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: 'center',
textAlign: "center",
marginTop: 10
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{' '}All attachments are end-to-end encrypted.
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
</SheetWrapper>

View File

@@ -1,18 +1,21 @@
import React, { useEffect, useRef, useState } from 'react';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import { Toast } from '../toast';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import { hideAuth, initialAuthMode } from './common';
import { Login } from './login';
import { Signup } from './signup';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Platform, View } from 'react-native';
import React, { useEffect, useRef, useState } from "react";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import { Toast } from "../toast";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import { hideAuth, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform, View } from "react-native";
export const AuthMode = {
login: 0,
@@ -22,7 +25,7 @@ export const AuthMode = {
};
const AuthModal = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
const actionSheetRef = useRef();
@@ -66,29 +69,31 @@ const AuthModal = () => {
>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={mode => setCurrentAuthMode(mode)}
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
changeMode={mode => setCurrentAuthMode(mode)}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
<View
style={{
position: 'absolute',
top: Platform.OS === 'ios' ? insets.top : 0,
position: "absolute",
top: Platform.OS === "ios" ? insets.top : 0,
zIndex: 999,
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
width: '100%',
width: "100%",
height: 50,
justifyContent:
initialAuthMode.current !== AuthMode.welcomeSignup ? 'space-between' : 'flex-end'
initialAuthMode.current !== AuthMode.welcomeSignup
? "space-between"
: "flex-end"
}}
>
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (

View File

@@ -1,2 +1,2 @@
export const SVG = color =>
export const SVG = (color) =>
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="1920" height="1080" preserveAspectRatio="none" viewBox="0 0 1920 1080"><g fill="none"><path d="M684.78-215.3C516.78-49.87 619.54 659.12 349.29 666.13 79.03 673.14-116.04 204.76-321.7 180.13" stroke="${color}" stroke-width="2"></path><path d="M1337.71-20.29C1105.2 122.03 1140.53 887.64 809.58 933.71 478.63 979.78 545.52 798.71 281.45 798.71 17.39 798.71-110.41 932.65-246.68 933.71" stroke="${color}" stroke-width="2"></path><path d="M1631.89-52.3C1379.35-21.93 1283.05 511.66 808.42 545.28 333.79 578.9 209.15 894.14-15.05 901.68" stroke="${color}" stroke-width="2"></path><path d="M1523.21-71.7C1244.4-46.38 1056.2 532.95 581.02 533.49 105.84 534.03-109.4 182.98-361.17 177.09" stroke="${color}" stroke-width="2"></path><path d="M1333.35-39.1C1041.86 57 974.42 893.52 550.36 906.48 126.3 919.44-23.8 619.56-232.63 614.88" stroke="${color}" stroke-width="2"></path></g><defs><mask id="SvgjsMask1032"><rect width="1920" height="1080" fill="#ffffff"></rect></mask></defs></svg>`;

View File

@@ -1,18 +1,22 @@
import React, { useRef, useState } from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
import { db } from '../../common/database';
import { eCloseProgressDialog } from '../../utils/events';
import { Button } from '../ui/button';
import DialogHeader from '../dialog/dialog-header';
import Input from '../ui/input';
import { Notice } from '../ui/notice';
import Seperator from '../ui/seperator';
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import {
eSendEvent,
presentSheet,
ToastEvent
} from "../../services/event-manager";
import { db } from "../../common/database";
import { eCloseProgressDialog } from "../../utils/events";
import { Button } from "../ui/button";
import DialogHeader from "../dialog/dialog-header";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
export const ChangePassword = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const passwordInputRef = useRef();
const password = useRef();
const oldPasswordInputRef = useRef();
@@ -20,24 +24,24 @@ export const ChangePassword = () => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const user = useUserStore(state => state.user);
const user = useUserStore((state) => state.user);
const changePassword = async () => {
if (!user?.isEmailConfirmed) {
ToastEvent.show({
heading: 'Email not confirmed',
message: 'Please confirm your email to change account password',
type: 'error',
context: 'local'
heading: "Email not confirmed",
message: "Please confirm your email to change account password",
type: "error",
context: "local"
});
return;
}
if (error || !oldPassword.current || !password.current) {
ToastEvent.show({
heading: 'All fields required',
message: 'Fill all the fields and try again.',
type: 'error',
context: 'local'
heading: "All fields required",
message: "Fill all the fields and try again.",
type: "error",
context: "local"
});
return;
}
@@ -47,18 +51,18 @@ export const ChangePassword = () => {
await db.user.changePassword(oldPassword.current, password.current);
ToastEvent.show({
heading: `Account password updated`,
type: 'success',
context: 'global'
type: "success",
context: "global"
});
setLoading(false);
eSendEvent(eCloseProgressDialog);
} catch (e) {
setLoading(false);
ToastEvent.show({
heading: 'Failed to change password',
heading: "Failed to change password",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
setLoading(false);
@@ -67,16 +71,19 @@ export const ChangePassword = () => {
return (
<View
style={{
width: '100%',
width: "100%",
padding: 12
}}
>
<DialogHeader title="Change password" paragraph="Enter your old and new passwords" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
/>
<Seperator />
<Input
fwdRef={oldPasswordInputRef}
onChangeText={value => {
onChangeText={(value) => {
oldPassword.current = value;
}}
returnKeyLabel="Next"
@@ -90,10 +97,10 @@ export const ChangePassword = () => {
<Input
fwdRef={passwordInputRef}
onChangeText={value => {
onChangeText={(value) => {
password.current = value;
}}
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyType="next"
secureTextEntry
@@ -112,12 +119,12 @@ export const ChangePassword = () => {
<Button
style={{
marginTop: 10,
width: '100%'
width: "100%"
}}
loading={loading}
onPress={changePassword}
type="accent"
title={loading ? null : 'I understand, change my password'}
title={loading ? null : "I understand, change my password"}
/>
</View>
);

View File

@@ -1,9 +1,9 @@
import { createRef } from 'react';
import { eSendEvent } from '../../services/event-manager';
import Navigation from '../../services/navigation';
import SettingsService from '../../services/settings';
import { eCloseLoginDialog } from '../../utils/events';
import { tabBarRef } from '../../utils/global-refs';
import { createRef } from "react";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SettingsService from "../../services/settings";
import { eCloseLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
export const initialAuthMode = createRef(0);
export function hideAuth() {

View File

@@ -1,21 +1,21 @@
import React, { useRef, useState } from 'react';
import { View } from 'react-native';
import ActionSheet from 'react-native-actions-sheet';
import { DDS } from '../../services/device-detection';
import { ToastEvent } from '../../services/event-manager';
import SettingsService from '../../services/settings';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import DialogHeader from '../dialog/dialog-header';
import { Button } from '../ui/button';
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 React, { useRef, useState } from "react";
import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
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";
export const ForgotPassword = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const email = useRef();
const emailInputRef = useRef();
const [error, setError] = useState(false);
@@ -25,17 +25,20 @@ export const ForgotPassword = () => {
const sendRecoveryEmail = async () => {
if (!email.current || error) {
ToastEvent.show({
heading: 'Account email is required.',
type: 'error',
context: 'local'
heading: "Account email is required.",
type: "error",
context: "local"
});
return;
}
setLoading(true);
try {
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
if (lastRecoveryEmailTime && Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3) {
throw new Error('Please wait before requesting another email');
if (
lastRecoveryEmailTime &&
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
) {
throw new Error("Please wait before requesting another email");
}
await db.user.recoverAccount(email.current.toLowerCase());
SettingsService.set({
@@ -44,8 +47,8 @@ export const ForgotPassword = () => {
ToastEvent.show({
heading: `Check your email to reset password`,
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
type: 'success',
context: 'local',
type: "success",
context: "local",
duration: 7000
});
setLoading(false);
@@ -53,10 +56,10 @@ export const ForgotPassword = () => {
} catch (e) {
setLoading(false);
ToastEvent.show({
heading: 'Recovery email not sent',
heading: "Recovery email not sent",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
};
@@ -64,7 +67,7 @@ export const ForgotPassword = () => {
return (
<>
<ActionSheet
onBeforeShow={data => (email.current = data)}
onBeforeShow={(data) => (email.current = data)}
onClose={() => {
setSent(false);
setLoading(false);
@@ -82,8 +85,8 @@ export const ForgotPassword = () => {
<View
style={{
padding: 12,
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
paddingBottom: 50
}}
>
@@ -99,7 +102,7 @@ export const ForgotPassword = () => {
<Heading>Recovery email sent!</Heading>
<Paragraph
style={{
textAlign: 'center'
textAlign: "center"
}}
>
Please follow the link in the email to recover your account.
@@ -111,7 +114,7 @@ export const ForgotPassword = () => {
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: '100%',
width: "100%",
padding: 12
}}
>
@@ -123,11 +126,11 @@ export const ForgotPassword = () => {
<Input
fwdRef={emailInputRef}
onChangeText={value => {
onChangeText={(value) => {
email.current = value;
}}
defaultValue={email.current}
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyType="next"
autoComplete="email"
@@ -142,12 +145,12 @@ export const ForgotPassword = () => {
<Button
style={{
marginTop: 10,
width: '100%'
width: "100%"
}}
loading={loading}
onPress={sendRecoveryEmail}
type="accent"
title={loading ? null : 'Next'}
title={loading ? null : "Next"}
/>
</View>
)}

View File

@@ -1,13 +1,13 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useThemeStore } from '../../stores/use-theme-store';
import { tabBarRef } from '../../utils/global-refs';
import { useNavigationFocus } from '../../hooks/use-navigation-focus';
import { Toast } from '../toast';
import { initialAuthMode } from './common';
import { Login } from './login';
import { Signup } from './signup';
import React, { useState } from "react";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useThemeStore } from "../../stores/use-theme-store";
import { tabBarRef } from "../../utils/global-refs";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { Toast } from "../toast";
import { initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
export const AuthMode = {
login: 0,
@@ -17,8 +17,10 @@ export const AuthMode = {
};
const Auth = ({ navigation, route }) => {
const colors = useThemeStore(state => state.colors);
const [currentAuthMode, setCurrentAuthMode] = useState(route?.params?.mode || AuthMode.login);
const colors = useThemeStore((state) => state.colors);
const [currentAuthMode, setCurrentAuthMode] = useState(
route?.params?.mode || AuthMode.login
);
const insets = useSafeAreaInsets();
initialAuthMode.current = route?.params.mode || AuthMode.login;
useNavigationFocus(navigation, {
@@ -32,12 +34,15 @@ const Auth = ({ navigation, route }) => {
<View style={{ flex: 1 }}>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={mode => setCurrentAuthMode(mode)}
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login welcome={initialAuthMode.current} changeMode={mode => setCurrentAuthMode(mode)} />
<Login
welcome={initialAuthMode.current}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (

View File

@@ -1,36 +1,40 @@
import React, { useEffect, useRef, useState } from 'react';
import { Platform, View } from 'react-native';
import { SheetManager } from 'react-native-actions-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { DDS } from '../../services/device-detection';
import { eSendEvent, ToastEvent } from '../../services/event-manager';
import { clearMessage } from '../../services/message';
import PremiumService from '../../services/premium';
import SettingsService from '../../services/settings';
import { useUserStore } from '../../stores/use-user-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import { eCloseLoginDialog } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import SheetProvider from '../sheet-provider';
import { Progress } from '../sheets/progress';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import Input from '../ui/input';
import { SvgView } from '../ui/svg';
import { BouncingView } from '../ui/transitions/bouncing-view';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { SVG } from './background';
import { ForgotPassword } from './forgot-password';
import TwoFactorVerification from './two-factor';
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
import Navigation from '../../services/navigation';
import { hideAuth } from './common';
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { DDS } from "../../services/device-detection";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import { clearMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import { eCloseLoginDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import { BouncingView } from "../ui/transitions/bouncing-view";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { ForgotPassword } from "./forgot-password";
import TwoFactorVerification from "./two-factor";
import Animated, {
FadeInDown,
FadeOutDown,
FadeOutUp
} from "react-native-reanimated";
import Navigation from "../../services/navigation";
import { hideAuth } from "./common";
export const Login = ({ changeMode, welcome }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const email = useRef();
const emailInputRef = useRef();
const passwordInputRef = useRef();
@@ -40,15 +44,15 @@ export const Login = ({ changeMode, welcome }) => {
const [loading, setLoading] = useState(false);
const insets = useSafeAreaInsets();
const setUser = useUserStore(state => state.setUser);
const setUser = useUserStore((state) => state.setUser);
const validateInfo = () => {
if (!password.current || !email.current) {
ToastEvent.show({
heading: 'All fields required',
message: 'Fill all the fields and try again',
type: 'error',
context: 'local'
heading: "All fields required",
message: "Fill all the fields and try again",
type: "error",
context: "local"
});
return false;
@@ -71,37 +75,41 @@ export const Login = ({ changeMode, welcome }) => {
let user;
try {
if (mfa) {
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
await db.user.mfaLogin(
email.current.toLowerCase(),
password.current,
mfa
);
} else {
await db.user.login(email.current.toLowerCase(), password.current);
}
callback && callback(true);
user = await db.user.getUser();
if (!user) throw new Error('Email or password incorrect!');
if (!user) throw new Error("Email or password incorrect!");
PremiumService.setPremiumStatus();
setUser(user);
clearMessage();
ToastEvent.show({
heading: 'Login successful',
heading: "Login successful",
message: `Logged in as ${user.email}`,
type: 'success',
context: 'global'
type: "success",
context: "global"
});
hideAuth();
SettingsService.set({
sessionExpired: false,
userEmailConfirmed: user?.isEmailConfirmed
});
eSendEvent('userLoggedIn', true);
eSendEvent("userLoggedIn", true);
await sleep(500);
Progress.present();
} catch (e) {
callback && callback(false);
if (e.message === 'Multifactor authentication required.') {
if (e.message === "Multifactor authentication required.") {
setLoading(false);
await sleep(300);
TwoFactorVerification.present(async mfa => {
TwoFactorVerification.present(async (mfa) => {
if (mfa) {
console.log(mfa);
await login(mfa);
@@ -112,10 +120,10 @@ export const Login = ({ changeMode, welcome }) => {
} else {
setLoading(false);
ToastEvent.show({
heading: user ? 'Failed to sync' : 'Login failed',
heading: user ? "Failed to sync" : "Login failed",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
}
@@ -125,7 +133,9 @@ export const Login = ({ changeMode, welcome }) => {
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
{loading ? (
<BaseDialog transparent={true} visible={true} animation="fade" />
) : null}
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
@@ -133,23 +143,26 @@ export const Login = ({ changeMode, welcome }) => {
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: '100%',
minHeight: '100%'
width: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: 'hidden'
overflow: "hidden"
}}
>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
</View>
<View
style={{
width: '100%',
justifyContent: 'center',
alignSelf: 'center',
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15
@@ -157,7 +170,7 @@ export const Login = ({ changeMode, welcome }) => {
>
<Heading
style={{
textAlign: 'center'
textAlign: "center"
}}
size={30}
color={colors.heading}
@@ -166,8 +179,8 @@ export const Login = ({ changeMode, welcome }) => {
</Heading>
<Paragraph
style={{
textDecorationLine: 'underline',
textAlign: 'center',
textDecorationLine: "underline",
textAlign: "center",
marginTop: 5
}}
onPress={() => {
@@ -180,20 +193,26 @@ export const Login = ({ changeMode, welcome }) => {
</View>
<View
style={{
width: DDS.isTab ? (focused ? '50%' : '49.99%') : focused ? '100%' : '99.9%',
width: DDS.isTab
? focused
? "50%"
: "49.99%"
: focused
? "100%"
: "99.9%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: 'center'
alignSelf: "center"
}}
>
<Input
fwdRef={emailInputRef}
onChangeText={value => {
onChangeText={(value) => {
email.current = value;
}}
testID="input.email"
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyType="next"
autoComplete="email"
@@ -209,7 +228,7 @@ export const Login = ({ changeMode, welcome }) => {
<Input
fwdRef={passwordInputRef}
onChangeText={value => {
onChangeText={(value) => {
password.current = value;
}}
testID="input.password"
@@ -226,15 +245,15 @@ export const Login = ({ changeMode, welcome }) => {
<Button
title="Forgot your password?"
style={{
alignSelf: 'flex-end',
alignSelf: "flex-end",
height: 30,
paddingHorizontal: 0
}}
onPress={() => {
SheetManager.show('forgotpassword_sheet', email.current);
SheetManager.show("forgotpassword_sheet", email.current);
}}
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
fontSize={SIZE.xs}
type="gray"
@@ -244,7 +263,7 @@ export const Login = ({ changeMode, welcome }) => {
style={{
// position: 'absolute',
marginTop: 25,
alignSelf: 'center'
alignSelf: "center"
}}
>
<Button
@@ -256,7 +275,7 @@ export const Login = ({ changeMode, welcome }) => {
onPress={() => login()}
// width="100%"
type="accent"
title={loading ? null : 'Login to your account'}
title={loading ? null : "Login to your account"}
/>
</View>
</View>

View File

@@ -1,47 +1,47 @@
import React, { useEffect, useRef, useState } from 'react';
import { Modal, View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import BiometricService from '../../services/biometrics';
import React, { useEffect, useRef, useState } from "react";
import { Modal, View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import BiometricService from "../../services/biometrics";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet,
ToastEvent
} from '../../services/event-manager';
import { clearMessage } from '../../services/message';
import PremiumService from '../../services/premium';
import Sync from '../../services/sync';
import { db } from '../../common/database';
import { MMKV } from '../../common/database/mmkv';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { IconButton } from '../ui/icon-button';
import { Button } from '../ui/button';
import { Dialog } from '../dialog';
import { presentDialog } from '../dialog/functions';
import Input from '../ui/input';
import { Toast } from '../toast';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import SettingsService from '../../services/settings';
import TwoFactorVerification from './two-factor';
import SheetProvider from '../sheet-provider';
import { Progress } from '../sheets/progress';
} from "../../services/event-manager";
import { clearMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import Sync from "../../services/sync";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { IconButton } from "../ui/icon-button";
import { Button } from "../ui/button";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import Input from "../ui/input";
import { Toast } from "../toast";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import SettingsService from "../../services/settings";
import TwoFactorVerification from "./two-factor";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
function getEmail(email) {
if (!email) return null;
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
for (let i = 0; i < gp3.length; i++) {
gp2 += '*';
gp2 += "*";
}
return gp2;
});
}
export const SessionExpired = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const email = useRef();
const emailInputRef = useRef();
const passwordInputRef = useRef();
@@ -49,7 +49,7 @@ export const SessionExpired = () => {
const [visible, setVisible] = useState(false);
const [focused, setFocused] = useState(false);
const setUser = useUserStore(state => state.setUser);
const setUser = useUserStore((state) => state.setUser);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
@@ -65,27 +65,28 @@ export const SessionExpired = () => {
} catch (e) {
ToastEvent.show({
heading: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
};
useEffect(() => {
eSubscribeEvent('session_expired', open);
eSubscribeEvent("session_expired", open);
return () => {
eUnSubscribeEvent('session_expired', open);
eUnSubscribeEvent("session_expired", open);
setFocused(false);
};
}, [visible]);
const open = async () => {
try {
console.log('REQUESTING NEW TOKEN');
console.log("REQUESTING NEW TOKEN");
let res = await db.user.tokenManager.getToken();
if (!res) throw new Error('no token found');
if (db.user.tokenManager._isTokenExpired(res)) throw new Error('token expired');
Sync.run('global', false, true, async complete => {
if (!res) throw new Error("no token found");
if (db.user.tokenManager._isTokenExpired(res))
throw new Error("token expired");
Sync.run("global", false, true, async (complete) => {
if (!complete) {
let user = await db.user.getUser();
if (!user) return;
@@ -113,35 +114,39 @@ export const SessionExpired = () => {
let user;
try {
if (mfa) {
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
await db.user.mfaLogin(
email.current.toLowerCase(),
password.current,
mfa
);
} else {
await db.user.login(email.current.toLowerCase(), password.current);
}
callback && callback(true);
setVisible(false);
user = await db.user.getUser();
if (!user) throw new Error('Email or password incorrect!');
if (!user) throw new Error("Email or password incorrect!");
PremiumService.setPremiumStatus();
setUser(user);
clearMessage();
ToastEvent.show({
heading: 'Login successful',
heading: "Login successful",
message: `Logged in as ${user.email}`,
type: 'success',
context: 'global'
type: "success",
context: "global"
});
await SettingsService.set({
sessionExpired: false,
userEmailConfirmed: user?.isEmailConfirmed
});
eSendEvent('userLoggedIn', true);
eSendEvent("userLoggedIn", true);
await sleep(500);
Progress.present();
setLoading(false);
} catch (e) {
callback && callback(false);
if (e.message === 'Multifactor authentication required.') {
TwoFactorVerification.present(async mfa => {
if (e.message === "Multifactor authentication required.") {
TwoFactorVerification.present(async (mfa) => {
if (mfa) {
console.log(mfa);
await login(mfa);
@@ -153,10 +158,10 @@ export const SessionExpired = () => {
console.log(e.stack);
setLoading(false);
ToastEvent.show({
heading: user ? 'Failed to sync' : 'Login failed',
heading: user ? "Failed to sync" : "Login failed",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
}
@@ -174,17 +179,17 @@ export const SessionExpired = () => {
<SheetProvider context="two_factor_verify" />
<View
style={{
width: focused ? '100%' : '99.9%',
width: focused ? "100%" : "99.9%",
padding: 12,
justifyContent: 'center',
justifyContent: "center",
flex: 1
}}
>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
width: '100%',
justifyContent: "center",
alignItems: "center",
width: "100%",
marginBottom: 20,
borderRadius: 10,
paddingVertical: 20
@@ -204,17 +209,17 @@ export const SessionExpired = () => {
</Heading>
<Paragraph
style={{
textAlign: 'center'
textAlign: "center"
}}
>
Your session on this device has expired. Please enter password for{' '}
Your session on this device has expired. Please enter password for{" "}
{getEmail(email.current)} to continue.
</Paragraph>
</View>
<Input
fwdRef={passwordInputRef}
onChangeText={value => {
onChangeText={(value) => {
password.current = value;
}}
returnKeyLabel="Next"
@@ -230,32 +235,32 @@ export const SessionExpired = () => {
<Button
style={{
marginTop: 10,
width: '100%'
width: "100%"
}}
loading={loading}
onPress={() => login()}
type="accent"
title={loading ? null : 'Login'}
title={loading ? null : "Login"}
/>
<Button
style={{
marginTop: 10,
width: '100%'
width: "100%"
}}
onPress={() => {
presentDialog({
context: 'session_expiry',
title: 'Logout',
context: "session_expiry",
title: "Logout",
paragraph:
'Are you sure you want to logout from this device? Any unsynced changes will be lost.',
positiveText: 'Logout',
positiveType: 'errorShade',
"Are you sure you want to logout from this device? Any unsynced changes will be lost.",
positiveText: "Logout",
positiveType: "errorShade",
positivePress: logout
});
}}
type="errorShade"
title={loading ? null : 'Logout from this device'}
title={loading ? null : "Logout from this device"}
/>
</View>
<Toast context="local" />

View File

@@ -1,32 +1,36 @@
import React, { useRef, useState } from 'react';
import { Dimensions, View } from 'react-native';
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
import { DDS } from '../../services/device-detection';
import { eSendEvent, ToastEvent } from '../../services/event-manager';
import { clearMessage, setEmailVerifyMessage } from '../../services/message';
import Navigation from '../../services/navigation';
import PremiumService from '../../services/premium';
import SettingsService from '../../services/settings';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import umami from '../../common/analytics';
import { db } from '../../common/database';
import { eCloseLoginDialog } from '../../utils/events';
import { openLinkInBrowser } from '../../utils/functions';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import { Button } from '../ui/button';
import Input from '../ui/input';
import { SvgView } from '../ui/svg';
import { BouncingView } from '../ui/transitions/bouncing-view';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { SVG } from './background';
import { hideAuth } from './common';
import React, { useRef, useState } from "react";
import { Dimensions, View } from "react-native";
import Animated, {
FadeInDown,
FadeOutDown,
FadeOutUp
} from "react-native-reanimated";
import { DDS } from "../../services/device-detection";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import umami from "../../common/analytics";
import { db } from "../../common/database";
import { eCloseLoginDialog } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import { BouncingView } from "../ui/transitions/bouncing-view";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { hideAuth } from "./common";
export const Signup = ({ changeMode, welcome, trial }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const email = useRef();
const emailInputRef = useRef();
const passwordInputRef = useRef();
@@ -35,16 +39,16 @@ export const Signup = ({ changeMode, welcome, trial }) => {
const confirmPassword = useRef();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const setUser = useUserStore(state => state.setUser);
const setLastSynced = useUserStore(state => state.setLastSynced);
const setUser = useUserStore((state) => state.setUser);
const setLastSynced = useUserStore((state) => state.setLastSynced);
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
ToastEvent.show({
heading: 'All fields required',
message: 'Fill all the fields and try again',
type: 'error',
context: 'local'
heading: "All fields required",
message: "Fill all the fields and try again",
type: "error",
context: "local"
});
return false;
@@ -64,7 +68,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
clearMessage();
setEmailVerifyMessage();
hideAuth();
umami.pageView('/account-created', '/welcome/signup');
umami.pageView("/account-created", "/welcome/signup");
await sleep(300);
if (trial) {
PremiumService.sheet(null, null, true);
@@ -74,17 +78,19 @@ export const Signup = ({ changeMode, welcome, trial }) => {
} catch (e) {
setLoading(false);
ToastEvent.show({
heading: 'Signup failed',
heading: "Signup failed",
message: e.message,
type: 'error',
context: 'local'
type: "error",
context: "local"
});
}
};
return (
<>
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
{loading ? (
<BaseDialog transparent={true} visible={true} animation="fade" />
) : null}
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
@@ -92,32 +98,35 @@ export const Signup = ({ changeMode, welcome, trial }) => {
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: '100%',
minHeight: '100%'
width: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: 'hidden'
overflow: "hidden"
}}
>
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
</View>
<View
style={{
width: '100%',
justifyContent: 'center',
alignSelf: 'center',
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: Dimensions.get('window').height < 700 ? -75 : 15
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
}}
>
<Heading
style={{
textAlign: 'center'
textAlign: "center"
}}
size={30}
color={colors.heading}
@@ -126,8 +135,8 @@ export const Signup = ({ changeMode, welcome, trial }) => {
</Heading>
<Paragraph
style={{
textDecorationLine: 'underline',
textAlign: 'center'
textDecorationLine: "underline",
textAlign: "center"
}}
onPress={() => {
changeMode(0);
@@ -139,20 +148,20 @@ export const Signup = ({ changeMode, welcome, trial }) => {
</View>
<View
style={{
width: DDS.isTab ? '50%' : '100%',
width: DDS.isTab ? "50%" : "100%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: 'center'
alignSelf: "center"
}}
>
<Input
fwdRef={emailInputRef}
onChangeText={value => {
onChangeText={(value) => {
email.current = value;
}}
testID="input.email"
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyType="next"
autoComplete="email"
@@ -168,11 +177,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
<Input
fwdRef={passwordInputRef}
onChangeText={value => {
onChangeText={(value) => {
password.current = value;
}}
testID="input.password"
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next"
returnKeyType="next"
secureTextEntry
@@ -188,11 +197,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
<Input
fwdRef={confirmPasswordInputRef}
onChangeText={value => {
onChangeText={(value) => {
confirmPassword.current = value;
}}
testID="input.confirmPassword"
onErrorCheck={e => setError(e)}
onErrorCheck={(e) => setError(e)}
returnKeyLabel="Signup"
returnKeyType="done"
secureTextEntry
@@ -208,7 +217,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
<View
style={{
marginTop: 25,
alignSelf: 'center'
alignSelf: "center"
}}
>
<Button
@@ -219,42 +228,42 @@ export const Signup = ({ changeMode, welcome, trial }) => {
loading={loading}
onPress={signup}
type="accent"
title={loading ? null : 'Agree and continue'}
title={loading ? null : "Agree and continue"}
/>
</View>
<Paragraph
style={{
textAlign: 'center',
position: 'absolute',
textAlign: "center",
position: "absolute",
bottom: 0,
alignSelf: 'center',
alignSelf: "center",
marginBottom: 20
}}
size={SIZE.xs}
color={colors.icon}
>
By signing up, you agree to our{' '}
By signing up, you agree to our{" "}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/tos', colors);
openLinkInBrowser("https://notesnook.com/tos", colors);
}}
style={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
color={colors.accent}
>
terms of service{' '}
terms of service{" "}
</Paragraph>
and{' '}
and{" "}
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser('https://notesnook.com/privacy', colors);
openLinkInBrowser("https://notesnook.com/privacy", colors);
}}
style={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
color={colors.accent}
>

View File

@@ -1,22 +1,22 @@
import React, { useEffect, useRef, useState } from 'react';
import { View } from 'react-native';
import { eSendEvent, presentSheet } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { eCloseProgressDialog } from '../../utils/events';
import useTimer from '../../hooks/use-timer';
import { SIZE } from '../../utils/size';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import Input from '../ui/input';
import { PressableButton } from '../ui/pressable';
import Seperator from '../ui/seperator';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { db } from '../../common/database/index';
import { ToastEvent } from '../../services/event-manager';
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { eCloseProgressDialog } from "../../utils/events";
import useTimer from "../../hooks/use-timer";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import { PressableButton } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { db } from "../../common/database/index";
import { ToastEvent } from "../../services/event-manager";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const code = useRef();
const [currentMethod, setCurrentMethod] = useState({
method: mfaInfo?.primaryMethod,
@@ -28,10 +28,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const [sending, setSending] = useState(false);
const codeHelpText = {
app: 'Enter the 6 digit code from your authenticator app to continue logging in',
sms: 'Enter the 6 digit code sent to your phone number to continue logging in',
email: 'Enter the 6 digit code sent to your email to continue logging in',
recoveryCode: 'Enter the 8 digit recovery code to continue logging in'
app: "Enter the 6 digit code from your authenticator app to continue logging in",
sms: "Enter the 6 digit code sent to your phone number to continue logging in",
email: "Enter the 6 digit code sent to your email to continue logging in",
recoveryCode: "Enter the 8 digit recovery code to continue logging in"
};
const secondaryMethodsText = {
@@ -42,7 +42,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
};
const onNext = async () => {
const length = currentMethod.method === 'recoveryCode' ? 8 : 6;
const length = currentMethod.method === "recoveryCode" ? 8 : 6;
if (!code.current || code.current.length !== length) return;
console.log(currentMethod.method, code.current);
@@ -53,10 +53,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
method: currentMethod.method,
code: code.current
},
result => {
console.log('result recieved');
(result) => {
console.log("result recieved");
if (result) {
eSendEvent(eCloseProgressDialog, 'two_factor_verify');
eSendEvent(eCloseProgressDialog, "two_factor_verify");
}
setLoading(false);
}
@@ -73,38 +73,38 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const methods = [
{
id: 'sms',
title: 'Send code via SMS',
icon: 'message-plus-outline'
id: "sms",
title: "Send code via SMS",
icon: "message-plus-outline"
},
{
id: 'email',
title: 'Send code via email',
icon: 'email-outline'
id: "email",
title: "Send code via email",
icon: "email-outline"
},
{
id: 'app',
title: 'Enter code from authenticator app',
icon: 'cellphone-key'
id: "app",
title: "Enter code from authenticator app",
icon: "cellphone-key"
},
{
id: 'recoveryCode',
title: 'I have a recovery code',
icon: 'key'
id: "recoveryCode",
title: "I have a recovery code",
icon: "key"
}
];
const getMethods = () => {
return methods.filter(
m =>
(m) =>
m.id === mfaInfo?.primaryMethod ||
m.id === mfaInfo?.secondaryMethod ||
m.id === 'recoveryCode'
m.id === "recoveryCode"
);
};
useEffect(() => {
if (currentMethod.method === 'sms' || currentMethod.method === 'email') {
if (currentMethod.method === "sms" || currentMethod.method === "email") {
onSendCode();
}
}, [currentMethod.method]);
@@ -114,13 +114,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
// TODO
setSending(true);
try {
console.log('sending code', currentMethod.method, mfaInfo.token);
console.log("sending code", currentMethod.method, mfaInfo.token);
await db.mfa.sendCode(currentMethod.method, mfaInfo.token);
start(60);
setSending(false);
} catch (e) {
setSending(false);
ToastEvent.error(e, 'Error sending 2FA Code', 'local');
ToastEvent.error(e, "Error sending 2FA Code", "local");
}
};
@@ -128,7 +128,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
<View>
<View
style={{
alignItems: 'center',
alignItems: "center",
paddingHorizontal: currentMethod.method ? 12 : 0
}}
>
@@ -143,29 +143,34 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
/>
<Heading
style={{
textAlign: 'center'
textAlign: "center"
}}
>
{currentMethod.method
? 'Two factor authentication'
: 'Select methods for two-factor authentication'}
? "Two factor authentication"
: "Select methods for two-factor authentication"}
</Heading>
<Paragraph
style={{
width: '80%',
textAlign: 'center'
width: "80%",
textAlign: "center"
}}
>
{codeHelpText[currentMethod.method] || `Select how you would like to recieve the code`}
{codeHelpText[currentMethod.method] ||
`Select how you would like to recieve the code`}
</Paragraph>
<Seperator />
{currentMethod.method === 'sms' || currentMethod.method === 'email' ? (
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
<Button
onPress={onSendCode}
type={seconds ? 'gray' : 'transparent'}
title={sending ? '' : `${seconds ? `Resend code in (${seconds})` : 'Send code'}`}
type={seconds ? "gray" : "transparent"}
title={
sending
? ""
: `${seconds ? `Resend code in (${seconds})` : "Send code"}`
}
loading={sending}
height={30}
/>
@@ -176,11 +181,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
{currentMethod.method ? (
<>
<Input
placeholder={currentMethod.method === 'recoveryCode' ? 'xxxxxxxx' : 'xxxxxx'}
maxLength={currentMethod.method === 'recoveryCode' ? 8 : 6}
placeholder={
currentMethod.method === "recoveryCode" ? "xxxxxxxx" : "xxxxxx"
}
maxLength={currentMethod.method === "recoveryCode" ? 8 : 6}
fwdRef={inputRef}
textAlign="center"
onChangeText={value => {
onChangeText={(value) => {
code.current = value;
onNext();
}}
@@ -188,22 +195,24 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
inputStyle={{
fontSize: SIZE.lg,
height: 60,
textAlign: 'center',
textAlign: "center",
letterSpacing: 10,
width: null
}}
keyboardType={currentMethod.method === 'recoveryCode' ? 'default' : 'numeric'}
keyboardType={
currentMethod.method === "recoveryCode" ? "default" : "numeric"
}
containerStyle={{
height: 60,
borderWidth: 0,
//@ts-ignore
width: null,
minWidth: '50%'
minWidth: "50%"
}}
/>
<Seperator />
<Button
title={loading ? null : 'Next'}
title={loading ? null : "Next"}
type="accent"
width={250}
loading={loading}
@@ -223,7 +232,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
</>
) : (
<>
{getMethods().map(item => (
{getMethods().map((item) => (
<PressableButton
key={item.title}
onPress={() => {
@@ -236,11 +245,11 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
flexDirection: 'row',
flexDirection: "row",
borderRadius: 0,
alignItems: 'center',
width: '100%',
justifyContent: 'flex-start'
alignItems: "center",
width: "100%",
justifyContent: "flex-start"
}}
>
<IconButton
@@ -271,12 +280,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
};
TwoFactorVerification.present = (onMfaLogin, data, context) => {
console.log('presenting sheet');
console.log("presenting sheet");
presentSheet({
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
context: context || 'two_factor_verify',
context: context || "two_factor_verify",
onClose: () => {
console.log('on close called');
console.log("on close called");
onMfaLogin();
},
disableClosing: true

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { useSelectionStore } from '../../stores/use-selection-store';
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { useSelectionStore } from "../../stores/use-selection-store";
export const ContainerHeader = ({ children }) => {
const colors = useThemeStore(state => state.colors);
const selectionMode = useSelectionStore(state => state.selectionMode);
const colors = useThemeStore((state) => state.colors);
const selectionMode = useSelectionStore((state) => state.selectionMode);
return !selectionMode ? (
<View
style={{
backgroundColor: colors.bg,
width: '100%',
overflow: 'hidden'
width: "100%",
overflow: "hidden"
}}
>
{children}

View File

@@ -1,25 +1,30 @@
import React, { useEffect } from 'react';
import { Keyboard, Platform, View } from 'react-native';
import React, { useEffect } from "react";
import { Keyboard, Platform, View } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { notesnook } from '../../../e2e/test.ids';
import { editorState } from '../../screens/editor/tiptap/utils';
import { useSelectionStore } from '../../stores/use-selection-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from '../../utils';
import { normalize, SIZE } from '../../utils/size';
import { PressableButton } from '../ui/pressable';
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import { editorState } from "../../screens/editor/tiptap/utils";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from "../../utils";
import { normalize, SIZE } from "../../utils/size";
import { PressableButton } from "../ui/pressable";
export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow = false }) => {
export const FloatingButton = ({
title,
onPress,
color = "accent",
shouldShow = false
}) => {
const insets = useSafeAreaInsets();
const deviceMode = useSettingStore(state => state.deviceMode);
const selectionMode = useSelectionStore(state => state.selectionMode);
const deviceMode = useSettingStore((state) => state.deviceMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const translate = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
@@ -48,19 +53,19 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
const onKeyboardHide = async () => {
editorState().keyboardState = false;
if (deviceMode !== 'mobile') return;
if (deviceMode !== "mobile") return;
animate(0);
};
const onKeyboardShow = async () => {
editorState().keyboardState = true;
if (deviceMode !== 'mobile') return;
if (deviceMode !== "mobile") return;
animate(150);
};
useEffect(() => {
let sub1 = Keyboard.addListener('keyboardDidShow', onKeyboardShow);
let sub2 = Keyboard.addListener('keyboardDidHide', onKeyboardHide);
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
return () => {
sub1?.remove();
sub2?.remove();
@@ -72,13 +77,13 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
iPad: 20
};
return deviceMode !== 'mobile' && !shouldShow ? null : (
return deviceMode !== "mobile" && !shouldShow ? null : (
<Animated.View
style={[
{
position: 'absolute',
position: "absolute",
right: 12,
bottom: paddings[Platform.isPad ? 'iPad' : Platform.OS],
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
zIndex: 10
},
animatedStyle
@@ -87,27 +92,27 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
<PressableButton
testID={notesnook.buttons.add}
type="accent"
accentColor={color || 'accent'}
accentColor={color || "accent"}
accentText="light"
customStyle={{
...getElevation(5),
borderRadius: 100
}}
onLongPress={event => {
onLongPress={(event) => {
showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
}}
onPress={onPress}
>
<View
style={{
alignItems: 'center',
justifyContent: 'center',
alignItems: "center",
justifyContent: "center",
height: normalize(60),
width: normalize(60)
}}
>
<Icon
name={title === 'Clear all trash' ? 'delete' : 'plus'}
name={title === "Clear all trash" ? "delete" : "plus"}
color="white"
size={SIZE.xxl}
/>

View File

@@ -1,16 +1,18 @@
import React from 'react';
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
import { useSettingStore } from '../../stores/use-setting-store';
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
import { Header } from '../header';
import SelectionHeader from '../selection-header';
import React from "react";
import { KeyboardAvoidingView, Platform, SafeAreaView } from "react-native";
import { useSettingStore } from "../../stores/use-setting-store";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { Header } from "../header";
import SelectionHeader from "../selection-header";
export const Container = ({ children }) => {
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(state => state.settings.introCompleted);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
return (
<KeyboardAvoidingView
behavior="padding"
enabled={Platform.OS === 'ios' && !floating}
enabled={Platform.OS === "ios" && !floating}
style={{
flex: 1
}}
@@ -18,7 +20,7 @@ export const Container = ({ children }) => {
<SafeAreaView
style={{
flex: 1,
overflow: 'hidden'
overflow: "hidden"
}}
>
{!introCompleted ? null : (

View File

@@ -1,29 +1,31 @@
import React from 'react';
import { View } from 'react-native';
import { useMessageStore } from '../../stores/use-message-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { COLORS_NOTE } from '../../utils/color-scheme';
import { hexToRGBA } from '../../utils/color-scheme/utils';
import React from "react";
import { View } from "react-native";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { COLORS_NOTE } from "../../utils/color-scheme";
import { hexToRGBA } from "../../utils/color-scheme/utils";
export const DefaultPlaceholder = ({ color }: { color: string }) => {
const colors = useThemeStore(state => state.colors);
const message = useMessageStore(state => state.message);
const annoucements = useMessageStore(state => state.announcements);
const colors = useThemeStore((state) => state.colors);
const message = useMessageStore((state) => state.message);
const annoucements = useMessageStore((state) => state.announcements);
const hasAnnoucements = annoucements.length > 0;
//@ts-ignore
const shadeColor = color ? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15) : colors.shade;
const shadeColor = color
? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15)
: colors.shade;
return (
<View
style={{
width: '100%',
width: "100%",
paddingHorizontal: 12
}}
>
{hasAnnoucements ? (
<View
style={{
width: '100%',
width: "100%",
height: 100,
borderRadius: 10,
marginBottom: 20,
@@ -64,12 +66,12 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
{message ? (
<View
style={{
width: '100%',
width: "100%",
height: 60,
borderRadius: 10,
marginBottom: 20,
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 20
}}
>
@@ -106,15 +108,15 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
<View
style={{
width: '100%',
width: "100%",
height: 30,
backgroundColor: colors.nav,
borderRadius: 10,
marginBottom: 20,
padding: 5,
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'center'
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center"
}}
>
<View
@@ -128,7 +130,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
<View
style={{
flexDirection: 'row'
flexDirection: "row"
}}
>
<View
@@ -161,7 +163,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
/>
<View
style={{
width: '85%',
width: "85%",
height: 13,
backgroundColor: colors.nav,
borderRadius: 5,
@@ -171,7 +173,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
<View
style={{
flexDirection: 'row',
flexDirection: "row",
marginTop: 10
}}
>

View File

@@ -1,15 +1,15 @@
import React from 'react';
import { ViewProps } from 'react-native';
import Animated, { FadeOutUp } from 'react-native-reanimated';
import { useThemeStore } from '../../stores/use-theme-store';
import { useDelayLayout } from '../../hooks/use-delay-layout';
import { DefaultPlaceholder } from './default-placeholder';
import { SettingsPlaceholder } from './settings-placeholder';
import React from "react";
import { ViewProps } from "react-native";
import Animated, { FadeOutUp } from "react-native-reanimated";
import { useThemeStore } from "../../stores/use-theme-store";
import { useDelayLayout } from "../../hooks/use-delay-layout";
import { DefaultPlaceholder } from "./default-placeholder";
import { SettingsPlaceholder } from "./settings-placeholder";
interface IDelayLayoutProps extends ViewProps {
delay?: number;
wait?: boolean;
type?: 'default' | 'settings';
type?: "default" | "settings";
color?: string;
animated?: boolean;
}
@@ -19,10 +19,15 @@ const placeholder = {
settings: SettingsPlaceholder
};
export default function DelayLayout({ animated = true, ...props }: IDelayLayoutProps) {
const colors = useThemeStore(state => state.colors);
const loading = useDelayLayout(!props.delay || props.delay < 300 ? 300 : props.delay);
const Placeholder = placeholder[props.type || 'default'];
export default function DelayLayout({
animated = true,
...props
}: IDelayLayoutProps) {
const colors = useThemeStore((state) => state.colors);
const loading = useDelayLayout(
!props.delay || props.delay < 300 ? 300 : props.delay
);
const Placeholder = placeholder[props.type || "default"];
return loading || props.wait ? (
<Animated.View

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
export const SettingsPlaceholder = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<View>
@@ -19,12 +19,12 @@ export const SettingsPlaceholder = () => {
/>
<View
style={{
width: '100%',
width: "100%",
height: 60,
borderRadius: 10,
marginBottom: 20,
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16
}}
>
@@ -60,14 +60,14 @@ export const SettingsPlaceholder = () => {
<View
style={{
width: '100%',
width: "100%",
height: 60,
borderRadius: 10,
marginBottom: 20,
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
justifyContent: 'space-between'
justifyContent: "space-between"
}}
>
<View
@@ -106,8 +106,8 @@ export const SettingsPlaceholder = () => {
backgroundColor: colors.nav,
borderRadius: 100,
marginLeft: 15,
alignItems: 'flex-end',
justifyContent: 'center',
alignItems: "flex-end",
justifyContent: "center",
paddingHorizontal: 4
}}
>

View File

@@ -1,33 +1,33 @@
import React from 'react';
import { useNoteStore } from '../../stores/use-notes-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { AnnouncementDialog } from '../announcements';
import { AttachmentDialog } from '../attachments';
import Auth from '../auth';
import AuthModal from '../auth/auth-modal';
import { SessionExpired } from '../auth/session-expired';
import { Dialog } from '../dialog';
import { AddTopicDialog } from '../dialogs/add-topic';
import ResultDialog from '../dialogs/result';
import { VaultDialog } from '../dialogs/vault';
import ImagePreview from '../image-preview';
import MergeConflicts from '../merge-conflicts';
import PremiumDialog from '../premium';
import { Expiring } from '../premium/expiring';
import SheetProvider from '../sheet-provider';
import { AddNotebookSheet } from '../sheets/add-notebook';
import AddToNotebookSheet from '../sheets/add-to';
import ExportNotesSheet from '../sheets/export-notes';
import ManageTagsSheet from '../sheets/manage-tags';
import PublishNoteSheet from '../sheets/publish-note';
import RateAppSheet from '../sheets/rate-app';
import RecoveryKeySheet from '../sheets/recovery-key';
import RestoreDataSheet from '../sheets/restore-data';
import React from "react";
import { useNoteStore } from "../../stores/use-notes-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { AnnouncementDialog } from "../announcements";
import { AttachmentDialog } from "../attachments";
import Auth from "../auth";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
import { AddTopicDialog } from "../dialogs/add-topic";
import ResultDialog from "../dialogs/result";
import { VaultDialog } from "../dialogs/vault";
import ImagePreview from "../image-preview";
import MergeConflicts from "../merge-conflicts";
import PremiumDialog from "../premium";
import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider";
import { AddNotebookSheet } from "../sheets/add-notebook";
import AddToNotebookSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import PublishNoteSheet from "../sheets/publish-note";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
const DialogProvider = React.memo(
() => {
const colors = useThemeStore(state => state.colors);
const loading = useNoteStore(state => state.loading);
const colors = useThemeStore((state) => state.colors);
const loading = useNoteStore((state) => state.loading);
return (
<>

View File

@@ -1,4 +1,4 @@
import { eSendEvent } from '../../services/event-manager';
import { eSendEvent } from "../../services/event-manager";
import {
eCloseActionSheet,
eCloseAddNotebookDialog,
@@ -8,7 +8,7 @@ import {
eOpenAddNotebookDialog,
eOpenAddTopicDialog,
eOpenMoveNoteDialog
} from '../../utils/events';
} from "../../utils/events";
export const ActionSheetEvent = (item, buttons) => {
eSendEvent(eOpenActionSheet, {
@@ -27,15 +27,15 @@ export const moveNoteHideEvent = () => {
eSendEvent(eCloseMoveNoteDialog);
};
export const AddNotebookEvent = notebook => {
export const AddNotebookEvent = (notebook) => {
eSendEvent(eOpenAddNotebookDialog, notebook);
};
export const HideAddNotebookEvent = notebook => {
export const HideAddNotebookEvent = (notebook) => {
eSendEvent(eCloseAddNotebookDialog, notebook);
};
export const AddTopicEvent = topic => {
export const AddTopicEvent = (topic) => {
eSendEvent(eOpenAddTopicDialog, topic);
};
export const HideAddTopicEvent = notebook => {
export const HideAddTopicEvent = (notebook) => {
eSendEvent(eCloseAddTopicDialog, notebook);
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect } from "react";
import {
KeyboardAvoidingView,
Modal,
@@ -7,17 +7,17 @@ import {
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import { useSettingStore } from '../../stores/use-setting-store';
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
import { BouncingView } from '../ui/transitions/bouncing-view';
} from "react-native";
import { useSettingStore } from "../../stores/use-setting-store";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { BouncingView } from "../ui/transitions/bouncing-view";
const BaseDialog = ({
visible,
onRequestClose,
children,
onShow,
animation = 'fade',
animation = "fade",
premium,
statusBarTranslucent = true,
transparent,
@@ -46,11 +46,11 @@ const BaseDialog = ({
animated
statusBarTranslucent={statusBarTranslucent}
supportedOrientations={[
'portrait',
'portrait-upside-down',
'landscape',
'landscape-left',
'landscape-right'
"portrait",
"portrait-upside-down",
"landscape",
"landscape-left",
"landscape-right"
]}
onShow={() => {
if (onShow) {
@@ -67,10 +67,17 @@ const BaseDialog = ({
>
<Wrapper
style={{
backgroundColor: background ? background : transparent ? 'transparent' : 'rgba(0,0,0,0.3)'
backgroundColor: background
? background
: transparent
? "transparent"
: "rgba(0,0,0,0.3)"
}}
>
<KeyboardAvoidingView enabled={!floating && Platform.OS === 'ios'} behavior="padding">
<KeyboardAvoidingView
enabled={!floating && Platform.OS === "ios"}
behavior="padding"
>
<BouncingView
duration={400}
animated={animated}
@@ -78,7 +85,11 @@ const BaseDialog = ({
style={[
styles.backdrop,
{
justifyContent: centered ? 'center' : bottom ? 'flex-end' : 'flex-start'
justifyContent: centered
? "center"
: bottom
? "flex-end"
: "flex-start"
}
]}
>
@@ -97,15 +108,15 @@ const BaseDialog = ({
const styles = StyleSheet.create({
backdrop: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center'
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
},
overlayButton: {
width: '100%',
height: '100%',
position: 'absolute'
width: "100%",
height: "100%",
position: "absolute"
}
});

View File

@@ -1,22 +1,22 @@
import React from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import { Button } from '../ui/button';
import Paragraph from '../ui/typography/paragraph';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { notesnook } from '../../../e2e/test.ids';
import React from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
const DialogButtons = ({
onPressPositive,
onPressNegative,
positiveTitle,
negativeTitle = 'Cancel',
negativeTitle = "Cancel",
loading,
doneText,
positiveType
}) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<View
@@ -36,12 +36,16 @@ const DialogButtons = ({
) : doneText ? (
<View
style={{
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
}}
>
<Icon color={colors.accent} name="check-circle-outline" size={SIZE.md} />
<Paragraph color={colors.accent}>{' ' + doneText}</Paragraph>
<Icon
color={colors.accent}
name="check-circle-outline"
size={SIZE.md}
/>
<Paragraph color={colors.accent}>{" " + doneText}</Paragraph>
</View>
) : (
<View />
@@ -49,8 +53,8 @@ const DialogButtons = ({
<View
style={{
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
}}
>
<Button
@@ -70,7 +74,7 @@ const DialogButtons = ({
marginLeft: 10
}}
bold
type={positiveType || 'transparent'}
type={positiveType || "transparent"}
title={positiveTitle}
/>
) : null}
@@ -83,9 +87,9 @@ export default DialogButtons;
const styles = StyleSheet.create({
container: {
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
justifyContent: "space-between",
alignItems: "center",
flexDirection: "row",
marginTop: 10
}
});

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { DDS } from '../../services/device-detection';
import { getElevation } from '../../utils';
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { DDS } from "../../services/device-detection";
import { getElevation } from "../../utils";
const DialogContainer = ({ width, height, ...restProps }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<View
{...restProps}
style={{
...getElevation(5),
width: width || DDS.isTab ? 500 : '85%',
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.bg,

View File

@@ -1,12 +1,12 @@
import React from 'react';
import { Text } from 'react-native';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import { Button } from '../ui/button';
import { PressableButtonProps } from '../ui/pressable';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import React from "react";
import { Text } from "react-native";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButtonProps } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
type DialogHeaderProps = {
icon?: string;
@@ -16,7 +16,7 @@ type DialogHeaderProps = {
onPress?: () => void;
loading?: boolean;
title?: string;
type?: PressableButtonProps['type'];
type?: PressableButtonProps["type"];
};
paragraphColor?: string;
padding?: number;
@@ -34,33 +34,39 @@ const DialogHeader = ({
centered,
titlePart
}: DialogHeaderProps) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
minHeight: 50,
paddingHorizontal: padding
}}
>
<View
style={{
width: '100%'
width: "100%"
}}
>
<View
style={{
flexDirection: 'row',
justifyContent: centered ? 'center' : 'space-between',
alignItems: 'center'
flexDirection: "row",
justifyContent: centered ? "center" : "space-between",
alignItems: "center"
}}
>
<Heading style={{ textAlign: centered ? 'center' : 'left' }} size={SIZE.lg}>
{title} {titlePart ? <Text style={{ color: colors.accent }}>{titlePart}</Text> : null}
<Heading
style={{ textAlign: centered ? "center" : "left" }}
size={SIZE.lg}
>
{title}{" "}
{titlePart ? (
<Text style={{ color: colors.accent }}>{titlePart}</Text>
) : null}
</Heading>
{button ? (
@@ -73,7 +79,7 @@ const DialogHeader = ({
loading={button.loading}
fontSize={13}
title={button.title}
type={button.type || 'grayBg'}
type={button.type || "grayBg"}
height={25}
/>
) : null}
@@ -82,9 +88,9 @@ const DialogHeader = ({
{paragraph ? (
<Paragraph
style={{
textAlign: centered ? 'center' : 'left',
maxWidth: centered ? '90%' : '100%',
alignSelf: centered ? 'center' : 'flex-start'
textAlign: centered ? "center" : "left",
maxWidth: centered ? "90%" : "100%",
alignSelf: centered ? "center" : "flex-start"
}}
color={paragraphColor || colors.icon}
>

View File

@@ -1,5 +1,5 @@
import { eSendEvent } from '../../services/event-manager';
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
import { eSendEvent } from "../../services/event-manager";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
type DialogInfo = {
title?: string;
@@ -9,20 +9,20 @@ type DialogInfo = {
positivePress?: (value: any) => void;
onClose?: () => void;
positiveType?:
| 'transparent'
| 'gray'
| 'grayBg'
| 'accent'
| 'inverted'
| 'shade'
| 'error'
| 'errorShade';
| "transparent"
| "gray"
| "grayBg"
| "accent"
| "inverted"
| "shade"
| "error"
| "errorShade";
icon?: string;
paragraphColor: string;
input: boolean;
inputPlaceholder: string;
defaultValue: string;
context: 'global' | 'local';
context: "global" | "local";
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -1,36 +1,39 @@
import React, { useEffect, useRef, useState } from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { DDS } from '../../services/device-detection';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { getElevation } from '../../utils';
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
import { sleep } from '../../utils/time';
import Input from '../ui/input';
import Seperator from '../ui/seperator';
import { Toast } from '../toast';
import BaseDialog from './base-dialog';
import DialogButtons from './dialog-buttons';
import DialogHeader from './dialog-header';
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { getElevation } from "../../utils";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import { Toast } from "../toast";
import BaseDialog from "./base-dialog";
import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
export const Dialog = ({ context = 'global' }) => {
const colors = useThemeStore(state => state.colors);
export const Dialog = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [inputValue, setInputValue] = useState(null);
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: '',
paragraph: '',
positiveText: 'Done',
negativeText: 'Cancel',
title: "",
paragraph: "",
positiveText: "Done",
negativeText: "Cancel",
positivePress: () => {},
onClose: () => {},
positiveType: 'transparent',
positiveType: "transparent",
icon: null,
paragraphColor: colors.pri,
input: false,
inputPlaceholder: 'Enter some text',
defaultValue: '',
inputPlaceholder: "Enter some text",
defaultValue: "",
disableBackdropClosing: false
});
@@ -47,7 +50,9 @@ export const Dialog = ({ context = 'global' }) => {
const onPressPositive = async () => {
if (dialogInfo.positivePress) {
inputRef.current?.blur();
let result = await dialogInfo.positivePress(inputValue || dialogInfo.defaultValue);
let result = await dialogInfo.positivePress(
inputValue || dialogInfo.defaultValue
);
if (result === false) {
return;
}
@@ -56,8 +61,8 @@ export const Dialog = ({ context = 'global' }) => {
hide();
};
const show = data => {
if (!data.context) data.context = 'global';
const show = (data) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setVisible(true);
@@ -78,7 +83,7 @@ export const Dialog = ({ context = 'global' }) => {
const style = {
...getElevation(5),
width: DDS.isTab ? 400 : '85%',
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
borderRadius: 5,
backgroundColor: colors.bg,
@@ -121,7 +126,7 @@ export const Dialog = ({ context = 'global' }) => {
<Input
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={value => {
onChangeText={(value) => {
setInputValue(value);
}}
testID="input-value"

View File

@@ -1,19 +1,30 @@
import React, { createRef } from 'react';
import { Keyboard, LayoutAnimation, UIManager, View } from 'react-native';
import { Transition, Transitioning, TransitioningView } from 'react-native-reanimated';
import { useMenuStore } from '../../../stores/use-menu-store';
import { eSubscribeEvent, eUnSubscribeEvent, ToastEvent } from '../../../services/event-manager';
import Navigation from '../../../services/navigation';
import { db } from '../../../common/database';
import { eCloseAddTopicDialog, eOpenAddTopicDialog } from '../../../utils/events';
import { sleep } from '../../../utils/time';
import BaseDialog from '../../dialog/base-dialog';
import DialogButtons from '../../dialog/dialog-buttons';
import DialogContainer from '../../dialog/dialog-container';
import DialogHeader from '../../dialog/dialog-header';
import Input from '../../ui/input';
import Seperator from '../../ui/seperator';
import { Toast } from '../../toast';
import React, { createRef } from "react";
import { Keyboard, LayoutAnimation, UIManager, View } from "react-native";
import {
Transition,
Transitioning,
TransitioningView
} from "react-native-reanimated";
import { useMenuStore } from "../../../stores/use-menu-store";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddTopicDialog,
eOpenAddTopicDialog
} from "../../../utils/events";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogContainer from "../../dialog/dialog-container";
import DialogHeader from "../../dialog/dialog-header";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { Toast } from "../../toast";
export class AddTopicDialog extends React.Component {
constructor(props) {
@@ -34,11 +45,11 @@ export class AddTopicDialog extends React.Component {
addNewTopic = async () => {
try {
this.setState({ loading: true });
if (!this.title || this.title?.trim() === '') {
if (!this.title || this.title?.trim() === "") {
ToastEvent.show({
heading: 'Topic title is required',
type: 'error',
context: 'local'
heading: "Topic title is required",
type: "error",
context: "local"
});
this.setState({ loading: false });
return;
@@ -54,7 +65,7 @@ export class AddTopicDialog extends React.Component {
}
this.setState({ loading: false });
this.close();
Navigation.queueRoutesForUpdate('Notebooks', 'Notebook', 'TopicNotes');
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
} catch (e) {}
};
@@ -116,9 +127,11 @@ export class AddTopicDialog extends React.Component {
<DialogContainer>
<DialogHeader
icon="book-outline"
title={this.toEdit ? 'Edit topic' : 'New topic'}
title={this.toEdit ? "Edit topic" : "New topic"}
paragraph={
this.toEdit ? 'Edit title of the topic' : 'Add a new topic in ' + this.notebook.title
this.toEdit
? "Edit title of the topic"
: "Add a new topic in " + this.notebook.title
}
padding={12}
/>
@@ -132,7 +145,7 @@ export class AddTopicDialog extends React.Component {
<Input
fwdRef={this.titleRef}
testID="input-title"
onChangeText={value => {
onChangeText={(value) => {
this.title = value;
}}
blurOnSubmit={false}
@@ -144,7 +157,7 @@ export class AddTopicDialog extends React.Component {
</View>
<DialogButtons
positiveTitle={this.toEdit ? 'Save' : 'Add'}
positiveTitle={this.toEdit ? "Save" : "Add"}
onPressNegative={() => this.close()}
onPressPositive={() => this.addNewTopic()}
loading={this.state.loading}

View File

@@ -1,26 +1,35 @@
import React, { useEffect, useState } from 'react';
import { ScrollView, View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { useMessageStore } from '../../../stores/use-message-store';
import { DDS } from '../../../services/device-detection';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
import { getElevation } from '../../../utils';
import { eCloseJumpToDialog, eOpenJumpToDialog, eScrollEvent } from '../../../utils/events';
import { SIZE } from '../../../utils/size';
import BaseDialog from '../../dialog/base-dialog';
import { PressableButton } from '../../ui/pressable';
import Paragraph from '../../ui/typography/paragraph';
import React, { useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useMessageStore } from "../../../stores/use-message-store";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { getElevation } from "../../../utils";
import {
eCloseJumpToDialog,
eOpenJumpToDialog,
eScrollEvent
} from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
import { PressableButton } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
const offsets = [];
let timeout = null;
const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const notes = data;
const [visible, setVisible] = useState(false);
const [currentIndex, setCurrentIndex] = useState(null);
const onPress = (item, index) => {
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
let ind = notes.findIndex(
(i) => i.title === item.title && i.type === "header"
);
console.log(scrollRef.current);
scrollRef.current?.scrollToIndex({
index: ind,
@@ -41,7 +50,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
};
}, []);
const onScroll = data => {
const onScroll = (data) => {
let y = data.y;
if (timeout) {
clearTimeout(timeout);
@@ -53,7 +62,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
}, 200);
};
const open = _type => {
const open = (_type) => {
if (_type !== type) return;
setVisible(true);
};
@@ -68,10 +77,12 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
const loadOffsets = () => {
notes
.filter(i => i.type === 'header')
.filter((i) => i.type === "header")
.map((item, index) => {
let offset = 35 * index;
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
let ind = notes.findIndex(
(i) => i.title === item.title && i.type === "header"
);
let messageState = useMessageStore.getState().message;
let msgOffset = messageState?.visible ? 60 : 10;
ind = ind + 1;
@@ -92,41 +103,41 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
<View
style={{
...getElevation(5),
width: DDS.isTab ? 500 : '85%',
width: DDS.isTab ? 500 : "85%",
backgroundColor: colors.bg,
zIndex: 100,
bottom: 20,
maxHeight: '65%',
maxHeight: "65%",
borderRadius: 10,
alignSelf: 'center',
alignSelf: "center",
padding: 10,
paddingTop: 30
}}
>
<ScrollView
style={{
maxHeight: '100%'
maxHeight: "100%"
}}
>
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
alignSelf: 'center',
justifyContent: 'center',
flexDirection: "row",
flexWrap: "wrap",
alignSelf: "center",
justifyContent: "center",
paddingBottom: 20
}}
>
{notes
.filter(i => i.type === 'header')
.filter((i) => i.type === "header")
.map((item, index) => {
return item.title ? (
<PressableButton
key={item.title}
onPress={() => onPress(item, index)}
type={currentIndex === index ? 'accent' : 'transparent'}
type={currentIndex === index ? "accent" : "transparent"}
customStyle={{
minWidth: '20%',
minWidth: "20%",
width: null,
paddingHorizontal: 12,
margin: 5,
@@ -137,9 +148,11 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
>
<Paragraph
size={SIZE.sm}
color={currentIndex === index ? colors.light : colors.accent}
color={
currentIndex === index ? colors.light : colors.accent
}
style={{
textAlign: 'center'
textAlign: "center"
}}
>
{item.title}

View File

@@ -1,25 +1,29 @@
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { DDS } from '../../../services/device-detection';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
import { getElevation } from '../../../utils';
import { eCloseResultDialog, eOpenResultDialog } from '../../../utils/events';
import { SIZE } from '../../../utils/size';
import { Button } from '../../ui/button';
import BaseDialog from '../../dialog/base-dialog';
import Seperator from '../../ui/seperator';
import Heading from '../../ui/typography/heading';
import Paragraph from '../../ui/typography/paragraph';
import { ProFeatures } from './pro-features';
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { getElevation } from "../../../utils";
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import BaseDialog from "../../dialog/base-dialog";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { ProFeatures } from "./pro-features";
const ResultDialog = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [dialogData, setDialogData] = useState({
title: 'Thank you for signing up!',
paragraph: 'Try out all features of Notesnook free for 7 days. No limitations. No commitments.',
button: 'Start taking notes'
title: "Thank you for signing up!",
paragraph:
"Try out all features of Notesnook free for 7 days. No limitations. No commitments.",
button: "Start taking notes"
});
useEffect(() => {
eSubscribeEvent(eOpenResultDialog, open);
@@ -30,7 +34,7 @@ const ResultDialog = () => {
};
}, []);
const open = data => {
const open = (data) => {
if (data) {
setDialogData(data);
}
@@ -46,23 +50,23 @@ const ResultDialog = () => {
<View
style={{
...getElevation(5),
width: DDS.isTab ? 350 : '85%',
width: DDS.isTab ? 350 : "85%",
maxHeight: 500,
borderRadius: 10,
backgroundColor: colors.bg,
paddingTop: 20,
justifyContent: 'center',
alignItems: 'center'
justifyContent: "center",
alignItems: "center"
}}
>
<Heading
size={SIZE.lg}
textBreakStrategy="balanced"
style={{
alignSelf: 'center',
textAlign: 'center',
alignSelf: "center",
textAlign: "center",
marginTop: 10,
maxWidth: '100%',
maxWidth: "100%",
marginBottom: 10,
paddingHorizontal: 12
}}
@@ -74,9 +78,9 @@ const ResultDialog = () => {
color={colors.icon}
size={SIZE.md}
style={{
alignSelf: 'center',
textAlign: 'center',
maxWidth: '80%',
alignSelf: "center",
textAlign: "center",
maxWidth: "80%",
lineHeight: SIZE.sm + 5
}}
>
@@ -88,8 +92,8 @@ const ResultDialog = () => {
<View
style={{
paddingHorizontal: 12,
alignItems: 'center',
width: '100%'
alignItems: "center",
width: "100%"
}}
>
<ProFeatures count={4} />
@@ -99,7 +103,7 @@ const ResultDialog = () => {
<View
style={{
backgroundColor: colors.nav,
width: '100%',
width: "100%",
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
paddingVertical: 10

View File

@@ -1,58 +1,63 @@
import React from 'react';
import { View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useThemeStore } from '../../../stores/use-theme-store';
import { eSendEvent } from '../../../services/event-manager';
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eSendEvent } from "../../../services/event-manager";
import {
eCloseProgressDialog,
eCloseResultDialog,
eOpenPremiumDialog
} from '../../../utils/events';
import { SIZE } from '../../../utils/size';
import { sleep } from '../../../utils/time';
import Paragraph from '../../ui/typography/paragraph';
} from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import Paragraph from "../../ui/typography/paragraph";
export const ProFeatures = ({ count = 6 }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<>
{[
{
content: 'Unlock unlimited notebooks, tags, colors. Organize like a pro'
content:
"Unlock unlimited notebooks, tags, colors. Organize like a pro"
},
{
content: 'Attach files upto 500MB, upload 4K images with unlimited storage'
content:
"Attach files upto 500MB, upload 4K images with unlimited storage"
},
{
content: 'Instantly sync to unlimited devices'
content: "Instantly sync to unlimited devices"
},
{
content: 'A private vault to keep everything imporant always locked'
content: "A private vault to keep everything imporant always locked"
},
{
content: 'Rich note editing experience with markdown, tables, checklists and more'
content:
"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"
}
]
.slice(0, count)
.map(item => (
.map((item) => (
<View
key={item.content}
style={{
flexDirection: 'row',
width: '100%',
flexDirection: "row",
width: "100%",
height: 40,
paddingHorizontal: 0,
marginBottom: 10,
alignItems: 'center',
alignItems: "center",
borderRadius: 5,
justifyContent: 'flex-start'
justifyContent: "flex-start"
}}
>
<Icon size={SIZE.lg} color={colors.accent} name="check" />
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>{item.content}</Paragraph>
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>
{item.content}
</Paragraph>
</View>
))}
<Paragraph
@@ -64,7 +69,7 @@ export const ProFeatures = ({ count = 6 }) => {
}}
size={SIZE.xs + 1}
style={{
textDecorationLine: 'underline',
textDecorationLine: "underline",
color: colors.icon
}}
>

View File

@@ -1,39 +1,39 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { Component, createRef } from 'react';
import { InteractionManager, View } from 'react-native';
import Share from 'react-native-share';
import { notesnook } from '../../../../e2e/test.ids';
import { editorController } from '../../../screens/editor/tiptap/utils';
import BiometricService from '../../../services/biometrics';
import { DDS } from '../../../services/device-detection';
import Clipboard from "@react-native-clipboard/clipboard";
import React, { Component, createRef } from "react";
import { InteractionManager, View } from "react-native";
import Share from "react-native-share";
import { notesnook } from "../../../../e2e/test.ids";
import { editorController } from "../../../screens/editor/tiptap/utils";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from '../../../services/event-manager';
import Navigation from '../../../services/navigation';
import SearchService from '../../../services/search';
import { getElevation, toTXT } from '../../../utils';
import { db } from '../../../common/database';
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { getElevation, toTXT } from "../../../utils";
import { db } from "../../../common/database";
import {
eClearEditor,
eCloseActionSheet,
eCloseVaultDialog,
eOnLoadNote,
eOpenVaultDialog
} from '../../../utils/events';
import { deleteItems } from '../../../utils/functions';
import { tabBarRef } from '../../../utils/global-refs';
import { sleep } from '../../../utils/time';
import BaseDialog from '../../dialog/base-dialog';
import DialogButtons from '../../dialog/dialog-buttons';
import DialogHeader from '../../dialog/dialog-header';
import { Toast } from '../../toast';
import { Button } from '../../ui/button';
import Input from '../../ui/input';
import Seperator from '../../ui/seperator';
import Paragraph from '../../ui/typography/paragraph';
} from "../../../utils/events";
import { deleteItems } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogHeader from "../../dialog/dialog-header";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
let Keychain;
const passInputRef = createRef();
@@ -62,7 +62,7 @@ export class VaultDialog extends Component {
changePassword: false,
copyNote: false,
revokeFingerprintAccess: false,
title: 'Unlock Note',
title: "Unlock Note",
description: null,
clearVault: false,
deleteVault: false,
@@ -72,45 +72,45 @@ export class VaultDialog extends Component {
this.confirmPassword = null;
this.newPassword = null;
(this.title = !this.state.novault
? 'Create Vault'
? "Create Vault"
: this.state.fingerprintAccess
? 'Vault Fingerprint Unlock'
? "Vault Fingerprint Unlock"
: this.state.revokeFingerprintAccess
? 'Revoke Vault Fingerprint Unlock'
? "Revoke Vault Fingerprint Unlock"
: this.state.changePassword
? 'Change Vault Password'
? "Change Vault Password"
: this.state.note.locked
? this.state.deleteNote
? 'Delete note'
? "Delete note"
: this.state.share
? 'Share note'
? "Share note"
: this.state.copyNote
? 'Copy note'
? "Copy note"
: this.state.goToEditor
? 'Unlock note'
: 'Unlock note'
: 'Lock note'),
? "Unlock note"
: "Unlock note"
: "Lock note"),
(this.description = !this.state.novault
? 'Set a password to create vault'
? "Set a password to create vault"
: this.state.fingerprintAccess
? 'Enter vault password to enable fingerprint unlocking.'
? "Enter vault password to enable fingerprint unlocking."
: this.state.revokeFingerprintAccess
? 'Disable vault fingerprint unlock '
? "Disable vault fingerprint unlock "
: this.state.changePassword
? 'Setup a new password for the vault.'
? "Setup a new password for the vault."
: this.state.permanant
? 'Enter password to remove note from vault.'
? "Enter password to remove note from vault."
: this.state.note.locked
? this.state.deleteNote
? 'Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault.'
? "Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.share
? 'Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault.'
? "Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.copyNote
? 'Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault.'
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? 'Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault.'
: 'Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault.'
: 'Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.');
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
}
componentDidMount() {
@@ -127,13 +127,13 @@ export class VaultDialog extends Component {
*
* @param {import('../../../services/event-manager').vaultType} data
*/
open = async data => {
open = async (data) => {
if (!Keychain) {
Keychain = require('react-native-keychain');
Keychain = require("react-native-keychain");
}
let biometry = await BiometricService.isBiometryAvailable();
let available = false;
let fingerprint = await BiometricService.hasInternetCredentials('nn_vault');
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
if (biometry) {
available = true;
@@ -181,19 +181,19 @@ export class VaultDialog extends Component {
if (this.state.loading) {
ToastEvent.show({
heading: this.state.title,
message: 'Please wait and do not close the app.',
type: 'success',
context: 'local'
message: "Please wait and do not close the app.",
type: "success",
context: "local"
});
return;
}
Navigation.queueRoutesForUpdate(
'Notes',
'Favorites',
'TopicNotes',
'TaggedNotes',
'ColoredNotes'
"Notes",
"Favorites",
"TopicNotes",
"TaggedNotes",
"ColoredNotes"
);
this.password = null;
@@ -223,19 +223,19 @@ export class VaultDialog extends Component {
if (!this.password) {
ToastEvent.show({
heading: 'Password not entered',
message: 'Enter a password for the vault and try again.',
type: 'error',
context: 'local'
heading: "Password not entered",
message: "Enter a password for the vault and try again.",
type: "error",
context: "local"
});
return;
}
if (this.password && this.password.length < 3) {
ToastEvent.show({
heading: 'Password too short',
message: 'Password must be longer than 3 characters.',
type: 'error',
context: 'local'
heading: "Password too short",
message: "Password must be longer than 3 characters.",
type: "error",
context: "local"
});
return;
@@ -244,9 +244,9 @@ export class VaultDialog extends Component {
if (!this.state.novault) {
if (this.password !== this.confirmPassword) {
ToastEvent.show({
heading: 'Passwords do not match',
type: 'error',
context: 'local'
heading: "Passwords do not match",
type: "error",
context: "local"
});
this.setState({
passwordsDontMatch: true
@@ -262,7 +262,7 @@ export class VaultDialog extends Component {
db.vault
.changePassword(this.password, this.newPassword)
.then(result => {
.then((result) => {
this.setState({
loading: false
});
@@ -270,32 +270,32 @@ export class VaultDialog extends Component {
this._enrollFingerprint(this.newPassword);
}
ToastEvent.show({
heading: 'Vault password updated successfully',
type: 'success',
context: 'global'
heading: "Vault password updated successfully",
type: "success",
context: "global"
});
this.close();
})
.catch(e => {
.catch((e) => {
this.setState({
loading: false
});
if (e.message === db.vault.ERRORS.wrongPassword) {
ToastEvent.show({
heading: 'Incorrect password',
message: 'Please enter the correct password and try again',
type: 'error',
context: 'local'
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
context: "local"
});
}
});
} else if (this.state.locked) {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
heading: 'Incorrect password',
message: 'Please enter the correct password and try again',
type: 'error',
context: 'local'
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
context: "local"
});
this.setState({
wrongPassword: true
@@ -313,7 +313,7 @@ export class VaultDialog extends Component {
});
await this._lockNote();
})
.catch(e => {
.catch((e) => {
this._takeErrorAction(e);
});
}
@@ -335,17 +335,17 @@ export class VaultDialog extends Component {
if (!(await db.user.getUser())) verified = true;
if (verified) {
await db.vault.delete(this.state.deleteAll);
eSendEvent('vaultUpdated');
eSendEvent("vaultUpdated");
this.setState({
loading: false
});
this.close();
} else {
ToastEvent.show({
heading: 'Account password incorrect',
message: 'Please enter correct password for your account.',
type: 'error',
context: 'local'
heading: "Account password incorrect",
message: "Please enter correct password for your account.",
type: "error",
context: "local"
});
}
} catch (e) {}
@@ -364,13 +364,13 @@ export class VaultDialog extends Component {
loading: false
});
this.close();
eSendEvent('vaultUpdated');
eSendEvent("vaultUpdated");
} catch (e) {
ToastEvent.show({
heading: 'Vault password incorrect',
message: 'Please enter correct password to clear vault.',
type: 'error',
context: 'local'
heading: "Vault password incorrect",
message: "Please enter correct password to clear vault.",
type: "error",
context: "local"
});
}
this.setState({
@@ -381,11 +381,11 @@ export class VaultDialog extends Component {
async _lockNote() {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
heading: 'Incorrect password',
type: 'error',
context: 'local'
heading: "Incorrect password",
type: "error",
context: "local"
});
console.log('returning from here');
console.log("returning from here");
return;
} else {
await db.vault.add(this.state.note.id);
@@ -394,9 +394,9 @@ export class VaultDialog extends Component {
}
this.close();
ToastEvent.show({
message: 'Note locked successfully',
type: 'error',
context: 'local'
message: "Note locked successfully",
type: "error",
context: "local"
});
this.setState({
loading: false
@@ -407,10 +407,10 @@ export class VaultDialog extends Component {
async _unlockNote() {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
heading: 'Incorrect password',
message: 'Please enter the correct password and try again',
type: 'error',
context: 'local'
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
context: "local"
});
return;
}
@@ -464,20 +464,21 @@ export class VaultDialog extends Component {
this.setState({
loading: false
});
eSendEvent('vaultUpdated');
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: 'Biometric unlocking enabled!',
message: 'Now you can unlock notes in vault with biometrics.',
type: 'success',
context: 'global'
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
this.close();
} catch (e) {
ToastEvent.show({
heading: 'Incorrect password',
message: 'Please enter the correct vault password to enable biometrics.',
type: 'error',
context: 'local'
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
this.setState({
loading: false
@@ -506,17 +507,17 @@ export class VaultDialog extends Component {
loading: false
});
ToastEvent.show({
heading: 'Note added to vault',
type: 'success',
context: 'global'
heading: "Note added to vault",
type: "success",
context: "global"
});
this.close();
} else {
eSendEvent('vaultUpdated');
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: 'Vault created successfully',
type: 'success',
context: 'global'
heading: "Vault created successfully",
type: "success",
context: "global"
});
this.close();
}
@@ -525,15 +526,15 @@ export class VaultDialog extends Component {
_permanantUnlock() {
db.vault
.remove(this.state.note.id, this.password)
.then(r => {
.then((r) => {
ToastEvent.show({
heading: 'Note permanantly unlocked.',
type: 'success',
context: 'global'
heading: "Note permanantly unlocked.",
type: "success",
context: "global"
});
this.close();
})
.catch(e => {
.catch((e) => {
this._takeErrorAction(e);
});
}
@@ -551,10 +552,10 @@ export class VaultDialog extends Component {
async _copyNote(note) {
Clipboard.setString(await toTXT(note));
ToastEvent.show({
heading: 'Note copied',
type: 'success',
message: 'Note has been copied to clipboard!',
context: 'global'
heading: "Note copied",
type: "success",
message: "Note has been copied to clipboard!",
context: "global"
});
this.close();
}
@@ -563,7 +564,7 @@ export class VaultDialog extends Component {
this.close();
try {
await Share.open({
heading: 'Share note',
heading: "Share note",
failOnCancel: false,
message: await toTXT(note)
});
@@ -571,16 +572,19 @@ export class VaultDialog extends Component {
}
_takeErrorAction(e) {
if (e.message === db.vault.ERRORS.wrongPassword || e.message === 'FAILURE') {
if (
e.message === db.vault.ERRORS.wrongPassword ||
e.message === "FAILURE"
) {
this.setState({
wrongPassword: true,
visible: true
});
setTimeout(() => {
ToastEvent.show({
heading: 'Incorrect password',
type: 'error',
context: 'local'
heading: "Incorrect password",
type: "error",
context: "local"
});
}, 500);
@@ -591,18 +595,18 @@ export class VaultDialog extends Component {
_revokeFingerprintAccess = async () => {
try {
await BiometricService.resetCredentials();
eSendEvent('vaultUpdated');
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: 'Biometric unlocking disabled!',
type: 'success',
context: 'global'
heading: "Biometric unlocking disabled!",
type: "success",
context: "global"
});
} catch (e) {
ToastEvent.show({
heading: 'Failed to disable Biometric unlocking.',
heading: "Failed to disable Biometric unlocking.",
message: e.message,
type: 'success',
context: 'global'
type: "success",
context: "global"
});
}
};
@@ -663,7 +667,7 @@ export class VaultDialog extends Component {
<View
style={{
...getElevation(5),
width: DDS.isTab ? 350 : '85%',
width: DDS.isTab ? 350 : "85%",
borderRadius: 10,
backgroundColor: colors.bg,
paddingTop: 12
@@ -682,7 +686,10 @@ export class VaultDialog extends Component {
paddingHorizontal: 12
}}
>
{(novault || changePassword || this.state.clearVault || this.state.deleteVault) &&
{(novault ||
changePassword ||
this.state.clearVault ||
this.state.deleteVault) &&
!this.state.revokeFingerprintAccess ? (
<>
<Input
@@ -690,7 +697,7 @@ export class VaultDialog extends Component {
editable={!loading}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={value => {
onChangeText={(value) => {
this.password = value;
}}
marginBottom={
@@ -702,13 +709,15 @@ export class VaultDialog extends Component {
: 10
}
onSubmit={() => {
changePassword ? changePassInputRef.current?.focus() : this.onPress;
changePassword
? changePassInputRef.current?.focus()
: this.onPress;
}}
autoComplete="password"
returnKeyLabel={changePassword ? 'Next' : this.state.title}
returnKeyType={changePassword ? 'next' : 'done'}
returnKeyLabel={changePassword ? "Next" : this.state.title}
returnKeyType={changePassword ? "next" : "done"}
secureTextEntry
placeholder={changePassword ? 'Current password' : 'Password'}
placeholder={changePassword ? "Current password" : "Password"}
/>
{!this.state.biometricUnlock ||
@@ -716,10 +725,12 @@ export class VaultDialog extends Component {
!novault ||
changePassword ? null : (
<Button
onPress={() => this._onPressFingerprintAuth('Unlock note', '')}
onPress={() =>
this._onPressFingerprintAuth("Unlock note", "")
}
icon="fingerprint"
width="100%"
title={'Biometric unlock'}
title={"Biometric unlock"}
type="transparent"
/>
)}
@@ -734,13 +745,15 @@ export class VaultDialog extends Component {
})
}
icon={
this.state.deleteAll ? 'check-circle-outline' : 'checkbox-blank-circle-outline'
this.state.deleteAll
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
style={{
marginTop: 10
}}
width="100%"
title={'Delete all notes'}
title={"Delete all notes"}
type="errorShade"
/>
)}
@@ -753,7 +766,7 @@ export class VaultDialog extends Component {
editable={!loading}
testID={notesnook.ids.dialogs.vault.changePwd}
autoCapitalize="none"
onChangeText={value => {
onChangeText={(value) => {
this.newPassword = value;
}}
autoComplete="password"
@@ -761,7 +774,7 @@ export class VaultDialog extends Component {
returnKeyLabel="Change"
returnKeyType="done"
secureTextEntry
placeholder={'New password'}
placeholder={"New password"}
/>
</>
) : null}
@@ -772,7 +785,7 @@ export class VaultDialog extends Component {
fwdRef={passInputRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={value => {
onChangeText={(value) => {
this.password = value;
}}
autoComplete="password"
@@ -793,12 +806,12 @@ export class VaultDialog extends Component {
validationType="confirmPassword"
customValidator={() => this.password}
errorMessage="Passwords do not match."
onErrorCheck={e => null}
onErrorCheck={(e) => null}
marginBottom={0}
autoComplete="password"
returnKeyLabel="Create"
returnKeyType="done"
onChangeText={value => {
onChangeText={(value) => {
this.confirmPassword = value;
if (value !== this.password) {
this.setState({
@@ -816,8 +829,12 @@ export class VaultDialog extends Component {
</View>
) : null}
{this.state.biometricUnlock && !this.state.isBiometryEnrolled && novault ? (
<Paragraph>Unlock with password once to enable biometric access.</Paragraph>
{this.state.biometricUnlock &&
!this.state.isBiometryEnrolled &&
novault ? (
<Paragraph>
Unlock with password once to enable biometric access.
</Paragraph>
) : null}
{this.state.isBiometryAvailable &&
@@ -838,7 +855,7 @@ export class VaultDialog extends Component {
icon="fingerprint"
width="100%"
title="Biometric unlocking"
type={this.state.biometricUnlock ? 'transparent' : 'gray'}
type={this.state.biometricUnlock ? "transparent" : "gray"}
/>
) : null}
</View>
@@ -847,29 +864,31 @@ export class VaultDialog extends Component {
onPressNegative={this.close}
onPressPositive={this.onPress}
loading={loading}
positiveType={deleteVault || clearVault ? 'errorShade' : 'transparent'}
positiveType={
deleteVault || clearVault ? "errorShade" : "transparent"
}
positiveTitle={
deleteVault
? 'Delete'
? "Delete"
: clearVault
? 'Clear'
? "Clear"
: fingerprintAccess
? 'Enable'
? "Enable"
: this.state.revokeFingerprintAccess
? 'Revoke'
? "Revoke"
: changePassword
? 'Change'
? "Change"
: note.locked
? deleteNote
? 'Delete'
? "Delete"
: share
? 'Share '
? "Share "
: goToEditor
? 'Open'
: 'Unlock'
? "Open"
: "Unlock"
: !note.id
? 'Create'
: 'Lock'
? "Create"
: "Lock"
}
/>
</View>

View File

@@ -1,8 +1,8 @@
import React from 'react';
import RNBootSplash from 'react-native-bootsplash';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Dialog } from '../dialog';
import { Issue } from '../sheets/github/issue';
import React from "react";
import RNBootSplash from "react-native-bootsplash";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Dialog } from "../dialog";
import { Issue } from "../sheets/github/issue";
const error = (
stack: string,
@@ -12,7 +12,10 @@ const error = (
_______________________________
Stacktrace: In ${component}::${stack}`;
class ExceptionHandler extends React.Component<{ children: React.ReactNode; component: string }> {
class ExceptionHandler extends React.Component<{
children: React.ReactNode;
component: string;
}> {
state: {
error: {
title: string;
@@ -45,8 +48,11 @@ class ExceptionHandler extends React.Component<{ children: React.ReactNode; comp
}}
>
<Issue
defaultBody={error(this.state.error?.stack || '', this.props.component)}
defaultTitle={this.state.error?.title || 'Unknown Error'}
defaultBody={error(
this.state.error?.stack || "",
this.props.component
)}
defaultTitle={this.state.error?.title || "Unknown Error"}
issueTitle="An exception occured"
/>
<Dialog />

View File

@@ -1,25 +1,30 @@
import React, { useEffect, useState } from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { SearchBar } from '../../screens/search/search-bar';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import useNavigationStore from '../../stores/use-navigation-store';
import { useSelectionStore } from '../../stores/use-selection-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { eScrollEvent } from '../../utils/events';
import { LeftMenus } from './left-menus';
import { RightMenus } from './right-menus';
import { Title } from './title';
import React, { useEffect, useState } from "react";
import { Platform, StyleSheet, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { SearchBar } from "../../screens/search/search-bar";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
export const Header = React.memo(
() => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const insets = useSafeAreaInsets();
const [hide, setHide] = useState(true);
const selectionMode = useSelectionStore(state => state.selectionMode);
const currentScreen = useNavigationStore(state => state.currentScreen?.name);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const currentScreen = useNavigationStore(
(state) => state.currentScreen?.name
);
const onScroll = data => {
const onScroll = (data) => {
if (data.y > 150) {
if (!hide) return;
setHide(false);
@@ -42,16 +47,16 @@ export const Header = React.memo(
style={[
styles.container,
{
marginTop: Platform.OS === 'android' ? insets.top : null,
marginTop: Platform.OS === "android" ? insets.top : null,
backgroundColor: colors.bg,
overflow: 'hidden',
overflow: "hidden",
borderBottomWidth: 1,
borderBottomColor: hide ? 'transparent' : colors.nav,
justifyContent: 'space-between'
borderBottomColor: hide ? "transparent" : colors.nav,
justifyContent: "space-between"
}
]}
>
{currentScreen === 'Search' ? (
{currentScreen === "Search" ? (
<SearchBar />
) : (
<>
@@ -71,24 +76,24 @@ export const Header = React.memo(
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
flexDirection: "row",
zIndex: 11,
height: 50,
maxHeight: 50,
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 12,
width: '100%'
width: "100%"
},
leftBtnContainer: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
flexShrink: 1
},
leftBtn: {
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
@@ -96,12 +101,12 @@ const styles = StyleSheet.create({
marginRight: 25
},
rightBtnContainer: {
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
},
rightBtn: {
justifyContent: 'center',
alignItems: 'flex-end',
justifyContent: "center",
alignItems: "flex-end",
height: 40,
width: 40,
paddingRight: 0

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { notesnook } from '../../../e2e/test.ids';
import { DDS } from '../../services/device-detection';
import Navigation from '../../services/navigation';
import useNavigationStore from '../../stores/use-navigation-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { tabBarRef } from '../../utils/global-refs';
import { IconButton } from '../ui/icon-button';
import React from "react";
import { notesnook } from "../../../e2e/test.ids";
import { DDS } from "../../services/device-detection";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { tabBarRef } from "../../utils/global-refs";
import { IconButton } from "../ui/icon-button";
export const LeftMenus = () => {
const colors = useThemeStore(state => state.colors);
const deviceMode = useSettingStore(state => state.deviceMode);
const canGoBack = useNavigationStore(state => state.canGoBack);
const isTablet = deviceMode === 'tablet';
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
const canGoBack = useNavigationStore((state) => state.canGoBack);
const isTablet = deviceMode === "tablet";
const onLeftButtonPress = () => {
if (!canGoBack) {
@@ -25,8 +25,8 @@ export const LeftMenus = () => {
}
Navigation.goBack();
if (
useNavigationStore.getState().currentScreen.name === 'Signup' ||
useNavigationStore.getState().currentScreen.name === 'Login'
useNavigationStore.getState().currentScreen.name === "Signup" ||
useNavigationStore.getState().currentScreen.name === "Login"
) {
tabBarRef.current.unlock();
}
@@ -36,8 +36,8 @@ export const LeftMenus = () => {
<IconButton
testID={notesnook.ids.default.header.buttons.left}
customStyle={{
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
@@ -51,7 +51,7 @@ export const LeftMenus = () => {
onLongPress={() => {
Navigation.popToTop();
}}
name={canGoBack ? 'arrow-left' : 'menu'}
name={canGoBack ? "arrow-left" : "menu"}
color={colors.pri}
iconStyle={{
marginLeft: canGoBack ? -5 : 0

View File

@@ -1,33 +1,33 @@
import React, { useRef } from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import Menu from 'react-native-reanimated-material-menu';
import { notesnook } from '../../../e2e/test.ids';
import Navigation from '../../services/navigation';
import SearchService from '../../services/search';
import useNavigationStore from '../../stores/use-navigation-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
import Menu from "react-native-reanimated-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import Navigation from "../../services/navigation";
import SearchService from "../../services/search";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
export const RightMenus = () => {
const colors = useThemeStore(state => state.colors);
const deviceMode = useSettingStore(state => state.deviceMode);
const rightButtons = useNavigationStore(state => state.headerRightButtons);
const currentScreen = useNavigationStore(state => state.currentScreen.name);
const buttonAction = useNavigationStore(state => state.buttonAction);
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
const buttonAction = useNavigationStore((state) => state.buttonAction);
const menuRef = useRef();
return (
<View style={styles.rightBtnContainer}>
{!currentScreen.startsWith('Settings') ? (
{!currentScreen.startsWith("Settings") ? (
<IconButton
onPress={async () => {
SearchService.prepareSearch();
Navigation.navigate({
name: 'Search'
name: "Search"
});
}}
testID="icon-search"
@@ -37,11 +37,11 @@ export const RightMenus = () => {
/>
) : null}
{deviceMode !== 'mobile' ? (
{deviceMode !== "mobile" ? (
<Button
onPress={buttonAction}
testID={notesnook.ids.default.addBtn}
icon={currentScreen === 'Trash' ? 'delete' : 'plus'}
icon={currentScreen === "Trash" ? "delete" : "plus"}
iconSize={SIZE.xl}
type="shade"
hitSlop={{
@@ -88,7 +88,7 @@ export const RightMenus = () => {
<Button
style={{
width: 150,
justifyContent: 'flex-start',
justifyContent: "flex-start",
borderRadius: 0
}}
type="gray"
@@ -99,7 +99,7 @@ export const RightMenus = () => {
title={item.title}
onPress={async () => {
menuRef.current?.hide();
if (Platform.OS === 'ios') await sleep(300);
if (Platform.OS === "ios") await sleep(300);
item.onPress();
}}
/>
@@ -112,12 +112,12 @@ export const RightMenus = () => {
const styles = StyleSheet.create({
rightBtnContainer: {
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
},
rightBtn: {
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
marginLeft: 10,

View File

@@ -1,40 +1,43 @@
import React, { useEffect, useState } from 'react';
import { Platform, View } from 'react-native';
import Notebook from '../../screens/notebook';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import useNavigationStore from '../../stores/use-navigation-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import { eScrollEvent } from '../../utils/events';
import { SIZE } from '../../utils/size';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import Notebook from "../../screens/notebook";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
const titleState = {};
export const Title = () => {
const colors = useThemeStore(state => state.colors);
const currentScreen = useNavigationStore(state => state.currentScreen);
const isNotebook = currentScreen.name === 'Notebook';
const isTopic = currentScreen?.name === 'TopicNotes';
const colors = useThemeStore((state) => state.colors);
const currentScreen = useNavigationStore((state) => state.currentScreen);
const isNotebook = currentScreen.name === "Notebook";
const isTopic = currentScreen?.name === "TopicNotes";
const [hide, setHide] = useState(
isNotebook
? typeof titleState[currentScreen.id] === 'boolean'
? typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
: true
: false
);
const isHidden = titleState[currentScreen.id];
console.log(currentScreen, 'header');
console.log(currentScreen, "header");
const notebook =
isTopic && currentScreen.notebookId
? db.notebooks?.notebook(currentScreen.notebookId)?.data
: null;
const title = currentScreen.title;
const isTag = currentScreen?.name === 'TaggedNotes';
const isTag = currentScreen?.name === "TaggedNotes";
const onScroll = data => {
if (currentScreen.name !== 'Notebook') {
const onScroll = (data) => {
if (currentScreen.name !== "Notebook") {
setHide(false);
return;
}
@@ -48,9 +51,11 @@ export const Title = () => {
};
useEffect(() => {
if (currentScreen.name === 'Notebook') {
if (currentScreen.name === "Notebook") {
let value =
typeof titleState[currentScreen.id] === 'boolean' ? titleState[currentScreen.id] : true;
typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
: true;
setHide(value);
} else {
setHide(titleState[currentScreen.id]);
@@ -77,7 +82,7 @@ export const Title = () => {
style={{
opacity: 1,
flexShrink: 1,
flexDirection: 'row'
flexDirection: "row"
}}
>
{!hide && !isHidden ? (
@@ -86,19 +91,22 @@ export const Title = () => {
numberOfLines={isTopic ? 2 : 1}
size={isTopic ? SIZE.md + 2 : SIZE.xl}
style={{
flexWrap: 'wrap',
marginTop: Platform.OS === 'ios' ? -1 : 0
flexWrap: "wrap",
marginTop: Platform.OS === "ios" ? -1 : 0
}}
color={currentScreen.color || colors.heading}
>
{isTopic ? (
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
{notebook?.title}
{'\n'}
{"\n"}
</Paragraph>
) : null}
{isTag ? (
<Heading size={isTopic ? SIZE.md + 2 : SIZE.xl} color={colors.accent}>
<Heading
size={isTopic ? SIZE.md + 2 : SIZE.xl}
color={colors.accent}
>
#
</Heading>
) : null}

View File

@@ -1,23 +1,26 @@
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import ImageViewer from 'react-native-image-zoom-viewer';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import BaseDialog from '../dialog/base-dialog';
import { IconButton } from '../ui/icon-button';
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
const ImagePreview = () => {
const [visible, setVisible] = useState(false);
const [image, setImage] = useState('');
const [image, setImage] = useState("");
useEffect(() => {
eSubscribeEvent('ImagePreview', open);
eSubscribeEvent("ImagePreview", open);
return () => {
eUnSubscribeEvent('ImagePreview', open);
eUnSubscribeEvent("ImagePreview", open);
};
}, []);
const open = image => {
const open = (image) => {
setImage(image);
setVisible(true);
};
@@ -32,9 +35,9 @@ const ImagePreview = () => {
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
<View
style={{
width: '100%',
height: '100%',
backgroundColor: 'black'
width: "100%",
height: "100%",
backgroundColor: "black"
}}
>
<ImageViewer
@@ -47,16 +50,16 @@ const ImagePreview = () => {
renderHeader={() => (
<View
style={{
flexDirection: 'row',
width: '100%',
justifyContent: 'flex-end',
alignItems: 'center',
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: 'absolute',
position: "absolute",
zIndex: 999,
backgroundColor: 'rgba(0,0,0,0.3)',
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: 30
}}
>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +1,61 @@
import React, { useEffect, useRef } from 'react';
import { Platform, View } from 'react-native';
import RNBootSplash from 'react-native-bootsplash';
import { checkVersion } from 'react-native-check-version';
import { enabled } from 'react-native-privacy-snapshot';
import { editorState } from '../../screens/editor/tiptap/utils';
import BackupService from '../../services/backup';
import BiometricService from '../../services/biometrics';
import { DDS } from '../../services/device-detection';
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
import { setRateAppMessage } from '../../services/message';
import PremiumService from '../../services/premium';
import SettingsService from '../../services/settings';
import { initialize } from '../../stores';
import { useMessageStore } from '../../stores/use-message-store';
import { useNoteStore } from '../../stores/use-notes-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import { DatabaseLogger, db, loadDatabase } from '../../common/database';
import { MMKV } from '../../common/database/mmkv';
import { eOpenAnnouncementDialog } from '../../utils/events';
import { tabBarRef } from '../../utils/global-refs';
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';
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';
import { useAppState } from '../../hooks/use-app-state';
import React, { useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import RNBootSplash from "react-native-bootsplash";
import { checkVersion } from "react-native-check-version";
import { enabled } from "react-native-privacy-snapshot";
import { editorState } from "../../screens/editor/tiptap/utils";
import BackupService from "../../services/backup";
import BiometricService from "../../services/biometrics";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
presentSheet,
ToastEvent
} from "../../services/event-manager";
import { setRateAppMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import { initialize } from "../../stores";
import { useMessageStore } from "../../stores/use-message-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import { eOpenAnnouncementDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
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";
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";
import { useAppState } from "../../hooks/use-app-state";
const Launcher = React.memo(
() => {
const colors = useThemeStore(state => state.colors);
const setLoading = useNoteStore(state => state.setLoading);
const loading = useNoteStore(state => state.loading);
const user = useUserStore(state => state.user);
const verifyUser = useUserStore(state => state.verifyUser);
const setVerifyUser = useUserStore(state => state.setVerifyUser);
const deviceMode = useSettingStore(state => state.deviceMode);
const colors = useThemeStore((state) => state.colors);
const setLoading = useNoteStore((state) => state.setLoading);
const loading = useNoteStore((state) => state.loading);
const user = useUserStore((state) => state.user);
const verifyUser = useUserStore((state) => state.verifyUser);
const setVerifyUser = useUserStore((state) => state.setVerifyUser);
const deviceMode = useSettingStore((state) => state.deviceMode);
const appState = useAppState();
const passwordInputRef = useRef();
const password = useRef();
const introCompleted = useSettingStore(state => state.settings.introCompleted);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const dbInitCompleted = useRef(false);
const loadNotes = async () => {
if (verifyUser) {
@@ -76,7 +82,7 @@ const Launcher = React.memo(
if (!dbInitCompleted.current) {
await RNBootSplash.hide({ fade: true });
await loadDatabase();
DatabaseLogger.info('Initializing database');
DatabaseLogger.info("Initializing database");
await db.init();
dbInitCompleted.current = true;
}
@@ -115,16 +121,20 @@ const Launcher = React.memo(
const doAppLoadActions = async () => {
await sleep(500);
if (SettingsService.get().sessionExpired) {
eSendEvent('session_expired');
eSendEvent("session_expired");
return;
}
const user = await db.user.getUser();
await useMessageStore.getState().setAnnouncement();
if (PremiumService.get() && user) {
if (SettingsService.get().reminder === 'off') {
SettingsService.set({ reminder: 'daily' });
if (SettingsService.get().reminder === "off") {
SettingsService.set({ reminder: "daily" });
}
if (await BackupService.checkBackupRequired(SettingsService.get().reminder)) {
if (
await BackupService.checkBackupRequired(
SettingsService.get().reminder
)
) {
sleep(2000).then(() => BackupService.run());
}
}
@@ -135,7 +145,7 @@ const Launcher = React.memo(
if (await PremiumService.getRemainingTrialDaysStatus()) return;
if (introCompleted) {
useMessageStore.subscribe(state => {
useMessageStore.subscribe((state) => {
let dialogs = state.dialogs;
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
@@ -150,7 +160,7 @@ const Launcher = React.memo(
const version = await checkVersion();
if (!version.needsUpdate) return false;
presentSheet({
component: ref => <Update version={version} fwdRef={ref} />
component: (ref) => <Update version={version} fwdRef={ref} />
});
return true;
} catch (e) {
@@ -159,7 +169,7 @@ const Launcher = React.memo(
};
const restoreEditorState = async () => {
let appState = MMKV.getString('appState');
let appState = MMKV.getString("appState");
if (appState) {
appState = JSON.parse(appState);
if (
@@ -174,14 +184,18 @@ const Launcher = React.memo(
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
eSendEvent('loadingNote', appState.note);
eSendEvent("loadingNote", appState.note);
}
}
};
const checkForRateAppRequest = async () => {
let rateApp = SettingsService.get().rateApp;
if (rateApp && rateApp < Date.now() && !useMessageStore.getState().message?.visible) {
if (
rateApp &&
rateApp < Date.now() &&
!useMessageStore.getState().message?.visible
) {
setRateAppMessage();
return false;
}
@@ -208,12 +222,15 @@ const Launcher = React.memo(
const onUnlockBiometrics = async () => {
if (!(await BiometricService.isBiometryAvailable())) {
ToastEvent.show({
heading: 'Biometrics unavailable',
message: 'Try unlocking the app with your account password'
heading: "Biometrics unavailable",
message: "Try unlocking the app with your account password"
});
return;
}
let verified = await BiometricService.validateUser('Unlock to access your notes', '');
let verified = await BiometricService.validateUser(
"Unlock to access your notes",
""
);
if (verified) {
setVerifyUser(false);
enabled(false);
@@ -226,7 +243,7 @@ const Launcher = React.memo(
}, [verifyUser]);
useEffect(() => {
if (verifyUser && appState === 'active') {
if (verifyUser && appState === "active") {
onUnlockBiometrics();
}
}, [appState]);
@@ -247,30 +264,35 @@ const Launcher = React.memo(
<View
style={{
backgroundColor: colors.bg,
width: '100%',
height: '100%',
position: 'absolute',
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999
}}
>
<View
style={{
height: 250,
overflow: 'hidden'
overflow: "hidden"
}}
>
<SvgView src={SVG(colors.night ? 'white' : 'black')} height={700} />
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
</View>
<View
style={{
flex: 1,
justifyContent: 'center',
width: deviceMode !== 'mobile' ? '50%' : Platform.OS == 'ios' ? '95%' : '100%',
justifyContent: "center",
width:
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,
alignSelf: 'center'
alignSelf: "center"
}}
>
<IconButton
@@ -288,8 +310,8 @@ const Launcher = React.memo(
<Heading
color={colors.heading}
style={{
alignSelf: 'center',
textAlign: 'center'
alignSelf: "center",
textAlign: "center"
}}
>
Unlock to access your notes
@@ -297,10 +319,10 @@ const Launcher = React.memo(
<Paragraph
style={{
alignSelf: 'center',
textAlign: 'center',
alignSelf: "center",
textAlign: "center",
fontSize: SIZE.md,
maxWidth: '90%'
maxWidth: "90%"
}}
>
Please verify it's you
@@ -308,7 +330,7 @@ const Launcher = React.memo(
<Seperator />
<View
style={{
width: '100%',
width: "100%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1
@@ -320,7 +342,7 @@ const Launcher = React.memo(
fwdRef={passwordInputRef}
secureTextEntry
placeholder="Enter account password"
onChangeText={v => (password.current = v)}
onChangeText={(v) => (password.current = v)}
onSubmit={onSubmit}
/>
</>
@@ -356,8 +378,8 @@ const Launcher = React.memo(
borderRadius: 100
}}
onPress={onUnlockBiometrics}
icon={'fingerprint'}
type={user ? 'grayAccent' : 'accent'}
icon={"fingerprint"}
type={user ? "grayAccent" : "accent"}
fontSize={SIZE.md}
/>
</View>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import React from "react";
import { View } from "react-native";
export const Footer = () => {
return <View style={{ height: 150 }} />;

View File

@@ -1,20 +1,27 @@
import React from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { useMessageStore } from '../../../stores/use-message-store';
import { COLORS_NOTE } from '../../../utils/color-scheme';
import { Announcement } from '../../announcements/announcement';
import { Card } from '../../list/card';
import Paragraph from '../../ui/typography/paragraph';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { SIZE } from '../../../utils/size';
import { useSelectionStore } from '../../../stores/use-selection-store';
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated';
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useMessageStore } from "../../../stores/use-message-store";
import { COLORS_NOTE } from "../../../utils/color-scheme";
import { Announcement } from "../../announcements/announcement";
import { Card } from "../../list/card";
import Paragraph from "../../ui/typography/paragraph";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SIZE } from "../../../utils/size";
import { useSelectionStore } from "../../../stores/use-selection-store";
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
export const Header = React.memo(
({ type, messageCard = true, color, shouldShow = false, noAnnouncement, warning }) => {
const colors = useThemeStore(state => state.colors);
const announcements = useMessageStore(state => state.announcements);
const selectionMode = useSelectionStore(state => state.selectionMode);
({
type,
messageCard = true,
color,
shouldShow = false,
noAnnouncement,
warning
}) => {
const colors = useThemeStore((state) => state.colors);
const announcements = useMessageStore((state) => state.announcements);
const selectionMode = useSelectionStore((state) => state.selectionMode);
return selectionMode ? null : (
<>
@@ -23,11 +30,11 @@ export const Header = React.memo(
style={{
padding: 12,
backgroundColor: colors.errorBg,
width: '95%',
alignSelf: 'center',
width: "95%",
alignSelf: "center",
borderRadius: 5,
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
}}
>
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
@@ -37,18 +44,20 @@ export const Header = React.memo(
</View>
) : announcements.length !== 0 && !noAnnouncement ? (
<Announcement color={color || colors.accent} />
) : type === 'search' ? null : !shouldShow ? (
) : type === "search" ? null : !shouldShow ? (
<View
style={{
marginBottom: 5,
padding: 0,
width: '100%',
justifyContent: 'center',
alignItems: 'center'
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
{messageCard ? (
<Card color={COLORS_NOTE[color?.toLowerCase()] || colors.accent} />
<Card
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
/>
) : null}
</View>
) : null}
@@ -57,4 +66,4 @@ export const Header = React.memo(
}
);
Header.displayName = 'Header';
Header.displayName = "Header";

View File

@@ -1,19 +1,21 @@
import React, { useRef, useState } from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { useMenuStore } from '../../../stores/use-menu-store';
import { ToastEvent } from '../../../services/event-manager';
import { getTotalNotes } from '../../../utils';
import { db } from '../../../common/database';
import { SIZE } from '../../../utils/size';
import { IconButton } from '../../ui/icon-button';
import Heading from '../../ui/typography/heading';
import Paragraph from '../../ui/typography/paragraph';
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useMenuStore } from "../../../stores/use-menu-store";
import { ToastEvent } from "../../../services/event-manager";
import { getTotalNotes } from "../../../utils";
import { db } from "../../../common/database";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
const colors = useThemeStore(state => state.colors);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(db.settings.isPinned(notebook.id));
const setMenuPins = useMenuStore(state => state.setMenuPins);
const colors = useThemeStore((state) => state.colors);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.settings.isPinned(notebook.id)
);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const totalNotes = getTotalNotes(notebook);
const shortcutRef = useRef();
@@ -24,8 +26,8 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
} else {
await db.settings.pin(notebook.type, { id: notebook.id });
ToastEvent.show({
heading: 'Shortcut created',
type: 'success'
heading: "Shortcut created",
type: "success"
});
}
setIsPinnedToMenu(db.settings.isPinned(notebook.id));
@@ -38,10 +40,10 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
style={{
marginBottom: 5,
padding: 0,
width: '100%',
width: "100%",
paddingVertical: 15,
paddingHorizontal: 12,
alignSelf: 'center',
alignSelf: "center",
borderRadius: 10,
paddingTop: 25
}}
@@ -51,29 +53,29 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
</Paragraph>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading size={SIZE.xxl}>{notebook.title}</Heading>
<View
style={{
flexDirection: 'row'
flexDirection: "row"
}}
>
<IconButton
name={isPinnedToMenu ? 'link-variant-off' : 'link-variant'}
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
onPress={onPinNotebook}
tooltipText={'Create shortcut in side menu'}
tooltipText={"Create shortcut in side menu"}
fwdRef={shortcutRef}
customStyle={{
marginRight: 15,
width: 40,
height: 40
}}
type={isPinnedToMenu ? 'grayBg' : 'grayBg'}
type={isPinnedToMenu ? "grayBg" : "grayBg"}
color={isPinnedToMenu ? colors.accent : colors.icon}
size={SIZE.lg}
/>
@@ -101,18 +103,21 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
<Paragraph
style={{
marginTop: 10,
fontStyle: 'italic',
fontStyle: "italic",
fontFamily: null
}}
size={SIZE.xs}
color={colors.icon}
>
{notebook.topics.length === 1 ? '1 topic' : `${notebook.topics.length} topics`},{' '}
{notebook.topics.length === 1
? "1 topic"
: `${notebook.topics.length} topics`}
,{" "}
{notebook && totalNotes > 1
? totalNotes + ' notes'
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + ' note'
: '0 notes'}
? totalNotes + " note"
: "0 notes"}
</Paragraph>
</View>
);

View File

@@ -1,40 +1,48 @@
import React, { useEffect, useRef, useState } from 'react';
import { TouchableOpacity, useWindowDimensions, View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { useSettingStore } from '../../../stores/use-setting-store';
import React, { useEffect, useRef, useState } from "react";
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet
} from '../../../services/event-manager';
import SettingsService from '../../../services/settings';
import { GROUP } from '../../../utils/constants';
import { COLORS_NOTE } from '../../../utils/color-scheme';
import { db } from '../../../common/database';
import { eOpenJumpToDialog } from '../../../utils/events';
import { SIZE } from '../../../utils/size';
import { IconButton } from '../../ui/icon-button';
import { Button } from '../../ui/button';
import Sort from '../../sheets/sort';
import Heading from '../../ui/typography/heading';
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { GROUP } from "../../../utils/constants";
import { COLORS_NOTE } from "../../../utils/color-scheme";
import { db } from "../../../common/database";
import { eOpenJumpToDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import Sort from "../../sheets/sort";
import Heading from "../../ui/typography/heading";
export const SectionHeader = React.memo(
({ item, index, type, color, screen }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const { fontScale } = useWindowDimensions();
const [groupOptions, setGroupOptions] = useState(db.settings?.getGroupOptions(type));
let groupBy = Object.keys(GROUP).find(key => GROUP[key] === groupOptions.groupBy);
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions(type)
);
let groupBy = Object.keys(GROUP).find(
(key) => GROUP[key] === groupOptions.groupBy
);
const jumpToRef = useRef();
const sortRef = useRef();
const compactModeRef = useRef();
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
const notesListMode = useSettingStore(state => state.settings.notesListMode);
const listMode = type === 'notebooks' ? notebooksListMode : notesListMode;
const notebooksListMode = useSettingStore(
(state) => state.settings.notebooksListMode
);
const notesListMode = useSettingStore(
(state) => state.settings.notesListMode
);
const listMode = type === "notebooks" ? notebooksListMode : notesListMode;
groupBy = !groupBy
? 'Default'
? "Default"
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
const onUpdate = () => {
@@ -42,23 +50,23 @@ export const SectionHeader = React.memo(
};
useEffect(() => {
eSubscribeEvent('groupOptionsUpdate', onUpdate);
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent('groupOptionsUpdate', onUpdate);
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, []);
return (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
width: '95%',
justifyContent: 'space-between',
flexDirection: "row",
alignItems: "center",
width: "95%",
justifyContent: "space-between",
paddingHorizontal: 12,
height: 35 * fontScale,
backgroundColor: colors.nav,
alignSelf: 'center',
alignSelf: "center",
borderRadius: 5,
marginVertical: 5
}}
@@ -71,8 +79,8 @@ export const SectionHeader = React.memo(
activeOpacity={0.9}
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
style={{
height: '100%',
justifyContent: 'center'
height: "100%",
justifyContent: "center"
}}
>
<Heading
@@ -80,18 +88,18 @@ export const SectionHeader = React.memo(
size={SIZE.sm}
style={{
minWidth: 60,
alignSelf: 'center',
textAlignVertical: 'center'
alignSelf: "center",
textAlignVertical: "center"
}}
>
{!item.title || item.title === '' ? 'Pinned' : item.title}
{!item.title || item.title === "" ? "Pinned" : item.title}
</Heading>
</TouchableOpacity>
<View
style={{
flexDirection: 'row',
alignItems: 'center'
flexDirection: "row",
alignItems: "center"
}}
>
{index === 0 ? (
@@ -105,19 +113,26 @@ export const SectionHeader = React.memo(
tooltipText="Change sorting of items in list"
fwdRef={sortRef}
title={groupBy}
icon={groupOptions.sortDirection === 'asc' ? 'sort-ascending' : 'sort-descending'}
icon={
groupOptions.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
height={25}
style={{
borderRadius: 100,
paddingHorizontal: 0,
backgroundColor: 'transparent',
marginRight: type === 'notes' || type === 'home' || type === 'notebooks' ? 10 : 0
backgroundColor: "transparent",
marginRight:
type === "notes" || type === "home" || type === "notebooks"
? 10
: 0
}}
type="gray"
iconPosition="right"
/>
{type === 'notes' || type === 'notebooks' || type === 'home' ? (
{type === "notes" || type === "notebooks" || type === "home" ? (
<IconButton
customStyle={{
width: 25,
@@ -125,15 +140,22 @@ export const SectionHeader = React.memo(
}}
testID="icon-compact-mode"
tooltipText={
listMode == 'compact' ? 'Switch to normal mode' : 'Switch to compact mode'
listMode == "compact"
? "Switch to normal mode"
: "Switch to compact mode"
}
fwdRef={compactModeRef}
color={colors.icon}
name={listMode == 'compact' ? 'view-list' : 'view-list-outline'}
name={
listMode == "compact" ? "view-list" : "view-list-outline"
}
onPress={() => {
let settings = {};
settings[type !== 'notebooks' ? 'notesListMode' : 'notebooksListMode'] =
listMode === 'normal' ? 'compact' : 'normal';
settings[
type !== "notebooks"
? "notesListMode"
: "notebooksListMode"
] = listMode === "normal" ? "compact" : "normal";
SettingsService.set(settings);
}}

View File

@@ -1,24 +1,24 @@
import { decode, EntityLevel } from 'entities';
import React from 'react';
import { View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { notesnook } from '../../../../e2e/test.ids';
import { TaggedNotes } from '../../../screens/notes/tagged';
import { TopicNotes } from '../../../screens/notes/topic-notes';
import useNavigationStore from '../../../stores/use-navigation-store';
import { useSettingStore } from '../../../stores/use-setting-store';
import { useThemeStore } from '../../../stores/use-theme-store';
import { COLORS_NOTE } from '../../../utils/color-scheme';
import { db } from '../../../common/database';
import { SIZE } from '../../../utils/size';
import { Properties } from '../../properties';
import { Button } from '../../ui/button';
import { IconButton } from '../../ui/icon-button';
import { TimeSince } from '../../ui/time-since';
import Heading from '../../ui/typography/heading';
import Paragraph from '../../ui/typography/paragraph';
import { decode, EntityLevel } from "entities";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { COLORS_NOTE } from "../../../utils/color-scheme";
import { db } from "../../../common/database";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const navigateToTopic = topic => {
const navigateToTopic = (topic) => {
TopicNotes.navigate(topic, true);
};
@@ -28,16 +28,19 @@ function navigateToTag(item) {
TaggedNotes.navigate(tag, true);
}
const showActionSheet = item => {
const showActionSheet = (item) => {
Properties.present(item);
};
function getNotebook(item) {
const isTrash = item.type === 'trash';
const isTrash = item.type === "trash";
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
const currentScreen = useNavigationStore.getState().currentScreen;
const filteredNotebooks = item.notebooks?.filter(n => n.id !== currentScreen.notebookId);
let item_notebook = filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
const filteredNotebooks = item.notebooks?.filter(
(n) => n.id !== currentScreen.notebookId
);
let item_notebook =
filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
let notebook = item_notebook && db.notebooks.notebook(item_notebook.id);
if (!notebook) return [];
let topic = notebook.topics.topic(item_notebook.topics[0])?._topic;
@@ -52,11 +55,19 @@ function getNotebook(item) {
];
}
const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false }) => {
const colors = useThemeStore(state => state.colors);
const notesListMode = useSettingStore(state => state.settings.notesListMode);
const compactMode = notesListMode === 'compact';
const attachmentCount = db.attachments?.ofNote(item.id, 'all')?.length || 0;
const NoteItem = ({
item,
isTrash,
tags,
dateBy = "dateCreated",
noOpen = false
}) => {
const colors = useThemeStore((state) => state.colors);
const notesListMode = useSettingStore(
(state) => state.settings.notesListMode
);
const compactMode = notesListMode === "compact";
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
const notebooks = React.useMemo(() => getNotebook(item), [item]);
return (
@@ -70,14 +81,14 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
{!compactMode ? (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
zIndex: 10,
elevation: 10,
marginBottom: 2.5
}}
>
{notebooks.map(_item => (
{notebooks.map((_item) => (
<Button
title={_item.title}
key={_item}
@@ -106,7 +117,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
numberOfLines={1}
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
size={SIZE.md}
>
@@ -116,7 +127,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
{item.headline && !compactMode ? (
<Paragraph
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
numberOfLines={2}
>
@@ -128,10 +139,10 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
<View
style={{
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
width: '100%',
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
width: "100%",
marginTop: 5,
height: SIZE.md + 2
}}
@@ -155,14 +166,16 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
marginRight: 6
}}
time={item[dateBy]}
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
updateFrequency={
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
}
/>
{attachmentCount > 0 ? (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
marginRight: 6
}}
>
@@ -181,7 +194,9 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
style={{
marginRight: 6
}}
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.accent}
color={
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
}
/>
) : null}
@@ -210,15 +225,15 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
) : null}
{!isTrash && !compactMode && tags
? tags.map(item =>
? tags.map((item) =>
item.id ? (
<Button
title={'#' + item.alias}
title={"#" + item.alias}
key={item.id}
height={23}
type="gray"
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
fontSize={SIZE.xs}
@@ -244,7 +259,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
marginRight: 6
}}
>
Deleted on{' '}
Deleted on{" "}
{item && item.dateDeleted
? new Date(item.dateDeleted).toISOString().slice(0, 10)
: null}
@@ -270,11 +285,11 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}
customStyle={{
justifyContent: 'center',
justifyContent: "center",
height: 35,
width: 35,
borderRadius: 100,
alignItems: 'center'
alignItems: "center"
}}
/>
</>

View File

@@ -1,24 +1,28 @@
import React from 'react';
import NoteItem from '.';
import { notesnook } from '../../../../e2e/test.ids';
import { useSelectionStore } from '../../../stores/use-selection-store';
import { useTrashStore } from '../../../stores/use-trash-store';
import { useEditorStore } from '../../../stores/use-editor-store';
import { DDS } from '../../../services/device-detection';
import { eSendEvent, openVault, ToastEvent } from '../../../services/event-manager';
import Navigation from '../../../services/navigation';
import { history } from '../../../utils';
import { db } from '../../../common/database';
import { eOnLoadNote, eShowMergeDialog } from '../../../utils/events';
import { tabBarRef } from '../../../utils/global-refs';
import { presentDialog } from '../../dialog/functions';
import SelectionWrapper from '../selection-wrapper';
import React from "react";
import NoteItem from ".";
import { notesnook } from "../../../../e2e/test.ids";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { useEditorStore } from "../../../stores/use-editor-store";
import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
openVault,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { history } from "../../../utils";
import { db } from "../../../common/database";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
const present = () =>
presentDialog({
title: 'Note not synced',
negativeText: 'Ok',
paragraph: 'Please sync again to open this note for editing'
title: "Note not synced",
negativeText: "Ok",
paragraph: "Please sync again to open this note for editing"
});
export const openNote = async (item, isTrash, setSelectedItem) => {
@@ -55,8 +59,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
novault: true,
locked: true,
goToEditor: true,
title: 'Open note',
description: 'Unlock note to open it in editor.'
title: "Open note",
description: "Unlock note to open it in editor."
});
return;
}
@@ -64,24 +68,24 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
presentDialog({
title: `Restore ${item.itemType}`,
paragraph: `Restore or delete ${item.itemType} forever`,
positiveText: 'Restore',
negativeText: 'Delete',
positiveText: "Restore",
negativeText: "Delete",
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate(
'Tags',
'Notes',
'Notebooks',
'Favorites',
'Trash',
'TaggedNotes',
'ColoredNotes',
'TopicNotes'
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: 'Restore successful',
type: 'success'
heading: "Restore successful",
type: "success"
});
},
onClose: async () => {
@@ -89,9 +93,9 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: 'Permanantly deleted items',
type: 'success',
context: 'local'
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
}
});
@@ -106,8 +110,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
export const NoteWrapper = React.memo(
({ item, index, tags, dateBy }) => {
const isTrash = item.type === 'trash';
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
const isTrash = item.type === "trash";
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
return (
<SelectionWrapper

View File

@@ -1,31 +1,39 @@
import React from 'react';
import { View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { notesnook } from '../../../../e2e/test.ids';
import { TopicNotes } from '../../../screens/notes/topic-notes';
import { useSettingStore } from '../../../stores/use-setting-store';
import { useThemeStore } from '../../../stores/use-theme-store';
import { getTotalNotes, history } from '../../../utils';
import { SIZE } from '../../../utils/size';
import { Properties } from '../../properties';
import { Button } from '../../ui/button';
import { IconButton } from '../../ui/icon-button';
import Heading from '../../ui/typography/heading';
import Paragraph from '../../ui/typography/paragraph';
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { getTotalNotes, history } from "../../../utils";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const showActionSheet = item => {
const showActionSheet = (item) => {
Properties.present(item);
};
const navigateToTopic = topic => {
const navigateToTopic = (topic) => {
if (history.selectedItemsList.length > 0) return;
TopicNotes.navigate(topic, true);
};
export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateBy }) => {
const colors = useThemeStore(state => state.colors);
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
const compactMode = notebooksListMode === 'compact';
export const NotebookItem = ({
item,
isTopic = false,
notebookID,
isTrash,
dateBy
}) => {
const colors = useThemeStore((state) => state.colors);
const notebooksListMode = useSettingStore(
(state) => state.settings.notebooksListMode
);
const compactMode = notebooksListMode === "compact";
const topics = item.topics?.slice(0, 3) || [];
const totalNotes = getTotalNotes(item);
@@ -41,7 +49,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
size={SIZE.md}
numberOfLines={1}
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
>
{item.title}
@@ -51,7 +59,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
size={SIZE.sm}
numberOfLines={2}
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
>
{item.description}
@@ -61,18 +69,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
{isTopic || compactMode ? null : (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap'
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap"
}}
>
{topics.map(topic => (
{topics.map((topic) => (
<Button
title={topic.title}
key={topic.id}
height={null}
textStyle={{
fontWeight: 'normal',
fontWeight: "normal",
fontFamily: null,
marginRight: 0
}}
@@ -98,9 +106,9 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
<View
style={{
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
marginTop: 5,
height: SIZE.md + 2
}}
@@ -112,7 +120,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
marginRight: 6
}}
>
{isTopic ? 'Topic' : 'Notebook'}
{isTopic ? "Topic" : "Notebook"}
</Paragraph>
{isTrash ? (
@@ -121,17 +129,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
color={colors.icon}
size={SIZE.xs}
style={{
textAlignVertical: 'center',
textAlignVertical: "center",
marginRight: 6
}}
>
{'Deleted on ' + new Date(item.dateDeleted).toISOString().slice(0, 10)}
{"Deleted on " +
new Date(item.dateDeleted).toISOString().slice(0, 10)}
</Paragraph>
<Paragraph
color={colors.accent}
size={SIZE.xs}
style={{
textAlignVertical: 'center',
textAlignVertical: "center",
marginRight: 6
}}
>
@@ -157,10 +166,10 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
}}
>
{item && totalNotes > 1
? totalNotes + ' notes'
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + ' note'
: '0 notes'}
? totalNotes + " note"
: "0 notes"}
</Paragraph>
{item.pinned ? (
@@ -183,11 +192,11 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
size={SIZE.xl}
onPress={() => showActionSheet(item)}
customStyle={{
justifyContent: 'center',
justifyContent: "center",
height: 35,
width: 35,
borderRadius: 100,
alignItems: 'center'
alignItems: "center"
}}
/>
</>

View File

@@ -1,18 +1,18 @@
import React from 'react';
import { NotebookItem } from '.';
import Notebook from '../../../screens/notebook';
import { TopicNotes } from '../../../screens/notes/topic-notes';
import { ToastEvent } from '../../../services/event-manager';
import Navigation from '../../../services/navigation';
import { useSelectionStore } from '../../../stores/use-selection-store';
import { useTrashStore } from '../../../stores/use-trash-store';
import { history } from '../../../utils';
import { db } from '../../../common/database';
import { presentDialog } from '../../dialog/functions';
import SelectionWrapper from '../selection-wrapper';
import React from "react";
import { NotebookItem } from ".";
import Notebook from "../../../screens/notebook";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import { ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { history } from "../../../utils";
import { db } from "../../../common/database";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
export const openNotebookTopic = item => {
const isTrash = item.type === 'trash';
export const openNotebookTopic = (item) => {
const isTrash = item.type === "trash";
if (history.selectedItemsList.length > 0 && history.selectionMode) {
useSelectionStore.getState().setSelectedItem(item);
return;
@@ -24,24 +24,24 @@ export const openNotebookTopic = item => {
presentDialog({
title: `Restore ${item.itemType}`,
paragraph: `Restore or delete ${item.itemType} forever`,
positiveText: 'Restore',
negativeText: 'Delete',
positiveText: "Restore",
negativeText: "Delete",
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate(
'Tags',
'Notes',
'Notebooks',
'Favorites',
'Trash',
'TaggedNotes',
'ColoredNotes',
'TopicNotes'
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: 'Restore successful',
type: 'success'
heading: "Restore successful",
type: "success"
});
},
onClose: async () => {
@@ -49,15 +49,15 @@ export const openNotebookTopic = item => {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: 'Permanantly deleted items',
type: 'success',
context: 'local'
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
}
});
return;
}
if (item.type === 'topic') {
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
Notebook.navigate(item, true);
@@ -66,18 +66,18 @@ export const openNotebookTopic = item => {
export const NotebookWrapper = React.memo(
({ item, index, dateBy }) => {
const isTrash = item.type === 'trash';
const isTrash = item.type === "trash";
return (
<SelectionWrapper
pinned={item.pinned}
index={index}
onPress={() => openNotebookTopic(item)}
height={item.type === 'topic' ? 80 : 110}
height={item.type === "topic" ? 80 : 110}
item={item}
>
<NotebookItem
isTopic={item.type === 'topic'}
isTopic={item.type === "topic"}
item={item}
dateBy={dateBy}
index={index}

View File

@@ -1,59 +1,59 @@
import Clipboard from '@react-native-clipboard/clipboard';
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { SlideInUp, SlideOutDown } from 'react-native-reanimated';
import { openVault, ToastEvent } from '../../../services/event-manager';
import Navigation from '../../../services/navigation';
import { useSelectionStore } from '../../../stores/use-selection-store';
import { useTrashStore } from '../../../stores/use-trash-store';
import { useMenuStore } from '../../../stores/use-menu-store';
import { useNotebookStore } from '../../../stores/use-notebook-store';
import { useThemeStore } from '../../../stores/use-theme-store';
import { dWidth, getElevation, toTXT } from '../../../utils';
import { db } from '../../../common/database';
import { deleteItems } from '../../../utils/functions';
import { presentDialog } from '../../dialog/functions';
import { Button } from '../../ui/button';
import { IconButton } from '../../ui/icon-button';
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Animated, { SlideInUp, SlideOutDown } from "react-native-reanimated";
import { openVault, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { dWidth, getElevation, toTXT } from "../../../utils";
import { db } from "../../../common/database";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
export const ActionStrip = ({ note, setActionStrip }) => {
const colors = useThemeStore(state => state.colors);
const selectionMode = useSelectionStore(state => state.selectionMode);
const setNotebooks = useNotebookStore(state => state.setNotebooks);
const setMenuPins = useMenuStore(state => state.setMenuPins);
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
const setSelectionMode = useSelectionStore(state => state.setSelectionMode);
const colors = useThemeStore((state) => state.colors);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(false);
const [width, setWidth] = useState(dWidth - 16);
useEffect(() => {
if (note.type === 'note') return;
if (note.type === "note") return;
setIsPinnedToMenu(db.settings.isPinned(note.id));
}, []);
const updateNotes = () => {
Navigation.queueRoutesForUpdate(
'Notes',
'Favorites',
'ColoredNotes',
'TaggedNotes',
'TopicNotes'
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
};
const actions = [
{
title: 'Pin ' + note.type,
icon: note.pinned ? 'pin-off' : 'pin',
visible: note.type === 'note' || note.type === 'notebook',
title: "Pin " + note.type,
icon: note.pinned ? "pin-off" : "pin",
visible: note.type === "note" || note.type === "notebook",
onPress: async () => {
if (!note.id) return;
if (note.type === 'note') {
if (note.type === "note") {
if (db.notes.pinned.length === 3 && !note.pinned) {
ToastEvent.show({
heading: 'Cannot pin more than 3 notes',
type: 'error'
heading: "Cannot pin more than 3 notes",
type: "error"
});
return;
}
@@ -61,8 +61,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
} else {
if (db.notebooks.pinned.length === 3 && !note.pinned) {
ToastEvent.show({
heading: 'Cannot pin more than 3 notebooks',
type: 'error'
heading: "Cannot pin more than 3 notebooks",
type: "error"
});
return;
}
@@ -74,11 +74,11 @@ export const ActionStrip = ({ note, setActionStrip }) => {
}
},
{
title: 'Add to favorites',
icon: note.favorite ? 'star-off' : 'star',
title: "Add to favorites",
icon: note.favorite ? "star-off" : "star",
onPress: async () => {
if (!note.id) return;
if (note.type === 'note') {
if (note.type === "note") {
await db.notes.note(note.id).favorite();
} else {
await db.notebooks.notebook(note.id).favorite();
@@ -86,23 +86,25 @@ export const ActionStrip = ({ note, setActionStrip }) => {
updateNotes();
setActionStrip(false);
},
visible: note.type === 'note',
color: !note.favorite ? 'orange' : null
visible: note.type === "note",
color: !note.favorite ? "orange" : null
},
{
title: isPinnedToMenu ? 'Remove Shortcut from Menu' : 'Add Shortcut to Menu',
icon: isPinnedToMenu ? 'link-variant-remove' : 'link-variant',
title: isPinnedToMenu
? "Remove Shortcut from Menu"
: "Add Shortcut to Menu",
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
onPress: async () => {
try {
if (isPinnedToMenu) {
await db.settings.unpin(note.id);
ToastEvent.show({
heading: 'Shortcut removed from menu',
type: 'success'
heading: "Shortcut removed from menu",
type: "success"
});
} else {
if (note.type === 'topic') {
if (note.type === "topic") {
await db.settings.pin(note.type, {
id: note.id,
notebookId: note.notebookId
@@ -111,8 +113,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
await db.settings.pin(note.type, { id: note.id });
}
ToastEvent.show({
heading: 'Shortcut added to menu',
type: 'success'
heading: "Shortcut added to menu",
type: "success"
});
}
setIsPinnedToMenu(db.settings.isPinned(note.id));
@@ -121,12 +123,12 @@ export const ActionStrip = ({ note, setActionStrip }) => {
setActionStrip(false);
} catch (e) {}
},
visible: note.type !== 'note'
visible: note.type !== "note"
},
{
title: 'Copy Note',
icon: 'content-copy',
visible: note.type === 'note',
title: "Copy Note",
icon: "content-copy",
visible: note.type === "note",
onPress: async () => {
if (note.locked) {
openVault({
@@ -134,75 +136,77 @@ export const ActionStrip = ({ note, setActionStrip }) => {
novault: true,
locked: true,
item: note,
title: 'Copy note',
description: 'Unlock note to copy to clipboard.'
title: "Copy note",
description: "Unlock note to copy to clipboard."
});
} else {
let text = await toTXT(note);
text = `${note.title}\n \n ${text}`;
Clipboard.setString(text);
ToastEvent.show({
heading: 'Note copied to clipboard',
type: 'success'
heading: "Note copied to clipboard",
type: "success"
});
}
setActionStrip(false);
}
},
{
title: 'Restore ' + note.itemType,
icon: 'delete-restore',
title: "Restore " + note.itemType,
icon: "delete-restore",
onPress: async () => {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate(
'Notes',
'Favorites',
'ColoredNotes',
'TaggedNotes',
'TopicNotes',
'Trash',
'Notebooks'
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks"
);
ToastEvent.show({
heading:
note.type === 'note' ? 'Note restored from trash' : 'Notebook restored from trash',
type: 'success'
note.type === "note"
? "Note restored from trash"
: "Notebook restored from trash",
type: "success"
});
setActionStrip(false);
},
visible: note.type === 'trash'
visible: note.type === "trash"
},
{
title: 'Delete' + note.itemType,
icon: 'delete',
visible: note.type === 'trash',
title: "Delete" + note.itemType,
icon: "delete",
visible: note.type === "trash",
onPress: () => {
presentDialog({
title: `Permanent delete`,
paragraph: `Are you sure you want to delete this ${note.itemType} permanantly from trash?`,
positiveText: 'Delete',
negativeText: 'Cancel',
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await db.trash.delete(note.id);
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: 'Permanantly deleted items',
type: 'success',
context: 'local'
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
},
positiveType: 'errorShade'
positiveType: "errorShade"
});
setActionStrip(false);
}
},
{
title: 'Delete' + note.type,
icon: 'delete',
visible: note.type !== 'trash',
title: "Delete" + note.type,
icon: "delete",
visible: note.type !== "trash",
onPress: async () => {
try {
await deleteItems(note);
@@ -211,8 +215,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
}
},
{
title: 'Close',
icon: 'close',
title: "Close",
icon: "close",
onPress: () => setActionStrip(false),
color: colors.light,
bg: colors.red,
@@ -222,19 +226,19 @@ export const ActionStrip = ({ note, setActionStrip }) => {
return (
<Animated.View
onLayout={event => {
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
entering={SlideInUp.springify().mass(0.4)}
exiting={SlideOutDown}
style={{
position: 'absolute',
position: "absolute",
zIndex: 999,
width: '102%',
height: '100%',
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center'
width: "102%",
height: "100%",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center"
}}
>
<Button
@@ -242,7 +246,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
title="Select"
icon="check"
tooltipText="Select Item"
onPress={event => {
onPress={(event) => {
if (!selectionMode) {
setSelectionMode(true);
}
@@ -256,7 +260,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
}}
height={30}
/>
{actions.map(item =>
{actions.map((item) =>
item.visible ? (
<View
key={item.icon}
@@ -265,8 +269,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
height: width / 1.4 / actions.length,
backgroundColor: item.bg || colors.nav,
borderRadius: 100,
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
...getElevation(5),
marginLeft: 15
}}

View File

@@ -1,26 +1,33 @@
import React from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../../stores/use-theme-store';
import { useEditorStore } from '../../../stores/use-editor-store';
import { hexToRGBA } from '../../../utils/color-scheme/utils';
import React from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useEditorStore } from "../../../stores/use-editor-store";
import { hexToRGBA } from "../../../utils/color-scheme/utils";
export const Filler = ({ item, background }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const currentEditingNote = useEditorStore(state => state.currentEditingNote);
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const color = 'gray';
const color = "gray";
return currentEditingNote === item.id ? (
<View
style={{
position: 'absolute',
width: '110%',
height: '150%',
backgroundColor: currentEditingNote === item.id ? hexToRGBA(colors[color], 0.12) : null,
position: "absolute",
width: "110%",
height: "150%",
backgroundColor:
currentEditingNote === item.id
? hexToRGBA(colors[color], 0.12)
: null,
borderLeftWidth: 5,
borderLeftColor:
currentEditingNote === item.id ? colors[item.color || 'accent'] : 'transparent'
currentEditingNote === item.id
? colors[item.color || "accent"]
: "transparent"
}}
></View>
) : null;

View File

@@ -1,20 +1,36 @@
import React, { useEffect, useState } from 'react';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
import { useSettingStore } from '../../../stores/use-setting-store';
import { useThemeStore } from '../../../stores/use-theme-store';
import { history } from '../../../utils';
import { PressableButton } from '../../ui/pressable';
import { ActionStrip } from './action-strip';
import { Filler } from './back-fill';
import { SelectionIcon } from './selection';
import React, { useEffect, useState } from "react";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { history } from "../../../utils";
import { PressableButton } from "../../ui/pressable";
import { ActionStrip } from "./action-strip";
import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
const SelectionWrapper = ({ children, item, background, onLongPress, onPress, testID }) => {
const colors = useThemeStore(state => state.colors);
const SelectionWrapper = ({
children,
item,
background,
onLongPress,
onPress,
testID
}) => {
const colors = useThemeStore((state) => state.colors);
const [actionStrip, setActionStrip] = 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';
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";
const _onLongPress = () => {
if (history.selectedItemsList.length > 0) return;
@@ -34,10 +50,10 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
};
useEffect(() => {
eSubscribeEvent('navigate', closeStrip);
eSubscribeEvent("navigate", closeStrip);
return () => {
eUnSubscribeEvent('navigate', closeStrip);
eUnSubscribeEvent("navigate", closeStrip);
};
}, []);
@@ -51,17 +67,19 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
customAlpha={!colors.night ? -0.02 : 0.02}
customOpacity={1}
customStyle={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
borderRadius: 0,
overflow: 'hidden',
overflow: "hidden",
paddingHorizontal: 12,
paddingVertical: compactMode ? 8 : 12
}}
>
{item.type === 'note' ? <Filler background={background} item={item} /> : null}
{item.type === "note" ? (
<Filler background={background} item={item} />
) : null}
<SelectionIcon
compactMode={compactMode}
setActionStrip={setActionStrip}
@@ -70,7 +88,9 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
/>
{children}
{actionStrip ? <ActionStrip note={item} setActionStrip={setActionStrip} /> : null}
{actionStrip ? (
<ActionStrip note={item} setActionStrip={setActionStrip} />
) : null}
</PressableButton>
);
};

View File

@@ -1,22 +1,26 @@
import React, { useEffect, useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
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 React, { useEffect, useState } from "react";
import { TouchableOpacity, View } from "react-native";
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";
export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const selectionMode = useSelectionStore(state => state.selectionMode);
const selectedItemsList = useSelectionStore(state => state.selectedItemsList);
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
const [selected, setSelected] = useState(false);
useEffect(() => {
if (selectionMode) {
setActionStrip(false);
let exists = selectedItemsList.filter(o => o.dateCreated === item.dateCreated);
let exists = selectedItemsList.filter(
(o) => o.dateCreated === item.dateCreated
);
if (exists[0]) {
if (!selected) {
@@ -37,12 +41,12 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
return selectionMode ? (
<View
style={{
display: 'flex',
display: "flex",
opacity: 1,
width: '10%',
width: "10%",
height: compactMode ? 40 : 70,
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
backgroundColor: colors.bg,
borderRadius: 5,
marginRight: 10,
@@ -54,13 +58,17 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
activeOpacity={1}
onPress={onPress}
style={{
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
height: 70
}}
>
{selected && (
<Icon size={SIZE.xl} color={selected ? colors.accent : colors.icon} name="check" />
<Icon
size={SIZE.xl}
color={selected ? colors.accent : colors.icon}
name="check"
/>
)}
</TouchableOpacity>
</View>

View File

@@ -1,19 +1,19 @@
import React from 'react';
import { View } from 'react-native';
import { notesnook } from '../../../../e2e/test.ids';
import { TaggedNotes } from '../../../screens/notes/tagged';
import { useThemeStore } from '../../../stores/use-theme-store';
import { db } from '../../../common/database';
import { SIZE } from '../../../utils/size';
import { Properties } from '../../properties';
import { IconButton } from '../../ui/icon-button';
import { PressableButton } from '../../ui/pressable';
import Heading from '../../ui/typography/heading';
import Paragraph from '../../ui/typography/paragraph';
import React from "react";
import { View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useThemeStore } from "../../../stores/use-theme-store";
import { db } from "../../../common/database";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const TagItem = React.memo(
({ item, index }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const onPress = () => {
TaggedNotes.navigate(item, true);
};
@@ -27,16 +27,16 @@ const TagItem = React.memo(
opacity={1}
customStyle={{
paddingHorizontal: 12,
flexDirection: 'row',
flexDirection: "row",
paddingVertical: 12,
alignItems: 'center',
width: '100%',
justifyContent: 'space-between'
alignItems: "center",
width: "100%",
justifyContent: "space-between"
}}
>
<View
style={{
maxWidth: '92%'
maxWidth: "92%"
}}
>
<Heading size={SIZE.md}>
@@ -58,9 +58,9 @@ const TagItem = React.memo(
}}
>
{item && item.noteIds.length && item.noteIds.length > 1
? item.noteIds.length + ' notes'
? item.noteIds.length + " notes"
: item.noteIds.length === 1
? item.noteIds.length + ' note'
? item.noteIds.length + " note"
: null}
</Paragraph>
</View>
@@ -74,11 +74,11 @@ const TagItem = React.memo(
}}
testID={notesnook.ids.tag.menu}
customStyle={{
justifyContent: 'center',
justifyContent: "center",
height: 35,
width: 35,
borderRadius: 100,
alignItems: 'center'
alignItems: "center"
}}
/>
</PressableButton>
@@ -96,6 +96,6 @@ const TagItem = React.memo(
}
);
TagItem.displayName = 'TagItem';
TagItem.displayName = "TagItem";
export default TagItem;

View File

@@ -1,23 +1,23 @@
import React from 'react';
import { View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useMessageStore } from '../../stores/use-message-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { hexToRGBA } from '../../utils/color-scheme/utils';
import { SIZE } from '../../utils/size';
import { PressableButton } from '../ui/pressable';
import Paragraph from '../ui/typography/paragraph';
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { hexToRGBA } from "../../utils/color-scheme/utils";
import { SIZE } from "../../utils/size";
import { PressableButton } from "../ui/pressable";
import Paragraph from "../ui/typography/paragraph";
export const Card = ({ color, warning }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
color = color ? color : colors.accent;
const messageBoardState = useMessageStore(state => state.message);
const announcement = useMessageStore(state => state.announcement);
const messageBoardState = useMessageStore((state) => state.message);
const announcement = useMessageStore((state) => state.announcement);
return !messageBoardState.visible || announcement || warning ? null : (
<View
style={{
width: '95%'
width: "95%"
}}
>
<PressableButton
@@ -25,10 +25,10 @@ export const Card = ({ color, warning }) => {
type="gray"
customStyle={{
paddingVertical: 12,
width: '95%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
width: "95%",
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
paddingHorizontal: 0
}}
>
@@ -36,19 +36,21 @@ export const Card = ({ color, warning }) => {
style={{
width: 40,
backgroundColor:
messageBoardState.type === 'error'
messageBoardState.type === "error"
? hexToRGBA(colors.red, 0.15)
: hexToRGBA(color, 0.15),
height: 40,
marginLeft: 10,
borderRadius: 100,
alignItems: 'center',
justifyContent: 'center'
alignItems: "center",
justifyContent: "center"
}}
>
<Icon
size={SIZE.lg}
color={messageBoardState.type === 'error' ? colors.errorText : color}
color={
messageBoardState.type === "error" ? colors.errorText : color
}
name={messageBoardState.icon}
/>
</View>
@@ -56,7 +58,7 @@ export const Card = ({ color, warning }) => {
<View
style={{
marginLeft: 10,
maxWidth: '70%'
maxWidth: "70%"
}}
>
<Paragraph color={colors.icon} size={SIZE.xs}>
@@ -64,7 +66,7 @@ export const Card = ({ color, warning }) => {
</Paragraph>
<Paragraph
style={{
maxWidth: '100%'
maxWidth: "100%"
}}
color={colors.heading}
>
@@ -76,15 +78,15 @@ export const Card = ({ color, warning }) => {
style={{
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
justifyContent: "center",
alignItems: "center",
position: "absolute",
right: 6
}}
>
<Icon
name="chevron-right"
color={messageBoardState.type === 'error' ? colors.red : color}
color={messageBoardState.type === "error" ? colors.red : color}
size={SIZE.lg}
/>
</View>

View File

@@ -1,49 +1,61 @@
import React from 'react';
import { ActivityIndicator, useWindowDimensions, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useThemeStore } from '../../stores/use-theme-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useTip } from '../../services/tip-manager';
import { COLORS_NOTE } from '../../utils/color-scheme';
import { SIZE } from '../../utils/size';
import { Button } from '../ui/button';
import Seperator from '../ui/seperator';
import { Tip } from '../tip';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { notesnook } from '../../../e2e/test.ids';
import React from "react";
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useThemeStore } from "../../stores/use-theme-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useTip } from "../../services/tip-manager";
import { COLORS_NOTE } from "../../utils/color-scheme";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Seperator from "../ui/seperator";
import { Tip } from "../tip";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { notesnook } from "../../../e2e/test.ids";
export const Empty = React.memo(
({ loading = true, placeholderData, headerProps, type, screen }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const insets = useSafeAreaInsets();
const { height } = useWindowDimensions();
const introCompleted = useSettingStore(state => state.settings.introCompleted);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const tip = useTip(
screen === 'Notes' && introCompleted ? 'first-note' : placeholderData.type || type,
screen === 'Notes' ? 'notes' : null
screen === "Notes" && introCompleted
? "first-note"
: placeholderData.type || type,
screen === "Notes" ? "notes" : null
);
const color =
colors[COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'];
colors[
COLORS_NOTE[headerProps.color?.toLowerCase()]
? headerProps.color
: "accent"
];
return (
<View
style={[
{
height: height - (140 + insets.top),
width: '80%',
justifyContent: 'center',
alignSelf: 'center'
width: "80%",
justifyContent: "center",
alignSelf: "center"
}
]}
>
{!loading ? (
<>
<Tip
color={COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'}
color={
COLORS_NOTE[headerProps.color?.toLowerCase()]
? headerProps.color
: "accent"
}
tip={tip || { text: placeholderData.paragraph }}
style={{
backgroundColor: 'transparent',
backgroundColor: "transparent",
paddingHorizontal: 0
}}
/>
@@ -56,11 +68,13 @@ export const Empty = React.memo(
icon="arrow-right"
onPress={placeholderData.action}
accentColor={
COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'
COLORS_NOTE[headerProps.color?.toLowerCase()]
? headerProps.color
: "accent"
}
accentText="light"
style={{
alignSelf: 'flex-start',
alignSelf: "flex-start",
borderRadius: 5,
height: 40
}}
@@ -71,9 +85,9 @@ export const Empty = React.memo(
<>
<View
style={{
alignSelf: 'center',
alignItems: 'flex-start',
width: '100%'
alignSelf: "center",
alignItems: "flex-start",
width: "100%"
}}
>
<Heading>{placeholderData.heading}</Heading>
@@ -83,7 +97,9 @@ export const Empty = React.memo(
<Seperator />
<ActivityIndicator
size={SIZE.lg}
color={COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent}
color={
COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent
}
/>
</View>
</>

View File

@@ -1,21 +1,21 @@
import React, { useRef } from 'react';
import { FlatList, RefreshControl, View } from 'react-native';
import Animated, { FadeInDown } from 'react-native-reanimated';
import { notesnook } from '../../../e2e/test.ids';
import { eSendEvent } from '../../services/event-manager';
import Sync from '../../services/sync';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import { eScrollEvent } from '../../utils/events';
import { tabBarRef } from '../../utils/global-refs';
import JumpToSectionDialog from '../dialogs/jump-to-section';
import { Footer } from '../list-items/footer';
import { Header } from '../list-items/headers/header';
import { SectionHeader } from '../list-items/headers/section-header';
import { NoteWrapper } from '../list-items/note/wrapper';
import { NotebookWrapper } from '../list-items/notebook/wrapper';
import TagItem from '../list-items/tag';
import { Empty } from './empty';
import React, { useRef } from "react";
import { FlatList, RefreshControl, View } from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated";
import { notesnook } from "../../../e2e/test.ids";
import { eSendEvent } from "../../services/event-manager";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import { eScrollEvent } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import JumpToSectionDialog from "../dialogs/jump-to-section";
import { Footer } from "../list-items/footer";
import { Header } from "../list-items/headers/header";
import { SectionHeader } from "../list-items/headers/section-header";
import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import TagItem from "../list-items/tag";
import { Empty } from "./empty";
const renderItems = {
note: NoteWrapper,
@@ -30,12 +30,13 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
if (!item) return <View />;
const Item = renderItems[item.itemType || item.type] || View;
const groupOptions = db.settings?.getGroupOptions(type);
const dateBy = groupOptions.sortBy !== 'title' ? groupOptions.sortBy : 'dateEdited';
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const tags =
item.tags
?.slice(0, 3)
?.map(item => {
?.map((item) => {
let tag = db.tags.tag(item);
if (!tag) return null;
@@ -45,8 +46,17 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
alias: tag.alias
};
})
.filter(t => t !== null) || [];
return <Item item={item} tags={tags} dateBy={dateBy} index={index} type={type} {...restArgs} />;
.filter((t) => t !== null) || [];
return (
<Item
item={item}
tags={tags}
dateBy={dateBy}
index={index}
type={type}
{...restArgs}
/>
);
};
const List = ({
@@ -56,14 +66,14 @@ const List = ({
placeholderData,
loading,
headerProps = {
heading: 'Home',
heading: "Home",
color: null
},
screen,
ListHeader,
warning
}) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const scrollRef = useRef();
const renderItem = React.useCallback(
@@ -73,7 +83,7 @@ const List = ({
index={index}
color={headerProps.color}
title={headerProps.heading}
type={screen === 'Notes' ? 'home' : type}
type={screen === "Notes" ? "home" : type}
screen={screen}
/>
),
@@ -81,7 +91,7 @@ const List = ({
);
const _onRefresh = async () => {
Sync.run('global', false, true, () => {
Sync.run("global", false, true, () => {
if (refreshCallback) {
refreshCallback();
}
@@ -89,7 +99,7 @@ const List = ({
};
const _onScroll = React.useCallback(
event => {
(event) => {
if (!event) return;
let y = event.nativeEvent.contentOffset.y;
eSendEvent(eScrollEvent, {
@@ -101,12 +111,12 @@ const List = ({
);
let styles = {
width: '100%',
width: "100%",
minHeight: 1,
minWidth: 1
};
const _keyExtractor = item => item.id || item.title;
const _keyExtractor = (item) => item.id || item.title;
return (
<>
@@ -114,7 +124,7 @@ const List = ({
style={{
flex: 1
}}
entering={type === 'search' ? undefined : FadeInDown}
entering={type === "search" ? undefined : FadeInDown}
>
<FlatList
style={styles}
@@ -171,7 +181,7 @@ const List = ({
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === 'Notes' ? 'home' : type}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
</>

View File

@@ -1,31 +1,35 @@
import KeepAwake from '@sayem314/react-native-keep-awake';
import React, { useEffect, useRef, useState } from 'react';
import { Modal, SafeAreaView, Text, View } from 'react-native';
import Animated from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Editor from '../../screens/editor';
import { editorController } from '../../screens/editor/tiptap/utils';
import { DDS } from '../../services/device-detection';
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import Navigation from '../../services/navigation';
import Sync from '../../services/sync';
import { useThemeStore } from '../../stores/use-theme-store';
import { dHeight } from '../../utils';
import { db } from '../../common/database';
import { eOnLoadNote, eShowMergeDialog } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { timeConverter } from '../../utils/time';
import BaseDialog from '../dialog/base-dialog';
import DialogButtons from '../dialog/dialog-buttons';
import DialogContainer from '../dialog/dialog-container';
import DialogHeader from '../dialog/dialog-header';
import { Button } from '../ui/button';
import { IconButton } from '../ui/icon-button';
import Seperator from '../ui/seperator';
import Paragraph from '../ui/typography/paragraph';
import KeepAwake from "@sayem314/react-native-keep-awake";
import React, { useEffect, useRef, useState } from "react";
import { Modal, SafeAreaView, Text, View } from "react-native";
import Animated from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Editor from "../../screens/editor";
import { editorController } from "../../screens/editor/tiptap/utils";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { dHeight } from "../../utils";
import { db } from "../../common/database";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { timeConverter } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import DialogButtons from "../dialog/dialog-buttons";
import DialogContainer from "../dialog/dialog-container";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
const MergeConflicts = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [keep, setKeep] = useState(null);
const [copy, setCopy] = useState(null);
@@ -55,7 +59,7 @@ const MergeConflicts = () => {
if (copy) {
await db.notes.add({
title: note.title + ' (Copy)',
title: note.title + " (Copy)",
content: {
data: copy.data,
type: copy.type
@@ -63,21 +67,24 @@ const MergeConflicts = () => {
});
}
Navigation.queueRoutesForUpdate(
'Notes',
'Favorites',
'ColoredNotes',
'TaggedNotes',
'TopicNotes'
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
if (editorController.current?.note?.id === note.id) {
// reload the note in editor
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
eSendEvent(eOnLoadNote, {
...editorController.current?.note,
forced: true
});
}
close();
Sync.run();
};
const show = async item => {
const show = async (item) => {
let noteContent = await db.content.raw(item.contentId);
content.current = { ...noteContent };
if (__DEV__) {
@@ -102,42 +109,59 @@ const MergeConflicts = () => {
setDialogVisible(false);
};
const ConfigBar = ({ isDiscarded, keeping, back, isCurrent, contentToKeep }) => {
const ConfigBar = ({
isDiscarded,
keeping,
back,
isCurrent,
contentToKeep
}) => {
return (
<View
style={{
width: '100%',
width: "100%",
height: 50,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 12,
paddingLeft: 6
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
flexShrink: 1
}}
>
{back && <IconButton onPress={close} color={colors.pri} name="arrow-left" />}
<Paragraph style={{ flexWrap: 'wrap' }} color={colors.icon} size={SIZE.xs}>
<Text style={{ color: isCurrent ? colors.accent : colors.red, fontWeight: 'bold' }}>
{isCurrent ? '(This Device)' : '(Incoming)'}
{back && (
<IconButton onPress={close} color={colors.pri} name="arrow-left" />
)}
<Paragraph
style={{ flexWrap: "wrap" }}
color={colors.icon}
size={SIZE.xs}
>
<Text
style={{
color: isCurrent ? colors.accent : colors.red,
fontWeight: "bold"
}}
>
{isCurrent ? "(This Device)" : "(Incoming)"}
</Text>
{'\n'}
{"\n"}
{timeConverter(contentToKeep?.dateEdited)}
</Paragraph>
</View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end'
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-end"
}}
>
{isDiscarded ? (
@@ -187,7 +211,7 @@ const MergeConflicts = () => {
}}
type="accent"
fontSize={SIZE.xs}
title={keeping && !isDiscarded ? 'Undo' : 'Keep'}
title={keeping && !isDiscarded ? "Undo" : "Keep"}
onPress={() => {
setKeep(keeping && !isDiscarded ? null : contentToKeep);
}}
@@ -208,11 +232,11 @@ const MergeConflicts = () => {
close();
}}
supportedOrientations={[
'portrait',
'portrait-upside-down',
'landscape',
'landscape-left',
'landscape-right'
"portrait",
"portrait-upside-down",
"landscape",
"landscape-left",
"landscape-right"
]}
visible={true}
>
@@ -244,9 +268,9 @@ const MergeConflicts = () => {
<View
style={{
height: '100%',
width: '100%',
backgroundColor: DDS.isLargeTablet() ? 'rgba(0,0,0,0.3)' : null
height: "100%",
width: "100%",
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
}}
>
<ConfigBar
@@ -273,7 +297,7 @@ const MergeConflicts = () => {
onLoad={() => {
const note = db.notes.note(content.current?.noteId)?.data;
if (!note) return;
eSendEvent(eOnLoadNote + ':conflictPrimary', {
eSendEvent(eOnLoadNote + ":conflictPrimary", {
...note,
content: {
...content.current,
@@ -307,7 +331,7 @@ const MergeConflicts = () => {
onLoad={() => {
const note = db.notes.note(content.current?.noteId)?.data;
if (!note) return;
eSendEvent(eOnLoadNote + ':conflictSecondary', {
eSendEvent(eOnLoadNote + ":conflictSecondary", {
...note,
content: { ...content.current.conflicted, isPreview: true }
});

View File

@@ -1,24 +1,24 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Text, View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useThemeStore } from '../../stores/use-theme-store';
import { presentSheet } from '../../services/event-manager';
import { db } from '../../common/database';
import { openLinkInBrowser } from '../../utils/functions';
import { SIZE } from '../../utils/size';
import { timeConverter, timeSince } from '../../utils/time';
import DialogHeader from '../dialog/dialog-header';
import SheetProvider from '../sheet-provider';
import { PressableButton } from '../ui/pressable';
import Seperator from '../ui/seperator';
import Paragraph from '../ui/typography/paragraph';
import NotePreview from './preview';
import React, { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeStore } from "../../stores/use-theme-store";
import { presentSheet } from "../../services/event-manager";
import { db } from "../../common/database";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { timeConverter, timeSince } from "../../utils/time";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { PressableButton } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import NotePreview from "./preview";
export default function NoteHistory({ note, fwdRef }) {
const [history, setHistory] = useState([]);
const [loading, setLoading] = useState(true);
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
useEffect(() => {
(async () => {
@@ -40,7 +40,7 @@ export default function NoteHistory({ note, fwdRef }) {
content={content}
/>
),
context: 'note_history'
context: "note_history"
});
}
@@ -48,12 +48,12 @@ export default function NoteHistory({ note, fwdRef }) {
let _start = timeConverter(start);
let _end = timeConverter(end + 60000);
if (_start === _end) return _start;
let final = _end.lastIndexOf(',');
let final = _end.lastIndexOf(",");
let part = _end.slice(0, final + 1);
if (_start.includes(part)) {
return _start + '' + _end.replace(part, '');
return _start + "" + _end.replace(part, "");
}
return _start + '' + _end;
return _start + "" + _end;
};
const renderItem = useCallback(
@@ -62,12 +62,12 @@ export default function NoteHistory({ note, fwdRef }) {
type="grayBg"
onPress={() => preview(item)}
customStyle={{
justifyContent: 'space-between',
alignItems: 'center',
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 12,
height: 45,
marginBottom: 10,
flexDirection: 'row'
flexDirection: "row"
}}
>
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
@@ -97,19 +97,21 @@ export default function NoteHistory({ note, fwdRef }) {
style={{
paddingHorizontal: 12
}}
keyExtractor={item => item.id}
keyExtractor={(item) => item.id}
data={history}
ListEmptyComponent={
<View
style={{
width: '100%',
justifyContent: 'center',
alignItems: 'center',
width: "100%",
justifyContent: "center",
alignItems: "center",
height: 200
}}
>
<Icon name="history" size={60} color={colors.icon} />
<Paragraph color={colors.icon}>No note history found on this device.</Paragraph>
<Paragraph color={colors.icon}>
No note history found on this device.
</Paragraph>
</View>
}
renderItem={renderItem}
@@ -118,15 +120,18 @@ export default function NoteHistory({ note, fwdRef }) {
size={SIZE.xs}
color={colors.icon}
style={{
alignSelf: 'center'
alignSelf: "center"
}}
>
Note version history is local only.{' '}
Note version history is local only.{" "}
<Text
onPress={() => {
openLinkInBrowser('https://docs.notesnook.com/versionhistory', colors);
openLinkInBrowser(
"https://docs.notesnook.com/versionhistory",
colors
);
}}
style={{ color: colors.accent, textDecorationLine: 'underline' }}
style={{ color: colors.accent, textDecorationLine: "underline" }}
>
Learn how this works.
</Text>

View File

@@ -1,42 +1,45 @@
import React from 'react';
import { View } from 'react-native';
import Editor from '../../screens/editor';
import EditorOverlay from '../../screens/editor/loading';
import { editorController } from '../../screens/editor/tiptap/utils';
import { eSendEvent, ToastEvent } from '../../services/event-manager';
import Navigation from '../../services/navigation';
import { useEditorStore } from '../../stores/use-editor-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { db } from '../../common/database';
import { eCloseProgressDialog, eOnLoadNote } from '../../utils/events';
import DialogHeader from '../dialog/dialog-header';
import { Button } from '../ui/button';
import Paragraph from '../ui/typography/paragraph';
import React from "react";
import { View } from "react-native";
import Editor from "../../screens/editor";
import EditorOverlay from "../../screens/editor/loading";
import { editorController } from "../../screens/editor/tiptap/utils";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useEditorStore } from "../../stores/use-editor-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { db } from "../../common/database";
import { eCloseProgressDialog, eOnLoadNote } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
export default function NotePreview({ session, content }) {
const colors = useThemeStore(state => state.colors);
const editorId = ':noteHistory';
const colors = useThemeStore((state) => state.colors);
const editorId = ":noteHistory";
async function restore() {
await db.noteHistory.restore(session.id);
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
if (editorController.current?.note) {
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
eSendEvent(eOnLoadNote, {
...editorController.current?.note,
forced: true
});
}
}
eSendEvent(eCloseProgressDialog, 'note_history');
eSendEvent(eCloseProgressDialog, "note_history");
eSendEvent(eCloseProgressDialog);
Navigation.queueRoutesForUpdate(
'Notes',
'Favorites',
'ColoredNotes',
'TaggedNotes',
'TopicNotes'
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
ToastEvent.show({
heading: 'Note restored successfully',
type: 'success'
heading: "Note restored successfully",
type: "success"
});
}
@@ -44,7 +47,7 @@ export default function NotePreview({ session, content }) {
<View
style={{
height: session.locked ? null : 600,
width: '100%'
width: "100%"
}}
>
<DialogHeader padding={12} title={session.session} />
@@ -71,13 +74,15 @@ export default function NotePreview({ session, content }) {
) : (
<View
style={{
width: '100%',
width: "100%",
height: 100,
justifyContent: 'center',
alignItems: 'center'
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.icon}>Preview not available, content is encrypted.</Paragraph>
<Paragraph color={colors.icon}>
Preview not available, content is encrypted.
</Paragraph>
</View>
)}
@@ -86,7 +91,12 @@ export default function NotePreview({ session, content }) {
paddingHorizontal: 12
}}
>
<Button onPress={restore} title="Restore this version" type="accent" width="100%" />
<Button
onPress={restore}
title="Restore this version"
type="accent"
width="100%"
/>
</View>
</View>
);

View File

@@ -1,50 +1,55 @@
import React from 'react';
import { ScrollView } from 'react-native';
import { FeatureBlock } from './feature';
import React from "react";
import { ScrollView } from "react-native";
import { FeatureBlock } from "./feature";
export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scrollRef }) => {
export const CompactFeatures = ({
vertical,
features = [],
maxHeight = 500,
scrollRef
}) => {
let data = vertical
? features
: [
{
highlight: 'Everything',
content: 'in basic',
icon: 'emoticon-wink'
highlight: "Everything",
content: "in basic",
icon: "emoticon-wink"
},
{
highlight: 'Unlimited',
content: 'notebooks',
icon: 'notebook'
highlight: "Unlimited",
content: "notebooks",
icon: "notebook"
},
{
highlight: 'File & image',
content: 'attachments',
icon: 'attachment'
highlight: "File & image",
content: "attachments",
icon: "attachment"
},
{
highlight: 'Instant',
content: 'syncing',
icon: 'sync'
highlight: "Instant",
content: "syncing",
icon: "sync"
},
{
highlight: 'Private',
content: 'vault',
icon: 'shield'
highlight: "Private",
content: "vault",
icon: "shield"
},
{
highlight: 'Rich text',
content: 'editing',
icon: 'square-edit-outline'
highlight: "Rich text",
content: "editing",
icon: "square-edit-outline"
},
{
highlight: 'PDF & markdown',
content: 'exports',
icon: 'file'
highlight: "PDF & markdown",
content: "exports",
icon: "file"
},
{
highlight: 'Encrypted',
content: 'backups',
icon: 'backup-restore'
highlight: "Encrypted",
content: "backups",
icon: "backup-restore"
}
];
@@ -57,11 +62,11 @@ export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scro
}}
showsHorizontalScrollIndicator={false}
style={{
width: '100%',
width: "100%",
maxHeight: maxHeight
}}
>
{data.map(item => (
{data.map((item) => (
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
))}
</ScrollView>

View File

@@ -1,62 +1,64 @@
import React, { useState } from 'react';
import { ActivityIndicator, ScrollView, View } from 'react-native';
import { LAUNCH_ROCKET } from '../../assets/images/assets';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
import { DDS } from '../../services/device-detection';
import { eSendEvent, presentSheet } from '../../services/event-manager';
import PremiumService from '../../services/premium';
import { getElevation } from '../../utils';
import { db } from '../../common/database';
import React, { useState } from "react";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { LAUNCH_ROCKET } from "../../assets/images/assets";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { DDS } from "../../services/device-detection";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import PremiumService from "../../services/premium";
import { getElevation } from "../../utils";
import { db } from "../../common/database";
import {
eClosePremiumDialog,
eCloseProgressDialog,
eOpenLoginDialog,
eOpenResultDialog
} from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import umami from '../../common/analytics';
import { IconButton } from '../ui/icon-button';
import { AuthMode } from '../auth';
import { Button } from '../ui/button';
import SheetProvider from '../sheet-provider';
import { SvgView } from '../ui/svg';
import Seperator from '../ui/seperator';
import { Toast } from '../toast';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { Walkthrough } from '../walkthroughs';
import { features } from './features';
import { Group } from './group';
import { PricingPlans } from './pricing-plans';
import { usePricing } from '../../hooks/use-pricing';
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import umami from "../../common/analytics";
import { IconButton } from "../ui/icon-button";
import { AuthMode } from "../auth";
import { Button } from "../ui/button";
import SheetProvider from "../sheet-provider";
import { SvgView } from "../ui/svg";
import Seperator from "../ui/seperator";
import { Toast } from "../toast";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import { features } from "./features";
import { Group } from "./group";
import { PricingPlans } from "./pricing-plans";
import { usePricing } from "../../hooks/use-pricing";
export const Component = ({ close, promo, getRef }) => {
const colors = useThemeStore(state => state.colors);
const user = useUserStore(state => state.user);
const colors = useThemeStore((state) => state.colors);
const user = useUserStore((state) => state.user);
const userCanRequestTrial =
user && (!user.subscription || !user.subscription.expiry) ? true : false;
const [floatingButton, setFloatingButton] = useState(false);
const pricing = usePricing('monthly');
const pricing = usePricing("monthly");
const onPress = async () => {
if (user) {
umami.pageView('/pro-plans', `/pro-screen`);
umami.pageView("/pro-plans", `/pro-screen`);
presentSheet({
context: 'pricing_plans',
component: <PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
context: "pricing_plans",
component: (
<PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
)
});
} else {
close();
umami.pageView('/signup', `/pro-screen`);
umami.pageView("/signup", `/pro-screen`);
setTimeout(() => {
eSendEvent(eOpenLoginDialog, AuthMode.trialSignup);
}, 400);
}
};
const onScroll = event => {
const onScroll = (event) => {
let contentSize = event.nativeEvent.contentSize.height;
contentSize = contentSize - event.nativeEvent.layoutMeasurement.height;
let yOffset = event.nativeEvent.contentOffset.y;
@@ -70,11 +72,11 @@ export const Component = ({ close, promo, getRef }) => {
return (
<View
style={{
width: '100%',
width: "100%",
backgroundColor: colors.bg,
justifyContent: 'space-between',
justifyContent: "space-between",
borderRadius: 10,
maxHeight: '100%'
maxHeight: "100%"
}}
>
<SheetProvider context="pricing_plans" />
@@ -83,7 +85,7 @@ export const Component = ({ close, promo, getRef }) => {
close();
}}
customStyle={{
position: 'absolute',
position: "absolute",
right: DDS.isTab ? 30 : 15,
top: 30,
zIndex: 10,
@@ -106,24 +108,28 @@ export const Component = ({ close, promo, getRef }) => {
<View
key="top-banner"
style={{
width: '100%',
alignItems: 'center',
width: "100%",
alignItems: "center",
height: 400,
justifyContent: 'center'
justifyContent: "center"
}}
>
<SvgView width={350} height={350} src={LAUNCH_ROCKET(colors.accent)} />
<SvgView
width={350}
height={350}
src={LAUNCH_ROCKET(colors.accent)}
/>
</View>
<Heading
key="heading"
size={SIZE.lg}
style={{
alignSelf: 'center',
alignSelf: "center",
paddingTop: 20
}}
>
Notesnook{' '}
Notesnook{" "}
<Heading size={SIZE.lg} color={colors.accent}>
Pro
</Heading>
@@ -140,7 +146,7 @@ export const Component = ({ close, promo, getRef }) => {
) : (
<Paragraph
style={{
alignSelf: 'center',
alignSelf: "center",
marginBottom: 20
}}
size={SIZE.md}
@@ -154,10 +160,10 @@ export const Component = ({ close, promo, getRef }) => {
size={SIZE.md}
style={{
paddingHorizontal: 12,
textAlign: 'center',
alignSelf: 'center',
textAlign: "center",
alignSelf: "center",
paddingBottom: 20,
width: '90%'
width: "90%"
}}
>
Ready to take the next step on your private note taking journey?
@@ -172,7 +178,7 @@ export const Component = ({ close, promo, getRef }) => {
eSendEvent(eClosePremiumDialog);
eSendEvent(eCloseProgressDialog);
await sleep(300);
Walkthrough.present('trialstarted', false, true);
Walkthrough.present("trialstarted", false, true);
} catch (e) {}
}}
title="Try free for 14 days"
@@ -189,8 +195,10 @@ export const Component = ({ close, promo, getRef }) => {
<Button
key="calltoaction"
onPress={onPress}
title={promo ? promo.text : user ? `See all plans` : `Sign up for free`}
type={userCanRequestTrial ? 'grayAccent' : 'accent'}
title={
promo ? promo.text : user ? `See all plans` : `Sign up for free`
}
type={userCanRequestTrial ? "grayAccent" : "accent"}
width={250}
style={{
paddingHorizontal: 12,
@@ -204,16 +212,16 @@ export const Component = ({ close, promo, getRef }) => {
color={colors.icon}
size={SIZE.xs}
style={{
alignSelf: 'center',
textAlign: 'center',
alignSelf: "center",
textAlign: "center",
marginTop: 10,
maxWidth: '80%'
maxWidth: "80%"
}}
>
{user
? `On clicking "Try free for 14 days", your free trial will be activated.`
: `After sign up you will be asked to activate your free trial.`}{' '}
<Paragraph size={SIZE.xs} style={{ fontWeight: 'bold' }}>
: `After sign up you will be asked to activate your free trial.`}{" "}
<Paragraph size={SIZE.xs} style={{ fontWeight: "bold" }}>
No credit card is required.
</Paragraph>
</Paragraph>
@@ -238,11 +246,13 @@ export const Component = ({ close, promo, getRef }) => {
{floatingButton ? (
<Button
onPress={onPress}
title={promo ? promo.text : user ? `See all plans` : 'Sign up for free'}
title={
promo ? promo.text : user ? `See all plans` : "Sign up for free"
}
type="accent"
style={{
paddingHorizontal: 24,
position: 'absolute',
position: "absolute",
borderRadius: 100,
bottom: 30,
...getElevation(10)

View File

@@ -1,30 +1,38 @@
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import PremiumService from '../../services/premium';
import { eOpenPremiumDialog, eOpenResultDialog, eOpenTrialEndingDialog } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { Button } from '../ui/button';
import BaseDialog from '../dialog/base-dialog';
import DialogContainer from '../dialog/dialog-container';
import Seperator from '../ui/seperator';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { CompactFeatures } from './compact-features';
import { Offer } from './offer';
import { usePricing } from '../../hooks/use-pricing';
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import PremiumService from "../../services/premium";
import {
eOpenPremiumDialog,
eOpenResultDialog,
eOpenTrialEndingDialog
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import BaseDialog from "../dialog/base-dialog";
import DialogContainer from "../dialog/dialog-container";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { CompactFeatures } from "./compact-features";
import { Offer } from "./offer";
import { usePricing } from "../../hooks/use-pricing";
export const Expiring = () => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [status, setStatus] = useState({
title: 'Your trial is ending soon',
title: "Your trial is ending soon",
offer: null,
extend: true
});
const pricing = usePricing('yearly');
const pricing = usePricing("yearly");
console.log(pricing?.info?.discount);
const promo = status.offer
@@ -32,8 +40,10 @@ export const Expiring = () => {
promoCode:
pricing?.info?.discount > 30
? pricing.info.sku
: 'com.streetwriters.notesnook.sub.yr.trialoffer',
text: `GET ${pricing?.info?.discount > 30 ? pricing?.info?.discount : 30}% OFF on yearly`,
: "com.streetwriters.notesnook.sub.yr.trialoffer",
text: `GET ${
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}% OFF on yearly`,
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}
: null;
@@ -45,7 +55,7 @@ export const Expiring = () => {
};
}, []);
const open = status => {
const open = (status) => {
setStatus(status);
setVisible(true);
};
@@ -60,20 +70,20 @@ export const Expiring = () => {
<DialogContainer>
<View
style={{
width: '100%',
alignItems: 'center'
width: "100%",
alignItems: "center"
}}
>
<View
style={{
paddingHorizontal: 12,
width: '100%'
width: "100%"
}}
>
<Heading
textBreakStrategy="balanced"
style={{
textAlign: 'center',
textAlign: "center",
paddingTop: 18
}}
>
@@ -82,8 +92,8 @@ export const Expiring = () => {
<Seperator />
<View
style={{
width: '100%',
alignItems: 'center'
width: "100%",
alignItems: "center"
}}
>
{status.offer ? (
@@ -95,13 +105,14 @@ export const Expiring = () => {
<Paragraph
textBreakStrategy="balanced"
style={{
textAlign: 'center',
textAlign: "center",
paddingTop: 0,
paddingBottom: 20
}}
size={SIZE.md + 2}
>
Upgrade now to continue using all the pro features after your trial ends
Upgrade now to continue using all the pro features after
your trial ends
</Paragraph>
</>
)}
@@ -116,7 +127,7 @@ export const Expiring = () => {
}}
size={SIZE.xs + 1}
style={{
textDecorationLine: 'underline',
textDecorationLine: "underline",
color: colors.icon,
marginTop: 10
}}
@@ -131,7 +142,7 @@ export const Expiring = () => {
<View
style={{
backgroundColor: colors.nav,
width: '100%',
width: "100%",
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10
}}
@@ -142,7 +153,10 @@ export const Expiring = () => {
onPress={async () => {
setVisible(false);
await sleep(300);
PremiumService.sheet(null, promo?.discount > 30 ? null : promo);
PremiumService.sheet(
null,
promo?.discount > 30 ? null : promo
);
}}
fontSize={SIZE.md + 2}
style={{
@@ -157,16 +171,16 @@ export const Expiring = () => {
type="gray"
title="Not sure yet? Extend trial for 7 days"
textStyle={{
textDecorationLine: 'underline'
textDecorationLine: "underline"
}}
onPress={async () => {
setVisible(false);
await sleep(300);
eSendEvent(eOpenResultDialog, {
title: 'Your trial has been extended',
title: "Your trial has been extended",
paragraph:
'Try out all features of Notesnook free for 7 more days. No limitations. No commitments.',
button: 'Continue'
"Try out all features of Notesnook free for 7 more days. No limitations. No commitments.",
button: "Continue"
});
}}
fontSize={SIZE.xs}

View File

@@ -1,19 +1,26 @@
import React from 'react';
import { Text, View } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import Paragraph from '../ui/typography/paragraph';
import { ProTag } from './pro-tag';
import React from "react";
import { Text, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { ProTag } from "./pro-tag";
export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg }) => {
const colors = useThemeStore(state => state.colors);
export const FeatureBlock = ({
vertical,
highlight,
content,
icon,
pro,
proTagBg
}) => {
const colors = useThemeStore((state) => state.colors);
return vertical ? (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
marginBottom: 10
}}
@@ -22,7 +29,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
<Paragraph
style={{
flexWrap: 'wrap',
flexWrap: "wrap",
marginLeft: 5,
flexShrink: 1
}}
@@ -35,7 +42,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
<View
style={{
height: 100,
justifyContent: 'center',
justifyContent: "center",
padding: 10,
marginRight: 10,
borderRadius: 5,
@@ -45,7 +52,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
<Icon color={colors.icon} name={icon} size={SIZE.xl} />
<Paragraph size={SIZE.md}>
<Text style={{ color: colors.accent }}>{highlight}</Text>
{'\n'}
{"\n"}
{content}
</Paragraph>

View File

@@ -1,224 +1,227 @@
export const features = [
{
title: 'Focused on privacy',
title: "Focused on privacy",
detail:
'Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.',
"Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.",
features: [
{
highlight: 'Zero ads',
content: '& zero trackers',
icon: 'billboard'
highlight: "Zero ads",
content: "& zero trackers",
icon: "billboard"
},
{
highlight: 'On device',
content: 'encryption',
icon: 'cellphone'
highlight: "On device",
content: "encryption",
icon: "cellphone"
},
{
highlight: 'Secure app',
content: 'lock for all',
icon: 'cellphone-lock'
highlight: "Secure app",
content: "lock for all",
icon: "cellphone-lock"
},
{
highlight: '100% end-to-end ',
content: 'encrypted',
icon: 'lock'
highlight: "100% end-to-end ",
content: "encrypted",
icon: "lock"
},
{
highlight: 'Password protected',
content: 'notes sharing',
icon: 'file-lock'
highlight: "Password protected",
content: "notes sharing",
icon: "file-lock"
}
]
},
{
title: 'No limit on notes or devices',
title: "No limit on notes or devices",
detail:
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
},
{
title: 'Attach files & images',
detail: 'Add your documents, PDFs, images and videos, and keep them safe and organized.',
title: "Attach files & images",
detail:
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
pro: true,
features: [
{
highlight: 'Bullet proof',
content: 'encryption',
icon: 'lock'
highlight: "Bullet proof",
content: "encryption",
icon: "lock"
},
{
highlight: 'High quality',
content: '4k images',
icon: 'image-multiple'
highlight: "High quality",
content: "4k images",
icon: "image-multiple"
},
{
highlight: 'No monthly',
content: 'storage limit',
icon: 'harddisk'
highlight: "No monthly",
content: "storage limit",
icon: "harddisk"
},
{
highlight: 'Generous 500 MB',
content: 'max file size',
icon: 'file-cabinet'
highlight: "Generous 500 MB",
content: "max file size",
icon: "file-cabinet"
},
{
highlight: 'No restriction',
content: 'on file type',
icon: 'file'
highlight: "No restriction",
content: "on file type",
icon: "file"
}
]
},
{
title: 'Keep secrets always locked with private vault',
title: "Keep secrets always locked with private vault",
detail:
'An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.',
"An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.",
pro: true
},
{
title: 'Organize yourself in the best way',
detail: 'We offer multiple ways to keep you organized. The only limit is your imagination.',
title: "Organize yourself in the best way",
detail:
"We offer multiple ways to keep you organized. The only limit is your imagination.",
features: [
{
highlight: 'Unlimited',
content: 'notebooks & tags*',
icon: 'emoticon',
highlight: "Unlimited",
content: "notebooks & tags*",
icon: "emoticon",
pro: true
},
{
highlight: 'Organize',
content: 'with colors',
icon: 'palette',
highlight: "Organize",
content: "with colors",
icon: "palette",
pro: true
},
{
highlight: 'Side menu',
content: 'shortcuts',
icon: 'link-variant'
highlight: "Side menu",
content: "shortcuts",
icon: "link-variant"
},
{
highlight: 'Pin note in',
content: 'notifications',
icon: 'pin',
platform: 'android'
highlight: "Pin note in",
content: "notifications",
icon: "pin",
platform: "android"
}
],
info: '* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags.'
info: "* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags."
},
{
title: 'Instant syncing',
title: "Instant syncing",
detail:
'Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.',
"Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.",
pro: true
},
{
title: 'Rich tools for rich editing',
title: "Rich tools for rich editing",
detail:
'Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.',
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
features: [
{
highlight: 'Basic formating',
content: 'and lists',
icon: 'format-bold'
highlight: "Basic formating",
content: "and lists",
icon: "format-bold"
},
{
highlight: 'Checklists',
content: '& tables',
icon: 'table',
highlight: "Checklists",
content: "& tables",
icon: "table",
pro: true
},
{
highlight: 'Markdown',
content: 'support',
icon: 'language-markdown',
highlight: "Markdown",
content: "support",
icon: "language-markdown",
pro: true
},
{
highlight: 'Write notes from',
content: 'notifications',
icon: 'bell',
platform: 'android'
highlight: "Write notes from",
content: "notifications",
icon: "bell",
platform: "android"
}
]
},
{
title: 'Safe publishing to the Internet',
title: "Safe publishing to the Internet",
detail:
'Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.',
"Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.",
features: [
{
highlight: 'Password protected',
content: 'sharing',
icon: 'send-lock'
highlight: "Password protected",
content: "sharing",
icon: "send-lock"
},
{
highlight: 'Self destruct',
content: 'monographs',
icon: 'bomb'
highlight: "Self destruct",
content: "monographs",
icon: "bomb"
}
]
},
{
title: 'Export and take your notes anywhere',
title: "Export and take your notes anywhere",
pro: true,
detail:
'You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.',
info: '* Free users can export notes in well formatted plain text.',
"You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.",
info: "* Free users can export notes in well formatted plain text.",
features: [
{
highlight: 'Export as ',
content: 'Markdown',
icon: 'language-markdown',
highlight: "Export as ",
content: "Markdown",
icon: "language-markdown",
pro: true
},
{
highlight: 'Export as',
content: 'PDF',
icon: 'file-pdf-box',
highlight: "Export as",
content: "PDF",
icon: "file-pdf-box",
pro: true
},
{
highlight: 'Export as',
content: 'HTML',
icon: 'language-html5',
highlight: "Export as",
content: "HTML",
icon: "language-html5",
pro: true
},
{
highlight: 'Export as',
content: 'text',
icon: 'clipboard-text-outline'
highlight: "Export as",
content: "text",
icon: "clipboard-text-outline"
}
]
},
{
title: 'Backup & keep your notes safe',
title: "Backup & keep your notes safe",
detail:
'Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.',
"Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.",
features: [
{
highlight: 'Backup',
content: 'encryption',
icon: 'backup-restore'
highlight: "Backup",
content: "encryption",
icon: "backup-restore"
}
],
pro: true
},
{
title: 'Personalize & make Notesnook your own',
detail: 'Change app themes to match your style. Custom themes are coming soon.',
title: "Personalize & make Notesnook your own",
detail:
"Change app themes to match your style. Custom themes are coming soon.",
features: [
{
highlight: 'Automatic',
content: 'dark mode',
icon: 'theme-light-dark',
highlight: "Automatic",
content: "dark mode",
icon: "theme-light-dark",
pro: false
},
{
highlight: 'Change accent',
content: 'color',
icon: 'invert-colors',
highlight: "Change accent",
content: "color",
icon: "invert-colors",
pro: true
}
]

View File

@@ -1,14 +1,14 @@
import React from 'react';
import { ScrollView, View } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import { FeatureBlock } from './feature';
import { ProTag } from './pro-tag';
import React from "react";
import { ScrollView, View } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { FeatureBlock } from "./feature";
import { ProTag } from "./pro-tag";
export const Group = ({ item, index }) => {
const colors = useThemeStore(state => state.colors);
const colors = useThemeStore((state) => state.colors);
return (
<View
@@ -19,7 +19,10 @@ export const Group = ({ item, index }) => {
}}
>
{item?.pro ? (
<ProTag size={SIZE.sm} background={index % 2 === 0 ? colors.bg : colors.nav} />
<ProTag
size={SIZE.sm}
background={index % 2 === 0 ? colors.bg : colors.nav}
/>
) : null}
<Heading>{item.title}</Heading>
<Paragraph size={SIZE.md}>{item.detail}</Paragraph>
@@ -32,7 +35,7 @@ export const Group = ({ item, index }) => {
horizontal
showsHorizontalScrollIndicator={false}
>
{item.features?.map(item => (
{item.features?.map((item) => (
<FeatureBlock
key={item.detail}
{...item}

View File

@@ -1,8 +1,11 @@
import React, { createRef } from 'react';
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { eClosePremiumDialog, eOpenPremiumDialog } from '../../utils/events';
import BaseDialog from '../dialog/base-dialog';
import { Component } from './component';
import React, { createRef } from "react";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { eClosePremiumDialog, eOpenPremiumDialog } from "../../utils/events";
import BaseDialog from "../dialog/base-dialog";
import { Component } from "./component";
class PremiumDialog extends React.Component {
constructor(props) {
@@ -24,7 +27,7 @@ class PremiumDialog extends React.Component {
eUnSubscribeEvent(eClosePremiumDialog, this.close);
}
open = promoInfo => {
open = (promoInfo) => {
this.setState({
visible: true,
promo: promoInfo
@@ -52,7 +55,11 @@ class PremiumDialog extends React.Component {
background={this.props.colors.bg}
onRequestClose={this.onClose}
>
<Component getRef={() => this.actionSheetRef} promo={this.state.promo} close={this.close} />
<Component
getRef={() => this.actionSheetRef}
promo={this.state.promo}
close={this.close}
/>
</BaseDialog>
);
}

View File

@@ -1,22 +1,26 @@
import React from 'react';
import { Text } from 'react-native';
import { useThemeStore } from '../../stores/use-theme-store';
import { SIZE } from '../../utils/size';
import Paragraph from '../ui/typography/paragraph';
import React from "react";
import { Text } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
export const Offer = ({ off = '30', text = 'on yearly plan, offer ends soon', padding = 0 }) => {
const colors = useThemeStore(state => state.colors);
export const Offer = ({
off = "30",
text = "on yearly plan, offer ends soon",
padding = 0
}) => {
const colors = useThemeStore((state) => state.colors);
return (
<Paragraph
style={{
textAlign: 'center',
textAlign: "center",
paddingVertical: padding
}}
size={SIZE.xxxl}
>
GET {off}
<Text style={{ color: colors.accent }}>%</Text> OFF!{'\n'}
<Text style={{ color: colors.accent }}>%</Text> OFF!{"\n"}
<Paragraph color={colors.icon}>{text}</Paragraph>
</Paragraph>
);

View File

@@ -1,24 +1,32 @@
import React, { 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 { DDS } from '../../services/device-detection';
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
import { useThemeStore } from '../../stores/use-theme-store';
import { getElevation } from '../../utils';
import { eCloseActionSheet, eOpenPremiumDialog, eShowGetPremium } from '../../utils/events';
import { SIZE } from '../../utils/size';
import { sleep } from '../../utils/time';
import { Button } from '../ui/button';
import Heading from '../ui/typography/heading';
import Paragraph from '../ui/typography/paragraph';
import React, { 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 { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { getElevation } from "../../utils";
import {
eCloseActionSheet,
eOpenPremiumDialog,
eShowGetPremium
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
const colors = useThemeStore(state => state.colors);
export const PremiumToast = ({ close, context = "global", offset = 0 }) => {
const colors = useThemeStore((state) => state.colors);
const [msg, setMsg] = useState(null);
const timer = useRef();
const open = event => {
const open = (event) => {
if (!event) {
clearTimeout(timer);
timer.current = null;
@@ -62,17 +70,17 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
entering={FadeInUp}
exiting={FadeOutUp}
style={{
position: 'absolute',
position: "absolute",
backgroundColor: colors.nav,
zIndex: 999,
...getElevation(20),
padding: 12,
borderRadius: 10,
flexDirection: 'row',
alignSelf: 'center',
justifyContent: 'space-between',
flexDirection: "row",
alignSelf: "center",
justifyContent: "space-between",
top: offset,
maxWidth: DDS.isLargeTablet() ? 400 : '98%'
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
}}
>
<View
@@ -84,7 +92,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
>
<Heading
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
color={colors.accent}
size={SIZE.md}
@@ -94,7 +102,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
<Paragraph
style={{
flexWrap: 'wrap'
flexWrap: "wrap"
}}
size={SIZE.sm}
color={colors.pri}

Some files were not shown because too many files have changed in this diff Show More