mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
refactor: run prettier on the whole codebase
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
# all
|
||||
build
|
||||
coverage
|
||||
node_modules
|
||||
dist
|
||||
native/
|
||||
public/an.js
|
||||
|
||||
# mobile
|
||||
apps/mobile/native/
|
||||
|
||||
# web
|
||||
apps/web/public/an.js
|
||||
|
||||
# editor
|
||||
packages/editor/styles/
|
||||
@@ -1,16 +1,16 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parser: "@typescript-eslint/parser",
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
'react-native/react-native': true
|
||||
"react-native/react-native": true
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
@@ -18,22 +18,31 @@ module.exports = {
|
||||
},
|
||||
ecmaVersion: 12,
|
||||
es6: true,
|
||||
sourceType: 'module'
|
||||
sourceType: "module"
|
||||
},
|
||||
plugins: ['react', 'react-native', 'prettier', 'unused-imports', '@typescript-eslint'],
|
||||
plugins: [
|
||||
"react",
|
||||
"react-native",
|
||||
"prettier",
|
||||
"unused-imports",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
rules: {
|
||||
'react/display-name': 0,
|
||||
'no-unused-vars': 'off',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'prefer-const': 'off',
|
||||
'no-empty': 'off',
|
||||
'react/prop-types': 0,
|
||||
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', 'ts', 'tsx'] }],
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
"react/display-name": 0,
|
||||
"no-unused-vars": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"unused-imports/no-unused-vars": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"prefer-const": "off",
|
||||
"no-empty": "off",
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-filename-extension": [
|
||||
1,
|
||||
{ extensions: [".js", ".jsx", "ts", "tsx"] }
|
||||
],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{},
|
||||
{
|
||||
usePrettierrc: true
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
|
||||
- name: Use specific Java version for the builds
|
||||
uses: joschi/setup-jdk@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
architecture: 'x64'
|
||||
java-version: "11"
|
||||
architecture: "x64"
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
|
||||
- name: Use specific Java version for the builds
|
||||
uses: joschi/setup-jdk@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
architecture: 'x64'
|
||||
java-version: "11"
|
||||
architecture: "x64"
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
4
apps/mobile/.vscode/launch.json
vendored
4
apps/mobile/.vscode/launch.json
vendored
@@ -4,8 +4,6 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
|
||||
{
|
||||
"name": "Debug Android",
|
||||
"cwd": "${workspaceFolder}",
|
||||
@@ -27,4 +25,4 @@
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{}
|
||||
{}
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import 'react-native';
|
||||
import React from "react";
|
||||
import "react-native";
|
||||
// Note: test renderer must be required after react-native.
|
||||
import renderer from 'react-test-renderer';
|
||||
import Heading from '../app/components/ui/typography/heading';
|
||||
import Paragraph from '../app/components/ui/typography/paragraph';
|
||||
import renderer from "react-test-renderer";
|
||||
import Heading from "../app/components/ui/typography/heading";
|
||||
import Paragraph from "../app/components/ui/typography/paragraph";
|
||||
|
||||
it('Heading renders correctly', done => {
|
||||
it("Heading renders correctly", (done) => {
|
||||
let instance = renderer.create(<Heading>Heading</Heading>);
|
||||
expect(instance.root.props.children).toBe('Heading');
|
||||
expect(instance.root.props.children).toBe("Heading");
|
||||
done();
|
||||
});
|
||||
|
||||
it('Paragraph renders correctly', done => {
|
||||
it("Paragraph renders correctly", (done) => {
|
||||
let instance = renderer.create(<Paragraph>Paragraph</Paragraph>);
|
||||
expect(instance.root.props.children).toBe('Paragraph');
|
||||
expect(instance.root.props.children).toBe("Paragraph");
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { withErrorBoundry } from './components/exception-handler';
|
||||
import Launcher from './components/launcher';
|
||||
import { ApplicationHolder } from './navigation';
|
||||
import Notifications from './services/notifications';
|
||||
import SettingsService from './services/settings';
|
||||
import { TipManager } from './services/tip-manager';
|
||||
import { useUserStore } from './stores/use-user-store';
|
||||
import { useAppEvents } from './hooks/use-app-events';
|
||||
import React, { useEffect } from "react";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
import { withErrorBoundry } from "./components/exception-handler";
|
||||
import Launcher from "./components/launcher";
|
||||
import { ApplicationHolder } from "./navigation";
|
||||
import Notifications from "./services/notifications";
|
||||
import SettingsService from "./services/settings";
|
||||
import { TipManager } from "./services/tip-manager";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
import { useAppEvents } from "./hooks/use-app-events";
|
||||
|
||||
SettingsService.init();
|
||||
SettingsService.checkOrientation();
|
||||
@@ -16,7 +16,7 @@ const App = () => {
|
||||
useAppEvents();
|
||||
useEffect(() => {
|
||||
let { appLockMode } = SettingsService.get();
|
||||
if (appLockMode && appLockMode !== 'none') {
|
||||
if (appLockMode && appLockMode !== "none") {
|
||||
useUserStore.getState().setVerifyUser(true);
|
||||
}
|
||||
setTimeout(() => {
|
||||
@@ -39,4 +39,4 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundry(App, 'App');
|
||||
export default withErrorBoundry(App, "App");
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,12 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { MMKV } from '../database/mmkv';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { Platform } from "react-native";
|
||||
import { MMKV } from "../database/mmkv";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`;
|
||||
const baseUrl = `https://analytics.streetwriters.co/api/collect`;
|
||||
|
||||
const UA =
|
||||
Platform.OS === 'ios'
|
||||
Platform.OS === "ios"
|
||||
? `Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1`
|
||||
: `
|
||||
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
|
||||
@@ -19,37 +19,37 @@ Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHT
|
||||
*/
|
||||
async function canUpdateAnalytics(route, conditions = []) {
|
||||
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
|
||||
let eventsList = MMKV.getString('notesnookUserEvents');
|
||||
let eventsList = MMKV.getString("notesnookUserEvents");
|
||||
|
||||
if (eventsList) {
|
||||
eventsList = JSON.parse(eventsList);
|
||||
}
|
||||
|
||||
if (eventsList && eventsList[route]) {
|
||||
console.log('analytics: event already sent', route);
|
||||
console.log("analytics: event already sent", route);
|
||||
return false;
|
||||
}
|
||||
if (route !== '/welcome') {
|
||||
if (route !== "/welcome") {
|
||||
for (let cond of conditions) {
|
||||
if (!eventsList || !eventsList[cond]) {
|
||||
console.log('analytics: conditions not met for event', route, cond);
|
||||
console.log("analytics: conditions not met for event", route, cond);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('analytics: will send event', route);
|
||||
console.log("analytics: will send event", route);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function saveAnalytics(route, value = true) {
|
||||
let eventsList = MMKV.getString('notesnookUserEvents');
|
||||
let eventsList = MMKV.getString("notesnookUserEvents");
|
||||
if (eventsList) {
|
||||
eventsList = JSON.parse(eventsList);
|
||||
} else {
|
||||
eventsList = {};
|
||||
}
|
||||
eventsList[route] = value;
|
||||
MMKV.setString('notesnookUserEvents', JSON.stringify(eventsList));
|
||||
MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,12 @@ async function saveAnalytics(route, value = true) {
|
||||
* @returns
|
||||
*/
|
||||
|
||||
async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = true) {
|
||||
async function pageView(
|
||||
route,
|
||||
prevRoute = "",
|
||||
conditions = ["/welcome"],
|
||||
once = true
|
||||
) {
|
||||
if (__DEV__) return;
|
||||
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
|
||||
let body = {
|
||||
@@ -70,22 +75,22 @@ async function pageView(route, prevRoute = '', conditions = ['/welcome'], once =
|
||||
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
|
||||
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
|
||||
hostname: `notesnook-${Platform.OS}`,
|
||||
language: 'en-US',
|
||||
screen: '1920x1080'
|
||||
language: "en-US",
|
||||
screen: "1920x1080"
|
||||
},
|
||||
type: 'pageview'
|
||||
type: "pageview"
|
||||
};
|
||||
|
||||
try {
|
||||
let response = await fetch(baseUrl, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': UA
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": UA
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
console.log('analytics: event sent', route);
|
||||
console.log("analytics: event sent", route);
|
||||
await saveAnalytics(route);
|
||||
return await response.text();
|
||||
} catch (e) {
|
||||
@@ -99,22 +104,22 @@ async function sendEvent(type, value, once = true) {
|
||||
let body = {
|
||||
payload: {
|
||||
website: WEBSITE_ID,
|
||||
url: '/',
|
||||
url: "/",
|
||||
event_type: type,
|
||||
event_value: value,
|
||||
hostname: 'notesnook-android-app',
|
||||
language: 'en-US',
|
||||
screen: '1920x1080'
|
||||
hostname: "notesnook-android-app",
|
||||
language: "en-US",
|
||||
screen: "1920x1080"
|
||||
},
|
||||
type: 'event'
|
||||
type: "event"
|
||||
};
|
||||
|
||||
try {
|
||||
let response = await fetch(baseUrl, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': UA
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": UA
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Platform } from 'react-native';
|
||||
import 'react-native-get-random-values';
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
import { generateSecureRandom } from 'react-native-securerandom';
|
||||
import Sodium from 'react-native-sodium';
|
||||
import { Platform } from "react-native";
|
||||
import "react-native-get-random-values";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import { generateSecureRandom } from "react-native-securerandom";
|
||||
import Sodium from "react-native-sodium";
|
||||
|
||||
const KEYSTORE_CONFIG = Platform.select({
|
||||
ios: {
|
||||
@@ -14,15 +14,23 @@ const KEYSTORE_CONFIG = Platform.select({
|
||||
export async function deriveCryptoKey(name, data) {
|
||||
try {
|
||||
let credentials = await Sodium.deriveKey(data.password, data.salt);
|
||||
await Keychain.setInternetCredentials('notesnook', name, credentials.key, KEYSTORE_CONFIG);
|
||||
await Keychain.setInternetCredentials(
|
||||
"notesnook",
|
||||
name,
|
||||
credentials.key,
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
return credentials.key;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export async function getCryptoKey(name) {
|
||||
try {
|
||||
if (await Keychain.hasInternetCredentials('notesnook')) {
|
||||
let credentials = await Keychain.getInternetCredentials('notesnook', KEYSTORE_CONFIG);
|
||||
if (await Keychain.hasInternetCredentials("notesnook")) {
|
||||
let credentials = await Keychain.getInternetCredentials(
|
||||
"notesnook",
|
||||
KEYSTORE_CONFIG
|
||||
);
|
||||
return credentials.password;
|
||||
} else {
|
||||
return null;
|
||||
@@ -32,7 +40,7 @@ export async function getCryptoKey(name) {
|
||||
|
||||
export async function removeCryptoKey(name) {
|
||||
try {
|
||||
let result = await Keychain.resetInternetCredentials('notesnook');
|
||||
let result = await Keychain.resetInternetCredentials("notesnook");
|
||||
return result;
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -51,7 +59,7 @@ export async function generateCryptoKey(password, salt) {
|
||||
let credentials = await Sodium.deriveKey(password, salt || null);
|
||||
return credentials;
|
||||
} catch (e) {
|
||||
console.log('generateCryptoKey: ', e);
|
||||
console.log("generateCryptoKey: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,30 +69,32 @@ export function getAlgorithm(base64Variant) {
|
||||
|
||||
export async function decrypt(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === '' && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
let _data = { ...data };
|
||||
_data.output = 'plain';
|
||||
_data.output = "plain";
|
||||
return await Sodium.decrypt(password, _data);
|
||||
}
|
||||
|
||||
export function parseAlgorithm(alg) {
|
||||
if (!alg) return {};
|
||||
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split('-');
|
||||
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
|
||||
return {
|
||||
encryptionAlgorithm: enc,
|
||||
kdfAlgorithm: kdf,
|
||||
compressionAlgorithm: compressionAlg,
|
||||
isCompress: compressed === '1',
|
||||
isCompress: compressed === "1",
|
||||
base64_variant: base64variant
|
||||
};
|
||||
}
|
||||
|
||||
export async function encrypt(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === '' && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
let message = {
|
||||
type: 'plain',
|
||||
type: "plain",
|
||||
data: data
|
||||
};
|
||||
let result = await Sodium.encrypt(password, message);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import Database from '@streetwriters/notesnook-core/api/index';
|
||||
import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger';
|
||||
import { Platform } from 'react-native';
|
||||
import { MMKVLoader } from 'react-native-mmkv-storage';
|
||||
import filesystem from '../filesystem';
|
||||
import EventSource from '../../utils/sse/even-source-ios';
|
||||
import AndroidEventSource from '../../utils/sse/event-source';
|
||||
import Storage, { KV } from './storage';
|
||||
const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize();
|
||||
import Database from "@streetwriters/notesnook-core/api/index";
|
||||
import {
|
||||
initalize,
|
||||
logger as dbLogger
|
||||
} from "@streetwriters/notesnook-core/logger";
|
||||
import { Platform } from "react-native";
|
||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import filesystem from "../filesystem";
|
||||
import EventSource from "../../utils/sse/even-source-ios";
|
||||
import AndroidEventSource from "../../utils/sse/event-source";
|
||||
import Storage, { KV } from "./storage";
|
||||
const LoggerStorage = new MMKVLoader()
|
||||
.withInstanceID("notesnook_logs")
|
||||
.initialize();
|
||||
console.log(LoggerStorage);
|
||||
initalize(new KV(LoggerStorage));
|
||||
export const DatabaseLogger = dbLogger;
|
||||
@@ -16,18 +21,18 @@ export const DatabaseLogger = dbLogger;
|
||||
*/
|
||||
export var db = new Database(
|
||||
Storage,
|
||||
Platform.OS === 'ios' ? EventSource : AndroidEventSource,
|
||||
Platform.OS === "ios" ? EventSource : AndroidEventSource,
|
||||
filesystem
|
||||
);
|
||||
|
||||
db.host(
|
||||
__DEV__
|
||||
? {
|
||||
API_HOST: 'https://api.notesnook.com',
|
||||
AUTH_HOST: 'https://auth.streetwriters.co',
|
||||
SSE_HOST: 'https://events.streetwriters.co',
|
||||
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
|
||||
ISSUES_HOST: 'https://issues.streetwriters.co'
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
// API_HOST: 'http://192.168.10.29:5264',
|
||||
// AUTH_HOST: 'http://192.168.10.29:8264',
|
||||
// SSE_HOST: 'http://192.168.10.29:7264',
|
||||
@@ -35,11 +40,11 @@ db.host(
|
||||
// ISSUES_HOST: 'http://192.168.10.29:2624'
|
||||
}
|
||||
: {
|
||||
API_HOST: 'https://api.notesnook.com',
|
||||
AUTH_HOST: 'https://auth.streetwriters.co',
|
||||
SSE_HOST: 'https://events.streetwriters.co',
|
||||
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
|
||||
ISSUES_HOST: 'https://issues.streetwriters.co'
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Platform } from 'react-native';
|
||||
import MMKVStorage, { ProcessingModes } from 'react-native-mmkv-storage';
|
||||
import { Platform } from "react-native";
|
||||
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
|
||||
|
||||
export const MMKV = new MMKVStorage.Loader()
|
||||
.setProcessingMode(
|
||||
Platform.OS === 'ios' ? ProcessingModes.MULTI_PROCESS : ProcessingModes.SINGLE_PROCESS
|
||||
Platform.OS === "ios"
|
||||
? ProcessingModes.MULTI_PROCESS
|
||||
: ProcessingModes.SINGLE_PROCESS
|
||||
)
|
||||
.initialize();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Platform } from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import {
|
||||
decrypt,
|
||||
deriveCryptoKey,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
getRandomBytes,
|
||||
hash,
|
||||
removeCryptoKey
|
||||
} from './encryption';
|
||||
import { MMKV } from './mmkv';
|
||||
} from "./encryption";
|
||||
import { MMKV } from "./mmkv";
|
||||
|
||||
export class KV {
|
||||
storage = null;
|
||||
@@ -30,7 +30,10 @@ export class KV {
|
||||
}
|
||||
|
||||
async write(key, data) {
|
||||
this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data));
|
||||
this.storage.setString(
|
||||
key,
|
||||
typeof data === "string" ? data : JSON.stringify(data)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,12 +67,12 @@ export class KV {
|
||||
async getAllKeys() {
|
||||
let keys = (await this.storage.indexer.getKeys()) || [];
|
||||
keys = keys.filter(
|
||||
k =>
|
||||
k !== 'stringIndex' &&
|
||||
k !== 'boolIndex' &&
|
||||
k !== 'mapIndex' &&
|
||||
k !== 'arrayIndex' &&
|
||||
k !== 'numberIndex' &&
|
||||
(k) =>
|
||||
k !== "stringIndex" &&
|
||||
k !== "boolIndex" &&
|
||||
k !== "mapIndex" &&
|
||||
k !== "arrayIndex" &&
|
||||
k !== "numberIndex" &&
|
||||
k !== this.storage.instanceID
|
||||
);
|
||||
return keys;
|
||||
@@ -79,14 +82,14 @@ export class KV {
|
||||
const DefaultStorage = new KV(MMKV);
|
||||
|
||||
async function requestPermission() {
|
||||
if (Platform.OS === 'ios') return true;
|
||||
if (Platform.OS === "ios") return true;
|
||||
return true;
|
||||
}
|
||||
async function checkAndCreateDir(path) {
|
||||
let dir =
|
||||
Platform.OS === 'ios'
|
||||
Platform.OS === "ios"
|
||||
? RNFetchBlob.fs.dirs.DocumentDir + path
|
||||
: RNFetchBlob.fs.dirs.SDCardDir + '/Notesnook/' + path;
|
||||
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
|
||||
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(dir);
|
||||
@@ -102,10 +105,10 @@ async function checkAndCreateDir(path) {
|
||||
}
|
||||
|
||||
export default {
|
||||
read: key => DefaultStorage.read(key),
|
||||
read: (key) => DefaultStorage.read(key),
|
||||
write: (key, value) => DefaultStorage.write(key, value),
|
||||
readMulti: keys => DefaultStorage.readMulti(keys),
|
||||
remove: key => DefaultStorage.remove(key),
|
||||
readMulti: (keys) => DefaultStorage.readMulti(keys),
|
||||
remove: (key) => DefaultStorage.remove(key),
|
||||
clear: () => DefaultStorage.clear(),
|
||||
getAllKeys: () => DefaultStorage.getAllKeys(),
|
||||
encrypt,
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import * as ScopedStorage from 'react-native-scoped-storage';
|
||||
import Sodium from 'react-native-sodium';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { ShareComponent } from '../../components/sheets/export-notes/share';
|
||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
||||
import { presentSheet, ToastEvent } from '../../services/event-manager';
|
||||
import { db } from '../database';
|
||||
import Storage from '../database/storage';
|
||||
import { cacheDir, fileCheck } from './utils';
|
||||
import hosts from '@streetwriters/notesnook-core/utils/constants';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Sodium from "react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { presentSheet, ToastEvent } from "../../services/event-manager";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import hosts from "@streetwriters/notesnook-core/utils/constants";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
|
||||
export async function downloadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
console.log('downloading file: ', filename, url);
|
||||
console.log("downloading file: ", filename, url);
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
if (exists) {
|
||||
console.log('file is downloaded');
|
||||
console.log("file is downloaded");
|
||||
return true;
|
||||
}
|
||||
|
||||
let res = await fetch(url, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers
|
||||
});
|
||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve download url`);
|
||||
if (!res.ok)
|
||||
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||
const downloadUrl = await res.text();
|
||||
|
||||
if (!downloadUrl) throw new Error('Unable to resolve download url');
|
||||
if (!downloadUrl) throw new Error("Unable to resolve download url");
|
||||
let totalSize = 0;
|
||||
let request = RNFetchBlob.config({
|
||||
path: path,
|
||||
IOSBackgroundTask: true
|
||||
})
|
||||
.fetch('GET', downloadUrl, null)
|
||||
.fetch("GET", downloadUrl, null)
|
||||
.progress((recieved, total) => {
|
||||
useAttachmentStore.getState().setProgress(0, total, filename, recieved, 'download');
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(0, total, filename, recieved, "download");
|
||||
totalSize = total;
|
||||
console.log('downloading: ', recieved, total);
|
||||
console.log("downloading: ", recieved, total);
|
||||
});
|
||||
|
||||
cancelToken.cancel = request.cancel;
|
||||
@@ -53,21 +56,21 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
return status >= 200 && status < 300;
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: 'Error downloading file',
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'global'
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: 'Error downloading file',
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log('download file error: ', e, url, headers);
|
||||
console.log("download file error: ", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -75,24 +78,30 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
export async function downloadAttachment(hash, global = true) {
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) {
|
||||
console.log('attachment not found');
|
||||
console.log("attachment not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let folder = {};
|
||||
if (Platform.OS === 'android') {
|
||||
if (Platform.OS === "android") {
|
||||
folder = await ScopedStorage.openDocumentTree();
|
||||
if (!folder) return;
|
||||
} else {
|
||||
folder.uri = await Storage.checkAndCreateDir('/downloads/');
|
||||
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||
}
|
||||
|
||||
try {
|
||||
await db.fs.downloadFile(attachment.metadata.hash, attachment.metadata.hash);
|
||||
if (!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))) return;
|
||||
await db.fs.downloadFile(
|
||||
attachment.metadata.hash,
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (
|
||||
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
|
||||
)
|
||||
return;
|
||||
|
||||
let key = await db.attachments.decryptKey(attachment.key);
|
||||
console.log('attachment key', key);
|
||||
console.log("attachment key", key);
|
||||
let info = {
|
||||
iv: attachment.iv,
|
||||
salt: attachment.salt,
|
||||
@@ -108,37 +117,48 @@ export async function downloadAttachment(hash, global = true) {
|
||||
|
||||
let fileUri = await Sodium.decryptFile(key, info, false);
|
||||
ToastEvent.show({
|
||||
heading: 'Download successful',
|
||||
message: attachment.metadata.filename + ' downloaded',
|
||||
type: 'success'
|
||||
heading: "Download successful",
|
||||
message: attachment.metadata.filename + " downloaded",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (attachment.dateUploaded) {
|
||||
console.log('Deleting attachment after download', attachment.dateUploaded);
|
||||
console.log(
|
||||
"Deleting attachment after download",
|
||||
attachment.dateUploaded
|
||||
);
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
.catch(console.log);
|
||||
}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
if (Platform.OS === "ios") {
|
||||
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
||||
}
|
||||
console.log('saved file uri: ', fileUri);
|
||||
console.log("saved file uri: ", fileUri);
|
||||
|
||||
presentSheet({
|
||||
title: `File downloaded`,
|
||||
paragraph: `${attachment.metadata.filename} saved to ${
|
||||
Platform.OS === 'android' ? 'selected path' : 'File Manager/Notesnook/downloads'
|
||||
Platform.OS === "android"
|
||||
? "selected path"
|
||||
: "File Manager/Notesnook/downloads"
|
||||
}`,
|
||||
icon: 'download',
|
||||
icon: "download",
|
||||
context: global ? null : attachment.metadata.hash,
|
||||
component: <ShareComponent uri={fileUri} name={attachment.metadata.filename} padding={12} />
|
||||
component: (
|
||||
<ShareComponent
|
||||
uri={fileUri}
|
||||
name={attachment.metadata.filename}
|
||||
padding={12}
|
||||
/>
|
||||
)
|
||||
});
|
||||
return fileUri;
|
||||
} catch (e) {
|
||||
console.log('download attachment error: ', e);
|
||||
console.log("download attachment error: ", e);
|
||||
if (attachment.dateUploaded) {
|
||||
console.log('Deleting attachment on error', attachment.dateUploaded);
|
||||
console.log("Deleting attachment on error", attachment.dateUploaded);
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
.catch(console.log);
|
||||
@@ -152,26 +172,27 @@ export async function getUploadedFileSize(hash) {
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: 'HEAD',
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(attachmentInfo.headers?.get('content-length'));
|
||||
console.log('contentLength:', contentLength, attachmentInfo.headers);
|
||||
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
|
||||
console.log("contentLength:", contentLength, attachmentInfo.headers);
|
||||
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash) {
|
||||
const internetState = await NetInfo.fetch();
|
||||
const isInternetReachable = internetState.isConnected && internetState.isInternetReachable;
|
||||
const isInternetReachable =
|
||||
internetState.isConnected && internetState.isInternetReachable;
|
||||
if (!isInternetReachable) return { success: true };
|
||||
const attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) return { failed: 'Attachment not found.' };
|
||||
if (!attachment) return { failed: "Attachment not found." };
|
||||
|
||||
try {
|
||||
const size = await getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: 'File length is 0.' };
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e?.message };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import { downloadAttachment, downloadFile, getUploadedFileSize, checkAttachment } from './download';
|
||||
import { clearFileStorage, deleteFile, exists, readEncrypted, writeEncrypted } from './io';
|
||||
import { uploadFile } from './upload';
|
||||
import { cancelable } from './utils';
|
||||
import {
|
||||
downloadAttachment,
|
||||
downloadFile,
|
||||
getUploadedFileSize,
|
||||
checkAttachment
|
||||
} from "./download";
|
||||
import {
|
||||
clearFileStorage,
|
||||
deleteFile,
|
||||
exists,
|
||||
readEncrypted,
|
||||
writeEncrypted
|
||||
} from "./io";
|
||||
import { uploadFile } from "./upload";
|
||||
import { cancelable } from "./utils";
|
||||
|
||||
export default {
|
||||
readEncrypted,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Platform } from 'react-native';
|
||||
import Sodium from 'react-native-sodium';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { cacheDir, getRandomId } from './utils';
|
||||
import { Platform } from "react-native";
|
||||
import Sodium from "react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
@@ -19,28 +19,28 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
},
|
||||
true
|
||||
);
|
||||
console.log('output length: ', output?.length);
|
||||
console.log("output length: ", output?.length);
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log(e);
|
||||
console.log('error');
|
||||
console.log("error");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeEncrypted(filename, { data, type, key }) {
|
||||
console.log('file input: ', { type, key });
|
||||
let filepath = cacheDir + `/${getRandomId('imagecache_')}`;
|
||||
console.log("file input: ", { type, key });
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
console.log(filepath);
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, 'base64');
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
uri: Platform.OS === 'ios' ? filepath : `file://` + filepath,
|
||||
type: 'url'
|
||||
uri: Platform.OS === "ios" ? filepath : `file://` + filepath,
|
||||
type: "url"
|
||||
});
|
||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||
|
||||
console.log('encrypted file output: ', output);
|
||||
console.log("encrypted file output: ", output);
|
||||
return {
|
||||
...output,
|
||||
alg: `xcha-stream`
|
||||
@@ -57,7 +57,7 @@ export async function deleteFile(filename, data) {
|
||||
|
||||
let { url, headers } = data;
|
||||
try {
|
||||
let response = await RNFetchBlob.fetch('DELETE', url, headers);
|
||||
let response = await RNFetchBlob.fetch("DELETE", url, headers);
|
||||
let status = response.info().status;
|
||||
let ok = status >= 200 && status < 300;
|
||||
if (ok) {
|
||||
@@ -65,7 +65,7 @@ export async function deleteFile(filename, data) {
|
||||
}
|
||||
return ok;
|
||||
} catch (e) {
|
||||
console.log('delete file: ', e, url, headers);
|
||||
console.log("delete file: ", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
||||
import { db } from '../database';
|
||||
import { cacheDir } from './utils';
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir } from "./utils";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
console.log('uploading file: ', filename, headers);
|
||||
console.log("uploading file: ", filename, headers);
|
||||
try {
|
||||
let res = await fetch(url, {
|
||||
method: 'PUT',
|
||||
method: "PUT",
|
||||
headers
|
||||
});
|
||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
||||
const uploadUrl = await res.text();
|
||||
if (!uploadUrl) throw new Error('Unable to resolve upload url');
|
||||
if (!uploadUrl) throw new Error("Unable to resolve upload url");
|
||||
|
||||
let request = RNFetchBlob.config({
|
||||
IOSBackgroundTask: true
|
||||
})
|
||||
.fetch(
|
||||
'PUT',
|
||||
"PUT",
|
||||
uploadUrl,
|
||||
{
|
||||
'content-type': ''
|
||||
"content-type": ""
|
||||
},
|
||||
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
|
||||
)
|
||||
.uploadProgress((sent, total) => {
|
||||
useAttachmentStore.getState().setProgress(sent, total, filename, 0, 'upload');
|
||||
console.log('uploading: ', sent, total);
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(sent, total, filename, 0, "upload");
|
||||
console.log("uploading: ", sent, total);
|
||||
});
|
||||
cancelToken.cancel = request.cancel;
|
||||
let response = await request;
|
||||
@@ -42,7 +44,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
if (result) {
|
||||
let attachment = db.attachments.attachment(filename);
|
||||
if (!attachment) return result;
|
||||
if (!attachment.metadata.type.startsWith('image/')) {
|
||||
if (!attachment.metadata.type.startsWith("image/")) {
|
||||
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +52,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
return result;
|
||||
} catch (e) {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
console.log('upload file: ', e, url, headers);
|
||||
console.log("upload file: ", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
|
||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
|
||||
export function getRandomId(prefix) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.replace('0.', prefix || '');
|
||||
.replace("0.", prefix || "");
|
||||
}
|
||||
|
||||
export function extractValueFromXmlTag(code, xml) {
|
||||
if (!xml.includes(code)) return `Unknown ${code}`;
|
||||
return xml.slice(xml.indexOf(`<${code}>`) + code.length + 2, xml.indexOf(`</${code}>`));
|
||||
return xml.slice(
|
||||
xml.indexOf(`<${code}>`) + code.length + 2,
|
||||
xml.indexOf(`</${code}>`)
|
||||
);
|
||||
}
|
||||
|
||||
export async function fileCheck(response, totalSize) {
|
||||
if (totalSize < 1000) {
|
||||
let text = await response.text();
|
||||
if (text.startsWith('<?xml')) {
|
||||
if (text.startsWith("<?xml")) {
|
||||
let errorJson = {
|
||||
Code: extractValueFromXmlTag('Code', text),
|
||||
Message: extractValueFromXmlTag('Message', text)
|
||||
Code: extractValueFromXmlTag("Code", text),
|
||||
Message: extractValueFromXmlTag("Message", text)
|
||||
};
|
||||
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ function info(context: string, ...logs: any[]) {
|
||||
}
|
||||
|
||||
function error(context: string, ...logs: any[]) {
|
||||
console.log(`${new Date().toLocaleDateString()}::error::${context}: `, ...logs);
|
||||
console.log(
|
||||
`${new Date().toLocaleDateString()}::error::${context}: `,
|
||||
...logs
|
||||
);
|
||||
}
|
||||
|
||||
type Logger = {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { allowedOnPlatform, renderItem } from './functions';
|
||||
import React from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { allowedOnPlatform, renderItem } from "./functions";
|
||||
|
||||
export const Announcement = ({ color }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const announcements = useMessageStore(state => state.announcements);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const announcements = useMessageStore((state) => state.announcements);
|
||||
let announcement = announcements.length > 0 ? announcements[0] : null;
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
return !announcement || selectionMode ? null : (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12
|
||||
@@ -22,9 +22,9 @@ export const Announcement = ({ color }) => {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
overflow: "hidden",
|
||||
backgroundColor: colors.nav,
|
||||
paddingBottom: 12
|
||||
}}
|
||||
@@ -32,12 +32,19 @@ export const Announcement = ({ color }) => {
|
||||
<View>
|
||||
<FlatList
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
marginTop: 12
|
||||
}}
|
||||
data={announcement?.body.filter(item => allowedOnPlatform(item.platforms))}
|
||||
data={announcement?.body.filter((item) =>
|
||||
allowedOnPlatform(item.platforms)
|
||||
)}
|
||||
renderItem={({ item, index }) =>
|
||||
renderItem({ item: item, index: index, color: colors[color], inline: true })
|
||||
renderItem({
|
||||
item: item,
|
||||
index: index,
|
||||
color: colors[color],
|
||||
inline: true
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const Body = ({ text, style = {} }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<Paragraph
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import React from 'react';
|
||||
import { Linking, View } from 'react-native';
|
||||
import React from "react";
|
||||
import { Linking, View } from "react-native";
|
||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
||||
import Sync from '../../services/sync';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { eCloseAnnouncementDialog, eCloseProgressDialog } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { PricingPlans } from '../premium/pricing-plans';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { Progress } from '../sheets/progress';
|
||||
import { Button } from '../ui/button';
|
||||
import { allowedOnPlatform, getStyle } from './functions';
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import {
|
||||
eCloseAnnouncementDialog,
|
||||
eCloseProgressDialog
|
||||
} from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { PricingPlans } from "../premium/pricing-plans";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
import { allowedOnPlatform, getStyle } from "./functions";
|
||||
|
||||
export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || [];
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
let buttons =
|
||||
actions.filter((item) => allowedOnPlatform(item.platforms)) || [];
|
||||
|
||||
const onPress = async item => {
|
||||
const onPress = async (item) => {
|
||||
if (!inline) {
|
||||
eSendEvent(eCloseAnnouncementDialog);
|
||||
await sleep(500);
|
||||
}
|
||||
if (item.type === 'link') {
|
||||
if (item.type === "link") {
|
||||
Linking.openURL(item.data).catch(console.log);
|
||||
} else if (item.type === 'promo') {
|
||||
} else if (item.type === "promo") {
|
||||
presentSheet({
|
||||
component: (
|
||||
<PricingPlans
|
||||
@@ -36,11 +40,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (item.type === 'force-sync') {
|
||||
} else if (item.type === "force-sync") {
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
await sleep(300);
|
||||
Progress.present();
|
||||
Sync.run('global', true, true, () => {
|
||||
Sync.run("global", true, true, () => {
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
});
|
||||
}
|
||||
@@ -50,7 +54,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
...getStyle(style),
|
||||
flexDirection: inline ? 'row' : 'column'
|
||||
flexDirection: inline ? "row" : "column"
|
||||
}}
|
||||
>
|
||||
<SheetProvider context="premium_cta" />
|
||||
@@ -58,20 +62,20 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
{inline ? (
|
||||
<>
|
||||
{buttons.length > 0 &&
|
||||
buttons.slice(0, 1).map(item => (
|
||||
buttons.slice(0, 1).map((item) => (
|
||||
<Button
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
fontSize={SIZE.sm}
|
||||
type="transparent"
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={() => onPress(item)}
|
||||
bold
|
||||
style={{
|
||||
height: 30,
|
||||
alignSelf: 'flex-start',
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
marginTop: -6
|
||||
}}
|
||||
@@ -89,13 +93,13 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
width={null}
|
||||
height={30}
|
||||
style={{
|
||||
alignSelf: 'flex-start',
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
marginTop: -6,
|
||||
marginLeft: 12
|
||||
}}
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -103,7 +107,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
) : (
|
||||
<>
|
||||
{buttons.length > 0 &&
|
||||
buttons.slice(0, 1).map(item => (
|
||||
buttons.slice(0, 1).map((item) => (
|
||||
<Button
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
@@ -134,11 +138,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
width={null}
|
||||
height={30}
|
||||
style={{
|
||||
minWidth: '50%',
|
||||
minWidth: "50%",
|
||||
marginTop: 5
|
||||
}}
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const Description = ({ text, style = {}, inline }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
return (
|
||||
<Paragraph
|
||||
style={{
|
||||
marginHorizontal: 12,
|
||||
...getStyle(style),
|
||||
textAlign: inline ? 'left' : style?.textAlign
|
||||
textAlign: inline ? "left" : style?.textAlign
|
||||
}}
|
||||
size={inline ? SIZE.sm : SIZE.md}
|
||||
>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { allowedPlatforms } from '../../stores/use-message-store';
|
||||
import { ProFeatures } from '../dialogs/result/pro-features';
|
||||
import { Body } from './body';
|
||||
import { Cta } from './cta';
|
||||
import { Description } from './description';
|
||||
import { List } from './list';
|
||||
import { Photo } from './photo';
|
||||
import { SubHeading } from './subheading';
|
||||
import { Title } from './title';
|
||||
import React, { Fragment } from "react";
|
||||
import { View } from "react-native";
|
||||
import { allowedPlatforms } from "../../stores/use-message-store";
|
||||
import { ProFeatures } from "../dialogs/result/pro-features";
|
||||
import { Body } from "./body";
|
||||
import { Cta } from "./cta";
|
||||
import { Description } from "./description";
|
||||
import { List } from "./list";
|
||||
import { Photo } from "./photo";
|
||||
import { SubHeading } from "./subheading";
|
||||
import { Title } from "./title";
|
||||
|
||||
export function allowedOnPlatform(platforms) {
|
||||
if (!platforms) return true;
|
||||
return platforms.some(platform => allowedPlatforms.indexOf(platform) > -1);
|
||||
return platforms.some((platform) => allowedPlatforms.indexOf(platform) > -1);
|
||||
}
|
||||
|
||||
export const margins = {
|
||||
@@ -21,12 +21,12 @@ export const margins = {
|
||||
2: 20
|
||||
};
|
||||
|
||||
export const getStyle = style => {
|
||||
export const getStyle = (style) => {
|
||||
if (!style) return {};
|
||||
return {
|
||||
marginTop: margins[style.marginTop] || 0,
|
||||
marginBottom: margins[style.marginBottom] || 0,
|
||||
textAlign: style.textAlign || 'left'
|
||||
textAlign: style.textAlign || "left"
|
||||
};
|
||||
};
|
||||
|
||||
@@ -35,8 +35,8 @@ const Features = () => {
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<ProFeatures />
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FlatList, View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { eCloseAnnouncementDialog, eOpenAnnouncementDialog } from '../../utils/events';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import { allowedOnPlatform, renderItem } from './functions';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import {
|
||||
eCloseAnnouncementDialog,
|
||||
eOpenAnnouncementDialog
|
||||
} from "../../utils/events";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { allowedOnPlatform, renderItem } from "./functions";
|
||||
|
||||
export const AnnouncementDialog = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [info, setInfo] = useState(null);
|
||||
const remove = useMessageStore(state => state.remove);
|
||||
const remove = useMessageStore((state) => state.remove);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenAnnouncementDialog, open);
|
||||
@@ -23,7 +29,7 @@ export const AnnouncementDialog = () => {
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const open = data => {
|
||||
const open = (data) => {
|
||||
setInfo(data);
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -46,11 +52,11 @@ export const AnnouncementDialog = () => {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab ? 600 : '100%',
|
||||
width: DDS.isTab ? 600 : "100%",
|
||||
backgroundColor: colors.bg,
|
||||
maxHeight: DDS.isTab ? '90%' : '100%',
|
||||
maxHeight: DDS.isTab ? "90%" : "100%",
|
||||
borderRadius: DDS.isTab ? 10 : 0,
|
||||
overflow: 'hidden',
|
||||
overflow: "hidden",
|
||||
marginBottom: DDS.isTab ? 20 : 0,
|
||||
borderTopRightRadius: 10,
|
||||
borderTopLeftRadius: 10
|
||||
@@ -58,9 +64,9 @@ export const AnnouncementDialog = () => {
|
||||
>
|
||||
<FlatList
|
||||
style={{
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
data={info?.body.filter(item => allowedOnPlatform(item.platforms))}
|
||||
data={info?.body.filter((item) => allowedOnPlatform(item.platforms))}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const List = ({ items, listType, style = {} }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
paddingLeft: listType === 'ordered' ? 25 : 25,
|
||||
paddingLeft: listType === "ordered" ? 25 : 25,
|
||||
...getStyle(style)
|
||||
}}
|
||||
>
|
||||
@@ -21,10 +21,10 @@ export const List = ({ items, listType, style = {} }) => {
|
||||
key={item.text}
|
||||
style={{
|
||||
paddingVertical: 6,
|
||||
flexDirection: 'row'
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{listType === 'ordered' ? (
|
||||
{listType === "ordered" ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Image } from 'react-native';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { Image } from "react-native";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const Photo = ({ src, style = {} }) => {
|
||||
return src ? (
|
||||
@@ -8,9 +8,9 @@ export const Photo = ({ src, style = {} }) => {
|
||||
source={{ uri: src }}
|
||||
resizeMode="cover"
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 200,
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
...getStyle(style)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const SubHeading = ({ text, style = {} }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<Heading
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { Button } from '../ui/button';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import { getStyle } from './functions';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { getStyle } from "./functions";
|
||||
|
||||
export const Title = ({ text, style = {}, inline }) => {
|
||||
const announcements = useMessageStore(state => state.announcements);
|
||||
const announcements = useMessageStore((state) => state.announcements);
|
||||
let announcement = announcements.length > 0 ? announcements[0] : null;
|
||||
const remove = useMessageStore(state => state.remove);
|
||||
const remove = useMessageStore((state) => state.remove);
|
||||
|
||||
return inline ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: inline ? 5 : 0
|
||||
}}
|
||||
>
|
||||
@@ -25,7 +25,7 @@ export const Title = ({ text, style = {}, inline }) => {
|
||||
marginHorizontal: 12,
|
||||
marginTop: 12,
|
||||
...getStyle(style),
|
||||
textAlign: inline ? 'left' : style?.textAlign,
|
||||
textAlign: inline ? "left" : style?.textAlign,
|
||||
flexShrink: 1
|
||||
}}
|
||||
numberOfLines={1}
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { ScrollView } from 'react-native-gesture-handler';
|
||||
import picker from '../../screens/editor/tiptap/picker';
|
||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
||||
import PremiumService from '../../services/premium';
|
||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { formatBytes } from '../../utils';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseAttachmentDialog, eCloseProgressDialog } from '../../utils/events';
|
||||
import filesystem from '../../common/filesystem';
|
||||
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { Dialog } from '../dialog';
|
||||
import { presentDialog } from '../dialog/functions';
|
||||
import { openNote } from '../list-items/note/wrapper';
|
||||
import { DateMeta } from '../properties/date-meta';
|
||||
import { Button } from '../ui/button';
|
||||
import { Notice } from '../ui/notice';
|
||||
import { PressableButton } from '../ui/pressable';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import picker from "../../screens/editor/tiptap/picker";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { formatBytes } from "../../utils";
|
||||
import { db } from "../../common/database";
|
||||
import {
|
||||
eCloseAttachmentDialog,
|
||||
eCloseProgressDialog
|
||||
} from "../../utils/events";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Dialog } from "../dialog";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import { openNote } from "../list-items/note/wrapper";
|
||||
import { DateMeta } from "../properties/date-meta";
|
||||
import { Button } from "../ui/button";
|
||||
import { Notice } from "../ui/notice";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const contextId = attachment.metadata.hash;
|
||||
const [filename, setFilename] = useState(attachment.metadata.filename);
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment);
|
||||
const [currentProgress, setCurrentProgress] =
|
||||
useAttachmentProgress(attachment);
|
||||
const [failed, setFailed] = useState(attachment.failed);
|
||||
const [notes, setNotes] = useState([]);
|
||||
const [loading, setLoading] = useState({
|
||||
@@ -37,25 +45,25 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
|
||||
const actions = [
|
||||
{
|
||||
name: 'Download',
|
||||
name: "Download",
|
||||
onPress: async () => {
|
||||
if (currentProgress) {
|
||||
await db.fs.cancel(attachment.metadata.hash, 'download');
|
||||
await db.fs.cancel(attachment.metadata.hash, "download");
|
||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
||||
}
|
||||
filesystem.downloadAttachment(attachment.metadata.hash, false);
|
||||
eSendEvent(eCloseProgressDialog, contextId);
|
||||
},
|
||||
icon: 'download'
|
||||
icon: "download"
|
||||
},
|
||||
{
|
||||
name: 'Reupload',
|
||||
name: "Reupload",
|
||||
onPress: async () => {
|
||||
if (!PremiumService.get()) {
|
||||
ToastEvent.show({
|
||||
heading: 'Upgrade to pro',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Upgrade to pro",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -66,13 +74,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
type: attachment.metadata.type
|
||||
});
|
||||
},
|
||||
icon: 'upload'
|
||||
icon: "upload"
|
||||
},
|
||||
{
|
||||
name: 'Run file check',
|
||||
name: "Run file check",
|
||||
onPress: async () => {
|
||||
setLoading({
|
||||
name: 'Run file check'
|
||||
name: "Run file check"
|
||||
});
|
||||
let res = await filesystem.checkAttachment(attachment.metadata.hash);
|
||||
if (res.failed) {
|
||||
@@ -83,27 +91,27 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
ToastEvent.show({
|
||||
heading: 'File check passed',
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
heading: "File check passed",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
setAttachments([...db.attachments.all]);
|
||||
setLoading({
|
||||
name: null
|
||||
});
|
||||
},
|
||||
icon: 'file-check'
|
||||
icon: "file-check"
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
name: "Rename",
|
||||
onPress: () => {
|
||||
presentDialog({
|
||||
context: contextId,
|
||||
input: true,
|
||||
title: 'Rename file',
|
||||
paragraph: 'Enter a new name for the file',
|
||||
title: "Rename file",
|
||||
paragraph: "Enter a new name for the file",
|
||||
defaultValue: attachment.metadata.filename,
|
||||
positivePress: async value => {
|
||||
positivePress: async (value) => {
|
||||
if (value && value.length > 0) {
|
||||
await db.attachments.add({
|
||||
hash: attachment.metadata.hash,
|
||||
@@ -113,31 +121,31 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
setAttachments([...db.attachments.all]);
|
||||
}
|
||||
},
|
||||
positiveText: 'Rename'
|
||||
positiveText: "Rename"
|
||||
});
|
||||
},
|
||||
icon: 'form-textbox'
|
||||
icon: "form-textbox"
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
name: "Delete",
|
||||
onPress: async () => {
|
||||
await db.attachments.remove(attachment.metadata.hash, false);
|
||||
setAttachments([...db.attachments.all]);
|
||||
eSendEvent(eCloseProgressDialog, contextId);
|
||||
},
|
||||
icon: 'delete-outline'
|
||||
icon: "delete-outline"
|
||||
}
|
||||
];
|
||||
|
||||
const getNotes = () => {
|
||||
let allNotes = db.notes.all;
|
||||
let attachmentNotes = attachment.noteIds?.map(id => {
|
||||
let index = allNotes?.findIndex(note => id === note.id);
|
||||
let attachmentNotes = attachment.noteIds?.map((id) => {
|
||||
let index = allNotes?.findIndex((note) => id === note.id);
|
||||
if (index !== -1) {
|
||||
return allNotes[index];
|
||||
} else {
|
||||
return {
|
||||
type: 'notfound',
|
||||
type: "notfound",
|
||||
title: `Note with id ${id} does not exist.`,
|
||||
id: id
|
||||
};
|
||||
@@ -157,7 +165,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
}}
|
||||
nestedScrollEnabled={true}
|
||||
style={{
|
||||
maxHeight: '100%'
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<Dialog context={contextId} />
|
||||
@@ -179,7 +187,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
@@ -210,15 +218,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
size={SIZE.xs + 1}
|
||||
color={colors.icon}
|
||||
>
|
||||
{attachment.noteIds.length} note{attachment.noteIds.length > 1 ? 's' : ''}
|
||||
{attachment.noteIds.length} note
|
||||
{attachment.noteIds.length > 1 ? "s" : ""}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
Clipboard.setString(attachment.metadata.hash);
|
||||
ToastEvent.show({
|
||||
type: 'success',
|
||||
heading: 'Attachment hash copied',
|
||||
context: 'local'
|
||||
type: "success",
|
||||
heading: "Attachment hash copied",
|
||||
context: "local"
|
||||
});
|
||||
}}
|
||||
size={SIZE.xs + 1}
|
||||
@@ -250,16 +259,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
List of notes:
|
||||
</Heading>
|
||||
|
||||
{notes.map(item => (
|
||||
{notes.map((item) => (
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
if (item.type === 'notfound') {
|
||||
if (item.type === "notfound") {
|
||||
ToastEvent.show({
|
||||
heading: 'Note not found',
|
||||
heading: "Note not found",
|
||||
message:
|
||||
'A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -267,11 +276,11 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
await sleep(150);
|
||||
eSendEvent(eCloseAttachmentDialog);
|
||||
await sleep(300);
|
||||
openNote(item, item.type === 'trash');
|
||||
openNote(item, item.type === "trash");
|
||||
}}
|
||||
customStyle={{
|
||||
paddingVertical: 12,
|
||||
alignItems: 'flex-start',
|
||||
alignItems: "flex-start",
|
||||
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
@@ -284,13 +293,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{actions.map(item => (
|
||||
{actions.map((item) => (
|
||||
<Button
|
||||
key={item.name}
|
||||
buttonType={{
|
||||
text: item.on
|
||||
? colors.accent
|
||||
: item.name === 'Delete' || item.name === 'PermDelete'
|
||||
: item.name === "Delete" || item.name === "PermDelete"
|
||||
? colors.errorText
|
||||
: colors.pri
|
||||
}}
|
||||
@@ -298,13 +307,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
title={item.name}
|
||||
icon={item.icon}
|
||||
loading={loading?.name === item.name}
|
||||
type={item.on ? 'shade' : 'gray'}
|
||||
type={item.on ? "shade" : "gray"}
|
||||
fontSize={SIZE.sm}
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
justifyContent: 'flex-start',
|
||||
alignSelf: 'flex-start',
|
||||
width: '100%'
|
||||
justifyContent: "flex-start",
|
||||
alignSelf: "flex-start",
|
||||
width: "100%"
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -329,7 +338,9 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
Actions.present = (attachment, set, context) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: ref => <Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
||||
component: (ref) => (
|
||||
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { formatBytes } from '../../utils';
|
||||
import { db } from '../../common/database';
|
||||
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import { ProgressCircleComponent } from '../ui/svg/lazy';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import Actions from './actions';
|
||||
import React from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { formatBytes } from "../../utils";
|
||||
import { db } from "../../common/database";
|
||||
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import Actions from "./actions";
|
||||
|
||||
function getFileExtension(filename) {
|
||||
var ext = /^.+\.([^.]+)$/.exec(filename);
|
||||
return ext == null ? '' : ext[1];
|
||||
return ext == null ? "" : ext[1];
|
||||
}
|
||||
|
||||
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment, encryption);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||
attachment,
|
||||
encryption
|
||||
);
|
||||
const encryptionProgress = encryption
|
||||
? useAttachmentStore(state => state.encryptionProgress)
|
||||
? useAttachmentStore((state) => state.encryptionProgress)
|
||||
: null;
|
||||
|
||||
const onPress = () => {
|
||||
@@ -33,9 +36,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
activeOpacity={0.9}
|
||||
onPress={onPress}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: "space-between",
|
||||
padding: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 5,
|
||||
@@ -47,14 +50,14 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: -5
|
||||
}}
|
||||
>
|
||||
@@ -65,7 +68,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
size={6}
|
||||
color={colors.light}
|
||||
style={{
|
||||
position: 'absolute'
|
||||
position: "absolute"
|
||||
}}
|
||||
>
|
||||
{getFileExtension(attachment.metadata.filename).toUpperCase()}
|
||||
@@ -81,7 +84,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
<Paragraph
|
||||
size={SIZE.sm - 1}
|
||||
style={{
|
||||
flexWrap: 'wrap',
|
||||
flexWrap: "wrap",
|
||||
marginBottom: 2.5
|
||||
}}
|
||||
numberOfLines={1}
|
||||
@@ -92,8 +95,10 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
{formatBytes(attachment.length)}{' '}
|
||||
{currentProgress?.type ? '(' + currentProgress.type + 'ing - tap to cancel)' : ''}
|
||||
{formatBytes(attachment.length)}{" "}
|
||||
{currentProgress?.type
|
||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||
: ""}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
@@ -107,7 +112,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
setCurrentProgress(null);
|
||||
}}
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
marginLeft: 5,
|
||||
marginTop: 5,
|
||||
marginRight: -5
|
||||
@@ -127,7 +132,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
fontSize: 10
|
||||
}}
|
||||
color={colors.accent}
|
||||
formatText={progress => (progress * 100).toFixed(0)}
|
||||
formatText={(progress) => (progress * 100).toFixed(0)}
|
||||
borderWidth={0}
|
||||
thickness={2}
|
||||
/>
|
||||
@@ -135,7 +140,11 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
) : (
|
||||
<>
|
||||
{attachment.failed ? (
|
||||
<IconButton onPress={onPress} name="alert-circle-outline" color={colors.errorText} />
|
||||
<IconButton
|
||||
onPress={onPress}
|
||||
name="alert-circle-outline"
|
||||
color={colors.errorText}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseAttachmentDialog, eOpenAttachmentsDialog } from '../../utils/events';
|
||||
import filesystem from '../../common/filesystem';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import { Toast } from '../toast';
|
||||
import Input from '../ui/input';
|
||||
import Seperator from '../ui/seperator';
|
||||
import SheetWrapper from '../ui/sheet';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { AttachmentItem } from './attachment-item';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import {
|
||||
eCloseAttachmentDialog,
|
||||
eOpenAttachmentsDialog
|
||||
} from "../../utils/events";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Toast } from "../toast";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import SheetWrapper from "../ui/sheet";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
export const AttachmentDialog = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [note, setNote] = useState(null);
|
||||
const actionSheetRef = useRef();
|
||||
@@ -34,10 +40,10 @@ export const AttachmentDialog = () => {
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const open = data => {
|
||||
const open = (data) => {
|
||||
if (data?.id) {
|
||||
setNote(data);
|
||||
let _attachments = db.attachments.ofNote(data.id, 'all');
|
||||
let _attachments = db.attachments.ofNote(data.id, "all");
|
||||
setAttachments(_attachments);
|
||||
} else {
|
||||
setAttachments([...db.attachments.all]);
|
||||
@@ -56,18 +62,24 @@ export const AttachmentDialog = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const onChangeText = text => {
|
||||
const onChangeText = (text) => {
|
||||
attachmentSearchValue.current = text;
|
||||
console.log(attachmentSearchValue.current?.length);
|
||||
if (!attachmentSearchValue.current || attachmentSearchValue.current === '') {
|
||||
console.log('resetting all');
|
||||
if (
|
||||
!attachmentSearchValue.current ||
|
||||
attachmentSearchValue.current === ""
|
||||
) {
|
||||
console.log("resetting all");
|
||||
setAttachments([...db.attachments.all]);
|
||||
}
|
||||
console.log(attachments.length);
|
||||
clearTimeout(searchTimer.current);
|
||||
searchTimer.current = setTimeout(() => {
|
||||
let results = db.lookup.attachments(db.attachments.all, attachmentSearchValue.current);
|
||||
console.log('results', results.length, attachments.length);
|
||||
let results = db.lookup.attachments(
|
||||
db.attachments.all,
|
||||
attachmentSearchValue.current
|
||||
);
|
||||
console.log("results", results.length, attachments.length);
|
||||
if (results.length === 0) return;
|
||||
setAttachments(results);
|
||||
}, 300);
|
||||
@@ -88,24 +100,29 @@ export const AttachmentDialog = () => {
|
||||
<Toast context="local" />
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
title={note ? 'Attachments' : 'Manage attachments'}
|
||||
title={note ? "Attachments" : "Manage attachments"}
|
||||
paragraph="Tap on an attachment to view properties"
|
||||
button={{
|
||||
title: 'Check all',
|
||||
type: 'grayAccent',
|
||||
title: "Check all",
|
||||
type: "grayAccent",
|
||||
loading: loading,
|
||||
onPress: async () => {
|
||||
setLoading(true);
|
||||
for (let attachment of attachments) {
|
||||
let result = await filesystem.checkAttachment(attachment.metadata.hash);
|
||||
let result = await filesystem.checkAttachment(
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (result.failed) {
|
||||
db.attachments.markAsFailed(attachment.metadata.hash, result.failed);
|
||||
db.attachments.markAsFailed(
|
||||
attachment.metadata.hash,
|
||||
result.failed
|
||||
);
|
||||
} else {
|
||||
db.attachments.markAsFailed(attachment.id, null);
|
||||
}
|
||||
@@ -139,12 +156,14 @@ export const AttachmentDialog = () => {
|
||||
<View
|
||||
style={{
|
||||
height: 150,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="attachment" size={60} color={colors.icon} />
|
||||
<Paragraph>{note ? `No attachments on this note` : `No attachments`}</Paragraph>
|
||||
<Paragraph>
|
||||
{note ? `No attachments on this note` : `No attachments`}
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={
|
||||
@@ -155,7 +174,7 @@ export const AttachmentDialog = () => {
|
||||
/>
|
||||
}
|
||||
data={attachments}
|
||||
keyExtractor={item => item.id}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
@@ -163,12 +182,12 @@ export const AttachmentDialog = () => {
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
||||
{' '}All attachments are end-to-end encrypted.
|
||||
{" "}All attachments are end-to-end encrypted.
|
||||
</Paragraph>
|
||||
</View>
|
||||
</SheetWrapper>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import { Toast } from '../toast';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import { hideAuth, initialAuthMode } from './common';
|
||||
import { Login } from './login';
|
||||
import { Signup } from './signup';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Platform, View } from 'react-native';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { Toast } from "../toast";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { hideAuth, initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { Platform, View } from "react-native";
|
||||
|
||||
export const AuthMode = {
|
||||
login: 0,
|
||||
@@ -22,7 +25,7 @@ export const AuthMode = {
|
||||
};
|
||||
|
||||
const AuthModal = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
|
||||
const actionSheetRef = useRef();
|
||||
@@ -66,29 +69,31 @@ const AuthModal = () => {
|
||||
>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
<Signup
|
||||
changeMode={mode => setCurrentAuthMode(mode)}
|
||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||
trial={AuthMode.trialSignup === currentAuthMode}
|
||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||
/>
|
||||
) : (
|
||||
<Login
|
||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||
changeMode={mode => setCurrentAuthMode(mode)}
|
||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: Platform.OS === 'ios' ? insets.top : 0,
|
||||
position: "absolute",
|
||||
top: Platform.OS === "ios" ? insets.top : 0,
|
||||
zIndex: 999,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 50,
|
||||
justifyContent:
|
||||
initialAuthMode.current !== AuthMode.welcomeSignup ? 'space-between' : 'flex-end'
|
||||
initialAuthMode.current !== AuthMode.welcomeSignup
|
||||
? "space-between"
|
||||
: "flex-end"
|
||||
}}
|
||||
>
|
||||
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
||||
|
||||
@@ -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>`;
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseProgressDialog } from '../../utils/events';
|
||||
import { Button } from '../ui/button';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import Input from '../ui/input';
|
||||
import { Notice } from '../ui/notice';
|
||||
import Seperator from '../ui/seperator';
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from "../../services/event-manager";
|
||||
import { db } from "../../common/database";
|
||||
import { eCloseProgressDialog } from "../../utils/events";
|
||||
import { Button } from "../ui/button";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import Input from "../ui/input";
|
||||
import { Notice } from "../ui/notice";
|
||||
import Seperator from "../ui/seperator";
|
||||
|
||||
export const ChangePassword = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const passwordInputRef = useRef();
|
||||
const password = useRef();
|
||||
const oldPasswordInputRef = useRef();
|
||||
@@ -20,24 +24,24 @@ export const ChangePassword = () => {
|
||||
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const user = useUserStore(state => state.user);
|
||||
const user = useUserStore((state) => state.user);
|
||||
|
||||
const changePassword = async () => {
|
||||
if (!user?.isEmailConfirmed) {
|
||||
ToastEvent.show({
|
||||
heading: 'Email not confirmed',
|
||||
message: 'Please confirm your email to change account password',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Email not confirmed",
|
||||
message: "Please confirm your email to change account password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (error || !oldPassword.current || !password.current) {
|
||||
ToastEvent.show({
|
||||
heading: 'All fields required',
|
||||
message: 'Fill all the fields and try again.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -47,18 +51,18 @@ export const ChangePassword = () => {
|
||||
await db.user.changePassword(oldPassword.current, password.current);
|
||||
ToastEvent.show({
|
||||
heading: `Account password updated`,
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
setLoading(false);
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Failed to change password',
|
||||
heading: "Failed to change password",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
@@ -67,16 +71,19 @@ export const ChangePassword = () => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader title="Change password" paragraph="Enter your old and new passwords" />
|
||||
<DialogHeader
|
||||
title="Change password"
|
||||
paragraph="Enter your old and new passwords"
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<Input
|
||||
fwdRef={oldPasswordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
oldPassword.current = value;
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
@@ -90,10 +97,10 @@ export const ChangePassword = () => {
|
||||
|
||||
<Input
|
||||
fwdRef={passwordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
password.current = value;
|
||||
}}
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
secureTextEntry
|
||||
@@ -112,12 +119,12 @@ export const ChangePassword = () => {
|
||||
<Button
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={changePassword}
|
||||
type="accent"
|
||||
title={loading ? null : 'I understand, change my password'}
|
||||
title={loading ? null : "I understand, change my password"}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createRef } from 'react';
|
||||
import { eSendEvent } from '../../services/event-manager';
|
||||
import Navigation from '../../services/navigation';
|
||||
import SettingsService from '../../services/settings';
|
||||
import { eCloseLoginDialog } from '../../utils/events';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { createRef } from "react";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { eCloseLoginDialog } from "../../utils/events";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
|
||||
export const initialAuthMode = createRef(0);
|
||||
export function hideAuth() {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ActionSheet from 'react-native-actions-sheet';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { ToastEvent } from '../../services/event-manager';
|
||||
import SettingsService from '../../services/settings';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import Input from '../ui/input';
|
||||
import Seperator from '../ui/seperator';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const ForgotPassword = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const [error, setError] = useState(false);
|
||||
@@ -25,17 +25,20 @@ export const ForgotPassword = () => {
|
||||
const sendRecoveryEmail = async () => {
|
||||
if (!email.current || error) {
|
||||
ToastEvent.show({
|
||||
heading: 'Account email is required.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Account email is required.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
|
||||
if (lastRecoveryEmailTime && Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3) {
|
||||
throw new Error('Please wait before requesting another email');
|
||||
if (
|
||||
lastRecoveryEmailTime &&
|
||||
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
|
||||
) {
|
||||
throw new Error("Please wait before requesting another email");
|
||||
}
|
||||
await db.user.recoverAccount(email.current.toLowerCase());
|
||||
SettingsService.set({
|
||||
@@ -44,8 +47,8 @@ export const ForgotPassword = () => {
|
||||
ToastEvent.show({
|
||||
heading: `Check your email to reset password`,
|
||||
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
|
||||
type: 'success',
|
||||
context: 'local',
|
||||
type: "success",
|
||||
context: "local",
|
||||
duration: 7000
|
||||
});
|
||||
setLoading(false);
|
||||
@@ -53,10 +56,10 @@ export const ForgotPassword = () => {
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Recovery email not sent',
|
||||
heading: "Recovery email not sent",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -64,7 +67,7 @@ export const ForgotPassword = () => {
|
||||
return (
|
||||
<>
|
||||
<ActionSheet
|
||||
onBeforeShow={data => (email.current = data)}
|
||||
onBeforeShow={(data) => (email.current = data)}
|
||||
onClose={() => {
|
||||
setSent(false);
|
||||
setLoading(false);
|
||||
@@ -82,8 +85,8 @@ export const ForgotPassword = () => {
|
||||
<View
|
||||
style={{
|
||||
padding: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingBottom: 50
|
||||
}}
|
||||
>
|
||||
@@ -99,7 +102,7 @@ export const ForgotPassword = () => {
|
||||
<Heading>Recovery email sent!</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Please follow the link in the email to recover your account.
|
||||
@@ -111,7 +114,7 @@ export const ForgotPassword = () => {
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
@@ -123,11 +126,11 @@ export const ForgotPassword = () => {
|
||||
|
||||
<Input
|
||||
fwdRef={emailInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
email.current = value;
|
||||
}}
|
||||
defaultValue={email.current}
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
autoComplete="email"
|
||||
@@ -142,12 +145,12 @@ export const ForgotPassword = () => {
|
||||
<Button
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={sendRecoveryEmail}
|
||||
type="accent"
|
||||
title={loading ? null : 'Next'}
|
||||
title={loading ? null : "Next"}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { useNavigationFocus } from '../../hooks/use-navigation-focus';
|
||||
import { Toast } from '../toast';
|
||||
import { initialAuthMode } from './common';
|
||||
import { Login } from './login';
|
||||
import { Signup } from './signup';
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { Toast } from "../toast";
|
||||
import { initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
|
||||
export const AuthMode = {
|
||||
login: 0,
|
||||
@@ -17,8 +17,10 @@ export const AuthMode = {
|
||||
};
|
||||
|
||||
const Auth = ({ navigation, route }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const [currentAuthMode, setCurrentAuthMode] = useState(route?.params?.mode || AuthMode.login);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [currentAuthMode, setCurrentAuthMode] = useState(
|
||||
route?.params?.mode || AuthMode.login
|
||||
);
|
||||
const insets = useSafeAreaInsets();
|
||||
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
||||
useNavigationFocus(navigation, {
|
||||
@@ -32,12 +34,15 @@ const Auth = ({ navigation, route }) => {
|
||||
<View style={{ flex: 1 }}>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
<Signup
|
||||
changeMode={mode => setCurrentAuthMode(mode)}
|
||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||
trial={AuthMode.trialSignup === currentAuthMode}
|
||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||
/>
|
||||
) : (
|
||||
<Login welcome={initialAuthMode.current} changeMode={mode => setCurrentAuthMode(mode)} />
|
||||
<Login
|
||||
welcome={initialAuthMode.current}
|
||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import { SheetManager } from 'react-native-actions-sheet';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
||||
import { clearMessage } from '../../services/message';
|
||||
import PremiumService from '../../services/premium';
|
||||
import SettingsService from '../../services/settings';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseLoginDialog } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { Progress } from '../sheets/progress';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import Input from '../ui/input';
|
||||
import { SvgView } from '../ui/svg';
|
||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { SVG } from './background';
|
||||
import { ForgotPassword } from './forgot-password';
|
||||
import TwoFactorVerification from './two-factor';
|
||||
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
|
||||
import Navigation from '../../services/navigation';
|
||||
import { hideAuth } from './common';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||
import { clearMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import { eCloseLoginDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { SVG } from "./background";
|
||||
import { ForgotPassword } from "./forgot-password";
|
||||
import TwoFactorVerification from "./two-factor";
|
||||
import Animated, {
|
||||
FadeInDown,
|
||||
FadeOutDown,
|
||||
FadeOutUp
|
||||
} from "react-native-reanimated";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { hideAuth } from "./common";
|
||||
export const Login = ({ changeMode, welcome }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const passwordInputRef = useRef();
|
||||
@@ -40,15 +44,15 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const setUser = useUserStore(state => state.setUser);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current) {
|
||||
ToastEvent.show({
|
||||
heading: 'All fields required',
|
||||
message: 'Fill all the fields and try again',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -71,37 +75,41 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
let user;
|
||||
try {
|
||||
if (mfa) {
|
||||
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
|
||||
await db.user.mfaLogin(
|
||||
email.current.toLowerCase(),
|
||||
password.current,
|
||||
mfa
|
||||
);
|
||||
} else {
|
||||
await db.user.login(email.current.toLowerCase(), password.current);
|
||||
}
|
||||
callback && callback(true);
|
||||
|
||||
user = await db.user.getUser();
|
||||
if (!user) throw new Error('Email or password incorrect!');
|
||||
if (!user) throw new Error("Email or password incorrect!");
|
||||
PremiumService.setPremiumStatus();
|
||||
setUser(user);
|
||||
clearMessage();
|
||||
ToastEvent.show({
|
||||
heading: 'Login successful',
|
||||
heading: "Login successful",
|
||||
message: `Logged in as ${user.email}`,
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
hideAuth();
|
||||
SettingsService.set({
|
||||
sessionExpired: false,
|
||||
userEmailConfirmed: user?.isEmailConfirmed
|
||||
});
|
||||
eSendEvent('userLoggedIn', true);
|
||||
eSendEvent("userLoggedIn", true);
|
||||
await sleep(500);
|
||||
Progress.present();
|
||||
} catch (e) {
|
||||
callback && callback(false);
|
||||
if (e.message === 'Multifactor authentication required.') {
|
||||
if (e.message === "Multifactor authentication required.") {
|
||||
setLoading(false);
|
||||
await sleep(300);
|
||||
TwoFactorVerification.present(async mfa => {
|
||||
TwoFactorVerification.present(async (mfa) => {
|
||||
if (mfa) {
|
||||
console.log(mfa);
|
||||
await login(mfa);
|
||||
@@ -112,10 +120,10 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
} else {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: user ? 'Failed to sync' : 'Login failed',
|
||||
heading: user ? "Failed to sync" : "Login failed",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -125,7 +133,9 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
<>
|
||||
<ForgotPassword />
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
|
||||
{loading ? (
|
||||
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||
) : null}
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
@@ -133,23 +143,26 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
minHeight: '100%'
|
||||
width: "100%",
|
||||
minHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: 'hidden'
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
|
||||
<SvgView
|
||||
src={SVG(colors.night ? colors.icon : "black")}
|
||||
height={700}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center',
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: 15
|
||||
@@ -157,7 +170,7 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={30}
|
||||
color={colors.heading}
|
||||
@@ -166,8 +179,8 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: 'underline',
|
||||
textAlign: 'center',
|
||||
textDecorationLine: "underline",
|
||||
textAlign: "center",
|
||||
marginTop: 5
|
||||
}}
|
||||
onPress={() => {
|
||||
@@ -180,20 +193,26 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab ? (focused ? '50%' : '49.99%') : focused ? '100%' : '99.9%',
|
||||
width: DDS.isTab
|
||||
? focused
|
||||
? "50%"
|
||||
: "49.99%"
|
||||
: focused
|
||||
? "100%"
|
||||
: "99.9%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1,
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
fwdRef={emailInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
email.current = value;
|
||||
}}
|
||||
testID="input.email"
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
autoComplete="email"
|
||||
@@ -209,7 +228,7 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
|
||||
<Input
|
||||
fwdRef={passwordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
password.current = value;
|
||||
}}
|
||||
testID="input.password"
|
||||
@@ -226,15 +245,15 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
<Button
|
||||
title="Forgot your password?"
|
||||
style={{
|
||||
alignSelf: 'flex-end',
|
||||
alignSelf: "flex-end",
|
||||
height: 30,
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
SheetManager.show('forgotpassword_sheet', email.current);
|
||||
SheetManager.show("forgotpassword_sheet", email.current);
|
||||
}}
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
type="gray"
|
||||
@@ -244,7 +263,7 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
style={{
|
||||
// position: 'absolute',
|
||||
marginTop: 25,
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -256,7 +275,7 @@ export const Login = ({ changeMode, welcome }) => {
|
||||
onPress={() => login()}
|
||||
// width="100%"
|
||||
type="accent"
|
||||
title={loading ? null : 'Login to your account'}
|
||||
title={loading ? null : "Login to your account"}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Modal, View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import BiometricService from '../../services/biometrics';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Modal, View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from '../../services/event-manager';
|
||||
import { clearMessage } from '../../services/message';
|
||||
import PremiumService from '../../services/premium';
|
||||
import Sync from '../../services/sync';
|
||||
import { db } from '../../common/database';
|
||||
import { MMKV } from '../../common/database/mmkv';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import { Button } from '../ui/button';
|
||||
import { Dialog } from '../dialog';
|
||||
import { presentDialog } from '../dialog/functions';
|
||||
import Input from '../ui/input';
|
||||
import { Toast } from '../toast';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import SettingsService from '../../services/settings';
|
||||
import TwoFactorVerification from './two-factor';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { Progress } from '../sheets/progress';
|
||||
} from "../../services/event-manager";
|
||||
import { clearMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import Sync from "../../services/sync";
|
||||
import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { Button } from "../ui/button";
|
||||
import { Dialog } from "../dialog";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import Input from "../ui/input";
|
||||
import { Toast } from "../toast";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import SettingsService from "../../services/settings";
|
||||
import TwoFactorVerification from "./two-factor";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Progress } from "../sheets/progress";
|
||||
|
||||
function getEmail(email) {
|
||||
if (!email) return null;
|
||||
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
|
||||
for (let i = 0; i < gp3.length; i++) {
|
||||
gp2 += '*';
|
||||
gp2 += "*";
|
||||
}
|
||||
return gp2;
|
||||
});
|
||||
}
|
||||
|
||||
export const SessionExpired = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const passwordInputRef = useRef();
|
||||
@@ -49,7 +49,7 @@ export const SessionExpired = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
const setUser = useUserStore(state => state.setUser);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -65,27 +65,28 @@ export const SessionExpired = () => {
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent('session_expired', open);
|
||||
eSubscribeEvent("session_expired", open);
|
||||
return () => {
|
||||
eUnSubscribeEvent('session_expired', open);
|
||||
eUnSubscribeEvent("session_expired", open);
|
||||
setFocused(false);
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const open = async () => {
|
||||
try {
|
||||
console.log('REQUESTING NEW TOKEN');
|
||||
console.log("REQUESTING NEW TOKEN");
|
||||
let res = await db.user.tokenManager.getToken();
|
||||
if (!res) throw new Error('no token found');
|
||||
if (db.user.tokenManager._isTokenExpired(res)) throw new Error('token expired');
|
||||
Sync.run('global', false, true, async complete => {
|
||||
if (!res) throw new Error("no token found");
|
||||
if (db.user.tokenManager._isTokenExpired(res))
|
||||
throw new Error("token expired");
|
||||
Sync.run("global", false, true, async (complete) => {
|
||||
if (!complete) {
|
||||
let user = await db.user.getUser();
|
||||
if (!user) return;
|
||||
@@ -113,35 +114,39 @@ export const SessionExpired = () => {
|
||||
let user;
|
||||
try {
|
||||
if (mfa) {
|
||||
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
|
||||
await db.user.mfaLogin(
|
||||
email.current.toLowerCase(),
|
||||
password.current,
|
||||
mfa
|
||||
);
|
||||
} else {
|
||||
await db.user.login(email.current.toLowerCase(), password.current);
|
||||
}
|
||||
callback && callback(true);
|
||||
setVisible(false);
|
||||
user = await db.user.getUser();
|
||||
if (!user) throw new Error('Email or password incorrect!');
|
||||
if (!user) throw new Error("Email or password incorrect!");
|
||||
PremiumService.setPremiumStatus();
|
||||
setUser(user);
|
||||
clearMessage();
|
||||
ToastEvent.show({
|
||||
heading: 'Login successful',
|
||||
heading: "Login successful",
|
||||
message: `Logged in as ${user.email}`,
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
await SettingsService.set({
|
||||
sessionExpired: false,
|
||||
userEmailConfirmed: user?.isEmailConfirmed
|
||||
});
|
||||
eSendEvent('userLoggedIn', true);
|
||||
eSendEvent("userLoggedIn", true);
|
||||
await sleep(500);
|
||||
Progress.present();
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
callback && callback(false);
|
||||
if (e.message === 'Multifactor authentication required.') {
|
||||
TwoFactorVerification.present(async mfa => {
|
||||
if (e.message === "Multifactor authentication required.") {
|
||||
TwoFactorVerification.present(async (mfa) => {
|
||||
if (mfa) {
|
||||
console.log(mfa);
|
||||
await login(mfa);
|
||||
@@ -153,10 +158,10 @@ export const SessionExpired = () => {
|
||||
console.log(e.stack);
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: user ? 'Failed to sync' : 'Login failed',
|
||||
heading: user ? "Failed to sync" : "Login failed",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -174,17 +179,17 @@ export const SessionExpired = () => {
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
width: focused ? '100%' : '99.9%',
|
||||
width: focused ? "100%" : "99.9%",
|
||||
padding: 12,
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginBottom: 20,
|
||||
borderRadius: 10,
|
||||
paddingVertical: 20
|
||||
@@ -204,17 +209,17 @@ export const SessionExpired = () => {
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Your session on this device has expired. Please enter password for{' '}
|
||||
Your session on this device has expired. Please enter password for{" "}
|
||||
{getEmail(email.current)} to continue.
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<Input
|
||||
fwdRef={passwordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
password.current = value;
|
||||
}}
|
||||
returnKeyLabel="Next"
|
||||
@@ -230,32 +235,32 @@ export const SessionExpired = () => {
|
||||
<Button
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={() => login()}
|
||||
type="accent"
|
||||
title={loading ? null : 'Login'}
|
||||
title={loading ? null : "Login"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={() => {
|
||||
presentDialog({
|
||||
context: 'session_expiry',
|
||||
title: 'Logout',
|
||||
context: "session_expiry",
|
||||
title: "Logout",
|
||||
paragraph:
|
||||
'Are you sure you want to logout from this device? Any unsynced changes will be lost.',
|
||||
positiveText: 'Logout',
|
||||
positiveType: 'errorShade',
|
||||
"Are you sure you want to logout from this device? Any unsynced changes will be lost.",
|
||||
positiveText: "Logout",
|
||||
positiveType: "errorShade",
|
||||
positivePress: logout
|
||||
});
|
||||
}}
|
||||
type="errorShade"
|
||||
title={loading ? null : 'Logout from this device'}
|
||||
title={loading ? null : "Logout from this device"}
|
||||
/>
|
||||
</View>
|
||||
<Toast context="local" />
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Dimensions, View } from 'react-native';
|
||||
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
||||
import { clearMessage, setEmailVerifyMessage } from '../../services/message';
|
||||
import Navigation from '../../services/navigation';
|
||||
import PremiumService from '../../services/premium';
|
||||
import SettingsService from '../../services/settings';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import umami from '../../common/analytics';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseLoginDialog } from '../../utils/events';
|
||||
import { openLinkInBrowser } from '../../utils/functions';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import { Button } from '../ui/button';
|
||||
import Input from '../ui/input';
|
||||
import { SvgView } from '../ui/svg';
|
||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { SVG } from './background';
|
||||
import { hideAuth } from './common';
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Dimensions, View } from "react-native";
|
||||
import Animated, {
|
||||
FadeInDown,
|
||||
FadeOutDown,
|
||||
FadeOutUp
|
||||
} from "react-native-reanimated";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||
import Navigation from "../../services/navigation";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import umami from "../../common/analytics";
|
||||
import { db } from "../../common/database";
|
||||
import { eCloseLoginDialog } from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { SVG } from "./background";
|
||||
import { hideAuth } from "./common";
|
||||
|
||||
export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const passwordInputRef = useRef();
|
||||
@@ -35,16 +39,16 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
const confirmPassword = useRef();
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setUser = useUserStore(state => state.setUser);
|
||||
const setLastSynced = useUserStore(state => state.setLastSynced);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current || !confirmPassword.current) {
|
||||
ToastEvent.show({
|
||||
heading: 'All fields required',
|
||||
message: 'Fill all the fields and try again',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "All fields required",
|
||||
message: "Fill all the fields and try again",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -64,7 +68,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
clearMessage();
|
||||
setEmailVerifyMessage();
|
||||
hideAuth();
|
||||
umami.pageView('/account-created', '/welcome/signup');
|
||||
umami.pageView("/account-created", "/welcome/signup");
|
||||
await sleep(300);
|
||||
if (trial) {
|
||||
PremiumService.sheet(null, null, true);
|
||||
@@ -74,17 +78,19 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Signup failed',
|
||||
heading: "Signup failed",
|
||||
message: e.message,
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
|
||||
{loading ? (
|
||||
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||
) : null}
|
||||
<Animated.View
|
||||
entering={FadeInDown}
|
||||
exiting={FadeOutUp}
|
||||
@@ -92,32 +98,35 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
minHeight: '100%'
|
||||
width: "100%",
|
||||
minHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: 'hidden'
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
|
||||
<SvgView
|
||||
src={SVG(colors.night ? colors.icon : "black")}
|
||||
height={700}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center',
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: Dimensions.get('window').height < 700 ? -75 : 15
|
||||
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={30}
|
||||
color={colors.heading}
|
||||
@@ -126,8 +135,8 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: 'underline',
|
||||
textAlign: 'center'
|
||||
textDecorationLine: "underline",
|
||||
textAlign: "center"
|
||||
}}
|
||||
onPress={() => {
|
||||
changeMode(0);
|
||||
@@ -139,20 +148,20 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab ? '50%' : '100%',
|
||||
width: DDS.isTab ? "50%" : "100%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1,
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
fwdRef={emailInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
email.current = value;
|
||||
}}
|
||||
testID="input.email"
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
autoComplete="email"
|
||||
@@ -168,11 +177,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
|
||||
<Input
|
||||
fwdRef={passwordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
password.current = value;
|
||||
}}
|
||||
testID="input.password"
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Next"
|
||||
returnKeyType="next"
|
||||
secureTextEntry
|
||||
@@ -188,11 +197,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
|
||||
<Input
|
||||
fwdRef={confirmPasswordInputRef}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
confirmPassword.current = value;
|
||||
}}
|
||||
testID="input.confirmPassword"
|
||||
onErrorCheck={e => setError(e)}
|
||||
onErrorCheck={(e) => setError(e)}
|
||||
returnKeyLabel="Signup"
|
||||
returnKeyType="done"
|
||||
secureTextEntry
|
||||
@@ -208,7 +217,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
<View
|
||||
style={{
|
||||
marginTop: 25,
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -219,42 +228,42 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
||||
loading={loading}
|
||||
onPress={signup}
|
||||
type="accent"
|
||||
title={loading ? null : 'Agree and continue'}
|
||||
title={loading ? null : "Agree and continue"}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
position: 'absolute',
|
||||
textAlign: "center",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
marginBottom: 20
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
>
|
||||
By signing up, you agree to our{' '}
|
||||
By signing up, you agree to our{" "}
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser('https://notesnook.com/tos', colors);
|
||||
openLinkInBrowser("https://notesnook.com/tos", colors);
|
||||
}}
|
||||
style={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.accent}
|
||||
>
|
||||
terms of service{' '}
|
||||
terms of service{" "}
|
||||
</Paragraph>
|
||||
and{' '}
|
||||
and{" "}
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser('https://notesnook.com/privacy', colors);
|
||||
openLinkInBrowser("https://notesnook.com/privacy", colors);
|
||||
}}
|
||||
style={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.accent}
|
||||
>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { eCloseProgressDialog } from '../../utils/events';
|
||||
import useTimer from '../../hooks/use-timer';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import Input from '../ui/input';
|
||||
import { PressableButton } from '../ui/pressable';
|
||||
import Seperator from '../ui/seperator';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { db } from '../../common/database/index';
|
||||
import { ToastEvent } from '../../services/event-manager';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eCloseProgressDialog } from "../../utils/events";
|
||||
import useTimer from "../../hooks/use-timer";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { db } from "../../common/database/index";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
|
||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const code = useRef();
|
||||
const [currentMethod, setCurrentMethod] = useState({
|
||||
method: mfaInfo?.primaryMethod,
|
||||
@@ -28,10 +28,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
const [sending, setSending] = useState(false);
|
||||
|
||||
const codeHelpText = {
|
||||
app: 'Enter the 6 digit code from your authenticator app to continue logging in',
|
||||
sms: 'Enter the 6 digit code sent to your phone number to continue logging in',
|
||||
email: 'Enter the 6 digit code sent to your email to continue logging in',
|
||||
recoveryCode: 'Enter the 8 digit recovery code to continue logging in'
|
||||
app: "Enter the 6 digit code from your authenticator app to continue logging in",
|
||||
sms: "Enter the 6 digit code sent to your phone number to continue logging in",
|
||||
email: "Enter the 6 digit code sent to your email to continue logging in",
|
||||
recoveryCode: "Enter the 8 digit recovery code to continue logging in"
|
||||
};
|
||||
|
||||
const secondaryMethodsText = {
|
||||
@@ -42,7 +42,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
};
|
||||
|
||||
const onNext = async () => {
|
||||
const length = currentMethod.method === 'recoveryCode' ? 8 : 6;
|
||||
const length = currentMethod.method === "recoveryCode" ? 8 : 6;
|
||||
|
||||
if (!code.current || code.current.length !== length) return;
|
||||
console.log(currentMethod.method, code.current);
|
||||
@@ -53,10 +53,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
method: currentMethod.method,
|
||||
code: code.current
|
||||
},
|
||||
result => {
|
||||
console.log('result recieved');
|
||||
(result) => {
|
||||
console.log("result recieved");
|
||||
if (result) {
|
||||
eSendEvent(eCloseProgressDialog, 'two_factor_verify');
|
||||
eSendEvent(eCloseProgressDialog, "two_factor_verify");
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -73,38 +73,38 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
|
||||
const methods = [
|
||||
{
|
||||
id: 'sms',
|
||||
title: 'Send code via SMS',
|
||||
icon: 'message-plus-outline'
|
||||
id: "sms",
|
||||
title: "Send code via SMS",
|
||||
icon: "message-plus-outline"
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
title: 'Send code via email',
|
||||
icon: 'email-outline'
|
||||
id: "email",
|
||||
title: "Send code via email",
|
||||
icon: "email-outline"
|
||||
},
|
||||
{
|
||||
id: 'app',
|
||||
title: 'Enter code from authenticator app',
|
||||
icon: 'cellphone-key'
|
||||
id: "app",
|
||||
title: "Enter code from authenticator app",
|
||||
icon: "cellphone-key"
|
||||
},
|
||||
{
|
||||
id: 'recoveryCode',
|
||||
title: 'I have a recovery code',
|
||||
icon: 'key'
|
||||
id: "recoveryCode",
|
||||
title: "I have a recovery code",
|
||||
icon: "key"
|
||||
}
|
||||
];
|
||||
|
||||
const getMethods = () => {
|
||||
return methods.filter(
|
||||
m =>
|
||||
(m) =>
|
||||
m.id === mfaInfo?.primaryMethod ||
|
||||
m.id === mfaInfo?.secondaryMethod ||
|
||||
m.id === 'recoveryCode'
|
||||
m.id === "recoveryCode"
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentMethod.method === 'sms' || currentMethod.method === 'email') {
|
||||
if (currentMethod.method === "sms" || currentMethod.method === "email") {
|
||||
onSendCode();
|
||||
}
|
||||
}, [currentMethod.method]);
|
||||
@@ -114,13 +114,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
// TODO
|
||||
setSending(true);
|
||||
try {
|
||||
console.log('sending code', currentMethod.method, mfaInfo.token);
|
||||
console.log("sending code", currentMethod.method, mfaInfo.token);
|
||||
await db.mfa.sendCode(currentMethod.method, mfaInfo.token);
|
||||
start(60);
|
||||
setSending(false);
|
||||
} catch (e) {
|
||||
setSending(false);
|
||||
ToastEvent.error(e, 'Error sending 2FA Code', 'local');
|
||||
ToastEvent.error(e, "Error sending 2FA Code", "local");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
<View>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
alignItems: "center",
|
||||
paddingHorizontal: currentMethod.method ? 12 : 0
|
||||
}}
|
||||
>
|
||||
@@ -143,29 +143,34 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
/>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{currentMethod.method
|
||||
? 'Two factor authentication'
|
||||
: 'Select methods for two-factor authentication'}
|
||||
? "Two factor authentication"
|
||||
: "Select methods for two-factor authentication"}
|
||||
</Heading>
|
||||
<Paragraph
|
||||
style={{
|
||||
width: '80%',
|
||||
textAlign: 'center'
|
||||
width: "80%",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{codeHelpText[currentMethod.method] || `Select how you would like to recieve the code`}
|
||||
{codeHelpText[currentMethod.method] ||
|
||||
`Select how you would like to recieve the code`}
|
||||
</Paragraph>
|
||||
|
||||
<Seperator />
|
||||
|
||||
{currentMethod.method === 'sms' || currentMethod.method === 'email' ? (
|
||||
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
|
||||
<Button
|
||||
onPress={onSendCode}
|
||||
type={seconds ? 'gray' : 'transparent'}
|
||||
title={sending ? '' : `${seconds ? `Resend code in (${seconds})` : 'Send code'}`}
|
||||
type={seconds ? "gray" : "transparent"}
|
||||
title={
|
||||
sending
|
||||
? ""
|
||||
: `${seconds ? `Resend code in (${seconds})` : "Send code"}`
|
||||
}
|
||||
loading={sending}
|
||||
height={30}
|
||||
/>
|
||||
@@ -176,11 +181,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
{currentMethod.method ? (
|
||||
<>
|
||||
<Input
|
||||
placeholder={currentMethod.method === 'recoveryCode' ? 'xxxxxxxx' : 'xxxxxx'}
|
||||
maxLength={currentMethod.method === 'recoveryCode' ? 8 : 6}
|
||||
placeholder={
|
||||
currentMethod.method === "recoveryCode" ? "xxxxxxxx" : "xxxxxx"
|
||||
}
|
||||
maxLength={currentMethod.method === "recoveryCode" ? 8 : 6}
|
||||
fwdRef={inputRef}
|
||||
textAlign="center"
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
code.current = value;
|
||||
onNext();
|
||||
}}
|
||||
@@ -188,22 +195,24 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
inputStyle={{
|
||||
fontSize: SIZE.lg,
|
||||
height: 60,
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
letterSpacing: 10,
|
||||
width: null
|
||||
}}
|
||||
keyboardType={currentMethod.method === 'recoveryCode' ? 'default' : 'numeric'}
|
||||
keyboardType={
|
||||
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
||||
}
|
||||
containerStyle={{
|
||||
height: 60,
|
||||
borderWidth: 0,
|
||||
//@ts-ignore
|
||||
width: null,
|
||||
minWidth: '50%'
|
||||
minWidth: "50%"
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
title={loading ? null : 'Next'}
|
||||
title={loading ? null : "Next"}
|
||||
type="accent"
|
||||
width={250}
|
||||
loading={loading}
|
||||
@@ -223,7 +232,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{getMethods().map(item => (
|
||||
{getMethods().map((item) => (
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
onPress={() => {
|
||||
@@ -236,11 +245,11 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
marginTop: 0,
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
borderRadius: 0,
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-start'
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -271,12 +280,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
};
|
||||
|
||||
TwoFactorVerification.present = (onMfaLogin, data, context) => {
|
||||
console.log('presenting sheet');
|
||||
console.log("presenting sheet");
|
||||
presentSheet({
|
||||
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
|
||||
context: context || 'two_factor_verify',
|
||||
context: context || "two_factor_verify",
|
||||
onClose: () => {
|
||||
console.log('on close called');
|
||||
console.log("on close called");
|
||||
onMfaLogin();
|
||||
},
|
||||
disableClosing: true
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
|
||||
export const ContainerHeader = ({ children }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
|
||||
return !selectionMode ? (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
width: '100%',
|
||||
overflow: 'hidden'
|
||||
width: "100%",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Keyboard, Platform, View } from 'react-native';
|
||||
import React, { useEffect } from "react";
|
||||
import { Keyboard, Platform, View } from "react-native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from '../../utils';
|
||||
import { normalize, SIZE } from '../../utils/size';
|
||||
import { PressableButton } from '../ui/pressable';
|
||||
} from "react-native-reanimated";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from "../../utils";
|
||||
import { normalize, SIZE } from "../../utils/size";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
|
||||
export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow = false }) => {
|
||||
export const FloatingButton = ({
|
||||
title,
|
||||
onPress,
|
||||
color = "accent",
|
||||
shouldShow = false
|
||||
}) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const translate = useSharedValue(0);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
@@ -48,19 +53,19 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
||||
|
||||
const onKeyboardHide = async () => {
|
||||
editorState().keyboardState = false;
|
||||
if (deviceMode !== 'mobile') return;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(0);
|
||||
};
|
||||
|
||||
const onKeyboardShow = async () => {
|
||||
editorState().keyboardState = true;
|
||||
if (deviceMode !== 'mobile') return;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(150);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let sub1 = Keyboard.addListener('keyboardDidShow', onKeyboardShow);
|
||||
let sub2 = Keyboard.addListener('keyboardDidHide', onKeyboardHide);
|
||||
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
|
||||
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
|
||||
return () => {
|
||||
sub1?.remove();
|
||||
sub2?.remove();
|
||||
@@ -72,13 +77,13 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
||||
iPad: 20
|
||||
};
|
||||
|
||||
return deviceMode !== 'mobile' && !shouldShow ? null : (
|
||||
return deviceMode !== "mobile" && !shouldShow ? null : (
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
right: 12,
|
||||
bottom: paddings[Platform.isPad ? 'iPad' : Platform.OS],
|
||||
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
|
||||
zIndex: 10
|
||||
},
|
||||
animatedStyle
|
||||
@@ -87,27 +92,27 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
||||
<PressableButton
|
||||
testID={notesnook.buttons.add}
|
||||
type="accent"
|
||||
accentColor={color || 'accent'}
|
||||
accentColor={color || "accent"}
|
||||
accentText="light"
|
||||
customStyle={{
|
||||
...getElevation(5),
|
||||
borderRadius: 100
|
||||
}}
|
||||
onLongPress={event => {
|
||||
onLongPress={(event) => {
|
||||
showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
|
||||
}}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: normalize(60),
|
||||
width: normalize(60)
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={title === 'Clear all trash' ? 'delete' : 'plus'}
|
||||
name={title === "Clear all trash" ? "delete" : "plus"}
|
||||
color="white"
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react';
|
||||
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
|
||||
import { Header } from '../header';
|
||||
import SelectionHeader from '../selection-header';
|
||||
import React from "react";
|
||||
import { KeyboardAvoidingView, Platform, SafeAreaView } from "react-native";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { Header } from "../header";
|
||||
import SelectionHeader from "../selection-header";
|
||||
export const Container = ({ children }) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
enabled={Platform.OS === 'ios' && !floating}
|
||||
enabled={Platform.OS === "ios" && !floating}
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
@@ -18,7 +20,7 @@ export const Container = ({ children }) => {
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden'
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
{!introCompleted ? null : (
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { COLORS_NOTE } from '../../utils/color-scheme';
|
||||
import { hexToRGBA } from '../../utils/color-scheme/utils';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { COLORS_NOTE } from "../../utils/color-scheme";
|
||||
import { hexToRGBA } from "../../utils/color-scheme/utils";
|
||||
|
||||
export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const message = useMessageStore(state => state.message);
|
||||
const annoucements = useMessageStore(state => state.announcements);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const message = useMessageStore((state) => state.message);
|
||||
const annoucements = useMessageStore((state) => state.announcements);
|
||||
const hasAnnoucements = annoucements.length > 0;
|
||||
//@ts-ignore
|
||||
const shadeColor = color ? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15) : colors.shade;
|
||||
const shadeColor = color
|
||||
? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15)
|
||||
: colors.shade;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{hasAnnoucements ? (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 100,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
@@ -64,12 +66,12 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
{message ? (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 60,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 20
|
||||
}}
|
||||
>
|
||||
@@ -106,15 +108,15 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 30,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
padding: 5,
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -128,7 +130,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row'
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -161,7 +163,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
width: '85%',
|
||||
width: "85%",
|
||||
height: 13,
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 5,
|
||||
@@ -171,7 +173,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import { ViewProps } from 'react-native';
|
||||
import Animated, { FadeOutUp } from 'react-native-reanimated';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useDelayLayout } from '../../hooks/use-delay-layout';
|
||||
import { DefaultPlaceholder } from './default-placeholder';
|
||||
import { SettingsPlaceholder } from './settings-placeholder';
|
||||
import React from "react";
|
||||
import { ViewProps } from "react-native";
|
||||
import Animated, { FadeOutUp } from "react-native-reanimated";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useDelayLayout } from "../../hooks/use-delay-layout";
|
||||
import { DefaultPlaceholder } from "./default-placeholder";
|
||||
import { SettingsPlaceholder } from "./settings-placeholder";
|
||||
|
||||
interface IDelayLayoutProps extends ViewProps {
|
||||
delay?: number;
|
||||
wait?: boolean;
|
||||
type?: 'default' | 'settings';
|
||||
type?: "default" | "settings";
|
||||
color?: string;
|
||||
animated?: boolean;
|
||||
}
|
||||
@@ -19,10 +19,15 @@ const placeholder = {
|
||||
settings: SettingsPlaceholder
|
||||
};
|
||||
|
||||
export default function DelayLayout({ animated = true, ...props }: IDelayLayoutProps) {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const loading = useDelayLayout(!props.delay || props.delay < 300 ? 300 : props.delay);
|
||||
const Placeholder = placeholder[props.type || 'default'];
|
||||
export default function DelayLayout({
|
||||
animated = true,
|
||||
...props
|
||||
}: IDelayLayoutProps) {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const loading = useDelayLayout(
|
||||
!props.delay || props.delay < 300 ? 300 : props.delay
|
||||
);
|
||||
const Placeholder = placeholder[props.type || "default"];
|
||||
|
||||
return loading || props.wait ? (
|
||||
<Animated.View
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
|
||||
export const SettingsPlaceholder = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<View>
|
||||
@@ -19,12 +19,12 @@ export const SettingsPlaceholder = () => {
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 60,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 16
|
||||
}}
|
||||
>
|
||||
@@ -60,14 +60,14 @@ export const SettingsPlaceholder = () => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 60,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'space-between'
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -106,8 +106,8 @@ export const SettingsPlaceholder = () => {
|
||||
backgroundColor: colors.nav,
|
||||
borderRadius: 100,
|
||||
marginLeft: 15,
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 4
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React from 'react';
|
||||
import { useNoteStore } from '../../stores/use-notes-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { AnnouncementDialog } from '../announcements';
|
||||
import { AttachmentDialog } from '../attachments';
|
||||
import Auth from '../auth';
|
||||
import AuthModal from '../auth/auth-modal';
|
||||
import { SessionExpired } from '../auth/session-expired';
|
||||
import { Dialog } from '../dialog';
|
||||
import { AddTopicDialog } from '../dialogs/add-topic';
|
||||
import ResultDialog from '../dialogs/result';
|
||||
import { VaultDialog } from '../dialogs/vault';
|
||||
import ImagePreview from '../image-preview';
|
||||
import MergeConflicts from '../merge-conflicts';
|
||||
import PremiumDialog from '../premium';
|
||||
import { Expiring } from '../premium/expiring';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { AddNotebookSheet } from '../sheets/add-notebook';
|
||||
import AddToNotebookSheet from '../sheets/add-to';
|
||||
import ExportNotesSheet from '../sheets/export-notes';
|
||||
import ManageTagsSheet from '../sheets/manage-tags';
|
||||
import PublishNoteSheet from '../sheets/publish-note';
|
||||
import RateAppSheet from '../sheets/rate-app';
|
||||
import RecoveryKeySheet from '../sheets/recovery-key';
|
||||
import RestoreDataSheet from '../sheets/restore-data';
|
||||
import React from "react";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { AnnouncementDialog } from "../announcements";
|
||||
import { AttachmentDialog } from "../attachments";
|
||||
import Auth from "../auth";
|
||||
import AuthModal from "../auth/auth-modal";
|
||||
import { SessionExpired } from "../auth/session-expired";
|
||||
import { Dialog } from "../dialog";
|
||||
import { AddTopicDialog } from "../dialogs/add-topic";
|
||||
import ResultDialog from "../dialogs/result";
|
||||
import { VaultDialog } from "../dialogs/vault";
|
||||
import ImagePreview from "../image-preview";
|
||||
import MergeConflicts from "../merge-conflicts";
|
||||
import PremiumDialog from "../premium";
|
||||
import { Expiring } from "../premium/expiring";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { AddNotebookSheet } from "../sheets/add-notebook";
|
||||
import AddToNotebookSheet from "../sheets/add-to";
|
||||
import ExportNotesSheet from "../sheets/export-notes";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
import PublishNoteSheet from "../sheets/publish-note";
|
||||
import RateAppSheet from "../sheets/rate-app";
|
||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||
import RestoreDataSheet from "../sheets/restore-data";
|
||||
|
||||
const DialogProvider = React.memo(
|
||||
() => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const loading = useNoteStore(state => state.loading);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { eSendEvent } from '../../services/event-manager';
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import {
|
||||
eCloseActionSheet,
|
||||
eCloseAddNotebookDialog,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
eOpenAddNotebookDialog,
|
||||
eOpenAddTopicDialog,
|
||||
eOpenMoveNoteDialog
|
||||
} from '../../utils/events';
|
||||
} from "../../utils/events";
|
||||
|
||||
export const ActionSheetEvent = (item, buttons) => {
|
||||
eSendEvent(eOpenActionSheet, {
|
||||
@@ -27,15 +27,15 @@ export const moveNoteHideEvent = () => {
|
||||
eSendEvent(eCloseMoveNoteDialog);
|
||||
};
|
||||
|
||||
export const AddNotebookEvent = notebook => {
|
||||
export const AddNotebookEvent = (notebook) => {
|
||||
eSendEvent(eOpenAddNotebookDialog, notebook);
|
||||
};
|
||||
export const HideAddNotebookEvent = notebook => {
|
||||
export const HideAddNotebookEvent = (notebook) => {
|
||||
eSendEvent(eCloseAddNotebookDialog, notebook);
|
||||
};
|
||||
export const AddTopicEvent = topic => {
|
||||
export const AddTopicEvent = (topic) => {
|
||||
eSendEvent(eOpenAddTopicDialog, topic);
|
||||
};
|
||||
export const HideAddTopicEvent = notebook => {
|
||||
export const HideAddTopicEvent = (notebook) => {
|
||||
eSendEvent(eCloseAddTopicDialog, notebook);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
@@ -7,17 +7,17 @@ import {
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
|
||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
||||
} from "react-native";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
|
||||
const BaseDialog = ({
|
||||
visible,
|
||||
onRequestClose,
|
||||
children,
|
||||
onShow,
|
||||
animation = 'fade',
|
||||
animation = "fade",
|
||||
premium,
|
||||
statusBarTranslucent = true,
|
||||
transparent,
|
||||
@@ -46,11 +46,11 @@ const BaseDialog = ({
|
||||
animated
|
||||
statusBarTranslucent={statusBarTranslucent}
|
||||
supportedOrientations={[
|
||||
'portrait',
|
||||
'portrait-upside-down',
|
||||
'landscape',
|
||||
'landscape-left',
|
||||
'landscape-right'
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
"landscape",
|
||||
"landscape-left",
|
||||
"landscape-right"
|
||||
]}
|
||||
onShow={() => {
|
||||
if (onShow) {
|
||||
@@ -67,10 +67,17 @@ const BaseDialog = ({
|
||||
>
|
||||
<Wrapper
|
||||
style={{
|
||||
backgroundColor: background ? background : transparent ? 'transparent' : 'rgba(0,0,0,0.3)'
|
||||
backgroundColor: background
|
||||
? background
|
||||
: transparent
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView enabled={!floating && Platform.OS === 'ios'} behavior="padding">
|
||||
<KeyboardAvoidingView
|
||||
enabled={!floating && Platform.OS === "ios"}
|
||||
behavior="padding"
|
||||
>
|
||||
<BouncingView
|
||||
duration={400}
|
||||
animated={animated}
|
||||
@@ -78,7 +85,11 @@ const BaseDialog = ({
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
justifyContent: centered ? 'center' : bottom ? 'flex-end' : 'flex-start'
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
}
|
||||
]}
|
||||
>
|
||||
@@ -97,15 +108,15 @@ const BaseDialog = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
},
|
||||
overlayButton: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { Button } from '../ui/button';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import React from "react";
|
||||
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
|
||||
const DialogButtons = ({
|
||||
onPressPositive,
|
||||
onPressNegative,
|
||||
positiveTitle,
|
||||
negativeTitle = 'Cancel',
|
||||
negativeTitle = "Cancel",
|
||||
loading,
|
||||
doneText,
|
||||
positiveType
|
||||
}) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -36,12 +36,16 @@ const DialogButtons = ({
|
||||
) : doneText ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon color={colors.accent} name="check-circle-outline" size={SIZE.md} />
|
||||
<Paragraph color={colors.accent}>{' ' + doneText}</Paragraph>
|
||||
<Icon
|
||||
color={colors.accent}
|
||||
name="check-circle-outline"
|
||||
size={SIZE.md}
|
||||
/>
|
||||
<Paragraph color={colors.accent}>{" " + doneText}</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<View />
|
||||
@@ -49,8 +53,8 @@ const DialogButtons = ({
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -70,7 +74,7 @@ const DialogButtons = ({
|
||||
marginLeft: 10
|
||||
}}
|
||||
bold
|
||||
type={positiveType || 'transparent'}
|
||||
type={positiveType || "transparent"}
|
||||
title={positiveTitle}
|
||||
/>
|
||||
) : null}
|
||||
@@ -83,9 +87,9 @@ export default DialogButtons;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
marginTop: 10
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { getElevation } from '../../utils';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { getElevation } from "../../utils";
|
||||
|
||||
const DialogContainer = ({ width, height, ...restProps }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<View
|
||||
{...restProps}
|
||||
style={{
|
||||
...getElevation(5),
|
||||
width: width || DDS.isTab ? 500 : '85%',
|
||||
width: width || DDS.isTab ? 500 : "85%",
|
||||
maxHeight: height || 450,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { Button } from '../ui/button';
|
||||
import { PressableButtonProps } from '../ui/pressable';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import { PressableButtonProps } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
type DialogHeaderProps = {
|
||||
icon?: string;
|
||||
@@ -16,7 +16,7 @@ type DialogHeaderProps = {
|
||||
onPress?: () => void;
|
||||
loading?: boolean;
|
||||
title?: string;
|
||||
type?: PressableButtonProps['type'];
|
||||
type?: PressableButtonProps["type"];
|
||||
};
|
||||
paragraphColor?: string;
|
||||
padding?: number;
|
||||
@@ -34,33 +34,39 @@ const DialogHeader = ({
|
||||
centered,
|
||||
titlePart
|
||||
}: DialogHeaderProps) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
minHeight: 50,
|
||||
paddingHorizontal: padding
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: centered ? 'center' : 'space-between',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
justifyContent: centered ? "center" : "space-between",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading style={{ textAlign: centered ? 'center' : 'left' }} size={SIZE.lg}>
|
||||
{title} {titlePart ? <Text style={{ color: colors.accent }}>{titlePart}</Text> : null}
|
||||
<Heading
|
||||
style={{ textAlign: centered ? "center" : "left" }}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
{title}{" "}
|
||||
{titlePart ? (
|
||||
<Text style={{ color: colors.accent }}>{titlePart}</Text>
|
||||
) : null}
|
||||
</Heading>
|
||||
|
||||
{button ? (
|
||||
@@ -73,7 +79,7 @@ const DialogHeader = ({
|
||||
loading={button.loading}
|
||||
fontSize={13}
|
||||
title={button.title}
|
||||
type={button.type || 'grayBg'}
|
||||
type={button.type || "grayBg"}
|
||||
height={25}
|
||||
/>
|
||||
) : null}
|
||||
@@ -82,9 +88,9 @@ const DialogHeader = ({
|
||||
{paragraph ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: centered ? 'center' : 'left',
|
||||
maxWidth: centered ? '90%' : '100%',
|
||||
alignSelf: centered ? 'center' : 'flex-start'
|
||||
textAlign: centered ? "center" : "left",
|
||||
maxWidth: centered ? "90%" : "100%",
|
||||
alignSelf: centered ? "center" : "flex-start"
|
||||
}}
|
||||
color={paragraphColor || colors.icon}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { eSendEvent } from '../../services/event-manager';
|
||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
|
||||
|
||||
type DialogInfo = {
|
||||
title?: string;
|
||||
@@ -9,20 +9,20 @@ type DialogInfo = {
|
||||
positivePress?: (value: any) => void;
|
||||
onClose?: () => void;
|
||||
positiveType?:
|
||||
| 'transparent'
|
||||
| 'gray'
|
||||
| 'grayBg'
|
||||
| 'accent'
|
||||
| 'inverted'
|
||||
| 'shade'
|
||||
| 'error'
|
||||
| 'errorShade';
|
||||
| "transparent"
|
||||
| "gray"
|
||||
| "grayBg"
|
||||
| "accent"
|
||||
| "inverted"
|
||||
| "shade"
|
||||
| "error"
|
||||
| "errorShade";
|
||||
icon?: string;
|
||||
paragraphColor: string;
|
||||
input: boolean;
|
||||
inputPlaceholder: string;
|
||||
defaultValue: string;
|
||||
context: 'global' | 'local';
|
||||
context: "global" | "local";
|
||||
};
|
||||
|
||||
export function presentDialog(data: Partial<DialogInfo>): void {
|
||||
|
||||
@@ -1,36 +1,39 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { getElevation } from '../../utils';
|
||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
|
||||
import { sleep } from '../../utils/time';
|
||||
import Input from '../ui/input';
|
||||
import Seperator from '../ui/seperator';
|
||||
import { Toast } from '../toast';
|
||||
import BaseDialog from './base-dialog';
|
||||
import DialogButtons from './dialog-buttons';
|
||||
import DialogHeader from './dialog-header';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { getElevation } from "../../utils";
|
||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
|
||||
import { sleep } from "../../utils/time";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { Toast } from "../toast";
|
||||
import BaseDialog from "./base-dialog";
|
||||
import DialogButtons from "./dialog-buttons";
|
||||
import DialogHeader from "./dialog-header";
|
||||
|
||||
export const Dialog = ({ context = 'global' }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(null);
|
||||
const inputRef = useRef();
|
||||
const [dialogInfo, setDialogInfo] = useState({
|
||||
title: '',
|
||||
paragraph: '',
|
||||
positiveText: 'Done',
|
||||
negativeText: 'Cancel',
|
||||
title: "",
|
||||
paragraph: "",
|
||||
positiveText: "Done",
|
||||
negativeText: "Cancel",
|
||||
positivePress: () => {},
|
||||
onClose: () => {},
|
||||
positiveType: 'transparent',
|
||||
positiveType: "transparent",
|
||||
icon: null,
|
||||
paragraphColor: colors.pri,
|
||||
input: false,
|
||||
inputPlaceholder: 'Enter some text',
|
||||
defaultValue: '',
|
||||
inputPlaceholder: "Enter some text",
|
||||
defaultValue: "",
|
||||
disableBackdropClosing: false
|
||||
});
|
||||
|
||||
@@ -47,7 +50,9 @@ export const Dialog = ({ context = 'global' }) => {
|
||||
const onPressPositive = async () => {
|
||||
if (dialogInfo.positivePress) {
|
||||
inputRef.current?.blur();
|
||||
let result = await dialogInfo.positivePress(inputValue || dialogInfo.defaultValue);
|
||||
let result = await dialogInfo.positivePress(
|
||||
inputValue || dialogInfo.defaultValue
|
||||
);
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
@@ -56,8 +61,8 @@ export const Dialog = ({ context = 'global' }) => {
|
||||
hide();
|
||||
};
|
||||
|
||||
const show = data => {
|
||||
if (!data.context) data.context = 'global';
|
||||
const show = (data) => {
|
||||
if (!data.context) data.context = "global";
|
||||
if (data.context !== context) return;
|
||||
setDialogInfo(data);
|
||||
setVisible(true);
|
||||
@@ -78,7 +83,7 @@ export const Dialog = ({ context = 'global' }) => {
|
||||
|
||||
const style = {
|
||||
...getElevation(5),
|
||||
width: DDS.isTab ? 400 : '85%',
|
||||
width: DDS.isTab ? 400 : "85%",
|
||||
maxHeight: 450,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
@@ -121,7 +126,7 @@ export const Dialog = ({ context = 'global' }) => {
|
||||
<Input
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
setInputValue(value);
|
||||
}}
|
||||
testID="input-value"
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import React, { createRef } from 'react';
|
||||
import { Keyboard, LayoutAnimation, UIManager, View } from 'react-native';
|
||||
import { Transition, Transitioning, TransitioningView } from 'react-native-reanimated';
|
||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent, ToastEvent } from '../../../services/event-manager';
|
||||
import Navigation from '../../../services/navigation';
|
||||
import { db } from '../../../common/database';
|
||||
import { eCloseAddTopicDialog, eOpenAddTopicDialog } from '../../../utils/events';
|
||||
import { sleep } from '../../../utils/time';
|
||||
import BaseDialog from '../../dialog/base-dialog';
|
||||
import DialogButtons from '../../dialog/dialog-buttons';
|
||||
import DialogContainer from '../../dialog/dialog-container';
|
||||
import DialogHeader from '../../dialog/dialog-header';
|
||||
import Input from '../../ui/input';
|
||||
import Seperator from '../../ui/seperator';
|
||||
import { Toast } from '../../toast';
|
||||
import React, { createRef } from "react";
|
||||
import { Keyboard, LayoutAnimation, UIManager, View } from "react-native";
|
||||
import {
|
||||
Transition,
|
||||
Transitioning,
|
||||
TransitioningView
|
||||
} from "react-native-reanimated";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eCloseAddTopicDialog,
|
||||
eOpenAddTopicDialog
|
||||
} from "../../../utils/events";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import DialogButtons from "../../dialog/dialog-buttons";
|
||||
import DialogContainer from "../../dialog/dialog-container";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import Input from "../../ui/input";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import { Toast } from "../../toast";
|
||||
|
||||
export class AddTopicDialog extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -34,11 +45,11 @@ export class AddTopicDialog extends React.Component {
|
||||
addNewTopic = async () => {
|
||||
try {
|
||||
this.setState({ loading: true });
|
||||
if (!this.title || this.title?.trim() === '') {
|
||||
if (!this.title || this.title?.trim() === "") {
|
||||
ToastEvent.show({
|
||||
heading: 'Topic title is required',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Topic title is required",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
@@ -54,7 +65,7 @@ export class AddTopicDialog extends React.Component {
|
||||
}
|
||||
this.setState({ loading: false });
|
||||
this.close();
|
||||
Navigation.queueRoutesForUpdate('Notebooks', 'Notebook', 'TopicNotes');
|
||||
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
|
||||
useMenuStore.getState().setMenuPins();
|
||||
} catch (e) {}
|
||||
};
|
||||
@@ -116,9 +127,11 @@ export class AddTopicDialog extends React.Component {
|
||||
<DialogContainer>
|
||||
<DialogHeader
|
||||
icon="book-outline"
|
||||
title={this.toEdit ? 'Edit topic' : 'New topic'}
|
||||
title={this.toEdit ? "Edit topic" : "New topic"}
|
||||
paragraph={
|
||||
this.toEdit ? 'Edit title of the topic' : 'Add a new topic in ' + this.notebook.title
|
||||
this.toEdit
|
||||
? "Edit title of the topic"
|
||||
: "Add a new topic in " + this.notebook.title
|
||||
}
|
||||
padding={12}
|
||||
/>
|
||||
@@ -132,7 +145,7 @@ export class AddTopicDialog extends React.Component {
|
||||
<Input
|
||||
fwdRef={this.titleRef}
|
||||
testID="input-title"
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
this.title = value;
|
||||
}}
|
||||
blurOnSubmit={false}
|
||||
@@ -144,7 +157,7 @@ export class AddTopicDialog extends React.Component {
|
||||
</View>
|
||||
|
||||
<DialogButtons
|
||||
positiveTitle={this.toEdit ? 'Save' : 'Add'}
|
||||
positiveTitle={this.toEdit ? "Save" : "Add"}
|
||||
onPressNegative={() => this.close()}
|
||||
onPressPositive={() => this.addNewTopic()}
|
||||
loading={this.state.loading}
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { useMessageStore } from '../../../stores/use-message-store';
|
||||
import { DDS } from '../../../services/device-detection';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { getElevation } from '../../../utils';
|
||||
import { eCloseJumpToDialog, eOpenJumpToDialog, eScrollEvent } from '../../../utils/events';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import BaseDialog from '../../dialog/base-dialog';
|
||||
import { PressableButton } from '../../ui/pressable';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useMessageStore } from "../../../stores/use-message-store";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { getElevation } from "../../../utils";
|
||||
import {
|
||||
eCloseJumpToDialog,
|
||||
eOpenJumpToDialog,
|
||||
eScrollEvent
|
||||
} from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
const offsets = [];
|
||||
let timeout = null;
|
||||
const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const notes = data;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState(null);
|
||||
|
||||
const onPress = (item, index) => {
|
||||
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
|
||||
let ind = notes.findIndex(
|
||||
(i) => i.title === item.title && i.type === "header"
|
||||
);
|
||||
console.log(scrollRef.current);
|
||||
scrollRef.current?.scrollToIndex({
|
||||
index: ind,
|
||||
@@ -41,7 +50,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onScroll = data => {
|
||||
const onScroll = (data) => {
|
||||
let y = data.y;
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
@@ -53,7 +62,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const open = _type => {
|
||||
const open = (_type) => {
|
||||
if (_type !== type) return;
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -68,10 +77,12 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
|
||||
const loadOffsets = () => {
|
||||
notes
|
||||
.filter(i => i.type === 'header')
|
||||
.filter((i) => i.type === "header")
|
||||
.map((item, index) => {
|
||||
let offset = 35 * index;
|
||||
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
|
||||
let ind = notes.findIndex(
|
||||
(i) => i.title === item.title && i.type === "header"
|
||||
);
|
||||
let messageState = useMessageStore.getState().message;
|
||||
let msgOffset = messageState?.visible ? 60 : 10;
|
||||
ind = ind + 1;
|
||||
@@ -92,41 +103,41 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
<View
|
||||
style={{
|
||||
...getElevation(5),
|
||||
width: DDS.isTab ? 500 : '85%',
|
||||
width: DDS.isTab ? 500 : "85%",
|
||||
backgroundColor: colors.bg,
|
||||
zIndex: 100,
|
||||
bottom: 20,
|
||||
maxHeight: '65%',
|
||||
maxHeight: "65%",
|
||||
borderRadius: 10,
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
padding: 10,
|
||||
paddingTop: 30
|
||||
}}
|
||||
>
|
||||
<ScrollView
|
||||
style={{
|
||||
maxHeight: '100%'
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
alignSelf: "center",
|
||||
justifyContent: "center",
|
||||
paddingBottom: 20
|
||||
}}
|
||||
>
|
||||
{notes
|
||||
.filter(i => i.type === 'header')
|
||||
.filter((i) => i.type === "header")
|
||||
.map((item, index) => {
|
||||
return item.title ? (
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
onPress={() => onPress(item, index)}
|
||||
type={currentIndex === index ? 'accent' : 'transparent'}
|
||||
type={currentIndex === index ? "accent" : "transparent"}
|
||||
customStyle={{
|
||||
minWidth: '20%',
|
||||
minWidth: "20%",
|
||||
width: null,
|
||||
paddingHorizontal: 12,
|
||||
margin: 5,
|
||||
@@ -137,9 +148,11 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
color={currentIndex === index ? colors.light : colors.accent}
|
||||
color={
|
||||
currentIndex === index ? colors.light : colors.accent
|
||||
}
|
||||
style={{
|
||||
textAlign: 'center'
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { DDS } from '../../../services/device-detection';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { getElevation } from '../../../utils';
|
||||
import { eCloseResultDialog, eOpenResultDialog } from '../../../utils/events';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { Button } from '../../ui/button';
|
||||
import BaseDialog from '../../dialog/base-dialog';
|
||||
import Seperator from '../../ui/seperator';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import { ProFeatures } from './pro-features';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { getElevation } from "../../../utils";
|
||||
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { ProFeatures } from "./pro-features";
|
||||
|
||||
const ResultDialog = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [dialogData, setDialogData] = useState({
|
||||
title: 'Thank you for signing up!',
|
||||
paragraph: 'Try out all features of Notesnook free for 7 days. No limitations. No commitments.',
|
||||
button: 'Start taking notes'
|
||||
title: "Thank you for signing up!",
|
||||
paragraph:
|
||||
"Try out all features of Notesnook free for 7 days. No limitations. No commitments.",
|
||||
button: "Start taking notes"
|
||||
});
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenResultDialog, open);
|
||||
@@ -30,7 +34,7 @@ const ResultDialog = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = data => {
|
||||
const open = (data) => {
|
||||
if (data) {
|
||||
setDialogData(data);
|
||||
}
|
||||
@@ -46,23 +50,23 @@ const ResultDialog = () => {
|
||||
<View
|
||||
style={{
|
||||
...getElevation(5),
|
||||
width: DDS.isTab ? 350 : '85%',
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
maxHeight: 500,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
paddingTop: 20,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
size={SIZE.lg}
|
||||
textBreakStrategy="balanced"
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
marginTop: 10,
|
||||
maxWidth: '100%',
|
||||
maxWidth: "100%",
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
@@ -74,9 +78,9 @@ const ResultDialog = () => {
|
||||
color={colors.icon}
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
maxWidth: '80%',
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
maxWidth: "80%",
|
||||
lineHeight: SIZE.sm + 5
|
||||
}}
|
||||
>
|
||||
@@ -88,8 +92,8 @@ const ResultDialog = () => {
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<ProFeatures count={4} />
|
||||
@@ -99,7 +103,7 @@ const ResultDialog = () => {
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.nav,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
paddingVertical: 10
|
||||
|
||||
@@ -1,58 +1,63 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { eSendEvent } from '../../../services/event-manager';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import {
|
||||
eCloseProgressDialog,
|
||||
eCloseResultDialog,
|
||||
eOpenPremiumDialog
|
||||
} from '../../../utils/events';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { sleep } from '../../../utils/time';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
} from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
export const ProFeatures = ({ count = 6 }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<>
|
||||
{[
|
||||
{
|
||||
content: 'Unlock unlimited notebooks, tags, colors. Organize like a pro'
|
||||
content:
|
||||
"Unlock unlimited notebooks, tags, colors. Organize like a pro"
|
||||
},
|
||||
{
|
||||
content: 'Attach files upto 500MB, upload 4K images with unlimited storage'
|
||||
content:
|
||||
"Attach files upto 500MB, upload 4K images with unlimited storage"
|
||||
},
|
||||
{
|
||||
content: 'Instantly sync to unlimited devices'
|
||||
content: "Instantly sync to unlimited devices"
|
||||
},
|
||||
{
|
||||
content: 'A private vault to keep everything imporant always locked'
|
||||
content: "A private vault to keep everything imporant always locked"
|
||||
},
|
||||
{
|
||||
content: 'Rich note editing experience with markdown, tables, checklists and more'
|
||||
content:
|
||||
"Rich note editing experience with markdown, tables, checklists and more"
|
||||
},
|
||||
{
|
||||
content: 'Export your notes in Pdf, markdown and html formats'
|
||||
content: "Export your notes in Pdf, markdown and html formats"
|
||||
}
|
||||
]
|
||||
.slice(0, count)
|
||||
.map(item => (
|
||||
.map((item) => (
|
||||
<View
|
||||
key={item.content}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
height: 40,
|
||||
paddingHorizontal: 0,
|
||||
marginBottom: 10,
|
||||
alignItems: 'center',
|
||||
alignItems: "center",
|
||||
borderRadius: 5,
|
||||
justifyContent: 'flex-start'
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
>
|
||||
<Icon size={SIZE.lg} color={colors.accent} name="check" />
|
||||
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>{item.content}</Paragraph>
|
||||
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>
|
||||
{item.content}
|
||||
</Paragraph>
|
||||
</View>
|
||||
))}
|
||||
<Paragraph
|
||||
@@ -64,7 +69,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
||||
}}
|
||||
size={SIZE.xs + 1}
|
||||
style={{
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationLine: "underline",
|
||||
color: colors.icon
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, { Component, createRef } from 'react';
|
||||
import { InteractionManager, View } from 'react-native';
|
||||
import Share from 'react-native-share';
|
||||
import { notesnook } from '../../../../e2e/test.ids';
|
||||
import { editorController } from '../../../screens/editor/tiptap/utils';
|
||||
import BiometricService from '../../../services/biometrics';
|
||||
import { DDS } from '../../../services/device-detection';
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { Component, createRef } from "react";
|
||||
import { InteractionManager, View } from "react-native";
|
||||
import Share from "react-native-share";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import BiometricService from "../../../services/biometrics";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
ToastEvent
|
||||
} from '../../../services/event-manager';
|
||||
import Navigation from '../../../services/navigation';
|
||||
import SearchService from '../../../services/search';
|
||||
import { getElevation, toTXT } from '../../../utils';
|
||||
import { db } from '../../../common/database';
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
import { getElevation, toTXT } from "../../../utils";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseActionSheet,
|
||||
eCloseVaultDialog,
|
||||
eOnLoadNote,
|
||||
eOpenVaultDialog
|
||||
} from '../../../utils/events';
|
||||
import { deleteItems } from '../../../utils/functions';
|
||||
import { tabBarRef } from '../../../utils/global-refs';
|
||||
import { sleep } from '../../../utils/time';
|
||||
import BaseDialog from '../../dialog/base-dialog';
|
||||
import DialogButtons from '../../dialog/dialog-buttons';
|
||||
import DialogHeader from '../../dialog/dialog-header';
|
||||
import { Toast } from '../../toast';
|
||||
import { Button } from '../../ui/button';
|
||||
import Input from '../../ui/input';
|
||||
import Seperator from '../../ui/seperator';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
} from "../../../utils/events";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import DialogButtons from "../../dialog/dialog-buttons";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Toast } from "../../toast";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
let Keychain;
|
||||
const passInputRef = createRef();
|
||||
@@ -62,7 +62,7 @@ export class VaultDialog extends Component {
|
||||
changePassword: false,
|
||||
copyNote: false,
|
||||
revokeFingerprintAccess: false,
|
||||
title: 'Unlock Note',
|
||||
title: "Unlock Note",
|
||||
description: null,
|
||||
clearVault: false,
|
||||
deleteVault: false,
|
||||
@@ -72,45 +72,45 @@ export class VaultDialog extends Component {
|
||||
this.confirmPassword = null;
|
||||
this.newPassword = null;
|
||||
(this.title = !this.state.novault
|
||||
? 'Create Vault'
|
||||
? "Create Vault"
|
||||
: this.state.fingerprintAccess
|
||||
? 'Vault Fingerprint Unlock'
|
||||
? "Vault Fingerprint Unlock"
|
||||
: this.state.revokeFingerprintAccess
|
||||
? 'Revoke Vault Fingerprint Unlock'
|
||||
? "Revoke Vault Fingerprint Unlock"
|
||||
: this.state.changePassword
|
||||
? 'Change Vault Password'
|
||||
? "Change Vault Password"
|
||||
: this.state.note.locked
|
||||
? this.state.deleteNote
|
||||
? 'Delete note'
|
||||
? "Delete note"
|
||||
: this.state.share
|
||||
? 'Share note'
|
||||
? "Share note"
|
||||
: this.state.copyNote
|
||||
? 'Copy note'
|
||||
? "Copy note"
|
||||
: this.state.goToEditor
|
||||
? 'Unlock note'
|
||||
: 'Unlock note'
|
||||
: 'Lock note'),
|
||||
? "Unlock note"
|
||||
: "Unlock note"
|
||||
: "Lock note"),
|
||||
(this.description = !this.state.novault
|
||||
? 'Set a password to create vault'
|
||||
? "Set a password to create vault"
|
||||
: this.state.fingerprintAccess
|
||||
? 'Enter vault password to enable fingerprint unlocking.'
|
||||
? "Enter vault password to enable fingerprint unlocking."
|
||||
: this.state.revokeFingerprintAccess
|
||||
? 'Disable vault fingerprint unlock '
|
||||
? "Disable vault fingerprint unlock "
|
||||
: this.state.changePassword
|
||||
? 'Setup a new password for the vault.'
|
||||
? "Setup a new password for the vault."
|
||||
: this.state.permanant
|
||||
? 'Enter password to remove note from vault.'
|
||||
? "Enter password to remove note from vault."
|
||||
: this.state.note.locked
|
||||
? this.state.deleteNote
|
||||
? 'Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault.'
|
||||
? "Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: this.state.share
|
||||
? 'Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault.'
|
||||
? "Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: this.state.copyNote
|
||||
? 'Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault.'
|
||||
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: this.state.goToEditor
|
||||
? 'Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault.'
|
||||
: 'Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault.'
|
||||
: 'Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.');
|
||||
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
|
||||
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -127,13 +127,13 @@ export class VaultDialog extends Component {
|
||||
*
|
||||
* @param {import('../../../services/event-manager').vaultType} data
|
||||
*/
|
||||
open = async data => {
|
||||
open = async (data) => {
|
||||
if (!Keychain) {
|
||||
Keychain = require('react-native-keychain');
|
||||
Keychain = require("react-native-keychain");
|
||||
}
|
||||
let biometry = await BiometricService.isBiometryAvailable();
|
||||
let available = false;
|
||||
let fingerprint = await BiometricService.hasInternetCredentials('nn_vault');
|
||||
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
|
||||
|
||||
if (biometry) {
|
||||
available = true;
|
||||
@@ -181,19 +181,19 @@ export class VaultDialog extends Component {
|
||||
if (this.state.loading) {
|
||||
ToastEvent.show({
|
||||
heading: this.state.title,
|
||||
message: 'Please wait and do not close the app.',
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
message: "Please wait and do not close the app.",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Notes',
|
||||
'Favorites',
|
||||
'TopicNotes',
|
||||
'TaggedNotes',
|
||||
'ColoredNotes'
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"TopicNotes",
|
||||
"TaggedNotes",
|
||||
"ColoredNotes"
|
||||
);
|
||||
|
||||
this.password = null;
|
||||
@@ -223,19 +223,19 @@ export class VaultDialog extends Component {
|
||||
|
||||
if (!this.password) {
|
||||
ToastEvent.show({
|
||||
heading: 'Password not entered',
|
||||
message: 'Enter a password for the vault and try again.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Password not entered",
|
||||
message: "Enter a password for the vault and try again.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.password && this.password.length < 3) {
|
||||
ToastEvent.show({
|
||||
heading: 'Password too short',
|
||||
message: 'Password must be longer than 3 characters.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Password too short",
|
||||
message: "Password must be longer than 3 characters.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -244,9 +244,9 @@ export class VaultDialog extends Component {
|
||||
if (!this.state.novault) {
|
||||
if (this.password !== this.confirmPassword) {
|
||||
ToastEvent.show({
|
||||
heading: 'Passwords do not match',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Passwords do not match",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
passwordsDontMatch: true
|
||||
@@ -262,7 +262,7 @@ export class VaultDialog extends Component {
|
||||
|
||||
db.vault
|
||||
.changePassword(this.password, this.newPassword)
|
||||
.then(result => {
|
||||
.then((result) => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
@@ -270,32 +270,32 @@ export class VaultDialog extends Component {
|
||||
this._enrollFingerprint(this.newPassword);
|
||||
}
|
||||
ToastEvent.show({
|
||||
heading: 'Vault password updated successfully',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Vault password updated successfully",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
if (e.message === db.vault.ERRORS.wrongPassword) {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
message: 'Please enter the correct password and try again',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this.state.locked) {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
message: 'Please enter the correct password and try again',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
wrongPassword: true
|
||||
@@ -313,7 +313,7 @@ export class VaultDialog extends Component {
|
||||
});
|
||||
await this._lockNote();
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
this._takeErrorAction(e);
|
||||
});
|
||||
}
|
||||
@@ -335,17 +335,17 @@ export class VaultDialog extends Component {
|
||||
if (!(await db.user.getUser())) verified = true;
|
||||
if (verified) {
|
||||
await db.vault.delete(this.state.deleteAll);
|
||||
eSendEvent('vaultUpdated');
|
||||
eSendEvent("vaultUpdated");
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
this.close();
|
||||
} else {
|
||||
ToastEvent.show({
|
||||
heading: 'Account password incorrect',
|
||||
message: 'Please enter correct password for your account.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Account password incorrect",
|
||||
message: "Please enter correct password for your account.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -364,13 +364,13 @@ export class VaultDialog extends Component {
|
||||
loading: false
|
||||
});
|
||||
this.close();
|
||||
eSendEvent('vaultUpdated');
|
||||
eSendEvent("vaultUpdated");
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: 'Vault password incorrect',
|
||||
message: 'Please enter correct password to clear vault.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Vault password incorrect",
|
||||
message: "Please enter correct password to clear vault.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
@@ -381,11 +381,11 @@ export class VaultDialog extends Component {
|
||||
async _lockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
console.log('returning from here');
|
||||
console.log("returning from here");
|
||||
return;
|
||||
} else {
|
||||
await db.vault.add(this.state.note.id);
|
||||
@@ -394,9 +394,9 @@ export class VaultDialog extends Component {
|
||||
}
|
||||
this.close();
|
||||
ToastEvent.show({
|
||||
message: 'Note locked successfully',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
message: "Note locked successfully",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
@@ -407,10 +407,10 @@ export class VaultDialog extends Component {
|
||||
async _unlockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
message: 'Please enter the correct password and try again',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
message: "Please enter the correct password and try again",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -464,20 +464,21 @@ export class VaultDialog extends Component {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent('vaultUpdated');
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: 'Biometric unlocking enabled!',
|
||||
message: 'Now you can unlock notes in vault with biometrics.',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
message: 'Please enter the correct vault password to enable biometrics.',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
@@ -506,17 +507,17 @@ export class VaultDialog extends Component {
|
||||
loading: false
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: 'Note added to vault',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Note added to vault",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} else {
|
||||
eSendEvent('vaultUpdated');
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: 'Vault created successfully',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Vault created successfully",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
@@ -525,15 +526,15 @@ export class VaultDialog extends Component {
|
||||
_permanantUnlock() {
|
||||
db.vault
|
||||
.remove(this.state.note.id, this.password)
|
||||
.then(r => {
|
||||
.then((r) => {
|
||||
ToastEvent.show({
|
||||
heading: 'Note permanantly unlocked.',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Note permanantly unlocked.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
this._takeErrorAction(e);
|
||||
});
|
||||
}
|
||||
@@ -551,10 +552,10 @@ export class VaultDialog extends Component {
|
||||
async _copyNote(note) {
|
||||
Clipboard.setString(await toTXT(note));
|
||||
ToastEvent.show({
|
||||
heading: 'Note copied',
|
||||
type: 'success',
|
||||
message: 'Note has been copied to clipboard!',
|
||||
context: 'global'
|
||||
heading: "Note copied",
|
||||
type: "success",
|
||||
message: "Note has been copied to clipboard!",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
@@ -563,7 +564,7 @@ export class VaultDialog extends Component {
|
||||
this.close();
|
||||
try {
|
||||
await Share.open({
|
||||
heading: 'Share note',
|
||||
heading: "Share note",
|
||||
failOnCancel: false,
|
||||
message: await toTXT(note)
|
||||
});
|
||||
@@ -571,16 +572,19 @@ export class VaultDialog extends Component {
|
||||
}
|
||||
|
||||
_takeErrorAction(e) {
|
||||
if (e.message === db.vault.ERRORS.wrongPassword || e.message === 'FAILURE') {
|
||||
if (
|
||||
e.message === db.vault.ERRORS.wrongPassword ||
|
||||
e.message === "FAILURE"
|
||||
) {
|
||||
this.setState({
|
||||
wrongPassword: true,
|
||||
visible: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
ToastEvent.show({
|
||||
heading: 'Incorrect password',
|
||||
type: 'error',
|
||||
context: 'local'
|
||||
heading: "Incorrect password",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}, 500);
|
||||
|
||||
@@ -591,18 +595,18 @@ export class VaultDialog extends Component {
|
||||
_revokeFingerprintAccess = async () => {
|
||||
try {
|
||||
await BiometricService.resetCredentials();
|
||||
eSendEvent('vaultUpdated');
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: 'Biometric unlocking disabled!',
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
heading: "Biometric unlocking disabled!",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: 'Failed to disable Biometric unlocking.',
|
||||
heading: "Failed to disable Biometric unlocking.",
|
||||
message: e.message,
|
||||
type: 'success',
|
||||
context: 'global'
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -663,7 +667,7 @@ export class VaultDialog extends Component {
|
||||
<View
|
||||
style={{
|
||||
...getElevation(5),
|
||||
width: DDS.isTab ? 350 : '85%',
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
paddingTop: 12
|
||||
@@ -682,7 +686,10 @@ export class VaultDialog extends Component {
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{(novault || changePassword || this.state.clearVault || this.state.deleteVault) &&
|
||||
{(novault ||
|
||||
changePassword ||
|
||||
this.state.clearVault ||
|
||||
this.state.deleteVault) &&
|
||||
!this.state.revokeFingerprintAccess ? (
|
||||
<>
|
||||
<Input
|
||||
@@ -690,7 +697,7 @@ export class VaultDialog extends Component {
|
||||
editable={!loading}
|
||||
autoCapitalize="none"
|
||||
testID={notesnook.ids.dialogs.vault.pwd}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
this.password = value;
|
||||
}}
|
||||
marginBottom={
|
||||
@@ -702,13 +709,15 @@ export class VaultDialog extends Component {
|
||||
: 10
|
||||
}
|
||||
onSubmit={() => {
|
||||
changePassword ? changePassInputRef.current?.focus() : this.onPress;
|
||||
changePassword
|
||||
? changePassInputRef.current?.focus()
|
||||
: this.onPress;
|
||||
}}
|
||||
autoComplete="password"
|
||||
returnKeyLabel={changePassword ? 'Next' : this.state.title}
|
||||
returnKeyType={changePassword ? 'next' : 'done'}
|
||||
returnKeyLabel={changePassword ? "Next" : this.state.title}
|
||||
returnKeyType={changePassword ? "next" : "done"}
|
||||
secureTextEntry
|
||||
placeholder={changePassword ? 'Current password' : 'Password'}
|
||||
placeholder={changePassword ? "Current password" : "Password"}
|
||||
/>
|
||||
|
||||
{!this.state.biometricUnlock ||
|
||||
@@ -716,10 +725,12 @@ export class VaultDialog extends Component {
|
||||
!novault ||
|
||||
changePassword ? null : (
|
||||
<Button
|
||||
onPress={() => this._onPressFingerprintAuth('Unlock note', '')}
|
||||
onPress={() =>
|
||||
this._onPressFingerprintAuth("Unlock note", "")
|
||||
}
|
||||
icon="fingerprint"
|
||||
width="100%"
|
||||
title={'Biometric unlock'}
|
||||
title={"Biometric unlock"}
|
||||
type="transparent"
|
||||
/>
|
||||
)}
|
||||
@@ -734,13 +745,15 @@ export class VaultDialog extends Component {
|
||||
})
|
||||
}
|
||||
icon={
|
||||
this.state.deleteAll ? 'check-circle-outline' : 'checkbox-blank-circle-outline'
|
||||
this.state.deleteAll
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
width="100%"
|
||||
title={'Delete all notes'}
|
||||
title={"Delete all notes"}
|
||||
type="errorShade"
|
||||
/>
|
||||
)}
|
||||
@@ -753,7 +766,7 @@ export class VaultDialog extends Component {
|
||||
editable={!loading}
|
||||
testID={notesnook.ids.dialogs.vault.changePwd}
|
||||
autoCapitalize="none"
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
this.newPassword = value;
|
||||
}}
|
||||
autoComplete="password"
|
||||
@@ -761,7 +774,7 @@ export class VaultDialog extends Component {
|
||||
returnKeyLabel="Change"
|
||||
returnKeyType="done"
|
||||
secureTextEntry
|
||||
placeholder={'New password'}
|
||||
placeholder={"New password"}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
@@ -772,7 +785,7 @@ export class VaultDialog extends Component {
|
||||
fwdRef={passInputRef}
|
||||
autoCapitalize="none"
|
||||
testID={notesnook.ids.dialogs.vault.pwd}
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
this.password = value;
|
||||
}}
|
||||
autoComplete="password"
|
||||
@@ -793,12 +806,12 @@ export class VaultDialog extends Component {
|
||||
validationType="confirmPassword"
|
||||
customValidator={() => this.password}
|
||||
errorMessage="Passwords do not match."
|
||||
onErrorCheck={e => null}
|
||||
onErrorCheck={(e) => null}
|
||||
marginBottom={0}
|
||||
autoComplete="password"
|
||||
returnKeyLabel="Create"
|
||||
returnKeyType="done"
|
||||
onChangeText={value => {
|
||||
onChangeText={(value) => {
|
||||
this.confirmPassword = value;
|
||||
if (value !== this.password) {
|
||||
this.setState({
|
||||
@@ -816,8 +829,12 @@ export class VaultDialog extends Component {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{this.state.biometricUnlock && !this.state.isBiometryEnrolled && novault ? (
|
||||
<Paragraph>Unlock with password once to enable biometric access.</Paragraph>
|
||||
{this.state.biometricUnlock &&
|
||||
!this.state.isBiometryEnrolled &&
|
||||
novault ? (
|
||||
<Paragraph>
|
||||
Unlock with password once to enable biometric access.
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
{this.state.isBiometryAvailable &&
|
||||
@@ -838,7 +855,7 @@ export class VaultDialog extends Component {
|
||||
icon="fingerprint"
|
||||
width="100%"
|
||||
title="Biometric unlocking"
|
||||
type={this.state.biometricUnlock ? 'transparent' : 'gray'}
|
||||
type={this.state.biometricUnlock ? "transparent" : "gray"}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
@@ -847,29 +864,31 @@ export class VaultDialog extends Component {
|
||||
onPressNegative={this.close}
|
||||
onPressPositive={this.onPress}
|
||||
loading={loading}
|
||||
positiveType={deleteVault || clearVault ? 'errorShade' : 'transparent'}
|
||||
positiveType={
|
||||
deleteVault || clearVault ? "errorShade" : "transparent"
|
||||
}
|
||||
positiveTitle={
|
||||
deleteVault
|
||||
? 'Delete'
|
||||
? "Delete"
|
||||
: clearVault
|
||||
? 'Clear'
|
||||
? "Clear"
|
||||
: fingerprintAccess
|
||||
? 'Enable'
|
||||
? "Enable"
|
||||
: this.state.revokeFingerprintAccess
|
||||
? 'Revoke'
|
||||
? "Revoke"
|
||||
: changePassword
|
||||
? 'Change'
|
||||
? "Change"
|
||||
: note.locked
|
||||
? deleteNote
|
||||
? 'Delete'
|
||||
? "Delete"
|
||||
: share
|
||||
? 'Share '
|
||||
? "Share "
|
||||
: goToEditor
|
||||
? 'Open'
|
||||
: 'Unlock'
|
||||
? "Open"
|
||||
: "Unlock"
|
||||
: !note.id
|
||||
? 'Create'
|
||||
: 'Lock'
|
||||
? "Create"
|
||||
: "Lock"
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import RNBootSplash from 'react-native-bootsplash';
|
||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { Dialog } from '../dialog';
|
||||
import { Issue } from '../sheets/github/issue';
|
||||
import React from "react";
|
||||
import RNBootSplash from "react-native-bootsplash";
|
||||
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Issue } from "../sheets/github/issue";
|
||||
|
||||
const error = (
|
||||
stack: string,
|
||||
@@ -12,7 +12,10 @@ const error = (
|
||||
_______________________________
|
||||
Stacktrace: In ${component}::${stack}`;
|
||||
|
||||
class ExceptionHandler extends React.Component<{ children: React.ReactNode; component: string }> {
|
||||
class ExceptionHandler extends React.Component<{
|
||||
children: React.ReactNode;
|
||||
component: string;
|
||||
}> {
|
||||
state: {
|
||||
error: {
|
||||
title: string;
|
||||
@@ -45,8 +48,11 @@ class ExceptionHandler extends React.Component<{ children: React.ReactNode; comp
|
||||
}}
|
||||
>
|
||||
<Issue
|
||||
defaultBody={error(this.state.error?.stack || '', this.props.component)}
|
||||
defaultTitle={this.state.error?.title || 'Unknown Error'}
|
||||
defaultBody={error(
|
||||
this.state.error?.stack || "",
|
||||
this.props.component
|
||||
)}
|
||||
defaultTitle={this.state.error?.title || "Unknown Error"}
|
||||
issueTitle="An exception occured"
|
||||
/>
|
||||
<Dialog />
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { SearchBar } from '../../screens/search/search-bar';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import useNavigationStore from '../../stores/use-navigation-store';
|
||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { eScrollEvent } from '../../utils/events';
|
||||
import { LeftMenus } from './left-menus';
|
||||
import { RightMenus } from './right-menus';
|
||||
import { Title } from './title';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { SearchBar } from "../../screens/search/search-bar";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { LeftMenus } from "./left-menus";
|
||||
import { RightMenus } from "./right-menus";
|
||||
import { Title } from "./title";
|
||||
|
||||
export const Header = React.memo(
|
||||
() => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const insets = useSafeAreaInsets();
|
||||
const [hide, setHide] = useState(true);
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const currentScreen = useNavigationStore(state => state.currentScreen?.name);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const currentScreen = useNavigationStore(
|
||||
(state) => state.currentScreen?.name
|
||||
);
|
||||
|
||||
const onScroll = data => {
|
||||
const onScroll = (data) => {
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
setHide(false);
|
||||
@@ -42,16 +47,16 @@ export const Header = React.memo(
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
marginTop: Platform.OS === 'android' ? insets.top : null,
|
||||
marginTop: Platform.OS === "android" ? insets.top : null,
|
||||
backgroundColor: colors.bg,
|
||||
overflow: 'hidden',
|
||||
overflow: "hidden",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: hide ? 'transparent' : colors.nav,
|
||||
justifyContent: 'space-between'
|
||||
borderBottomColor: hide ? "transparent" : colors.nav,
|
||||
justifyContent: "space-between"
|
||||
}
|
||||
]}
|
||||
>
|
||||
{currentScreen === 'Search' ? (
|
||||
{currentScreen === "Search" ? (
|
||||
<SearchBar />
|
||||
) : (
|
||||
<>
|
||||
@@ -71,24 +76,24 @@ export const Header = React.memo(
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
zIndex: 11,
|
||||
height: 50,
|
||||
maxHeight: 50,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
},
|
||||
leftBtnContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
flexShrink: 1
|
||||
},
|
||||
leftBtn: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: 100,
|
||||
@@ -96,12 +101,12 @@ const styles = StyleSheet.create({
|
||||
marginRight: 25
|
||||
},
|
||||
rightBtnContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
},
|
||||
rightBtn: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-end",
|
||||
height: 40,
|
||||
width: 40,
|
||||
paddingRight: 0
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import Navigation from '../../services/navigation';
|
||||
import useNavigationStore from '../../stores/use-navigation-store';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import React from "react";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import Navigation from "../../services/navigation";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const LeftMenus = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
||||
const canGoBack = useNavigationStore(state => state.canGoBack);
|
||||
const isTablet = deviceMode === 'tablet';
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const canGoBack = useNavigationStore((state) => state.canGoBack);
|
||||
const isTablet = deviceMode === "tablet";
|
||||
|
||||
const onLeftButtonPress = () => {
|
||||
if (!canGoBack) {
|
||||
@@ -25,8 +25,8 @@ export const LeftMenus = () => {
|
||||
}
|
||||
Navigation.goBack();
|
||||
if (
|
||||
useNavigationStore.getState().currentScreen.name === 'Signup' ||
|
||||
useNavigationStore.getState().currentScreen.name === 'Login'
|
||||
useNavigationStore.getState().currentScreen.name === "Signup" ||
|
||||
useNavigationStore.getState().currentScreen.name === "Login"
|
||||
) {
|
||||
tabBarRef.current.unlock();
|
||||
}
|
||||
@@ -36,8 +36,8 @@ export const LeftMenus = () => {
|
||||
<IconButton
|
||||
testID={notesnook.ids.default.header.buttons.left}
|
||||
customStyle={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 40,
|
||||
width: 40,
|
||||
borderRadius: 100,
|
||||
@@ -51,7 +51,7 @@ export const LeftMenus = () => {
|
||||
onLongPress={() => {
|
||||
Navigation.popToTop();
|
||||
}}
|
||||
name={canGoBack ? 'arrow-left' : 'menu'}
|
||||
name={canGoBack ? "arrow-left" : "menu"}
|
||||
color={colors.pri}
|
||||
iconStyle={{
|
||||
marginLeft: canGoBack ? -5 : 0
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import Menu from 'react-native-reanimated-material-menu';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import Navigation from '../../services/navigation';
|
||||
import SearchService from '../../services/search';
|
||||
import useNavigationStore from '../../stores/use-navigation-store';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import React, { useRef } from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
import Menu from "react-native-reanimated-material-menu";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const RightMenus = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
||||
const rightButtons = useNavigationStore(state => state.headerRightButtons);
|
||||
const currentScreen = useNavigationStore(state => state.currentScreen.name);
|
||||
const buttonAction = useNavigationStore(state => state.buttonAction);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
|
||||
const buttonAction = useNavigationStore((state) => state.buttonAction);
|
||||
const menuRef = useRef();
|
||||
|
||||
return (
|
||||
<View style={styles.rightBtnContainer}>
|
||||
{!currentScreen.startsWith('Settings') ? (
|
||||
{!currentScreen.startsWith("Settings") ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
SearchService.prepareSearch();
|
||||
Navigation.navigate({
|
||||
name: 'Search'
|
||||
name: "Search"
|
||||
});
|
||||
}}
|
||||
testID="icon-search"
|
||||
@@ -37,11 +37,11 @@ export const RightMenus = () => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{deviceMode !== 'mobile' ? (
|
||||
{deviceMode !== "mobile" ? (
|
||||
<Button
|
||||
onPress={buttonAction}
|
||||
testID={notesnook.ids.default.addBtn}
|
||||
icon={currentScreen === 'Trash' ? 'delete' : 'plus'}
|
||||
icon={currentScreen === "Trash" ? "delete" : "plus"}
|
||||
iconSize={SIZE.xl}
|
||||
type="shade"
|
||||
hitSlop={{
|
||||
@@ -88,7 +88,7 @@ export const RightMenus = () => {
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
justifyContent: 'flex-start',
|
||||
justifyContent: "flex-start",
|
||||
borderRadius: 0
|
||||
}}
|
||||
type="gray"
|
||||
@@ -99,7 +99,7 @@ export const RightMenus = () => {
|
||||
title={item.title}
|
||||
onPress={async () => {
|
||||
menuRef.current?.hide();
|
||||
if (Platform.OS === 'ios') await sleep(300);
|
||||
if (Platform.OS === "ios") await sleep(300);
|
||||
item.onPress();
|
||||
}}
|
||||
/>
|
||||
@@ -112,12 +112,12 @@ export const RightMenus = () => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
rightBtnContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
},
|
||||
rightBtn: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 40,
|
||||
width: 40,
|
||||
marginLeft: 10,
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import Notebook from '../../screens/notebook';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import useNavigationStore from '../../stores/use-navigation-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import { eScrollEvent } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import Notebook from "../../screens/notebook";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
const titleState = {};
|
||||
|
||||
export const Title = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const currentScreen = useNavigationStore(state => state.currentScreen);
|
||||
const isNotebook = currentScreen.name === 'Notebook';
|
||||
const isTopic = currentScreen?.name === 'TopicNotes';
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const isNotebook = currentScreen.name === "Notebook";
|
||||
const isTopic = currentScreen?.name === "TopicNotes";
|
||||
const [hide, setHide] = useState(
|
||||
isNotebook
|
||||
? typeof titleState[currentScreen.id] === 'boolean'
|
||||
? typeof titleState[currentScreen.id] === "boolean"
|
||||
? titleState[currentScreen.id]
|
||||
: true
|
||||
: false
|
||||
);
|
||||
const isHidden = titleState[currentScreen.id];
|
||||
console.log(currentScreen, 'header');
|
||||
console.log(currentScreen, "header");
|
||||
const notebook =
|
||||
isTopic && currentScreen.notebookId
|
||||
? db.notebooks?.notebook(currentScreen.notebookId)?.data
|
||||
: null;
|
||||
const title = currentScreen.title;
|
||||
const isTag = currentScreen?.name === 'TaggedNotes';
|
||||
const isTag = currentScreen?.name === "TaggedNotes";
|
||||
|
||||
const onScroll = data => {
|
||||
if (currentScreen.name !== 'Notebook') {
|
||||
const onScroll = (data) => {
|
||||
if (currentScreen.name !== "Notebook") {
|
||||
setHide(false);
|
||||
return;
|
||||
}
|
||||
@@ -48,9 +51,11 @@ export const Title = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScreen.name === 'Notebook') {
|
||||
if (currentScreen.name === "Notebook") {
|
||||
let value =
|
||||
typeof titleState[currentScreen.id] === 'boolean' ? titleState[currentScreen.id] : true;
|
||||
typeof titleState[currentScreen.id] === "boolean"
|
||||
? titleState[currentScreen.id]
|
||||
: true;
|
||||
setHide(value);
|
||||
} else {
|
||||
setHide(titleState[currentScreen.id]);
|
||||
@@ -77,7 +82,7 @@ export const Title = () => {
|
||||
style={{
|
||||
opacity: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row'
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{!hide && !isHidden ? (
|
||||
@@ -86,19 +91,22 @@ export const Title = () => {
|
||||
numberOfLines={isTopic ? 2 : 1}
|
||||
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||
style={{
|
||||
flexWrap: 'wrap',
|
||||
marginTop: Platform.OS === 'ios' ? -1 : 0
|
||||
flexWrap: "wrap",
|
||||
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||
}}
|
||||
color={currentScreen.color || colors.heading}
|
||||
>
|
||||
{isTopic ? (
|
||||
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
|
||||
{notebook?.title}
|
||||
{'\n'}
|
||||
{"\n"}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{isTag ? (
|
||||
<Heading size={isTopic ? SIZE.md + 2 : SIZE.xl} color={colors.accent}>
|
||||
<Heading
|
||||
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||
color={colors.accent}
|
||||
>
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
const ImagePreview = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState('');
|
||||
const [image, setImage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent('ImagePreview', open);
|
||||
eSubscribeEvent("ImagePreview", open);
|
||||
|
||||
return () => {
|
||||
eUnSubscribeEvent('ImagePreview', open);
|
||||
eUnSubscribeEvent("ImagePreview", open);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = image => {
|
||||
const open = (image) => {
|
||||
setImage(image);
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -32,9 +35,9 @@ const ImagePreview = () => {
|
||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'black'
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "black"
|
||||
}}
|
||||
>
|
||||
<ImageViewer
|
||||
@@ -47,16 +50,16 @@ const ImagePreview = () => {
|
||||
renderHeader={() => (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
height: 80,
|
||||
marginTop: 0,
|
||||
paddingHorizontal: 12,
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: 30
|
||||
}}
|
||||
>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,55 +1,61 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import RNBootSplash from 'react-native-bootsplash';
|
||||
import { checkVersion } from 'react-native-check-version';
|
||||
import { enabled } from 'react-native-privacy-snapshot';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import BackupService from '../../services/backup';
|
||||
import BiometricService from '../../services/biometrics';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
||||
import { setRateAppMessage } from '../../services/message';
|
||||
import PremiumService from '../../services/premium';
|
||||
import SettingsService from '../../services/settings';
|
||||
import { initialize } from '../../stores';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { useNoteStore } from '../../stores/use-notes-store';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import { DatabaseLogger, db, loadDatabase } from '../../common/database';
|
||||
import { MMKV } from '../../common/database/mmkv';
|
||||
import { eOpenAnnouncementDialog } from '../../utils/events';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { SVG } from '../auth/background';
|
||||
import Migrate from '../sheets/migrate';
|
||||
import NewFeature from '../sheets/new-feature/index';
|
||||
import { Update } from '../sheets/update';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import Input from '../ui/input';
|
||||
import Seperator from '../ui/seperator';
|
||||
import { SvgView } from '../ui/svg';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { Walkthrough } from '../walkthroughs';
|
||||
import { useAppState } from '../../hooks/use-app-state';
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import RNBootSplash from "react-native-bootsplash";
|
||||
import { checkVersion } from "react-native-check-version";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||
import BackupService from "../../services/backup";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from "../../services/event-manager";
|
||||
import { setRateAppMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { initialize } from "../../stores";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import { eOpenAnnouncementDialog } from "../../utils/events";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { SVG } from "../auth/background";
|
||||
import Migrate from "../sheets/migrate";
|
||||
import NewFeature from "../sheets/new-feature/index";
|
||||
import { Update } from "../sheets/update";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Walkthrough } from "../walkthroughs";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
|
||||
const Launcher = React.memo(
|
||||
() => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const setLoading = useNoteStore(state => state.setLoading);
|
||||
const loading = useNoteStore(state => state.loading);
|
||||
const user = useUserStore(state => state.user);
|
||||
const verifyUser = useUserStore(state => state.verifyUser);
|
||||
const setVerifyUser = useUserStore(state => state.setVerifyUser);
|
||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const setLoading = useNoteStore((state) => state.setLoading);
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
const user = useUserStore((state) => state.user);
|
||||
const verifyUser = useUserStore((state) => state.verifyUser);
|
||||
const setVerifyUser = useUserStore((state) => state.setVerifyUser);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const appState = useAppState();
|
||||
const passwordInputRef = useRef();
|
||||
const password = useRef();
|
||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const dbInitCompleted = useRef(false);
|
||||
const loadNotes = async () => {
|
||||
if (verifyUser) {
|
||||
@@ -76,7 +82,7 @@ const Launcher = React.memo(
|
||||
if (!dbInitCompleted.current) {
|
||||
await RNBootSplash.hide({ fade: true });
|
||||
await loadDatabase();
|
||||
DatabaseLogger.info('Initializing database');
|
||||
DatabaseLogger.info("Initializing database");
|
||||
await db.init();
|
||||
dbInitCompleted.current = true;
|
||||
}
|
||||
@@ -115,16 +121,20 @@ const Launcher = React.memo(
|
||||
const doAppLoadActions = async () => {
|
||||
await sleep(500);
|
||||
if (SettingsService.get().sessionExpired) {
|
||||
eSendEvent('session_expired');
|
||||
eSendEvent("session_expired");
|
||||
return;
|
||||
}
|
||||
const user = await db.user.getUser();
|
||||
await useMessageStore.getState().setAnnouncement();
|
||||
if (PremiumService.get() && user) {
|
||||
if (SettingsService.get().reminder === 'off') {
|
||||
SettingsService.set({ reminder: 'daily' });
|
||||
if (SettingsService.get().reminder === "off") {
|
||||
SettingsService.set({ reminder: "daily" });
|
||||
}
|
||||
if (await BackupService.checkBackupRequired(SettingsService.get().reminder)) {
|
||||
if (
|
||||
await BackupService.checkBackupRequired(
|
||||
SettingsService.get().reminder
|
||||
)
|
||||
) {
|
||||
sleep(2000).then(() => BackupService.run());
|
||||
}
|
||||
}
|
||||
@@ -135,7 +145,7 @@ const Launcher = React.memo(
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
|
||||
if (introCompleted) {
|
||||
useMessageStore.subscribe(state => {
|
||||
useMessageStore.subscribe((state) => {
|
||||
let dialogs = state.dialogs;
|
||||
if (dialogs.length > 0) {
|
||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||
@@ -150,7 +160,7 @@ const Launcher = React.memo(
|
||||
const version = await checkVersion();
|
||||
if (!version.needsUpdate) return false;
|
||||
presentSheet({
|
||||
component: ref => <Update version={version} fwdRef={ref} />
|
||||
component: (ref) => <Update version={version} fwdRef={ref} />
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -159,7 +169,7 @@ const Launcher = React.memo(
|
||||
};
|
||||
|
||||
const restoreEditorState = async () => {
|
||||
let appState = MMKV.getString('appState');
|
||||
let appState = MMKV.getString("appState");
|
||||
if (appState) {
|
||||
appState = JSON.parse(appState);
|
||||
if (
|
||||
@@ -174,14 +184,18 @@ const Launcher = React.memo(
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
eSendEvent('loadingNote', appState.note);
|
||||
eSendEvent("loadingNote", appState.note);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkForRateAppRequest = async () => {
|
||||
let rateApp = SettingsService.get().rateApp;
|
||||
if (rateApp && rateApp < Date.now() && !useMessageStore.getState().message?.visible) {
|
||||
if (
|
||||
rateApp &&
|
||||
rateApp < Date.now() &&
|
||||
!useMessageStore.getState().message?.visible
|
||||
) {
|
||||
setRateAppMessage();
|
||||
return false;
|
||||
}
|
||||
@@ -208,12 +222,15 @@ const Launcher = React.memo(
|
||||
const onUnlockBiometrics = async () => {
|
||||
if (!(await BiometricService.isBiometryAvailable())) {
|
||||
ToastEvent.show({
|
||||
heading: 'Biometrics unavailable',
|
||||
message: 'Try unlocking the app with your account password'
|
||||
heading: "Biometrics unavailable",
|
||||
message: "Try unlocking the app with your account password"
|
||||
});
|
||||
return;
|
||||
}
|
||||
let verified = await BiometricService.validateUser('Unlock to access your notes', '');
|
||||
let verified = await BiometricService.validateUser(
|
||||
"Unlock to access your notes",
|
||||
""
|
||||
);
|
||||
if (verified) {
|
||||
setVerifyUser(false);
|
||||
enabled(false);
|
||||
@@ -226,7 +243,7 @@ const Launcher = React.memo(
|
||||
}, [verifyUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (verifyUser && appState === 'active') {
|
||||
if (verifyUser && appState === "active") {
|
||||
onUnlockBiometrics();
|
||||
}
|
||||
}, [appState]);
|
||||
@@ -247,30 +264,35 @@ const Launcher = React.memo(
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
zIndex: 999
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 250,
|
||||
overflow: 'hidden'
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<SvgView src={SVG(colors.night ? 'white' : 'black')} height={700} />
|
||||
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
width: deviceMode !== 'mobile' ? '50%' : Platform.OS == 'ios' ? '95%' : '100%',
|
||||
justifyContent: "center",
|
||||
width:
|
||||
deviceMode !== "mobile"
|
||||
? "50%"
|
||||
: Platform.OS == "ios"
|
||||
? "95%"
|
||||
: "100%",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: 15,
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -288,8 +310,8 @@ const Launcher = React.memo(
|
||||
<Heading
|
||||
color={colors.heading}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center'
|
||||
alignSelf: "center",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Unlock to access your notes
|
||||
@@ -297,10 +319,10 @@ const Launcher = React.memo(
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
fontSize: SIZE.md,
|
||||
maxWidth: '90%'
|
||||
maxWidth: "90%"
|
||||
}}
|
||||
>
|
||||
Please verify it's you
|
||||
@@ -308,7 +330,7 @@ const Launcher = React.memo(
|
||||
<Seperator />
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg,
|
||||
flexGrow: 1
|
||||
@@ -320,7 +342,7 @@ const Launcher = React.memo(
|
||||
fwdRef={passwordInputRef}
|
||||
secureTextEntry
|
||||
placeholder="Enter account password"
|
||||
onChangeText={v => (password.current = v)}
|
||||
onChangeText={(v) => (password.current = v)}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</>
|
||||
@@ -356,8 +378,8 @@ const Launcher = React.memo(
|
||||
borderRadius: 100
|
||||
}}
|
||||
onPress={onUnlockBiometrics}
|
||||
icon={'fingerprint'}
|
||||
type={user ? 'grayAccent' : 'accent'}
|
||||
icon={"fingerprint"}
|
||||
type={user ? "grayAccent" : "accent"}
|
||||
fontSize={SIZE.md}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export const Footer = () => {
|
||||
return <View style={{ height: 150 }} />;
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { useMessageStore } from '../../../stores/use-message-store';
|
||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
||||
import { Announcement } from '../../announcements/announcement';
|
||||
import { Card } from '../../list/card';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
||||
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useMessageStore } from "../../../stores/use-message-store";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { Announcement } from "../../announcements/announcement";
|
||||
import { Card } from "../../list/card";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||
export const Header = React.memo(
|
||||
({ type, messageCard = true, color, shouldShow = false, noAnnouncement, warning }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const announcements = useMessageStore(state => state.announcements);
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
({
|
||||
type,
|
||||
messageCard = true,
|
||||
color,
|
||||
shouldShow = false,
|
||||
noAnnouncement,
|
||||
warning
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const announcements = useMessageStore((state) => state.announcements);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
|
||||
return selectionMode ? null : (
|
||||
<>
|
||||
@@ -23,11 +30,11 @@ export const Header = React.memo(
|
||||
style={{
|
||||
padding: 12,
|
||||
backgroundColor: colors.errorBg,
|
||||
width: '95%',
|
||||
alignSelf: 'center',
|
||||
width: "95%",
|
||||
alignSelf: "center",
|
||||
borderRadius: 5,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
|
||||
@@ -37,18 +44,20 @@ export const Header = React.memo(
|
||||
</View>
|
||||
) : announcements.length !== 0 && !noAnnouncement ? (
|
||||
<Announcement color={color || colors.accent} />
|
||||
) : type === 'search' ? null : !shouldShow ? (
|
||||
) : type === "search" ? null : !shouldShow ? (
|
||||
<View
|
||||
style={{
|
||||
marginBottom: 5,
|
||||
padding: 0,
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{messageCard ? (
|
||||
<Card color={COLORS_NOTE[color?.toLowerCase()] || colors.accent} />
|
||||
<Card
|
||||
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
@@ -57,4 +66,4 @@ export const Header = React.memo(
|
||||
}
|
||||
);
|
||||
|
||||
Header.displayName = 'Header';
|
||||
Header.displayName = "Header";
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
||||
import { ToastEvent } from '../../../services/event-manager';
|
||||
import { getTotalNotes } from '../../../utils';
|
||||
import { db } from '../../../common/database';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { getTotalNotes } from "../../../utils";
|
||||
import { db } from "../../../common/database";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(db.settings.isPinned(notebook.id));
|
||||
const setMenuPins = useMenuStore(state => state.setMenuPins);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||
db.settings.isPinned(notebook.id)
|
||||
);
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
const totalNotes = getTotalNotes(notebook);
|
||||
const shortcutRef = useRef();
|
||||
|
||||
@@ -24,8 +26,8 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
} else {
|
||||
await db.settings.pin(notebook.type, { id: notebook.id });
|
||||
ToastEvent.show({
|
||||
heading: 'Shortcut created',
|
||||
type: 'success'
|
||||
heading: "Shortcut created",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
setIsPinnedToMenu(db.settings.isPinned(notebook.id));
|
||||
@@ -38,10 +40,10 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
style={{
|
||||
marginBottom: 5,
|
||||
padding: 0,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
paddingVertical: 15,
|
||||
paddingHorizontal: 12,
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
borderRadius: 10,
|
||||
paddingTop: 25
|
||||
}}
|
||||
@@ -51,29 +53,29 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
</Paragraph>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.xxl}>{notebook.title}</Heading>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row'
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
name={isPinnedToMenu ? 'link-variant-off' : 'link-variant'}
|
||||
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
|
||||
onPress={onPinNotebook}
|
||||
tooltipText={'Create shortcut in side menu'}
|
||||
tooltipText={"Create shortcut in side menu"}
|
||||
fwdRef={shortcutRef}
|
||||
customStyle={{
|
||||
marginRight: 15,
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
type={isPinnedToMenu ? 'grayBg' : 'grayBg'}
|
||||
type={isPinnedToMenu ? "grayBg" : "grayBg"}
|
||||
color={isPinnedToMenu ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
@@ -101,18 +103,21 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
<Paragraph
|
||||
style={{
|
||||
marginTop: 10,
|
||||
fontStyle: 'italic',
|
||||
fontStyle: "italic",
|
||||
fontFamily: null
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
>
|
||||
{notebook.topics.length === 1 ? '1 topic' : `${notebook.topics.length} topics`},{' '}
|
||||
{notebook.topics.length === 1
|
||||
? "1 topic"
|
||||
: `${notebook.topics.length} topics`}
|
||||
,{" "}
|
||||
{notebook && totalNotes > 1
|
||||
? totalNotes + ' notes'
|
||||
? totalNotes + " notes"
|
||||
: totalNotes === 1
|
||||
? totalNotes + ' note'
|
||||
: '0 notes'}
|
||||
? totalNotes + " note"
|
||||
: "0 notes"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,40 +1,48 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { TouchableOpacity, useWindowDimensions, View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
presentSheet
|
||||
} from '../../../services/event-manager';
|
||||
import SettingsService from '../../../services/settings';
|
||||
import { GROUP } from '../../../utils/constants';
|
||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
||||
import { db } from '../../../common/database';
|
||||
import { eOpenJumpToDialog } from '../../../utils/events';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import { Button } from '../../ui/button';
|
||||
import Sort from '../../sheets/sort';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
} from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { GROUP } from "../../../utils/constants";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { db } from "../../../common/database";
|
||||
import { eOpenJumpToDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Button } from "../../ui/button";
|
||||
import Sort from "../../sheets/sort";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
|
||||
export const SectionHeader = React.memo(
|
||||
({ item, index, type, color, screen }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const [groupOptions, setGroupOptions] = useState(db.settings?.getGroupOptions(type));
|
||||
let groupBy = Object.keys(GROUP).find(key => GROUP[key] === groupOptions.groupBy);
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions(type)
|
||||
);
|
||||
let groupBy = Object.keys(GROUP).find(
|
||||
(key) => GROUP[key] === groupOptions.groupBy
|
||||
);
|
||||
const jumpToRef = useRef();
|
||||
const sortRef = useRef();
|
||||
const compactModeRef = useRef();
|
||||
|
||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
||||
const listMode = type === 'notebooks' ? notebooksListMode : notesListMode;
|
||||
const notebooksListMode = useSettingStore(
|
||||
(state) => state.settings.notebooksListMode
|
||||
);
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
);
|
||||
const listMode = type === "notebooks" ? notebooksListMode : notesListMode;
|
||||
|
||||
groupBy = !groupBy
|
||||
? 'Default'
|
||||
? "Default"
|
||||
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
||||
|
||||
const onUpdate = () => {
|
||||
@@ -42,23 +50,23 @@ export const SectionHeader = React.memo(
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent('groupOptionsUpdate', onUpdate);
|
||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent('groupOptionsUpdate', onUpdate);
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: '95%',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
width: "95%",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 12,
|
||||
height: 35 * fontScale,
|
||||
backgroundColor: colors.nav,
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
borderRadius: 5,
|
||||
marginVertical: 5
|
||||
}}
|
||||
@@ -71,8 +79,8 @@ export const SectionHeader = React.memo(
|
||||
activeOpacity={0.9}
|
||||
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
|
||||
style={{
|
||||
height: '100%',
|
||||
justifyContent: 'center'
|
||||
height: "100%",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
@@ -80,18 +88,18 @@ export const SectionHeader = React.memo(
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
minWidth: 60,
|
||||
alignSelf: 'center',
|
||||
textAlignVertical: 'center'
|
||||
alignSelf: "center",
|
||||
textAlignVertical: "center"
|
||||
}}
|
||||
>
|
||||
{!item.title || item.title === '' ? 'Pinned' : item.title}
|
||||
{!item.title || item.title === "" ? "Pinned" : item.title}
|
||||
</Heading>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{index === 0 ? (
|
||||
@@ -105,19 +113,26 @@ export const SectionHeader = React.memo(
|
||||
tooltipText="Change sorting of items in list"
|
||||
fwdRef={sortRef}
|
||||
title={groupBy}
|
||||
icon={groupOptions.sortDirection === 'asc' ? 'sort-ascending' : 'sort-descending'}
|
||||
icon={
|
||||
groupOptions.sortDirection === "asc"
|
||||
? "sort-ascending"
|
||||
: "sort-descending"
|
||||
}
|
||||
height={25}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 0,
|
||||
backgroundColor: 'transparent',
|
||||
marginRight: type === 'notes' || type === 'home' || type === 'notebooks' ? 10 : 0
|
||||
backgroundColor: "transparent",
|
||||
marginRight:
|
||||
type === "notes" || type === "home" || type === "notebooks"
|
||||
? 10
|
||||
: 0
|
||||
}}
|
||||
type="gray"
|
||||
iconPosition="right"
|
||||
/>
|
||||
|
||||
{type === 'notes' || type === 'notebooks' || type === 'home' ? (
|
||||
{type === "notes" || type === "notebooks" || type === "home" ? (
|
||||
<IconButton
|
||||
customStyle={{
|
||||
width: 25,
|
||||
@@ -125,15 +140,22 @@ export const SectionHeader = React.memo(
|
||||
}}
|
||||
testID="icon-compact-mode"
|
||||
tooltipText={
|
||||
listMode == 'compact' ? 'Switch to normal mode' : 'Switch to compact mode'
|
||||
listMode == "compact"
|
||||
? "Switch to normal mode"
|
||||
: "Switch to compact mode"
|
||||
}
|
||||
fwdRef={compactModeRef}
|
||||
color={colors.icon}
|
||||
name={listMode == 'compact' ? 'view-list' : 'view-list-outline'}
|
||||
name={
|
||||
listMode == "compact" ? "view-list" : "view-list-outline"
|
||||
}
|
||||
onPress={() => {
|
||||
let settings = {};
|
||||
settings[type !== 'notebooks' ? 'notesListMode' : 'notebooksListMode'] =
|
||||
listMode === 'normal' ? 'compact' : 'normal';
|
||||
settings[
|
||||
type !== "notebooks"
|
||||
? "notesListMode"
|
||||
: "notebooksListMode"
|
||||
] = listMode === "normal" ? "compact" : "normal";
|
||||
|
||||
SettingsService.set(settings);
|
||||
}}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { decode, EntityLevel } from 'entities';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { notesnook } from '../../../../e2e/test.ids';
|
||||
import { TaggedNotes } from '../../../screens/notes/tagged';
|
||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
||||
import useNavigationStore from '../../../stores/use-navigation-store';
|
||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
||||
import { db } from '../../../common/database';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { Properties } from '../../properties';
|
||||
import { Button } from '../../ui/button';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import { TimeSince } from '../../ui/time-since';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import { decode, EntityLevel } from "entities";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { db } from "../../../common/database";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { TimeSince } from "../../ui/time-since";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
const navigateToTopic = topic => {
|
||||
const navigateToTopic = (topic) => {
|
||||
TopicNotes.navigate(topic, true);
|
||||
};
|
||||
|
||||
@@ -28,16 +28,19 @@ function navigateToTag(item) {
|
||||
TaggedNotes.navigate(tag, true);
|
||||
}
|
||||
|
||||
const showActionSheet = item => {
|
||||
const showActionSheet = (item) => {
|
||||
Properties.present(item);
|
||||
};
|
||||
|
||||
function getNotebook(item) {
|
||||
const isTrash = item.type === 'trash';
|
||||
const isTrash = item.type === "trash";
|
||||
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
|
||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||
const filteredNotebooks = item.notebooks?.filter(n => n.id !== currentScreen.notebookId);
|
||||
let item_notebook = filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
|
||||
const filteredNotebooks = item.notebooks?.filter(
|
||||
(n) => n.id !== currentScreen.notebookId
|
||||
);
|
||||
let item_notebook =
|
||||
filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
|
||||
let notebook = item_notebook && db.notebooks.notebook(item_notebook.id);
|
||||
if (!notebook) return [];
|
||||
let topic = notebook.topics.topic(item_notebook.topics[0])?._topic;
|
||||
@@ -52,11 +55,19 @@ function getNotebook(item) {
|
||||
];
|
||||
}
|
||||
|
||||
const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
||||
const compactMode = notesListMode === 'compact';
|
||||
const attachmentCount = db.attachments?.ofNote(item.id, 'all')?.length || 0;
|
||||
const NoteItem = ({
|
||||
item,
|
||||
isTrash,
|
||||
tags,
|
||||
dateBy = "dateCreated",
|
||||
noOpen = false
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
);
|
||||
const compactMode = notesListMode === "compact";
|
||||
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
|
||||
const notebooks = React.useMemo(() => getNotebook(item), [item]);
|
||||
|
||||
return (
|
||||
@@ -70,14 +81,14 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
{!compactMode ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
zIndex: 10,
|
||||
elevation: 10,
|
||||
marginBottom: 2.5
|
||||
}}
|
||||
>
|
||||
{notebooks.map(_item => (
|
||||
{notebooks.map((_item) => (
|
||||
<Button
|
||||
title={_item.title}
|
||||
key={_item}
|
||||
@@ -106,7 +117,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
@@ -116,7 +127,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
{item.headline && !compactMode ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
@@ -128,10 +139,10 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
@@ -155,14 +166,16 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
marginRight: 6
|
||||
}}
|
||||
time={item[dateBy]}
|
||||
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
|
||||
updateFrequency={
|
||||
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
|
||||
}
|
||||
/>
|
||||
|
||||
{attachmentCount > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
@@ -181,7 +194,9 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.accent}
|
||||
color={
|
||||
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -210,15 +225,15 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
) : null}
|
||||
|
||||
{!isTrash && !compactMode && tags
|
||||
? tags.map(item =>
|
||||
? tags.map((item) =>
|
||||
item.id ? (
|
||||
<Button
|
||||
title={'#' + item.alias}
|
||||
title={"#" + item.alias}
|
||||
key={item.id}
|
||||
height={23}
|
||||
type="gray"
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
||||
fontSize={SIZE.xs}
|
||||
@@ -244,7 +259,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
Deleted on{' '}
|
||||
Deleted on{" "}
|
||||
{item && item.dateDeleted
|
||||
? new Date(item.dateDeleted).toISOString().slice(0, 10)
|
||||
: null}
|
||||
@@ -270,11 +285,11 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
||||
size={SIZE.xl}
|
||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
||||
customStyle={{
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
alignItems: 'center'
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import React from 'react';
|
||||
import NoteItem from '.';
|
||||
import { notesnook } from '../../../../e2e/test.ids';
|
||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
||||
import { useEditorStore } from '../../../stores/use-editor-store';
|
||||
import { DDS } from '../../../services/device-detection';
|
||||
import { eSendEvent, openVault, ToastEvent } from '../../../services/event-manager';
|
||||
import Navigation from '../../../services/navigation';
|
||||
import { history } from '../../../utils';
|
||||
import { db } from '../../../common/database';
|
||||
import { eOnLoadNote, eShowMergeDialog } from '../../../utils/events';
|
||||
import { tabBarRef } from '../../../utils/global-refs';
|
||||
import { presentDialog } from '../../dialog/functions';
|
||||
import SelectionWrapper from '../selection-wrapper';
|
||||
import React from "react";
|
||||
import NoteItem from ".";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
openVault,
|
||||
ToastEvent
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { history } from "../../../utils";
|
||||
import { db } from "../../../common/database";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
|
||||
const present = () =>
|
||||
presentDialog({
|
||||
title: 'Note not synced',
|
||||
negativeText: 'Ok',
|
||||
paragraph: 'Please sync again to open this note for editing'
|
||||
title: "Note not synced",
|
||||
negativeText: "Ok",
|
||||
paragraph: "Please sync again to open this note for editing"
|
||||
});
|
||||
|
||||
export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||
@@ -55,8 +59,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||
novault: true,
|
||||
locked: true,
|
||||
goToEditor: true,
|
||||
title: 'Open note',
|
||||
description: 'Unlock note to open it in editor.'
|
||||
title: "Open note",
|
||||
description: "Unlock note to open it in editor."
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -64,24 +68,24 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||
presentDialog({
|
||||
title: `Restore ${item.itemType}`,
|
||||
paragraph: `Restore or delete ${item.itemType} forever`,
|
||||
positiveText: 'Restore',
|
||||
negativeText: 'Delete',
|
||||
positiveText: "Restore",
|
||||
negativeText: "Delete",
|
||||
positivePress: async () => {
|
||||
await db.trash.restore(item.id);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Tags',
|
||||
'Notes',
|
||||
'Notebooks',
|
||||
'Favorites',
|
||||
'Trash',
|
||||
'TaggedNotes',
|
||||
'ColoredNotes',
|
||||
'TopicNotes'
|
||||
"Tags",
|
||||
"Notes",
|
||||
"Notebooks",
|
||||
"Favorites",
|
||||
"Trash",
|
||||
"TaggedNotes",
|
||||
"ColoredNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Restore successful',
|
||||
type: 'success'
|
||||
heading: "Restore successful",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
onClose: async () => {
|
||||
@@ -89,9 +93,9 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Permanantly deleted items',
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
heading: "Permanantly deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -106,8 +110,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||
|
||||
export const NoteWrapper = React.memo(
|
||||
({ item, index, tags, dateBy }) => {
|
||||
const isTrash = item.type === 'trash';
|
||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
||||
const isTrash = item.type === "trash";
|
||||
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||
|
||||
return (
|
||||
<SelectionWrapper
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { notesnook } from '../../../../e2e/test.ids';
|
||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { getTotalNotes, history } from '../../../utils';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { Properties } from '../../properties';
|
||||
import { Button } from '../../ui/button';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { getTotalNotes, history } from "../../../utils";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
const showActionSheet = item => {
|
||||
const showActionSheet = (item) => {
|
||||
Properties.present(item);
|
||||
};
|
||||
|
||||
const navigateToTopic = topic => {
|
||||
const navigateToTopic = (topic) => {
|
||||
if (history.selectedItemsList.length > 0) return;
|
||||
TopicNotes.navigate(topic, true);
|
||||
};
|
||||
|
||||
export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateBy }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
||||
const compactMode = notebooksListMode === 'compact';
|
||||
export const NotebookItem = ({
|
||||
item,
|
||||
isTopic = false,
|
||||
notebookID,
|
||||
isTrash,
|
||||
dateBy
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const notebooksListMode = useSettingStore(
|
||||
(state) => state.settings.notebooksListMode
|
||||
);
|
||||
const compactMode = notebooksListMode === "compact";
|
||||
const topics = item.topics?.slice(0, 3) || [];
|
||||
const totalNotes = getTotalNotes(item);
|
||||
|
||||
@@ -41,7 +49,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
size={SIZE.md}
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
@@ -51,7 +59,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
size={SIZE.sm}
|
||||
numberOfLines={2}
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{item.description}
|
||||
@@ -61,18 +69,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
{isTopic || compactMode ? null : (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap'
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
>
|
||||
{topics.map(topic => (
|
||||
{topics.map((topic) => (
|
||||
<Button
|
||||
title={topic.title}
|
||||
key={topic.id}
|
||||
height={null}
|
||||
textStyle={{
|
||||
fontWeight: 'normal',
|
||||
fontWeight: "normal",
|
||||
fontFamily: null,
|
||||
marginRight: 0
|
||||
}}
|
||||
@@ -98,9 +106,9 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
marginTop: 5,
|
||||
height: SIZE.md + 2
|
||||
}}
|
||||
@@ -112,7 +120,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{isTopic ? 'Topic' : 'Notebook'}
|
||||
{isTopic ? "Topic" : "Notebook"}
|
||||
</Paragraph>
|
||||
|
||||
{isTrash ? (
|
||||
@@ -121,17 +129,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: 'center',
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
{'Deleted on ' + new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
||||
{"Deleted on " +
|
||||
new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlignVertical: 'center',
|
||||
textAlignVertical: "center",
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
@@ -157,10 +166,10 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
}}
|
||||
>
|
||||
{item && totalNotes > 1
|
||||
? totalNotes + ' notes'
|
||||
? totalNotes + " notes"
|
||||
: totalNotes === 1
|
||||
? totalNotes + ' note'
|
||||
: '0 notes'}
|
||||
? totalNotes + " note"
|
||||
: "0 notes"}
|
||||
</Paragraph>
|
||||
|
||||
{item.pinned ? (
|
||||
@@ -183,11 +192,11 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
||||
size={SIZE.xl}
|
||||
onPress={() => showActionSheet(item)}
|
||||
customStyle={{
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
alignItems: 'center'
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import { NotebookItem } from '.';
|
||||
import Notebook from '../../../screens/notebook';
|
||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
||||
import { ToastEvent } from '../../../services/event-manager';
|
||||
import Navigation from '../../../services/navigation';
|
||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
||||
import { history } from '../../../utils';
|
||||
import { db } from '../../../common/database';
|
||||
import { presentDialog } from '../../dialog/functions';
|
||||
import SelectionWrapper from '../selection-wrapper';
|
||||
import React from "react";
|
||||
import { NotebookItem } from ".";
|
||||
import Notebook from "../../../screens/notebook";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { history } from "../../../utils";
|
||||
import { db } from "../../../common/database";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
|
||||
export const openNotebookTopic = item => {
|
||||
const isTrash = item.type === 'trash';
|
||||
export const openNotebookTopic = (item) => {
|
||||
const isTrash = item.type === "trash";
|
||||
if (history.selectedItemsList.length > 0 && history.selectionMode) {
|
||||
useSelectionStore.getState().setSelectedItem(item);
|
||||
return;
|
||||
@@ -24,24 +24,24 @@ export const openNotebookTopic = item => {
|
||||
presentDialog({
|
||||
title: `Restore ${item.itemType}`,
|
||||
paragraph: `Restore or delete ${item.itemType} forever`,
|
||||
positiveText: 'Restore',
|
||||
negativeText: 'Delete',
|
||||
positiveText: "Restore",
|
||||
negativeText: "Delete",
|
||||
positivePress: async () => {
|
||||
await db.trash.restore(item.id);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Tags',
|
||||
'Notes',
|
||||
'Notebooks',
|
||||
'Favorites',
|
||||
'Trash',
|
||||
'TaggedNotes',
|
||||
'ColoredNotes',
|
||||
'TopicNotes'
|
||||
"Tags",
|
||||
"Notes",
|
||||
"Notebooks",
|
||||
"Favorites",
|
||||
"Trash",
|
||||
"TaggedNotes",
|
||||
"ColoredNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Restore successful',
|
||||
type: 'success'
|
||||
heading: "Restore successful",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
onClose: async () => {
|
||||
@@ -49,15 +49,15 @@ export const openNotebookTopic = item => {
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Permanantly deleted items',
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
heading: "Permanantly deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (item.type === 'topic') {
|
||||
if (item.type === "topic") {
|
||||
TopicNotes.navigate(item, true);
|
||||
} else {
|
||||
Notebook.navigate(item, true);
|
||||
@@ -66,18 +66,18 @@ export const openNotebookTopic = item => {
|
||||
|
||||
export const NotebookWrapper = React.memo(
|
||||
({ item, index, dateBy }) => {
|
||||
const isTrash = item.type === 'trash';
|
||||
const isTrash = item.type === "trash";
|
||||
|
||||
return (
|
||||
<SelectionWrapper
|
||||
pinned={item.pinned}
|
||||
index={index}
|
||||
onPress={() => openNotebookTopic(item)}
|
||||
height={item.type === 'topic' ? 80 : 110}
|
||||
height={item.type === "topic" ? 80 : 110}
|
||||
item={item}
|
||||
>
|
||||
<NotebookItem
|
||||
isTopic={item.type === 'topic'}
|
||||
isTopic={item.type === "topic"}
|
||||
item={item}
|
||||
dateBy={dateBy}
|
||||
index={index}
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Animated, { SlideInUp, SlideOutDown } from 'react-native-reanimated';
|
||||
import { openVault, ToastEvent } from '../../../services/event-manager';
|
||||
import Navigation from '../../../services/navigation';
|
||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
||||
import { useNotebookStore } from '../../../stores/use-notebook-store';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { dWidth, getElevation, toTXT } from '../../../utils';
|
||||
import { db } from '../../../common/database';
|
||||
import { deleteItems } from '../../../utils/functions';
|
||||
import { presentDialog } from '../../dialog/functions';
|
||||
import { Button } from '../../ui/button';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Animated, { SlideInUp, SlideOutDown } from "react-native-reanimated";
|
||||
import { openVault, ToastEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { dWidth, getElevation, toTXT } from "../../../utils";
|
||||
import { db } from "../../../common/database";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
|
||||
export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const setNotebooks = useNotebookStore(state => state.setNotebooks);
|
||||
const setMenuPins = useMenuStore(state => state.setMenuPins);
|
||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
||||
const setSelectionMode = useSelectionStore(state => state.setSelectionMode);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
|
||||
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(false);
|
||||
const [width, setWidth] = useState(dWidth - 16);
|
||||
useEffect(() => {
|
||||
if (note.type === 'note') return;
|
||||
if (note.type === "note") return;
|
||||
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
||||
}, []);
|
||||
|
||||
const updateNotes = () => {
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Notes',
|
||||
'Favorites',
|
||||
'ColoredNotes',
|
||||
'TaggedNotes',
|
||||
'TopicNotes'
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
};
|
||||
|
||||
const actions = [
|
||||
{
|
||||
title: 'Pin ' + note.type,
|
||||
icon: note.pinned ? 'pin-off' : 'pin',
|
||||
visible: note.type === 'note' || note.type === 'notebook',
|
||||
title: "Pin " + note.type,
|
||||
icon: note.pinned ? "pin-off" : "pin",
|
||||
visible: note.type === "note" || note.type === "notebook",
|
||||
onPress: async () => {
|
||||
if (!note.id) return;
|
||||
|
||||
if (note.type === 'note') {
|
||||
if (note.type === "note") {
|
||||
if (db.notes.pinned.length === 3 && !note.pinned) {
|
||||
ToastEvent.show({
|
||||
heading: 'Cannot pin more than 3 notes',
|
||||
type: 'error'
|
||||
heading: "Cannot pin more than 3 notes",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -61,8 +61,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
} else {
|
||||
if (db.notebooks.pinned.length === 3 && !note.pinned) {
|
||||
ToastEvent.show({
|
||||
heading: 'Cannot pin more than 3 notebooks',
|
||||
type: 'error'
|
||||
heading: "Cannot pin more than 3 notebooks",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -74,11 +74,11 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Add to favorites',
|
||||
icon: note.favorite ? 'star-off' : 'star',
|
||||
title: "Add to favorites",
|
||||
icon: note.favorite ? "star-off" : "star",
|
||||
onPress: async () => {
|
||||
if (!note.id) return;
|
||||
if (note.type === 'note') {
|
||||
if (note.type === "note") {
|
||||
await db.notes.note(note.id).favorite();
|
||||
} else {
|
||||
await db.notebooks.notebook(note.id).favorite();
|
||||
@@ -86,23 +86,25 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
updateNotes();
|
||||
setActionStrip(false);
|
||||
},
|
||||
visible: note.type === 'note',
|
||||
color: !note.favorite ? 'orange' : null
|
||||
visible: note.type === "note",
|
||||
color: !note.favorite ? "orange" : null
|
||||
},
|
||||
|
||||
{
|
||||
title: isPinnedToMenu ? 'Remove Shortcut from Menu' : 'Add Shortcut to Menu',
|
||||
icon: isPinnedToMenu ? 'link-variant-remove' : 'link-variant',
|
||||
title: isPinnedToMenu
|
||||
? "Remove Shortcut from Menu"
|
||||
: "Add Shortcut to Menu",
|
||||
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
|
||||
onPress: async () => {
|
||||
try {
|
||||
if (isPinnedToMenu) {
|
||||
await db.settings.unpin(note.id);
|
||||
ToastEvent.show({
|
||||
heading: 'Shortcut removed from menu',
|
||||
type: 'success'
|
||||
heading: "Shortcut removed from menu",
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
if (note.type === 'topic') {
|
||||
if (note.type === "topic") {
|
||||
await db.settings.pin(note.type, {
|
||||
id: note.id,
|
||||
notebookId: note.notebookId
|
||||
@@ -111,8 +113,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
await db.settings.pin(note.type, { id: note.id });
|
||||
}
|
||||
ToastEvent.show({
|
||||
heading: 'Shortcut added to menu',
|
||||
type: 'success'
|
||||
heading: "Shortcut added to menu",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
||||
@@ -121,12 +123,12 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
setActionStrip(false);
|
||||
} catch (e) {}
|
||||
},
|
||||
visible: note.type !== 'note'
|
||||
visible: note.type !== "note"
|
||||
},
|
||||
{
|
||||
title: 'Copy Note',
|
||||
icon: 'content-copy',
|
||||
visible: note.type === 'note',
|
||||
title: "Copy Note",
|
||||
icon: "content-copy",
|
||||
visible: note.type === "note",
|
||||
onPress: async () => {
|
||||
if (note.locked) {
|
||||
openVault({
|
||||
@@ -134,75 +136,77 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: note,
|
||||
title: 'Copy note',
|
||||
description: 'Unlock note to copy to clipboard.'
|
||||
title: "Copy note",
|
||||
description: "Unlock note to copy to clipboard."
|
||||
});
|
||||
} else {
|
||||
let text = await toTXT(note);
|
||||
text = `${note.title}\n \n ${text}`;
|
||||
Clipboard.setString(text);
|
||||
ToastEvent.show({
|
||||
heading: 'Note copied to clipboard',
|
||||
type: 'success'
|
||||
heading: "Note copied to clipboard",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
setActionStrip(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Restore ' + note.itemType,
|
||||
icon: 'delete-restore',
|
||||
title: "Restore " + note.itemType,
|
||||
icon: "delete-restore",
|
||||
onPress: async () => {
|
||||
await db.trash.restore(note.id);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Notes',
|
||||
'Favorites',
|
||||
'ColoredNotes',
|
||||
'TaggedNotes',
|
||||
'TopicNotes',
|
||||
'Trash',
|
||||
'Notebooks'
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes",
|
||||
"Trash",
|
||||
"Notebooks"
|
||||
);
|
||||
|
||||
ToastEvent.show({
|
||||
heading:
|
||||
note.type === 'note' ? 'Note restored from trash' : 'Notebook restored from trash',
|
||||
type: 'success'
|
||||
note.type === "note"
|
||||
? "Note restored from trash"
|
||||
: "Notebook restored from trash",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
setActionStrip(false);
|
||||
},
|
||||
visible: note.type === 'trash'
|
||||
visible: note.type === "trash"
|
||||
},
|
||||
{
|
||||
title: 'Delete' + note.itemType,
|
||||
icon: 'delete',
|
||||
visible: note.type === 'trash',
|
||||
title: "Delete" + note.itemType,
|
||||
icon: "delete",
|
||||
visible: note.type === "trash",
|
||||
onPress: () => {
|
||||
presentDialog({
|
||||
title: `Permanent delete`,
|
||||
paragraph: `Are you sure you want to delete this ${note.itemType} permanantly from trash?`,
|
||||
positiveText: 'Delete',
|
||||
negativeText: 'Cancel',
|
||||
positiveText: "Delete",
|
||||
negativeText: "Cancel",
|
||||
positivePress: async () => {
|
||||
await db.trash.delete(note.id);
|
||||
useTrashStore.getState().setTrash();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastEvent.show({
|
||||
heading: 'Permanantly deleted items',
|
||||
type: 'success',
|
||||
context: 'local'
|
||||
heading: "Permanantly deleted items",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
},
|
||||
positiveType: 'errorShade'
|
||||
positiveType: "errorShade"
|
||||
});
|
||||
setActionStrip(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Delete' + note.type,
|
||||
icon: 'delete',
|
||||
visible: note.type !== 'trash',
|
||||
title: "Delete" + note.type,
|
||||
icon: "delete",
|
||||
visible: note.type !== "trash",
|
||||
onPress: async () => {
|
||||
try {
|
||||
await deleteItems(note);
|
||||
@@ -211,8 +215,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Close',
|
||||
icon: 'close',
|
||||
title: "Close",
|
||||
icon: "close",
|
||||
onPress: () => setActionStrip(false),
|
||||
color: colors.light,
|
||||
bg: colors.red,
|
||||
@@ -222,19 +226,19 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
onLayout={event => {
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
entering={SlideInUp.springify().mass(0.4)}
|
||||
exiting={SlideOutDown}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
width: '102%',
|
||||
height: '100%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center'
|
||||
width: "102%",
|
||||
height: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -242,7 +246,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
title="Select"
|
||||
icon="check"
|
||||
tooltipText="Select Item"
|
||||
onPress={event => {
|
||||
onPress={(event) => {
|
||||
if (!selectionMode) {
|
||||
setSelectionMode(true);
|
||||
}
|
||||
@@ -256,7 +260,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
}}
|
||||
height={30}
|
||||
/>
|
||||
{actions.map(item =>
|
||||
{actions.map((item) =>
|
||||
item.visible ? (
|
||||
<View
|
||||
key={item.icon}
|
||||
@@ -265,8 +269,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
||||
height: width / 1.4 / actions.length,
|
||||
backgroundColor: item.bg || colors.nav,
|
||||
borderRadius: 100,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
...getElevation(5),
|
||||
marginLeft: 15
|
||||
}}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { useEditorStore } from '../../../stores/use-editor-store';
|
||||
import { hexToRGBA } from '../../../utils/color-scheme/utils';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
import { hexToRGBA } from "../../../utils/color-scheme/utils";
|
||||
|
||||
export const Filler = ({ item, background }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
const currentEditingNote = useEditorStore(state => state.currentEditingNote);
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
);
|
||||
|
||||
const color = 'gray';
|
||||
const color = "gray";
|
||||
|
||||
return currentEditingNote === item.id ? (
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '110%',
|
||||
height: '150%',
|
||||
backgroundColor: currentEditingNote === item.id ? hexToRGBA(colors[color], 0.12) : null,
|
||||
position: "absolute",
|
||||
width: "110%",
|
||||
height: "150%",
|
||||
backgroundColor:
|
||||
currentEditingNote === item.id
|
||||
? hexToRGBA(colors[color], 0.12)
|
||||
: null,
|
||||
borderLeftWidth: 5,
|
||||
borderLeftColor:
|
||||
currentEditingNote === item.id ? colors[item.color || 'accent'] : 'transparent'
|
||||
currentEditingNote === item.id
|
||||
? colors[item.color || "accent"]
|
||||
: "transparent"
|
||||
}}
|
||||
></View>
|
||||
) : null;
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { history } from '../../../utils';
|
||||
import { PressableButton } from '../../ui/pressable';
|
||||
import { ActionStrip } from './action-strip';
|
||||
import { Filler } from './back-fill';
|
||||
import { SelectionIcon } from './selection';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { history } from "../../../utils";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import { ActionStrip } from "./action-strip";
|
||||
import { Filler } from "./back-fill";
|
||||
import { SelectionIcon } from "./selection";
|
||||
|
||||
const SelectionWrapper = ({ children, item, background, onLongPress, onPress, testID }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const SelectionWrapper = ({
|
||||
children,
|
||||
item,
|
||||
background,
|
||||
onLongPress,
|
||||
onPress,
|
||||
testID
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [actionStrip, setActionStrip] = useState(false);
|
||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
||||
const listMode = item.type === 'notebook' ? notebooksListMode : notesListMode;
|
||||
const compactMode = (item.type === 'notebook' || item.type === 'note') && listMode === 'compact';
|
||||
const notebooksListMode = useSettingStore(
|
||||
(state) => state.settings.notebooksListMode
|
||||
);
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
);
|
||||
const listMode = item.type === "notebook" ? notebooksListMode : notesListMode;
|
||||
const compactMode =
|
||||
(item.type === "notebook" || item.type === "note") &&
|
||||
listMode === "compact";
|
||||
|
||||
const _onLongPress = () => {
|
||||
if (history.selectedItemsList.length > 0) return;
|
||||
@@ -34,10 +50,10 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent('navigate', closeStrip);
|
||||
eSubscribeEvent("navigate", closeStrip);
|
||||
|
||||
return () => {
|
||||
eUnSubscribeEvent('navigate', closeStrip);
|
||||
eUnSubscribeEvent("navigate", closeStrip);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -51,17 +67,19 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
||||
customAlpha={!colors.night ? -0.02 : 0.02}
|
||||
customOpacity={1}
|
||||
customStyle={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
borderRadius: 0,
|
||||
overflow: 'hidden',
|
||||
overflow: "hidden",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: compactMode ? 8 : 12
|
||||
}}
|
||||
>
|
||||
{item.type === 'note' ? <Filler background={background} item={item} /> : null}
|
||||
{item.type === "note" ? (
|
||||
<Filler background={background} item={item} />
|
||||
) : null}
|
||||
<SelectionIcon
|
||||
compactMode={compactMode}
|
||||
setActionStrip={setActionStrip}
|
||||
@@ -70,7 +88,9 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
||||
/>
|
||||
{children}
|
||||
|
||||
{actionStrip ? <ActionStrip note={item} setActionStrip={setActionStrip} /> : null}
|
||||
{actionStrip ? (
|
||||
<ActionStrip note={item} setActionStrip={setActionStrip} />
|
||||
) : null}
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
|
||||
export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
||||
const selectedItemsList = useSelectionStore(state => state.selectedItemsList);
|
||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||
const [selected, setSelected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectionMode) {
|
||||
setActionStrip(false);
|
||||
let exists = selectedItemsList.filter(o => o.dateCreated === item.dateCreated);
|
||||
let exists = selectedItemsList.filter(
|
||||
(o) => o.dateCreated === item.dateCreated
|
||||
);
|
||||
|
||||
if (exists[0]) {
|
||||
if (!selected) {
|
||||
@@ -37,12 +41,12 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
||||
return selectionMode ? (
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
display: "flex",
|
||||
opacity: 1,
|
||||
width: '10%',
|
||||
width: "10%",
|
||||
height: compactMode ? 40 : 70,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: colors.bg,
|
||||
borderRadius: 5,
|
||||
marginRight: 10,
|
||||
@@ -54,13 +58,17 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
||||
activeOpacity={1}
|
||||
onPress={onPress}
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 70
|
||||
}}
|
||||
>
|
||||
{selected && (
|
||||
<Icon size={SIZE.xl} color={selected ? colors.accent : colors.icon} name="check" />
|
||||
<Icon
|
||||
size={SIZE.xl}
|
||||
color={selected ? colors.accent : colors.icon}
|
||||
name="check"
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { notesnook } from '../../../../e2e/test.ids';
|
||||
import { TaggedNotes } from '../../../screens/notes/tagged';
|
||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
||||
import { db } from '../../../common/database';
|
||||
import { SIZE } from '../../../utils/size';
|
||||
import { Properties } from '../../properties';
|
||||
import { IconButton } from '../../ui/icon-button';
|
||||
import { PressableButton } from '../../ui/pressable';
|
||||
import Heading from '../../ui/typography/heading';
|
||||
import Paragraph from '../../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { db } from "../../../common/database";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
const TagItem = React.memo(
|
||||
({ item, index }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const onPress = () => {
|
||||
TaggedNotes.navigate(item, true);
|
||||
};
|
||||
@@ -27,16 +27,16 @@ const TagItem = React.memo(
|
||||
opacity={1}
|
||||
customStyle={{
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: 'row',
|
||||
flexDirection: "row",
|
||||
paddingVertical: 12,
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
maxWidth: '92%'
|
||||
maxWidth: "92%"
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md}>
|
||||
@@ -58,9 +58,9 @@ const TagItem = React.memo(
|
||||
}}
|
||||
>
|
||||
{item && item.noteIds.length && item.noteIds.length > 1
|
||||
? item.noteIds.length + ' notes'
|
||||
? item.noteIds.length + " notes"
|
||||
: item.noteIds.length === 1
|
||||
? item.noteIds.length + ' note'
|
||||
? item.noteIds.length + " note"
|
||||
: null}
|
||||
</Paragraph>
|
||||
</View>
|
||||
@@ -74,11 +74,11 @@ const TagItem = React.memo(
|
||||
}}
|
||||
testID={notesnook.ids.tag.menu}
|
||||
customStyle={{
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
height: 35,
|
||||
width: 35,
|
||||
borderRadius: 100,
|
||||
alignItems: 'center'
|
||||
alignItems: "center"
|
||||
}}
|
||||
/>
|
||||
</PressableButton>
|
||||
@@ -96,6 +96,6 @@ const TagItem = React.memo(
|
||||
}
|
||||
);
|
||||
|
||||
TagItem.displayName = 'TagItem';
|
||||
TagItem.displayName = "TagItem";
|
||||
|
||||
export default TagItem;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useMessageStore } from '../../stores/use-message-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { hexToRGBA } from '../../utils/color-scheme/utils';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { PressableButton } from '../ui/pressable';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { hexToRGBA } from "../../utils/color-scheme/utils";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const Card = ({ color, warning }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
color = color ? color : colors.accent;
|
||||
const messageBoardState = useMessageStore(state => state.message);
|
||||
const announcement = useMessageStore(state => state.announcement);
|
||||
const messageBoardState = useMessageStore((state) => state.message);
|
||||
const announcement = useMessageStore((state) => state.announcement);
|
||||
|
||||
return !messageBoardState.visible || announcement || warning ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: '95%'
|
||||
width: "95%"
|
||||
}}
|
||||
>
|
||||
<PressableButton
|
||||
@@ -25,10 +25,10 @@ export const Card = ({ color, warning }) => {
|
||||
type="gray"
|
||||
customStyle={{
|
||||
paddingVertical: 12,
|
||||
width: '95%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: "95%",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
>
|
||||
@@ -36,19 +36,21 @@ export const Card = ({ color, warning }) => {
|
||||
style={{
|
||||
width: 40,
|
||||
backgroundColor:
|
||||
messageBoardState.type === 'error'
|
||||
messageBoardState.type === "error"
|
||||
? hexToRGBA(colors.red, 0.15)
|
||||
: hexToRGBA(color, 0.15),
|
||||
height: 40,
|
||||
marginLeft: 10,
|
||||
borderRadius: 100,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={SIZE.lg}
|
||||
color={messageBoardState.type === 'error' ? colors.errorText : color}
|
||||
color={
|
||||
messageBoardState.type === "error" ? colors.errorText : color
|
||||
}
|
||||
name={messageBoardState.icon}
|
||||
/>
|
||||
</View>
|
||||
@@ -56,7 +58,7 @@ export const Card = ({ color, warning }) => {
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
maxWidth: '70%'
|
||||
maxWidth: "70%"
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
@@ -64,7 +66,7 @@ export const Card = ({ color, warning }) => {
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
style={{
|
||||
maxWidth: '100%'
|
||||
maxWidth: "100%"
|
||||
}}
|
||||
color={colors.heading}
|
||||
>
|
||||
@@ -76,15 +78,15 @@ export const Card = ({ color, warning }) => {
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "absolute",
|
||||
right: 6
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="chevron-right"
|
||||
color={messageBoardState.type === 'error' ? colors.red : color}
|
||||
color={messageBoardState.type === "error" ? colors.red : color}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,49 +1,61 @@
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, useWindowDimensions, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useSettingStore } from '../../stores/use-setting-store';
|
||||
import { useTip } from '../../services/tip-manager';
|
||||
import { COLORS_NOTE } from '../../utils/color-scheme';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { Button } from '../ui/button';
|
||||
import Seperator from '../ui/seperator';
|
||||
import { Tip } from '../tip';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import React from "react";
|
||||
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useTip } from "../../services/tip-manager";
|
||||
import { COLORS_NOTE } from "../../utils/color-scheme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { Tip } from "../tip";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
|
||||
export const Empty = React.memo(
|
||||
({ loading = true, placeholderData, headerProps, type, screen }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const insets = useSafeAreaInsets();
|
||||
const { height } = useWindowDimensions();
|
||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
|
||||
const tip = useTip(
|
||||
screen === 'Notes' && introCompleted ? 'first-note' : placeholderData.type || type,
|
||||
screen === 'Notes' ? 'notes' : null
|
||||
screen === "Notes" && introCompleted
|
||||
? "first-note"
|
||||
: placeholderData.type || type,
|
||||
screen === "Notes" ? "notes" : null
|
||||
);
|
||||
const color =
|
||||
colors[COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'];
|
||||
colors[
|
||||
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||
? headerProps.color
|
||||
: "accent"
|
||||
];
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
height: height - (140 + insets.top),
|
||||
width: '80%',
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'center'
|
||||
width: "80%",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center"
|
||||
}
|
||||
]}
|
||||
>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Tip
|
||||
color={COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'}
|
||||
color={
|
||||
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||
? headerProps.color
|
||||
: "accent"
|
||||
}
|
||||
tip={tip || { text: placeholderData.paragraph }}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
backgroundColor: "transparent",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
/>
|
||||
@@ -56,11 +68,13 @@ export const Empty = React.memo(
|
||||
icon="arrow-right"
|
||||
onPress={placeholderData.action}
|
||||
accentColor={
|
||||
COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'
|
||||
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||
? headerProps.color
|
||||
: "accent"
|
||||
}
|
||||
accentText="light"
|
||||
style={{
|
||||
alignSelf: 'flex-start',
|
||||
alignSelf: "flex-start",
|
||||
borderRadius: 5,
|
||||
height: 40
|
||||
}}
|
||||
@@ -71,9 +85,9 @@ export const Empty = React.memo(
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%'
|
||||
alignSelf: "center",
|
||||
alignItems: "flex-start",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading>{placeholderData.heading}</Heading>
|
||||
@@ -83,7 +97,9 @@ export const Empty = React.memo(
|
||||
<Seperator />
|
||||
<ActivityIndicator
|
||||
size={SIZE.lg}
|
||||
color={COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent}
|
||||
color={
|
||||
COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { FlatList, RefreshControl, View } from 'react-native';
|
||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import { eSendEvent } from '../../services/event-manager';
|
||||
import Sync from '../../services/sync';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import { eScrollEvent } from '../../utils/events';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import JumpToSectionDialog from '../dialogs/jump-to-section';
|
||||
import { Footer } from '../list-items/footer';
|
||||
import { Header } from '../list-items/headers/header';
|
||||
import { SectionHeader } from '../list-items/headers/section-header';
|
||||
import { NoteWrapper } from '../list-items/note/wrapper';
|
||||
import { NotebookWrapper } from '../list-items/notebook/wrapper';
|
||||
import TagItem from '../list-items/tag';
|
||||
import { Empty } from './empty';
|
||||
import React, { useRef } from "react";
|
||||
import { FlatList, RefreshControl, View } from "react-native";
|
||||
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import JumpToSectionDialog from "../dialogs/jump-to-section";
|
||||
import { Footer } from "../list-items/footer";
|
||||
import { Header } from "../list-items/headers/header";
|
||||
import { SectionHeader } from "../list-items/headers/section-header";
|
||||
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import TagItem from "../list-items/tag";
|
||||
import { Empty } from "./empty";
|
||||
|
||||
const renderItems = {
|
||||
note: NoteWrapper,
|
||||
@@ -30,12 +30,13 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
|
||||
if (!item) return <View />;
|
||||
const Item = renderItems[item.itemType || item.type] || View;
|
||||
const groupOptions = db.settings?.getGroupOptions(type);
|
||||
const dateBy = groupOptions.sortBy !== 'title' ? groupOptions.sortBy : 'dateEdited';
|
||||
const dateBy =
|
||||
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
|
||||
|
||||
const tags =
|
||||
item.tags
|
||||
?.slice(0, 3)
|
||||
?.map(item => {
|
||||
?.map((item) => {
|
||||
let tag = db.tags.tag(item);
|
||||
|
||||
if (!tag) return null;
|
||||
@@ -45,8 +46,17 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
|
||||
alias: tag.alias
|
||||
};
|
||||
})
|
||||
.filter(t => t !== null) || [];
|
||||
return <Item item={item} tags={tags} dateBy={dateBy} index={index} type={type} {...restArgs} />;
|
||||
.filter((t) => t !== null) || [];
|
||||
return (
|
||||
<Item
|
||||
item={item}
|
||||
tags={tags}
|
||||
dateBy={dateBy}
|
||||
index={index}
|
||||
type={type}
|
||||
{...restArgs}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const List = ({
|
||||
@@ -56,14 +66,14 @@ const List = ({
|
||||
placeholderData,
|
||||
loading,
|
||||
headerProps = {
|
||||
heading: 'Home',
|
||||
heading: "Home",
|
||||
color: null
|
||||
},
|
||||
screen,
|
||||
ListHeader,
|
||||
warning
|
||||
}) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const scrollRef = useRef();
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
@@ -73,7 +83,7 @@ const List = ({
|
||||
index={index}
|
||||
color={headerProps.color}
|
||||
title={headerProps.heading}
|
||||
type={screen === 'Notes' ? 'home' : type}
|
||||
type={screen === "Notes" ? "home" : type}
|
||||
screen={screen}
|
||||
/>
|
||||
),
|
||||
@@ -81,7 +91,7 @@ const List = ({
|
||||
);
|
||||
|
||||
const _onRefresh = async () => {
|
||||
Sync.run('global', false, true, () => {
|
||||
Sync.run("global", false, true, () => {
|
||||
if (refreshCallback) {
|
||||
refreshCallback();
|
||||
}
|
||||
@@ -89,7 +99,7 @@ const List = ({
|
||||
};
|
||||
|
||||
const _onScroll = React.useCallback(
|
||||
event => {
|
||||
(event) => {
|
||||
if (!event) return;
|
||||
let y = event.nativeEvent.contentOffset.y;
|
||||
eSendEvent(eScrollEvent, {
|
||||
@@ -101,12 +111,12 @@ const List = ({
|
||||
);
|
||||
|
||||
let styles = {
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
minHeight: 1,
|
||||
minWidth: 1
|
||||
};
|
||||
|
||||
const _keyExtractor = item => item.id || item.title;
|
||||
const _keyExtractor = (item) => item.id || item.title;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -114,7 +124,7 @@ const List = ({
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
entering={type === 'search' ? undefined : FadeInDown}
|
||||
entering={type === "search" ? undefined : FadeInDown}
|
||||
>
|
||||
<FlatList
|
||||
style={styles}
|
||||
@@ -171,7 +181,7 @@ const List = ({
|
||||
<JumpToSectionDialog
|
||||
screen={screen}
|
||||
data={listData}
|
||||
type={screen === 'Notes' ? 'home' : type}
|
||||
type={screen === "Notes" ? "home" : type}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
import KeepAwake from '@sayem314/react-native-keep-awake';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Modal, SafeAreaView, Text, View } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Editor from '../../screens/editor';
|
||||
import { editorController } from '../../screens/editor/tiptap/utils';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import Navigation from '../../services/navigation';
|
||||
import Sync from '../../services/sync';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { dHeight } from '../../utils';
|
||||
import { db } from '../../common/database';
|
||||
import { eOnLoadNote, eShowMergeDialog } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { timeConverter } from '../../utils/time';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import DialogButtons from '../dialog/dialog-buttons';
|
||||
import DialogContainer from '../dialog/dialog-container';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import { Button } from '../ui/button';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import Seperator from '../ui/seperator';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import KeepAwake from "@sayem314/react-native-keep-awake";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Modal, SafeAreaView, Text, View } from "react-native";
|
||||
import Animated from "react-native-reanimated";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Editor from "../../screens/editor";
|
||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { dHeight } from "../../utils";
|
||||
import { db } from "../../common/database";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { timeConverter } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogButtons from "../dialog/dialog-buttons";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
const MergeConflicts = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [keep, setKeep] = useState(null);
|
||||
const [copy, setCopy] = useState(null);
|
||||
@@ -55,7 +59,7 @@ const MergeConflicts = () => {
|
||||
|
||||
if (copy) {
|
||||
await db.notes.add({
|
||||
title: note.title + ' (Copy)',
|
||||
title: note.title + " (Copy)",
|
||||
content: {
|
||||
data: copy.data,
|
||||
type: copy.type
|
||||
@@ -63,21 +67,24 @@ const MergeConflicts = () => {
|
||||
});
|
||||
}
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Notes',
|
||||
'Favorites',
|
||||
'ColoredNotes',
|
||||
'TaggedNotes',
|
||||
'TopicNotes'
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
if (editorController.current?.note?.id === note.id) {
|
||||
// reload the note in editor
|
||||
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
|
||||
eSendEvent(eOnLoadNote, {
|
||||
...editorController.current?.note,
|
||||
forced: true
|
||||
});
|
||||
}
|
||||
close();
|
||||
Sync.run();
|
||||
};
|
||||
|
||||
const show = async item => {
|
||||
const show = async (item) => {
|
||||
let noteContent = await db.content.raw(item.contentId);
|
||||
content.current = { ...noteContent };
|
||||
if (__DEV__) {
|
||||
@@ -102,42 +109,59 @@ const MergeConflicts = () => {
|
||||
setDialogVisible(false);
|
||||
};
|
||||
|
||||
const ConfigBar = ({ isDiscarded, keeping, back, isCurrent, contentToKeep }) => {
|
||||
const ConfigBar = ({
|
||||
isDiscarded,
|
||||
keeping,
|
||||
back,
|
||||
isCurrent,
|
||||
contentToKeep
|
||||
}) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 50,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingLeft: 6
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{back && <IconButton onPress={close} color={colors.pri} name="arrow-left" />}
|
||||
<Paragraph style={{ flexWrap: 'wrap' }} color={colors.icon} size={SIZE.xs}>
|
||||
<Text style={{ color: isCurrent ? colors.accent : colors.red, fontWeight: 'bold' }}>
|
||||
{isCurrent ? '(This Device)' : '(Incoming)'}
|
||||
{back && (
|
||||
<IconButton onPress={close} color={colors.pri} name="arrow-left" />
|
||||
)}
|
||||
<Paragraph
|
||||
style={{ flexWrap: "wrap" }}
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: isCurrent ? colors.accent : colors.red,
|
||||
fontWeight: "bold"
|
||||
}}
|
||||
>
|
||||
{isCurrent ? "(This Device)" : "(Incoming)"}
|
||||
</Text>
|
||||
{'\n'}
|
||||
{"\n"}
|
||||
{timeConverter(contentToKeep?.dateEdited)}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end'
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
}}
|
||||
>
|
||||
{isDiscarded ? (
|
||||
@@ -187,7 +211,7 @@ const MergeConflicts = () => {
|
||||
}}
|
||||
type="accent"
|
||||
fontSize={SIZE.xs}
|
||||
title={keeping && !isDiscarded ? 'Undo' : 'Keep'}
|
||||
title={keeping && !isDiscarded ? "Undo" : "Keep"}
|
||||
onPress={() => {
|
||||
setKeep(keeping && !isDiscarded ? null : contentToKeep);
|
||||
}}
|
||||
@@ -208,11 +232,11 @@ const MergeConflicts = () => {
|
||||
close();
|
||||
}}
|
||||
supportedOrientations={[
|
||||
'portrait',
|
||||
'portrait-upside-down',
|
||||
'landscape',
|
||||
'landscape-left',
|
||||
'landscape-right'
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
"landscape",
|
||||
"landscape-left",
|
||||
"landscape-right"
|
||||
]}
|
||||
visible={true}
|
||||
>
|
||||
@@ -244,9 +268,9 @@ const MergeConflicts = () => {
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
backgroundColor: DDS.isLargeTablet() ? 'rgba(0,0,0,0.3)' : null
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
|
||||
}}
|
||||
>
|
||||
<ConfigBar
|
||||
@@ -273,7 +297,7 @@ const MergeConflicts = () => {
|
||||
onLoad={() => {
|
||||
const note = db.notes.note(content.current?.noteId)?.data;
|
||||
if (!note) return;
|
||||
eSendEvent(eOnLoadNote + ':conflictPrimary', {
|
||||
eSendEvent(eOnLoadNote + ":conflictPrimary", {
|
||||
...note,
|
||||
content: {
|
||||
...content.current,
|
||||
@@ -307,7 +331,7 @@ const MergeConflicts = () => {
|
||||
onLoad={() => {
|
||||
const note = db.notes.note(content.current?.noteId)?.data;
|
||||
if (!note) return;
|
||||
eSendEvent(eOnLoadNote + ':conflictSecondary', {
|
||||
eSendEvent(eOnLoadNote + ":conflictSecondary", {
|
||||
...note,
|
||||
content: { ...content.current.conflicted, isPreview: true }
|
||||
});
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { presentSheet } from '../../services/event-manager';
|
||||
import { db } from '../../common/database';
|
||||
import { openLinkInBrowser } from '../../utils/functions';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { timeConverter, timeSince } from '../../utils/time';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { PressableButton } from '../ui/pressable';
|
||||
import Seperator from '../ui/seperator';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import NotePreview from './preview';
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { presentSheet } from "../../services/event-manager";
|
||||
import { db } from "../../common/database";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { timeConverter, timeSince } from "../../utils/time";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import NotePreview from "./preview";
|
||||
|
||||
export default function NoteHistory({ note, fwdRef }) {
|
||||
const [history, setHistory] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -40,7 +40,7 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
content={content}
|
||||
/>
|
||||
),
|
||||
context: 'note_history'
|
||||
context: "note_history"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,12 +48,12 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
let _start = timeConverter(start);
|
||||
let _end = timeConverter(end + 60000);
|
||||
if (_start === _end) return _start;
|
||||
let final = _end.lastIndexOf(',');
|
||||
let final = _end.lastIndexOf(",");
|
||||
let part = _end.slice(0, final + 1);
|
||||
if (_start.includes(part)) {
|
||||
return _start + ' —' + _end.replace(part, '');
|
||||
return _start + " —" + _end.replace(part, "");
|
||||
}
|
||||
return _start + ' — ' + _end;
|
||||
return _start + " — " + _end;
|
||||
};
|
||||
|
||||
const renderItem = useCallback(
|
||||
@@ -62,12 +62,12 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
type="grayBg"
|
||||
onPress={() => preview(item)}
|
||||
customStyle={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
marginBottom: 10,
|
||||
flexDirection: 'row'
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
|
||||
@@ -97,19 +97,21 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
keyExtractor={item => item.id}
|
||||
keyExtractor={(item) => item.id}
|
||||
data={history}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200
|
||||
}}
|
||||
>
|
||||
<Icon name="history" size={60} color={colors.icon} />
|
||||
<Paragraph color={colors.icon}>No note history found on this device.</Paragraph>
|
||||
<Paragraph color={colors.icon}>
|
||||
No note history found on this device.
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
renderItem={renderItem}
|
||||
@@ -118,15 +120,18 @@ export default function NoteHistory({ note, fwdRef }) {
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
style={{
|
||||
alignSelf: 'center'
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
Note version history is local only.{' '}
|
||||
Note version history is local only.{" "}
|
||||
<Text
|
||||
onPress={() => {
|
||||
openLinkInBrowser('https://docs.notesnook.com/versionhistory', colors);
|
||||
openLinkInBrowser(
|
||||
"https://docs.notesnook.com/versionhistory",
|
||||
colors
|
||||
);
|
||||
}}
|
||||
style={{ color: colors.accent, textDecorationLine: 'underline' }}
|
||||
style={{ color: colors.accent, textDecorationLine: "underline" }}
|
||||
>
|
||||
Learn how this works.
|
||||
</Text>
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Editor from '../../screens/editor';
|
||||
import EditorOverlay from '../../screens/editor/loading';
|
||||
import { editorController } from '../../screens/editor/tiptap/utils';
|
||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
||||
import Navigation from '../../services/navigation';
|
||||
import { useEditorStore } from '../../stores/use-editor-store';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { db } from '../../common/database';
|
||||
import { eCloseProgressDialog, eOnLoadNote } from '../../utils/events';
|
||||
import DialogHeader from '../dialog/dialog-header';
|
||||
import { Button } from '../ui/button';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Editor from "../../screens/editor";
|
||||
import EditorOverlay from "../../screens/editor/loading";
|
||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useEditorStore } from "../../stores/use-editor-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { db } from "../../common/database";
|
||||
import { eCloseProgressDialog, eOnLoadNote } from "../../utils/events";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Button } from "../ui/button";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export default function NotePreview({ session, content }) {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const editorId = ':noteHistory';
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const editorId = ":noteHistory";
|
||||
|
||||
async function restore() {
|
||||
await db.noteHistory.restore(session.id);
|
||||
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
|
||||
if (editorController.current?.note) {
|
||||
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
|
||||
eSendEvent(eOnLoadNote, {
|
||||
...editorController.current?.note,
|
||||
forced: true
|
||||
});
|
||||
}
|
||||
}
|
||||
eSendEvent(eCloseProgressDialog, 'note_history');
|
||||
eSendEvent(eCloseProgressDialog, "note_history");
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
'Notes',
|
||||
'Favorites',
|
||||
'ColoredNotes',
|
||||
'TaggedNotes',
|
||||
'TopicNotes'
|
||||
"Notes",
|
||||
"Favorites",
|
||||
"ColoredNotes",
|
||||
"TaggedNotes",
|
||||
"TopicNotes"
|
||||
);
|
||||
|
||||
ToastEvent.show({
|
||||
heading: 'Note restored successfully',
|
||||
type: 'success'
|
||||
heading: "Note restored successfully",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +47,7 @@ export default function NotePreview({ session, content }) {
|
||||
<View
|
||||
style={{
|
||||
height: session.locked ? null : 600,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<DialogHeader padding={12} title={session.session} />
|
||||
@@ -71,13 +74,15 @@ export default function NotePreview({ session, content }) {
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
height: 100,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon}>Preview not available, content is encrypted.</Paragraph>
|
||||
<Paragraph color={colors.icon}>
|
||||
Preview not available, content is encrypted.
|
||||
</Paragraph>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -86,7 +91,12 @@ export default function NotePreview({ session, content }) {
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Button onPress={restore} title="Restore this version" type="accent" width="100%" />
|
||||
<Button
|
||||
onPress={restore}
|
||||
title="Restore this version"
|
||||
type="accent"
|
||||
width="100%"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,50 +1,55 @@
|
||||
import React from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
import { FeatureBlock } from './feature';
|
||||
import React from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
import { FeatureBlock } from "./feature";
|
||||
|
||||
export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scrollRef }) => {
|
||||
export const CompactFeatures = ({
|
||||
vertical,
|
||||
features = [],
|
||||
maxHeight = 500,
|
||||
scrollRef
|
||||
}) => {
|
||||
let data = vertical
|
||||
? features
|
||||
: [
|
||||
{
|
||||
highlight: 'Everything',
|
||||
content: 'in basic',
|
||||
icon: 'emoticon-wink'
|
||||
highlight: "Everything",
|
||||
content: "in basic",
|
||||
icon: "emoticon-wink"
|
||||
},
|
||||
{
|
||||
highlight: 'Unlimited',
|
||||
content: 'notebooks',
|
||||
icon: 'notebook'
|
||||
highlight: "Unlimited",
|
||||
content: "notebooks",
|
||||
icon: "notebook"
|
||||
},
|
||||
{
|
||||
highlight: 'File & image',
|
||||
content: 'attachments',
|
||||
icon: 'attachment'
|
||||
highlight: "File & image",
|
||||
content: "attachments",
|
||||
icon: "attachment"
|
||||
},
|
||||
{
|
||||
highlight: 'Instant',
|
||||
content: 'syncing',
|
||||
icon: 'sync'
|
||||
highlight: "Instant",
|
||||
content: "syncing",
|
||||
icon: "sync"
|
||||
},
|
||||
{
|
||||
highlight: 'Private',
|
||||
content: 'vault',
|
||||
icon: 'shield'
|
||||
highlight: "Private",
|
||||
content: "vault",
|
||||
icon: "shield"
|
||||
},
|
||||
{
|
||||
highlight: 'Rich text',
|
||||
content: 'editing',
|
||||
icon: 'square-edit-outline'
|
||||
highlight: "Rich text",
|
||||
content: "editing",
|
||||
icon: "square-edit-outline"
|
||||
},
|
||||
{
|
||||
highlight: 'PDF & markdown',
|
||||
content: 'exports',
|
||||
icon: 'file'
|
||||
highlight: "PDF & markdown",
|
||||
content: "exports",
|
||||
icon: "file"
|
||||
},
|
||||
{
|
||||
highlight: 'Encrypted',
|
||||
content: 'backups',
|
||||
icon: 'backup-restore'
|
||||
highlight: "Encrypted",
|
||||
content: "backups",
|
||||
icon: "backup-restore"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -57,11 +62,11 @@ export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scro
|
||||
}}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
maxHeight: maxHeight
|
||||
}}
|
||||
>
|
||||
{data.map(item => (
|
||||
{data.map((item) => (
|
||||
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
@@ -1,62 +1,64 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ActivityIndicator, ScrollView, View } from 'react-native';
|
||||
import { LAUNCH_ROCKET } from '../../assets/images/assets';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { useUserStore } from '../../stores/use-user-store';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
||||
import PremiumService from '../../services/premium';
|
||||
import { getElevation } from '../../utils';
|
||||
import { db } from '../../common/database';
|
||||
import React, { useState } from "react";
|
||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||
import { LAUNCH_ROCKET } from "../../assets/images/assets";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { getElevation } from "../../utils";
|
||||
import { db } from "../../common/database";
|
||||
import {
|
||||
eClosePremiumDialog,
|
||||
eCloseProgressDialog,
|
||||
eOpenLoginDialog,
|
||||
eOpenResultDialog
|
||||
} from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import umami from '../../common/analytics';
|
||||
import { IconButton } from '../ui/icon-button';
|
||||
import { AuthMode } from '../auth';
|
||||
import { Button } from '../ui/button';
|
||||
import SheetProvider from '../sheet-provider';
|
||||
import { SvgView } from '../ui/svg';
|
||||
import Seperator from '../ui/seperator';
|
||||
import { Toast } from '../toast';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { Walkthrough } from '../walkthroughs';
|
||||
import { features } from './features';
|
||||
import { Group } from './group';
|
||||
import { PricingPlans } from './pricing-plans';
|
||||
import { usePricing } from '../../hooks/use-pricing';
|
||||
} from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import umami from "../../common/analytics";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { AuthMode } from "../auth";
|
||||
import { Button } from "../ui/button";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { SvgView } from "../ui/svg";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { Toast } from "../toast";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Walkthrough } from "../walkthroughs";
|
||||
import { features } from "./features";
|
||||
import { Group } from "./group";
|
||||
import { PricingPlans } from "./pricing-plans";
|
||||
import { usePricing } from "../../hooks/use-pricing";
|
||||
|
||||
export const Component = ({ close, promo, getRef }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const user = useUserStore(state => state.user);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const user = useUserStore((state) => state.user);
|
||||
const userCanRequestTrial =
|
||||
user && (!user.subscription || !user.subscription.expiry) ? true : false;
|
||||
const [floatingButton, setFloatingButton] = useState(false);
|
||||
const pricing = usePricing('monthly');
|
||||
const pricing = usePricing("monthly");
|
||||
|
||||
const onPress = async () => {
|
||||
if (user) {
|
||||
umami.pageView('/pro-plans', `/pro-screen`);
|
||||
umami.pageView("/pro-plans", `/pro-screen`);
|
||||
presentSheet({
|
||||
context: 'pricing_plans',
|
||||
component: <PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
|
||||
context: "pricing_plans",
|
||||
component: (
|
||||
<PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
|
||||
)
|
||||
});
|
||||
} else {
|
||||
close();
|
||||
umami.pageView('/signup', `/pro-screen`);
|
||||
umami.pageView("/signup", `/pro-screen`);
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOpenLoginDialog, AuthMode.trialSignup);
|
||||
}, 400);
|
||||
}
|
||||
};
|
||||
|
||||
const onScroll = event => {
|
||||
const onScroll = (event) => {
|
||||
let contentSize = event.nativeEvent.contentSize.height;
|
||||
contentSize = contentSize - event.nativeEvent.layoutMeasurement.height;
|
||||
let yOffset = event.nativeEvent.contentOffset.y;
|
||||
@@ -70,11 +72,11 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
backgroundColor: colors.bg,
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: "space-between",
|
||||
borderRadius: 10,
|
||||
maxHeight: '100%'
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<SheetProvider context="pricing_plans" />
|
||||
@@ -83,7 +85,7 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
close();
|
||||
}}
|
||||
customStyle={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
right: DDS.isTab ? 30 : 15,
|
||||
top: 30,
|
||||
zIndex: 10,
|
||||
@@ -106,24 +108,28 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
<View
|
||||
key="top-banner"
|
||||
style={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
height: 400,
|
||||
justifyContent: 'center'
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<SvgView width={350} height={350} src={LAUNCH_ROCKET(colors.accent)} />
|
||||
<SvgView
|
||||
width={350}
|
||||
height={350}
|
||||
src={LAUNCH_ROCKET(colors.accent)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Heading
|
||||
key="heading"
|
||||
size={SIZE.lg}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
paddingTop: 20
|
||||
}}
|
||||
>
|
||||
Notesnook{' '}
|
||||
Notesnook{" "}
|
||||
<Heading size={SIZE.lg} color={colors.accent}>
|
||||
Pro
|
||||
</Heading>
|
||||
@@ -140,7 +146,7 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
) : (
|
||||
<Paragraph
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
alignSelf: "center",
|
||||
marginBottom: 20
|
||||
}}
|
||||
size={SIZE.md}
|
||||
@@ -154,10 +160,10 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
textAlign: 'center',
|
||||
alignSelf: 'center',
|
||||
textAlign: "center",
|
||||
alignSelf: "center",
|
||||
paddingBottom: 20,
|
||||
width: '90%'
|
||||
width: "90%"
|
||||
}}
|
||||
>
|
||||
Ready to take the next step on your private note taking journey?
|
||||
@@ -172,7 +178,7 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
eSendEvent(eCloseProgressDialog);
|
||||
await sleep(300);
|
||||
Walkthrough.present('trialstarted', false, true);
|
||||
Walkthrough.present("trialstarted", false, true);
|
||||
} catch (e) {}
|
||||
}}
|
||||
title="Try free for 14 days"
|
||||
@@ -189,8 +195,10 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
<Button
|
||||
key="calltoaction"
|
||||
onPress={onPress}
|
||||
title={promo ? promo.text : user ? `See all plans` : `Sign up for free`}
|
||||
type={userCanRequestTrial ? 'grayAccent' : 'accent'}
|
||||
title={
|
||||
promo ? promo.text : user ? `See all plans` : `Sign up for free`
|
||||
}
|
||||
type={userCanRequestTrial ? "grayAccent" : "accent"}
|
||||
width={250}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
@@ -204,16 +212,16 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
color={colors.icon}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
marginTop: 10,
|
||||
maxWidth: '80%'
|
||||
maxWidth: "80%"
|
||||
}}
|
||||
>
|
||||
{user
|
||||
? `On clicking "Try free for 14 days", your free trial will be activated.`
|
||||
: `After sign up you will be asked to activate your free trial.`}{' '}
|
||||
<Paragraph size={SIZE.xs} style={{ fontWeight: 'bold' }}>
|
||||
: `After sign up you will be asked to activate your free trial.`}{" "}
|
||||
<Paragraph size={SIZE.xs} style={{ fontWeight: "bold" }}>
|
||||
No credit card is required.
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
@@ -238,11 +246,13 @@ export const Component = ({ close, promo, getRef }) => {
|
||||
{floatingButton ? (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
title={promo ? promo.text : user ? `See all plans` : 'Sign up for free'}
|
||||
title={
|
||||
promo ? promo.text : user ? `See all plans` : "Sign up for free"
|
||||
}
|
||||
type="accent"
|
||||
style={{
|
||||
paddingHorizontal: 24,
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
borderRadius: 100,
|
||||
bottom: 30,
|
||||
...getElevation(10)
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import PremiumService from '../../services/premium';
|
||||
import { eOpenPremiumDialog, eOpenResultDialog, eOpenTrialEndingDialog } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { Button } from '../ui/button';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import DialogContainer from '../dialog/dialog-container';
|
||||
import Seperator from '../ui/seperator';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { CompactFeatures } from './compact-features';
|
||||
import { Offer } from './offer';
|
||||
import { usePricing } from '../../hooks/use-pricing';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import {
|
||||
eOpenPremiumDialog,
|
||||
eOpenResultDialog,
|
||||
eOpenTrialEndingDialog
|
||||
} from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { CompactFeatures } from "./compact-features";
|
||||
import { Offer } from "./offer";
|
||||
import { usePricing } from "../../hooks/use-pricing";
|
||||
|
||||
export const Expiring = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [status, setStatus] = useState({
|
||||
title: 'Your trial is ending soon',
|
||||
title: "Your trial is ending soon",
|
||||
offer: null,
|
||||
extend: true
|
||||
});
|
||||
const pricing = usePricing('yearly');
|
||||
const pricing = usePricing("yearly");
|
||||
console.log(pricing?.info?.discount);
|
||||
|
||||
const promo = status.offer
|
||||
@@ -32,8 +40,10 @@ export const Expiring = () => {
|
||||
promoCode:
|
||||
pricing?.info?.discount > 30
|
||||
? pricing.info.sku
|
||||
: 'com.streetwriters.notesnook.sub.yr.trialoffer',
|
||||
text: `GET ${pricing?.info?.discount > 30 ? pricing?.info?.discount : 30}% OFF on yearly`,
|
||||
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
||||
text: `GET ${
|
||||
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}% OFF on yearly`,
|
||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}
|
||||
: null;
|
||||
@@ -45,7 +55,7 @@ export const Expiring = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = status => {
|
||||
const open = (status) => {
|
||||
setStatus(status);
|
||||
setVisible(true);
|
||||
};
|
||||
@@ -60,20 +70,20 @@ export const Expiring = () => {
|
||||
<DialogContainer>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
alignItems: 'center'
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
width: '100%'
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
textBreakStrategy="balanced"
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
paddingTop: 18
|
||||
}}
|
||||
>
|
||||
@@ -82,8 +92,8 @@ export const Expiring = () => {
|
||||
<Seperator />
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
alignItems: 'center'
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{status.offer ? (
|
||||
@@ -95,13 +105,14 @@ export const Expiring = () => {
|
||||
<Paragraph
|
||||
textBreakStrategy="balanced"
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
paddingTop: 0,
|
||||
paddingBottom: 20
|
||||
}}
|
||||
size={SIZE.md + 2}
|
||||
>
|
||||
Upgrade now to continue using all the pro features after your trial ends
|
||||
Upgrade now to continue using all the pro features after
|
||||
your trial ends
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
@@ -116,7 +127,7 @@ export const Expiring = () => {
|
||||
}}
|
||||
size={SIZE.xs + 1}
|
||||
style={{
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationLine: "underline",
|
||||
color: colors.icon,
|
||||
marginTop: 10
|
||||
}}
|
||||
@@ -131,7 +142,7 @@ export const Expiring = () => {
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.nav,
|
||||
width: '100%',
|
||||
width: "100%",
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10
|
||||
}}
|
||||
@@ -142,7 +153,10 @@ export const Expiring = () => {
|
||||
onPress={async () => {
|
||||
setVisible(false);
|
||||
await sleep(300);
|
||||
PremiumService.sheet(null, promo?.discount > 30 ? null : promo);
|
||||
PremiumService.sheet(
|
||||
null,
|
||||
promo?.discount > 30 ? null : promo
|
||||
);
|
||||
}}
|
||||
fontSize={SIZE.md + 2}
|
||||
style={{
|
||||
@@ -157,16 +171,16 @@ export const Expiring = () => {
|
||||
type="gray"
|
||||
title="Not sure yet? Extend trial for 7 days"
|
||||
textStyle={{
|
||||
textDecorationLine: 'underline'
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={async () => {
|
||||
setVisible(false);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenResultDialog, {
|
||||
title: 'Your trial has been extended',
|
||||
title: "Your trial has been extended",
|
||||
paragraph:
|
||||
'Try out all features of Notesnook free for 7 more days. No limitations. No commitments.',
|
||||
button: 'Continue'
|
||||
"Try out all features of Notesnook free for 7 more days. No limitations. No commitments.",
|
||||
button: "Continue"
|
||||
});
|
||||
}}
|
||||
fontSize={SIZE.xs}
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { ProTag } from './pro-tag';
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { ProTag } from "./pro-tag";
|
||||
|
||||
export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
export const FeatureBlock = ({
|
||||
vertical,
|
||||
highlight,
|
||||
content,
|
||||
icon,
|
||||
pro,
|
||||
proTagBg
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return vertical ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 10
|
||||
}}
|
||||
@@ -22,7 +29,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: 'wrap',
|
||||
flexWrap: "wrap",
|
||||
marginLeft: 5,
|
||||
flexShrink: 1
|
||||
}}
|
||||
@@ -35,7 +42,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
||||
<View
|
||||
style={{
|
||||
height: 100,
|
||||
justifyContent: 'center',
|
||||
justifyContent: "center",
|
||||
padding: 10,
|
||||
marginRight: 10,
|
||||
borderRadius: 5,
|
||||
@@ -45,7 +52,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
||||
<Icon color={colors.icon} name={icon} size={SIZE.xl} />
|
||||
<Paragraph size={SIZE.md}>
|
||||
<Text style={{ color: colors.accent }}>{highlight}</Text>
|
||||
{'\n'}
|
||||
{"\n"}
|
||||
{content}
|
||||
</Paragraph>
|
||||
|
||||
|
||||
@@ -1,224 +1,227 @@
|
||||
export const features = [
|
||||
{
|
||||
title: 'Focused on privacy',
|
||||
title: "Focused on privacy",
|
||||
detail:
|
||||
'Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.',
|
||||
"Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Zero ads',
|
||||
content: '& zero trackers',
|
||||
icon: 'billboard'
|
||||
highlight: "Zero ads",
|
||||
content: "& zero trackers",
|
||||
icon: "billboard"
|
||||
},
|
||||
{
|
||||
highlight: 'On device',
|
||||
content: 'encryption',
|
||||
icon: 'cellphone'
|
||||
highlight: "On device",
|
||||
content: "encryption",
|
||||
icon: "cellphone"
|
||||
},
|
||||
{
|
||||
highlight: 'Secure app',
|
||||
content: 'lock for all',
|
||||
icon: 'cellphone-lock'
|
||||
highlight: "Secure app",
|
||||
content: "lock for all",
|
||||
icon: "cellphone-lock"
|
||||
},
|
||||
{
|
||||
highlight: '100% end-to-end ',
|
||||
content: 'encrypted',
|
||||
icon: 'lock'
|
||||
highlight: "100% end-to-end ",
|
||||
content: "encrypted",
|
||||
icon: "lock"
|
||||
},
|
||||
{
|
||||
highlight: 'Password protected',
|
||||
content: 'notes sharing',
|
||||
icon: 'file-lock'
|
||||
highlight: "Password protected",
|
||||
content: "notes sharing",
|
||||
icon: "file-lock"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
title: 'No limit on notes or devices',
|
||||
title: "No limit on notes or devices",
|
||||
detail:
|
||||
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
|
||||
},
|
||||
{
|
||||
title: 'Attach files & images',
|
||||
detail: 'Add your documents, PDFs, images and videos, and keep them safe and organized.',
|
||||
title: "Attach files & images",
|
||||
detail:
|
||||
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
|
||||
pro: true,
|
||||
features: [
|
||||
{
|
||||
highlight: 'Bullet proof',
|
||||
content: 'encryption',
|
||||
icon: 'lock'
|
||||
highlight: "Bullet proof",
|
||||
content: "encryption",
|
||||
icon: "lock"
|
||||
},
|
||||
{
|
||||
highlight: 'High quality',
|
||||
content: '4k images',
|
||||
icon: 'image-multiple'
|
||||
highlight: "High quality",
|
||||
content: "4k images",
|
||||
icon: "image-multiple"
|
||||
},
|
||||
{
|
||||
highlight: 'No monthly',
|
||||
content: 'storage limit',
|
||||
icon: 'harddisk'
|
||||
highlight: "No monthly",
|
||||
content: "storage limit",
|
||||
icon: "harddisk"
|
||||
},
|
||||
{
|
||||
highlight: 'Generous 500 MB',
|
||||
content: 'max file size',
|
||||
icon: 'file-cabinet'
|
||||
highlight: "Generous 500 MB",
|
||||
content: "max file size",
|
||||
icon: "file-cabinet"
|
||||
},
|
||||
{
|
||||
highlight: 'No restriction',
|
||||
content: 'on file type',
|
||||
icon: 'file'
|
||||
highlight: "No restriction",
|
||||
content: "on file type",
|
||||
icon: "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Keep secrets always locked with private vault',
|
||||
title: "Keep secrets always locked with private vault",
|
||||
detail:
|
||||
'An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.',
|
||||
"An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
title: 'Organize yourself in the best way',
|
||||
detail: 'We offer multiple ways to keep you organized. The only limit is your imagination.',
|
||||
title: "Organize yourself in the best way",
|
||||
detail:
|
||||
"We offer multiple ways to keep you organized. The only limit is your imagination.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Unlimited',
|
||||
content: 'notebooks & tags*',
|
||||
icon: 'emoticon',
|
||||
highlight: "Unlimited",
|
||||
content: "notebooks & tags*",
|
||||
icon: "emoticon",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Organize',
|
||||
content: 'with colors',
|
||||
icon: 'palette',
|
||||
highlight: "Organize",
|
||||
content: "with colors",
|
||||
icon: "palette",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Side menu',
|
||||
content: 'shortcuts',
|
||||
icon: 'link-variant'
|
||||
highlight: "Side menu",
|
||||
content: "shortcuts",
|
||||
icon: "link-variant"
|
||||
},
|
||||
{
|
||||
highlight: 'Pin note in',
|
||||
content: 'notifications',
|
||||
icon: 'pin',
|
||||
platform: 'android'
|
||||
highlight: "Pin note in",
|
||||
content: "notifications",
|
||||
icon: "pin",
|
||||
platform: "android"
|
||||
}
|
||||
],
|
||||
info: '* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags.'
|
||||
info: "* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags."
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Instant syncing',
|
||||
title: "Instant syncing",
|
||||
detail:
|
||||
'Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.',
|
||||
"Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
title: 'Rich tools for rich editing',
|
||||
title: "Rich tools for rich editing",
|
||||
detail:
|
||||
'Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.',
|
||||
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Basic formating',
|
||||
content: 'and lists',
|
||||
icon: 'format-bold'
|
||||
highlight: "Basic formating",
|
||||
content: "and lists",
|
||||
icon: "format-bold"
|
||||
},
|
||||
{
|
||||
highlight: 'Checklists',
|
||||
content: '& tables',
|
||||
icon: 'table',
|
||||
highlight: "Checklists",
|
||||
content: "& tables",
|
||||
icon: "table",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Markdown',
|
||||
content: 'support',
|
||||
icon: 'language-markdown',
|
||||
highlight: "Markdown",
|
||||
content: "support",
|
||||
icon: "language-markdown",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Write notes from',
|
||||
content: 'notifications',
|
||||
icon: 'bell',
|
||||
platform: 'android'
|
||||
highlight: "Write notes from",
|
||||
content: "notifications",
|
||||
icon: "bell",
|
||||
platform: "android"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Safe publishing to the Internet',
|
||||
title: "Safe publishing to the Internet",
|
||||
detail:
|
||||
'Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.',
|
||||
"Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Password protected',
|
||||
content: 'sharing',
|
||||
icon: 'send-lock'
|
||||
highlight: "Password protected",
|
||||
content: "sharing",
|
||||
icon: "send-lock"
|
||||
},
|
||||
{
|
||||
highlight: 'Self destruct',
|
||||
content: 'monographs',
|
||||
icon: 'bomb'
|
||||
highlight: "Self destruct",
|
||||
content: "monographs",
|
||||
icon: "bomb"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Export and take your notes anywhere',
|
||||
title: "Export and take your notes anywhere",
|
||||
pro: true,
|
||||
detail:
|
||||
'You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.',
|
||||
info: '* Free users can export notes in well formatted plain text.',
|
||||
"You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.",
|
||||
info: "* Free users can export notes in well formatted plain text.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Export as ',
|
||||
content: 'Markdown',
|
||||
icon: 'language-markdown',
|
||||
highlight: "Export as ",
|
||||
content: "Markdown",
|
||||
icon: "language-markdown",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Export as',
|
||||
content: 'PDF',
|
||||
icon: 'file-pdf-box',
|
||||
highlight: "Export as",
|
||||
content: "PDF",
|
||||
icon: "file-pdf-box",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Export as',
|
||||
content: 'HTML',
|
||||
icon: 'language-html5',
|
||||
highlight: "Export as",
|
||||
content: "HTML",
|
||||
icon: "language-html5",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: 'Export as',
|
||||
content: 'text',
|
||||
icon: 'clipboard-text-outline'
|
||||
highlight: "Export as",
|
||||
content: "text",
|
||||
icon: "clipboard-text-outline"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Backup & keep your notes safe',
|
||||
title: "Backup & keep your notes safe",
|
||||
detail:
|
||||
'Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.',
|
||||
"Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.",
|
||||
features: [
|
||||
{
|
||||
highlight: 'Backup',
|
||||
content: 'encryption',
|
||||
icon: 'backup-restore'
|
||||
highlight: "Backup",
|
||||
content: "encryption",
|
||||
icon: "backup-restore"
|
||||
}
|
||||
],
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
title: 'Personalize & make Notesnook your own',
|
||||
detail: 'Change app themes to match your style. Custom themes are coming soon.',
|
||||
title: "Personalize & make Notesnook your own",
|
||||
detail:
|
||||
"Change app themes to match your style. Custom themes are coming soon.",
|
||||
|
||||
features: [
|
||||
{
|
||||
highlight: 'Automatic',
|
||||
content: 'dark mode',
|
||||
icon: 'theme-light-dark',
|
||||
highlight: "Automatic",
|
||||
content: "dark mode",
|
||||
icon: "theme-light-dark",
|
||||
pro: false
|
||||
},
|
||||
{
|
||||
highlight: 'Change accent',
|
||||
content: 'color',
|
||||
icon: 'invert-colors',
|
||||
highlight: "Change accent",
|
||||
content: "color",
|
||||
icon: "invert-colors",
|
||||
pro: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { FeatureBlock } from './feature';
|
||||
import { ProTag } from './pro-tag';
|
||||
import React from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { FeatureBlock } from "./feature";
|
||||
import { ProTag } from "./pro-tag";
|
||||
|
||||
export const Group = ({ item, index }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -19,7 +19,10 @@ export const Group = ({ item, index }) => {
|
||||
}}
|
||||
>
|
||||
{item?.pro ? (
|
||||
<ProTag size={SIZE.sm} background={index % 2 === 0 ? colors.bg : colors.nav} />
|
||||
<ProTag
|
||||
size={SIZE.sm}
|
||||
background={index % 2 === 0 ? colors.bg : colors.nav}
|
||||
/>
|
||||
) : null}
|
||||
<Heading>{item.title}</Heading>
|
||||
<Paragraph size={SIZE.md}>{item.detail}</Paragraph>
|
||||
@@ -32,7 +35,7 @@ export const Group = ({ item, index }) => {
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
{item.features?.map(item => (
|
||||
{item.features?.map((item) => (
|
||||
<FeatureBlock
|
||||
key={item.detail}
|
||||
{...item}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import React, { createRef } from 'react';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { eClosePremiumDialog, eOpenPremiumDialog } from '../../utils/events';
|
||||
import BaseDialog from '../dialog/base-dialog';
|
||||
import { Component } from './component';
|
||||
import React, { createRef } from "react";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { eClosePremiumDialog, eOpenPremiumDialog } from "../../utils/events";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { Component } from "./component";
|
||||
|
||||
class PremiumDialog extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -24,7 +27,7 @@ class PremiumDialog extends React.Component {
|
||||
eUnSubscribeEvent(eClosePremiumDialog, this.close);
|
||||
}
|
||||
|
||||
open = promoInfo => {
|
||||
open = (promoInfo) => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
promo: promoInfo
|
||||
@@ -52,7 +55,11 @@ class PremiumDialog extends React.Component {
|
||||
background={this.props.colors.bg}
|
||||
onRequestClose={this.onClose}
|
||||
>
|
||||
<Component getRef={() => this.actionSheetRef} promo={this.state.promo} close={this.close} />
|
||||
<Component
|
||||
getRef={() => this.actionSheetRef}
|
||||
promo={this.state.promo}
|
||||
close={this.close}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const Offer = ({ off = '30', text = 'on yearly plan, offer ends soon', padding = 0 }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
export const Offer = ({
|
||||
off = "30",
|
||||
text = "on yearly plan, offer ends soon",
|
||||
padding = 0
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
return (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
textAlign: "center",
|
||||
paddingVertical: padding
|
||||
}}
|
||||
size={SIZE.xxxl}
|
||||
>
|
||||
GET {off}
|
||||
<Text style={{ color: colors.accent }}>%</Text> OFF!{'\n'}
|
||||
<Text style={{ color: colors.accent }}>%</Text> OFF!{"\n"}
|
||||
<Paragraph color={colors.icon}>{text}</Paragraph>
|
||||
</Paragraph>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { useThemeStore } from '../../stores/use-theme-store';
|
||||
import { getElevation } from '../../utils';
|
||||
import { eCloseActionSheet, eOpenPremiumDialog, eShowGetPremium } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep } from '../../utils/time';
|
||||
import { Button } from '../ui/button';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { getElevation } from "../../utils";
|
||||
import {
|
||||
eCloseActionSheet,
|
||||
eOpenPremiumDialog,
|
||||
eShowGetPremium
|
||||
} from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
export const PremiumToast = ({ close, context = "global", offset = 0 }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [msg, setMsg] = useState(null);
|
||||
const timer = useRef();
|
||||
|
||||
const open = event => {
|
||||
const open = (event) => {
|
||||
if (!event) {
|
||||
clearTimeout(timer);
|
||||
timer.current = null;
|
||||
@@ -62,17 +70,17 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
||||
entering={FadeInUp}
|
||||
exiting={FadeOutUp}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: "absolute",
|
||||
backgroundColor: colors.nav,
|
||||
zIndex: 999,
|
||||
...getElevation(20),
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
justifyContent: "space-between",
|
||||
top: offset,
|
||||
maxWidth: DDS.isLargeTablet() ? 400 : '98%'
|
||||
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -84,7 +92,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
color={colors.accent}
|
||||
size={SIZE.md}
|
||||
@@ -94,7 +102,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.pri}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user