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

View File

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

View File

@@ -28,13 +28,13 @@ jobs:
- uses: actions/setup-node@master - uses: actions/setup-node@master
with: with:
node-version: '16' node-version: "16"
- name: Use specific Java version for the builds - name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2 uses: joschi/setup-jdk@v2
with: with:
java-version: '11' java-version: "11"
architecture: 'x64' architecture: "x64"
- name: Install node modules - name: Install node modules
run: | 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 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug Android", "name": "Debug Android",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
@@ -27,4 +25,4 @@
"request": "attach" "request": "attach"
} }
] ]
} }

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider } from "react-native-safe-area-context";
import { withErrorBoundry } from './components/exception-handler'; import { withErrorBoundry } from "./components/exception-handler";
import Launcher from './components/launcher'; import Launcher from "./components/launcher";
import { ApplicationHolder } from './navigation'; import { ApplicationHolder } from "./navigation";
import Notifications from './services/notifications'; import Notifications from "./services/notifications";
import SettingsService from './services/settings'; import SettingsService from "./services/settings";
import { TipManager } from './services/tip-manager'; import { TipManager } from "./services/tip-manager";
import { useUserStore } from './stores/use-user-store'; import { useUserStore } from "./stores/use-user-store";
import { useAppEvents } from './hooks/use-app-events'; import { useAppEvents } from "./hooks/use-app-events";
SettingsService.init(); SettingsService.init();
SettingsService.checkOrientation(); SettingsService.checkOrientation();
@@ -16,7 +16,7 @@ const App = () => {
useAppEvents(); useAppEvents();
useEffect(() => { useEffect(() => {
let { appLockMode } = SettingsService.get(); let { appLockMode } = SettingsService.get();
if (appLockMode && appLockMode !== 'none') { if (appLockMode && appLockMode !== "none") {
useUserStore.getState().setVerifyUser(true); useUserStore.getState().setVerifyUser(true);
} }
setTimeout(() => { 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 { Platform } from "react-native";
import { MMKV } from '../database/mmkv'; import { MMKV } from "../database/mmkv";
import { useSettingStore } from '../../stores/use-setting-store'; import { useSettingStore } from "../../stores/use-setting-store";
const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`; const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`;
const baseUrl = `https://analytics.streetwriters.co/api/collect`; const baseUrl = `https://analytics.streetwriters.co/api/collect`;
const UA = 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 (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`; 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 = []) { async function canUpdateAnalytics(route, conditions = []) {
if (!useSettingStore?.getState()?.settings?.telemetry) return false; if (!useSettingStore?.getState()?.settings?.telemetry) return false;
let eventsList = MMKV.getString('notesnookUserEvents'); let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) { if (eventsList) {
eventsList = JSON.parse(eventsList); eventsList = JSON.parse(eventsList);
} }
if (eventsList && eventsList[route]) { if (eventsList && eventsList[route]) {
console.log('analytics: event already sent', route); console.log("analytics: event already sent", route);
return false; return false;
} }
if (route !== '/welcome') { if (route !== "/welcome") {
for (let cond of conditions) { for (let cond of conditions) {
if (!eventsList || !eventsList[cond]) { 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; return false;
} }
} }
} }
console.log('analytics: will send event', route); console.log("analytics: will send event", route);
return true; return true;
} }
async function saveAnalytics(route, value = true) { async function saveAnalytics(route, value = true) {
let eventsList = MMKV.getString('notesnookUserEvents'); let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) { if (eventsList) {
eventsList = JSON.parse(eventsList); eventsList = JSON.parse(eventsList);
} else { } else {
eventsList = {}; eventsList = {};
} }
eventsList[route] = value; 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 * @returns
*/ */
async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = true) { async function pageView(
route,
prevRoute = "",
conditions = ["/welcome"],
once = true
) {
if (__DEV__) return; if (__DEV__) return;
if (!(await canUpdateAnalytics(route, conditions)) && once) return; if (!(await canUpdateAnalytics(route, conditions)) && once) return;
let body = { let body = {
@@ -70,22 +75,22 @@ async function pageView(route, prevRoute = '', conditions = ['/welcome'], once =
url: `notesnook-${Platform.OS}${prevRoute}${route}`, url: `notesnook-${Platform.OS}${prevRoute}${route}`,
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`, referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
hostname: `notesnook-${Platform.OS}`, hostname: `notesnook-${Platform.OS}`,
language: 'en-US', language: "en-US",
screen: '1920x1080' screen: "1920x1080"
}, },
type: 'pageview' type: "pageview"
}; };
try { try {
let response = await fetch(baseUrl, { let response = await fetch(baseUrl, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'User-Agent': UA "User-Agent": UA
}, },
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
console.log('analytics: event sent', route); console.log("analytics: event sent", route);
await saveAnalytics(route); await saveAnalytics(route);
return await response.text(); return await response.text();
} catch (e) { } catch (e) {
@@ -99,22 +104,22 @@ async function sendEvent(type, value, once = true) {
let body = { let body = {
payload: { payload: {
website: WEBSITE_ID, website: WEBSITE_ID,
url: '/', url: "/",
event_type: type, event_type: type,
event_value: value, event_value: value,
hostname: 'notesnook-android-app', hostname: "notesnook-android-app",
language: 'en-US', language: "en-US",
screen: '1920x1080' screen: "1920x1080"
}, },
type: 'event' type: "event"
}; };
try { try {
let response = await fetch(baseUrl, { let response = await fetch(baseUrl, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
'User-Agent': UA "User-Agent": UA
}, },
body: JSON.stringify(body) body: JSON.stringify(body)
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,36 +1,38 @@
import RNFetchBlob from 'rn-fetch-blob'; import RNFetchBlob from "rn-fetch-blob";
import { useAttachmentStore } from '../../stores/use-attachment-store'; import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from '../database'; import { db } from "../database";
import { cacheDir } from './utils'; import { cacheDir } from "./utils";
export async function uploadFile(filename, data, cancelToken) { export async function uploadFile(filename, data, cancelToken) {
if (!data) return false; if (!data) return false;
let { url, headers } = data; let { url, headers } = data;
console.log('uploading file: ', filename, headers); console.log("uploading file: ", filename, headers);
try { try {
let res = await fetch(url, { let res = await fetch(url, {
method: 'PUT', method: "PUT",
headers headers
}); });
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`); if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
const uploadUrl = await res.text(); 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({ let request = RNFetchBlob.config({
IOSBackgroundTask: true IOSBackgroundTask: true
}) })
.fetch( .fetch(
'PUT', "PUT",
uploadUrl, uploadUrl,
{ {
'content-type': '' "content-type": ""
}, },
RNFetchBlob.wrap(`${cacheDir}/${filename}`) RNFetchBlob.wrap(`${cacheDir}/${filename}`)
) )
.uploadProgress((sent, total) => { .uploadProgress((sent, total) => {
useAttachmentStore.getState().setProgress(sent, total, filename, 0, 'upload'); useAttachmentStore
console.log('uploading: ', sent, total); .getState()
.setProgress(sent, total, filename, 0, "upload");
console.log("uploading: ", sent, total);
}); });
cancelToken.cancel = request.cancel; cancelToken.cancel = request.cancel;
let response = await request; let response = await request;
@@ -42,7 +44,7 @@ export async function uploadFile(filename, data, cancelToken) {
if (result) { if (result) {
let attachment = db.attachments.attachment(filename); let attachment = db.attachments.attachment(filename);
if (!attachment) return result; if (!attachment) return result;
if (!attachment.metadata.type.startsWith('image/')) { if (!attachment.metadata.type.startsWith("image/")) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log); RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
} }
} }
@@ -50,7 +52,7 @@ export async function uploadFile(filename, data, cancelToken) {
return result; return result;
} catch (e) { } catch (e) {
useAttachmentStore.getState().remove(filename); useAttachmentStore.getState().remove(filename);
console.log('upload file: ', e, url, headers); console.log("upload file: ", e, url, headers);
return false; 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 const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
export function getRandomId(prefix) { export function getRandomId(prefix) {
return Math.random() return Math.random()
.toString(36) .toString(36)
.replace('0.', prefix || ''); .replace("0.", prefix || "");
} }
export function extractValueFromXmlTag(code, xml) { export function extractValueFromXmlTag(code, xml) {
if (!xml.includes(code)) return `Unknown ${code}`; 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) { export async function fileCheck(response, totalSize) {
if (totalSize < 1000) { if (totalSize < 1000) {
let text = await response.text(); let text = await response.text();
if (text.startsWith('<?xml')) { if (text.startsWith("<?xml")) {
let errorJson = { let errorJson = {
Code: extractValueFromXmlTag('Code', text), Code: extractValueFromXmlTag("Code", text),
Message: extractValueFromXmlTag('Message', text) Message: extractValueFromXmlTag("Message", text)
}; };
throw new Error(`${errorJson.Code}: ${errorJson.Message}`); 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[]) { function error(context: string, ...logs: any[]) {
console.log(`${new Date().toLocaleDateString()}::error::${context}: `, ...logs); console.log(
`${new Date().toLocaleDateString()}::error::${context}: `,
...logs
);
} }
type Logger = { type Logger = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,34 +1,42 @@
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from "react";
import { View } from 'react-native'; import { View } from "react-native";
import { ScrollView } from 'react-native-gesture-handler'; import { ScrollView } from "react-native-gesture-handler";
import picker from '../../screens/editor/tiptap/picker'; import picker from "../../screens/editor/tiptap/picker";
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager'; import {
import PremiumService from '../../services/premium'; eSendEvent,
import { useAttachmentStore } from '../../stores/use-attachment-store'; presentSheet,
import { useThemeStore } from '../../stores/use-theme-store'; ToastEvent
import { formatBytes } from '../../utils'; } from "../../services/event-manager";
import { db } from '../../common/database'; import PremiumService from "../../services/premium";
import { eCloseAttachmentDialog, eCloseProgressDialog } from '../../utils/events'; import { useAttachmentStore } from "../../stores/use-attachment-store";
import filesystem from '../../common/filesystem'; import { useThemeStore } from "../../stores/use-theme-store";
import { useAttachmentProgress } from '../../hooks/use-attachment-progress'; import { formatBytes } from "../../utils";
import { SIZE } from '../../utils/size'; import { db } from "../../common/database";
import { sleep } from '../../utils/time'; import {
import { Dialog } from '../dialog'; eCloseAttachmentDialog,
import { presentDialog } from '../dialog/functions'; eCloseProgressDialog
import { openNote } from '../list-items/note/wrapper'; } from "../../utils/events";
import { DateMeta } from '../properties/date-meta'; import filesystem from "../../common/filesystem";
import { Button } from '../ui/button'; import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { Notice } from '../ui/notice'; import { SIZE } from "../../utils/size";
import { PressableButton } from '../ui/pressable'; import { sleep } from "../../utils/time";
import Heading from '../ui/typography/heading'; import { Dialog } from "../dialog";
import Paragraph from '../ui/typography/paragraph'; 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 Actions = ({ attachment, setAttachments, fwdRef }) => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
const contextId = attachment.metadata.hash; const contextId = attachment.metadata.hash;
const [filename, setFilename] = useState(attachment.metadata.filename); const [filename, setFilename] = useState(attachment.metadata.filename);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment); const [currentProgress, setCurrentProgress] =
useAttachmentProgress(attachment);
const [failed, setFailed] = useState(attachment.failed); const [failed, setFailed] = useState(attachment.failed);
const [notes, setNotes] = useState([]); const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState({ const [loading, setLoading] = useState({
@@ -37,25 +45,25 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
const actions = [ const actions = [
{ {
name: 'Download', name: "Download",
onPress: async () => { onPress: async () => {
if (currentProgress) { if (currentProgress) {
await db.fs.cancel(attachment.metadata.hash, 'download'); await db.fs.cancel(attachment.metadata.hash, "download");
useAttachmentStore.getState().remove(attachment.metadata.hash); useAttachmentStore.getState().remove(attachment.metadata.hash);
} }
filesystem.downloadAttachment(attachment.metadata.hash, false); filesystem.downloadAttachment(attachment.metadata.hash, false);
eSendEvent(eCloseProgressDialog, contextId); eSendEvent(eCloseProgressDialog, contextId);
}, },
icon: 'download' icon: "download"
}, },
{ {
name: 'Reupload', name: "Reupload",
onPress: async () => { onPress: async () => {
if (!PremiumService.get()) { if (!PremiumService.get()) {
ToastEvent.show({ ToastEvent.show({
heading: 'Upgrade to pro', heading: "Upgrade to pro",
type: 'error', type: "error",
context: 'local' context: "local"
}); });
return; return;
} }
@@ -66,13 +74,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
type: attachment.metadata.type type: attachment.metadata.type
}); });
}, },
icon: 'upload' icon: "upload"
}, },
{ {
name: 'Run file check', name: "Run file check",
onPress: async () => { onPress: async () => {
setLoading({ setLoading({
name: 'Run file check' name: "Run file check"
}); });
let res = await filesystem.checkAttachment(attachment.metadata.hash); let res = await filesystem.checkAttachment(attachment.metadata.hash);
if (res.failed) { if (res.failed) {
@@ -83,27 +91,27 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
db.attachments.markAsFailed(attachment.id, null); db.attachments.markAsFailed(attachment.id, null);
} }
ToastEvent.show({ ToastEvent.show({
heading: 'File check passed', heading: "File check passed",
type: 'success', type: "success",
context: 'local' context: "local"
}); });
setAttachments([...db.attachments.all]); setAttachments([...db.attachments.all]);
setLoading({ setLoading({
name: null name: null
}); });
}, },
icon: 'file-check' icon: "file-check"
}, },
{ {
name: 'Rename', name: "Rename",
onPress: () => { onPress: () => {
presentDialog({ presentDialog({
context: contextId, context: contextId,
input: true, input: true,
title: 'Rename file', title: "Rename file",
paragraph: 'Enter a new name for the file', paragraph: "Enter a new name for the file",
defaultValue: attachment.metadata.filename, defaultValue: attachment.metadata.filename,
positivePress: async value => { positivePress: async (value) => {
if (value && value.length > 0) { if (value && value.length > 0) {
await db.attachments.add({ await db.attachments.add({
hash: attachment.metadata.hash, hash: attachment.metadata.hash,
@@ -113,31 +121,31 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
setAttachments([...db.attachments.all]); setAttachments([...db.attachments.all]);
} }
}, },
positiveText: 'Rename' positiveText: "Rename"
}); });
}, },
icon: 'form-textbox' icon: "form-textbox"
}, },
{ {
name: 'Delete', name: "Delete",
onPress: async () => { onPress: async () => {
await db.attachments.remove(attachment.metadata.hash, false); await db.attachments.remove(attachment.metadata.hash, false);
setAttachments([...db.attachments.all]); setAttachments([...db.attachments.all]);
eSendEvent(eCloseProgressDialog, contextId); eSendEvent(eCloseProgressDialog, contextId);
}, },
icon: 'delete-outline' icon: "delete-outline"
} }
]; ];
const getNotes = () => { const getNotes = () => {
let allNotes = db.notes.all; let allNotes = db.notes.all;
let attachmentNotes = attachment.noteIds?.map(id => { let attachmentNotes = attachment.noteIds?.map((id) => {
let index = allNotes?.findIndex(note => id === note.id); let index = allNotes?.findIndex((note) => id === note.id);
if (index !== -1) { if (index !== -1) {
return allNotes[index]; return allNotes[index];
} else { } else {
return { return {
type: 'notfound', type: "notfound",
title: `Note with id ${id} does not exist.`, title: `Note with id ${id} does not exist.`,
id: id id: id
}; };
@@ -157,7 +165,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}} }}
nestedScrollEnabled={true} nestedScrollEnabled={true}
style={{ style={{
maxHeight: '100%' maxHeight: "100%"
}} }}
> >
<Dialog context={contextId} /> <Dialog context={contextId} />
@@ -179,7 +187,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: "row",
marginBottom: 10, marginBottom: 10,
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
@@ -210,15 +218,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
size={SIZE.xs + 1} size={SIZE.xs + 1}
color={colors.icon} color={colors.icon}
> >
{attachment.noteIds.length} note{attachment.noteIds.length > 1 ? 's' : ''} {attachment.noteIds.length} note
{attachment.noteIds.length > 1 ? "s" : ""}
</Paragraph> </Paragraph>
<Paragraph <Paragraph
onPress={() => { onPress={() => {
Clipboard.setString(attachment.metadata.hash); Clipboard.setString(attachment.metadata.hash);
ToastEvent.show({ ToastEvent.show({
type: 'success', type: "success",
heading: 'Attachment hash copied', heading: "Attachment hash copied",
context: 'local' context: "local"
}); });
}} }}
size={SIZE.xs + 1} size={SIZE.xs + 1}
@@ -250,16 +259,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
List of notes: List of notes:
</Heading> </Heading>
{notes.map(item => ( {notes.map((item) => (
<PressableButton <PressableButton
onPress={async () => { onPress={async () => {
if (item.type === 'notfound') { if (item.type === "notfound") {
ToastEvent.show({ ToastEvent.show({
heading: 'Note not found', heading: "Note not found",
message: message:
'A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.', "A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
type: 'error', type: "error",
context: 'local' context: "local"
}); });
return; return;
} }
@@ -267,11 +276,11 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
await sleep(150); await sleep(150);
eSendEvent(eCloseAttachmentDialog); eSendEvent(eCloseAttachmentDialog);
await sleep(300); await sleep(300);
openNote(item, item.type === 'trash'); openNote(item, item.type === "trash");
}} }}
customStyle={{ customStyle={{
paddingVertical: 12, paddingVertical: 12,
alignItems: 'flex-start', alignItems: "flex-start",
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
@@ -284,13 +293,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
</View> </View>
) : null} ) : null}
{actions.map(item => ( {actions.map((item) => (
<Button <Button
key={item.name} key={item.name}
buttonType={{ buttonType={{
text: item.on text: item.on
? colors.accent ? colors.accent
: item.name === 'Delete' || item.name === 'PermDelete' : item.name === "Delete" || item.name === "PermDelete"
? colors.errorText ? colors.errorText
: colors.pri : colors.pri
}} }}
@@ -298,13 +307,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
title={item.name} title={item.name}
icon={item.icon} icon={item.icon}
loading={loading?.name === item.name} loading={loading?.name === item.name}
type={item.on ? 'shade' : 'gray'} type={item.on ? "shade" : "gray"}
fontSize={SIZE.sm} fontSize={SIZE.sm}
style={{ style={{
borderRadius: 0, borderRadius: 0,
justifyContent: 'flex-start', justifyContent: "flex-start",
alignSelf: 'flex-start', alignSelf: "flex-start",
width: '100%' width: "100%"
}} }}
/> />
))} ))}
@@ -329,7 +338,9 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
Actions.present = (attachment, set, context) => { Actions.present = (attachment, set, context) => {
presentSheet({ presentSheet({
context: context, 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 React from "react";
import { TouchableOpacity, View } from 'react-native'; import { TouchableOpacity, View } from "react-native";
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useAttachmentStore } from '../../stores/use-attachment-store'; import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from '../../stores/use-theme-store'; import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from '../../utils'; import { formatBytes } from "../../utils";
import { db } from '../../common/database'; import { db } from "../../common/database";
import { useAttachmentProgress } from '../../hooks/use-attachment-progress'; import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { SIZE } from '../../utils/size'; import { SIZE } from "../../utils/size";
import SheetProvider from '../sheet-provider'; import SheetProvider from "../sheet-provider";
import { IconButton } from '../ui/icon-button'; import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from '../ui/svg/lazy'; import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from '../ui/typography/paragraph'; import Paragraph from "../ui/typography/paragraph";
import Actions from './actions'; import Actions from "./actions";
function getFileExtension(filename) { function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename); var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? '' : ext[1]; return ext == null ? "" : ext[1];
} }
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => { export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment, encryption); const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const encryptionProgress = encryption const encryptionProgress = encryption
? useAttachmentStore(state => state.encryptionProgress) ? useAttachmentStore((state) => state.encryptionProgress)
: null; : null;
const onPress = () => { const onPress = () => {
@@ -33,9 +36,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
activeOpacity={0.9} activeOpacity={0.9}
onPress={onPress} onPress={onPress}
style={{ style={{
flexDirection: 'row', flexDirection: "row",
marginVertical: 5, marginVertical: 5,
justifyContent: 'space-between', justifyContent: "space-between",
padding: 12, padding: 12,
paddingVertical: 6, paddingVertical: 6,
borderRadius: 5, borderRadius: 5,
@@ -47,14 +50,14 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
<View <View
style={{ style={{
flexShrink: 1, flexShrink: 1,
flexDirection: 'row', flexDirection: "row",
alignItems: 'center' alignItems: "center"
}} }}
> >
<View <View
style={{ style={{
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
marginLeft: -5 marginLeft: -5
}} }}
> >
@@ -65,7 +68,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
size={6} size={6}
color={colors.light} color={colors.light}
style={{ style={{
position: 'absolute' position: "absolute"
}} }}
> >
{getFileExtension(attachment.metadata.filename).toUpperCase()} {getFileExtension(attachment.metadata.filename).toUpperCase()}
@@ -81,7 +84,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
<Paragraph <Paragraph
size={SIZE.sm - 1} size={SIZE.sm - 1}
style={{ style={{
flexWrap: 'wrap', flexWrap: "wrap",
marginBottom: 2.5 marginBottom: 2.5
}} }}
numberOfLines={1} numberOfLines={1}
@@ -92,8 +95,10 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
</Paragraph> </Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}> <Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{' '} {formatBytes(attachment.length)}{" "}
{currentProgress?.type ? '(' + currentProgress.type + 'ing - tap to cancel)' : ''} {currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph> </Paragraph>
</View> </View>
</View> </View>
@@ -107,7 +112,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
setCurrentProgress(null); setCurrentProgress(null);
}} }}
style={{ style={{
justifyContent: 'center', justifyContent: "center",
marginLeft: 5, marginLeft: 5,
marginTop: 5, marginTop: 5,
marginRight: -5 marginRight: -5
@@ -127,7 +132,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
fontSize: 10 fontSize: 10
}} }}
color={colors.accent} color={colors.accent}
formatText={progress => (progress * 100).toFixed(0)} formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0} borderWidth={0}
thickness={2} thickness={2}
/> />
@@ -135,7 +140,11 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
) : ( ) : (
<> <>
{attachment.failed ? ( {attachment.failed ? (
<IconButton onPress={onPress} name="alert-circle-outline" color={colors.errorText} /> <IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.errorText}
/>
) : null} ) : null}
</> </>
)} )}

View File

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

View File

@@ -1,18 +1,21 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from "react";
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager'; import {
import { useThemeStore } from '../../stores/use-theme-store'; eSubscribeEvent,
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events'; eUnSubscribeEvent
import { SIZE } from '../../utils/size'; } from "../../services/event-manager";
import { sleep } from '../../utils/time'; import { useThemeStore } from "../../stores/use-theme-store";
import BaseDialog from '../dialog/base-dialog'; import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
import { Toast } from '../toast'; import { SIZE } from "../../utils/size";
import { Button } from '../ui/button'; import { sleep } from "../../utils/time";
import { IconButton } from '../ui/icon-button'; import BaseDialog from "../dialog/base-dialog";
import { hideAuth, initialAuthMode } from './common'; import { Toast } from "../toast";
import { Login } from './login'; import { Button } from "../ui/button";
import { Signup } from './signup'; import { IconButton } from "../ui/icon-button";
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { hideAuth, initialAuthMode } from "./common";
import { Platform, View } from 'react-native'; 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 = { export const AuthMode = {
login: 0, login: 0,
@@ -22,7 +25,7 @@ export const AuthMode = {
}; };
const AuthModal = () => { const AuthModal = () => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login); const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
const actionSheetRef = useRef(); const actionSheetRef = useRef();
@@ -66,29 +69,31 @@ const AuthModal = () => {
> >
{currentAuthMode !== AuthMode.login ? ( {currentAuthMode !== AuthMode.login ? (
<Signup <Signup
changeMode={mode => setCurrentAuthMode(mode)} changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode} trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup} welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/> />
) : ( ) : (
<Login <Login
welcome={initialAuthMode.current === AuthMode.welcomeSignup} welcome={initialAuthMode.current === AuthMode.welcomeSignup}
changeMode={mode => setCurrentAuthMode(mode)} changeMode={(mode) => setCurrentAuthMode(mode)}
/> />
)} )}
<View <View
style={{ style={{
position: 'absolute', position: "absolute",
top: Platform.OS === 'ios' ? insets.top : 0, top: Platform.OS === "ios" ? insets.top : 0,
zIndex: 999, zIndex: 999,
flexDirection: 'row', flexDirection: "row",
alignItems: 'center', alignItems: "center",
paddingHorizontal: 12, paddingHorizontal: 12,
width: '100%', width: "100%",
height: 50, height: 50,
justifyContent: justifyContent:
initialAuthMode.current !== AuthMode.welcomeSignup ? 'space-between' : 'flex-end' initialAuthMode.current !== AuthMode.welcomeSignup
? "space-between"
: "flex-end"
}} }}
> >
{initialAuthMode.current === AuthMode.welcomeSignup ? null : ( {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>`; `<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 React, { useRef, useState } from "react";
import { View } from 'react-native'; import { View } from "react-native";
import { useThemeStore } from '../../stores/use-theme-store'; import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from '../../stores/use-user-store'; import { useUserStore } from "../../stores/use-user-store";
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager'; import {
import { db } from '../../common/database'; eSendEvent,
import { eCloseProgressDialog } from '../../utils/events'; presentSheet,
import { Button } from '../ui/button'; ToastEvent
import DialogHeader from '../dialog/dialog-header'; } from "../../services/event-manager";
import Input from '../ui/input'; import { db } from "../../common/database";
import { Notice } from '../ui/notice'; import { eCloseProgressDialog } from "../../utils/events";
import Seperator from '../ui/seperator'; 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 = () => { export const ChangePassword = () => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
const passwordInputRef = useRef(); const passwordInputRef = useRef();
const password = useRef(); const password = useRef();
const oldPasswordInputRef = useRef(); const oldPasswordInputRef = useRef();
@@ -20,24 +24,24 @@ export const ChangePassword = () => {
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const user = useUserStore(state => state.user); const user = useUserStore((state) => state.user);
const changePassword = async () => { const changePassword = async () => {
if (!user?.isEmailConfirmed) { if (!user?.isEmailConfirmed) {
ToastEvent.show({ ToastEvent.show({
heading: 'Email not confirmed', heading: "Email not confirmed",
message: 'Please confirm your email to change account password', message: "Please confirm your email to change account password",
type: 'error', type: "error",
context: 'local' context: "local"
}); });
return; return;
} }
if (error || !oldPassword.current || !password.current) { if (error || !oldPassword.current || !password.current) {
ToastEvent.show({ ToastEvent.show({
heading: 'All fields required', heading: "All fields required",
message: 'Fill all the fields and try again.', message: "Fill all the fields and try again.",
type: 'error', type: "error",
context: 'local' context: "local"
}); });
return; return;
} }
@@ -47,18 +51,18 @@ export const ChangePassword = () => {
await db.user.changePassword(oldPassword.current, password.current); await db.user.changePassword(oldPassword.current, password.current);
ToastEvent.show({ ToastEvent.show({
heading: `Account password updated`, heading: `Account password updated`,
type: 'success', type: "success",
context: 'global' context: "global"
}); });
setLoading(false); setLoading(false);
eSendEvent(eCloseProgressDialog); eSendEvent(eCloseProgressDialog);
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
ToastEvent.show({ ToastEvent.show({
heading: 'Failed to change password', heading: "Failed to change password",
message: e.message, message: e.message,
type: 'error', type: "error",
context: 'local' context: "local"
}); });
} }
setLoading(false); setLoading(false);
@@ -67,16 +71,19 @@ export const ChangePassword = () => {
return ( return (
<View <View
style={{ style={{
width: '100%', width: "100%",
padding: 12 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 /> <Seperator />
<Input <Input
fwdRef={oldPasswordInputRef} fwdRef={oldPasswordInputRef}
onChangeText={value => { onChangeText={(value) => {
oldPassword.current = value; oldPassword.current = value;
}} }}
returnKeyLabel="Next" returnKeyLabel="Next"
@@ -90,10 +97,10 @@ export const ChangePassword = () => {
<Input <Input
fwdRef={passwordInputRef} fwdRef={passwordInputRef}
onChangeText={value => { onChangeText={(value) => {
password.current = value; password.current = value;
}} }}
onErrorCheck={e => setError(e)} onErrorCheck={(e) => setError(e)}
returnKeyLabel="Next" returnKeyLabel="Next"
returnKeyType="next" returnKeyType="next"
secureTextEntry secureTextEntry
@@ -112,12 +119,12 @@ export const ChangePassword = () => {
<Button <Button
style={{ style={{
marginTop: 10, marginTop: 10,
width: '100%' width: "100%"
}} }}
loading={loading} loading={loading}
onPress={changePassword} onPress={changePassword}
type="accent" type="accent"
title={loading ? null : 'I understand, change my password'} title={loading ? null : "I understand, change my password"}
/> />
</View> </View>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { import {
KeyboardAvoidingView, KeyboardAvoidingView,
Modal, Modal,
@@ -7,17 +7,17 @@ import {
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View View
} from 'react-native'; } from "react-native";
import { useSettingStore } from '../../stores/use-setting-store'; import { useSettingStore } from "../../stores/use-setting-store";
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard'; import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { BouncingView } from '../ui/transitions/bouncing-view'; import { BouncingView } from "../ui/transitions/bouncing-view";
const BaseDialog = ({ const BaseDialog = ({
visible, visible,
onRequestClose, onRequestClose,
children, children,
onShow, onShow,
animation = 'fade', animation = "fade",
premium, premium,
statusBarTranslucent = true, statusBarTranslucent = true,
transparent, transparent,
@@ -46,11 +46,11 @@ const BaseDialog = ({
animated animated
statusBarTranslucent={statusBarTranslucent} statusBarTranslucent={statusBarTranslucent}
supportedOrientations={[ supportedOrientations={[
'portrait', "portrait",
'portrait-upside-down', "portrait-upside-down",
'landscape', "landscape",
'landscape-left', "landscape-left",
'landscape-right' "landscape-right"
]} ]}
onShow={() => { onShow={() => {
if (onShow) { if (onShow) {
@@ -67,10 +67,17 @@ const BaseDialog = ({
> >
<Wrapper <Wrapper
style={{ 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 <BouncingView
duration={400} duration={400}
animated={animated} animated={animated}
@@ -78,7 +85,11 @@ const BaseDialog = ({
style={[ style={[
styles.backdrop, 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({ const styles = StyleSheet.create({
backdrop: { backdrop: {
width: '100%', width: "100%",
height: '100%', height: "100%",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center' alignItems: "center"
}, },
overlayButton: { overlayButton: {
width: '100%', width: "100%",
height: '100%', height: "100%",
position: 'absolute' position: "absolute"
} }
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,58 +1,63 @@
import React from 'react'; import React from "react";
import { View } from 'react-native'; import { View } from "react-native";
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeStore } from '../../../stores/use-theme-store'; import { useThemeStore } from "../../../stores/use-theme-store";
import { eSendEvent } from '../../../services/event-manager'; import { eSendEvent } from "../../../services/event-manager";
import { import {
eCloseProgressDialog, eCloseProgressDialog,
eCloseResultDialog, eCloseResultDialog,
eOpenPremiumDialog eOpenPremiumDialog
} from '../../../utils/events'; } from "../../../utils/events";
import { SIZE } from '../../../utils/size'; import { SIZE } from "../../../utils/size";
import { sleep } from '../../../utils/time'; import { sleep } from "../../../utils/time";
import Paragraph from '../../ui/typography/paragraph'; import Paragraph from "../../ui/typography/paragraph";
export const ProFeatures = ({ count = 6 }) => { export const ProFeatures = ({ count = 6 }) => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
return ( 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) .slice(0, count)
.map(item => ( .map((item) => (
<View <View
key={item.content} key={item.content}
style={{ style={{
flexDirection: 'row', flexDirection: "row",
width: '100%', width: "100%",
height: 40, height: 40,
paddingHorizontal: 0, paddingHorizontal: 0,
marginBottom: 10, marginBottom: 10,
alignItems: 'center', alignItems: "center",
borderRadius: 5, borderRadius: 5,
justifyContent: 'flex-start' justifyContent: "flex-start"
}} }}
> >
<Icon size={SIZE.lg} color={colors.accent} name="check" /> <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> </View>
))} ))}
<Paragraph <Paragraph
@@ -64,7 +69,7 @@ export const ProFeatures = ({ count = 6 }) => {
}} }}
size={SIZE.xs + 1} size={SIZE.xs + 1}
style={{ style={{
textDecorationLine: 'underline', textDecorationLine: "underline",
color: colors.icon color: colors.icon
}} }}
> >

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,27 @@
import React from 'react'; import React from "react";
import { View } from 'react-native'; import { View } from "react-native";
import { useThemeStore } from '../../../stores/use-theme-store'; import { useThemeStore } from "../../../stores/use-theme-store";
import { useMessageStore } from '../../../stores/use-message-store'; import { useMessageStore } from "../../../stores/use-message-store";
import { COLORS_NOTE } from '../../../utils/color-scheme'; import { COLORS_NOTE } from "../../../utils/color-scheme";
import { Announcement } from '../../announcements/announcement'; import { Announcement } from "../../announcements/announcement";
import { Card } from '../../list/card'; import { Card } from "../../list/card";
import Paragraph from '../../ui/typography/paragraph'; import Paragraph from "../../ui/typography/paragraph";
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SIZE } from '../../../utils/size'; import { SIZE } from "../../../utils/size";
import { useSelectionStore } from '../../../stores/use-selection-store'; import { useSelectionStore } from "../../../stores/use-selection-store";
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated'; import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
export const Header = React.memo( export const Header = React.memo(
({ type, messageCard = true, color, shouldShow = false, noAnnouncement, warning }) => { ({
const colors = useThemeStore(state => state.colors); type,
const announcements = useMessageStore(state => state.announcements); messageCard = true,
const selectionMode = useSelectionStore(state => state.selectionMode); 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 : ( return selectionMode ? null : (
<> <>
@@ -23,11 +30,11 @@ export const Header = React.memo(
style={{ style={{
padding: 12, padding: 12,
backgroundColor: colors.errorBg, backgroundColor: colors.errorBg,
width: '95%', width: "95%",
alignSelf: 'center', alignSelf: "center",
borderRadius: 5, borderRadius: 5,
flexDirection: 'row', flexDirection: "row",
alignItems: 'center' alignItems: "center"
}} }}
> >
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f /> <Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
@@ -37,18 +44,20 @@ export const Header = React.memo(
</View> </View>
) : announcements.length !== 0 && !noAnnouncement ? ( ) : announcements.length !== 0 && !noAnnouncement ? (
<Announcement color={color || colors.accent} /> <Announcement color={color || colors.accent} />
) : type === 'search' ? null : !shouldShow ? ( ) : type === "search" ? null : !shouldShow ? (
<View <View
style={{ style={{
marginBottom: 5, marginBottom: 5,
padding: 0, padding: 0,
width: '100%', width: "100%",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center' alignItems: "center"
}} }}
> >
{messageCard ? ( {messageCard ? (
<Card color={COLORS_NOTE[color?.toLowerCase()] || colors.accent} /> <Card
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
/>
) : null} ) : null}
</View> </View>
) : null} ) : 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 React, { useRef, useState } from "react";
import { View } from 'react-native'; import { View } from "react-native";
import { useThemeStore } from '../../../stores/use-theme-store'; import { useThemeStore } from "../../../stores/use-theme-store";
import { useMenuStore } from '../../../stores/use-menu-store'; import { useMenuStore } from "../../../stores/use-menu-store";
import { ToastEvent } from '../../../services/event-manager'; import { ToastEvent } from "../../../services/event-manager";
import { getTotalNotes } from '../../../utils'; import { getTotalNotes } from "../../../utils";
import { db } from '../../../common/database'; import { db } from "../../../common/database";
import { SIZE } from '../../../utils/size'; import { SIZE } from "../../../utils/size";
import { IconButton } from '../../ui/icon-button'; import { IconButton } from "../../ui/icon-button";
import Heading from '../../ui/typography/heading'; import Heading from "../../ui/typography/heading";
import Paragraph from '../../ui/typography/paragraph'; import Paragraph from "../../ui/typography/paragraph";
export const NotebookHeader = ({ notebook, onEditNotebook }) => { export const NotebookHeader = ({ notebook, onEditNotebook }) => {
const colors = useThemeStore(state => state.colors); const colors = useThemeStore((state) => state.colors);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(db.settings.isPinned(notebook.id)); const [isPinnedToMenu, setIsPinnedToMenu] = useState(
const setMenuPins = useMenuStore(state => state.setMenuPins); db.settings.isPinned(notebook.id)
);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const totalNotes = getTotalNotes(notebook); const totalNotes = getTotalNotes(notebook);
const shortcutRef = useRef(); const shortcutRef = useRef();
@@ -24,8 +26,8 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
} else { } else {
await db.settings.pin(notebook.type, { id: notebook.id }); await db.settings.pin(notebook.type, { id: notebook.id });
ToastEvent.show({ ToastEvent.show({
heading: 'Shortcut created', heading: "Shortcut created",
type: 'success' type: "success"
}); });
} }
setIsPinnedToMenu(db.settings.isPinned(notebook.id)); setIsPinnedToMenu(db.settings.isPinned(notebook.id));
@@ -38,10 +40,10 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
style={{ style={{
marginBottom: 5, marginBottom: 5,
padding: 0, padding: 0,
width: '100%', width: "100%",
paddingVertical: 15, paddingVertical: 15,
paddingHorizontal: 12, paddingHorizontal: 12,
alignSelf: 'center', alignSelf: "center",
borderRadius: 10, borderRadius: 10,
paddingTop: 25 paddingTop: 25
}} }}
@@ -51,29 +53,29 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
</Paragraph> </Paragraph>
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: "row",
justifyContent: 'space-between', justifyContent: "space-between",
alignItems: 'center' alignItems: "center"
}} }}
> >
<Heading size={SIZE.xxl}>{notebook.title}</Heading> <Heading size={SIZE.xxl}>{notebook.title}</Heading>
<View <View
style={{ style={{
flexDirection: 'row' flexDirection: "row"
}} }}
> >
<IconButton <IconButton
name={isPinnedToMenu ? 'link-variant-off' : 'link-variant'} name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
onPress={onPinNotebook} onPress={onPinNotebook}
tooltipText={'Create shortcut in side menu'} tooltipText={"Create shortcut in side menu"}
fwdRef={shortcutRef} fwdRef={shortcutRef}
customStyle={{ customStyle={{
marginRight: 15, marginRight: 15,
width: 40, width: 40,
height: 40 height: 40
}} }}
type={isPinnedToMenu ? 'grayBg' : 'grayBg'} type={isPinnedToMenu ? "grayBg" : "grayBg"}
color={isPinnedToMenu ? colors.accent : colors.icon} color={isPinnedToMenu ? colors.accent : colors.icon}
size={SIZE.lg} size={SIZE.lg}
/> />
@@ -101,18 +103,21 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
<Paragraph <Paragraph
style={{ style={{
marginTop: 10, marginTop: 10,
fontStyle: 'italic', fontStyle: "italic",
fontFamily: null fontFamily: null
}} }}
size={SIZE.xs} size={SIZE.xs}
color={colors.icon} 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 {notebook && totalNotes > 1
? totalNotes + ' notes' ? totalNotes + " notes"
: totalNotes === 1 : totalNotes === 1
? totalNotes + ' note' ? totalNotes + " note"
: '0 notes'} : "0 notes"}
</Paragraph> </Paragraph>
</View> </View>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,224 +1,227 @@
export const features = [ export const features = [
{ {
title: 'Focused on privacy', title: "Focused on privacy",
detail: 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: [ features: [
{ {
highlight: 'Zero ads', highlight: "Zero ads",
content: '& zero trackers', content: "& zero trackers",
icon: 'billboard' icon: "billboard"
}, },
{ {
highlight: 'On device', highlight: "On device",
content: 'encryption', content: "encryption",
icon: 'cellphone' icon: "cellphone"
}, },
{ {
highlight: 'Secure app', highlight: "Secure app",
content: 'lock for all', content: "lock for all",
icon: 'cellphone-lock' icon: "cellphone-lock"
}, },
{ {
highlight: '100% end-to-end ', highlight: "100% end-to-end ",
content: 'encrypted', content: "encrypted",
icon: 'lock' icon: "lock"
}, },
{ {
highlight: 'Password protected', highlight: "Password protected",
content: 'notes sharing', content: "notes sharing",
icon: 'file-lock' icon: "file-lock"
} }
] ]
}, },
{ {
title: 'No limit on notes or devices', title: "No limit on notes or devices",
detail: 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." "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', title: "Attach files & images",
detail: 'Add your documents, PDFs, images and videos, and keep them safe and organized.', detail:
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
pro: true, pro: true,
features: [ features: [
{ {
highlight: 'Bullet proof', highlight: "Bullet proof",
content: 'encryption', content: "encryption",
icon: 'lock' icon: "lock"
}, },
{ {
highlight: 'High quality', highlight: "High quality",
content: '4k images', content: "4k images",
icon: 'image-multiple' icon: "image-multiple"
}, },
{ {
highlight: 'No monthly', highlight: "No monthly",
content: 'storage limit', content: "storage limit",
icon: 'harddisk' icon: "harddisk"
}, },
{ {
highlight: 'Generous 500 MB', highlight: "Generous 500 MB",
content: 'max file size', content: "max file size",
icon: 'file-cabinet' icon: "file-cabinet"
}, },
{ {
highlight: 'No restriction', highlight: "No restriction",
content: 'on file type', content: "on file type",
icon: 'file' icon: "file"
} }
] ]
}, },
{ {
title: 'Keep secrets always locked with private vault', title: "Keep secrets always locked with private vault",
detail: 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 pro: true
}, },
{ {
title: 'Organize yourself in the best way', title: "Organize yourself in the best way",
detail: 'We offer multiple ways to keep you organized. The only limit is your imagination.', detail:
"We offer multiple ways to keep you organized. The only limit is your imagination.",
features: [ features: [
{ {
highlight: 'Unlimited', highlight: "Unlimited",
content: 'notebooks & tags*', content: "notebooks & tags*",
icon: 'emoticon', icon: "emoticon",
pro: true pro: true
}, },
{ {
highlight: 'Organize', highlight: "Organize",
content: 'with colors', content: "with colors",
icon: 'palette', icon: "palette",
pro: true pro: true
}, },
{ {
highlight: 'Side menu', highlight: "Side menu",
content: 'shortcuts', content: "shortcuts",
icon: 'link-variant' icon: "link-variant"
}, },
{ {
highlight: 'Pin note in', highlight: "Pin note in",
content: 'notifications', content: "notifications",
icon: 'pin', icon: "pin",
platform: 'android' 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: 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 pro: true
}, },
{ {
title: 'Rich tools for rich editing', title: "Rich tools for rich editing",
detail: 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: [ features: [
{ {
highlight: 'Basic formating', highlight: "Basic formating",
content: 'and lists', content: "and lists",
icon: 'format-bold' icon: "format-bold"
}, },
{ {
highlight: 'Checklists', highlight: "Checklists",
content: '& tables', content: "& tables",
icon: 'table', icon: "table",
pro: true pro: true
}, },
{ {
highlight: 'Markdown', highlight: "Markdown",
content: 'support', content: "support",
icon: 'language-markdown', icon: "language-markdown",
pro: true pro: true
}, },
{ {
highlight: 'Write notes from', highlight: "Write notes from",
content: 'notifications', content: "notifications",
icon: 'bell', icon: "bell",
platform: 'android' platform: "android"
} }
] ]
}, },
{ {
title: 'Safe publishing to the Internet', title: "Safe publishing to the Internet",
detail: 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: [ features: [
{ {
highlight: 'Password protected', highlight: "Password protected",
content: 'sharing', content: "sharing",
icon: 'send-lock' icon: "send-lock"
}, },
{ {
highlight: 'Self destruct', highlight: "Self destruct",
content: 'monographs', content: "monographs",
icon: 'bomb' icon: "bomb"
} }
] ]
}, },
{ {
title: 'Export and take your notes anywhere', title: "Export and take your notes anywhere",
pro: true, pro: true,
detail: detail:
'You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.', "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.', info: "* Free users can export notes in well formatted plain text.",
features: [ features: [
{ {
highlight: 'Export as ', highlight: "Export as ",
content: 'Markdown', content: "Markdown",
icon: 'language-markdown', icon: "language-markdown",
pro: true pro: true
}, },
{ {
highlight: 'Export as', highlight: "Export as",
content: 'PDF', content: "PDF",
icon: 'file-pdf-box', icon: "file-pdf-box",
pro: true pro: true
}, },
{ {
highlight: 'Export as', highlight: "Export as",
content: 'HTML', content: "HTML",
icon: 'language-html5', icon: "language-html5",
pro: true pro: true
}, },
{ {
highlight: 'Export as', highlight: "Export as",
content: 'text', content: "text",
icon: 'clipboard-text-outline' icon: "clipboard-text-outline"
} }
] ]
}, },
{ {
title: 'Backup & keep your notes safe', title: "Backup & keep your notes safe",
detail: 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: [ features: [
{ {
highlight: 'Backup', highlight: "Backup",
content: 'encryption', content: "encryption",
icon: 'backup-restore' icon: "backup-restore"
} }
], ],
pro: true pro: true
}, },
{ {
title: 'Personalize & make Notesnook your own', title: "Personalize & make Notesnook your own",
detail: 'Change app themes to match your style. Custom themes are coming soon.', detail:
"Change app themes to match your style. Custom themes are coming soon.",
features: [ features: [
{ {
highlight: 'Automatic', highlight: "Automatic",
content: 'dark mode', content: "dark mode",
icon: 'theme-light-dark', icon: "theme-light-dark",
pro: false pro: false
}, },
{ {
highlight: 'Change accent', highlight: "Change accent",
content: 'color', content: "color",
icon: 'invert-colors', icon: "invert-colors",
pro: true pro: true
} }
] ]

View File

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

View File

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

View File

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

View File

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

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