mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
refactor: run prettier on the whole codebase
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
|
# all
|
||||||
build
|
build
|
||||||
coverage
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
native/
|
|
||||||
public/an.js
|
# mobile
|
||||||
|
apps/mobile/native/
|
||||||
|
|
||||||
|
# web
|
||||||
|
apps/web/public/an.js
|
||||||
|
|
||||||
|
# editor
|
||||||
|
packages/editor/styles/
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
parser: "@typescript-eslint/parser",
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true,
|
es2021: true,
|
||||||
'react-native/react-native': true
|
"react-native/react-native": true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:react/recommended',
|
"plugin:react/recommended",
|
||||||
'plugin:prettier/recommended',
|
"plugin:prettier/recommended",
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
'plugin:@typescript-eslint/recommended'
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
@@ -18,22 +18,31 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
ecmaVersion: 12,
|
ecmaVersion: 12,
|
||||||
es6: true,
|
es6: true,
|
||||||
sourceType: 'module'
|
sourceType: "module"
|
||||||
},
|
},
|
||||||
plugins: ['react', 'react-native', 'prettier', 'unused-imports', '@typescript-eslint'],
|
plugins: [
|
||||||
|
"react",
|
||||||
|
"react-native",
|
||||||
|
"prettier",
|
||||||
|
"unused-imports",
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'react/display-name': 0,
|
"react/display-name": 0,
|
||||||
'no-unused-vars': 'off',
|
"no-unused-vars": "off",
|
||||||
'react/no-unescaped-entities': 'off',
|
"react/no-unescaped-entities": "off",
|
||||||
'unused-imports/no-unused-vars': 'off',
|
"unused-imports/no-unused-vars": "off",
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
'prefer-const': 'off',
|
"prefer-const": "off",
|
||||||
'no-empty': 'off',
|
"no-empty": "off",
|
||||||
'react/prop-types': 0,
|
"react/prop-types": 0,
|
||||||
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', 'ts', 'tsx'] }],
|
"react/jsx-filename-extension": [
|
||||||
'prettier/prettier': [
|
1,
|
||||||
'error',
|
{ extensions: [".js", ".jsx", "ts", "tsx"] }
|
||||||
|
],
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
usePrettierrc: true
|
usePrettierrc: true
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@master
|
- uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: "16"
|
||||||
|
|
||||||
- name: Use specific Java version for the builds
|
- name: Use specific Java version for the builds
|
||||||
uses: joschi/setup-jdk@v2
|
uses: joschi/setup-jdk@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: "11"
|
||||||
architecture: 'x64'
|
architecture: "x64"
|
||||||
|
|
||||||
- name: Install node modules
|
- name: Install node modules
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@master
|
- uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: "16"
|
||||||
|
|
||||||
- name: Use specific Java version for the builds
|
- name: Use specific Java version for the builds
|
||||||
uses: joschi/setup-jdk@v2
|
uses: joschi/setup-jdk@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: "11"
|
||||||
architecture: 'x64'
|
architecture: "x64"
|
||||||
|
|
||||||
- name: Install node modules
|
- name: Install node modules
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -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
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Debug Android",
|
"name": "Debug Android",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
@@ -27,4 +25,4 @@
|
|||||||
"request": "attach"
|
"request": "attach"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{}
|
{}
|
||||||
|
|||||||
@@ -2,21 +2,21 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import 'react-native';
|
import "react-native";
|
||||||
// Note: test renderer must be required after react-native.
|
// Note: test renderer must be required after react-native.
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from "react-test-renderer";
|
||||||
import Heading from '../app/components/ui/typography/heading';
|
import Heading from "../app/components/ui/typography/heading";
|
||||||
import Paragraph from '../app/components/ui/typography/paragraph';
|
import Paragraph from "../app/components/ui/typography/paragraph";
|
||||||
|
|
||||||
it('Heading renders correctly', done => {
|
it("Heading renders correctly", (done) => {
|
||||||
let instance = renderer.create(<Heading>Heading</Heading>);
|
let instance = renderer.create(<Heading>Heading</Heading>);
|
||||||
expect(instance.root.props.children).toBe('Heading');
|
expect(instance.root.props.children).toBe("Heading");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Paragraph renders correctly', done => {
|
it("Paragraph renders correctly", (done) => {
|
||||||
let instance = renderer.create(<Paragraph>Paragraph</Paragraph>);
|
let instance = renderer.create(<Paragraph>Paragraph</Paragraph>);
|
||||||
expect(instance.root.props.children).toBe('Paragraph');
|
expect(instance.root.props.children).toBe("Paragraph");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||||
import { withErrorBoundry } from './components/exception-handler';
|
import { withErrorBoundry } from "./components/exception-handler";
|
||||||
import Launcher from './components/launcher';
|
import Launcher from "./components/launcher";
|
||||||
import { ApplicationHolder } from './navigation';
|
import { ApplicationHolder } from "./navigation";
|
||||||
import Notifications from './services/notifications';
|
import Notifications from "./services/notifications";
|
||||||
import SettingsService from './services/settings';
|
import SettingsService from "./services/settings";
|
||||||
import { TipManager } from './services/tip-manager';
|
import { TipManager } from "./services/tip-manager";
|
||||||
import { useUserStore } from './stores/use-user-store';
|
import { useUserStore } from "./stores/use-user-store";
|
||||||
import { useAppEvents } from './hooks/use-app-events';
|
import { useAppEvents } from "./hooks/use-app-events";
|
||||||
|
|
||||||
SettingsService.init();
|
SettingsService.init();
|
||||||
SettingsService.checkOrientation();
|
SettingsService.checkOrientation();
|
||||||
@@ -16,7 +16,7 @@ const App = () => {
|
|||||||
useAppEvents();
|
useAppEvents();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let { appLockMode } = SettingsService.get();
|
let { appLockMode } = SettingsService.get();
|
||||||
if (appLockMode && appLockMode !== 'none') {
|
if (appLockMode && appLockMode !== "none") {
|
||||||
useUserStore.getState().setVerifyUser(true);
|
useUserStore.getState().setVerifyUser(true);
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -39,4 +39,4 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withErrorBoundry(App, 'App');
|
export default withErrorBoundry(App, "App");
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,12 @@
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import { MMKV } from '../database/mmkv';
|
import { MMKV } from "../database/mmkv";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
|
|
||||||
const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`;
|
const WEBSITE_ID = `3c6890ce-8410-49d5-8831-15fb2eb28a21`;
|
||||||
const baseUrl = `https://analytics.streetwriters.co/api/collect`;
|
const baseUrl = `https://analytics.streetwriters.co/api/collect`;
|
||||||
|
|
||||||
const UA =
|
const UA =
|
||||||
Platform.OS === 'ios'
|
Platform.OS === "ios"
|
||||||
? `Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1`
|
? `Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1`
|
||||||
: `
|
: `
|
||||||
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
|
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
|
||||||
@@ -19,37 +19,37 @@ Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHT
|
|||||||
*/
|
*/
|
||||||
async function canUpdateAnalytics(route, conditions = []) {
|
async function canUpdateAnalytics(route, conditions = []) {
|
||||||
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
|
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
|
||||||
let eventsList = MMKV.getString('notesnookUserEvents');
|
let eventsList = MMKV.getString("notesnookUserEvents");
|
||||||
|
|
||||||
if (eventsList) {
|
if (eventsList) {
|
||||||
eventsList = JSON.parse(eventsList);
|
eventsList = JSON.parse(eventsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventsList && eventsList[route]) {
|
if (eventsList && eventsList[route]) {
|
||||||
console.log('analytics: event already sent', route);
|
console.log("analytics: event already sent", route);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (route !== '/welcome') {
|
if (route !== "/welcome") {
|
||||||
for (let cond of conditions) {
|
for (let cond of conditions) {
|
||||||
if (!eventsList || !eventsList[cond]) {
|
if (!eventsList || !eventsList[cond]) {
|
||||||
console.log('analytics: conditions not met for event', route, cond);
|
console.log("analytics: conditions not met for event", route, cond);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('analytics: will send event', route);
|
console.log("analytics: will send event", route);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveAnalytics(route, value = true) {
|
async function saveAnalytics(route, value = true) {
|
||||||
let eventsList = MMKV.getString('notesnookUserEvents');
|
let eventsList = MMKV.getString("notesnookUserEvents");
|
||||||
if (eventsList) {
|
if (eventsList) {
|
||||||
eventsList = JSON.parse(eventsList);
|
eventsList = JSON.parse(eventsList);
|
||||||
} else {
|
} else {
|
||||||
eventsList = {};
|
eventsList = {};
|
||||||
}
|
}
|
||||||
eventsList[route] = value;
|
eventsList[route] = value;
|
||||||
MMKV.setString('notesnookUserEvents', JSON.stringify(eventsList));
|
MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +61,12 @@ async function saveAnalytics(route, value = true) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function pageView(route, prevRoute = '', conditions = ['/welcome'], once = true) {
|
async function pageView(
|
||||||
|
route,
|
||||||
|
prevRoute = "",
|
||||||
|
conditions = ["/welcome"],
|
||||||
|
once = true
|
||||||
|
) {
|
||||||
if (__DEV__) return;
|
if (__DEV__) return;
|
||||||
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
|
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
|
||||||
let body = {
|
let body = {
|
||||||
@@ -70,22 +75,22 @@ async function pageView(route, prevRoute = '', conditions = ['/welcome'], once =
|
|||||||
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
|
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
|
||||||
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
|
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
|
||||||
hostname: `notesnook-${Platform.OS}`,
|
hostname: `notesnook-${Platform.OS}`,
|
||||||
language: 'en-US',
|
language: "en-US",
|
||||||
screen: '1920x1080'
|
screen: "1920x1080"
|
||||||
},
|
},
|
||||||
type: 'pageview'
|
type: "pageview"
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await fetch(baseUrl, {
|
let response = await fetch(baseUrl, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
'User-Agent': UA
|
"User-Agent": UA
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
console.log('analytics: event sent', route);
|
console.log("analytics: event sent", route);
|
||||||
await saveAnalytics(route);
|
await saveAnalytics(route);
|
||||||
return await response.text();
|
return await response.text();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -99,22 +104,22 @@ async function sendEvent(type, value, once = true) {
|
|||||||
let body = {
|
let body = {
|
||||||
payload: {
|
payload: {
|
||||||
website: WEBSITE_ID,
|
website: WEBSITE_ID,
|
||||||
url: '/',
|
url: "/",
|
||||||
event_type: type,
|
event_type: type,
|
||||||
event_value: value,
|
event_value: value,
|
||||||
hostname: 'notesnook-android-app',
|
hostname: "notesnook-android-app",
|
||||||
language: 'en-US',
|
language: "en-US",
|
||||||
screen: '1920x1080'
|
screen: "1920x1080"
|
||||||
},
|
},
|
||||||
type: 'event'
|
type: "event"
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await fetch(baseUrl, {
|
let response = await fetch(baseUrl, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
'User-Agent': UA
|
"User-Agent": UA
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import 'react-native-get-random-values';
|
import "react-native-get-random-values";
|
||||||
import * as Keychain from 'react-native-keychain';
|
import * as Keychain from "react-native-keychain";
|
||||||
import { generateSecureRandom } from 'react-native-securerandom';
|
import { generateSecureRandom } from "react-native-securerandom";
|
||||||
import Sodium from 'react-native-sodium';
|
import Sodium from "react-native-sodium";
|
||||||
|
|
||||||
const KEYSTORE_CONFIG = Platform.select({
|
const KEYSTORE_CONFIG = Platform.select({
|
||||||
ios: {
|
ios: {
|
||||||
@@ -14,15 +14,23 @@ const KEYSTORE_CONFIG = Platform.select({
|
|||||||
export async function deriveCryptoKey(name, data) {
|
export async function deriveCryptoKey(name, data) {
|
||||||
try {
|
try {
|
||||||
let credentials = await Sodium.deriveKey(data.password, data.salt);
|
let credentials = await Sodium.deriveKey(data.password, data.salt);
|
||||||
await Keychain.setInternetCredentials('notesnook', name, credentials.key, KEYSTORE_CONFIG);
|
await Keychain.setInternetCredentials(
|
||||||
|
"notesnook",
|
||||||
|
name,
|
||||||
|
credentials.key,
|
||||||
|
KEYSTORE_CONFIG
|
||||||
|
);
|
||||||
return credentials.key;
|
return credentials.key;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCryptoKey(name) {
|
export async function getCryptoKey(name) {
|
||||||
try {
|
try {
|
||||||
if (await Keychain.hasInternetCredentials('notesnook')) {
|
if (await Keychain.hasInternetCredentials("notesnook")) {
|
||||||
let credentials = await Keychain.getInternetCredentials('notesnook', KEYSTORE_CONFIG);
|
let credentials = await Keychain.getInternetCredentials(
|
||||||
|
"notesnook",
|
||||||
|
KEYSTORE_CONFIG
|
||||||
|
);
|
||||||
return credentials.password;
|
return credentials.password;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@@ -32,7 +40,7 @@ export async function getCryptoKey(name) {
|
|||||||
|
|
||||||
export async function removeCryptoKey(name) {
|
export async function removeCryptoKey(name) {
|
||||||
try {
|
try {
|
||||||
let result = await Keychain.resetInternetCredentials('notesnook');
|
let result = await Keychain.resetInternetCredentials("notesnook");
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
@@ -51,7 +59,7 @@ export async function generateCryptoKey(password, salt) {
|
|||||||
let credentials = await Sodium.deriveKey(password, salt || null);
|
let credentials = await Sodium.deriveKey(password, salt || null);
|
||||||
return credentials;
|
return credentials;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('generateCryptoKey: ', e);
|
console.log("generateCryptoKey: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,30 +69,32 @@ export function getAlgorithm(base64Variant) {
|
|||||||
|
|
||||||
export async function decrypt(password, data) {
|
export async function decrypt(password, data) {
|
||||||
if (!password.password && !password.key) return undefined;
|
if (!password.password && !password.key) return undefined;
|
||||||
if (password.password && password.password === '' && !password.key) return undefined;
|
if (password.password && password.password === "" && !password.key)
|
||||||
|
return undefined;
|
||||||
let _data = { ...data };
|
let _data = { ...data };
|
||||||
_data.output = 'plain';
|
_data.output = "plain";
|
||||||
return await Sodium.decrypt(password, _data);
|
return await Sodium.decrypt(password, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAlgorithm(alg) {
|
export function parseAlgorithm(alg) {
|
||||||
if (!alg) return {};
|
if (!alg) return {};
|
||||||
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split('-');
|
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
|
||||||
return {
|
return {
|
||||||
encryptionAlgorithm: enc,
|
encryptionAlgorithm: enc,
|
||||||
kdfAlgorithm: kdf,
|
kdfAlgorithm: kdf,
|
||||||
compressionAlgorithm: compressionAlg,
|
compressionAlgorithm: compressionAlg,
|
||||||
isCompress: compressed === '1',
|
isCompress: compressed === "1",
|
||||||
base64_variant: base64variant
|
base64_variant: base64variant
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encrypt(password, data) {
|
export async function encrypt(password, data) {
|
||||||
if (!password.password && !password.key) return undefined;
|
if (!password.password && !password.key) return undefined;
|
||||||
if (password.password && password.password === '' && !password.key) return undefined;
|
if (password.password && password.password === "" && !password.key)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
let message = {
|
let message = {
|
||||||
type: 'plain',
|
type: "plain",
|
||||||
data: data
|
data: data
|
||||||
};
|
};
|
||||||
let result = await Sodium.encrypt(password, message);
|
let result = await Sodium.encrypt(password, message);
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import Database from '@streetwriters/notesnook-core/api/index';
|
import Database from "@streetwriters/notesnook-core/api/index";
|
||||||
import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger';
|
import {
|
||||||
import { Platform } from 'react-native';
|
initalize,
|
||||||
import { MMKVLoader } from 'react-native-mmkv-storage';
|
logger as dbLogger
|
||||||
import filesystem from '../filesystem';
|
} from "@streetwriters/notesnook-core/logger";
|
||||||
import EventSource from '../../utils/sse/even-source-ios';
|
import { Platform } from "react-native";
|
||||||
import AndroidEventSource from '../../utils/sse/event-source';
|
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||||
import Storage, { KV } from './storage';
|
import filesystem from "../filesystem";
|
||||||
const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize();
|
import EventSource from "../../utils/sse/even-source-ios";
|
||||||
|
import AndroidEventSource from "../../utils/sse/event-source";
|
||||||
|
import Storage, { KV } from "./storage";
|
||||||
|
const LoggerStorage = new MMKVLoader()
|
||||||
|
.withInstanceID("notesnook_logs")
|
||||||
|
.initialize();
|
||||||
console.log(LoggerStorage);
|
console.log(LoggerStorage);
|
||||||
initalize(new KV(LoggerStorage));
|
initalize(new KV(LoggerStorage));
|
||||||
export const DatabaseLogger = dbLogger;
|
export const DatabaseLogger = dbLogger;
|
||||||
@@ -16,18 +21,18 @@ export const DatabaseLogger = dbLogger;
|
|||||||
*/
|
*/
|
||||||
export var db = new Database(
|
export var db = new Database(
|
||||||
Storage,
|
Storage,
|
||||||
Platform.OS === 'ios' ? EventSource : AndroidEventSource,
|
Platform.OS === "ios" ? EventSource : AndroidEventSource,
|
||||||
filesystem
|
filesystem
|
||||||
);
|
);
|
||||||
|
|
||||||
db.host(
|
db.host(
|
||||||
__DEV__
|
__DEV__
|
||||||
? {
|
? {
|
||||||
API_HOST: 'https://api.notesnook.com',
|
API_HOST: "https://api.notesnook.com",
|
||||||
AUTH_HOST: 'https://auth.streetwriters.co',
|
AUTH_HOST: "https://auth.streetwriters.co",
|
||||||
SSE_HOST: 'https://events.streetwriters.co',
|
SSE_HOST: "https://events.streetwriters.co",
|
||||||
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
|
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||||
ISSUES_HOST: 'https://issues.streetwriters.co'
|
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||||
// API_HOST: 'http://192.168.10.29:5264',
|
// API_HOST: 'http://192.168.10.29:5264',
|
||||||
// AUTH_HOST: 'http://192.168.10.29:8264',
|
// AUTH_HOST: 'http://192.168.10.29:8264',
|
||||||
// SSE_HOST: 'http://192.168.10.29:7264',
|
// SSE_HOST: 'http://192.168.10.29:7264',
|
||||||
@@ -35,11 +40,11 @@ db.host(
|
|||||||
// ISSUES_HOST: 'http://192.168.10.29:2624'
|
// ISSUES_HOST: 'http://192.168.10.29:2624'
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
API_HOST: 'https://api.notesnook.com',
|
API_HOST: "https://api.notesnook.com",
|
||||||
AUTH_HOST: 'https://auth.streetwriters.co',
|
AUTH_HOST: "https://auth.streetwriters.co",
|
||||||
SSE_HOST: 'https://events.streetwriters.co',
|
SSE_HOST: "https://events.streetwriters.co",
|
||||||
SUBSCRIPTIONS_HOST: 'https://subscriptions.streetwriters.co',
|
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||||
ISSUES_HOST: 'https://issues.streetwriters.co'
|
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import MMKVStorage, { ProcessingModes } from 'react-native-mmkv-storage';
|
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
|
||||||
|
|
||||||
export const MMKV = new MMKVStorage.Loader()
|
export const MMKV = new MMKVStorage.Loader()
|
||||||
.setProcessingMode(
|
.setProcessingMode(
|
||||||
Platform.OS === 'ios' ? ProcessingModes.MULTI_PROCESS : ProcessingModes.SINGLE_PROCESS
|
Platform.OS === "ios"
|
||||||
|
? ProcessingModes.MULTI_PROCESS
|
||||||
|
: ProcessingModes.SINGLE_PROCESS
|
||||||
)
|
)
|
||||||
.initialize();
|
.initialize();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from "rn-fetch-blob";
|
||||||
import {
|
import {
|
||||||
decrypt,
|
decrypt,
|
||||||
deriveCryptoKey,
|
deriveCryptoKey,
|
||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
hash,
|
hash,
|
||||||
removeCryptoKey
|
removeCryptoKey
|
||||||
} from './encryption';
|
} from "./encryption";
|
||||||
import { MMKV } from './mmkv';
|
import { MMKV } from "./mmkv";
|
||||||
|
|
||||||
export class KV {
|
export class KV {
|
||||||
storage = null;
|
storage = null;
|
||||||
@@ -30,7 +30,10 @@ export class KV {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async write(key, data) {
|
async write(key, data) {
|
||||||
this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data));
|
this.storage.setString(
|
||||||
|
key,
|
||||||
|
typeof data === "string" ? data : JSON.stringify(data)
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,12 +67,12 @@ export class KV {
|
|||||||
async getAllKeys() {
|
async getAllKeys() {
|
||||||
let keys = (await this.storage.indexer.getKeys()) || [];
|
let keys = (await this.storage.indexer.getKeys()) || [];
|
||||||
keys = keys.filter(
|
keys = keys.filter(
|
||||||
k =>
|
(k) =>
|
||||||
k !== 'stringIndex' &&
|
k !== "stringIndex" &&
|
||||||
k !== 'boolIndex' &&
|
k !== "boolIndex" &&
|
||||||
k !== 'mapIndex' &&
|
k !== "mapIndex" &&
|
||||||
k !== 'arrayIndex' &&
|
k !== "arrayIndex" &&
|
||||||
k !== 'numberIndex' &&
|
k !== "numberIndex" &&
|
||||||
k !== this.storage.instanceID
|
k !== this.storage.instanceID
|
||||||
);
|
);
|
||||||
return keys;
|
return keys;
|
||||||
@@ -79,14 +82,14 @@ export class KV {
|
|||||||
const DefaultStorage = new KV(MMKV);
|
const DefaultStorage = new KV(MMKV);
|
||||||
|
|
||||||
async function requestPermission() {
|
async function requestPermission() {
|
||||||
if (Platform.OS === 'ios') return true;
|
if (Platform.OS === "ios") return true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async function checkAndCreateDir(path) {
|
async function checkAndCreateDir(path) {
|
||||||
let dir =
|
let dir =
|
||||||
Platform.OS === 'ios'
|
Platform.OS === "ios"
|
||||||
? RNFetchBlob.fs.dirs.DocumentDir + path
|
? RNFetchBlob.fs.dirs.DocumentDir + path
|
||||||
: RNFetchBlob.fs.dirs.SDCardDir + '/Notesnook/' + path;
|
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let exists = await RNFetchBlob.fs.exists(dir);
|
let exists = await RNFetchBlob.fs.exists(dir);
|
||||||
@@ -102,10 +105,10 @@ async function checkAndCreateDir(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
read: key => DefaultStorage.read(key),
|
read: (key) => DefaultStorage.read(key),
|
||||||
write: (key, value) => DefaultStorage.write(key, value),
|
write: (key, value) => DefaultStorage.write(key, value),
|
||||||
readMulti: keys => DefaultStorage.readMulti(keys),
|
readMulti: (keys) => DefaultStorage.readMulti(keys),
|
||||||
remove: key => DefaultStorage.remove(key),
|
remove: (key) => DefaultStorage.remove(key),
|
||||||
clear: () => DefaultStorage.clear(),
|
clear: () => DefaultStorage.clear(),
|
||||||
getAllKeys: () => DefaultStorage.getAllKeys(),
|
getAllKeys: () => DefaultStorage.getAllKeys(),
|
||||||
encrypt,
|
encrypt,
|
||||||
|
|||||||
@@ -1,48 +1,51 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import * as ScopedStorage from 'react-native-scoped-storage';
|
import * as ScopedStorage from "react-native-scoped-storage";
|
||||||
import Sodium from 'react-native-sodium';
|
import Sodium from "react-native-sodium";
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from "rn-fetch-blob";
|
||||||
import { ShareComponent } from '../../components/sheets/export-notes/share';
|
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { presentSheet, ToastEvent } from '../../services/event-manager';
|
import { presentSheet, ToastEvent } from "../../services/event-manager";
|
||||||
import { db } from '../database';
|
import { db } from "../database";
|
||||||
import Storage from '../database/storage';
|
import Storage from "../database/storage";
|
||||||
import { cacheDir, fileCheck } from './utils';
|
import { cacheDir, fileCheck } from "./utils";
|
||||||
import hosts from '@streetwriters/notesnook-core/utils/constants';
|
import hosts from "@streetwriters/notesnook-core/utils/constants";
|
||||||
import NetInfo from '@react-native-community/netinfo';
|
import NetInfo from "@react-native-community/netinfo";
|
||||||
|
|
||||||
export async function downloadFile(filename, data, cancelToken) {
|
export async function downloadFile(filename, data, cancelToken) {
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
let { url, headers } = data;
|
let { url, headers } = data;
|
||||||
|
|
||||||
console.log('downloading file: ', filename, url);
|
console.log("downloading file: ", filename, url);
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
try {
|
try {
|
||||||
let exists = await RNFetchBlob.fs.exists(path);
|
let exists = await RNFetchBlob.fs.exists(path);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
console.log('file is downloaded');
|
console.log("file is downloaded");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await fetch(url, {
|
let res = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve download url`);
|
if (!res.ok)
|
||||||
|
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||||
const downloadUrl = await res.text();
|
const downloadUrl = await res.text();
|
||||||
|
|
||||||
if (!downloadUrl) throw new Error('Unable to resolve download url');
|
if (!downloadUrl) throw new Error("Unable to resolve download url");
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
path: path,
|
path: path,
|
||||||
IOSBackgroundTask: true
|
IOSBackgroundTask: true
|
||||||
})
|
})
|
||||||
.fetch('GET', downloadUrl, null)
|
.fetch("GET", downloadUrl, null)
|
||||||
.progress((recieved, total) => {
|
.progress((recieved, total) => {
|
||||||
useAttachmentStore.getState().setProgress(0, total, filename, recieved, 'download');
|
useAttachmentStore
|
||||||
|
.getState()
|
||||||
|
.setProgress(0, total, filename, recieved, "download");
|
||||||
totalSize = total;
|
totalSize = total;
|
||||||
console.log('downloading: ', recieved, total);
|
console.log("downloading: ", recieved, total);
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelToken.cancel = request.cancel;
|
cancelToken.cancel = request.cancel;
|
||||||
@@ -53,21 +56,21 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
return status >= 200 && status < 300;
|
return status >= 200 && status < 300;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Error downloading file',
|
heading: "Error downloading file",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Error downloading file',
|
heading: "Error downloading file",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
|
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log('download file error: ', e, url, headers);
|
console.log("download file error: ", e, url, headers);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,24 +78,30 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
export async function downloadAttachment(hash, global = true) {
|
export async function downloadAttachment(hash, global = true) {
|
||||||
let attachment = db.attachments.attachment(hash);
|
let attachment = db.attachments.attachment(hash);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
console.log('attachment not found');
|
console.log("attachment not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let folder = {};
|
let folder = {};
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === "android") {
|
||||||
folder = await ScopedStorage.openDocumentTree();
|
folder = await ScopedStorage.openDocumentTree();
|
||||||
if (!folder) return;
|
if (!folder) return;
|
||||||
} else {
|
} else {
|
||||||
folder.uri = await Storage.checkAndCreateDir('/downloads/');
|
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.fs.downloadFile(attachment.metadata.hash, attachment.metadata.hash);
|
await db.fs.downloadFile(
|
||||||
if (!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))) return;
|
attachment.metadata.hash,
|
||||||
|
attachment.metadata.hash
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
let key = await db.attachments.decryptKey(attachment.key);
|
let key = await db.attachments.decryptKey(attachment.key);
|
||||||
console.log('attachment key', key);
|
console.log("attachment key", key);
|
||||||
let info = {
|
let info = {
|
||||||
iv: attachment.iv,
|
iv: attachment.iv,
|
||||||
salt: attachment.salt,
|
salt: attachment.salt,
|
||||||
@@ -108,37 +117,48 @@ export async function downloadAttachment(hash, global = true) {
|
|||||||
|
|
||||||
let fileUri = await Sodium.decryptFile(key, info, false);
|
let fileUri = await Sodium.decryptFile(key, info, false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Download successful',
|
heading: "Download successful",
|
||||||
message: attachment.metadata.filename + ' downloaded',
|
message: attachment.metadata.filename + " downloaded",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (attachment.dateUploaded) {
|
if (attachment.dateUploaded) {
|
||||||
console.log('Deleting attachment after download', attachment.dateUploaded);
|
console.log(
|
||||||
|
"Deleting attachment after download",
|
||||||
|
attachment.dateUploaded
|
||||||
|
);
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === "ios") {
|
||||||
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
||||||
}
|
}
|
||||||
console.log('saved file uri: ', fileUri);
|
console.log("saved file uri: ", fileUri);
|
||||||
|
|
||||||
presentSheet({
|
presentSheet({
|
||||||
title: `File downloaded`,
|
title: `File downloaded`,
|
||||||
paragraph: `${attachment.metadata.filename} saved to ${
|
paragraph: `${attachment.metadata.filename} saved to ${
|
||||||
Platform.OS === 'android' ? 'selected path' : 'File Manager/Notesnook/downloads'
|
Platform.OS === "android"
|
||||||
|
? "selected path"
|
||||||
|
: "File Manager/Notesnook/downloads"
|
||||||
}`,
|
}`,
|
||||||
icon: 'download',
|
icon: "download",
|
||||||
context: global ? null : attachment.metadata.hash,
|
context: global ? null : attachment.metadata.hash,
|
||||||
component: <ShareComponent uri={fileUri} name={attachment.metadata.filename} padding={12} />
|
component: (
|
||||||
|
<ShareComponent
|
||||||
|
uri={fileUri}
|
||||||
|
name={attachment.metadata.filename}
|
||||||
|
padding={12}
|
||||||
|
/>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return fileUri;
|
return fileUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('download attachment error: ', e);
|
console.log("download attachment error: ", e);
|
||||||
if (attachment.dateUploaded) {
|
if (attachment.dateUploaded) {
|
||||||
console.log('Deleting attachment on error', attachment.dateUploaded);
|
console.log("Deleting attachment on error", attachment.dateUploaded);
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
@@ -152,26 +172,27 @@ export async function getUploadedFileSize(hash) {
|
|||||||
const token = await db.user.tokenManager.getAccessToken();
|
const token = await db.user.tokenManager.getAccessToken();
|
||||||
|
|
||||||
const attachmentInfo = await fetch(url, {
|
const attachmentInfo = await fetch(url, {
|
||||||
method: 'HEAD',
|
method: "HEAD",
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentLength = parseInt(attachmentInfo.headers?.get('content-length'));
|
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
|
||||||
console.log('contentLength:', contentLength, attachmentInfo.headers);
|
console.log("contentLength:", contentLength, attachmentInfo.headers);
|
||||||
|
|
||||||
return isNaN(contentLength) ? 0 : contentLength;
|
return isNaN(contentLength) ? 0 : contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkAttachment(hash) {
|
export async function checkAttachment(hash) {
|
||||||
const internetState = await NetInfo.fetch();
|
const internetState = await NetInfo.fetch();
|
||||||
const isInternetReachable = internetState.isConnected && internetState.isInternetReachable;
|
const isInternetReachable =
|
||||||
|
internetState.isConnected && internetState.isInternetReachable;
|
||||||
if (!isInternetReachable) return { success: true };
|
if (!isInternetReachable) return { success: true };
|
||||||
const attachment = db.attachments.attachment(hash);
|
const attachment = db.attachments.attachment(hash);
|
||||||
if (!attachment) return { failed: 'Attachment not found.' };
|
if (!attachment) return { failed: "Attachment not found." };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const size = await getUploadedFileSize(hash);
|
const size = await getUploadedFileSize(hash);
|
||||||
if (size <= 0) return { failed: 'File length is 0.' };
|
if (size <= 0) return { failed: "File length is 0." };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { failed: e?.message };
|
return { failed: e?.message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import { downloadAttachment, downloadFile, getUploadedFileSize, checkAttachment } from './download';
|
import {
|
||||||
import { clearFileStorage, deleteFile, exists, readEncrypted, writeEncrypted } from './io';
|
downloadAttachment,
|
||||||
import { uploadFile } from './upload';
|
downloadFile,
|
||||||
import { cancelable } from './utils';
|
getUploadedFileSize,
|
||||||
|
checkAttachment
|
||||||
|
} from "./download";
|
||||||
|
import {
|
||||||
|
clearFileStorage,
|
||||||
|
deleteFile,
|
||||||
|
exists,
|
||||||
|
readEncrypted,
|
||||||
|
writeEncrypted
|
||||||
|
} from "./io";
|
||||||
|
import { uploadFile } from "./upload";
|
||||||
|
import { cancelable } from "./utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
readEncrypted,
|
readEncrypted,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from "react-native";
|
||||||
import Sodium from 'react-native-sodium';
|
import Sodium from "react-native-sodium";
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from "rn-fetch-blob";
|
||||||
import { cacheDir, getRandomId } from './utils';
|
import { cacheDir, getRandomId } from "./utils";
|
||||||
|
|
||||||
export async function readEncrypted(filename, key, cipherData) {
|
export async function readEncrypted(filename, key, cipherData) {
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
@@ -19,28 +19,28 @@ export async function readEncrypted(filename, key, cipherData) {
|
|||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
console.log('output length: ', output?.length);
|
console.log("output length: ", output?.length);
|
||||||
return output;
|
return output;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
console.log('error');
|
console.log("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeEncrypted(filename, { data, type, key }) {
|
export async function writeEncrypted(filename, { data, type, key }) {
|
||||||
console.log('file input: ', { type, key });
|
console.log("file input: ", { type, key });
|
||||||
let filepath = cacheDir + `/${getRandomId('imagecache_')}`;
|
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||||
console.log(filepath);
|
console.log(filepath);
|
||||||
await RNFetchBlob.fs.writeFile(filepath, data, 'base64');
|
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||||
let output = await Sodium.encryptFile(key, {
|
let output = await Sodium.encryptFile(key, {
|
||||||
uri: Platform.OS === 'ios' ? filepath : `file://` + filepath,
|
uri: Platform.OS === "ios" ? filepath : `file://` + filepath,
|
||||||
type: 'url'
|
type: "url"
|
||||||
});
|
});
|
||||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||||
|
|
||||||
console.log('encrypted file output: ', output);
|
console.log("encrypted file output: ", output);
|
||||||
return {
|
return {
|
||||||
...output,
|
...output,
|
||||||
alg: `xcha-stream`
|
alg: `xcha-stream`
|
||||||
@@ -57,7 +57,7 @@ export async function deleteFile(filename, data) {
|
|||||||
|
|
||||||
let { url, headers } = data;
|
let { url, headers } = data;
|
||||||
try {
|
try {
|
||||||
let response = await RNFetchBlob.fetch('DELETE', url, headers);
|
let response = await RNFetchBlob.fetch("DELETE", url, headers);
|
||||||
let status = response.info().status;
|
let status = response.info().status;
|
||||||
let ok = status >= 200 && status < 300;
|
let ok = status >= 200 && status < 300;
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -65,7 +65,7 @@ export async function deleteFile(filename, data) {
|
|||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('delete file: ', e, url, headers);
|
console.log("delete file: ", e, url, headers);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,38 @@
|
|||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from "rn-fetch-blob";
|
||||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { db } from '../database';
|
import { db } from "../database";
|
||||||
import { cacheDir } from './utils';
|
import { cacheDir } from "./utils";
|
||||||
|
|
||||||
export async function uploadFile(filename, data, cancelToken) {
|
export async function uploadFile(filename, data, cancelToken) {
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
let { url, headers } = data;
|
let { url, headers } = data;
|
||||||
|
|
||||||
console.log('uploading file: ', filename, headers);
|
console.log("uploading file: ", filename, headers);
|
||||||
try {
|
try {
|
||||||
let res = await fetch(url, {
|
let res = await fetch(url, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
||||||
const uploadUrl = await res.text();
|
const uploadUrl = await res.text();
|
||||||
if (!uploadUrl) throw new Error('Unable to resolve upload url');
|
if (!uploadUrl) throw new Error("Unable to resolve upload url");
|
||||||
|
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
IOSBackgroundTask: true
|
IOSBackgroundTask: true
|
||||||
})
|
})
|
||||||
.fetch(
|
.fetch(
|
||||||
'PUT',
|
"PUT",
|
||||||
uploadUrl,
|
uploadUrl,
|
||||||
{
|
{
|
||||||
'content-type': ''
|
"content-type": ""
|
||||||
},
|
},
|
||||||
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
|
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
|
||||||
)
|
)
|
||||||
.uploadProgress((sent, total) => {
|
.uploadProgress((sent, total) => {
|
||||||
useAttachmentStore.getState().setProgress(sent, total, filename, 0, 'upload');
|
useAttachmentStore
|
||||||
console.log('uploading: ', sent, total);
|
.getState()
|
||||||
|
.setProgress(sent, total, filename, 0, "upload");
|
||||||
|
console.log("uploading: ", sent, total);
|
||||||
});
|
});
|
||||||
cancelToken.cancel = request.cancel;
|
cancelToken.cancel = request.cancel;
|
||||||
let response = await request;
|
let response = await request;
|
||||||
@@ -42,7 +44,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
if (result) {
|
if (result) {
|
||||||
let attachment = db.attachments.attachment(filename);
|
let attachment = db.attachments.attachment(filename);
|
||||||
if (!attachment) return result;
|
if (!attachment) return result;
|
||||||
if (!attachment.metadata.type.startsWith('image/')) {
|
if (!attachment.metadata.type.startsWith("image/")) {
|
||||||
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +52,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
console.log('upload file: ', e, url, headers);
|
console.log("upload file: ", e, url, headers);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from "rn-fetch-blob";
|
||||||
|
|
||||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||||
|
|
||||||
export function getRandomId(prefix) {
|
export function getRandomId(prefix) {
|
||||||
return Math.random()
|
return Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.replace('0.', prefix || '');
|
.replace("0.", prefix || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractValueFromXmlTag(code, xml) {
|
export function extractValueFromXmlTag(code, xml) {
|
||||||
if (!xml.includes(code)) return `Unknown ${code}`;
|
if (!xml.includes(code)) return `Unknown ${code}`;
|
||||||
return xml.slice(xml.indexOf(`<${code}>`) + code.length + 2, xml.indexOf(`</${code}>`));
|
return xml.slice(
|
||||||
|
xml.indexOf(`<${code}>`) + code.length + 2,
|
||||||
|
xml.indexOf(`</${code}>`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fileCheck(response, totalSize) {
|
export async function fileCheck(response, totalSize) {
|
||||||
if (totalSize < 1000) {
|
if (totalSize < 1000) {
|
||||||
let text = await response.text();
|
let text = await response.text();
|
||||||
if (text.startsWith('<?xml')) {
|
if (text.startsWith("<?xml")) {
|
||||||
let errorJson = {
|
let errorJson = {
|
||||||
Code: extractValueFromXmlTag('Code', text),
|
Code: extractValueFromXmlTag("Code", text),
|
||||||
Message: extractValueFromXmlTag('Message', text)
|
Message: extractValueFromXmlTag("Message", text)
|
||||||
};
|
};
|
||||||
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
|
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ function info(context: string, ...logs: any[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function error(context: string, ...logs: any[]) {
|
function error(context: string, ...logs: any[]) {
|
||||||
console.log(`${new Date().toLocaleDateString()}::error::${context}: `, ...logs);
|
console.log(
|
||||||
|
`${new Date().toLocaleDateString()}::error::${context}: `,
|
||||||
|
...logs
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger = {
|
type Logger = {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { FlatList, View } from 'react-native';
|
import { FlatList, View } from "react-native";
|
||||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { allowedOnPlatform, renderItem } from './functions';
|
import { allowedOnPlatform, renderItem } from "./functions";
|
||||||
|
|
||||||
export const Announcement = ({ color }) => {
|
export const Announcement = ({ color }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const announcements = useMessageStore(state => state.announcements);
|
const announcements = useMessageStore((state) => state.announcements);
|
||||||
let announcement = announcements.length > 0 ? announcements[0] : null;
|
let announcement = announcements.length > 0 ? announcements[0] : null;
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
return !announcement || selectionMode ? null : (
|
return !announcement || selectionMode ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
paddingBottom: 12
|
paddingBottom: 12
|
||||||
@@ -22,9 +22,9 @@ export const Announcement = ({ color }) => {
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
paddingBottom: 12
|
paddingBottom: 12
|
||||||
}}
|
}}
|
||||||
@@ -32,12 +32,19 @@ export const Announcement = ({ color }) => {
|
|||||||
<View>
|
<View>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
marginTop: 12
|
marginTop: 12
|
||||||
}}
|
}}
|
||||||
data={announcement?.body.filter(item => allowedOnPlatform(item.platforms))}
|
data={announcement?.body.filter((item) =>
|
||||||
|
allowedOnPlatform(item.platforms)
|
||||||
|
)}
|
||||||
renderItem={({ item, index }) =>
|
renderItem={({ item, index }) =>
|
||||||
renderItem({ item: item, index: index, color: colors[color], inline: true })
|
renderItem({
|
||||||
|
item: item,
|
||||||
|
index: index,
|
||||||
|
color: colors[color],
|
||||||
|
inline: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const Body = ({ text, style = {} }) => {
|
export const Body = ({ text, style = {} }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Linking, View } from 'react-native';
|
import { Linking, View } from "react-native";
|
||||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
||||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
import Sync from '../../services/sync';
|
import Sync from "../../services/sync";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { eCloseAnnouncementDialog, eCloseProgressDialog } from '../../utils/events';
|
import {
|
||||||
import { SIZE } from '../../utils/size';
|
eCloseAnnouncementDialog,
|
||||||
import { sleep } from '../../utils/time';
|
eCloseProgressDialog
|
||||||
import { PricingPlans } from '../premium/pricing-plans';
|
} from "../../utils/events";
|
||||||
import SheetProvider from '../sheet-provider';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Progress } from '../sheets/progress';
|
import { sleep } from "../../utils/time";
|
||||||
import { Button } from '../ui/button';
|
import { PricingPlans } from "../premium/pricing-plans";
|
||||||
import { allowedOnPlatform, getStyle } from './functions';
|
import SheetProvider from "../sheet-provider";
|
||||||
|
import { Progress } from "../sheets/progress";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { allowedOnPlatform, getStyle } from "./functions";
|
||||||
|
|
||||||
export const Cta = ({ actions, style = {}, color, inline }) => {
|
export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || [];
|
let buttons =
|
||||||
|
actions.filter((item) => allowedOnPlatform(item.platforms)) || [];
|
||||||
|
|
||||||
const onPress = async item => {
|
const onPress = async (item) => {
|
||||||
if (!inline) {
|
if (!inline) {
|
||||||
eSendEvent(eCloseAnnouncementDialog);
|
eSendEvent(eCloseAnnouncementDialog);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
}
|
}
|
||||||
if (item.type === 'link') {
|
if (item.type === "link") {
|
||||||
Linking.openURL(item.data).catch(console.log);
|
Linking.openURL(item.data).catch(console.log);
|
||||||
} else if (item.type === 'promo') {
|
} else if (item.type === "promo") {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (
|
component: (
|
||||||
<PricingPlans
|
<PricingPlans
|
||||||
@@ -36,11 +40,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else if (item.type === 'force-sync') {
|
} else if (item.type === "force-sync") {
|
||||||
eSendEvent(eCloseProgressDialog);
|
eSendEvent(eCloseProgressDialog);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
Progress.present();
|
Progress.present();
|
||||||
Sync.run('global', true, true, () => {
|
Sync.run("global", true, true, () => {
|
||||||
eSendEvent(eCloseProgressDialog);
|
eSendEvent(eCloseProgressDialog);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -50,7 +54,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
...getStyle(style),
|
...getStyle(style),
|
||||||
flexDirection: inline ? 'row' : 'column'
|
flexDirection: inline ? "row" : "column"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SheetProvider context="premium_cta" />
|
<SheetProvider context="premium_cta" />
|
||||||
@@ -58,20 +62,20 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
{inline ? (
|
{inline ? (
|
||||||
<>
|
<>
|
||||||
{buttons.length > 0 &&
|
{buttons.length > 0 &&
|
||||||
buttons.slice(0, 1).map(item => (
|
buttons.slice(0, 1).map((item) => (
|
||||||
<Button
|
<Button
|
||||||
key={item.title}
|
key={item.title}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
fontSize={SIZE.sm}
|
fontSize={SIZE.sm}
|
||||||
type="transparent"
|
type="transparent"
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
onPress={() => onPress(item)}
|
onPress={() => onPress(item)}
|
||||||
bold
|
bold
|
||||||
style={{
|
style={{
|
||||||
height: 30,
|
height: 30,
|
||||||
alignSelf: 'flex-start',
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
marginTop: -6
|
marginTop: -6
|
||||||
}}
|
}}
|
||||||
@@ -89,13 +93,13 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
width={null}
|
width={null}
|
||||||
height={30}
|
height={30}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'flex-start',
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
marginTop: -6,
|
marginTop: -6,
|
||||||
marginLeft: 12
|
marginLeft: 12
|
||||||
}}
|
}}
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -103,7 +107,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{buttons.length > 0 &&
|
{buttons.length > 0 &&
|
||||||
buttons.slice(0, 1).map(item => (
|
buttons.slice(0, 1).map((item) => (
|
||||||
<Button
|
<Button
|
||||||
key={item.title}
|
key={item.title}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
@@ -134,11 +138,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
width={null}
|
width={null}
|
||||||
height={30}
|
height={30}
|
||||||
style={{
|
style={{
|
||||||
minWidth: '50%',
|
minWidth: "50%",
|
||||||
marginTop: 5
|
marginTop: 5
|
||||||
}}
|
}}
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const Description = ({ text, style = {}, inline }) => {
|
export const Description = ({ text, style = {}, inline }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
return (
|
return (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
marginHorizontal: 12,
|
marginHorizontal: 12,
|
||||||
...getStyle(style),
|
...getStyle(style),
|
||||||
textAlign: inline ? 'left' : style?.textAlign
|
textAlign: inline ? "left" : style?.textAlign
|
||||||
}}
|
}}
|
||||||
size={inline ? SIZE.sm : SIZE.md}
|
size={inline ? SIZE.sm : SIZE.md}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { allowedPlatforms } from '../../stores/use-message-store';
|
import { allowedPlatforms } from "../../stores/use-message-store";
|
||||||
import { ProFeatures } from '../dialogs/result/pro-features';
|
import { ProFeatures } from "../dialogs/result/pro-features";
|
||||||
import { Body } from './body';
|
import { Body } from "./body";
|
||||||
import { Cta } from './cta';
|
import { Cta } from "./cta";
|
||||||
import { Description } from './description';
|
import { Description } from "./description";
|
||||||
import { List } from './list';
|
import { List } from "./list";
|
||||||
import { Photo } from './photo';
|
import { Photo } from "./photo";
|
||||||
import { SubHeading } from './subheading';
|
import { SubHeading } from "./subheading";
|
||||||
import { Title } from './title';
|
import { Title } from "./title";
|
||||||
|
|
||||||
export function allowedOnPlatform(platforms) {
|
export function allowedOnPlatform(platforms) {
|
||||||
if (!platforms) return true;
|
if (!platforms) return true;
|
||||||
return platforms.some(platform => allowedPlatforms.indexOf(platform) > -1);
|
return platforms.some((platform) => allowedPlatforms.indexOf(platform) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const margins = {
|
export const margins = {
|
||||||
@@ -21,12 +21,12 @@ export const margins = {
|
|||||||
2: 20
|
2: 20
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStyle = style => {
|
export const getStyle = (style) => {
|
||||||
if (!style) return {};
|
if (!style) return {};
|
||||||
return {
|
return {
|
||||||
marginTop: margins[style.marginTop] || 0,
|
marginTop: margins[style.marginTop] || 0,
|
||||||
marginBottom: margins[style.marginBottom] || 0,
|
marginBottom: margins[style.marginBottom] || 0,
|
||||||
textAlign: style.textAlign || 'left'
|
textAlign: style.textAlign || "left"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ const Features = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProFeatures />
|
<ProFeatures />
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { FlatList, View } from 'react-native';
|
import { FlatList, View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { eCloseAnnouncementDialog, eOpenAnnouncementDialog } from '../../utils/events';
|
eSubscribeEvent,
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
eUnSubscribeEvent
|
||||||
import { allowedOnPlatform, renderItem } from './functions';
|
} from "../../services/event-manager";
|
||||||
|
import {
|
||||||
|
eCloseAnnouncementDialog,
|
||||||
|
eOpenAnnouncementDialog
|
||||||
|
} from "../../utils/events";
|
||||||
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
|
import { allowedOnPlatform, renderItem } from "./functions";
|
||||||
|
|
||||||
export const AnnouncementDialog = () => {
|
export const AnnouncementDialog = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [info, setInfo] = useState(null);
|
const [info, setInfo] = useState(null);
|
||||||
const remove = useMessageStore(state => state.remove);
|
const remove = useMessageStore((state) => state.remove);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent(eOpenAnnouncementDialog, open);
|
eSubscribeEvent(eOpenAnnouncementDialog, open);
|
||||||
@@ -23,7 +29,7 @@ export const AnnouncementDialog = () => {
|
|||||||
};
|
};
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
const open = data => {
|
const open = (data) => {
|
||||||
setInfo(data);
|
setInfo(data);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
@@ -46,11 +52,11 @@ export const AnnouncementDialog = () => {
|
|||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: DDS.isTab ? 600 : '100%',
|
width: DDS.isTab ? 600 : "100%",
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
maxHeight: DDS.isTab ? '90%' : '100%',
|
maxHeight: DDS.isTab ? "90%" : "100%",
|
||||||
borderRadius: DDS.isTab ? 10 : 0,
|
borderRadius: DDS.isTab ? 10 : 0,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
marginBottom: DDS.isTab ? 20 : 0,
|
marginBottom: DDS.isTab ? 20 : 0,
|
||||||
borderTopRightRadius: 10,
|
borderTopRightRadius: 10,
|
||||||
borderTopLeftRadius: 10
|
borderTopLeftRadius: 10
|
||||||
@@ -58,9 +64,9 @@ export const AnnouncementDialog = () => {
|
|||||||
>
|
>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={{
|
style={{
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
data={info?.body.filter(item => allowedOnPlatform(item.platforms))}
|
data={info?.body.filter((item) => allowedOnPlatform(item.platforms))}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const List = ({ items, listType, style = {} }) => {
|
export const List = ({ items, listType, style = {} }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingLeft: listType === 'ordered' ? 25 : 25,
|
paddingLeft: listType === "ordered" ? 25 : 25,
|
||||||
...getStyle(style)
|
...getStyle(style)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -21,10 +21,10 @@ export const List = ({ items, listType, style = {} }) => {
|
|||||||
key={item.text}
|
key={item.text}
|
||||||
style={{
|
style={{
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{listType === 'ordered' ? (
|
{listType === "ordered" ? (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5
|
marginRight: 5
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Image } from 'react-native';
|
import { Image } from "react-native";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const Photo = ({ src, style = {} }) => {
|
export const Photo = ({ src, style = {} }) => {
|
||||||
return src ? (
|
return src ? (
|
||||||
@@ -8,9 +8,9 @@ export const Photo = ({ src, style = {} }) => {
|
|||||||
source={{ uri: src }}
|
source={{ uri: src }}
|
||||||
resizeMode="cover"
|
resizeMode="cover"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 200,
|
height: 200,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
...getStyle(style)
|
...getStyle(style)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const SubHeading = ({ text, style = {} }) => {
|
export const SubHeading = ({ text, style = {} }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Heading
|
<Heading
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import { getStyle } from './functions';
|
import { getStyle } from "./functions";
|
||||||
|
|
||||||
export const Title = ({ text, style = {}, inline }) => {
|
export const Title = ({ text, style = {}, inline }) => {
|
||||||
const announcements = useMessageStore(state => state.announcements);
|
const announcements = useMessageStore((state) => state.announcements);
|
||||||
let announcement = announcements.length > 0 ? announcements[0] : null;
|
let announcement = announcements.length > 0 ? announcements[0] : null;
|
||||||
const remove = useMessageStore(state => state.remove);
|
const remove = useMessageStore((state) => state.remove);
|
||||||
|
|
||||||
return inline ? (
|
return inline ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginBottom: inline ? 5 : 0
|
marginBottom: inline ? 5 : 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -25,7 +25,7 @@ export const Title = ({ text, style = {}, inline }) => {
|
|||||||
marginHorizontal: 12,
|
marginHorizontal: 12,
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
...getStyle(style),
|
...getStyle(style),
|
||||||
textAlign: inline ? 'left' : style?.textAlign,
|
textAlign: inline ? "left" : style?.textAlign,
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
|||||||
@@ -1,34 +1,42 @@
|
|||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { ScrollView } from 'react-native-gesture-handler';
|
import { ScrollView } from "react-native-gesture-handler";
|
||||||
import picker from '../../screens/editor/tiptap/picker';
|
import picker from "../../screens/editor/tiptap/picker";
|
||||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
import {
|
||||||
import PremiumService from '../../services/premium';
|
eSendEvent,
|
||||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
presentSheet,
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
ToastEvent
|
||||||
import { formatBytes } from '../../utils';
|
} from "../../services/event-manager";
|
||||||
import { db } from '../../common/database';
|
import PremiumService from "../../services/premium";
|
||||||
import { eCloseAttachmentDialog, eCloseProgressDialog } from '../../utils/events';
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import filesystem from '../../common/filesystem';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
|
import { formatBytes } from "../../utils";
|
||||||
import { SIZE } from '../../utils/size';
|
import { db } from "../../common/database";
|
||||||
import { sleep } from '../../utils/time';
|
import {
|
||||||
import { Dialog } from '../dialog';
|
eCloseAttachmentDialog,
|
||||||
import { presentDialog } from '../dialog/functions';
|
eCloseProgressDialog
|
||||||
import { openNote } from '../list-items/note/wrapper';
|
} from "../../utils/events";
|
||||||
import { DateMeta } from '../properties/date-meta';
|
import filesystem from "../../common/filesystem";
|
||||||
import { Button } from '../ui/button';
|
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||||
import { Notice } from '../ui/notice';
|
import { SIZE } from "../../utils/size";
|
||||||
import { PressableButton } from '../ui/pressable';
|
import { sleep } from "../../utils/time";
|
||||||
import Heading from '../ui/typography/heading';
|
import { Dialog } from "../dialog";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import { presentDialog } from "../dialog/functions";
|
||||||
|
import { openNote } from "../list-items/note/wrapper";
|
||||||
|
import { DateMeta } from "../properties/date-meta";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Notice } from "../ui/notice";
|
||||||
|
import { PressableButton } from "../ui/pressable";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const contextId = attachment.metadata.hash;
|
const contextId = attachment.metadata.hash;
|
||||||
const [filename, setFilename] = useState(attachment.metadata.filename);
|
const [filename, setFilename] = useState(attachment.metadata.filename);
|
||||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment);
|
const [currentProgress, setCurrentProgress] =
|
||||||
|
useAttachmentProgress(attachment);
|
||||||
const [failed, setFailed] = useState(attachment.failed);
|
const [failed, setFailed] = useState(attachment.failed);
|
||||||
const [notes, setNotes] = useState([]);
|
const [notes, setNotes] = useState([]);
|
||||||
const [loading, setLoading] = useState({
|
const [loading, setLoading] = useState({
|
||||||
@@ -37,25 +45,25 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
name: 'Download',
|
name: "Download",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (currentProgress) {
|
if (currentProgress) {
|
||||||
await db.fs.cancel(attachment.metadata.hash, 'download');
|
await db.fs.cancel(attachment.metadata.hash, "download");
|
||||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
||||||
}
|
}
|
||||||
filesystem.downloadAttachment(attachment.metadata.hash, false);
|
filesystem.downloadAttachment(attachment.metadata.hash, false);
|
||||||
eSendEvent(eCloseProgressDialog, contextId);
|
eSendEvent(eCloseProgressDialog, contextId);
|
||||||
},
|
},
|
||||||
icon: 'download'
|
icon: "download"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Reupload',
|
name: "Reupload",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (!PremiumService.get()) {
|
if (!PremiumService.get()) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Upgrade to pro',
|
heading: "Upgrade to pro",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -66,13 +74,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
type: attachment.metadata.type
|
type: attachment.metadata.type
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: 'upload'
|
icon: "upload"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Run file check',
|
name: "Run file check",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
setLoading({
|
setLoading({
|
||||||
name: 'Run file check'
|
name: "Run file check"
|
||||||
});
|
});
|
||||||
let res = await filesystem.checkAttachment(attachment.metadata.hash);
|
let res = await filesystem.checkAttachment(attachment.metadata.hash);
|
||||||
if (res.failed) {
|
if (res.failed) {
|
||||||
@@ -83,27 +91,27 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
db.attachments.markAsFailed(attachment.id, null);
|
db.attachments.markAsFailed(attachment.id, null);
|
||||||
}
|
}
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'File check passed',
|
heading: "File check passed",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
setAttachments([...db.attachments.all]);
|
setAttachments([...db.attachments.all]);
|
||||||
setLoading({
|
setLoading({
|
||||||
name: null
|
name: null
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: 'file-check'
|
icon: "file-check"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Rename',
|
name: "Rename",
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
context: contextId,
|
context: contextId,
|
||||||
input: true,
|
input: true,
|
||||||
title: 'Rename file',
|
title: "Rename file",
|
||||||
paragraph: 'Enter a new name for the file',
|
paragraph: "Enter a new name for the file",
|
||||||
defaultValue: attachment.metadata.filename,
|
defaultValue: attachment.metadata.filename,
|
||||||
positivePress: async value => {
|
positivePress: async (value) => {
|
||||||
if (value && value.length > 0) {
|
if (value && value.length > 0) {
|
||||||
await db.attachments.add({
|
await db.attachments.add({
|
||||||
hash: attachment.metadata.hash,
|
hash: attachment.metadata.hash,
|
||||||
@@ -113,31 +121,31 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
setAttachments([...db.attachments.all]);
|
setAttachments([...db.attachments.all]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
positiveText: 'Rename'
|
positiveText: "Rename"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: 'form-textbox'
|
icon: "form-textbox"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Delete',
|
name: "Delete",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
await db.attachments.remove(attachment.metadata.hash, false);
|
await db.attachments.remove(attachment.metadata.hash, false);
|
||||||
setAttachments([...db.attachments.all]);
|
setAttachments([...db.attachments.all]);
|
||||||
eSendEvent(eCloseProgressDialog, contextId);
|
eSendEvent(eCloseProgressDialog, contextId);
|
||||||
},
|
},
|
||||||
icon: 'delete-outline'
|
icon: "delete-outline"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const getNotes = () => {
|
const getNotes = () => {
|
||||||
let allNotes = db.notes.all;
|
let allNotes = db.notes.all;
|
||||||
let attachmentNotes = attachment.noteIds?.map(id => {
|
let attachmentNotes = attachment.noteIds?.map((id) => {
|
||||||
let index = allNotes?.findIndex(note => id === note.id);
|
let index = allNotes?.findIndex((note) => id === note.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
return allNotes[index];
|
return allNotes[index];
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'notfound',
|
type: "notfound",
|
||||||
title: `Note with id ${id} does not exist.`,
|
title: `Note with id ${id} does not exist.`,
|
||||||
id: id
|
id: id
|
||||||
};
|
};
|
||||||
@@ -157,7 +165,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
}}
|
}}
|
||||||
nestedScrollEnabled={true}
|
nestedScrollEnabled={true}
|
||||||
style={{
|
style={{
|
||||||
maxHeight: '100%'
|
maxHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog context={contextId} />
|
<Dialog context={contextId} />
|
||||||
@@ -179,7 +187,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
@@ -210,15 +218,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
size={SIZE.xs + 1}
|
size={SIZE.xs + 1}
|
||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
>
|
>
|
||||||
{attachment.noteIds.length} note{attachment.noteIds.length > 1 ? 's' : ''}
|
{attachment.noteIds.length} note
|
||||||
|
{attachment.noteIds.length > 1 ? "s" : ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Clipboard.setString(attachment.metadata.hash);
|
Clipboard.setString(attachment.metadata.hash);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
type: 'success',
|
type: "success",
|
||||||
heading: 'Attachment hash copied',
|
heading: "Attachment hash copied",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
size={SIZE.xs + 1}
|
size={SIZE.xs + 1}
|
||||||
@@ -250,16 +259,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
List of notes:
|
List of notes:
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
{notes.map(item => (
|
{notes.map((item) => (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (item.type === 'notfound') {
|
if (item.type === "notfound") {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note not found',
|
heading: "Note not found",
|
||||||
message:
|
message:
|
||||||
'A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.',
|
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -267,11 +276,11 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
await sleep(150);
|
await sleep(150);
|
||||||
eSendEvent(eCloseAttachmentDialog);
|
eSendEvent(eCloseAttachmentDialog);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
openNote(item, item.type === 'trash');
|
openNote(item, item.type === "trash");
|
||||||
}}
|
}}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
alignItems: 'flex-start',
|
alignItems: "flex-start",
|
||||||
|
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
@@ -284,13 +293,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{actions.map(item => (
|
{actions.map((item) => (
|
||||||
<Button
|
<Button
|
||||||
key={item.name}
|
key={item.name}
|
||||||
buttonType={{
|
buttonType={{
|
||||||
text: item.on
|
text: item.on
|
||||||
? colors.accent
|
? colors.accent
|
||||||
: item.name === 'Delete' || item.name === 'PermDelete'
|
: item.name === "Delete" || item.name === "PermDelete"
|
||||||
? colors.errorText
|
? colors.errorText
|
||||||
: colors.pri
|
: colors.pri
|
||||||
}}
|
}}
|
||||||
@@ -298,13 +307,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
title={item.name}
|
title={item.name}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
loading={loading?.name === item.name}
|
loading={loading?.name === item.name}
|
||||||
type={item.on ? 'shade' : 'gray'}
|
type={item.on ? "shade" : "gray"}
|
||||||
fontSize={SIZE.sm}
|
fontSize={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
alignSelf: 'flex-start',
|
alignSelf: "flex-start",
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -329,7 +338,9 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
|||||||
Actions.present = (attachment, set, context) => {
|
Actions.present = (attachment, set, context) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
context: context,
|
context: context,
|
||||||
component: ref => <Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
component: (ref) => (
|
||||||
|
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { TouchableOpacity, View } from 'react-native';
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useAttachmentStore } from '../../stores/use-attachment-store';
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { formatBytes } from '../../utils';
|
import { formatBytes } from "../../utils";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { useAttachmentProgress } from '../../hooks/use-attachment-progress';
|
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { ProgressCircleComponent } from '../ui/svg/lazy';
|
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import Actions from './actions';
|
import Actions from "./actions";
|
||||||
|
|
||||||
function getFileExtension(filename) {
|
function getFileExtension(filename) {
|
||||||
var ext = /^.+\.([^.]+)$/.exec(filename);
|
var ext = /^.+\.([^.]+)$/.exec(filename);
|
||||||
return ext == null ? '' : ext[1];
|
return ext == null ? "" : ext[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(attachment, encryption);
|
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||||
|
attachment,
|
||||||
|
encryption
|
||||||
|
);
|
||||||
const encryptionProgress = encryption
|
const encryptionProgress = encryption
|
||||||
? useAttachmentStore(state => state.encryptionProgress)
|
? useAttachmentStore((state) => state.encryptionProgress)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
@@ -33,9 +36,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
marginVertical: 5,
|
marginVertical: 5,
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
padding: 12,
|
padding: 12,
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
@@ -47,14 +50,14 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginLeft: -5
|
marginLeft: -5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -65,7 +68,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
size={6}
|
size={6}
|
||||||
color={colors.light}
|
color={colors.light}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute'
|
position: "absolute"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getFileExtension(attachment.metadata.filename).toUpperCase()}
|
{getFileExtension(attachment.metadata.filename).toUpperCase()}
|
||||||
@@ -81,7 +84,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.sm - 1}
|
size={SIZE.sm - 1}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
marginBottom: 2.5
|
marginBottom: 2.5
|
||||||
}}
|
}}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@@ -92,8 +95,10 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||||
{formatBytes(attachment.length)}{' '}
|
{formatBytes(attachment.length)}{" "}
|
||||||
{currentProgress?.type ? '(' + currentProgress.type + 'ing - tap to cancel)' : ''}
|
{currentProgress?.type
|
||||||
|
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||||
|
: ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -107,7 +112,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
setCurrentProgress(null);
|
setCurrentProgress(null);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
marginRight: -5
|
marginRight: -5
|
||||||
@@ -127,7 +132,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
fontSize: 10
|
fontSize: 10
|
||||||
}}
|
}}
|
||||||
color={colors.accent}
|
color={colors.accent}
|
||||||
formatText={progress => (progress * 100).toFixed(0)}
|
formatText={(progress) => (progress * 100).toFixed(0)}
|
||||||
borderWidth={0}
|
borderWidth={0}
|
||||||
thickness={2}
|
thickness={2}
|
||||||
/>
|
/>
|
||||||
@@ -135,7 +140,11 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{attachment.failed ? (
|
{attachment.failed ? (
|
||||||
<IconButton onPress={onPress} name="alert-circle-outline" color={colors.errorText} />
|
<IconButton
|
||||||
|
onPress={onPress}
|
||||||
|
name="alert-circle-outline"
|
||||||
|
color={colors.errorText}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from "react-native-gesture-handler";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
eSubscribeEvent,
|
||||||
import { db } from '../../common/database';
|
eUnSubscribeEvent
|
||||||
import { eCloseAttachmentDialog, eOpenAttachmentsDialog } from '../../utils/events';
|
} from "../../services/event-manager";
|
||||||
import filesystem from '../../common/filesystem';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { db } from "../../common/database";
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
import {
|
||||||
import { Toast } from '../toast';
|
eCloseAttachmentDialog,
|
||||||
import Input from '../ui/input';
|
eOpenAttachmentsDialog
|
||||||
import Seperator from '../ui/seperator';
|
} from "../../utils/events";
|
||||||
import SheetWrapper from '../ui/sheet';
|
import filesystem from "../../common/filesystem";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import { SIZE } from "../../utils/size";
|
||||||
import { AttachmentItem } from './attachment-item';
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
|
import { Toast } from "../toast";
|
||||||
|
import Input from "../ui/input";
|
||||||
|
import Seperator from "../ui/seperator";
|
||||||
|
import SheetWrapper from "../ui/sheet";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { AttachmentItem } from "./attachment-item";
|
||||||
export const AttachmentDialog = () => {
|
export const AttachmentDialog = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [note, setNote] = useState(null);
|
const [note, setNote] = useState(null);
|
||||||
const actionSheetRef = useRef();
|
const actionSheetRef = useRef();
|
||||||
@@ -34,10 +40,10 @@ export const AttachmentDialog = () => {
|
|||||||
};
|
};
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
const open = data => {
|
const open = (data) => {
|
||||||
if (data?.id) {
|
if (data?.id) {
|
||||||
setNote(data);
|
setNote(data);
|
||||||
let _attachments = db.attachments.ofNote(data.id, 'all');
|
let _attachments = db.attachments.ofNote(data.id, "all");
|
||||||
setAttachments(_attachments);
|
setAttachments(_attachments);
|
||||||
} else {
|
} else {
|
||||||
setAttachments([...db.attachments.all]);
|
setAttachments([...db.attachments.all]);
|
||||||
@@ -56,18 +62,24 @@ export const AttachmentDialog = () => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeText = text => {
|
const onChangeText = (text) => {
|
||||||
attachmentSearchValue.current = text;
|
attachmentSearchValue.current = text;
|
||||||
console.log(attachmentSearchValue.current?.length);
|
console.log(attachmentSearchValue.current?.length);
|
||||||
if (!attachmentSearchValue.current || attachmentSearchValue.current === '') {
|
if (
|
||||||
console.log('resetting all');
|
!attachmentSearchValue.current ||
|
||||||
|
attachmentSearchValue.current === ""
|
||||||
|
) {
|
||||||
|
console.log("resetting all");
|
||||||
setAttachments([...db.attachments.all]);
|
setAttachments([...db.attachments.all]);
|
||||||
}
|
}
|
||||||
console.log(attachments.length);
|
console.log(attachments.length);
|
||||||
clearTimeout(searchTimer.current);
|
clearTimeout(searchTimer.current);
|
||||||
searchTimer.current = setTimeout(() => {
|
searchTimer.current = setTimeout(() => {
|
||||||
let results = db.lookup.attachments(db.attachments.all, attachmentSearchValue.current);
|
let results = db.lookup.attachments(
|
||||||
console.log('results', results.length, attachments.length);
|
db.attachments.all,
|
||||||
|
attachmentSearchValue.current
|
||||||
|
);
|
||||||
|
console.log("results", results.length, attachments.length);
|
||||||
if (results.length === 0) return;
|
if (results.length === 0) return;
|
||||||
setAttachments(results);
|
setAttachments(results);
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -88,24 +100,29 @@ export const AttachmentDialog = () => {
|
|||||||
<Toast context="local" />
|
<Toast context="local" />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
title={note ? 'Attachments' : 'Manage attachments'}
|
title={note ? "Attachments" : "Manage attachments"}
|
||||||
paragraph="Tap on an attachment to view properties"
|
paragraph="Tap on an attachment to view properties"
|
||||||
button={{
|
button={{
|
||||||
title: 'Check all',
|
title: "Check all",
|
||||||
type: 'grayAccent',
|
type: "grayAccent",
|
||||||
loading: loading,
|
loading: loading,
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
for (let attachment of attachments) {
|
for (let attachment of attachments) {
|
||||||
let result = await filesystem.checkAttachment(attachment.metadata.hash);
|
let result = await filesystem.checkAttachment(
|
||||||
|
attachment.metadata.hash
|
||||||
|
);
|
||||||
if (result.failed) {
|
if (result.failed) {
|
||||||
db.attachments.markAsFailed(attachment.metadata.hash, result.failed);
|
db.attachments.markAsFailed(
|
||||||
|
attachment.metadata.hash,
|
||||||
|
result.failed
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
db.attachments.markAsFailed(attachment.id, null);
|
db.attachments.markAsFailed(attachment.id, null);
|
||||||
}
|
}
|
||||||
@@ -139,12 +156,14 @@ export const AttachmentDialog = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 150,
|
height: 150,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="attachment" size={60} color={colors.icon} />
|
<Icon name="attachment" size={60} color={colors.icon} />
|
||||||
<Paragraph>{note ? `No attachments on this note` : `No attachments`}</Paragraph>
|
<Paragraph>
|
||||||
|
{note ? `No attachments on this note` : `No attachments`}
|
||||||
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
@@ -155,7 +174,7 @@ export const AttachmentDialog = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
data={attachments}
|
data={attachments}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -163,12 +182,12 @@ export const AttachmentDialog = () => {
|
|||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
||||||
{' '}All attachments are end-to-end encrypted.
|
{" "}All attachments are end-to-end encrypted.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
</SheetWrapper>
|
</SheetWrapper>
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
eSubscribeEvent,
|
||||||
import { eCloseLoginDialog, eOpenLoginDialog } from '../../utils/events';
|
eUnSubscribeEvent
|
||||||
import { SIZE } from '../../utils/size';
|
} from "../../services/event-manager";
|
||||||
import { sleep } from '../../utils/time';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
|
||||||
import { Toast } from '../toast';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { sleep } from "../../utils/time";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { hideAuth, initialAuthMode } from './common';
|
import { Toast } from "../toast";
|
||||||
import { Login } from './login';
|
import { Button } from "../ui/button";
|
||||||
import { Signup } from './signup';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { hideAuth, initialAuthMode } from "./common";
|
||||||
import { Platform, View } from 'react-native';
|
import { Login } from "./login";
|
||||||
|
import { Signup } from "./signup";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { Platform, View } from "react-native";
|
||||||
|
|
||||||
export const AuthMode = {
|
export const AuthMode = {
|
||||||
login: 0,
|
login: 0,
|
||||||
@@ -22,7 +25,7 @@ export const AuthMode = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AuthModal = () => {
|
const AuthModal = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
|
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
|
||||||
const actionSheetRef = useRef();
|
const actionSheetRef = useRef();
|
||||||
@@ -66,29 +69,31 @@ const AuthModal = () => {
|
|||||||
>
|
>
|
||||||
{currentAuthMode !== AuthMode.login ? (
|
{currentAuthMode !== AuthMode.login ? (
|
||||||
<Signup
|
<Signup
|
||||||
changeMode={mode => setCurrentAuthMode(mode)}
|
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||||
trial={AuthMode.trialSignup === currentAuthMode}
|
trial={AuthMode.trialSignup === currentAuthMode}
|
||||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Login
|
<Login
|
||||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||||
changeMode={mode => setCurrentAuthMode(mode)}
|
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: Platform.OS === 'ios' ? insets.top : 0,
|
top: Platform.OS === "ios" ? insets.top : 0,
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 50,
|
height: 50,
|
||||||
justifyContent:
|
justifyContent:
|
||||||
initialAuthMode.current !== AuthMode.welcomeSignup ? 'space-between' : 'flex-end'
|
initialAuthMode.current !== AuthMode.welcomeSignup
|
||||||
|
? "space-between"
|
||||||
|
: "flex-end"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export const SVG = color =>
|
export const SVG = (color) =>
|
||||||
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="1920" height="1080" preserveAspectRatio="none" viewBox="0 0 1920 1080"><g fill="none"><path d="M684.78-215.3C516.78-49.87 619.54 659.12 349.29 666.13 79.03 673.14-116.04 204.76-321.7 180.13" stroke="${color}" stroke-width="2"></path><path d="M1337.71-20.29C1105.2 122.03 1140.53 887.64 809.58 933.71 478.63 979.78 545.52 798.71 281.45 798.71 17.39 798.71-110.41 932.65-246.68 933.71" stroke="${color}" stroke-width="2"></path><path d="M1631.89-52.3C1379.35-21.93 1283.05 511.66 808.42 545.28 333.79 578.9 209.15 894.14-15.05 901.68" stroke="${color}" stroke-width="2"></path><path d="M1523.21-71.7C1244.4-46.38 1056.2 532.95 581.02 533.49 105.84 534.03-109.4 182.98-361.17 177.09" stroke="${color}" stroke-width="2"></path><path d="M1333.35-39.1C1041.86 57 974.42 893.52 550.36 906.48 126.3 919.44-23.8 619.56-232.63 614.88" stroke="${color}" stroke-width="2"></path></g><defs><mask id="SvgjsMask1032"><rect width="1920" height="1080" fill="#ffffff"></rect></mask></defs></svg>`;
|
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="1920" height="1080" preserveAspectRatio="none" viewBox="0 0 1920 1080"><g fill="none"><path d="M684.78-215.3C516.78-49.87 619.54 659.12 349.29 666.13 79.03 673.14-116.04 204.76-321.7 180.13" stroke="${color}" stroke-width="2"></path><path d="M1337.71-20.29C1105.2 122.03 1140.53 887.64 809.58 933.71 478.63 979.78 545.52 798.71 281.45 798.71 17.39 798.71-110.41 932.65-246.68 933.71" stroke="${color}" stroke-width="2"></path><path d="M1631.89-52.3C1379.35-21.93 1283.05 511.66 808.42 545.28 333.79 578.9 209.15 894.14-15.05 901.68" stroke="${color}" stroke-width="2"></path><path d="M1523.21-71.7C1244.4-46.38 1056.2 532.95 581.02 533.49 105.84 534.03-109.4 182.98-361.17 177.09" stroke="${color}" stroke-width="2"></path><path d="M1333.35-39.1C1041.86 57 974.42 893.52 550.36 906.48 126.3 919.44-23.8 619.56-232.63 614.88" stroke="${color}" stroke-width="2"></path></g><defs><mask id="SvgjsMask1032"><rect width="1920" height="1080" fill="#ffffff"></rect></mask></defs></svg>`;
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
import {
|
||||||
import { db } from '../../common/database';
|
eSendEvent,
|
||||||
import { eCloseProgressDialog } from '../../utils/events';
|
presentSheet,
|
||||||
import { Button } from '../ui/button';
|
ToastEvent
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
} from "../../services/event-manager";
|
||||||
import Input from '../ui/input';
|
import { db } from "../../common/database";
|
||||||
import { Notice } from '../ui/notice';
|
import { eCloseProgressDialog } from "../../utils/events";
|
||||||
import Seperator from '../ui/seperator';
|
import { Button } from "../ui/button";
|
||||||
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
|
import Input from "../ui/input";
|
||||||
|
import { Notice } from "../ui/notice";
|
||||||
|
import Seperator from "../ui/seperator";
|
||||||
|
|
||||||
export const ChangePassword = () => {
|
export const ChangePassword = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
const password = useRef();
|
const password = useRef();
|
||||||
const oldPasswordInputRef = useRef();
|
const oldPasswordInputRef = useRef();
|
||||||
@@ -20,24 +24,24 @@ export const ChangePassword = () => {
|
|||||||
|
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const user = useUserStore(state => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
|
|
||||||
const changePassword = async () => {
|
const changePassword = async () => {
|
||||||
if (!user?.isEmailConfirmed) {
|
if (!user?.isEmailConfirmed) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Email not confirmed',
|
heading: "Email not confirmed",
|
||||||
message: 'Please confirm your email to change account password',
|
message: "Please confirm your email to change account password",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (error || !oldPassword.current || !password.current) {
|
if (error || !oldPassword.current || !password.current) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'All fields required',
|
heading: "All fields required",
|
||||||
message: 'Fill all the fields and try again.',
|
message: "Fill all the fields and try again.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,18 +51,18 @@ export const ChangePassword = () => {
|
|||||||
await db.user.changePassword(oldPassword.current, password.current);
|
await db.user.changePassword(oldPassword.current, password.current);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: `Account password updated`,
|
heading: `Account password updated`,
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
eSendEvent(eCloseProgressDialog);
|
eSendEvent(eCloseProgressDialog);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Failed to change password',
|
heading: "Failed to change password",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -67,16 +71,19 @@ export const ChangePassword = () => {
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
padding: 12
|
padding: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader title="Change password" paragraph="Enter your old and new passwords" />
|
<DialogHeader
|
||||||
|
title="Change password"
|
||||||
|
paragraph="Enter your old and new passwords"
|
||||||
|
/>
|
||||||
<Seperator />
|
<Seperator />
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={oldPasswordInputRef}
|
fwdRef={oldPasswordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
oldPassword.current = value;
|
oldPassword.current = value;
|
||||||
}}
|
}}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
@@ -90,10 +97,10 @@ export const ChangePassword = () => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={passwordInputRef}
|
fwdRef={passwordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
password.current = value;
|
password.current = value;
|
||||||
}}
|
}}
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
@@ -112,12 +119,12 @@ export const ChangePassword = () => {
|
|||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={changePassword}
|
onPress={changePassword}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={loading ? null : 'I understand, change my password'}
|
title={loading ? null : "I understand, change my password"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createRef } from 'react';
|
import { createRef } from "react";
|
||||||
import { eSendEvent } from '../../services/event-manager';
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import Navigation from '../../services/navigation';
|
import Navigation from "../../services/navigation";
|
||||||
import SettingsService from '../../services/settings';
|
import SettingsService from "../../services/settings";
|
||||||
import { eCloseLoginDialog } from '../../utils/events';
|
import { eCloseLoginDialog } from "../../utils/events";
|
||||||
import { tabBarRef } from '../../utils/global-refs';
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
|
|
||||||
export const initialAuthMode = createRef(0);
|
export const initialAuthMode = createRef(0);
|
||||||
export function hideAuth() {
|
export function hideAuth() {
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import ActionSheet from 'react-native-actions-sheet';
|
import ActionSheet from "react-native-actions-sheet";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { ToastEvent } from '../../services/event-manager';
|
import { ToastEvent } from "../../services/event-manager";
|
||||||
import SettingsService from '../../services/settings';
|
import SettingsService from "../../services/settings";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from '../ui/input';
|
import Input from "../ui/input";
|
||||||
import Seperator from '../ui/seperator';
|
import Seperator from "../ui/seperator";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const ForgotPassword = () => {
|
export const ForgotPassword = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const email = useRef();
|
const email = useRef();
|
||||||
const emailInputRef = useRef();
|
const emailInputRef = useRef();
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
@@ -25,17 +25,20 @@ export const ForgotPassword = () => {
|
|||||||
const sendRecoveryEmail = async () => {
|
const sendRecoveryEmail = async () => {
|
||||||
if (!email.current || error) {
|
if (!email.current || error) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Account email is required.',
|
heading: "Account email is required.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
|
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
|
||||||
if (lastRecoveryEmailTime && Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3) {
|
if (
|
||||||
throw new Error('Please wait before requesting another email');
|
lastRecoveryEmailTime &&
|
||||||
|
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
|
||||||
|
) {
|
||||||
|
throw new Error("Please wait before requesting another email");
|
||||||
}
|
}
|
||||||
await db.user.recoverAccount(email.current.toLowerCase());
|
await db.user.recoverAccount(email.current.toLowerCase());
|
||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
@@ -44,8 +47,8 @@ export const ForgotPassword = () => {
|
|||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: `Check your email to reset password`,
|
heading: `Check your email to reset password`,
|
||||||
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
|
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local',
|
context: "local",
|
||||||
duration: 7000
|
duration: 7000
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -53,10 +56,10 @@ export const ForgotPassword = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Recovery email not sent',
|
heading: "Recovery email not sent",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -64,7 +67,7 @@ export const ForgotPassword = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
onBeforeShow={data => (email.current = data)}
|
onBeforeShow={(data) => (email.current = data)}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setSent(false);
|
setSent(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -82,8 +85,8 @@ export const ForgotPassword = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
padding: 12,
|
padding: 12,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingBottom: 50
|
paddingBottom: 50
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -99,7 +102,7 @@ export const ForgotPassword = () => {
|
|||||||
<Heading>Recovery email sent!</Heading>
|
<Heading>Recovery email sent!</Heading>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Please follow the link in the email to recover your account.
|
Please follow the link in the email to recover your account.
|
||||||
@@ -111,7 +114,7 @@ export const ForgotPassword = () => {
|
|||||||
borderRadius: DDS.isTab ? 5 : 0,
|
borderRadius: DDS.isTab ? 5 : 0,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
padding: 12
|
padding: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -123,11 +126,11 @@ export const ForgotPassword = () => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={emailInputRef}
|
fwdRef={emailInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
email.current = value;
|
email.current = value;
|
||||||
}}
|
}}
|
||||||
defaultValue={email.current}
|
defaultValue={email.current}
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
@@ -142,12 +145,12 @@ export const ForgotPassword = () => {
|
|||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={sendRecoveryEmail}
|
onPress={sendRecoveryEmail}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={loading ? null : 'Next'}
|
title={loading ? null : "Next"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { tabBarRef } from '../../utils/global-refs';
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { useNavigationFocus } from '../../hooks/use-navigation-focus';
|
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||||
import { Toast } from '../toast';
|
import { Toast } from "../toast";
|
||||||
import { initialAuthMode } from './common';
|
import { initialAuthMode } from "./common";
|
||||||
import { Login } from './login';
|
import { Login } from "./login";
|
||||||
import { Signup } from './signup';
|
import { Signup } from "./signup";
|
||||||
|
|
||||||
export const AuthMode = {
|
export const AuthMode = {
|
||||||
login: 0,
|
login: 0,
|
||||||
@@ -17,8 +17,10 @@ export const AuthMode = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Auth = ({ navigation, route }) => {
|
const Auth = ({ navigation, route }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [currentAuthMode, setCurrentAuthMode] = useState(route?.params?.mode || AuthMode.login);
|
const [currentAuthMode, setCurrentAuthMode] = useState(
|
||||||
|
route?.params?.mode || AuthMode.login
|
||||||
|
);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
||||||
useNavigationFocus(navigation, {
|
useNavigationFocus(navigation, {
|
||||||
@@ -32,12 +34,15 @@ const Auth = ({ navigation, route }) => {
|
|||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
{currentAuthMode !== AuthMode.login ? (
|
{currentAuthMode !== AuthMode.login ? (
|
||||||
<Signup
|
<Signup
|
||||||
changeMode={mode => setCurrentAuthMode(mode)}
|
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||||
trial={AuthMode.trialSignup === currentAuthMode}
|
trial={AuthMode.trialSignup === currentAuthMode}
|
||||||
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Login welcome={initialAuthMode.current} changeMode={mode => setCurrentAuthMode(mode)} />
|
<Login
|
||||||
|
welcome={initialAuthMode.current}
|
||||||
|
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Platform, View } from 'react-native';
|
import { Platform, View } from "react-native";
|
||||||
import { SheetManager } from 'react-native-actions-sheet';
|
import { SheetManager } from "react-native-actions-sheet";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||||
import { clearMessage } from '../../services/message';
|
import { clearMessage } from "../../services/message";
|
||||||
import PremiumService from '../../services/premium';
|
import PremiumService from "../../services/premium";
|
||||||
import SettingsService from '../../services/settings';
|
import SettingsService from "../../services/settings";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { eCloseLoginDialog } from '../../utils/events';
|
import { eCloseLoginDialog } from "../../utils/events";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from '../../utils/time';
|
import { sleep } from "../../utils/time";
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { Progress } from '../sheets/progress';
|
import { Progress } from "../sheets/progress";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from '../ui/input';
|
import Input from "../ui/input";
|
||||||
import { SvgView } from '../ui/svg';
|
import { SvgView } from "../ui/svg";
|
||||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { SVG } from './background';
|
import { SVG } from "./background";
|
||||||
import { ForgotPassword } from './forgot-password';
|
import { ForgotPassword } from "./forgot-password";
|
||||||
import TwoFactorVerification from './two-factor';
|
import TwoFactorVerification from "./two-factor";
|
||||||
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
|
import Animated, {
|
||||||
import Navigation from '../../services/navigation';
|
FadeInDown,
|
||||||
import { hideAuth } from './common';
|
FadeOutDown,
|
||||||
|
FadeOutUp
|
||||||
|
} from "react-native-reanimated";
|
||||||
|
import Navigation from "../../services/navigation";
|
||||||
|
import { hideAuth } from "./common";
|
||||||
export const Login = ({ changeMode, welcome }) => {
|
export const Login = ({ changeMode, welcome }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const email = useRef();
|
const email = useRef();
|
||||||
const emailInputRef = useRef();
|
const emailInputRef = useRef();
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
@@ -40,15 +44,15 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const setUser = useUserStore(state => state.setUser);
|
const setUser = useUserStore((state) => state.setUser);
|
||||||
|
|
||||||
const validateInfo = () => {
|
const validateInfo = () => {
|
||||||
if (!password.current || !email.current) {
|
if (!password.current || !email.current) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'All fields required',
|
heading: "All fields required",
|
||||||
message: 'Fill all the fields and try again',
|
message: "Fill all the fields and try again",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -71,37 +75,41 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
let user;
|
let user;
|
||||||
try {
|
try {
|
||||||
if (mfa) {
|
if (mfa) {
|
||||||
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
|
await db.user.mfaLogin(
|
||||||
|
email.current.toLowerCase(),
|
||||||
|
password.current,
|
||||||
|
mfa
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await db.user.login(email.current.toLowerCase(), password.current);
|
await db.user.login(email.current.toLowerCase(), password.current);
|
||||||
}
|
}
|
||||||
callback && callback(true);
|
callback && callback(true);
|
||||||
|
|
||||||
user = await db.user.getUser();
|
user = await db.user.getUser();
|
||||||
if (!user) throw new Error('Email or password incorrect!');
|
if (!user) throw new Error("Email or password incorrect!");
|
||||||
PremiumService.setPremiumStatus();
|
PremiumService.setPremiumStatus();
|
||||||
setUser(user);
|
setUser(user);
|
||||||
clearMessage();
|
clearMessage();
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Login successful',
|
heading: "Login successful",
|
||||||
message: `Logged in as ${user.email}`,
|
message: `Logged in as ${user.email}`,
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
hideAuth();
|
hideAuth();
|
||||||
SettingsService.set({
|
SettingsService.set({
|
||||||
sessionExpired: false,
|
sessionExpired: false,
|
||||||
userEmailConfirmed: user?.isEmailConfirmed
|
userEmailConfirmed: user?.isEmailConfirmed
|
||||||
});
|
});
|
||||||
eSendEvent('userLoggedIn', true);
|
eSendEvent("userLoggedIn", true);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
Progress.present();
|
Progress.present();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback && callback(false);
|
callback && callback(false);
|
||||||
if (e.message === 'Multifactor authentication required.') {
|
if (e.message === "Multifactor authentication required.") {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
TwoFactorVerification.present(async mfa => {
|
TwoFactorVerification.present(async (mfa) => {
|
||||||
if (mfa) {
|
if (mfa) {
|
||||||
console.log(mfa);
|
console.log(mfa);
|
||||||
await login(mfa);
|
await login(mfa);
|
||||||
@@ -112,10 +120,10 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: user ? 'Failed to sync' : 'Login failed',
|
heading: user ? "Failed to sync" : "Login failed",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +133,9 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
<>
|
<>
|
||||||
<ForgotPassword />
|
<ForgotPassword />
|
||||||
<SheetProvider context="two_factor_verify" />
|
<SheetProvider context="two_factor_verify" />
|
||||||
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
|
{loading ? (
|
||||||
|
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||||
|
) : null}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeInDown}
|
entering={FadeInDown}
|
||||||
exiting={FadeOutUp}
|
exiting={FadeOutUp}
|
||||||
@@ -133,23 +143,26 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
borderRadius: DDS.isTab ? 5 : 0,
|
borderRadius: DDS.isTab ? 5 : 0,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
minHeight: '100%'
|
minHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 250,
|
height: 250,
|
||||||
overflow: 'hidden'
|
overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
|
<SvgView
|
||||||
|
src={SVG(colors.night ? colors.icon : "black")}
|
||||||
|
height={700}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
marginTop: 15
|
marginTop: 15
|
||||||
@@ -157,7 +170,7 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
size={30}
|
size={30}
|
||||||
color={colors.heading}
|
color={colors.heading}
|
||||||
@@ -166,8 +179,8 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: "underline",
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginTop: 5
|
marginTop: 5
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -180,20 +193,26 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: DDS.isTab ? (focused ? '50%' : '49.99%') : focused ? '100%' : '99.9%',
|
width: DDS.isTab
|
||||||
|
? focused
|
||||||
|
? "50%"
|
||||||
|
: "49.99%"
|
||||||
|
: focused
|
||||||
|
? "100%"
|
||||||
|
: "99.9%",
|
||||||
padding: 12,
|
padding: 12,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
fwdRef={emailInputRef}
|
fwdRef={emailInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
email.current = value;
|
email.current = value;
|
||||||
}}
|
}}
|
||||||
testID="input.email"
|
testID="input.email"
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
@@ -209,7 +228,7 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={passwordInputRef}
|
fwdRef={passwordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
password.current = value;
|
password.current = value;
|
||||||
}}
|
}}
|
||||||
testID="input.password"
|
testID="input.password"
|
||||||
@@ -226,15 +245,15 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
<Button
|
<Button
|
||||||
title="Forgot your password?"
|
title="Forgot your password?"
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'flex-end',
|
alignSelf: "flex-end",
|
||||||
height: 30,
|
height: 30,
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
SheetManager.show('forgotpassword_sheet', email.current);
|
SheetManager.show("forgotpassword_sheet", email.current);
|
||||||
}}
|
}}
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
fontSize={SIZE.xs}
|
fontSize={SIZE.xs}
|
||||||
type="gray"
|
type="gray"
|
||||||
@@ -244,7 +263,7 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
style={{
|
style={{
|
||||||
// position: 'absolute',
|
// position: 'absolute',
|
||||||
marginTop: 25,
|
marginTop: 25,
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -256,7 +275,7 @@ export const Login = ({ changeMode, welcome }) => {
|
|||||||
onPress={() => login()}
|
onPress={() => login()}
|
||||||
// width="100%"
|
// width="100%"
|
||||||
type="accent"
|
type="accent"
|
||||||
title={loading ? null : 'Login to your account'}
|
title={loading ? null : "Login to your account"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Modal, View } from 'react-native';
|
import { Modal, View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import BiometricService from '../../services/biometrics';
|
import BiometricService from "../../services/biometrics";
|
||||||
import {
|
import {
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent,
|
eUnSubscribeEvent,
|
||||||
presentSheet,
|
presentSheet,
|
||||||
ToastEvent
|
ToastEvent
|
||||||
} from '../../services/event-manager';
|
} from "../../services/event-manager";
|
||||||
import { clearMessage } from '../../services/message';
|
import { clearMessage } from "../../services/message";
|
||||||
import PremiumService from '../../services/premium';
|
import PremiumService from "../../services/premium";
|
||||||
import Sync from '../../services/sync';
|
import Sync from "../../services/sync";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { MMKV } from '../../common/database/mmkv';
|
import { MMKV } from "../../common/database/mmkv";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from '../../utils/time';
|
import { sleep } from "../../utils/time";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { Dialog } from '../dialog';
|
import { Dialog } from "../dialog";
|
||||||
import { presentDialog } from '../dialog/functions';
|
import { presentDialog } from "../dialog/functions";
|
||||||
import Input from '../ui/input';
|
import Input from "../ui/input";
|
||||||
import { Toast } from '../toast';
|
import { Toast } from "../toast";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import SettingsService from '../../services/settings';
|
import SettingsService from "../../services/settings";
|
||||||
import TwoFactorVerification from './two-factor';
|
import TwoFactorVerification from "./two-factor";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { Progress } from '../sheets/progress';
|
import { Progress } from "../sheets/progress";
|
||||||
|
|
||||||
function getEmail(email) {
|
function getEmail(email) {
|
||||||
if (!email) return null;
|
if (!email) return null;
|
||||||
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
|
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
|
||||||
for (let i = 0; i < gp3.length; i++) {
|
for (let i = 0; i < gp3.length; i++) {
|
||||||
gp2 += '*';
|
gp2 += "*";
|
||||||
}
|
}
|
||||||
return gp2;
|
return gp2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SessionExpired = () => {
|
export const SessionExpired = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const email = useRef();
|
const email = useRef();
|
||||||
const emailInputRef = useRef();
|
const emailInputRef = useRef();
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
@@ -49,7 +49,7 @@ export const SessionExpired = () => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
|
|
||||||
const setUser = useUserStore(state => state.setUser);
|
const setUser = useUserStore((state) => state.setUser);
|
||||||
|
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -65,27 +65,28 @@ export const SessionExpired = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: e.message,
|
heading: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent('session_expired', open);
|
eSubscribeEvent("session_expired", open);
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent('session_expired', open);
|
eUnSubscribeEvent("session_expired", open);
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
};
|
};
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('REQUESTING NEW TOKEN');
|
console.log("REQUESTING NEW TOKEN");
|
||||||
let res = await db.user.tokenManager.getToken();
|
let res = await db.user.tokenManager.getToken();
|
||||||
if (!res) throw new Error('no token found');
|
if (!res) throw new Error("no token found");
|
||||||
if (db.user.tokenManager._isTokenExpired(res)) throw new Error('token expired');
|
if (db.user.tokenManager._isTokenExpired(res))
|
||||||
Sync.run('global', false, true, async complete => {
|
throw new Error("token expired");
|
||||||
|
Sync.run("global", false, true, async (complete) => {
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
let user = await db.user.getUser();
|
let user = await db.user.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@@ -113,35 +114,39 @@ export const SessionExpired = () => {
|
|||||||
let user;
|
let user;
|
||||||
try {
|
try {
|
||||||
if (mfa) {
|
if (mfa) {
|
||||||
await db.user.mfaLogin(email.current.toLowerCase(), password.current, mfa);
|
await db.user.mfaLogin(
|
||||||
|
email.current.toLowerCase(),
|
||||||
|
password.current,
|
||||||
|
mfa
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await db.user.login(email.current.toLowerCase(), password.current);
|
await db.user.login(email.current.toLowerCase(), password.current);
|
||||||
}
|
}
|
||||||
callback && callback(true);
|
callback && callback(true);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
user = await db.user.getUser();
|
user = await db.user.getUser();
|
||||||
if (!user) throw new Error('Email or password incorrect!');
|
if (!user) throw new Error("Email or password incorrect!");
|
||||||
PremiumService.setPremiumStatus();
|
PremiumService.setPremiumStatus();
|
||||||
setUser(user);
|
setUser(user);
|
||||||
clearMessage();
|
clearMessage();
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Login successful',
|
heading: "Login successful",
|
||||||
message: `Logged in as ${user.email}`,
|
message: `Logged in as ${user.email}`,
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
await SettingsService.set({
|
await SettingsService.set({
|
||||||
sessionExpired: false,
|
sessionExpired: false,
|
||||||
userEmailConfirmed: user?.isEmailConfirmed
|
userEmailConfirmed: user?.isEmailConfirmed
|
||||||
});
|
});
|
||||||
eSendEvent('userLoggedIn', true);
|
eSendEvent("userLoggedIn", true);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
Progress.present();
|
Progress.present();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback && callback(false);
|
callback && callback(false);
|
||||||
if (e.message === 'Multifactor authentication required.') {
|
if (e.message === "Multifactor authentication required.") {
|
||||||
TwoFactorVerification.present(async mfa => {
|
TwoFactorVerification.present(async (mfa) => {
|
||||||
if (mfa) {
|
if (mfa) {
|
||||||
console.log(mfa);
|
console.log(mfa);
|
||||||
await login(mfa);
|
await login(mfa);
|
||||||
@@ -153,10 +158,10 @@ export const SessionExpired = () => {
|
|||||||
console.log(e.stack);
|
console.log(e.stack);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: user ? 'Failed to sync' : 'Login failed',
|
heading: user ? "Failed to sync" : "Login failed",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,17 +179,17 @@ export const SessionExpired = () => {
|
|||||||
<SheetProvider context="two_factor_verify" />
|
<SheetProvider context="two_factor_verify" />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: focused ? '100%' : '99.9%',
|
width: focused ? "100%" : "99.9%",
|
||||||
padding: 12,
|
padding: 12,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
flex: 1
|
flex: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
paddingVertical: 20
|
paddingVertical: 20
|
||||||
@@ -204,17 +209,17 @@ export const SessionExpired = () => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Your session on this device has expired. Please enter password for{' '}
|
Your session on this device has expired. Please enter password for{" "}
|
||||||
{getEmail(email.current)} to continue.
|
{getEmail(email.current)} to continue.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={passwordInputRef}
|
fwdRef={passwordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
password.current = value;
|
password.current = value;
|
||||||
}}
|
}}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
@@ -230,32 +235,32 @@ export const SessionExpired = () => {
|
|||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={() => login()}
|
onPress={() => login()}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={loading ? null : 'Login'}
|
title={loading ? null : "Login"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
context: 'session_expiry',
|
context: "session_expiry",
|
||||||
title: 'Logout',
|
title: "Logout",
|
||||||
paragraph:
|
paragraph:
|
||||||
'Are you sure you want to logout from this device? Any unsynced changes will be lost.',
|
"Are you sure you want to logout from this device? Any unsynced changes will be lost.",
|
||||||
positiveText: 'Logout',
|
positiveText: "Logout",
|
||||||
positiveType: 'errorShade',
|
positiveType: "errorShade",
|
||||||
positivePress: logout
|
positivePress: logout
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="errorShade"
|
type="errorShade"
|
||||||
title={loading ? null : 'Logout from this device'}
|
title={loading ? null : "Logout from this device"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Toast context="local" />
|
<Toast context="local" />
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from "react";
|
||||||
import { Dimensions, View } from 'react-native';
|
import { Dimensions, View } from "react-native";
|
||||||
import Animated, { FadeInDown, FadeOutDown, FadeOutUp } from 'react-native-reanimated';
|
import Animated, {
|
||||||
import { DDS } from '../../services/device-detection';
|
FadeInDown,
|
||||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
FadeOutDown,
|
||||||
import { clearMessage, setEmailVerifyMessage } from '../../services/message';
|
FadeOutUp
|
||||||
import Navigation from '../../services/navigation';
|
} from "react-native-reanimated";
|
||||||
import PremiumService from '../../services/premium';
|
import { DDS } from "../../services/device-detection";
|
||||||
import SettingsService from '../../services/settings';
|
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import Navigation from "../../services/navigation";
|
||||||
import umami from '../../common/analytics';
|
import PremiumService from "../../services/premium";
|
||||||
import { db } from '../../common/database';
|
import SettingsService from "../../services/settings";
|
||||||
import { eCloseLoginDialog } from '../../utils/events';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { openLinkInBrowser } from '../../utils/functions';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import umami from "../../common/analytics";
|
||||||
import { sleep } from '../../utils/time';
|
import { db } from "../../common/database";
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
import { eCloseLoginDialog } from "../../utils/events";
|
||||||
import { Button } from '../ui/button';
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
import Input from '../ui/input';
|
import { SIZE } from "../../utils/size";
|
||||||
import { SvgView } from '../ui/svg';
|
import { sleep } from "../../utils/time";
|
||||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import Heading from '../ui/typography/heading';
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Input from "../ui/input";
|
||||||
import { SVG } from './background';
|
import { SvgView } from "../ui/svg";
|
||||||
import { hideAuth } from './common';
|
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { SVG } from "./background";
|
||||||
|
import { hideAuth } from "./common";
|
||||||
|
|
||||||
export const Signup = ({ changeMode, welcome, trial }) => {
|
export const Signup = ({ changeMode, welcome, trial }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const email = useRef();
|
const email = useRef();
|
||||||
const emailInputRef = useRef();
|
const emailInputRef = useRef();
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
@@ -35,16 +39,16 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
const confirmPassword = useRef();
|
const confirmPassword = useRef();
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const setUser = useUserStore(state => state.setUser);
|
const setUser = useUserStore((state) => state.setUser);
|
||||||
const setLastSynced = useUserStore(state => state.setLastSynced);
|
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||||
|
|
||||||
const validateInfo = () => {
|
const validateInfo = () => {
|
||||||
if (!password.current || !email.current || !confirmPassword.current) {
|
if (!password.current || !email.current || !confirmPassword.current) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'All fields required',
|
heading: "All fields required",
|
||||||
message: 'Fill all the fields and try again',
|
message: "Fill all the fields and try again",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -64,7 +68,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
clearMessage();
|
clearMessage();
|
||||||
setEmailVerifyMessage();
|
setEmailVerifyMessage();
|
||||||
hideAuth();
|
hideAuth();
|
||||||
umami.pageView('/account-created', '/welcome/signup');
|
umami.pageView("/account-created", "/welcome/signup");
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
if (trial) {
|
if (trial) {
|
||||||
PremiumService.sheet(null, null, true);
|
PremiumService.sheet(null, null, true);
|
||||||
@@ -74,17 +78,19 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Signup failed',
|
heading: "Signup failed",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{loading ? <BaseDialog transparent={true} visible={true} animation="fade" /> : null}
|
{loading ? (
|
||||||
|
<BaseDialog transparent={true} visible={true} animation="fade" />
|
||||||
|
) : null}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
entering={FadeInDown}
|
entering={FadeInDown}
|
||||||
exiting={FadeOutUp}
|
exiting={FadeOutUp}
|
||||||
@@ -92,32 +98,35 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
borderRadius: DDS.isTab ? 5 : 0,
|
borderRadius: DDS.isTab ? 5 : 0,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
minHeight: '100%'
|
minHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 250,
|
height: 250,
|
||||||
overflow: 'hidden'
|
overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgView src={SVG(colors.night ? colors.icon : 'black')} height={700} />
|
<SvgView
|
||||||
|
src={SVG(colors.night ? colors.icon : "black")}
|
||||||
|
height={700}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
marginTop: Dimensions.get('window').height < 700 ? -75 : 15
|
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
size={30}
|
size={30}
|
||||||
color={colors.heading}
|
color={colors.heading}
|
||||||
@@ -126,8 +135,8 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: "underline",
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
changeMode(0);
|
changeMode(0);
|
||||||
@@ -139,20 +148,20 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: DDS.isTab ? '50%' : '100%',
|
width: DDS.isTab ? "50%" : "100%",
|
||||||
padding: 12,
|
padding: 12,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
fwdRef={emailInputRef}
|
fwdRef={emailInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
email.current = value;
|
email.current = value;
|
||||||
}}
|
}}
|
||||||
testID="input.email"
|
testID="input.email"
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
@@ -168,11 +177,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={passwordInputRef}
|
fwdRef={passwordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
password.current = value;
|
password.current = value;
|
||||||
}}
|
}}
|
||||||
testID="input.password"
|
testID="input.password"
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Next"
|
returnKeyLabel="Next"
|
||||||
returnKeyType="next"
|
returnKeyType="next"
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
@@ -188,11 +197,11 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
fwdRef={confirmPasswordInputRef}
|
fwdRef={confirmPasswordInputRef}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
confirmPassword.current = value;
|
confirmPassword.current = value;
|
||||||
}}
|
}}
|
||||||
testID="input.confirmPassword"
|
testID="input.confirmPassword"
|
||||||
onErrorCheck={e => setError(e)}
|
onErrorCheck={(e) => setError(e)}
|
||||||
returnKeyLabel="Signup"
|
returnKeyLabel="Signup"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
@@ -208,7 +217,7 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginTop: 25,
|
marginTop: 25,
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -219,42 +228,42 @@ export const Signup = ({ changeMode, welcome, trial }) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
onPress={signup}
|
onPress={signup}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={loading ? null : 'Agree and continue'}
|
title={loading ? null : "Agree and continue"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
marginBottom: 20
|
marginBottom: 20
|
||||||
}}
|
}}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
>
|
>
|
||||||
By signing up, you agree to our{' '}
|
By signing up, you agree to our{" "}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
openLinkInBrowser('https://notesnook.com/tos', colors);
|
openLinkInBrowser("https://notesnook.com/tos", colors);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
color={colors.accent}
|
color={colors.accent}
|
||||||
>
|
>
|
||||||
terms of service{' '}
|
terms of service{" "}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
and{' '}
|
and{" "}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
openLinkInBrowser('https://notesnook.com/privacy', colors);
|
openLinkInBrowser("https://notesnook.com/privacy", colors);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
color={colors.accent}
|
color={colors.accent}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { eCloseProgressDialog } from '../../utils/events';
|
import { eCloseProgressDialog } from "../../utils/events";
|
||||||
import useTimer from '../../hooks/use-timer';
|
import useTimer from "../../hooks/use-timer";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from '../ui/input';
|
import Input from "../ui/input";
|
||||||
import { PressableButton } from '../ui/pressable';
|
import { PressableButton } from "../ui/pressable";
|
||||||
import Seperator from '../ui/seperator';
|
import Seperator from "../ui/seperator";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { db } from '../../common/database/index';
|
import { db } from "../../common/database/index";
|
||||||
import { ToastEvent } from '../../services/event-manager';
|
import { ToastEvent } from "../../services/event-manager";
|
||||||
|
|
||||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const code = useRef();
|
const code = useRef();
|
||||||
const [currentMethod, setCurrentMethod] = useState({
|
const [currentMethod, setCurrentMethod] = useState({
|
||||||
method: mfaInfo?.primaryMethod,
|
method: mfaInfo?.primaryMethod,
|
||||||
@@ -28,10 +28,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
|
|
||||||
const codeHelpText = {
|
const codeHelpText = {
|
||||||
app: 'Enter the 6 digit code from your authenticator app to continue logging in',
|
app: "Enter the 6 digit code from your authenticator app to continue logging in",
|
||||||
sms: 'Enter the 6 digit code sent to your phone number to continue logging in',
|
sms: "Enter the 6 digit code sent to your phone number to continue logging in",
|
||||||
email: 'Enter the 6 digit code sent to your email to continue logging in',
|
email: "Enter the 6 digit code sent to your email to continue logging in",
|
||||||
recoveryCode: 'Enter the 8 digit recovery code to continue logging in'
|
recoveryCode: "Enter the 8 digit recovery code to continue logging in"
|
||||||
};
|
};
|
||||||
|
|
||||||
const secondaryMethodsText = {
|
const secondaryMethodsText = {
|
||||||
@@ -42,7 +42,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onNext = async () => {
|
const onNext = async () => {
|
||||||
const length = currentMethod.method === 'recoveryCode' ? 8 : 6;
|
const length = currentMethod.method === "recoveryCode" ? 8 : 6;
|
||||||
|
|
||||||
if (!code.current || code.current.length !== length) return;
|
if (!code.current || code.current.length !== length) return;
|
||||||
console.log(currentMethod.method, code.current);
|
console.log(currentMethod.method, code.current);
|
||||||
@@ -53,10 +53,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
method: currentMethod.method,
|
method: currentMethod.method,
|
||||||
code: code.current
|
code: code.current
|
||||||
},
|
},
|
||||||
result => {
|
(result) => {
|
||||||
console.log('result recieved');
|
console.log("result recieved");
|
||||||
if (result) {
|
if (result) {
|
||||||
eSendEvent(eCloseProgressDialog, 'two_factor_verify');
|
eSendEvent(eCloseProgressDialog, "two_factor_verify");
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -73,38 +73,38 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
{
|
{
|
||||||
id: 'sms',
|
id: "sms",
|
||||||
title: 'Send code via SMS',
|
title: "Send code via SMS",
|
||||||
icon: 'message-plus-outline'
|
icon: "message-plus-outline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'email',
|
id: "email",
|
||||||
title: 'Send code via email',
|
title: "Send code via email",
|
||||||
icon: 'email-outline'
|
icon: "email-outline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'app',
|
id: "app",
|
||||||
title: 'Enter code from authenticator app',
|
title: "Enter code from authenticator app",
|
||||||
icon: 'cellphone-key'
|
icon: "cellphone-key"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'recoveryCode',
|
id: "recoveryCode",
|
||||||
title: 'I have a recovery code',
|
title: "I have a recovery code",
|
||||||
icon: 'key'
|
icon: "key"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const getMethods = () => {
|
const getMethods = () => {
|
||||||
return methods.filter(
|
return methods.filter(
|
||||||
m =>
|
(m) =>
|
||||||
m.id === mfaInfo?.primaryMethod ||
|
m.id === mfaInfo?.primaryMethod ||
|
||||||
m.id === mfaInfo?.secondaryMethod ||
|
m.id === mfaInfo?.secondaryMethod ||
|
||||||
m.id === 'recoveryCode'
|
m.id === "recoveryCode"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentMethod.method === 'sms' || currentMethod.method === 'email') {
|
if (currentMethod.method === "sms" || currentMethod.method === "email") {
|
||||||
onSendCode();
|
onSendCode();
|
||||||
}
|
}
|
||||||
}, [currentMethod.method]);
|
}, [currentMethod.method]);
|
||||||
@@ -114,13 +114,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
// TODO
|
// TODO
|
||||||
setSending(true);
|
setSending(true);
|
||||||
try {
|
try {
|
||||||
console.log('sending code', currentMethod.method, mfaInfo.token);
|
console.log("sending code", currentMethod.method, mfaInfo.token);
|
||||||
await db.mfa.sendCode(currentMethod.method, mfaInfo.token);
|
await db.mfa.sendCode(currentMethod.method, mfaInfo.token);
|
||||||
start(60);
|
start(60);
|
||||||
setSending(false);
|
setSending(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setSending(false);
|
setSending(false);
|
||||||
ToastEvent.error(e, 'Error sending 2FA Code', 'local');
|
ToastEvent.error(e, "Error sending 2FA Code", "local");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: currentMethod.method ? 12 : 0
|
paddingHorizontal: currentMethod.method ? 12 : 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -143,29 +143,34 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
/>
|
/>
|
||||||
<Heading
|
<Heading
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentMethod.method
|
{currentMethod.method
|
||||||
? 'Two factor authentication'
|
? "Two factor authentication"
|
||||||
: 'Select methods for two-factor authentication'}
|
: "Select methods for two-factor authentication"}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
width: '80%',
|
width: "80%",
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{codeHelpText[currentMethod.method] || `Select how you would like to recieve the code`}
|
{codeHelpText[currentMethod.method] ||
|
||||||
|
`Select how you would like to recieve the code`}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Seperator />
|
<Seperator />
|
||||||
|
|
||||||
{currentMethod.method === 'sms' || currentMethod.method === 'email' ? (
|
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
|
||||||
<Button
|
<Button
|
||||||
onPress={onSendCode}
|
onPress={onSendCode}
|
||||||
type={seconds ? 'gray' : 'transparent'}
|
type={seconds ? "gray" : "transparent"}
|
||||||
title={sending ? '' : `${seconds ? `Resend code in (${seconds})` : 'Send code'}`}
|
title={
|
||||||
|
sending
|
||||||
|
? ""
|
||||||
|
: `${seconds ? `Resend code in (${seconds})` : "Send code"}`
|
||||||
|
}
|
||||||
loading={sending}
|
loading={sending}
|
||||||
height={30}
|
height={30}
|
||||||
/>
|
/>
|
||||||
@@ -176,11 +181,13 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
{currentMethod.method ? (
|
{currentMethod.method ? (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder={currentMethod.method === 'recoveryCode' ? 'xxxxxxxx' : 'xxxxxx'}
|
placeholder={
|
||||||
maxLength={currentMethod.method === 'recoveryCode' ? 8 : 6}
|
currentMethod.method === "recoveryCode" ? "xxxxxxxx" : "xxxxxx"
|
||||||
|
}
|
||||||
|
maxLength={currentMethod.method === "recoveryCode" ? 8 : 6}
|
||||||
fwdRef={inputRef}
|
fwdRef={inputRef}
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
code.current = value;
|
code.current = value;
|
||||||
onNext();
|
onNext();
|
||||||
}}
|
}}
|
||||||
@@ -188,22 +195,24 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
inputStyle={{
|
inputStyle={{
|
||||||
fontSize: SIZE.lg,
|
fontSize: SIZE.lg,
|
||||||
height: 60,
|
height: 60,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
letterSpacing: 10,
|
letterSpacing: 10,
|
||||||
width: null
|
width: null
|
||||||
}}
|
}}
|
||||||
keyboardType={currentMethod.method === 'recoveryCode' ? 'default' : 'numeric'}
|
keyboardType={
|
||||||
|
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
||||||
|
}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
height: 60,
|
height: 60,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
width: null,
|
width: null,
|
||||||
minWidth: '50%'
|
minWidth: "50%"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Seperator />
|
<Seperator />
|
||||||
<Button
|
<Button
|
||||||
title={loading ? null : 'Next'}
|
title={loading ? null : "Next"}
|
||||||
type="accent"
|
type="accent"
|
||||||
width={250}
|
width={250}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -223,7 +232,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{getMethods().map(item => (
|
{getMethods().map((item) => (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
key={item.title}
|
key={item.title}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -236,11 +245,11 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'flex-start'
|
justifyContent: "flex-start"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -271,12 +280,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TwoFactorVerification.present = (onMfaLogin, data, context) => {
|
TwoFactorVerification.present = (onMfaLogin, data, context) => {
|
||||||
console.log('presenting sheet');
|
console.log("presenting sheet");
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
|
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
|
||||||
context: context || 'two_factor_verify',
|
context: context || "two_factor_verify",
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
console.log('on close called');
|
console.log("on close called");
|
||||||
onMfaLogin();
|
onMfaLogin();
|
||||||
},
|
},
|
||||||
disableClosing: true
|
disableClosing: true
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
|
|
||||||
export const ContainerHeader = ({ children }) => {
|
export const ContainerHeader = ({ children }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
|
|
||||||
return !selectionMode ? (
|
return !selectionMode ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
overflow: 'hidden'
|
overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { Keyboard, Platform, View } from 'react-native';
|
import { Keyboard, Platform, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
withTiming
|
withTiming
|
||||||
} from 'react-native-reanimated';
|
} from "react-native-reanimated";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from '../../utils';
|
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from "../../utils";
|
||||||
import { normalize, SIZE } from '../../utils/size';
|
import { normalize, SIZE } from "../../utils/size";
|
||||||
import { PressableButton } from '../ui/pressable';
|
import { PressableButton } from "../ui/pressable";
|
||||||
|
|
||||||
export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow = false }) => {
|
export const FloatingButton = ({
|
||||||
|
title,
|
||||||
|
onPress,
|
||||||
|
color = "accent",
|
||||||
|
shouldShow = false
|
||||||
|
}) => {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const translate = useSharedValue(0);
|
const translate = useSharedValue(0);
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => {
|
||||||
@@ -48,19 +53,19 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
|||||||
|
|
||||||
const onKeyboardHide = async () => {
|
const onKeyboardHide = async () => {
|
||||||
editorState().keyboardState = false;
|
editorState().keyboardState = false;
|
||||||
if (deviceMode !== 'mobile') return;
|
if (deviceMode !== "mobile") return;
|
||||||
animate(0);
|
animate(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyboardShow = async () => {
|
const onKeyboardShow = async () => {
|
||||||
editorState().keyboardState = true;
|
editorState().keyboardState = true;
|
||||||
if (deviceMode !== 'mobile') return;
|
if (deviceMode !== "mobile") return;
|
||||||
animate(150);
|
animate(150);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let sub1 = Keyboard.addListener('keyboardDidShow', onKeyboardShow);
|
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
|
||||||
let sub2 = Keyboard.addListener('keyboardDidHide', onKeyboardHide);
|
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
|
||||||
return () => {
|
return () => {
|
||||||
sub1?.remove();
|
sub1?.remove();
|
||||||
sub2?.remove();
|
sub2?.remove();
|
||||||
@@ -72,13 +77,13 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
|||||||
iPad: 20
|
iPad: 20
|
||||||
};
|
};
|
||||||
|
|
||||||
return deviceMode !== 'mobile' && !shouldShow ? null : (
|
return deviceMode !== "mobile" && !shouldShow ? null : (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
right: 12,
|
right: 12,
|
||||||
bottom: paddings[Platform.isPad ? 'iPad' : Platform.OS],
|
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
|
||||||
zIndex: 10
|
zIndex: 10
|
||||||
},
|
},
|
||||||
animatedStyle
|
animatedStyle
|
||||||
@@ -87,27 +92,27 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
|||||||
<PressableButton
|
<PressableButton
|
||||||
testID={notesnook.buttons.add}
|
testID={notesnook.buttons.add}
|
||||||
type="accent"
|
type="accent"
|
||||||
accentColor={color || 'accent'}
|
accentColor={color || "accent"}
|
||||||
accentText="light"
|
accentText="light"
|
||||||
customStyle={{
|
customStyle={{
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
onLongPress={event => {
|
onLongPress={(event) => {
|
||||||
showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
|
showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
|
||||||
}}
|
}}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
height: normalize(60),
|
height: normalize(60),
|
||||||
width: normalize(60)
|
width: normalize(60)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={title === 'Clear all trash' ? 'delete' : 'plus'}
|
name={title === "Clear all trash" ? "delete" : "plus"}
|
||||||
color="white"
|
color="white"
|
||||||
size={SIZE.xxl}
|
size={SIZE.xxl}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
|
import { KeyboardAvoidingView, Platform, SafeAreaView } from "react-native";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
|
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||||
import { Header } from '../header';
|
import { Header } from "../header";
|
||||||
import SelectionHeader from '../selection-header';
|
import SelectionHeader from "../selection-header";
|
||||||
export const Container = ({ children }) => {
|
export const Container = ({ children }) => {
|
||||||
const floating = useIsFloatingKeyboard();
|
const floating = useIsFloatingKeyboard();
|
||||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
const introCompleted = useSettingStore(
|
||||||
|
(state) => state.settings.introCompleted
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
behavior="padding"
|
behavior="padding"
|
||||||
enabled={Platform.OS === 'ios' && !floating}
|
enabled={Platform.OS === "ios" && !floating}
|
||||||
style={{
|
style={{
|
||||||
flex: 1
|
flex: 1
|
||||||
}}
|
}}
|
||||||
@@ -18,7 +20,7 @@ export const Container = ({ children }) => {
|
|||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflow: 'hidden'
|
overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!introCompleted ? null : (
|
{!introCompleted ? null : (
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { COLORS_NOTE } from '../../utils/color-scheme';
|
import { COLORS_NOTE } from "../../utils/color-scheme";
|
||||||
import { hexToRGBA } from '../../utils/color-scheme/utils';
|
import { hexToRGBA } from "../../utils/color-scheme/utils";
|
||||||
|
|
||||||
export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const message = useMessageStore(state => state.message);
|
const message = useMessageStore((state) => state.message);
|
||||||
const annoucements = useMessageStore(state => state.announcements);
|
const annoucements = useMessageStore((state) => state.announcements);
|
||||||
const hasAnnoucements = annoucements.length > 0;
|
const hasAnnoucements = annoucements.length > 0;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const shadeColor = color ? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15) : colors.shade;
|
const shadeColor = color
|
||||||
|
? hexToRGBA(COLORS_NOTE[color?.toLowerCase()], 0.15)
|
||||||
|
: colors.shade;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hasAnnoucements ? (
|
{hasAnnoucements ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 100,
|
height: 100,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
@@ -64,12 +66,12 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
|||||||
{message ? (
|
{message ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 60,
|
height: 60,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 20
|
paddingHorizontal: 20
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -106,15 +108,15 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 30,
|
height: 30,
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -128,7 +130,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -161,7 +163,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
|||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '85%',
|
width: "85%",
|
||||||
height: 13,
|
height: 13,
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
@@ -171,7 +173,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { ViewProps } from 'react-native';
|
import { ViewProps } from "react-native";
|
||||||
import Animated, { FadeOutUp } from 'react-native-reanimated';
|
import Animated, { FadeOutUp } from "react-native-reanimated";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useDelayLayout } from '../../hooks/use-delay-layout';
|
import { useDelayLayout } from "../../hooks/use-delay-layout";
|
||||||
import { DefaultPlaceholder } from './default-placeholder';
|
import { DefaultPlaceholder } from "./default-placeholder";
|
||||||
import { SettingsPlaceholder } from './settings-placeholder';
|
import { SettingsPlaceholder } from "./settings-placeholder";
|
||||||
|
|
||||||
interface IDelayLayoutProps extends ViewProps {
|
interface IDelayLayoutProps extends ViewProps {
|
||||||
delay?: number;
|
delay?: number;
|
||||||
wait?: boolean;
|
wait?: boolean;
|
||||||
type?: 'default' | 'settings';
|
type?: "default" | "settings";
|
||||||
color?: string;
|
color?: string;
|
||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
}
|
}
|
||||||
@@ -19,10 +19,15 @@ const placeholder = {
|
|||||||
settings: SettingsPlaceholder
|
settings: SettingsPlaceholder
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DelayLayout({ animated = true, ...props }: IDelayLayoutProps) {
|
export default function DelayLayout({
|
||||||
const colors = useThemeStore(state => state.colors);
|
animated = true,
|
||||||
const loading = useDelayLayout(!props.delay || props.delay < 300 ? 300 : props.delay);
|
...props
|
||||||
const Placeholder = placeholder[props.type || 'default'];
|
}: IDelayLayoutProps) {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
const loading = useDelayLayout(
|
||||||
|
!props.delay || props.delay < 300 ? 300 : props.delay
|
||||||
|
);
|
||||||
|
const Placeholder = placeholder[props.type || "default"];
|
||||||
|
|
||||||
return loading || props.wait ? (
|
return loading || props.wait ? (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
|
|
||||||
export const SettingsPlaceholder = () => {
|
export const SettingsPlaceholder = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
@@ -19,12 +19,12 @@ export const SettingsPlaceholder = () => {
|
|||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 60,
|
height: 60,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 16
|
paddingHorizontal: 16
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -60,14 +60,14 @@ export const SettingsPlaceholder = () => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 60,
|
height: 60,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -106,8 +106,8 @@ export const SettingsPlaceholder = () => {
|
|||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
marginLeft: 15,
|
marginLeft: 15,
|
||||||
alignItems: 'flex-end',
|
alignItems: "flex-end",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
paddingHorizontal: 4
|
paddingHorizontal: 4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useNoteStore } from '../../stores/use-notes-store';
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { AnnouncementDialog } from '../announcements';
|
import { AnnouncementDialog } from "../announcements";
|
||||||
import { AttachmentDialog } from '../attachments';
|
import { AttachmentDialog } from "../attachments";
|
||||||
import Auth from '../auth';
|
import Auth from "../auth";
|
||||||
import AuthModal from '../auth/auth-modal';
|
import AuthModal from "../auth/auth-modal";
|
||||||
import { SessionExpired } from '../auth/session-expired';
|
import { SessionExpired } from "../auth/session-expired";
|
||||||
import { Dialog } from '../dialog';
|
import { Dialog } from "../dialog";
|
||||||
import { AddTopicDialog } from '../dialogs/add-topic';
|
import { AddTopicDialog } from "../dialogs/add-topic";
|
||||||
import ResultDialog from '../dialogs/result';
|
import ResultDialog from "../dialogs/result";
|
||||||
import { VaultDialog } from '../dialogs/vault';
|
import { VaultDialog } from "../dialogs/vault";
|
||||||
import ImagePreview from '../image-preview';
|
import ImagePreview from "../image-preview";
|
||||||
import MergeConflicts from '../merge-conflicts';
|
import MergeConflicts from "../merge-conflicts";
|
||||||
import PremiumDialog from '../premium';
|
import PremiumDialog from "../premium";
|
||||||
import { Expiring } from '../premium/expiring';
|
import { Expiring } from "../premium/expiring";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { AddNotebookSheet } from '../sheets/add-notebook';
|
import { AddNotebookSheet } from "../sheets/add-notebook";
|
||||||
import AddToNotebookSheet from '../sheets/add-to';
|
import AddToNotebookSheet from "../sheets/add-to";
|
||||||
import ExportNotesSheet from '../sheets/export-notes';
|
import ExportNotesSheet from "../sheets/export-notes";
|
||||||
import ManageTagsSheet from '../sheets/manage-tags';
|
import ManageTagsSheet from "../sheets/manage-tags";
|
||||||
import PublishNoteSheet from '../sheets/publish-note';
|
import PublishNoteSheet from "../sheets/publish-note";
|
||||||
import RateAppSheet from '../sheets/rate-app';
|
import RateAppSheet from "../sheets/rate-app";
|
||||||
import RecoveryKeySheet from '../sheets/recovery-key';
|
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||||
import RestoreDataSheet from '../sheets/restore-data';
|
import RestoreDataSheet from "../sheets/restore-data";
|
||||||
|
|
||||||
const DialogProvider = React.memo(
|
const DialogProvider = React.memo(
|
||||||
() => {
|
() => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const loading = useNoteStore(state => state.loading);
|
const loading = useNoteStore((state) => state.loading);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { eSendEvent } from '../../services/event-manager';
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import {
|
import {
|
||||||
eCloseActionSheet,
|
eCloseActionSheet,
|
||||||
eCloseAddNotebookDialog,
|
eCloseAddNotebookDialog,
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
eOpenAddNotebookDialog,
|
eOpenAddNotebookDialog,
|
||||||
eOpenAddTopicDialog,
|
eOpenAddTopicDialog,
|
||||||
eOpenMoveNoteDialog
|
eOpenMoveNoteDialog
|
||||||
} from '../../utils/events';
|
} from "../../utils/events";
|
||||||
|
|
||||||
export const ActionSheetEvent = (item, buttons) => {
|
export const ActionSheetEvent = (item, buttons) => {
|
||||||
eSendEvent(eOpenActionSheet, {
|
eSendEvent(eOpenActionSheet, {
|
||||||
@@ -27,15 +27,15 @@ export const moveNoteHideEvent = () => {
|
|||||||
eSendEvent(eCloseMoveNoteDialog);
|
eSendEvent(eCloseMoveNoteDialog);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddNotebookEvent = notebook => {
|
export const AddNotebookEvent = (notebook) => {
|
||||||
eSendEvent(eOpenAddNotebookDialog, notebook);
|
eSendEvent(eOpenAddNotebookDialog, notebook);
|
||||||
};
|
};
|
||||||
export const HideAddNotebookEvent = notebook => {
|
export const HideAddNotebookEvent = (notebook) => {
|
||||||
eSendEvent(eCloseAddNotebookDialog, notebook);
|
eSendEvent(eCloseAddNotebookDialog, notebook);
|
||||||
};
|
};
|
||||||
export const AddTopicEvent = topic => {
|
export const AddTopicEvent = (topic) => {
|
||||||
eSendEvent(eOpenAddTopicDialog, topic);
|
eSendEvent(eOpenAddTopicDialog, topic);
|
||||||
};
|
};
|
||||||
export const HideAddTopicEvent = notebook => {
|
export const HideAddTopicEvent = (notebook) => {
|
||||||
eSendEvent(eCloseAddTopicDialog, notebook);
|
eSendEvent(eCloseAddTopicDialog, notebook);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -7,17 +7,17 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from "react-native";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import useIsFloatingKeyboard from '../../hooks/use-is-floating-keyboard';
|
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||||
import { BouncingView } from '../ui/transitions/bouncing-view';
|
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||||
|
|
||||||
const BaseDialog = ({
|
const BaseDialog = ({
|
||||||
visible,
|
visible,
|
||||||
onRequestClose,
|
onRequestClose,
|
||||||
children,
|
children,
|
||||||
onShow,
|
onShow,
|
||||||
animation = 'fade',
|
animation = "fade",
|
||||||
premium,
|
premium,
|
||||||
statusBarTranslucent = true,
|
statusBarTranslucent = true,
|
||||||
transparent,
|
transparent,
|
||||||
@@ -46,11 +46,11 @@ const BaseDialog = ({
|
|||||||
animated
|
animated
|
||||||
statusBarTranslucent={statusBarTranslucent}
|
statusBarTranslucent={statusBarTranslucent}
|
||||||
supportedOrientations={[
|
supportedOrientations={[
|
||||||
'portrait',
|
"portrait",
|
||||||
'portrait-upside-down',
|
"portrait-upside-down",
|
||||||
'landscape',
|
"landscape",
|
||||||
'landscape-left',
|
"landscape-left",
|
||||||
'landscape-right'
|
"landscape-right"
|
||||||
]}
|
]}
|
||||||
onShow={() => {
|
onShow={() => {
|
||||||
if (onShow) {
|
if (onShow) {
|
||||||
@@ -67,10 +67,17 @@ const BaseDialog = ({
|
|||||||
>
|
>
|
||||||
<Wrapper
|
<Wrapper
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: background ? background : transparent ? 'transparent' : 'rgba(0,0,0,0.3)'
|
backgroundColor: background
|
||||||
|
? background
|
||||||
|
: transparent
|
||||||
|
? "transparent"
|
||||||
|
: "rgba(0,0,0,0.3)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<KeyboardAvoidingView enabled={!floating && Platform.OS === 'ios'} behavior="padding">
|
<KeyboardAvoidingView
|
||||||
|
enabled={!floating && Platform.OS === "ios"}
|
||||||
|
behavior="padding"
|
||||||
|
>
|
||||||
<BouncingView
|
<BouncingView
|
||||||
duration={400}
|
duration={400}
|
||||||
animated={animated}
|
animated={animated}
|
||||||
@@ -78,7 +85,11 @@ const BaseDialog = ({
|
|||||||
style={[
|
style={[
|
||||||
styles.backdrop,
|
styles.backdrop,
|
||||||
{
|
{
|
||||||
justifyContent: centered ? 'center' : bottom ? 'flex-end' : 'flex-start'
|
justifyContent: centered
|
||||||
|
? "center"
|
||||||
|
: bottom
|
||||||
|
? "flex-end"
|
||||||
|
: "flex-start"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -97,15 +108,15 @@ const BaseDialog = ({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
backdrop: {
|
backdrop: {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
},
|
},
|
||||||
overlayButton: {
|
overlayButton: {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
position: 'absolute'
|
position: "absolute"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
|
|
||||||
const DialogButtons = ({
|
const DialogButtons = ({
|
||||||
onPressPositive,
|
onPressPositive,
|
||||||
onPressNegative,
|
onPressNegative,
|
||||||
positiveTitle,
|
positiveTitle,
|
||||||
negativeTitle = 'Cancel',
|
negativeTitle = "Cancel",
|
||||||
loading,
|
loading,
|
||||||
doneText,
|
doneText,
|
||||||
positiveType
|
positiveType
|
||||||
}) => {
|
}) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -36,12 +36,16 @@ const DialogButtons = ({
|
|||||||
) : doneText ? (
|
) : doneText ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon color={colors.accent} name="check-circle-outline" size={SIZE.md} />
|
<Icon
|
||||||
<Paragraph color={colors.accent}>{' ' + doneText}</Paragraph>
|
color={colors.accent}
|
||||||
|
name="check-circle-outline"
|
||||||
|
size={SIZE.md}
|
||||||
|
/>
|
||||||
|
<Paragraph color={colors.accent}>{" " + doneText}</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View />
|
<View />
|
||||||
@@ -49,8 +53,8 @@ const DialogButtons = ({
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -70,7 +74,7 @@ const DialogButtons = ({
|
|||||||
marginLeft: 10
|
marginLeft: 10
|
||||||
}}
|
}}
|
||||||
bold
|
bold
|
||||||
type={positiveType || 'transparent'}
|
type={positiveType || "transparent"}
|
||||||
title={positiveTitle}
|
title={positiveTitle}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -83,9 +87,9 @@ export default DialogButtons;
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { getElevation } from '../../utils';
|
import { getElevation } from "../../utils";
|
||||||
|
|
||||||
const DialogContainer = ({ width, height, ...restProps }) => {
|
const DialogContainer = ({ width, height, ...restProps }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
{...restProps}
|
{...restProps}
|
||||||
style={{
|
style={{
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
width: width || DDS.isTab ? 500 : '85%',
|
width: width || DDS.isTab ? 500 : "85%",
|
||||||
maxHeight: height || 450,
|
maxHeight: height || 450,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Text } from 'react-native';
|
import { Text } from "react-native";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { PressableButtonProps } from '../ui/pressable';
|
import { PressableButtonProps } from "../ui/pressable";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
type DialogHeaderProps = {
|
type DialogHeaderProps = {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@@ -16,7 +16,7 @@ type DialogHeaderProps = {
|
|||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
type?: PressableButtonProps['type'];
|
type?: PressableButtonProps["type"];
|
||||||
};
|
};
|
||||||
paragraphColor?: string;
|
paragraphColor?: string;
|
||||||
padding?: number;
|
padding?: number;
|
||||||
@@ -34,33 +34,39 @@ const DialogHeader = ({
|
|||||||
centered,
|
centered,
|
||||||
titlePart
|
titlePart
|
||||||
}: DialogHeaderProps) => {
|
}: DialogHeaderProps) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
minHeight: 50,
|
minHeight: 50,
|
||||||
paddingHorizontal: padding
|
paddingHorizontal: padding
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: centered ? 'center' : 'space-between',
|
justifyContent: centered ? "center" : "space-between",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading style={{ textAlign: centered ? 'center' : 'left' }} size={SIZE.lg}>
|
<Heading
|
||||||
{title} {titlePart ? <Text style={{ color: colors.accent }}>{titlePart}</Text> : null}
|
style={{ textAlign: centered ? "center" : "left" }}
|
||||||
|
size={SIZE.lg}
|
||||||
|
>
|
||||||
|
{title}{" "}
|
||||||
|
{titlePart ? (
|
||||||
|
<Text style={{ color: colors.accent }}>{titlePart}</Text>
|
||||||
|
) : null}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
{button ? (
|
{button ? (
|
||||||
@@ -73,7 +79,7 @@ const DialogHeader = ({
|
|||||||
loading={button.loading}
|
loading={button.loading}
|
||||||
fontSize={13}
|
fontSize={13}
|
||||||
title={button.title}
|
title={button.title}
|
||||||
type={button.type || 'grayBg'}
|
type={button.type || "grayBg"}
|
||||||
height={25}
|
height={25}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -82,9 +88,9 @@ const DialogHeader = ({
|
|||||||
{paragraph ? (
|
{paragraph ? (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: centered ? 'center' : 'left',
|
textAlign: centered ? "center" : "left",
|
||||||
maxWidth: centered ? '90%' : '100%',
|
maxWidth: centered ? "90%" : "100%",
|
||||||
alignSelf: centered ? 'center' : 'flex-start'
|
alignSelf: centered ? "center" : "flex-start"
|
||||||
}}
|
}}
|
||||||
color={paragraphColor || colors.icon}
|
color={paragraphColor || colors.icon}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { eSendEvent } from '../../services/event-manager';
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
|
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
|
||||||
|
|
||||||
type DialogInfo = {
|
type DialogInfo = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -9,20 +9,20 @@ type DialogInfo = {
|
|||||||
positivePress?: (value: any) => void;
|
positivePress?: (value: any) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
positiveType?:
|
positiveType?:
|
||||||
| 'transparent'
|
| "transparent"
|
||||||
| 'gray'
|
| "gray"
|
||||||
| 'grayBg'
|
| "grayBg"
|
||||||
| 'accent'
|
| "accent"
|
||||||
| 'inverted'
|
| "inverted"
|
||||||
| 'shade'
|
| "shade"
|
||||||
| 'error'
|
| "error"
|
||||||
| 'errorShade';
|
| "errorShade";
|
||||||
icon?: string;
|
icon?: string;
|
||||||
paragraphColor: string;
|
paragraphColor: string;
|
||||||
input: boolean;
|
input: boolean;
|
||||||
inputPlaceholder: string;
|
inputPlaceholder: string;
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
context: 'global' | 'local';
|
context: "global" | "local";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function presentDialog(data: Partial<DialogInfo>): void {
|
export function presentDialog(data: Partial<DialogInfo>): void {
|
||||||
|
|||||||
@@ -1,36 +1,39 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { getElevation } from '../../utils';
|
eSubscribeEvent,
|
||||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/events';
|
eUnSubscribeEvent
|
||||||
import { sleep } from '../../utils/time';
|
} from "../../services/event-manager";
|
||||||
import Input from '../ui/input';
|
import { getElevation } from "../../utils";
|
||||||
import Seperator from '../ui/seperator';
|
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
|
||||||
import { Toast } from '../toast';
|
import { sleep } from "../../utils/time";
|
||||||
import BaseDialog from './base-dialog';
|
import Input from "../ui/input";
|
||||||
import DialogButtons from './dialog-buttons';
|
import Seperator from "../ui/seperator";
|
||||||
import DialogHeader from './dialog-header';
|
import { Toast } from "../toast";
|
||||||
|
import BaseDialog from "./base-dialog";
|
||||||
|
import DialogButtons from "./dialog-buttons";
|
||||||
|
import DialogHeader from "./dialog-header";
|
||||||
|
|
||||||
export const Dialog = ({ context = 'global' }) => {
|
export const Dialog = ({ context = "global" }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState(null);
|
const [inputValue, setInputValue] = useState(null);
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const [dialogInfo, setDialogInfo] = useState({
|
const [dialogInfo, setDialogInfo] = useState({
|
||||||
title: '',
|
title: "",
|
||||||
paragraph: '',
|
paragraph: "",
|
||||||
positiveText: 'Done',
|
positiveText: "Done",
|
||||||
negativeText: 'Cancel',
|
negativeText: "Cancel",
|
||||||
positivePress: () => {},
|
positivePress: () => {},
|
||||||
onClose: () => {},
|
onClose: () => {},
|
||||||
positiveType: 'transparent',
|
positiveType: "transparent",
|
||||||
icon: null,
|
icon: null,
|
||||||
paragraphColor: colors.pri,
|
paragraphColor: colors.pri,
|
||||||
input: false,
|
input: false,
|
||||||
inputPlaceholder: 'Enter some text',
|
inputPlaceholder: "Enter some text",
|
||||||
defaultValue: '',
|
defaultValue: "",
|
||||||
disableBackdropClosing: false
|
disableBackdropClosing: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,7 +50,9 @@ export const Dialog = ({ context = 'global' }) => {
|
|||||||
const onPressPositive = async () => {
|
const onPressPositive = async () => {
|
||||||
if (dialogInfo.positivePress) {
|
if (dialogInfo.positivePress) {
|
||||||
inputRef.current?.blur();
|
inputRef.current?.blur();
|
||||||
let result = await dialogInfo.positivePress(inputValue || dialogInfo.defaultValue);
|
let result = await dialogInfo.positivePress(
|
||||||
|
inputValue || dialogInfo.defaultValue
|
||||||
|
);
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -56,8 +61,8 @@ export const Dialog = ({ context = 'global' }) => {
|
|||||||
hide();
|
hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = data => {
|
const show = (data) => {
|
||||||
if (!data.context) data.context = 'global';
|
if (!data.context) data.context = "global";
|
||||||
if (data.context !== context) return;
|
if (data.context !== context) return;
|
||||||
setDialogInfo(data);
|
setDialogInfo(data);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
@@ -78,7 +83,7 @@ export const Dialog = ({ context = 'global' }) => {
|
|||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
width: DDS.isTab ? 400 : '85%',
|
width: DDS.isTab ? 400 : "85%",
|
||||||
maxHeight: 450,
|
maxHeight: 450,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
@@ -121,7 +126,7 @@ export const Dialog = ({ context = 'global' }) => {
|
|||||||
<Input
|
<Input
|
||||||
fwdRef={inputRef}
|
fwdRef={inputRef}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
}}
|
}}
|
||||||
testID="input-value"
|
testID="input-value"
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
import React, { createRef } from 'react';
|
import React, { createRef } from "react";
|
||||||
import { Keyboard, LayoutAnimation, UIManager, View } from 'react-native';
|
import { Keyboard, LayoutAnimation, UIManager, View } from "react-native";
|
||||||
import { Transition, Transitioning, TransitioningView } from 'react-native-reanimated';
|
import {
|
||||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
Transition,
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent, ToastEvent } from '../../../services/event-manager';
|
Transitioning,
|
||||||
import Navigation from '../../../services/navigation';
|
TransitioningView
|
||||||
import { db } from '../../../common/database';
|
} from "react-native-reanimated";
|
||||||
import { eCloseAddTopicDialog, eOpenAddTopicDialog } from '../../../utils/events';
|
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||||
import { sleep } from '../../../utils/time';
|
import {
|
||||||
import BaseDialog from '../../dialog/base-dialog';
|
eSubscribeEvent,
|
||||||
import DialogButtons from '../../dialog/dialog-buttons';
|
eUnSubscribeEvent,
|
||||||
import DialogContainer from '../../dialog/dialog-container';
|
ToastEvent
|
||||||
import DialogHeader from '../../dialog/dialog-header';
|
} from "../../../services/event-manager";
|
||||||
import Input from '../../ui/input';
|
import Navigation from "../../../services/navigation";
|
||||||
import Seperator from '../../ui/seperator';
|
import { db } from "../../../common/database";
|
||||||
import { Toast } from '../../toast';
|
import {
|
||||||
|
eCloseAddTopicDialog,
|
||||||
|
eOpenAddTopicDialog
|
||||||
|
} from "../../../utils/events";
|
||||||
|
import { sleep } from "../../../utils/time";
|
||||||
|
import BaseDialog from "../../dialog/base-dialog";
|
||||||
|
import DialogButtons from "../../dialog/dialog-buttons";
|
||||||
|
import DialogContainer from "../../dialog/dialog-container";
|
||||||
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
|
import Input from "../../ui/input";
|
||||||
|
import Seperator from "../../ui/seperator";
|
||||||
|
import { Toast } from "../../toast";
|
||||||
|
|
||||||
export class AddTopicDialog extends React.Component {
|
export class AddTopicDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -34,11 +45,11 @@ export class AddTopicDialog extends React.Component {
|
|||||||
addNewTopic = async () => {
|
addNewTopic = async () => {
|
||||||
try {
|
try {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
if (!this.title || this.title?.trim() === '') {
|
if (!this.title || this.title?.trim() === "") {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Topic title is required',
|
heading: "Topic title is required",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
return;
|
return;
|
||||||
@@ -54,7 +65,7 @@ export class AddTopicDialog extends React.Component {
|
|||||||
}
|
}
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
this.close();
|
this.close();
|
||||||
Navigation.queueRoutesForUpdate('Notebooks', 'Notebook', 'TopicNotes');
|
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
|
||||||
useMenuStore.getState().setMenuPins();
|
useMenuStore.getState().setMenuPins();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
@@ -116,9 +127,11 @@ export class AddTopicDialog extends React.Component {
|
|||||||
<DialogContainer>
|
<DialogContainer>
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
icon="book-outline"
|
icon="book-outline"
|
||||||
title={this.toEdit ? 'Edit topic' : 'New topic'}
|
title={this.toEdit ? "Edit topic" : "New topic"}
|
||||||
paragraph={
|
paragraph={
|
||||||
this.toEdit ? 'Edit title of the topic' : 'Add a new topic in ' + this.notebook.title
|
this.toEdit
|
||||||
|
? "Edit title of the topic"
|
||||||
|
: "Add a new topic in " + this.notebook.title
|
||||||
}
|
}
|
||||||
padding={12}
|
padding={12}
|
||||||
/>
|
/>
|
||||||
@@ -132,7 +145,7 @@ export class AddTopicDialog extends React.Component {
|
|||||||
<Input
|
<Input
|
||||||
fwdRef={this.titleRef}
|
fwdRef={this.titleRef}
|
||||||
testID="input-title"
|
testID="input-title"
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
this.title = value;
|
this.title = value;
|
||||||
}}
|
}}
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
@@ -144,7 +157,7 @@ export class AddTopicDialog extends React.Component {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
positiveTitle={this.toEdit ? 'Save' : 'Add'}
|
positiveTitle={this.toEdit ? "Save" : "Add"}
|
||||||
onPressNegative={() => this.close()}
|
onPressNegative={() => this.close()}
|
||||||
onPressPositive={() => this.addNewTopic()}
|
onPressPositive={() => this.addNewTopic()}
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
|||||||
@@ -1,26 +1,35 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { ScrollView, View } from 'react-native';
|
import { ScrollView, View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { useMessageStore } from '../../../stores/use-message-store';
|
import { useMessageStore } from "../../../stores/use-message-store";
|
||||||
import { DDS } from '../../../services/device-detection';
|
import { DDS } from "../../../services/device-detection";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
import {
|
||||||
import { getElevation } from '../../../utils';
|
eSubscribeEvent,
|
||||||
import { eCloseJumpToDialog, eOpenJumpToDialog, eScrollEvent } from '../../../utils/events';
|
eUnSubscribeEvent
|
||||||
import { SIZE } from '../../../utils/size';
|
} from "../../../services/event-manager";
|
||||||
import BaseDialog from '../../dialog/base-dialog';
|
import { getElevation } from "../../../utils";
|
||||||
import { PressableButton } from '../../ui/pressable';
|
import {
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
eCloseJumpToDialog,
|
||||||
|
eOpenJumpToDialog,
|
||||||
|
eScrollEvent
|
||||||
|
} from "../../../utils/events";
|
||||||
|
import { SIZE } from "../../../utils/size";
|
||||||
|
import BaseDialog from "../../dialog/base-dialog";
|
||||||
|
import { PressableButton } from "../../ui/pressable";
|
||||||
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
const offsets = [];
|
const offsets = [];
|
||||||
let timeout = null;
|
let timeout = null;
|
||||||
const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const notes = data;
|
const notes = data;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [currentIndex, setCurrentIndex] = useState(null);
|
const [currentIndex, setCurrentIndex] = useState(null);
|
||||||
|
|
||||||
const onPress = (item, index) => {
|
const onPress = (item, index) => {
|
||||||
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
|
let ind = notes.findIndex(
|
||||||
|
(i) => i.title === item.title && i.type === "header"
|
||||||
|
);
|
||||||
console.log(scrollRef.current);
|
console.log(scrollRef.current);
|
||||||
scrollRef.current?.scrollToIndex({
|
scrollRef.current?.scrollToIndex({
|
||||||
index: ind,
|
index: ind,
|
||||||
@@ -41,7 +50,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onScroll = data => {
|
const onScroll = (data) => {
|
||||||
let y = data.y;
|
let y = data.y;
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
@@ -53,7 +62,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
|||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = _type => {
|
const open = (_type) => {
|
||||||
if (_type !== type) return;
|
if (_type !== type) return;
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
@@ -68,10 +77,12 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
|||||||
|
|
||||||
const loadOffsets = () => {
|
const loadOffsets = () => {
|
||||||
notes
|
notes
|
||||||
.filter(i => i.type === 'header')
|
.filter((i) => i.type === "header")
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
let offset = 35 * index;
|
let offset = 35 * index;
|
||||||
let ind = notes.findIndex(i => i.title === item.title && i.type === 'header');
|
let ind = notes.findIndex(
|
||||||
|
(i) => i.title === item.title && i.type === "header"
|
||||||
|
);
|
||||||
let messageState = useMessageStore.getState().message;
|
let messageState = useMessageStore.getState().message;
|
||||||
let msgOffset = messageState?.visible ? 60 : 10;
|
let msgOffset = messageState?.visible ? 60 : 10;
|
||||||
ind = ind + 1;
|
ind = ind + 1;
|
||||||
@@ -92,41 +103,41 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
width: DDS.isTab ? 500 : '85%',
|
width: DDS.isTab ? 500 : "85%",
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
maxHeight: '65%',
|
maxHeight: "65%",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
paddingTop: 30
|
paddingTop: 30
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{
|
style={{
|
||||||
maxHeight: '100%'
|
maxHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
paddingBottom: 20
|
paddingBottom: 20
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{notes
|
{notes
|
||||||
.filter(i => i.type === 'header')
|
.filter((i) => i.type === "header")
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
return item.title ? (
|
return item.title ? (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
key={item.title}
|
key={item.title}
|
||||||
onPress={() => onPress(item, index)}
|
onPress={() => onPress(item, index)}
|
||||||
type={currentIndex === index ? 'accent' : 'transparent'}
|
type={currentIndex === index ? "accent" : "transparent"}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
minWidth: '20%',
|
minWidth: "20%",
|
||||||
width: null,
|
width: null,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
margin: 5,
|
margin: 5,
|
||||||
@@ -137,9 +148,11 @@ const JumpToSectionDialog = ({ scrollRef, data, type, screen }) => {
|
|||||||
>
|
>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
color={currentIndex === index ? colors.light : colors.accent}
|
color={
|
||||||
|
currentIndex === index ? colors.light : colors.accent
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { DDS } from '../../../services/device-detection';
|
import { DDS } from "../../../services/device-detection";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
import {
|
||||||
import { getElevation } from '../../../utils';
|
eSubscribeEvent,
|
||||||
import { eCloseResultDialog, eOpenResultDialog } from '../../../utils/events';
|
eUnSubscribeEvent
|
||||||
import { SIZE } from '../../../utils/size';
|
} from "../../../services/event-manager";
|
||||||
import { Button } from '../../ui/button';
|
import { getElevation } from "../../../utils";
|
||||||
import BaseDialog from '../../dialog/base-dialog';
|
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
|
||||||
import Seperator from '../../ui/seperator';
|
import { SIZE } from "../../../utils/size";
|
||||||
import Heading from '../../ui/typography/heading';
|
import { Button } from "../../ui/button";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import BaseDialog from "../../dialog/base-dialog";
|
||||||
import { ProFeatures } from './pro-features';
|
import Seperator from "../../ui/seperator";
|
||||||
|
import Heading from "../../ui/typography/heading";
|
||||||
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
import { ProFeatures } from "./pro-features";
|
||||||
|
|
||||||
const ResultDialog = () => {
|
const ResultDialog = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [dialogData, setDialogData] = useState({
|
const [dialogData, setDialogData] = useState({
|
||||||
title: 'Thank you for signing up!',
|
title: "Thank you for signing up!",
|
||||||
paragraph: 'Try out all features of Notesnook free for 7 days. No limitations. No commitments.',
|
paragraph:
|
||||||
button: 'Start taking notes'
|
"Try out all features of Notesnook free for 7 days. No limitations. No commitments.",
|
||||||
|
button: "Start taking notes"
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent(eOpenResultDialog, open);
|
eSubscribeEvent(eOpenResultDialog, open);
|
||||||
@@ -30,7 +34,7 @@ const ResultDialog = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = data => {
|
const open = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setDialogData(data);
|
setDialogData(data);
|
||||||
}
|
}
|
||||||
@@ -46,23 +50,23 @@ const ResultDialog = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
width: DDS.isTab ? 350 : '85%',
|
width: DDS.isTab ? 350 : "85%",
|
||||||
maxHeight: 500,
|
maxHeight: 500,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
paddingTop: 20,
|
paddingTop: 20,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
textBreakStrategy="balanced"
|
textBreakStrategy="balanced"
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
maxWidth: '100%',
|
maxWidth: "100%",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
@@ -74,9 +78,9 @@ const ResultDialog = () => {
|
|||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
maxWidth: '80%',
|
maxWidth: "80%",
|
||||||
lineHeight: SIZE.sm + 5
|
lineHeight: SIZE.sm + 5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -88,8 +92,8 @@ const ResultDialog = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProFeatures count={4} />
|
<ProFeatures count={4} />
|
||||||
@@ -99,7 +103,7 @@ const ResultDialog = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
borderBottomRightRadius: 10,
|
borderBottomRightRadius: 10,
|
||||||
borderBottomLeftRadius: 10,
|
borderBottomLeftRadius: 10,
|
||||||
paddingVertical: 10
|
paddingVertical: 10
|
||||||
|
|||||||
@@ -1,58 +1,63 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { eSendEvent } from '../../../services/event-manager';
|
import { eSendEvent } from "../../../services/event-manager";
|
||||||
import {
|
import {
|
||||||
eCloseProgressDialog,
|
eCloseProgressDialog,
|
||||||
eCloseResultDialog,
|
eCloseResultDialog,
|
||||||
eOpenPremiumDialog
|
eOpenPremiumDialog
|
||||||
} from '../../../utils/events';
|
} from "../../../utils/events";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { sleep } from '../../../utils/time';
|
import { sleep } from "../../../utils/time";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
export const ProFeatures = ({ count = 6 }) => {
|
export const ProFeatures = ({ count = 6 }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
content: 'Unlock unlimited notebooks, tags, colors. Organize like a pro'
|
content:
|
||||||
|
"Unlock unlimited notebooks, tags, colors. Organize like a pro"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: 'Attach files upto 500MB, upload 4K images with unlimited storage'
|
content:
|
||||||
|
"Attach files upto 500MB, upload 4K images with unlimited storage"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: 'Instantly sync to unlimited devices'
|
content: "Instantly sync to unlimited devices"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: 'A private vault to keep everything imporant always locked'
|
content: "A private vault to keep everything imporant always locked"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: 'Rich note editing experience with markdown, tables, checklists and more'
|
content:
|
||||||
|
"Rich note editing experience with markdown, tables, checklists and more"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: 'Export your notes in Pdf, markdown and html formats'
|
content: "Export your notes in Pdf, markdown and html formats"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
.slice(0, count)
|
.slice(0, count)
|
||||||
.map(item => (
|
.map((item) => (
|
||||||
<View
|
<View
|
||||||
key={item.content}
|
key={item.content}
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 40,
|
height: 40,
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
justifyContent: 'flex-start'
|
justifyContent: "flex-start"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon size={SIZE.lg} color={colors.accent} name="check" />
|
<Icon size={SIZE.lg} color={colors.accent} name="check" />
|
||||||
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>{item.content}</Paragraph>
|
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>
|
||||||
|
{item.content}
|
||||||
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
@@ -64,7 +69,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
|||||||
}}
|
}}
|
||||||
size={SIZE.xs + 1}
|
size={SIZE.xs + 1}
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: "underline",
|
||||||
color: colors.icon
|
color: colors.icon
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import React, { Component, createRef } from 'react';
|
import React, { Component, createRef } from "react";
|
||||||
import { InteractionManager, View } from 'react-native';
|
import { InteractionManager, View } from "react-native";
|
||||||
import Share from 'react-native-share';
|
import Share from "react-native-share";
|
||||||
import { notesnook } from '../../../../e2e/test.ids';
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { editorController } from '../../../screens/editor/tiptap/utils';
|
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||||
import BiometricService from '../../../services/biometrics';
|
import BiometricService from "../../../services/biometrics";
|
||||||
import { DDS } from '../../../services/device-detection';
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent,
|
eUnSubscribeEvent,
|
||||||
ToastEvent
|
ToastEvent
|
||||||
} from '../../../services/event-manager';
|
} from "../../../services/event-manager";
|
||||||
import Navigation from '../../../services/navigation';
|
import Navigation from "../../../services/navigation";
|
||||||
import SearchService from '../../../services/search';
|
import SearchService from "../../../services/search";
|
||||||
import { getElevation, toTXT } from '../../../utils';
|
import { getElevation, toTXT } from "../../../utils";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import {
|
import {
|
||||||
eClearEditor,
|
eClearEditor,
|
||||||
eCloseActionSheet,
|
eCloseActionSheet,
|
||||||
eCloseVaultDialog,
|
eCloseVaultDialog,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenVaultDialog
|
eOpenVaultDialog
|
||||||
} from '../../../utils/events';
|
} from "../../../utils/events";
|
||||||
import { deleteItems } from '../../../utils/functions';
|
import { deleteItems } from "../../../utils/functions";
|
||||||
import { tabBarRef } from '../../../utils/global-refs';
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
import { sleep } from '../../../utils/time';
|
import { sleep } from "../../../utils/time";
|
||||||
import BaseDialog from '../../dialog/base-dialog';
|
import BaseDialog from "../../dialog/base-dialog";
|
||||||
import DialogButtons from '../../dialog/dialog-buttons';
|
import DialogButtons from "../../dialog/dialog-buttons";
|
||||||
import DialogHeader from '../../dialog/dialog-header';
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { Toast } from '../../toast';
|
import { Toast } from "../../toast";
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from "../../ui/button";
|
||||||
import Input from '../../ui/input';
|
import Input from "../../ui/input";
|
||||||
import Seperator from '../../ui/seperator';
|
import Seperator from "../../ui/seperator";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
let Keychain;
|
let Keychain;
|
||||||
const passInputRef = createRef();
|
const passInputRef = createRef();
|
||||||
@@ -62,7 +62,7 @@ export class VaultDialog extends Component {
|
|||||||
changePassword: false,
|
changePassword: false,
|
||||||
copyNote: false,
|
copyNote: false,
|
||||||
revokeFingerprintAccess: false,
|
revokeFingerprintAccess: false,
|
||||||
title: 'Unlock Note',
|
title: "Unlock Note",
|
||||||
description: null,
|
description: null,
|
||||||
clearVault: false,
|
clearVault: false,
|
||||||
deleteVault: false,
|
deleteVault: false,
|
||||||
@@ -72,45 +72,45 @@ export class VaultDialog extends Component {
|
|||||||
this.confirmPassword = null;
|
this.confirmPassword = null;
|
||||||
this.newPassword = null;
|
this.newPassword = null;
|
||||||
(this.title = !this.state.novault
|
(this.title = !this.state.novault
|
||||||
? 'Create Vault'
|
? "Create Vault"
|
||||||
: this.state.fingerprintAccess
|
: this.state.fingerprintAccess
|
||||||
? 'Vault Fingerprint Unlock'
|
? "Vault Fingerprint Unlock"
|
||||||
: this.state.revokeFingerprintAccess
|
: this.state.revokeFingerprintAccess
|
||||||
? 'Revoke Vault Fingerprint Unlock'
|
? "Revoke Vault Fingerprint Unlock"
|
||||||
: this.state.changePassword
|
: this.state.changePassword
|
||||||
? 'Change Vault Password'
|
? "Change Vault Password"
|
||||||
: this.state.note.locked
|
: this.state.note.locked
|
||||||
? this.state.deleteNote
|
? this.state.deleteNote
|
||||||
? 'Delete note'
|
? "Delete note"
|
||||||
: this.state.share
|
: this.state.share
|
||||||
? 'Share note'
|
? "Share note"
|
||||||
: this.state.copyNote
|
: this.state.copyNote
|
||||||
? 'Copy note'
|
? "Copy note"
|
||||||
: this.state.goToEditor
|
: this.state.goToEditor
|
||||||
? 'Unlock note'
|
? "Unlock note"
|
||||||
: 'Unlock note'
|
: "Unlock note"
|
||||||
: 'Lock note'),
|
: "Lock note"),
|
||||||
(this.description = !this.state.novault
|
(this.description = !this.state.novault
|
||||||
? 'Set a password to create vault'
|
? "Set a password to create vault"
|
||||||
: this.state.fingerprintAccess
|
: this.state.fingerprintAccess
|
||||||
? 'Enter vault password to enable fingerprint unlocking.'
|
? "Enter vault password to enable fingerprint unlocking."
|
||||||
: this.state.revokeFingerprintAccess
|
: this.state.revokeFingerprintAccess
|
||||||
? 'Disable vault fingerprint unlock '
|
? "Disable vault fingerprint unlock "
|
||||||
: this.state.changePassword
|
: this.state.changePassword
|
||||||
? 'Setup a new password for the vault.'
|
? "Setup a new password for the vault."
|
||||||
: this.state.permanant
|
: this.state.permanant
|
||||||
? 'Enter password to remove note from vault.'
|
? "Enter password to remove note from vault."
|
||||||
: this.state.note.locked
|
: this.state.note.locked
|
||||||
? this.state.deleteNote
|
? this.state.deleteNote
|
||||||
? 'Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault.'
|
? "Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: this.state.share
|
: this.state.share
|
||||||
? 'Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault.'
|
? "Unlock note to share it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: this.state.copyNote
|
: this.state.copyNote
|
||||||
? 'Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault.'
|
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: this.state.goToEditor
|
: this.state.goToEditor
|
||||||
? 'Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault.'
|
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: 'Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault.'
|
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: 'Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.');
|
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -127,13 +127,13 @@ export class VaultDialog extends Component {
|
|||||||
*
|
*
|
||||||
* @param {import('../../../services/event-manager').vaultType} data
|
* @param {import('../../../services/event-manager').vaultType} data
|
||||||
*/
|
*/
|
||||||
open = async data => {
|
open = async (data) => {
|
||||||
if (!Keychain) {
|
if (!Keychain) {
|
||||||
Keychain = require('react-native-keychain');
|
Keychain = require("react-native-keychain");
|
||||||
}
|
}
|
||||||
let biometry = await BiometricService.isBiometryAvailable();
|
let biometry = await BiometricService.isBiometryAvailable();
|
||||||
let available = false;
|
let available = false;
|
||||||
let fingerprint = await BiometricService.hasInternetCredentials('nn_vault');
|
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
|
||||||
|
|
||||||
if (biometry) {
|
if (biometry) {
|
||||||
available = true;
|
available = true;
|
||||||
@@ -181,19 +181,19 @@ export class VaultDialog extends Component {
|
|||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: this.state.title,
|
heading: this.state.title,
|
||||||
message: 'Please wait and do not close the app.',
|
message: "Please wait and do not close the app.",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Notes',
|
"Notes",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'TopicNotes',
|
"TopicNotes",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'ColoredNotes'
|
"ColoredNotes"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.password = null;
|
this.password = null;
|
||||||
@@ -223,19 +223,19 @@ export class VaultDialog extends Component {
|
|||||||
|
|
||||||
if (!this.password) {
|
if (!this.password) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Password not entered',
|
heading: "Password not entered",
|
||||||
message: 'Enter a password for the vault and try again.',
|
message: "Enter a password for the vault and try again.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.password && this.password.length < 3) {
|
if (this.password && this.password.length < 3) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Password too short',
|
heading: "Password too short",
|
||||||
message: 'Password must be longer than 3 characters.',
|
message: "Password must be longer than 3 characters.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -244,9 +244,9 @@ export class VaultDialog extends Component {
|
|||||||
if (!this.state.novault) {
|
if (!this.state.novault) {
|
||||||
if (this.password !== this.confirmPassword) {
|
if (this.password !== this.confirmPassword) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Passwords do not match',
|
heading: "Passwords do not match",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
passwordsDontMatch: true
|
passwordsDontMatch: true
|
||||||
@@ -262,7 +262,7 @@ export class VaultDialog extends Component {
|
|||||||
|
|
||||||
db.vault
|
db.vault
|
||||||
.changePassword(this.password, this.newPassword)
|
.changePassword(this.password, this.newPassword)
|
||||||
.then(result => {
|
.then((result) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
@@ -270,32 +270,32 @@ export class VaultDialog extends Component {
|
|||||||
this._enrollFingerprint(this.newPassword);
|
this._enrollFingerprint(this.newPassword);
|
||||||
}
|
}
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Vault password updated successfully',
|
heading: "Vault password updated successfully",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
if (e.message === db.vault.ERRORS.wrongPassword) {
|
if (e.message === db.vault.ERRORS.wrongPassword) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
message: 'Please enter the correct password and try again',
|
message: "Please enter the correct password and try again",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.state.locked) {
|
} else if (this.state.locked) {
|
||||||
if (!this.password || this.password.trim() === 0) {
|
if (!this.password || this.password.trim() === 0) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
message: 'Please enter the correct password and try again',
|
message: "Please enter the correct password and try again",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
wrongPassword: true
|
wrongPassword: true
|
||||||
@@ -313,7 +313,7 @@ export class VaultDialog extends Component {
|
|||||||
});
|
});
|
||||||
await this._lockNote();
|
await this._lockNote();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
this._takeErrorAction(e);
|
this._takeErrorAction(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -335,17 +335,17 @@ export class VaultDialog extends Component {
|
|||||||
if (!(await db.user.getUser())) verified = true;
|
if (!(await db.user.getUser())) verified = true;
|
||||||
if (verified) {
|
if (verified) {
|
||||||
await db.vault.delete(this.state.deleteAll);
|
await db.vault.delete(this.state.deleteAll);
|
||||||
eSendEvent('vaultUpdated');
|
eSendEvent("vaultUpdated");
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Account password incorrect',
|
heading: "Account password incorrect",
|
||||||
message: 'Please enter correct password for your account.',
|
message: "Please enter correct password for your account.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -364,13 +364,13 @@ export class VaultDialog extends Component {
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
eSendEvent('vaultUpdated');
|
eSendEvent("vaultUpdated");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Vault password incorrect',
|
heading: "Vault password incorrect",
|
||||||
message: 'Please enter correct password to clear vault.',
|
message: "Please enter correct password to clear vault.",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -381,11 +381,11 @@ export class VaultDialog extends Component {
|
|||||||
async _lockNote() {
|
async _lockNote() {
|
||||||
if (!this.password || this.password.trim() === 0) {
|
if (!this.password || this.password.trim() === 0) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
console.log('returning from here');
|
console.log("returning from here");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await db.vault.add(this.state.note.id);
|
await db.vault.add(this.state.note.id);
|
||||||
@@ -394,9 +394,9 @@ export class VaultDialog extends Component {
|
|||||||
}
|
}
|
||||||
this.close();
|
this.close();
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
message: 'Note locked successfully',
|
message: "Note locked successfully",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
@@ -407,10 +407,10 @@ export class VaultDialog extends Component {
|
|||||||
async _unlockNote() {
|
async _unlockNote() {
|
||||||
if (!this.password || this.password.trim() === 0) {
|
if (!this.password || this.password.trim() === 0) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
message: 'Please enter the correct password and try again',
|
message: "Please enter the correct password and try again",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -464,20 +464,21 @@ export class VaultDialog extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
eSendEvent('vaultUpdated');
|
eSendEvent("vaultUpdated");
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Biometric unlocking enabled!',
|
heading: "Biometric unlocking enabled!",
|
||||||
message: 'Now you can unlock notes in vault with biometrics.',
|
message: "Now you can unlock notes in vault with biometrics.",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
message: 'Please enter the correct vault password to enable biometrics.',
|
message:
|
||||||
type: 'error',
|
"Please enter the correct vault password to enable biometrics.",
|
||||||
context: 'local'
|
type: "error",
|
||||||
|
context: "local"
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
@@ -506,17 +507,17 @@ export class VaultDialog extends Component {
|
|||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note added to vault',
|
heading: "Note added to vault",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
} else {
|
} else {
|
||||||
eSendEvent('vaultUpdated');
|
eSendEvent("vaultUpdated");
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Vault created successfully',
|
heading: "Vault created successfully",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
@@ -525,15 +526,15 @@ export class VaultDialog extends Component {
|
|||||||
_permanantUnlock() {
|
_permanantUnlock() {
|
||||||
db.vault
|
db.vault
|
||||||
.remove(this.state.note.id, this.password)
|
.remove(this.state.note.id, this.password)
|
||||||
.then(r => {
|
.then((r) => {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note permanantly unlocked.',
|
heading: "Note permanantly unlocked.",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
this._takeErrorAction(e);
|
this._takeErrorAction(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -551,10 +552,10 @@ export class VaultDialog extends Component {
|
|||||||
async _copyNote(note) {
|
async _copyNote(note) {
|
||||||
Clipboard.setString(await toTXT(note));
|
Clipboard.setString(await toTXT(note));
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note copied',
|
heading: "Note copied",
|
||||||
type: 'success',
|
type: "success",
|
||||||
message: 'Note has been copied to clipboard!',
|
message: "Note has been copied to clipboard!",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
@@ -563,7 +564,7 @@ export class VaultDialog extends Component {
|
|||||||
this.close();
|
this.close();
|
||||||
try {
|
try {
|
||||||
await Share.open({
|
await Share.open({
|
||||||
heading: 'Share note',
|
heading: "Share note",
|
||||||
failOnCancel: false,
|
failOnCancel: false,
|
||||||
message: await toTXT(note)
|
message: await toTXT(note)
|
||||||
});
|
});
|
||||||
@@ -571,16 +572,19 @@ export class VaultDialog extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_takeErrorAction(e) {
|
_takeErrorAction(e) {
|
||||||
if (e.message === db.vault.ERRORS.wrongPassword || e.message === 'FAILURE') {
|
if (
|
||||||
|
e.message === db.vault.ERRORS.wrongPassword ||
|
||||||
|
e.message === "FAILURE"
|
||||||
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
wrongPassword: true,
|
wrongPassword: true,
|
||||||
visible: true
|
visible: true
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Incorrect password',
|
heading: "Incorrect password",
|
||||||
type: 'error',
|
type: "error",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
@@ -591,18 +595,18 @@ export class VaultDialog extends Component {
|
|||||||
_revokeFingerprintAccess = async () => {
|
_revokeFingerprintAccess = async () => {
|
||||||
try {
|
try {
|
||||||
await BiometricService.resetCredentials();
|
await BiometricService.resetCredentials();
|
||||||
eSendEvent('vaultUpdated');
|
eSendEvent("vaultUpdated");
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Biometric unlocking disabled!',
|
heading: "Biometric unlocking disabled!",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Failed to disable Biometric unlocking.',
|
heading: "Failed to disable Biometric unlocking.",
|
||||||
message: e.message,
|
message: e.message,
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'global'
|
context: "global"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -663,7 +667,7 @@ export class VaultDialog extends Component {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
width: DDS.isTab ? 350 : '85%',
|
width: DDS.isTab ? 350 : "85%",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
paddingTop: 12
|
paddingTop: 12
|
||||||
@@ -682,7 +686,10 @@ export class VaultDialog extends Component {
|
|||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(novault || changePassword || this.state.clearVault || this.state.deleteVault) &&
|
{(novault ||
|
||||||
|
changePassword ||
|
||||||
|
this.state.clearVault ||
|
||||||
|
this.state.deleteVault) &&
|
||||||
!this.state.revokeFingerprintAccess ? (
|
!this.state.revokeFingerprintAccess ? (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
@@ -690,7 +697,7 @@ export class VaultDialog extends Component {
|
|||||||
editable={!loading}
|
editable={!loading}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
testID={notesnook.ids.dialogs.vault.pwd}
|
testID={notesnook.ids.dialogs.vault.pwd}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
this.password = value;
|
this.password = value;
|
||||||
}}
|
}}
|
||||||
marginBottom={
|
marginBottom={
|
||||||
@@ -702,13 +709,15 @@ export class VaultDialog extends Component {
|
|||||||
: 10
|
: 10
|
||||||
}
|
}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
changePassword ? changePassInputRef.current?.focus() : this.onPress;
|
changePassword
|
||||||
|
? changePassInputRef.current?.focus()
|
||||||
|
: this.onPress;
|
||||||
}}
|
}}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
returnKeyLabel={changePassword ? 'Next' : this.state.title}
|
returnKeyLabel={changePassword ? "Next" : this.state.title}
|
||||||
returnKeyType={changePassword ? 'next' : 'done'}
|
returnKeyType={changePassword ? "next" : "done"}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
placeholder={changePassword ? 'Current password' : 'Password'}
|
placeholder={changePassword ? "Current password" : "Password"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!this.state.biometricUnlock ||
|
{!this.state.biometricUnlock ||
|
||||||
@@ -716,10 +725,12 @@ export class VaultDialog extends Component {
|
|||||||
!novault ||
|
!novault ||
|
||||||
changePassword ? null : (
|
changePassword ? null : (
|
||||||
<Button
|
<Button
|
||||||
onPress={() => this._onPressFingerprintAuth('Unlock note', '')}
|
onPress={() =>
|
||||||
|
this._onPressFingerprintAuth("Unlock note", "")
|
||||||
|
}
|
||||||
icon="fingerprint"
|
icon="fingerprint"
|
||||||
width="100%"
|
width="100%"
|
||||||
title={'Biometric unlock'}
|
title={"Biometric unlock"}
|
||||||
type="transparent"
|
type="transparent"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -734,13 +745,15 @@ export class VaultDialog extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
this.state.deleteAll ? 'check-circle-outline' : 'checkbox-blank-circle-outline'
|
this.state.deleteAll
|
||||||
|
? "check-circle-outline"
|
||||||
|
: "checkbox-blank-circle-outline"
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
width="100%"
|
width="100%"
|
||||||
title={'Delete all notes'}
|
title={"Delete all notes"}
|
||||||
type="errorShade"
|
type="errorShade"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -753,7 +766,7 @@ export class VaultDialog extends Component {
|
|||||||
editable={!loading}
|
editable={!loading}
|
||||||
testID={notesnook.ids.dialogs.vault.changePwd}
|
testID={notesnook.ids.dialogs.vault.changePwd}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
this.newPassword = value;
|
this.newPassword = value;
|
||||||
}}
|
}}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
@@ -761,7 +774,7 @@ export class VaultDialog extends Component {
|
|||||||
returnKeyLabel="Change"
|
returnKeyLabel="Change"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
placeholder={'New password'}
|
placeholder={"New password"}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -772,7 +785,7 @@ export class VaultDialog extends Component {
|
|||||||
fwdRef={passInputRef}
|
fwdRef={passInputRef}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
testID={notesnook.ids.dialogs.vault.pwd}
|
testID={notesnook.ids.dialogs.vault.pwd}
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
this.password = value;
|
this.password = value;
|
||||||
}}
|
}}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
@@ -793,12 +806,12 @@ export class VaultDialog extends Component {
|
|||||||
validationType="confirmPassword"
|
validationType="confirmPassword"
|
||||||
customValidator={() => this.password}
|
customValidator={() => this.password}
|
||||||
errorMessage="Passwords do not match."
|
errorMessage="Passwords do not match."
|
||||||
onErrorCheck={e => null}
|
onErrorCheck={(e) => null}
|
||||||
marginBottom={0}
|
marginBottom={0}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
returnKeyLabel="Create"
|
returnKeyLabel="Create"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
onChangeText={value => {
|
onChangeText={(value) => {
|
||||||
this.confirmPassword = value;
|
this.confirmPassword = value;
|
||||||
if (value !== this.password) {
|
if (value !== this.password) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -816,8 +829,12 @@ export class VaultDialog extends Component {
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{this.state.biometricUnlock && !this.state.isBiometryEnrolled && novault ? (
|
{this.state.biometricUnlock &&
|
||||||
<Paragraph>Unlock with password once to enable biometric access.</Paragraph>
|
!this.state.isBiometryEnrolled &&
|
||||||
|
novault ? (
|
||||||
|
<Paragraph>
|
||||||
|
Unlock with password once to enable biometric access.
|
||||||
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{this.state.isBiometryAvailable &&
|
{this.state.isBiometryAvailable &&
|
||||||
@@ -838,7 +855,7 @@ export class VaultDialog extends Component {
|
|||||||
icon="fingerprint"
|
icon="fingerprint"
|
||||||
width="100%"
|
width="100%"
|
||||||
title="Biometric unlocking"
|
title="Biometric unlocking"
|
||||||
type={this.state.biometricUnlock ? 'transparent' : 'gray'}
|
type={this.state.biometricUnlock ? "transparent" : "gray"}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
@@ -847,29 +864,31 @@ export class VaultDialog extends Component {
|
|||||||
onPressNegative={this.close}
|
onPressNegative={this.close}
|
||||||
onPressPositive={this.onPress}
|
onPressPositive={this.onPress}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
positiveType={deleteVault || clearVault ? 'errorShade' : 'transparent'}
|
positiveType={
|
||||||
|
deleteVault || clearVault ? "errorShade" : "transparent"
|
||||||
|
}
|
||||||
positiveTitle={
|
positiveTitle={
|
||||||
deleteVault
|
deleteVault
|
||||||
? 'Delete'
|
? "Delete"
|
||||||
: clearVault
|
: clearVault
|
||||||
? 'Clear'
|
? "Clear"
|
||||||
: fingerprintAccess
|
: fingerprintAccess
|
||||||
? 'Enable'
|
? "Enable"
|
||||||
: this.state.revokeFingerprintAccess
|
: this.state.revokeFingerprintAccess
|
||||||
? 'Revoke'
|
? "Revoke"
|
||||||
: changePassword
|
: changePassword
|
||||||
? 'Change'
|
? "Change"
|
||||||
: note.locked
|
: note.locked
|
||||||
? deleteNote
|
? deleteNote
|
||||||
? 'Delete'
|
? "Delete"
|
||||||
: share
|
: share
|
||||||
? 'Share '
|
? "Share "
|
||||||
: goToEditor
|
: goToEditor
|
||||||
? 'Open'
|
? "Open"
|
||||||
: 'Unlock'
|
: "Unlock"
|
||||||
: !note.id
|
: !note.id
|
||||||
? 'Create'
|
? "Create"
|
||||||
: 'Lock'
|
: "Lock"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import RNBootSplash from 'react-native-bootsplash';
|
import RNBootSplash from "react-native-bootsplash";
|
||||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
||||||
import { Dialog } from '../dialog';
|
import { Dialog } from "../dialog";
|
||||||
import { Issue } from '../sheets/github/issue';
|
import { Issue } from "../sheets/github/issue";
|
||||||
|
|
||||||
const error = (
|
const error = (
|
||||||
stack: string,
|
stack: string,
|
||||||
@@ -12,7 +12,10 @@ const error = (
|
|||||||
_______________________________
|
_______________________________
|
||||||
Stacktrace: In ${component}::${stack}`;
|
Stacktrace: In ${component}::${stack}`;
|
||||||
|
|
||||||
class ExceptionHandler extends React.Component<{ children: React.ReactNode; component: string }> {
|
class ExceptionHandler extends React.Component<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
component: string;
|
||||||
|
}> {
|
||||||
state: {
|
state: {
|
||||||
error: {
|
error: {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -45,8 +48,11 @@ class ExceptionHandler extends React.Component<{ children: React.ReactNode; comp
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Issue
|
<Issue
|
||||||
defaultBody={error(this.state.error?.stack || '', this.props.component)}
|
defaultBody={error(
|
||||||
defaultTitle={this.state.error?.title || 'Unknown Error'}
|
this.state.error?.stack || "",
|
||||||
|
this.props.component
|
||||||
|
)}
|
||||||
|
defaultTitle={this.state.error?.title || "Unknown Error"}
|
||||||
issueTitle="An exception occured"
|
issueTitle="An exception occured"
|
||||||
/>
|
/>
|
||||||
<Dialog />
|
<Dialog />
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { SearchBar } from '../../screens/search/search-bar';
|
import { SearchBar } from "../../screens/search/search-bar";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import useNavigationStore from '../../stores/use-navigation-store';
|
eSubscribeEvent,
|
||||||
import { useSelectionStore } from '../../stores/use-selection-store';
|
eUnSubscribeEvent
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
} from "../../services/event-manager";
|
||||||
import { eScrollEvent } from '../../utils/events';
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { LeftMenus } from './left-menus';
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { RightMenus } from './right-menus';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { Title } from './title';
|
import { eScrollEvent } from "../../utils/events";
|
||||||
|
import { LeftMenus } from "./left-menus";
|
||||||
|
import { RightMenus } from "./right-menus";
|
||||||
|
import { Title } from "./title";
|
||||||
|
|
||||||
export const Header = React.memo(
|
export const Header = React.memo(
|
||||||
() => {
|
() => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const [hide, setHide] = useState(true);
|
const [hide, setHide] = useState(true);
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const currentScreen = useNavigationStore(state => state.currentScreen?.name);
|
const currentScreen = useNavigationStore(
|
||||||
|
(state) => state.currentScreen?.name
|
||||||
|
);
|
||||||
|
|
||||||
const onScroll = data => {
|
const onScroll = (data) => {
|
||||||
if (data.y > 150) {
|
if (data.y > 150) {
|
||||||
if (!hide) return;
|
if (!hide) return;
|
||||||
setHide(false);
|
setHide(false);
|
||||||
@@ -42,16 +47,16 @@ export const Header = React.memo(
|
|||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
{
|
{
|
||||||
marginTop: Platform.OS === 'android' ? insets.top : null,
|
marginTop: Platform.OS === "android" ? insets.top : null,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: hide ? 'transparent' : colors.nav,
|
borderBottomColor: hide ? "transparent" : colors.nav,
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{currentScreen === 'Search' ? (
|
{currentScreen === "Search" ? (
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -71,24 +76,24 @@ export const Header = React.memo(
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
zIndex: 11,
|
zIndex: 11,
|
||||||
height: 50,
|
height: 50,
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
},
|
},
|
||||||
leftBtnContainer: {
|
leftBtnContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
},
|
},
|
||||||
leftBtn: {
|
leftBtn: {
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
@@ -96,12 +101,12 @@ const styles = StyleSheet.create({
|
|||||||
marginRight: 25
|
marginRight: 25
|
||||||
},
|
},
|
||||||
rightBtnContainer: {
|
rightBtnContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
},
|
},
|
||||||
rightBtn: {
|
rightBtn: {
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'flex-end',
|
alignItems: "flex-end",
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
paddingRight: 0
|
paddingRight: 0
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import Navigation from '../../services/navigation';
|
import Navigation from "../../services/navigation";
|
||||||
import useNavigationStore from '../../stores/use-navigation-store';
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { tabBarRef } from '../../utils/global-refs';
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
|
||||||
export const LeftMenus = () => {
|
export const LeftMenus = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const canGoBack = useNavigationStore(state => state.canGoBack);
|
const canGoBack = useNavigationStore((state) => state.canGoBack);
|
||||||
const isTablet = deviceMode === 'tablet';
|
const isTablet = deviceMode === "tablet";
|
||||||
|
|
||||||
const onLeftButtonPress = () => {
|
const onLeftButtonPress = () => {
|
||||||
if (!canGoBack) {
|
if (!canGoBack) {
|
||||||
@@ -25,8 +25,8 @@ export const LeftMenus = () => {
|
|||||||
}
|
}
|
||||||
Navigation.goBack();
|
Navigation.goBack();
|
||||||
if (
|
if (
|
||||||
useNavigationStore.getState().currentScreen.name === 'Signup' ||
|
useNavigationStore.getState().currentScreen.name === "Signup" ||
|
||||||
useNavigationStore.getState().currentScreen.name === 'Login'
|
useNavigationStore.getState().currentScreen.name === "Login"
|
||||||
) {
|
) {
|
||||||
tabBarRef.current.unlock();
|
tabBarRef.current.unlock();
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,8 @@ export const LeftMenus = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
testID={notesnook.ids.default.header.buttons.left}
|
testID={notesnook.ids.default.header.buttons.left}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
@@ -51,7 +51,7 @@ export const LeftMenus = () => {
|
|||||||
onLongPress={() => {
|
onLongPress={() => {
|
||||||
Navigation.popToTop();
|
Navigation.popToTop();
|
||||||
}}
|
}}
|
||||||
name={canGoBack ? 'arrow-left' : 'menu'}
|
name={canGoBack ? "arrow-left" : "menu"}
|
||||||
color={colors.pri}
|
color={colors.pri}
|
||||||
iconStyle={{
|
iconStyle={{
|
||||||
marginLeft: canGoBack ? -5 : 0
|
marginLeft: canGoBack ? -5 : 0
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef } from "react";
|
||||||
import { Platform, StyleSheet, View } from 'react-native';
|
import { Platform, StyleSheet, View } from "react-native";
|
||||||
import Menu from 'react-native-reanimated-material-menu';
|
import Menu from "react-native-reanimated-material-menu";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import Navigation from '../../services/navigation';
|
import Navigation from "../../services/navigation";
|
||||||
import SearchService from '../../services/search';
|
import SearchService from "../../services/search";
|
||||||
import useNavigationStore from '../../stores/use-navigation-store';
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from '../../utils/time';
|
import { sleep } from "../../utils/time";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
|
||||||
export const RightMenus = () => {
|
export const RightMenus = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const rightButtons = useNavigationStore(state => state.headerRightButtons);
|
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
|
||||||
const currentScreen = useNavigationStore(state => state.currentScreen.name);
|
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
|
||||||
const buttonAction = useNavigationStore(state => state.buttonAction);
|
const buttonAction = useNavigationStore((state) => state.buttonAction);
|
||||||
const menuRef = useRef();
|
const menuRef = useRef();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.rightBtnContainer}>
|
<View style={styles.rightBtnContainer}>
|
||||||
{!currentScreen.startsWith('Settings') ? (
|
{!currentScreen.startsWith("Settings") ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SearchService.prepareSearch();
|
SearchService.prepareSearch();
|
||||||
Navigation.navigate({
|
Navigation.navigate({
|
||||||
name: 'Search'
|
name: "Search"
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
testID="icon-search"
|
testID="icon-search"
|
||||||
@@ -37,11 +37,11 @@ export const RightMenus = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{deviceMode !== 'mobile' ? (
|
{deviceMode !== "mobile" ? (
|
||||||
<Button
|
<Button
|
||||||
onPress={buttonAction}
|
onPress={buttonAction}
|
||||||
testID={notesnook.ids.default.addBtn}
|
testID={notesnook.ids.default.addBtn}
|
||||||
icon={currentScreen === 'Trash' ? 'delete' : 'plus'}
|
icon={currentScreen === "Trash" ? "delete" : "plus"}
|
||||||
iconSize={SIZE.xl}
|
iconSize={SIZE.xl}
|
||||||
type="shade"
|
type="shade"
|
||||||
hitSlop={{
|
hitSlop={{
|
||||||
@@ -88,7 +88,7 @@ export const RightMenus = () => {
|
|||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
width: 150,
|
width: 150,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
borderRadius: 0
|
borderRadius: 0
|
||||||
}}
|
}}
|
||||||
type="gray"
|
type="gray"
|
||||||
@@ -99,7 +99,7 @@ export const RightMenus = () => {
|
|||||||
title={item.title}
|
title={item.title}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
menuRef.current?.hide();
|
menuRef.current?.hide();
|
||||||
if (Platform.OS === 'ios') await sleep(300);
|
if (Platform.OS === "ios") await sleep(300);
|
||||||
item.onPress();
|
item.onPress();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -112,12 +112,12 @@ export const RightMenus = () => {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
rightBtnContainer: {
|
rightBtnContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
},
|
},
|
||||||
rightBtn: {
|
rightBtn: {
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
|
|||||||
@@ -1,40 +1,43 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Platform, View } from 'react-native';
|
import { Platform, View } from "react-native";
|
||||||
import Notebook from '../../screens/notebook';
|
import Notebook from "../../screens/notebook";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import useNavigationStore from '../../stores/use-navigation-store';
|
eSubscribeEvent,
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
eUnSubscribeEvent
|
||||||
import { db } from '../../common/database';
|
} from "../../services/event-manager";
|
||||||
import { eScrollEvent } from '../../utils/events';
|
import useNavigationStore from "../../stores/use-navigation-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import Heading from '../ui/typography/heading';
|
import { db } from "../../common/database";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import { eScrollEvent } from "../../utils/events";
|
||||||
|
import { SIZE } from "../../utils/size";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
const titleState = {};
|
const titleState = {};
|
||||||
|
|
||||||
export const Title = () => {
|
export const Title = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const currentScreen = useNavigationStore(state => state.currentScreen);
|
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||||
const isNotebook = currentScreen.name === 'Notebook';
|
const isNotebook = currentScreen.name === "Notebook";
|
||||||
const isTopic = currentScreen?.name === 'TopicNotes';
|
const isTopic = currentScreen?.name === "TopicNotes";
|
||||||
const [hide, setHide] = useState(
|
const [hide, setHide] = useState(
|
||||||
isNotebook
|
isNotebook
|
||||||
? typeof titleState[currentScreen.id] === 'boolean'
|
? typeof titleState[currentScreen.id] === "boolean"
|
||||||
? titleState[currentScreen.id]
|
? titleState[currentScreen.id]
|
||||||
: true
|
: true
|
||||||
: false
|
: false
|
||||||
);
|
);
|
||||||
const isHidden = titleState[currentScreen.id];
|
const isHidden = titleState[currentScreen.id];
|
||||||
console.log(currentScreen, 'header');
|
console.log(currentScreen, "header");
|
||||||
const notebook =
|
const notebook =
|
||||||
isTopic && currentScreen.notebookId
|
isTopic && currentScreen.notebookId
|
||||||
? db.notebooks?.notebook(currentScreen.notebookId)?.data
|
? db.notebooks?.notebook(currentScreen.notebookId)?.data
|
||||||
: null;
|
: null;
|
||||||
const title = currentScreen.title;
|
const title = currentScreen.title;
|
||||||
const isTag = currentScreen?.name === 'TaggedNotes';
|
const isTag = currentScreen?.name === "TaggedNotes";
|
||||||
|
|
||||||
const onScroll = data => {
|
const onScroll = (data) => {
|
||||||
if (currentScreen.name !== 'Notebook') {
|
if (currentScreen.name !== "Notebook") {
|
||||||
setHide(false);
|
setHide(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -48,9 +51,11 @@ export const Title = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentScreen.name === 'Notebook') {
|
if (currentScreen.name === "Notebook") {
|
||||||
let value =
|
let value =
|
||||||
typeof titleState[currentScreen.id] === 'boolean' ? titleState[currentScreen.id] : true;
|
typeof titleState[currentScreen.id] === "boolean"
|
||||||
|
? titleState[currentScreen.id]
|
||||||
|
: true;
|
||||||
setHide(value);
|
setHide(value);
|
||||||
} else {
|
} else {
|
||||||
setHide(titleState[currentScreen.id]);
|
setHide(titleState[currentScreen.id]);
|
||||||
@@ -77,7 +82,7 @@ export const Title = () => {
|
|||||||
style={{
|
style={{
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!hide && !isHidden ? (
|
{!hide && !isHidden ? (
|
||||||
@@ -86,19 +91,22 @@ export const Title = () => {
|
|||||||
numberOfLines={isTopic ? 2 : 1}
|
numberOfLines={isTopic ? 2 : 1}
|
||||||
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
marginTop: Platform.OS === 'ios' ? -1 : 0
|
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||||
}}
|
}}
|
||||||
color={currentScreen.color || colors.heading}
|
color={currentScreen.color || colors.heading}
|
||||||
>
|
>
|
||||||
{isTopic ? (
|
{isTopic ? (
|
||||||
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
|
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
|
||||||
{notebook?.title}
|
{notebook?.title}
|
||||||
{'\n'}
|
{"\n"}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
{isTag ? (
|
{isTag ? (
|
||||||
<Heading size={isTopic ? SIZE.md + 2 : SIZE.xl} color={colors.accent}>
|
<Heading
|
||||||
|
size={isTopic ? SIZE.md + 2 : SIZE.xl}
|
||||||
|
color={colors.accent}
|
||||||
|
>
|
||||||
#
|
#
|
||||||
</Heading>
|
</Heading>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
import ImageViewer from "react-native-image-zoom-viewer";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
eSubscribeEvent,
|
||||||
import { IconButton } from '../ui/icon-button';
|
eUnSubscribeEvent
|
||||||
|
} from "../../services/event-manager";
|
||||||
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
|
||||||
const ImagePreview = () => {
|
const ImagePreview = () => {
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [image, setImage] = useState('');
|
const [image, setImage] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent('ImagePreview', open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent('ImagePreview', open);
|
eUnSubscribeEvent("ImagePreview", open);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = image => {
|
const open = (image) => {
|
||||||
setImage(image);
|
setImage(image);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
@@ -32,9 +35,9 @@ const ImagePreview = () => {
|
|||||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
backgroundColor: 'black'
|
backgroundColor: "black"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
@@ -47,16 +50,16 @@ const ImagePreview = () => {
|
|||||||
renderHeader={() => (
|
renderHeader={() => (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'flex-end',
|
justifyContent: "flex-end",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 80,
|
height: 80,
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
backgroundColor: "rgba(0,0,0,0.3)",
|
||||||
paddingTop: 30
|
paddingTop: 30
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,55 +1,61 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Platform, View } from 'react-native';
|
import { Platform, View } from "react-native";
|
||||||
import RNBootSplash from 'react-native-bootsplash';
|
import RNBootSplash from "react-native-bootsplash";
|
||||||
import { checkVersion } from 'react-native-check-version';
|
import { checkVersion } from "react-native-check-version";
|
||||||
import { enabled } from 'react-native-privacy-snapshot';
|
import { enabled } from "react-native-privacy-snapshot";
|
||||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||||
import BackupService from '../../services/backup';
|
import BackupService from "../../services/backup";
|
||||||
import BiometricService from '../../services/biometrics';
|
import BiometricService from "../../services/biometrics";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent, presentSheet, ToastEvent } from '../../services/event-manager';
|
import {
|
||||||
import { setRateAppMessage } from '../../services/message';
|
eSendEvent,
|
||||||
import PremiumService from '../../services/premium';
|
presentSheet,
|
||||||
import SettingsService from '../../services/settings';
|
ToastEvent
|
||||||
import { initialize } from '../../stores';
|
} from "../../services/event-manager";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { setRateAppMessage } from "../../services/message";
|
||||||
import { useNoteStore } from '../../stores/use-notes-store';
|
import PremiumService from "../../services/premium";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import SettingsService from "../../services/settings";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { initialize } from "../../stores";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { DatabaseLogger, db, loadDatabase } from '../../common/database';
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { MMKV } from '../../common/database/mmkv';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { eOpenAnnouncementDialog } from '../../utils/events';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { tabBarRef } from '../../utils/global-refs';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
|
||||||
import { sleep } from '../../utils/time';
|
import { MMKV } from "../../common/database/mmkv";
|
||||||
import { SVG } from '../auth/background';
|
import { eOpenAnnouncementDialog } from "../../utils/events";
|
||||||
import Migrate from '../sheets/migrate';
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import NewFeature from '../sheets/new-feature/index';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Update } from '../sheets/update';
|
import { sleep } from "../../utils/time";
|
||||||
import { Button } from '../ui/button';
|
import { SVG } from "../auth/background";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import Migrate from "../sheets/migrate";
|
||||||
import Input from '../ui/input';
|
import NewFeature from "../sheets/new-feature/index";
|
||||||
import Seperator from '../ui/seperator';
|
import { Update } from "../sheets/update";
|
||||||
import { SvgView } from '../ui/svg';
|
import { Button } from "../ui/button";
|
||||||
import Heading from '../ui/typography/heading';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Input from "../ui/input";
|
||||||
import { Walkthrough } from '../walkthroughs';
|
import Seperator from "../ui/seperator";
|
||||||
import { useAppState } from '../../hooks/use-app-state';
|
import { SvgView } from "../ui/svg";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { Walkthrough } from "../walkthroughs";
|
||||||
|
import { useAppState } from "../../hooks/use-app-state";
|
||||||
|
|
||||||
const Launcher = React.memo(
|
const Launcher = React.memo(
|
||||||
() => {
|
() => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const setLoading = useNoteStore(state => state.setLoading);
|
const setLoading = useNoteStore((state) => state.setLoading);
|
||||||
const loading = useNoteStore(state => state.loading);
|
const loading = useNoteStore((state) => state.loading);
|
||||||
const user = useUserStore(state => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const verifyUser = useUserStore(state => state.verifyUser);
|
const verifyUser = useUserStore((state) => state.verifyUser);
|
||||||
const setVerifyUser = useUserStore(state => state.setVerifyUser);
|
const setVerifyUser = useUserStore((state) => state.setVerifyUser);
|
||||||
const deviceMode = useSettingStore(state => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
const password = useRef();
|
const password = useRef();
|
||||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
const introCompleted = useSettingStore(
|
||||||
|
(state) => state.settings.introCompleted
|
||||||
|
);
|
||||||
const dbInitCompleted = useRef(false);
|
const dbInitCompleted = useRef(false);
|
||||||
const loadNotes = async () => {
|
const loadNotes = async () => {
|
||||||
if (verifyUser) {
|
if (verifyUser) {
|
||||||
@@ -76,7 +82,7 @@ const Launcher = React.memo(
|
|||||||
if (!dbInitCompleted.current) {
|
if (!dbInitCompleted.current) {
|
||||||
await RNBootSplash.hide({ fade: true });
|
await RNBootSplash.hide({ fade: true });
|
||||||
await loadDatabase();
|
await loadDatabase();
|
||||||
DatabaseLogger.info('Initializing database');
|
DatabaseLogger.info("Initializing database");
|
||||||
await db.init();
|
await db.init();
|
||||||
dbInitCompleted.current = true;
|
dbInitCompleted.current = true;
|
||||||
}
|
}
|
||||||
@@ -115,16 +121,20 @@ const Launcher = React.memo(
|
|||||||
const doAppLoadActions = async () => {
|
const doAppLoadActions = async () => {
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
if (SettingsService.get().sessionExpired) {
|
if (SettingsService.get().sessionExpired) {
|
||||||
eSendEvent('session_expired');
|
eSendEvent("session_expired");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const user = await db.user.getUser();
|
const user = await db.user.getUser();
|
||||||
await useMessageStore.getState().setAnnouncement();
|
await useMessageStore.getState().setAnnouncement();
|
||||||
if (PremiumService.get() && user) {
|
if (PremiumService.get() && user) {
|
||||||
if (SettingsService.get().reminder === 'off') {
|
if (SettingsService.get().reminder === "off") {
|
||||||
SettingsService.set({ reminder: 'daily' });
|
SettingsService.set({ reminder: "daily" });
|
||||||
}
|
}
|
||||||
if (await BackupService.checkBackupRequired(SettingsService.get().reminder)) {
|
if (
|
||||||
|
await BackupService.checkBackupRequired(
|
||||||
|
SettingsService.get().reminder
|
||||||
|
)
|
||||||
|
) {
|
||||||
sleep(2000).then(() => BackupService.run());
|
sleep(2000).then(() => BackupService.run());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +145,7 @@ const Launcher = React.memo(
|
|||||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||||
|
|
||||||
if (introCompleted) {
|
if (introCompleted) {
|
||||||
useMessageStore.subscribe(state => {
|
useMessageStore.subscribe((state) => {
|
||||||
let dialogs = state.dialogs;
|
let dialogs = state.dialogs;
|
||||||
if (dialogs.length > 0) {
|
if (dialogs.length > 0) {
|
||||||
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
|
||||||
@@ -150,7 +160,7 @@ const Launcher = React.memo(
|
|||||||
const version = await checkVersion();
|
const version = await checkVersion();
|
||||||
if (!version.needsUpdate) return false;
|
if (!version.needsUpdate) return false;
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: ref => <Update version={version} fwdRef={ref} />
|
component: (ref) => <Update version={version} fwdRef={ref} />
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -159,7 +169,7 @@ const Launcher = React.memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreEditorState = async () => {
|
const restoreEditorState = async () => {
|
||||||
let appState = MMKV.getString('appState');
|
let appState = MMKV.getString("appState");
|
||||||
if (appState) {
|
if (appState) {
|
||||||
appState = JSON.parse(appState);
|
appState = JSON.parse(appState);
|
||||||
if (
|
if (
|
||||||
@@ -174,14 +184,18 @@ const Launcher = React.memo(
|
|||||||
if (!DDS.isTab) {
|
if (!DDS.isTab) {
|
||||||
tabBarRef.current?.goToPage(1);
|
tabBarRef.current?.goToPage(1);
|
||||||
}
|
}
|
||||||
eSendEvent('loadingNote', appState.note);
|
eSendEvent("loadingNote", appState.note);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForRateAppRequest = async () => {
|
const checkForRateAppRequest = async () => {
|
||||||
let rateApp = SettingsService.get().rateApp;
|
let rateApp = SettingsService.get().rateApp;
|
||||||
if (rateApp && rateApp < Date.now() && !useMessageStore.getState().message?.visible) {
|
if (
|
||||||
|
rateApp &&
|
||||||
|
rateApp < Date.now() &&
|
||||||
|
!useMessageStore.getState().message?.visible
|
||||||
|
) {
|
||||||
setRateAppMessage();
|
setRateAppMessage();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -208,12 +222,15 @@ const Launcher = React.memo(
|
|||||||
const onUnlockBiometrics = async () => {
|
const onUnlockBiometrics = async () => {
|
||||||
if (!(await BiometricService.isBiometryAvailable())) {
|
if (!(await BiometricService.isBiometryAvailable())) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Biometrics unavailable',
|
heading: "Biometrics unavailable",
|
||||||
message: 'Try unlocking the app with your account password'
|
message: "Try unlocking the app with your account password"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let verified = await BiometricService.validateUser('Unlock to access your notes', '');
|
let verified = await BiometricService.validateUser(
|
||||||
|
"Unlock to access your notes",
|
||||||
|
""
|
||||||
|
);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
setVerifyUser(false);
|
setVerifyUser(false);
|
||||||
enabled(false);
|
enabled(false);
|
||||||
@@ -226,7 +243,7 @@ const Launcher = React.memo(
|
|||||||
}, [verifyUser]);
|
}, [verifyUser]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (verifyUser && appState === 'active') {
|
if (verifyUser && appState === "active") {
|
||||||
onUnlockBiometrics();
|
onUnlockBiometrics();
|
||||||
}
|
}
|
||||||
}, [appState]);
|
}, [appState]);
|
||||||
@@ -247,30 +264,35 @@ const Launcher = React.memo(
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
zIndex: 999
|
zIndex: 999
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 250,
|
height: 250,
|
||||||
overflow: 'hidden'
|
overflow: "hidden"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgView src={SVG(colors.night ? 'white' : 'black')} height={700} />
|
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
width: deviceMode !== 'mobile' ? '50%' : Platform.OS == 'ios' ? '95%' : '100%',
|
width:
|
||||||
|
deviceMode !== "mobile"
|
||||||
|
? "50%"
|
||||||
|
: Platform.OS == "ios"
|
||||||
|
? "95%"
|
||||||
|
: "100%",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
marginTop: 15,
|
marginTop: 15,
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -288,8 +310,8 @@ const Launcher = React.memo(
|
|||||||
<Heading
|
<Heading
|
||||||
color={colors.heading}
|
color={colors.heading}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlign: 'center'
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Unlock to access your notes
|
Unlock to access your notes
|
||||||
@@ -297,10 +319,10 @@ const Launcher = React.memo(
|
|||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
fontSize: SIZE.md,
|
fontSize: SIZE.md,
|
||||||
maxWidth: '90%'
|
maxWidth: "90%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Please verify it's you
|
Please verify it's you
|
||||||
@@ -308,7 +330,7 @@ const Launcher = React.memo(
|
|||||||
<Seperator />
|
<Seperator />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
padding: 12,
|
padding: 12,
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
flexGrow: 1
|
flexGrow: 1
|
||||||
@@ -320,7 +342,7 @@ const Launcher = React.memo(
|
|||||||
fwdRef={passwordInputRef}
|
fwdRef={passwordInputRef}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
placeholder="Enter account password"
|
placeholder="Enter account password"
|
||||||
onChangeText={v => (password.current = v)}
|
onChangeText={(v) => (password.current = v)}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -356,8 +378,8 @@ const Launcher = React.memo(
|
|||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
onPress={onUnlockBiometrics}
|
onPress={onUnlockBiometrics}
|
||||||
icon={'fingerprint'}
|
icon={"fingerprint"}
|
||||||
type={user ? 'grayAccent' : 'accent'}
|
type={user ? "grayAccent" : "accent"}
|
||||||
fontSize={SIZE.md}
|
fontSize={SIZE.md}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
|
|
||||||
export const Footer = () => {
|
export const Footer = () => {
|
||||||
return <View style={{ height: 150 }} />;
|
return <View style={{ height: 150 }} />;
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { useMessageStore } from '../../../stores/use-message-store';
|
import { useMessageStore } from "../../../stores/use-message-store";
|
||||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||||
import { Announcement } from '../../announcements/announcement';
|
import { Announcement } from "../../announcements/announcement";
|
||||||
import { Card } from '../../list/card';
|
import { Card } from "../../list/card";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated';
|
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||||
export const Header = React.memo(
|
export const Header = React.memo(
|
||||||
({ type, messageCard = true, color, shouldShow = false, noAnnouncement, warning }) => {
|
({
|
||||||
const colors = useThemeStore(state => state.colors);
|
type,
|
||||||
const announcements = useMessageStore(state => state.announcements);
|
messageCard = true,
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
color,
|
||||||
|
shouldShow = false,
|
||||||
|
noAnnouncement,
|
||||||
|
warning
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
const announcements = useMessageStore((state) => state.announcements);
|
||||||
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
|
|
||||||
return selectionMode ? null : (
|
return selectionMode ? null : (
|
||||||
<>
|
<>
|
||||||
@@ -23,11 +30,11 @@ export const Header = React.memo(
|
|||||||
style={{
|
style={{
|
||||||
padding: 12,
|
padding: 12,
|
||||||
backgroundColor: colors.errorBg,
|
backgroundColor: colors.errorBg,
|
||||||
width: '95%',
|
width: "95%",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
|
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
|
||||||
@@ -37,18 +44,20 @@ export const Header = React.memo(
|
|||||||
</View>
|
</View>
|
||||||
) : announcements.length !== 0 && !noAnnouncement ? (
|
) : announcements.length !== 0 && !noAnnouncement ? (
|
||||||
<Announcement color={color || colors.accent} />
|
<Announcement color={color || colors.accent} />
|
||||||
) : type === 'search' ? null : !shouldShow ? (
|
) : type === "search" ? null : !shouldShow ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 5,
|
marginBottom: 5,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{messageCard ? (
|
{messageCard ? (
|
||||||
<Card color={COLORS_NOTE[color?.toLowerCase()] || colors.accent} />
|
<Card
|
||||||
|
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -57,4 +66,4 @@ export const Header = React.memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
Header.displayName = 'Header';
|
Header.displayName = "Header";
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||||
import { ToastEvent } from '../../../services/event-manager';
|
import { ToastEvent } from "../../../services/event-manager";
|
||||||
import { getTotalNotes } from '../../../utils';
|
import { getTotalNotes } from "../../../utils";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Heading from '../../ui/typography/heading';
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(db.settings.isPinned(notebook.id));
|
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||||
const setMenuPins = useMenuStore(state => state.setMenuPins);
|
db.settings.isPinned(notebook.id)
|
||||||
|
);
|
||||||
|
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||||
const totalNotes = getTotalNotes(notebook);
|
const totalNotes = getTotalNotes(notebook);
|
||||||
const shortcutRef = useRef();
|
const shortcutRef = useRef();
|
||||||
|
|
||||||
@@ -24,8 +26,8 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
} else {
|
} else {
|
||||||
await db.settings.pin(notebook.type, { id: notebook.id });
|
await db.settings.pin(notebook.type, { id: notebook.id });
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Shortcut created',
|
heading: "Shortcut created",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsPinnedToMenu(db.settings.isPinned(notebook.id));
|
setIsPinnedToMenu(db.settings.isPinned(notebook.id));
|
||||||
@@ -38,10 +40,10 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
style={{
|
style={{
|
||||||
marginBottom: 5,
|
marginBottom: 5,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
paddingVertical: 15,
|
paddingVertical: 15,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
paddingTop: 25
|
paddingTop: 25
|
||||||
}}
|
}}
|
||||||
@@ -51,29 +53,29 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.xxl}>{notebook.title}</Heading>
|
<Heading size={SIZE.xxl}>{notebook.title}</Heading>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={isPinnedToMenu ? 'link-variant-off' : 'link-variant'}
|
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
|
||||||
onPress={onPinNotebook}
|
onPress={onPinNotebook}
|
||||||
tooltipText={'Create shortcut in side menu'}
|
tooltipText={"Create shortcut in side menu"}
|
||||||
fwdRef={shortcutRef}
|
fwdRef={shortcutRef}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40
|
height: 40
|
||||||
}}
|
}}
|
||||||
type={isPinnedToMenu ? 'grayBg' : 'grayBg'}
|
type={isPinnedToMenu ? "grayBg" : "grayBg"}
|
||||||
color={isPinnedToMenu ? colors.accent : colors.icon}
|
color={isPinnedToMenu ? colors.accent : colors.icon}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
/>
|
/>
|
||||||
@@ -101,18 +103,21 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
fontStyle: 'italic',
|
fontStyle: "italic",
|
||||||
fontFamily: null
|
fontFamily: null
|
||||||
}}
|
}}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
>
|
>
|
||||||
{notebook.topics.length === 1 ? '1 topic' : `${notebook.topics.length} topics`},{' '}
|
{notebook.topics.length === 1
|
||||||
|
? "1 topic"
|
||||||
|
: `${notebook.topics.length} topics`}
|
||||||
|
,{" "}
|
||||||
{notebook && totalNotes > 1
|
{notebook && totalNotes > 1
|
||||||
? totalNotes + ' notes'
|
? totalNotes + " notes"
|
||||||
: totalNotes === 1
|
: totalNotes === 1
|
||||||
? totalNotes + ' note'
|
? totalNotes + " note"
|
||||||
: '0 notes'}
|
: "0 notes"}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,40 +1,48 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { TouchableOpacity, useWindowDimensions, View } from 'react-native';
|
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import {
|
import {
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent,
|
eUnSubscribeEvent,
|
||||||
presentSheet
|
presentSheet
|
||||||
} from '../../../services/event-manager';
|
} from "../../../services/event-manager";
|
||||||
import SettingsService from '../../../services/settings';
|
import SettingsService from "../../../services/settings";
|
||||||
import { GROUP } from '../../../utils/constants';
|
import { GROUP } from "../../../utils/constants";
|
||||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { eOpenJumpToDialog } from '../../../utils/events';
|
import { eOpenJumpToDialog } from "../../../utils/events";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from "../../ui/button";
|
||||||
import Sort from '../../sheets/sort';
|
import Sort from "../../sheets/sort";
|
||||||
import Heading from '../../ui/typography/heading';
|
import Heading from "../../ui/typography/heading";
|
||||||
|
|
||||||
export const SectionHeader = React.memo(
|
export const SectionHeader = React.memo(
|
||||||
({ item, index, type, color, screen }) => {
|
({ item, index, type, color, screen }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const { fontScale } = useWindowDimensions();
|
const { fontScale } = useWindowDimensions();
|
||||||
const [groupOptions, setGroupOptions] = useState(db.settings?.getGroupOptions(type));
|
const [groupOptions, setGroupOptions] = useState(
|
||||||
let groupBy = Object.keys(GROUP).find(key => GROUP[key] === groupOptions.groupBy);
|
db.settings?.getGroupOptions(type)
|
||||||
|
);
|
||||||
|
let groupBy = Object.keys(GROUP).find(
|
||||||
|
(key) => GROUP[key] === groupOptions.groupBy
|
||||||
|
);
|
||||||
const jumpToRef = useRef();
|
const jumpToRef = useRef();
|
||||||
const sortRef = useRef();
|
const sortRef = useRef();
|
||||||
const compactModeRef = useRef();
|
const compactModeRef = useRef();
|
||||||
|
|
||||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
const notebooksListMode = useSettingStore(
|
||||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
(state) => state.settings.notebooksListMode
|
||||||
const listMode = type === 'notebooks' ? notebooksListMode : notesListMode;
|
);
|
||||||
|
const notesListMode = useSettingStore(
|
||||||
|
(state) => state.settings.notesListMode
|
||||||
|
);
|
||||||
|
const listMode = type === "notebooks" ? notebooksListMode : notesListMode;
|
||||||
|
|
||||||
groupBy = !groupBy
|
groupBy = !groupBy
|
||||||
? 'Default'
|
? "Default"
|
||||||
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
||||||
|
|
||||||
const onUpdate = () => {
|
const onUpdate = () => {
|
||||||
@@ -42,23 +50,23 @@ export const SectionHeader = React.memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent('groupOptionsUpdate', onUpdate);
|
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent('groupOptionsUpdate', onUpdate);
|
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '95%',
|
width: "95%",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
height: 35 * fontScale,
|
height: 35 * fontScale,
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
marginVertical: 5
|
marginVertical: 5
|
||||||
}}
|
}}
|
||||||
@@ -71,8 +79,8 @@ export const SectionHeader = React.memo(
|
|||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
|
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: "100%",
|
||||||
justifyContent: 'center'
|
justifyContent: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
@@ -80,18 +88,18 @@ export const SectionHeader = React.memo(
|
|||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlignVertical: 'center'
|
textAlignVertical: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!item.title || item.title === '' ? 'Pinned' : item.title}
|
{!item.title || item.title === "" ? "Pinned" : item.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{index === 0 ? (
|
{index === 0 ? (
|
||||||
@@ -105,19 +113,26 @@ export const SectionHeader = React.memo(
|
|||||||
tooltipText="Change sorting of items in list"
|
tooltipText="Change sorting of items in list"
|
||||||
fwdRef={sortRef}
|
fwdRef={sortRef}
|
||||||
title={groupBy}
|
title={groupBy}
|
||||||
icon={groupOptions.sortDirection === 'asc' ? 'sort-ascending' : 'sort-descending'}
|
icon={
|
||||||
|
groupOptions.sortDirection === "asc"
|
||||||
|
? "sort-ascending"
|
||||||
|
: "sort-descending"
|
||||||
|
}
|
||||||
height={25}
|
height={25}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: "transparent",
|
||||||
marginRight: type === 'notes' || type === 'home' || type === 'notebooks' ? 10 : 0
|
marginRight:
|
||||||
|
type === "notes" || type === "home" || type === "notebooks"
|
||||||
|
? 10
|
||||||
|
: 0
|
||||||
}}
|
}}
|
||||||
type="gray"
|
type="gray"
|
||||||
iconPosition="right"
|
iconPosition="right"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{type === 'notes' || type === 'notebooks' || type === 'home' ? (
|
{type === "notes" || type === "notebooks" || type === "home" ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
customStyle={{
|
customStyle={{
|
||||||
width: 25,
|
width: 25,
|
||||||
@@ -125,15 +140,22 @@ export const SectionHeader = React.memo(
|
|||||||
}}
|
}}
|
||||||
testID="icon-compact-mode"
|
testID="icon-compact-mode"
|
||||||
tooltipText={
|
tooltipText={
|
||||||
listMode == 'compact' ? 'Switch to normal mode' : 'Switch to compact mode'
|
listMode == "compact"
|
||||||
|
? "Switch to normal mode"
|
||||||
|
: "Switch to compact mode"
|
||||||
}
|
}
|
||||||
fwdRef={compactModeRef}
|
fwdRef={compactModeRef}
|
||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
name={listMode == 'compact' ? 'view-list' : 'view-list-outline'}
|
name={
|
||||||
|
listMode == "compact" ? "view-list" : "view-list-outline"
|
||||||
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
let settings = {};
|
let settings = {};
|
||||||
settings[type !== 'notebooks' ? 'notesListMode' : 'notebooksListMode'] =
|
settings[
|
||||||
listMode === 'normal' ? 'compact' : 'normal';
|
type !== "notebooks"
|
||||||
|
? "notesListMode"
|
||||||
|
: "notebooksListMode"
|
||||||
|
] = listMode === "normal" ? "compact" : "normal";
|
||||||
|
|
||||||
SettingsService.set(settings);
|
SettingsService.set(settings);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { decode, EntityLevel } from 'entities';
|
import { decode, EntityLevel } from "entities";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from '../../../../e2e/test.ids';
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TaggedNotes } from '../../../screens/notes/tagged';
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||||
import useNavigationStore from '../../../stores/use-navigation-store';
|
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { COLORS_NOTE } from '../../../utils/color-scheme';
|
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from '../../properties';
|
import { Properties } from "../../properties";
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { TimeSince } from '../../ui/time-since';
|
import { TimeSince } from "../../ui/time-since";
|
||||||
import Heading from '../../ui/typography/heading';
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
const navigateToTopic = topic => {
|
const navigateToTopic = (topic) => {
|
||||||
TopicNotes.navigate(topic, true);
|
TopicNotes.navigate(topic, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,16 +28,19 @@ function navigateToTag(item) {
|
|||||||
TaggedNotes.navigate(tag, true);
|
TaggedNotes.navigate(tag, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const showActionSheet = item => {
|
const showActionSheet = (item) => {
|
||||||
Properties.present(item);
|
Properties.present(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNotebook(item) {
|
function getNotebook(item) {
|
||||||
const isTrash = item.type === 'trash';
|
const isTrash = item.type === "trash";
|
||||||
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
|
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||||
const filteredNotebooks = item.notebooks?.filter(n => n.id !== currentScreen.notebookId);
|
const filteredNotebooks = item.notebooks?.filter(
|
||||||
let item_notebook = filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
|
(n) => n.id !== currentScreen.notebookId
|
||||||
|
);
|
||||||
|
let item_notebook =
|
||||||
|
filteredNotebooks?.length > 0 ? filteredNotebooks.slice(0, 1)[0] : null;
|
||||||
let notebook = item_notebook && db.notebooks.notebook(item_notebook.id);
|
let notebook = item_notebook && db.notebooks.notebook(item_notebook.id);
|
||||||
if (!notebook) return [];
|
if (!notebook) return [];
|
||||||
let topic = notebook.topics.topic(item_notebook.topics[0])?._topic;
|
let topic = notebook.topics.topic(item_notebook.topics[0])?._topic;
|
||||||
@@ -52,11 +55,19 @@ function getNotebook(item) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false }) => {
|
const NoteItem = ({
|
||||||
const colors = useThemeStore(state => state.colors);
|
item,
|
||||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
isTrash,
|
||||||
const compactMode = notesListMode === 'compact';
|
tags,
|
||||||
const attachmentCount = db.attachments?.ofNote(item.id, 'all')?.length || 0;
|
dateBy = "dateCreated",
|
||||||
|
noOpen = false
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
const notesListMode = useSettingStore(
|
||||||
|
(state) => state.settings.notesListMode
|
||||||
|
);
|
||||||
|
const compactMode = notesListMode === "compact";
|
||||||
|
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
|
||||||
const notebooks = React.useMemo(() => getNotebook(item), [item]);
|
const notebooks = React.useMemo(() => getNotebook(item), [item]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,14 +81,14 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
{!compactMode ? (
|
{!compactMode ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
elevation: 10,
|
elevation: 10,
|
||||||
marginBottom: 2.5
|
marginBottom: 2.5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{notebooks.map(_item => (
|
{notebooks.map((_item) => (
|
||||||
<Button
|
<Button
|
||||||
title={_item.title}
|
title={_item.title}
|
||||||
key={_item}
|
key={_item}
|
||||||
@@ -106,7 +117,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
>
|
>
|
||||||
@@ -116,7 +127,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
{item.headline && !compactMode ? (
|
{item.headline && !compactMode ? (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
>
|
>
|
||||||
@@ -128,10 +139,10 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
height: SIZE.md + 2
|
height: SIZE.md + 2
|
||||||
}}
|
}}
|
||||||
@@ -155,14 +166,16 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
time={item[dateBy]}
|
time={item[dateBy]}
|
||||||
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
|
updateFrequency={
|
||||||
|
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{attachmentCount > 0 ? (
|
{attachmentCount > 0 ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -181,7 +194,9 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
style={{
|
style={{
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.accent}
|
color={
|
||||||
|
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -210,15 +225,15 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!isTrash && !compactMode && tags
|
{!isTrash && !compactMode && tags
|
||||||
? tags.map(item =>
|
? tags.map((item) =>
|
||||||
item.id ? (
|
item.id ? (
|
||||||
<Button
|
<Button
|
||||||
title={'#' + item.alias}
|
title={"#" + item.alias}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
height={23}
|
height={23}
|
||||||
type="gray"
|
type="gray"
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
||||||
fontSize={SIZE.xs}
|
fontSize={SIZE.xs}
|
||||||
@@ -244,7 +259,7 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Deleted on{' '}
|
Deleted on{" "}
|
||||||
{item && item.dateDeleted
|
{item && item.dateDeleted
|
||||||
? new Date(item.dateDeleted).toISOString().slice(0, 10)
|
? new Date(item.dateDeleted).toISOString().slice(0, 10)
|
||||||
: null}
|
: null}
|
||||||
@@ -270,11 +285,11 @@ const NoteItem = ({ item, isTrash, tags, dateBy = 'dateCreated', noOpen = false
|
|||||||
size={SIZE.xl}
|
size={SIZE.xl}
|
||||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 35,
|
width: 35,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import NoteItem from '.';
|
import NoteItem from ".";
|
||||||
import { notesnook } from '../../../../e2e/test.ids';
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||||
import { useEditorStore } from '../../../stores/use-editor-store';
|
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||||
import { DDS } from '../../../services/device-detection';
|
import { DDS } from "../../../services/device-detection";
|
||||||
import { eSendEvent, openVault, ToastEvent } from '../../../services/event-manager';
|
import {
|
||||||
import Navigation from '../../../services/navigation';
|
eSendEvent,
|
||||||
import { history } from '../../../utils';
|
openVault,
|
||||||
import { db } from '../../../common/database';
|
ToastEvent
|
||||||
import { eOnLoadNote, eShowMergeDialog } from '../../../utils/events';
|
} from "../../../services/event-manager";
|
||||||
import { tabBarRef } from '../../../utils/global-refs';
|
import Navigation from "../../../services/navigation";
|
||||||
import { presentDialog } from '../../dialog/functions';
|
import { history } from "../../../utils";
|
||||||
import SelectionWrapper from '../selection-wrapper';
|
import { db } from "../../../common/database";
|
||||||
|
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||||
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
|
import { presentDialog } from "../../dialog/functions";
|
||||||
|
import SelectionWrapper from "../selection-wrapper";
|
||||||
|
|
||||||
const present = () =>
|
const present = () =>
|
||||||
presentDialog({
|
presentDialog({
|
||||||
title: 'Note not synced',
|
title: "Note not synced",
|
||||||
negativeText: 'Ok',
|
negativeText: "Ok",
|
||||||
paragraph: 'Please sync again to open this note for editing'
|
paragraph: "Please sync again to open this note for editing"
|
||||||
});
|
});
|
||||||
|
|
||||||
export const openNote = async (item, isTrash, setSelectedItem) => {
|
export const openNote = async (item, isTrash, setSelectedItem) => {
|
||||||
@@ -55,8 +59,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
|||||||
novault: true,
|
novault: true,
|
||||||
locked: true,
|
locked: true,
|
||||||
goToEditor: true,
|
goToEditor: true,
|
||||||
title: 'Open note',
|
title: "Open note",
|
||||||
description: 'Unlock note to open it in editor.'
|
description: "Unlock note to open it in editor."
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,24 +68,24 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
|||||||
presentDialog({
|
presentDialog({
|
||||||
title: `Restore ${item.itemType}`,
|
title: `Restore ${item.itemType}`,
|
||||||
paragraph: `Restore or delete ${item.itemType} forever`,
|
paragraph: `Restore or delete ${item.itemType} forever`,
|
||||||
positiveText: 'Restore',
|
positiveText: "Restore",
|
||||||
negativeText: 'Delete',
|
negativeText: "Delete",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
await db.trash.restore(item.id);
|
await db.trash.restore(item.id);
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Tags',
|
"Tags",
|
||||||
'Notes',
|
"Notes",
|
||||||
'Notebooks',
|
"Notebooks",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'Trash',
|
"Trash",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TopicNotes'
|
"TopicNotes"
|
||||||
);
|
);
|
||||||
useSelectionStore.getState().setSelectionMode(false);
|
useSelectionStore.getState().setSelectionMode(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Restore successful',
|
heading: "Restore successful",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onClose: async () => {
|
onClose: async () => {
|
||||||
@@ -89,9 +93,9 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
|||||||
useTrashStore.getState().setTrash();
|
useTrashStore.getState().setTrash();
|
||||||
useSelectionStore.getState().setSelectionMode(false);
|
useSelectionStore.getState().setSelectionMode(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Permanantly deleted items',
|
heading: "Permanantly deleted items",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -106,8 +110,8 @@ export const openNote = async (item, isTrash, setSelectedItem) => {
|
|||||||
|
|
||||||
export const NoteWrapper = React.memo(
|
export const NoteWrapper = React.memo(
|
||||||
({ item, index, tags, dateBy }) => {
|
({ item, index, tags, dateBy }) => {
|
||||||
const isTrash = item.type === 'trash';
|
const isTrash = item.type === "trash";
|
||||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectionWrapper
|
<SelectionWrapper
|
||||||
|
|||||||
@@ -1,31 +1,39 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from '../../../../e2e/test.ids';
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { getTotalNotes, history } from '../../../utils';
|
import { getTotalNotes, history } from "../../../utils";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from '../../properties';
|
import { Properties } from "../../properties";
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Heading from '../../ui/typography/heading';
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
const showActionSheet = item => {
|
const showActionSheet = (item) => {
|
||||||
Properties.present(item);
|
Properties.present(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToTopic = topic => {
|
const navigateToTopic = (topic) => {
|
||||||
if (history.selectedItemsList.length > 0) return;
|
if (history.selectedItemsList.length > 0) return;
|
||||||
TopicNotes.navigate(topic, true);
|
TopicNotes.navigate(topic, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateBy }) => {
|
export const NotebookItem = ({
|
||||||
const colors = useThemeStore(state => state.colors);
|
item,
|
||||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
isTopic = false,
|
||||||
const compactMode = notebooksListMode === 'compact';
|
notebookID,
|
||||||
|
isTrash,
|
||||||
|
dateBy
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
const notebooksListMode = useSettingStore(
|
||||||
|
(state) => state.settings.notebooksListMode
|
||||||
|
);
|
||||||
|
const compactMode = notebooksListMode === "compact";
|
||||||
const topics = item.topics?.slice(0, 3) || [];
|
const topics = item.topics?.slice(0, 3) || [];
|
||||||
const totalNotes = getTotalNotes(item);
|
const totalNotes = getTotalNotes(item);
|
||||||
|
|
||||||
@@ -41,7 +49,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
@@ -51,7 +59,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.description}
|
{item.description}
|
||||||
@@ -61,18 +69,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
{isTopic || compactMode ? null : (
|
{isTopic || compactMode ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{topics.map(topic => (
|
{topics.map((topic) => (
|
||||||
<Button
|
<Button
|
||||||
title={topic.title}
|
title={topic.title}
|
||||||
key={topic.id}
|
key={topic.id}
|
||||||
height={null}
|
height={null}
|
||||||
textStyle={{
|
textStyle={{
|
||||||
fontWeight: 'normal',
|
fontWeight: "normal",
|
||||||
fontFamily: null,
|
fontFamily: null,
|
||||||
marginRight: 0
|
marginRight: 0
|
||||||
}}
|
}}
|
||||||
@@ -98,9 +106,9 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
height: SIZE.md + 2
|
height: SIZE.md + 2
|
||||||
}}
|
}}
|
||||||
@@ -112,7 +120,7 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isTopic ? 'Topic' : 'Notebook'}
|
{isTopic ? "Topic" : "Notebook"}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{isTrash ? (
|
{isTrash ? (
|
||||||
@@ -121,17 +129,18 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
style={{
|
style={{
|
||||||
textAlignVertical: 'center',
|
textAlignVertical: "center",
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{'Deleted on ' + new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
{"Deleted on " +
|
||||||
|
new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
color={colors.accent}
|
color={colors.accent}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
style={{
|
style={{
|
||||||
textAlignVertical: 'center',
|
textAlignVertical: "center",
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -157,10 +166,10 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item && totalNotes > 1
|
{item && totalNotes > 1
|
||||||
? totalNotes + ' notes'
|
? totalNotes + " notes"
|
||||||
: totalNotes === 1
|
: totalNotes === 1
|
||||||
? totalNotes + ' note'
|
? totalNotes + " note"
|
||||||
: '0 notes'}
|
: "0 notes"}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{item.pinned ? (
|
{item.pinned ? (
|
||||||
@@ -183,11 +192,11 @@ export const NotebookItem = ({ item, isTopic = false, notebookID, isTrash, dateB
|
|||||||
size={SIZE.xl}
|
size={SIZE.xl}
|
||||||
onPress={() => showActionSheet(item)}
|
onPress={() => showActionSheet(item)}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 35,
|
width: 35,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { NotebookItem } from '.';
|
import { NotebookItem } from ".";
|
||||||
import Notebook from '../../../screens/notebook';
|
import Notebook from "../../../screens/notebook";
|
||||||
import { TopicNotes } from '../../../screens/notes/topic-notes';
|
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||||
import { ToastEvent } from '../../../services/event-manager';
|
import { ToastEvent } from "../../../services/event-manager";
|
||||||
import Navigation from '../../../services/navigation';
|
import Navigation from "../../../services/navigation";
|
||||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||||
import { history } from '../../../utils';
|
import { history } from "../../../utils";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { presentDialog } from '../../dialog/functions';
|
import { presentDialog } from "../../dialog/functions";
|
||||||
import SelectionWrapper from '../selection-wrapper';
|
import SelectionWrapper from "../selection-wrapper";
|
||||||
|
|
||||||
export const openNotebookTopic = item => {
|
export const openNotebookTopic = (item) => {
|
||||||
const isTrash = item.type === 'trash';
|
const isTrash = item.type === "trash";
|
||||||
if (history.selectedItemsList.length > 0 && history.selectionMode) {
|
if (history.selectedItemsList.length > 0 && history.selectionMode) {
|
||||||
useSelectionStore.getState().setSelectedItem(item);
|
useSelectionStore.getState().setSelectedItem(item);
|
||||||
return;
|
return;
|
||||||
@@ -24,24 +24,24 @@ export const openNotebookTopic = item => {
|
|||||||
presentDialog({
|
presentDialog({
|
||||||
title: `Restore ${item.itemType}`,
|
title: `Restore ${item.itemType}`,
|
||||||
paragraph: `Restore or delete ${item.itemType} forever`,
|
paragraph: `Restore or delete ${item.itemType} forever`,
|
||||||
positiveText: 'Restore',
|
positiveText: "Restore",
|
||||||
negativeText: 'Delete',
|
negativeText: "Delete",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
await db.trash.restore(item.id);
|
await db.trash.restore(item.id);
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Tags',
|
"Tags",
|
||||||
'Notes',
|
"Notes",
|
||||||
'Notebooks',
|
"Notebooks",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'Trash',
|
"Trash",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TopicNotes'
|
"TopicNotes"
|
||||||
);
|
);
|
||||||
useSelectionStore.getState().setSelectionMode(false);
|
useSelectionStore.getState().setSelectionMode(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Restore successful',
|
heading: "Restore successful",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onClose: async () => {
|
onClose: async () => {
|
||||||
@@ -49,15 +49,15 @@ export const openNotebookTopic = item => {
|
|||||||
useTrashStore.getState().setTrash();
|
useTrashStore.getState().setTrash();
|
||||||
useSelectionStore.getState().setSelectionMode(false);
|
useSelectionStore.getState().setSelectionMode(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Permanantly deleted items',
|
heading: "Permanantly deleted items",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item.type === 'topic') {
|
if (item.type === "topic") {
|
||||||
TopicNotes.navigate(item, true);
|
TopicNotes.navigate(item, true);
|
||||||
} else {
|
} else {
|
||||||
Notebook.navigate(item, true);
|
Notebook.navigate(item, true);
|
||||||
@@ -66,18 +66,18 @@ export const openNotebookTopic = item => {
|
|||||||
|
|
||||||
export const NotebookWrapper = React.memo(
|
export const NotebookWrapper = React.memo(
|
||||||
({ item, index, dateBy }) => {
|
({ item, index, dateBy }) => {
|
||||||
const isTrash = item.type === 'trash';
|
const isTrash = item.type === "trash";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectionWrapper
|
<SelectionWrapper
|
||||||
pinned={item.pinned}
|
pinned={item.pinned}
|
||||||
index={index}
|
index={index}
|
||||||
onPress={() => openNotebookTopic(item)}
|
onPress={() => openNotebookTopic(item)}
|
||||||
height={item.type === 'topic' ? 80 : 110}
|
height={item.type === "topic" ? 80 : 110}
|
||||||
item={item}
|
item={item}
|
||||||
>
|
>
|
||||||
<NotebookItem
|
<NotebookItem
|
||||||
isTopic={item.type === 'topic'}
|
isTopic={item.type === "topic"}
|
||||||
item={item}
|
item={item}
|
||||||
dateBy={dateBy}
|
dateBy={dateBy}
|
||||||
index={index}
|
index={index}
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Animated, { SlideInUp, SlideOutDown } from 'react-native-reanimated';
|
import Animated, { SlideInUp, SlideOutDown } from "react-native-reanimated";
|
||||||
import { openVault, ToastEvent } from '../../../services/event-manager';
|
import { openVault, ToastEvent } from "../../../services/event-manager";
|
||||||
import Navigation from '../../../services/navigation';
|
import Navigation from "../../../services/navigation";
|
||||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useTrashStore } from '../../../stores/use-trash-store';
|
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||||
import { useMenuStore } from '../../../stores/use-menu-store';
|
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||||
import { useNotebookStore } from '../../../stores/use-notebook-store';
|
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { dWidth, getElevation, toTXT } from '../../../utils';
|
import { dWidth, getElevation, toTXT } from "../../../utils";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { deleteItems } from '../../../utils/functions';
|
import { deleteItems } from "../../../utils/functions";
|
||||||
import { presentDialog } from '../../dialog/functions';
|
import { presentDialog } from "../../dialog/functions";
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
|
|
||||||
export const ActionStrip = ({ note, setActionStrip }) => {
|
export const ActionStrip = ({ note, setActionStrip }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const setNotebooks = useNotebookStore(state => state.setNotebooks);
|
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||||
const setMenuPins = useMenuStore(state => state.setMenuPins);
|
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||||
const setSelectionMode = useSelectionStore(state => state.setSelectionMode);
|
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
|
||||||
|
|
||||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(false);
|
const [isPinnedToMenu, setIsPinnedToMenu] = useState(false);
|
||||||
const [width, setWidth] = useState(dWidth - 16);
|
const [width, setWidth] = useState(dWidth - 16);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (note.type === 'note') return;
|
if (note.type === "note") return;
|
||||||
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateNotes = () => {
|
const updateNotes = () => {
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Notes',
|
"Notes",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'TopicNotes'
|
"TopicNotes"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
title: 'Pin ' + note.type,
|
title: "Pin " + note.type,
|
||||||
icon: note.pinned ? 'pin-off' : 'pin',
|
icon: note.pinned ? "pin-off" : "pin",
|
||||||
visible: note.type === 'note' || note.type === 'notebook',
|
visible: note.type === "note" || note.type === "notebook",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (!note.id) return;
|
if (!note.id) return;
|
||||||
|
|
||||||
if (note.type === 'note') {
|
if (note.type === "note") {
|
||||||
if (db.notes.pinned.length === 3 && !note.pinned) {
|
if (db.notes.pinned.length === 3 && !note.pinned) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Cannot pin more than 3 notes',
|
heading: "Cannot pin more than 3 notes",
|
||||||
type: 'error'
|
type: "error"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,8 +61,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
} else {
|
} else {
|
||||||
if (db.notebooks.pinned.length === 3 && !note.pinned) {
|
if (db.notebooks.pinned.length === 3 && !note.pinned) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Cannot pin more than 3 notebooks',
|
heading: "Cannot pin more than 3 notebooks",
|
||||||
type: 'error'
|
type: "error"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -74,11 +74,11 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Add to favorites',
|
title: "Add to favorites",
|
||||||
icon: note.favorite ? 'star-off' : 'star',
|
icon: note.favorite ? "star-off" : "star",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (!note.id) return;
|
if (!note.id) return;
|
||||||
if (note.type === 'note') {
|
if (note.type === "note") {
|
||||||
await db.notes.note(note.id).favorite();
|
await db.notes.note(note.id).favorite();
|
||||||
} else {
|
} else {
|
||||||
await db.notebooks.notebook(note.id).favorite();
|
await db.notebooks.notebook(note.id).favorite();
|
||||||
@@ -86,23 +86,25 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
updateNotes();
|
updateNotes();
|
||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
},
|
},
|
||||||
visible: note.type === 'note',
|
visible: note.type === "note",
|
||||||
color: !note.favorite ? 'orange' : null
|
color: !note.favorite ? "orange" : null
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: isPinnedToMenu ? 'Remove Shortcut from Menu' : 'Add Shortcut to Menu',
|
title: isPinnedToMenu
|
||||||
icon: isPinnedToMenu ? 'link-variant-remove' : 'link-variant',
|
? "Remove Shortcut from Menu"
|
||||||
|
: "Add Shortcut to Menu",
|
||||||
|
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
if (isPinnedToMenu) {
|
if (isPinnedToMenu) {
|
||||||
await db.settings.unpin(note.id);
|
await db.settings.unpin(note.id);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Shortcut removed from menu',
|
heading: "Shortcut removed from menu",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (note.type === 'topic') {
|
if (note.type === "topic") {
|
||||||
await db.settings.pin(note.type, {
|
await db.settings.pin(note.type, {
|
||||||
id: note.id,
|
id: note.id,
|
||||||
notebookId: note.notebookId
|
notebookId: note.notebookId
|
||||||
@@ -111,8 +113,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
await db.settings.pin(note.type, { id: note.id });
|
await db.settings.pin(note.type, { id: note.id });
|
||||||
}
|
}
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Shortcut added to menu',
|
heading: "Shortcut added to menu",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
setIsPinnedToMenu(db.settings.isPinned(note.id));
|
||||||
@@ -121,12 +123,12 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
visible: note.type !== 'note'
|
visible: note.type !== "note"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Copy Note',
|
title: "Copy Note",
|
||||||
icon: 'content-copy',
|
icon: "content-copy",
|
||||||
visible: note.type === 'note',
|
visible: note.type === "note",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (note.locked) {
|
if (note.locked) {
|
||||||
openVault({
|
openVault({
|
||||||
@@ -134,75 +136,77 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
novault: true,
|
novault: true,
|
||||||
locked: true,
|
locked: true,
|
||||||
item: note,
|
item: note,
|
||||||
title: 'Copy note',
|
title: "Copy note",
|
||||||
description: 'Unlock note to copy to clipboard.'
|
description: "Unlock note to copy to clipboard."
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let text = await toTXT(note);
|
let text = await toTXT(note);
|
||||||
text = `${note.title}\n \n ${text}`;
|
text = `${note.title}\n \n ${text}`;
|
||||||
Clipboard.setString(text);
|
Clipboard.setString(text);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note copied to clipboard',
|
heading: "Note copied to clipboard",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Restore ' + note.itemType,
|
title: "Restore " + note.itemType,
|
||||||
icon: 'delete-restore',
|
icon: "delete-restore",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
await db.trash.restore(note.id);
|
await db.trash.restore(note.id);
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Notes',
|
"Notes",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'TopicNotes',
|
"TopicNotes",
|
||||||
'Trash',
|
"Trash",
|
||||||
'Notebooks'
|
"Notebooks"
|
||||||
);
|
);
|
||||||
|
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading:
|
heading:
|
||||||
note.type === 'note' ? 'Note restored from trash' : 'Notebook restored from trash',
|
note.type === "note"
|
||||||
type: 'success'
|
? "Note restored from trash"
|
||||||
|
: "Notebook restored from trash",
|
||||||
|
type: "success"
|
||||||
});
|
});
|
||||||
|
|
||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
},
|
},
|
||||||
visible: note.type === 'trash'
|
visible: note.type === "trash"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Delete' + note.itemType,
|
title: "Delete" + note.itemType,
|
||||||
icon: 'delete',
|
icon: "delete",
|
||||||
visible: note.type === 'trash',
|
visible: note.type === "trash",
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
title: `Permanent delete`,
|
title: `Permanent delete`,
|
||||||
paragraph: `Are you sure you want to delete this ${note.itemType} permanantly from trash?`,
|
paragraph: `Are you sure you want to delete this ${note.itemType} permanantly from trash?`,
|
||||||
positiveText: 'Delete',
|
positiveText: "Delete",
|
||||||
negativeText: 'Cancel',
|
negativeText: "Cancel",
|
||||||
positivePress: async () => {
|
positivePress: async () => {
|
||||||
await db.trash.delete(note.id);
|
await db.trash.delete(note.id);
|
||||||
useTrashStore.getState().setTrash();
|
useTrashStore.getState().setTrash();
|
||||||
useSelectionStore.getState().setSelectionMode(false);
|
useSelectionStore.getState().setSelectionMode(false);
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Permanantly deleted items',
|
heading: "Permanantly deleted items",
|
||||||
type: 'success',
|
type: "success",
|
||||||
context: 'local'
|
context: "local"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
positiveType: 'errorShade'
|
positiveType: "errorShade"
|
||||||
});
|
});
|
||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Delete' + note.type,
|
title: "Delete" + note.type,
|
||||||
icon: 'delete',
|
icon: "delete",
|
||||||
visible: note.type !== 'trash',
|
visible: note.type !== "trash",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteItems(note);
|
await deleteItems(note);
|
||||||
@@ -211,8 +215,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Close',
|
title: "Close",
|
||||||
icon: 'close',
|
icon: "close",
|
||||||
onPress: () => setActionStrip(false),
|
onPress: () => setActionStrip(false),
|
||||||
color: colors.light,
|
color: colors.light,
|
||||||
bg: colors.red,
|
bg: colors.red,
|
||||||
@@ -222,19 +226,19 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
onLayout={event => {
|
onLayout={(event) => {
|
||||||
setWidth(event.nativeEvent.layout.width);
|
setWidth(event.nativeEvent.layout.width);
|
||||||
}}
|
}}
|
||||||
entering={SlideInUp.springify().mass(0.4)}
|
entering={SlideInUp.springify().mass(0.4)}
|
||||||
exiting={SlideOutDown}
|
exiting={SlideOutDown}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
width: '102%',
|
width: "102%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'flex-end',
|
justifyContent: "flex-end",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -242,7 +246,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
title="Select"
|
title="Select"
|
||||||
icon="check"
|
icon="check"
|
||||||
tooltipText="Select Item"
|
tooltipText="Select Item"
|
||||||
onPress={event => {
|
onPress={(event) => {
|
||||||
if (!selectionMode) {
|
if (!selectionMode) {
|
||||||
setSelectionMode(true);
|
setSelectionMode(true);
|
||||||
}
|
}
|
||||||
@@ -256,7 +260,7 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
}}
|
}}
|
||||||
height={30}
|
height={30}
|
||||||
/>
|
/>
|
||||||
{actions.map(item =>
|
{actions.map((item) =>
|
||||||
item.visible ? (
|
item.visible ? (
|
||||||
<View
|
<View
|
||||||
key={item.icon}
|
key={item.icon}
|
||||||
@@ -265,8 +269,8 @@ export const ActionStrip = ({ note, setActionStrip }) => {
|
|||||||
height: width / 1.4 / actions.length,
|
height: width / 1.4 / actions.length,
|
||||||
backgroundColor: item.bg || colors.nav,
|
backgroundColor: item.bg || colors.nav,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
...getElevation(5),
|
...getElevation(5),
|
||||||
marginLeft: 15
|
marginLeft: 15
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { useEditorStore } from '../../../stores/use-editor-store';
|
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||||
import { hexToRGBA } from '../../../utils/color-scheme/utils';
|
import { hexToRGBA } from "../../../utils/color-scheme/utils";
|
||||||
|
|
||||||
export const Filler = ({ item, background }) => {
|
export const Filler = ({ item, background }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
const currentEditingNote = useEditorStore(state => state.currentEditingNote);
|
const currentEditingNote = useEditorStore(
|
||||||
|
(state) => state.currentEditingNote
|
||||||
|
);
|
||||||
|
|
||||||
const color = 'gray';
|
const color = "gray";
|
||||||
|
|
||||||
return currentEditingNote === item.id ? (
|
return currentEditingNote === item.id ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
width: '110%',
|
width: "110%",
|
||||||
height: '150%',
|
height: "150%",
|
||||||
backgroundColor: currentEditingNote === item.id ? hexToRGBA(colors[color], 0.12) : null,
|
backgroundColor:
|
||||||
|
currentEditingNote === item.id
|
||||||
|
? hexToRGBA(colors[color], 0.12)
|
||||||
|
: null,
|
||||||
borderLeftWidth: 5,
|
borderLeftWidth: 5,
|
||||||
borderLeftColor:
|
borderLeftColor:
|
||||||
currentEditingNote === item.id ? colors[item.color || 'accent'] : 'transparent'
|
currentEditingNote === item.id
|
||||||
|
? colors[item.color || "accent"]
|
||||||
|
: "transparent"
|
||||||
}}
|
}}
|
||||||
></View>
|
></View>
|
||||||
) : null;
|
) : null;
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
import {
|
||||||
import { useSettingStore } from '../../../stores/use-setting-store';
|
eSubscribeEvent,
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
eUnSubscribeEvent
|
||||||
import { history } from '../../../utils';
|
} from "../../../services/event-manager";
|
||||||
import { PressableButton } from '../../ui/pressable';
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { ActionStrip } from './action-strip';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { Filler } from './back-fill';
|
import { history } from "../../../utils";
|
||||||
import { SelectionIcon } from './selection';
|
import { PressableButton } from "../../ui/pressable";
|
||||||
|
import { ActionStrip } from "./action-strip";
|
||||||
|
import { Filler } from "./back-fill";
|
||||||
|
import { SelectionIcon } from "./selection";
|
||||||
|
|
||||||
const SelectionWrapper = ({ children, item, background, onLongPress, onPress, testID }) => {
|
const SelectionWrapper = ({
|
||||||
const colors = useThemeStore(state => state.colors);
|
children,
|
||||||
|
item,
|
||||||
|
background,
|
||||||
|
onLongPress,
|
||||||
|
onPress,
|
||||||
|
testID
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [actionStrip, setActionStrip] = useState(false);
|
const [actionStrip, setActionStrip] = useState(false);
|
||||||
const notebooksListMode = useSettingStore(state => state.settings.notebooksListMode);
|
const notebooksListMode = useSettingStore(
|
||||||
const notesListMode = useSettingStore(state => state.settings.notesListMode);
|
(state) => state.settings.notebooksListMode
|
||||||
const listMode = item.type === 'notebook' ? notebooksListMode : notesListMode;
|
);
|
||||||
const compactMode = (item.type === 'notebook' || item.type === 'note') && listMode === 'compact';
|
const notesListMode = useSettingStore(
|
||||||
|
(state) => state.settings.notesListMode
|
||||||
|
);
|
||||||
|
const listMode = item.type === "notebook" ? notebooksListMode : notesListMode;
|
||||||
|
const compactMode =
|
||||||
|
(item.type === "notebook" || item.type === "note") &&
|
||||||
|
listMode === "compact";
|
||||||
|
|
||||||
const _onLongPress = () => {
|
const _onLongPress = () => {
|
||||||
if (history.selectedItemsList.length > 0) return;
|
if (history.selectedItemsList.length > 0) return;
|
||||||
@@ -34,10 +50,10 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent('navigate', closeStrip);
|
eSubscribeEvent("navigate", closeStrip);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent('navigate', closeStrip);
|
eUnSubscribeEvent("navigate", closeStrip);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -51,17 +67,19 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
|||||||
customAlpha={!colors.night ? -0.02 : 0.02}
|
customAlpha={!colors.night ? -0.02 : 0.02}
|
||||||
customOpacity={1}
|
customOpacity={1}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingVertical: compactMode ? 8 : 12
|
paddingVertical: compactMode ? 8 : 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.type === 'note' ? <Filler background={background} item={item} /> : null}
|
{item.type === "note" ? (
|
||||||
|
<Filler background={background} item={item} />
|
||||||
|
) : null}
|
||||||
<SelectionIcon
|
<SelectionIcon
|
||||||
compactMode={compactMode}
|
compactMode={compactMode}
|
||||||
setActionStrip={setActionStrip}
|
setActionStrip={setActionStrip}
|
||||||
@@ -70,7 +88,9 @@ const SelectionWrapper = ({ children, item, background, onLongPress, onPress, te
|
|||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
{actionStrip ? <ActionStrip note={item} setActionStrip={setActionStrip} /> : null}
|
{actionStrip ? (
|
||||||
|
<ActionStrip note={item} setActionStrip={setActionStrip} />
|
||||||
|
) : null}
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { TouchableOpacity, View } from 'react-native';
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useSelectionStore } from '../../../stores/use-selection-store';
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
|
|
||||||
export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
const selectionMode = useSelectionStore(state => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const selectedItemsList = useSelectionStore(state => state.selectedItemsList);
|
const selectedItemsList = useSelectionStore(
|
||||||
const setSelectedItem = useSelectionStore(state => state.setSelectedItem);
|
(state) => state.selectedItemsList
|
||||||
|
);
|
||||||
|
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
||||||
const [selected, setSelected] = useState(false);
|
const [selected, setSelected] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectionMode) {
|
if (selectionMode) {
|
||||||
setActionStrip(false);
|
setActionStrip(false);
|
||||||
let exists = selectedItemsList.filter(o => o.dateCreated === item.dateCreated);
|
let exists = selectedItemsList.filter(
|
||||||
|
(o) => o.dateCreated === item.dateCreated
|
||||||
|
);
|
||||||
|
|
||||||
if (exists[0]) {
|
if (exists[0]) {
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
@@ -37,12 +41,12 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
|||||||
return selectionMode ? (
|
return selectionMode ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: '10%',
|
width: "10%",
|
||||||
height: compactMode ? 40 : 70,
|
height: compactMode ? 40 : 70,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
@@ -54,13 +58,17 @@ export const SelectionIcon = ({ setActionStrip, item, compactMode }) => {
|
|||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
style={{
|
style={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 70
|
height: 70
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selected && (
|
{selected && (
|
||||||
<Icon size={SIZE.xl} color={selected ? colors.accent : colors.icon} name="check" />
|
<Icon
|
||||||
|
size={SIZE.xl}
|
||||||
|
color={selected ? colors.accent : colors.icon}
|
||||||
|
name="check"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { notesnook } from '../../../../e2e/test.ids';
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TaggedNotes } from '../../../screens/notes/tagged';
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { useThemeStore } from '../../../stores/use-theme-store';
|
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||||
import { db } from '../../../common/database';
|
import { db } from "../../../common/database";
|
||||||
import { SIZE } from '../../../utils/size';
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from '../../properties';
|
import { Properties } from "../../properties";
|
||||||
import { IconButton } from '../../ui/icon-button';
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { PressableButton } from '../../ui/pressable';
|
import { PressableButton } from "../../ui/pressable";
|
||||||
import Heading from '../../ui/typography/heading';
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from '../../ui/typography/paragraph';
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
const TagItem = React.memo(
|
const TagItem = React.memo(
|
||||||
({ item, index }) => {
|
({ item, index }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
TaggedNotes.navigate(item, true);
|
TaggedNotes.navigate(item, true);
|
||||||
};
|
};
|
||||||
@@ -27,16 +27,16 @@ const TagItem = React.memo(
|
|||||||
opacity={1}
|
opacity={1}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '92%'
|
maxWidth: "92%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size={SIZE.md}>
|
<Heading size={SIZE.md}>
|
||||||
@@ -58,9 +58,9 @@ const TagItem = React.memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item && item.noteIds.length && item.noteIds.length > 1
|
{item && item.noteIds.length && item.noteIds.length > 1
|
||||||
? item.noteIds.length + ' notes'
|
? item.noteIds.length + " notes"
|
||||||
: item.noteIds.length === 1
|
: item.noteIds.length === 1
|
||||||
? item.noteIds.length + ' note'
|
? item.noteIds.length + " note"
|
||||||
: null}
|
: null}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
@@ -74,11 +74,11 @@ const TagItem = React.memo(
|
|||||||
}}
|
}}
|
||||||
testID={notesnook.ids.tag.menu}
|
testID={notesnook.ids.tag.menu}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 35,
|
width: 35,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
@@ -96,6 +96,6 @@ const TagItem = React.memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
TagItem.displayName = 'TagItem';
|
TagItem.displayName = "TagItem";
|
||||||
|
|
||||||
export default TagItem;
|
export default TagItem;
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useMessageStore } from '../../stores/use-message-store';
|
import { useMessageStore } from "../../stores/use-message-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { hexToRGBA } from '../../utils/color-scheme/utils';
|
import { hexToRGBA } from "../../utils/color-scheme/utils";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { PressableButton } from '../ui/pressable';
|
import { PressableButton } from "../ui/pressable";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Card = ({ color, warning }) => {
|
export const Card = ({ color, warning }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
color = color ? color : colors.accent;
|
color = color ? color : colors.accent;
|
||||||
const messageBoardState = useMessageStore(state => state.message);
|
const messageBoardState = useMessageStore((state) => state.message);
|
||||||
const announcement = useMessageStore(state => state.announcement);
|
const announcement = useMessageStore((state) => state.announcement);
|
||||||
|
|
||||||
return !messageBoardState.visible || announcement || warning ? null : (
|
return !messageBoardState.visible || announcement || warning ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '95%'
|
width: "95%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PressableButton
|
<PressableButton
|
||||||
@@ -25,10 +25,10 @@ export const Card = ({ color, warning }) => {
|
|||||||
type="gray"
|
type="gray"
|
||||||
customStyle={{
|
customStyle={{
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
width: '95%',
|
width: "95%",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -36,19 +36,21 @@ export const Card = ({ color, warning }) => {
|
|||||||
style={{
|
style={{
|
||||||
width: 40,
|
width: 40,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
messageBoardState.type === 'error'
|
messageBoardState.type === "error"
|
||||||
? hexToRGBA(colors.red, 0.15)
|
? hexToRGBA(colors.red, 0.15)
|
||||||
: hexToRGBA(color, 0.15),
|
: hexToRGBA(color, 0.15),
|
||||||
height: 40,
|
height: 40,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center'
|
justifyContent: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
color={messageBoardState.type === 'error' ? colors.errorText : color}
|
color={
|
||||||
|
messageBoardState.type === "error" ? colors.errorText : color
|
||||||
|
}
|
||||||
name={messageBoardState.icon}
|
name={messageBoardState.icon}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -56,7 +58,7 @@ export const Card = ({ color, warning }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
maxWidth: '70%'
|
maxWidth: "70%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||||
@@ -64,7 +66,7 @@ export const Card = ({ color, warning }) => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%'
|
maxWidth: "100%"
|
||||||
}}
|
}}
|
||||||
color={colors.heading}
|
color={colors.heading}
|
||||||
>
|
>
|
||||||
@@ -76,15 +78,15 @@ export const Card = ({ color, warning }) => {
|
|||||||
style={{
|
style={{
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
right: 6
|
right: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="chevron-right"
|
name="chevron-right"
|
||||||
color={messageBoardState.type === 'error' ? colors.red : color}
|
color={messageBoardState.type === "error" ? colors.red : color}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,49 +1,61 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { ActivityIndicator, useWindowDimensions, View } from 'react-native';
|
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useSettingStore } from '../../stores/use-setting-store';
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useTip } from '../../services/tip-manager';
|
import { useTip } from "../../services/tip-manager";
|
||||||
import { COLORS_NOTE } from '../../utils/color-scheme';
|
import { COLORS_NOTE } from "../../utils/color-scheme";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import Seperator from '../ui/seperator';
|
import Seperator from "../ui/seperator";
|
||||||
import { Tip } from '../tip';
|
import { Tip } from "../tip";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
|
|
||||||
export const Empty = React.memo(
|
export const Empty = React.memo(
|
||||||
({ loading = true, placeholderData, headerProps, type, screen }) => {
|
({ loading = true, placeholderData, headerProps, type, screen }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { height } = useWindowDimensions();
|
const { height } = useWindowDimensions();
|
||||||
const introCompleted = useSettingStore(state => state.settings.introCompleted);
|
const introCompleted = useSettingStore(
|
||||||
|
(state) => state.settings.introCompleted
|
||||||
|
);
|
||||||
|
|
||||||
const tip = useTip(
|
const tip = useTip(
|
||||||
screen === 'Notes' && introCompleted ? 'first-note' : placeholderData.type || type,
|
screen === "Notes" && introCompleted
|
||||||
screen === 'Notes' ? 'notes' : null
|
? "first-note"
|
||||||
|
: placeholderData.type || type,
|
||||||
|
screen === "Notes" ? "notes" : null
|
||||||
);
|
);
|
||||||
const color =
|
const color =
|
||||||
colors[COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'];
|
colors[
|
||||||
|
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||||
|
? headerProps.color
|
||||||
|
: "accent"
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
height: height - (140 + insets.top),
|
height: height - (140 + insets.top),
|
||||||
width: '80%',
|
width: "80%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<>
|
<>
|
||||||
<Tip
|
<Tip
|
||||||
color={COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'}
|
color={
|
||||||
|
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||||
|
? headerProps.color
|
||||||
|
: "accent"
|
||||||
|
}
|
||||||
tip={tip || { text: placeholderData.paragraph }}
|
tip={tip || { text: placeholderData.paragraph }}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: "transparent",
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -56,11 +68,13 @@ export const Empty = React.memo(
|
|||||||
icon="arrow-right"
|
icon="arrow-right"
|
||||||
onPress={placeholderData.action}
|
onPress={placeholderData.action}
|
||||||
accentColor={
|
accentColor={
|
||||||
COLORS_NOTE[headerProps.color?.toLowerCase()] ? headerProps.color : 'accent'
|
COLORS_NOTE[headerProps.color?.toLowerCase()]
|
||||||
|
? headerProps.color
|
||||||
|
: "accent"
|
||||||
}
|
}
|
||||||
accentText="light"
|
accentText="light"
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'flex-start',
|
alignSelf: "flex-start",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
height: 40
|
height: 40
|
||||||
}}
|
}}
|
||||||
@@ -71,9 +85,9 @@ export const Empty = React.memo(
|
|||||||
<>
|
<>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
alignItems: 'flex-start',
|
alignItems: "flex-start",
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading>{placeholderData.heading}</Heading>
|
<Heading>{placeholderData.heading}</Heading>
|
||||||
@@ -83,7 +97,9 @@ export const Empty = React.memo(
|
|||||||
<Seperator />
|
<Seperator />
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
color={COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent}
|
color={
|
||||||
|
COLORS_NOTE[headerProps.color?.toLowerCase()] || colors.accent
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef } from "react";
|
||||||
import { FlatList, RefreshControl, View } from 'react-native';
|
import { FlatList, RefreshControl, View } from "react-native";
|
||||||
import Animated, { FadeInDown } from 'react-native-reanimated';
|
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||||
import { notesnook } from '../../../e2e/test.ids';
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { eSendEvent } from '../../services/event-manager';
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import Sync from '../../services/sync';
|
import Sync from "../../services/sync";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { eScrollEvent } from '../../utils/events';
|
import { eScrollEvent } from "../../utils/events";
|
||||||
import { tabBarRef } from '../../utils/global-refs';
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import JumpToSectionDialog from '../dialogs/jump-to-section';
|
import JumpToSectionDialog from "../dialogs/jump-to-section";
|
||||||
import { Footer } from '../list-items/footer';
|
import { Footer } from "../list-items/footer";
|
||||||
import { Header } from '../list-items/headers/header';
|
import { Header } from "../list-items/headers/header";
|
||||||
import { SectionHeader } from '../list-items/headers/section-header';
|
import { SectionHeader } from "../list-items/headers/section-header";
|
||||||
import { NoteWrapper } from '../list-items/note/wrapper';
|
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||||
import { NotebookWrapper } from '../list-items/notebook/wrapper';
|
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||||
import TagItem from '../list-items/tag';
|
import TagItem from "../list-items/tag";
|
||||||
import { Empty } from './empty';
|
import { Empty } from "./empty";
|
||||||
|
|
||||||
const renderItems = {
|
const renderItems = {
|
||||||
note: NoteWrapper,
|
note: NoteWrapper,
|
||||||
@@ -30,12 +30,13 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
|
|||||||
if (!item) return <View />;
|
if (!item) return <View />;
|
||||||
const Item = renderItems[item.itemType || item.type] || View;
|
const Item = renderItems[item.itemType || item.type] || View;
|
||||||
const groupOptions = db.settings?.getGroupOptions(type);
|
const groupOptions = db.settings?.getGroupOptions(type);
|
||||||
const dateBy = groupOptions.sortBy !== 'title' ? groupOptions.sortBy : 'dateEdited';
|
const dateBy =
|
||||||
|
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
|
||||||
|
|
||||||
const tags =
|
const tags =
|
||||||
item.tags
|
item.tags
|
||||||
?.slice(0, 3)
|
?.slice(0, 3)
|
||||||
?.map(item => {
|
?.map((item) => {
|
||||||
let tag = db.tags.tag(item);
|
let tag = db.tags.tag(item);
|
||||||
|
|
||||||
if (!tag) return null;
|
if (!tag) return null;
|
||||||
@@ -45,8 +46,17 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
|
|||||||
alias: tag.alias
|
alias: tag.alias
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(t => t !== null) || [];
|
.filter((t) => t !== null) || [];
|
||||||
return <Item item={item} tags={tags} dateBy={dateBy} index={index} type={type} {...restArgs} />;
|
return (
|
||||||
|
<Item
|
||||||
|
item={item}
|
||||||
|
tags={tags}
|
||||||
|
dateBy={dateBy}
|
||||||
|
index={index}
|
||||||
|
type={type}
|
||||||
|
{...restArgs}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const List = ({
|
const List = ({
|
||||||
@@ -56,14 +66,14 @@ const List = ({
|
|||||||
placeholderData,
|
placeholderData,
|
||||||
loading,
|
loading,
|
||||||
headerProps = {
|
headerProps = {
|
||||||
heading: 'Home',
|
heading: "Home",
|
||||||
color: null
|
color: null
|
||||||
},
|
},
|
||||||
screen,
|
screen,
|
||||||
ListHeader,
|
ListHeader,
|
||||||
warning
|
warning
|
||||||
}) => {
|
}) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const scrollRef = useRef();
|
const scrollRef = useRef();
|
||||||
|
|
||||||
const renderItem = React.useCallback(
|
const renderItem = React.useCallback(
|
||||||
@@ -73,7 +83,7 @@ const List = ({
|
|||||||
index={index}
|
index={index}
|
||||||
color={headerProps.color}
|
color={headerProps.color}
|
||||||
title={headerProps.heading}
|
title={headerProps.heading}
|
||||||
type={screen === 'Notes' ? 'home' : type}
|
type={screen === "Notes" ? "home" : type}
|
||||||
screen={screen}
|
screen={screen}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -81,7 +91,7 @@ const List = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const _onRefresh = async () => {
|
const _onRefresh = async () => {
|
||||||
Sync.run('global', false, true, () => {
|
Sync.run("global", false, true, () => {
|
||||||
if (refreshCallback) {
|
if (refreshCallback) {
|
||||||
refreshCallback();
|
refreshCallback();
|
||||||
}
|
}
|
||||||
@@ -89,7 +99,7 @@ const List = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _onScroll = React.useCallback(
|
const _onScroll = React.useCallback(
|
||||||
event => {
|
(event) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
let y = event.nativeEvent.contentOffset.y;
|
let y = event.nativeEvent.contentOffset.y;
|
||||||
eSendEvent(eScrollEvent, {
|
eSendEvent(eScrollEvent, {
|
||||||
@@ -101,12 +111,12 @@ const List = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
let styles = {
|
let styles = {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
minHeight: 1,
|
minHeight: 1,
|
||||||
minWidth: 1
|
minWidth: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const _keyExtractor = item => item.id || item.title;
|
const _keyExtractor = (item) => item.id || item.title;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -114,7 +124,7 @@ const List = ({
|
|||||||
style={{
|
style={{
|
||||||
flex: 1
|
flex: 1
|
||||||
}}
|
}}
|
||||||
entering={type === 'search' ? undefined : FadeInDown}
|
entering={type === "search" ? undefined : FadeInDown}
|
||||||
>
|
>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={styles}
|
style={styles}
|
||||||
@@ -171,7 +181,7 @@ const List = ({
|
|||||||
<JumpToSectionDialog
|
<JumpToSectionDialog
|
||||||
screen={screen}
|
screen={screen}
|
||||||
data={listData}
|
data={listData}
|
||||||
type={screen === 'Notes' ? 'home' : type}
|
type={screen === "Notes" ? "home" : type}
|
||||||
scrollRef={scrollRef}
|
scrollRef={scrollRef}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import KeepAwake from '@sayem314/react-native-keep-awake';
|
import KeepAwake from "@sayem314/react-native-keep-awake";
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Modal, SafeAreaView, Text, View } from 'react-native';
|
import { Modal, SafeAreaView, Text, View } from "react-native";
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated from "react-native-reanimated";
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import Editor from '../../screens/editor';
|
import Editor from "../../screens/editor";
|
||||||
import { editorController } from '../../screens/editor/tiptap/utils';
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import Navigation from '../../services/navigation';
|
eSendEvent,
|
||||||
import Sync from '../../services/sync';
|
eSubscribeEvent,
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
eUnSubscribeEvent
|
||||||
import { dHeight } from '../../utils';
|
} from "../../services/event-manager";
|
||||||
import { db } from '../../common/database';
|
import Navigation from "../../services/navigation";
|
||||||
import { eOnLoadNote, eShowMergeDialog } from '../../utils/events';
|
import Sync from "../../services/sync";
|
||||||
import { SIZE } from '../../utils/size';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { timeConverter } from '../../utils/time';
|
import { dHeight } from "../../utils";
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
import { db } from "../../common/database";
|
||||||
import DialogButtons from '../dialog/dialog-buttons';
|
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||||
import DialogContainer from '../dialog/dialog-container';
|
import { SIZE } from "../../utils/size";
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
import { timeConverter } from "../../utils/time";
|
||||||
import { Button } from '../ui/button';
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import DialogButtons from "../dialog/dialog-buttons";
|
||||||
import Seperator from '../ui/seperator';
|
import DialogContainer from "../dialog/dialog-container";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
import Seperator from "../ui/seperator";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
const MergeConflicts = () => {
|
const MergeConflicts = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [keep, setKeep] = useState(null);
|
const [keep, setKeep] = useState(null);
|
||||||
const [copy, setCopy] = useState(null);
|
const [copy, setCopy] = useState(null);
|
||||||
@@ -55,7 +59,7 @@ const MergeConflicts = () => {
|
|||||||
|
|
||||||
if (copy) {
|
if (copy) {
|
||||||
await db.notes.add({
|
await db.notes.add({
|
||||||
title: note.title + ' (Copy)',
|
title: note.title + " (Copy)",
|
||||||
content: {
|
content: {
|
||||||
data: copy.data,
|
data: copy.data,
|
||||||
type: copy.type
|
type: copy.type
|
||||||
@@ -63,21 +67,24 @@ const MergeConflicts = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Notes',
|
"Notes",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'TopicNotes'
|
"TopicNotes"
|
||||||
);
|
);
|
||||||
if (editorController.current?.note?.id === note.id) {
|
if (editorController.current?.note?.id === note.id) {
|
||||||
// reload the note in editor
|
// reload the note in editor
|
||||||
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
|
eSendEvent(eOnLoadNote, {
|
||||||
|
...editorController.current?.note,
|
||||||
|
forced: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
Sync.run();
|
Sync.run();
|
||||||
};
|
};
|
||||||
|
|
||||||
const show = async item => {
|
const show = async (item) => {
|
||||||
let noteContent = await db.content.raw(item.contentId);
|
let noteContent = await db.content.raw(item.contentId);
|
||||||
content.current = { ...noteContent };
|
content.current = { ...noteContent };
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@@ -102,42 +109,59 @@ const MergeConflicts = () => {
|
|||||||
setDialogVisible(false);
|
setDialogVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConfigBar = ({ isDiscarded, keeping, back, isCurrent, contentToKeep }) => {
|
const ConfigBar = ({
|
||||||
|
isDiscarded,
|
||||||
|
keeping,
|
||||||
|
back,
|
||||||
|
isCurrent,
|
||||||
|
contentToKeep
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 50,
|
height: 50,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingLeft: 6
|
paddingLeft: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{back && <IconButton onPress={close} color={colors.pri} name="arrow-left" />}
|
{back && (
|
||||||
<Paragraph style={{ flexWrap: 'wrap' }} color={colors.icon} size={SIZE.xs}>
|
<IconButton onPress={close} color={colors.pri} name="arrow-left" />
|
||||||
<Text style={{ color: isCurrent ? colors.accent : colors.red, fontWeight: 'bold' }}>
|
)}
|
||||||
{isCurrent ? '(This Device)' : '(Incoming)'}
|
<Paragraph
|
||||||
|
style={{ flexWrap: "wrap" }}
|
||||||
|
color={colors.icon}
|
||||||
|
size={SIZE.xs}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: isCurrent ? colors.accent : colors.red,
|
||||||
|
fontWeight: "bold"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isCurrent ? "(This Device)" : "(Incoming)"}
|
||||||
</Text>
|
</Text>
|
||||||
{'\n'}
|
{"\n"}
|
||||||
{timeConverter(contentToKeep?.dateEdited)}
|
{timeConverter(contentToKeep?.dateEdited)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'flex-end'
|
justifyContent: "flex-end"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isDiscarded ? (
|
{isDiscarded ? (
|
||||||
@@ -187,7 +211,7 @@ const MergeConflicts = () => {
|
|||||||
}}
|
}}
|
||||||
type="accent"
|
type="accent"
|
||||||
fontSize={SIZE.xs}
|
fontSize={SIZE.xs}
|
||||||
title={keeping && !isDiscarded ? 'Undo' : 'Keep'}
|
title={keeping && !isDiscarded ? "Undo" : "Keep"}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setKeep(keeping && !isDiscarded ? null : contentToKeep);
|
setKeep(keeping && !isDiscarded ? null : contentToKeep);
|
||||||
}}
|
}}
|
||||||
@@ -208,11 +232,11 @@ const MergeConflicts = () => {
|
|||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
supportedOrientations={[
|
supportedOrientations={[
|
||||||
'portrait',
|
"portrait",
|
||||||
'portrait-upside-down',
|
"portrait-upside-down",
|
||||||
'landscape',
|
"landscape",
|
||||||
'landscape-left',
|
"landscape-left",
|
||||||
'landscape-right'
|
"landscape-right"
|
||||||
]}
|
]}
|
||||||
visible={true}
|
visible={true}
|
||||||
>
|
>
|
||||||
@@ -244,9 +268,9 @@ const MergeConflicts = () => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: "100%",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
backgroundColor: DDS.isLargeTablet() ? 'rgba(0,0,0,0.3)' : null
|
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ConfigBar
|
<ConfigBar
|
||||||
@@ -273,7 +297,7 @@ const MergeConflicts = () => {
|
|||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
const note = db.notes.note(content.current?.noteId)?.data;
|
const note = db.notes.note(content.current?.noteId)?.data;
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
eSendEvent(eOnLoadNote + ':conflictPrimary', {
|
eSendEvent(eOnLoadNote + ":conflictPrimary", {
|
||||||
...note,
|
...note,
|
||||||
content: {
|
content: {
|
||||||
...content.current,
|
...content.current,
|
||||||
@@ -307,7 +331,7 @@ const MergeConflicts = () => {
|
|||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
const note = db.notes.note(content.current?.noteId)?.data;
|
const note = db.notes.note(content.current?.noteId)?.data;
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
eSendEvent(eOnLoadNote + ':conflictSecondary', {
|
eSendEvent(eOnLoadNote + ":conflictSecondary", {
|
||||||
...note,
|
...note,
|
||||||
content: { ...content.current.conflicted, isPreview: true }
|
content: { ...content.current.conflicted, isPreview: true }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from "react-native";
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from "react-native-gesture-handler";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { presentSheet } from '../../services/event-manager';
|
import { presentSheet } from "../../services/event-manager";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { openLinkInBrowser } from '../../utils/functions';
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { timeConverter, timeSince } from '../../utils/time';
|
import { timeConverter, timeSince } from "../../utils/time";
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { PressableButton } from '../ui/pressable';
|
import { PressableButton } from "../ui/pressable";
|
||||||
import Seperator from '../ui/seperator';
|
import Seperator from "../ui/seperator";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import NotePreview from './preview';
|
import NotePreview from "./preview";
|
||||||
|
|
||||||
export default function NoteHistory({ note, fwdRef }) {
|
export default function NoteHistory({ note, fwdRef }) {
|
||||||
const [history, setHistory] = useState([]);
|
const [history, setHistory] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -40,7 +40,7 @@ export default function NoteHistory({ note, fwdRef }) {
|
|||||||
content={content}
|
content={content}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
context: 'note_history'
|
context: "note_history"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +48,12 @@ export default function NoteHistory({ note, fwdRef }) {
|
|||||||
let _start = timeConverter(start);
|
let _start = timeConverter(start);
|
||||||
let _end = timeConverter(end + 60000);
|
let _end = timeConverter(end + 60000);
|
||||||
if (_start === _end) return _start;
|
if (_start === _end) return _start;
|
||||||
let final = _end.lastIndexOf(',');
|
let final = _end.lastIndexOf(",");
|
||||||
let part = _end.slice(0, final + 1);
|
let part = _end.slice(0, final + 1);
|
||||||
if (_start.includes(part)) {
|
if (_start.includes(part)) {
|
||||||
return _start + ' —' + _end.replace(part, '');
|
return _start + " —" + _end.replace(part, "");
|
||||||
}
|
}
|
||||||
return _start + ' — ' + _end;
|
return _start + " — " + _end;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
@@ -62,12 +62,12 @@ export default function NoteHistory({ note, fwdRef }) {
|
|||||||
type="grayBg"
|
type="grayBg"
|
||||||
onPress={() => preview(item)}
|
onPress={() => preview(item)}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
height: 45,
|
height: 45,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
flexDirection: 'row'
|
flexDirection: "row"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
|
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
|
||||||
@@ -97,19 +97,21 @@ export default function NoteHistory({ note, fwdRef }) {
|
|||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
data={history}
|
data={history}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 200
|
height: 200
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="history" size={60} color={colors.icon} />
|
<Icon name="history" size={60} color={colors.icon} />
|
||||||
<Paragraph color={colors.icon}>No note history found on this device.</Paragraph>
|
<Paragraph color={colors.icon}>
|
||||||
|
No note history found on this device.
|
||||||
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
@@ -118,15 +120,18 @@ export default function NoteHistory({ note, fwdRef }) {
|
|||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center'
|
alignSelf: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Note version history is local only.{' '}
|
Note version history is local only.{" "}
|
||||||
<Text
|
<Text
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
openLinkInBrowser('https://docs.notesnook.com/versionhistory', colors);
|
openLinkInBrowser(
|
||||||
|
"https://docs.notesnook.com/versionhistory",
|
||||||
|
colors
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
style={{ color: colors.accent, textDecorationLine: 'underline' }}
|
style={{ color: colors.accent, textDecorationLine: "underline" }}
|
||||||
>
|
>
|
||||||
Learn how this works.
|
Learn how this works.
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Editor from '../../screens/editor';
|
import Editor from "../../screens/editor";
|
||||||
import EditorOverlay from '../../screens/editor/loading';
|
import EditorOverlay from "../../screens/editor/loading";
|
||||||
import { editorController } from '../../screens/editor/tiptap/utils';
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
import { eSendEvent, ToastEvent } from '../../services/event-manager';
|
import { eSendEvent, ToastEvent } from "../../services/event-manager";
|
||||||
import Navigation from '../../services/navigation';
|
import Navigation from "../../services/navigation";
|
||||||
import { useEditorStore } from '../../stores/use-editor-store';
|
import { useEditorStore } from "../../stores/use-editor-store";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import { eCloseProgressDialog, eOnLoadNote } from '../../utils/events';
|
import { eCloseProgressDialog, eOnLoadNote } from "../../utils/events";
|
||||||
import DialogHeader from '../dialog/dialog-header';
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export default function NotePreview({ session, content }) {
|
export default function NotePreview({ session, content }) {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const editorId = ':noteHistory';
|
const editorId = ":noteHistory";
|
||||||
|
|
||||||
async function restore() {
|
async function restore() {
|
||||||
await db.noteHistory.restore(session.id);
|
await db.noteHistory.restore(session.id);
|
||||||
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
|
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
|
||||||
if (editorController.current?.note) {
|
if (editorController.current?.note) {
|
||||||
eSendEvent(eOnLoadNote, { ...editorController.current?.note, forced: true });
|
eSendEvent(eOnLoadNote, {
|
||||||
|
...editorController.current?.note,
|
||||||
|
forced: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eSendEvent(eCloseProgressDialog, 'note_history');
|
eSendEvent(eCloseProgressDialog, "note_history");
|
||||||
eSendEvent(eCloseProgressDialog);
|
eSendEvent(eCloseProgressDialog);
|
||||||
Navigation.queueRoutesForUpdate(
|
Navigation.queueRoutesForUpdate(
|
||||||
'Notes',
|
"Notes",
|
||||||
'Favorites',
|
"Favorites",
|
||||||
'ColoredNotes',
|
"ColoredNotes",
|
||||||
'TaggedNotes',
|
"TaggedNotes",
|
||||||
'TopicNotes'
|
"TopicNotes"
|
||||||
);
|
);
|
||||||
|
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: 'Note restored successfully',
|
heading: "Note restored successfully",
|
||||||
type: 'success'
|
type: "success"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ export default function NotePreview({ session, content }) {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: session.locked ? null : 600,
|
height: session.locked ? null : 600,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogHeader padding={12} title={session.session} />
|
<DialogHeader padding={12} title={session.session} />
|
||||||
@@ -71,13 +74,15 @@ export default function NotePreview({ session, content }) {
|
|||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: 100,
|
height: 100,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph color={colors.icon}>Preview not available, content is encrypted.</Paragraph>
|
<Paragraph color={colors.icon}>
|
||||||
|
Preview not available, content is encrypted.
|
||||||
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -86,7 +91,12 @@ export default function NotePreview({ session, content }) {
|
|||||||
paddingHorizontal: 12
|
paddingHorizontal: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button onPress={restore} title="Restore this version" type="accent" width="100%" />
|
<Button
|
||||||
|
onPress={restore}
|
||||||
|
title="Restore this version"
|
||||||
|
type="accent"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,50 +1,55 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from "react-native";
|
||||||
import { FeatureBlock } from './feature';
|
import { FeatureBlock } from "./feature";
|
||||||
|
|
||||||
export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scrollRef }) => {
|
export const CompactFeatures = ({
|
||||||
|
vertical,
|
||||||
|
features = [],
|
||||||
|
maxHeight = 500,
|
||||||
|
scrollRef
|
||||||
|
}) => {
|
||||||
let data = vertical
|
let data = vertical
|
||||||
? features
|
? features
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
highlight: 'Everything',
|
highlight: "Everything",
|
||||||
content: 'in basic',
|
content: "in basic",
|
||||||
icon: 'emoticon-wink'
|
icon: "emoticon-wink"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Unlimited',
|
highlight: "Unlimited",
|
||||||
content: 'notebooks',
|
content: "notebooks",
|
||||||
icon: 'notebook'
|
icon: "notebook"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'File & image',
|
highlight: "File & image",
|
||||||
content: 'attachments',
|
content: "attachments",
|
||||||
icon: 'attachment'
|
icon: "attachment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Instant',
|
highlight: "Instant",
|
||||||
content: 'syncing',
|
content: "syncing",
|
||||||
icon: 'sync'
|
icon: "sync"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Private',
|
highlight: "Private",
|
||||||
content: 'vault',
|
content: "vault",
|
||||||
icon: 'shield'
|
icon: "shield"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Rich text',
|
highlight: "Rich text",
|
||||||
content: 'editing',
|
content: "editing",
|
||||||
icon: 'square-edit-outline'
|
icon: "square-edit-outline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'PDF & markdown',
|
highlight: "PDF & markdown",
|
||||||
content: 'exports',
|
content: "exports",
|
||||||
icon: 'file'
|
icon: "file"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Encrypted',
|
highlight: "Encrypted",
|
||||||
content: 'backups',
|
content: "backups",
|
||||||
icon: 'backup-restore'
|
icon: "backup-restore"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -57,11 +62,11 @@ export const CompactFeatures = ({ vertical, features = [], maxHeight = 500, scro
|
|||||||
}}
|
}}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
maxHeight: maxHeight
|
maxHeight: maxHeight
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.map(item => (
|
{data.map((item) => (
|
||||||
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
|
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,62 +1,64 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { ActivityIndicator, ScrollView, View } from 'react-native';
|
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||||
import { LAUNCH_ROCKET } from '../../assets/images/assets';
|
import { LAUNCH_ROCKET } from "../../assets/images/assets";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { useUserStore } from '../../stores/use-user-store';
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent, presentSheet } from '../../services/event-manager';
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
import PremiumService from '../../services/premium';
|
import PremiumService from "../../services/premium";
|
||||||
import { getElevation } from '../../utils';
|
import { getElevation } from "../../utils";
|
||||||
import { db } from '../../common/database';
|
import { db } from "../../common/database";
|
||||||
import {
|
import {
|
||||||
eClosePremiumDialog,
|
eClosePremiumDialog,
|
||||||
eCloseProgressDialog,
|
eCloseProgressDialog,
|
||||||
eOpenLoginDialog,
|
eOpenLoginDialog,
|
||||||
eOpenResultDialog
|
eOpenResultDialog
|
||||||
} from '../../utils/events';
|
} from "../../utils/events";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from '../../utils/time';
|
import { sleep } from "../../utils/time";
|
||||||
import umami from '../../common/analytics';
|
import umami from "../../common/analytics";
|
||||||
import { IconButton } from '../ui/icon-button';
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { AuthMode } from '../auth';
|
import { AuthMode } from "../auth";
|
||||||
import { Button } from '../ui/button';
|
import { Button } from "../ui/button";
|
||||||
import SheetProvider from '../sheet-provider';
|
import SheetProvider from "../sheet-provider";
|
||||||
import { SvgView } from '../ui/svg';
|
import { SvgView } from "../ui/svg";
|
||||||
import Seperator from '../ui/seperator';
|
import Seperator from "../ui/seperator";
|
||||||
import { Toast } from '../toast';
|
import { Toast } from "../toast";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { Walkthrough } from '../walkthroughs';
|
import { Walkthrough } from "../walkthroughs";
|
||||||
import { features } from './features';
|
import { features } from "./features";
|
||||||
import { Group } from './group';
|
import { Group } from "./group";
|
||||||
import { PricingPlans } from './pricing-plans';
|
import { PricingPlans } from "./pricing-plans";
|
||||||
import { usePricing } from '../../hooks/use-pricing';
|
import { usePricing } from "../../hooks/use-pricing";
|
||||||
|
|
||||||
export const Component = ({ close, promo, getRef }) => {
|
export const Component = ({ close, promo, getRef }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const user = useUserStore(state => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const userCanRequestTrial =
|
const userCanRequestTrial =
|
||||||
user && (!user.subscription || !user.subscription.expiry) ? true : false;
|
user && (!user.subscription || !user.subscription.expiry) ? true : false;
|
||||||
const [floatingButton, setFloatingButton] = useState(false);
|
const [floatingButton, setFloatingButton] = useState(false);
|
||||||
const pricing = usePricing('monthly');
|
const pricing = usePricing("monthly");
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
if (user) {
|
if (user) {
|
||||||
umami.pageView('/pro-plans', `/pro-screen`);
|
umami.pageView("/pro-plans", `/pro-screen`);
|
||||||
presentSheet({
|
presentSheet({
|
||||||
context: 'pricing_plans',
|
context: "pricing_plans",
|
||||||
component: <PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
|
component: (
|
||||||
|
<PricingPlans showTrialOption={false} marginTop={1} promo={promo} />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
close();
|
close();
|
||||||
umami.pageView('/signup', `/pro-screen`);
|
umami.pageView("/signup", `/pro-screen`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
eSendEvent(eOpenLoginDialog, AuthMode.trialSignup);
|
eSendEvent(eOpenLoginDialog, AuthMode.trialSignup);
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScroll = event => {
|
const onScroll = (event) => {
|
||||||
let contentSize = event.nativeEvent.contentSize.height;
|
let contentSize = event.nativeEvent.contentSize.height;
|
||||||
contentSize = contentSize - event.nativeEvent.layoutMeasurement.height;
|
contentSize = contentSize - event.nativeEvent.layoutMeasurement.height;
|
||||||
let yOffset = event.nativeEvent.contentOffset.y;
|
let yOffset = event.nativeEvent.contentOffset.y;
|
||||||
@@ -70,11 +72,11 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
maxHeight: '100%'
|
maxHeight: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SheetProvider context="pricing_plans" />
|
<SheetProvider context="pricing_plans" />
|
||||||
@@ -83,7 +85,7 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
right: DDS.isTab ? 30 : 15,
|
right: DDS.isTab ? 30 : 15,
|
||||||
top: 30,
|
top: 30,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
@@ -106,24 +108,28 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
<View
|
<View
|
||||||
key="top-banner"
|
key="top-banner"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
height: 400,
|
height: 400,
|
||||||
justifyContent: 'center'
|
justifyContent: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgView width={350} height={350} src={LAUNCH_ROCKET(colors.accent)} />
|
<SvgView
|
||||||
|
width={350}
|
||||||
|
height={350}
|
||||||
|
src={LAUNCH_ROCKET(colors.accent)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
key="heading"
|
key="heading"
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
paddingTop: 20
|
paddingTop: 20
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Notesnook{' '}
|
Notesnook{" "}
|
||||||
<Heading size={SIZE.lg} color={colors.accent}>
|
<Heading size={SIZE.lg} color={colors.accent}>
|
||||||
Pro
|
Pro
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -140,7 +146,7 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
) : (
|
) : (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
marginBottom: 20
|
marginBottom: 20
|
||||||
}}
|
}}
|
||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
@@ -154,10 +160,10 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
paddingBottom: 20,
|
paddingBottom: 20,
|
||||||
width: '90%'
|
width: "90%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Ready to take the next step on your private note taking journey?
|
Ready to take the next step on your private note taking journey?
|
||||||
@@ -172,7 +178,7 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
eSendEvent(eClosePremiumDialog);
|
eSendEvent(eClosePremiumDialog);
|
||||||
eSendEvent(eCloseProgressDialog);
|
eSendEvent(eCloseProgressDialog);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
Walkthrough.present('trialstarted', false, true);
|
Walkthrough.present("trialstarted", false, true);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}}
|
}}
|
||||||
title="Try free for 14 days"
|
title="Try free for 14 days"
|
||||||
@@ -189,8 +195,10 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
<Button
|
<Button
|
||||||
key="calltoaction"
|
key="calltoaction"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
title={promo ? promo.text : user ? `See all plans` : `Sign up for free`}
|
title={
|
||||||
type={userCanRequestTrial ? 'grayAccent' : 'accent'}
|
promo ? promo.text : user ? `See all plans` : `Sign up for free`
|
||||||
|
}
|
||||||
|
type={userCanRequestTrial ? "grayAccent" : "accent"}
|
||||||
width={250}
|
width={250}
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
@@ -204,16 +212,16 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
color={colors.icon}
|
color={colors.icon}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
maxWidth: '80%'
|
maxWidth: "80%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user
|
{user
|
||||||
? `On clicking "Try free for 14 days", your free trial will be activated.`
|
? `On clicking "Try free for 14 days", your free trial will be activated.`
|
||||||
: `After sign up you will be asked to activate your free trial.`}{' '}
|
: `After sign up you will be asked to activate your free trial.`}{" "}
|
||||||
<Paragraph size={SIZE.xs} style={{ fontWeight: 'bold' }}>
|
<Paragraph size={SIZE.xs} style={{ fontWeight: "bold" }}>
|
||||||
No credit card is required.
|
No credit card is required.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -238,11 +246,13 @@ export const Component = ({ close, promo, getRef }) => {
|
|||||||
{floatingButton ? (
|
{floatingButton ? (
|
||||||
<Button
|
<Button
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
title={promo ? promo.text : user ? `See all plans` : 'Sign up for free'}
|
title={
|
||||||
|
promo ? promo.text : user ? `See all plans` : "Sign up for free"
|
||||||
|
}
|
||||||
type="accent"
|
type="accent"
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
bottom: 30,
|
bottom: 30,
|
||||||
...getElevation(10)
|
...getElevation(10)
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import PremiumService from '../../services/premium';
|
eSendEvent,
|
||||||
import { eOpenPremiumDialog, eOpenResultDialog, eOpenTrialEndingDialog } from '../../utils/events';
|
eSubscribeEvent,
|
||||||
import { SIZE } from '../../utils/size';
|
eUnSubscribeEvent
|
||||||
import { sleep } from '../../utils/time';
|
} from "../../services/event-manager";
|
||||||
import { Button } from '../ui/button';
|
import PremiumService from "../../services/premium";
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
import {
|
||||||
import DialogContainer from '../dialog/dialog-container';
|
eOpenPremiumDialog,
|
||||||
import Seperator from '../ui/seperator';
|
eOpenResultDialog,
|
||||||
import Heading from '../ui/typography/heading';
|
eOpenTrialEndingDialog
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
} from "../../utils/events";
|
||||||
import { CompactFeatures } from './compact-features';
|
import { SIZE } from "../../utils/size";
|
||||||
import { Offer } from './offer';
|
import { sleep } from "../../utils/time";
|
||||||
import { usePricing } from '../../hooks/use-pricing';
|
import { Button } from "../ui/button";
|
||||||
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
|
import DialogContainer from "../dialog/dialog-container";
|
||||||
|
import Seperator from "../ui/seperator";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { CompactFeatures } from "./compact-features";
|
||||||
|
import { Offer } from "./offer";
|
||||||
|
import { usePricing } from "../../hooks/use-pricing";
|
||||||
|
|
||||||
export const Expiring = () => {
|
export const Expiring = () => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [status, setStatus] = useState({
|
const [status, setStatus] = useState({
|
||||||
title: 'Your trial is ending soon',
|
title: "Your trial is ending soon",
|
||||||
offer: null,
|
offer: null,
|
||||||
extend: true
|
extend: true
|
||||||
});
|
});
|
||||||
const pricing = usePricing('yearly');
|
const pricing = usePricing("yearly");
|
||||||
console.log(pricing?.info?.discount);
|
console.log(pricing?.info?.discount);
|
||||||
|
|
||||||
const promo = status.offer
|
const promo = status.offer
|
||||||
@@ -32,8 +40,10 @@ export const Expiring = () => {
|
|||||||
promoCode:
|
promoCode:
|
||||||
pricing?.info?.discount > 30
|
pricing?.info?.discount > 30
|
||||||
? pricing.info.sku
|
? pricing.info.sku
|
||||||
: 'com.streetwriters.notesnook.sub.yr.trialoffer',
|
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
||||||
text: `GET ${pricing?.info?.discount > 30 ? pricing?.info?.discount : 30}% OFF on yearly`,
|
text: `GET ${
|
||||||
|
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||||
|
}% OFF on yearly`,
|
||||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
@@ -45,7 +55,7 @@ export const Expiring = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = status => {
|
const open = (status) => {
|
||||||
setStatus(status);
|
setStatus(status);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
@@ -60,20 +70,20 @@ export const Expiring = () => {
|
|||||||
<DialogContainer>
|
<DialogContainer>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
width: '100%'
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
textBreakStrategy="balanced"
|
textBreakStrategy="balanced"
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
paddingTop: 18
|
paddingTop: 18
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -82,8 +92,8 @@ export const Expiring = () => {
|
|||||||
<Seperator />
|
<Seperator />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
alignItems: 'center'
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{status.offer ? (
|
{status.offer ? (
|
||||||
@@ -95,13 +105,14 @@ export const Expiring = () => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
textBreakStrategy="balanced"
|
textBreakStrategy="balanced"
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
paddingBottom: 20
|
paddingBottom: 20
|
||||||
}}
|
}}
|
||||||
size={SIZE.md + 2}
|
size={SIZE.md + 2}
|
||||||
>
|
>
|
||||||
Upgrade now to continue using all the pro features after your trial ends
|
Upgrade now to continue using all the pro features after
|
||||||
|
your trial ends
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -116,7 +127,7 @@ export const Expiring = () => {
|
|||||||
}}
|
}}
|
||||||
size={SIZE.xs + 1}
|
size={SIZE.xs + 1}
|
||||||
style={{
|
style={{
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: "underline",
|
||||||
color: colors.icon,
|
color: colors.icon,
|
||||||
marginTop: 10
|
marginTop: 10
|
||||||
}}
|
}}
|
||||||
@@ -131,7 +142,7 @@ export const Expiring = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
borderBottomRightRadius: 10,
|
borderBottomRightRadius: 10,
|
||||||
borderBottomLeftRadius: 10
|
borderBottomLeftRadius: 10
|
||||||
}}
|
}}
|
||||||
@@ -142,7 +153,10 @@ export const Expiring = () => {
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
PremiumService.sheet(null, promo?.discount > 30 ? null : promo);
|
PremiumService.sheet(
|
||||||
|
null,
|
||||||
|
promo?.discount > 30 ? null : promo
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
fontSize={SIZE.md + 2}
|
fontSize={SIZE.md + 2}
|
||||||
style={{
|
style={{
|
||||||
@@ -157,16 +171,16 @@ export const Expiring = () => {
|
|||||||
type="gray"
|
type="gray"
|
||||||
title="Not sure yet? Extend trial for 7 days"
|
title="Not sure yet? Extend trial for 7 days"
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: 'underline'
|
textDecorationLine: "underline"
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
eSendEvent(eOpenResultDialog, {
|
eSendEvent(eOpenResultDialog, {
|
||||||
title: 'Your trial has been extended',
|
title: "Your trial has been extended",
|
||||||
paragraph:
|
paragraph:
|
||||||
'Try out all features of Notesnook free for 7 more days. No limitations. No commitments.',
|
"Try out all features of Notesnook free for 7 more days. No limitations. No commitments.",
|
||||||
button: 'Continue'
|
button: "Continue"
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
fontSize={SIZE.xs}
|
fontSize={SIZE.xs}
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from "react-native";
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { ProTag } from './pro-tag';
|
import { ProTag } from "./pro-tag";
|
||||||
|
|
||||||
export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg }) => {
|
export const FeatureBlock = ({
|
||||||
const colors = useThemeStore(state => state.colors);
|
vertical,
|
||||||
|
highlight,
|
||||||
|
content,
|
||||||
|
icon,
|
||||||
|
pro,
|
||||||
|
proTagBg
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return vertical ? (
|
return vertical ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginBottom: 10
|
marginBottom: 10
|
||||||
}}
|
}}
|
||||||
@@ -22,7 +29,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
|||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
@@ -35,7 +42,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 100,
|
height: 100,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
@@ -45,7 +52,7 @@ export const FeatureBlock = ({ vertical, highlight, content, icon, pro, proTagBg
|
|||||||
<Icon color={colors.icon} name={icon} size={SIZE.xl} />
|
<Icon color={colors.icon} name={icon} size={SIZE.xl} />
|
||||||
<Paragraph size={SIZE.md}>
|
<Paragraph size={SIZE.md}>
|
||||||
<Text style={{ color: colors.accent }}>{highlight}</Text>
|
<Text style={{ color: colors.accent }}>{highlight}</Text>
|
||||||
{'\n'}
|
{"\n"}
|
||||||
{content}
|
{content}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
|
|||||||
@@ -1,224 +1,227 @@
|
|||||||
export const features = [
|
export const features = [
|
||||||
{
|
{
|
||||||
title: 'Focused on privacy',
|
title: "Focused on privacy",
|
||||||
detail:
|
detail:
|
||||||
'Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.',
|
"Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Zero ads',
|
highlight: "Zero ads",
|
||||||
content: '& zero trackers',
|
content: "& zero trackers",
|
||||||
icon: 'billboard'
|
icon: "billboard"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'On device',
|
highlight: "On device",
|
||||||
content: 'encryption',
|
content: "encryption",
|
||||||
icon: 'cellphone'
|
icon: "cellphone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Secure app',
|
highlight: "Secure app",
|
||||||
content: 'lock for all',
|
content: "lock for all",
|
||||||
icon: 'cellphone-lock'
|
icon: "cellphone-lock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: '100% end-to-end ',
|
highlight: "100% end-to-end ",
|
||||||
content: 'encrypted',
|
content: "encrypted",
|
||||||
icon: 'lock'
|
icon: "lock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Password protected',
|
highlight: "Password protected",
|
||||||
content: 'notes sharing',
|
content: "notes sharing",
|
||||||
icon: 'file-lock'
|
icon: "file-lock"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'No limit on notes or devices',
|
title: "No limit on notes or devices",
|
||||||
detail:
|
detail:
|
||||||
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
|
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Attach files & images',
|
title: "Attach files & images",
|
||||||
detail: 'Add your documents, PDFs, images and videos, and keep them safe and organized.',
|
detail:
|
||||||
|
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
|
||||||
pro: true,
|
pro: true,
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Bullet proof',
|
highlight: "Bullet proof",
|
||||||
content: 'encryption',
|
content: "encryption",
|
||||||
icon: 'lock'
|
icon: "lock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'High quality',
|
highlight: "High quality",
|
||||||
content: '4k images',
|
content: "4k images",
|
||||||
icon: 'image-multiple'
|
icon: "image-multiple"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'No monthly',
|
highlight: "No monthly",
|
||||||
content: 'storage limit',
|
content: "storage limit",
|
||||||
icon: 'harddisk'
|
icon: "harddisk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Generous 500 MB',
|
highlight: "Generous 500 MB",
|
||||||
content: 'max file size',
|
content: "max file size",
|
||||||
icon: 'file-cabinet'
|
icon: "file-cabinet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'No restriction',
|
highlight: "No restriction",
|
||||||
content: 'on file type',
|
content: "on file type",
|
||||||
icon: 'file'
|
icon: "file"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Keep secrets always locked with private vault',
|
title: "Keep secrets always locked with private vault",
|
||||||
detail:
|
detail:
|
||||||
'An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.',
|
"An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Organize yourself in the best way',
|
title: "Organize yourself in the best way",
|
||||||
detail: 'We offer multiple ways to keep you organized. The only limit is your imagination.',
|
detail:
|
||||||
|
"We offer multiple ways to keep you organized. The only limit is your imagination.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Unlimited',
|
highlight: "Unlimited",
|
||||||
content: 'notebooks & tags*',
|
content: "notebooks & tags*",
|
||||||
icon: 'emoticon',
|
icon: "emoticon",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Organize',
|
highlight: "Organize",
|
||||||
content: 'with colors',
|
content: "with colors",
|
||||||
icon: 'palette',
|
icon: "palette",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Side menu',
|
highlight: "Side menu",
|
||||||
content: 'shortcuts',
|
content: "shortcuts",
|
||||||
icon: 'link-variant'
|
icon: "link-variant"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Pin note in',
|
highlight: "Pin note in",
|
||||||
content: 'notifications',
|
content: "notifications",
|
||||||
icon: 'pin',
|
icon: "pin",
|
||||||
platform: 'android'
|
platform: "android"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
info: '* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags.'
|
info: "* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'Instant syncing',
|
title: "Instant syncing",
|
||||||
detail:
|
detail:
|
||||||
'Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.',
|
"Seemlessly work from anywhere on any device. Every change is synced instantly to all your devices.",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Rich tools for rich editing',
|
title: "Rich tools for rich editing",
|
||||||
detail:
|
detail:
|
||||||
'Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.',
|
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Basic formating',
|
highlight: "Basic formating",
|
||||||
content: 'and lists',
|
content: "and lists",
|
||||||
icon: 'format-bold'
|
icon: "format-bold"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Checklists',
|
highlight: "Checklists",
|
||||||
content: '& tables',
|
content: "& tables",
|
||||||
icon: 'table',
|
icon: "table",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Markdown',
|
highlight: "Markdown",
|
||||||
content: 'support',
|
content: "support",
|
||||||
icon: 'language-markdown',
|
icon: "language-markdown",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Write notes from',
|
highlight: "Write notes from",
|
||||||
content: 'notifications',
|
content: "notifications",
|
||||||
icon: 'bell',
|
icon: "bell",
|
||||||
platform: 'android'
|
platform: "android"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Safe publishing to the Internet',
|
title: "Safe publishing to the Internet",
|
||||||
detail:
|
detail:
|
||||||
'Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.',
|
"Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Password protected',
|
highlight: "Password protected",
|
||||||
content: 'sharing',
|
content: "sharing",
|
||||||
icon: 'send-lock'
|
icon: "send-lock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Self destruct',
|
highlight: "Self destruct",
|
||||||
content: 'monographs',
|
content: "monographs",
|
||||||
icon: 'bomb'
|
icon: "bomb"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Export and take your notes anywhere',
|
title: "Export and take your notes anywhere",
|
||||||
pro: true,
|
pro: true,
|
||||||
detail:
|
detail:
|
||||||
'You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.',
|
"You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.",
|
||||||
info: '* Free users can export notes in well formatted plain text.',
|
info: "* Free users can export notes in well formatted plain text.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Export as ',
|
highlight: "Export as ",
|
||||||
content: 'Markdown',
|
content: "Markdown",
|
||||||
icon: 'language-markdown',
|
icon: "language-markdown",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Export as',
|
highlight: "Export as",
|
||||||
content: 'PDF',
|
content: "PDF",
|
||||||
icon: 'file-pdf-box',
|
icon: "file-pdf-box",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Export as',
|
highlight: "Export as",
|
||||||
content: 'HTML',
|
content: "HTML",
|
||||||
icon: 'language-html5',
|
icon: "language-html5",
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Export as',
|
highlight: "Export as",
|
||||||
content: 'text',
|
content: "text",
|
||||||
icon: 'clipboard-text-outline'
|
icon: "clipboard-text-outline"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Backup & keep your notes safe',
|
title: "Backup & keep your notes safe",
|
||||||
detail:
|
detail:
|
||||||
'Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.',
|
"Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.",
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Backup',
|
highlight: "Backup",
|
||||||
content: 'encryption',
|
content: "encryption",
|
||||||
icon: 'backup-restore'
|
icon: "backup-restore"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
pro: true
|
pro: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Personalize & make Notesnook your own',
|
title: "Personalize & make Notesnook your own",
|
||||||
detail: 'Change app themes to match your style. Custom themes are coming soon.',
|
detail:
|
||||||
|
"Change app themes to match your style. Custom themes are coming soon.",
|
||||||
|
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
highlight: 'Automatic',
|
highlight: "Automatic",
|
||||||
content: 'dark mode',
|
content: "dark mode",
|
||||||
icon: 'theme-light-dark',
|
icon: "theme-light-dark",
|
||||||
pro: false
|
pro: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'Change accent',
|
highlight: "Change accent",
|
||||||
content: 'color',
|
content: "color",
|
||||||
icon: 'invert-colors',
|
icon: "invert-colors",
|
||||||
pro: true
|
pro: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { ScrollView, View } from 'react-native';
|
import { ScrollView, View } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import Heading from '../ui/typography/heading';
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { FeatureBlock } from './feature';
|
import { FeatureBlock } from "./feature";
|
||||||
import { ProTag } from './pro-tag';
|
import { ProTag } from "./pro-tag";
|
||||||
|
|
||||||
export const Group = ({ item, index }) => {
|
export const Group = ({ item, index }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -19,7 +19,10 @@ export const Group = ({ item, index }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item?.pro ? (
|
{item?.pro ? (
|
||||||
<ProTag size={SIZE.sm} background={index % 2 === 0 ? colors.bg : colors.nav} />
|
<ProTag
|
||||||
|
size={SIZE.sm}
|
||||||
|
background={index % 2 === 0 ? colors.bg : colors.nav}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<Heading>{item.title}</Heading>
|
<Heading>{item.title}</Heading>
|
||||||
<Paragraph size={SIZE.md}>{item.detail}</Paragraph>
|
<Paragraph size={SIZE.md}>{item.detail}</Paragraph>
|
||||||
@@ -32,7 +35,7 @@ export const Group = ({ item, index }) => {
|
|||||||
horizontal
|
horizontal
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
{item.features?.map(item => (
|
{item.features?.map((item) => (
|
||||||
<FeatureBlock
|
<FeatureBlock
|
||||||
key={item.detail}
|
key={item.detail}
|
||||||
{...item}
|
{...item}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import React, { createRef } from 'react';
|
import React, { createRef } from "react";
|
||||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { eClosePremiumDialog, eOpenPremiumDialog } from '../../utils/events';
|
eSubscribeEvent,
|
||||||
import BaseDialog from '../dialog/base-dialog';
|
eUnSubscribeEvent
|
||||||
import { Component } from './component';
|
} from "../../services/event-manager";
|
||||||
|
import { eClosePremiumDialog, eOpenPremiumDialog } from "../../utils/events";
|
||||||
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
|
import { Component } from "./component";
|
||||||
|
|
||||||
class PremiumDialog extends React.Component {
|
class PremiumDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -24,7 +27,7 @@ class PremiumDialog extends React.Component {
|
|||||||
eUnSubscribeEvent(eClosePremiumDialog, this.close);
|
eUnSubscribeEvent(eClosePremiumDialog, this.close);
|
||||||
}
|
}
|
||||||
|
|
||||||
open = promoInfo => {
|
open = (promoInfo) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: true,
|
visible: true,
|
||||||
promo: promoInfo
|
promo: promoInfo
|
||||||
@@ -52,7 +55,11 @@ class PremiumDialog extends React.Component {
|
|||||||
background={this.props.colors.bg}
|
background={this.props.colors.bg}
|
||||||
onRequestClose={this.onClose}
|
onRequestClose={this.onClose}
|
||||||
>
|
>
|
||||||
<Component getRef={() => this.actionSheetRef} promo={this.state.promo} close={this.close} />
|
<Component
|
||||||
|
getRef={() => this.actionSheetRef}
|
||||||
|
promo={this.state.promo}
|
||||||
|
close={this.close}
|
||||||
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Text } from 'react-native';
|
import { Text } from "react-native";
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { SIZE } from '../../utils/size';
|
import { SIZE } from "../../utils/size";
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Offer = ({ off = '30', text = 'on yearly plan, offer ends soon', padding = 0 }) => {
|
export const Offer = ({
|
||||||
const colors = useThemeStore(state => state.colors);
|
off = "30",
|
||||||
|
text = "on yearly plan, offer ends soon",
|
||||||
|
padding = 0
|
||||||
|
}) => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
paddingVertical: padding
|
paddingVertical: padding
|
||||||
}}
|
}}
|
||||||
size={SIZE.xxxl}
|
size={SIZE.xxxl}
|
||||||
>
|
>
|
||||||
GET {off}
|
GET {off}
|
||||||
<Text style={{ color: colors.accent }}>%</Text> OFF!{'\n'}
|
<Text style={{ color: colors.accent }}>%</Text> OFF!{"\n"}
|
||||||
<Paragraph color={colors.icon}>{text}</Paragraph>
|
<Paragraph color={colors.icon}>{text}</Paragraph>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import Animated, { FadeInUp, FadeOutUp } from 'react-native-reanimated';
|
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
import { editorState } from "../../screens/editor/tiptap/utils";
|
||||||
import { DDS } from '../../services/device-detection';
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
import {
|
||||||
import { useThemeStore } from '../../stores/use-theme-store';
|
eSendEvent,
|
||||||
import { getElevation } from '../../utils';
|
eSubscribeEvent,
|
||||||
import { eCloseActionSheet, eOpenPremiumDialog, eShowGetPremium } from '../../utils/events';
|
eUnSubscribeEvent
|
||||||
import { SIZE } from '../../utils/size';
|
} from "../../services/event-manager";
|
||||||
import { sleep } from '../../utils/time';
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import { Button } from '../ui/button';
|
import { getElevation } from "../../utils";
|
||||||
import Heading from '../ui/typography/heading';
|
import {
|
||||||
import Paragraph from '../ui/typography/paragraph';
|
eCloseActionSheet,
|
||||||
|
eOpenPremiumDialog,
|
||||||
|
eShowGetPremium
|
||||||
|
} from "../../utils/events";
|
||||||
|
import { SIZE } from "../../utils/size";
|
||||||
|
import { sleep } from "../../utils/time";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import Heading from "../ui/typography/heading";
|
||||||
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
export const PremiumToast = ({ close, context = "global", offset = 0 }) => {
|
||||||
const colors = useThemeStore(state => state.colors);
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [msg, setMsg] = useState(null);
|
const [msg, setMsg] = useState(null);
|
||||||
const timer = useRef();
|
const timer = useRef();
|
||||||
|
|
||||||
const open = event => {
|
const open = (event) => {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer.current = null;
|
timer.current = null;
|
||||||
@@ -62,17 +70,17 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
|||||||
entering={FadeInUp}
|
entering={FadeInUp}
|
||||||
exiting={FadeOutUp}
|
exiting={FadeOutUp}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
backgroundColor: colors.nav,
|
backgroundColor: colors.nav,
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
...getElevation(20),
|
...getElevation(20),
|
||||||
padding: 12,
|
padding: 12,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
top: offset,
|
top: offset,
|
||||||
maxWidth: DDS.isLargeTablet() ? 400 : '98%'
|
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -84,7 +92,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
|||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
color={colors.accent}
|
color={colors.accent}
|
||||||
size={SIZE.md}
|
size={SIZE.md}
|
||||||
@@ -94,7 +102,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
|||||||
|
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
flexWrap: 'wrap'
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
color={colors.pri}
|
color={colors.pri}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user