Compare commits

..

2 Commits

Author SHA1 Message Date
Abdullah Atta
09fd5d8642 Merge pull request #9002 from streetwriters/web/new-note-properties-pane
web: release space taken by properties pane for new note/tab
2025-12-03 14:01:34 +05:00
01zulfi
f7914c57c7 web: release space taken by properties pane for new note/tab
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-26 16:49:17 +05:00
459 changed files with 43173 additions and 32977 deletions

View File

@@ -5,6 +5,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-22.04
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
@@ -30,23 +31,7 @@ jobs:
npm run bootstrap -- --scope=mobile
- name: Make Gradlew Executable
run: cd apps/mobile/android && chmod +x ./gradlew
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Install CCache
uses: hendrikmuhs/ccache-action@v1.2.11
@@ -94,9 +79,6 @@ jobs:
cd apps/mobile
npx tsc --noEmit
- name: Remove debug keystore
run: rm -rf apps/mobile/android/app/debug.keystore
- name: Build unsigned app bundle
run: yarn release:android:bundle
@@ -107,7 +89,7 @@ jobs:
id: sign_app
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/android/app/build/outputs/bundle/release
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
@@ -122,7 +104,7 @@ jobs:
id: sign_apk
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/android/app/build/outputs/apk/release
releaseDirectory: apps/mobile/native/android/app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.PUBLIC_SIGNING_KEY }}
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
@@ -132,7 +114,7 @@ jobs:
- name: Rename apk files
run: |
cd apps/mobile/android/app/build/outputs/apk/release/
cd apps/mobile/native/android/app/build/outputs/apk/release/
mv app-arm64-v8a-release-unsigned-signed.apk notesnook-arm64-v8a.apk
mv app-armeabi-v7a-release-unsigned-signed.apk notesnook-armeabi-v7a.apk
mv app-x86-release-unsigned-signed.apk notesnook-x86.apk
@@ -140,7 +122,7 @@ jobs:
- name: Get app version
id: package-version
uses: saionaro/extract-package-version@master
uses: martinbeentjes/npm-get-version-action@master
with:
path: apps/mobile
@@ -153,21 +135,21 @@ jobs:
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: beta
status: completed
whatsNewDirectory: apps/mobile/android/releasenotes/
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Create release draft on Github
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ steps.package-version.outputs.version}}-beta-android
name: Notesnook Android v${{ steps.package-version.outputs.version}}
tag_name: ${{ steps.package-version.outputs.current-version}}-beta-android
name: Notesnook Android v${{ steps.package-version.outputs.current-version}}
repository: streetwriters/notesnook
token: ${{ secrets.GITHUB_TOKEN }}
files: |
apps/mobile/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-x86_64.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
${{steps.sign_app.outputs.signedReleaseFile}}
- name: Upload sourcemaps
@@ -175,5 +157,5 @@ jobs:
with:
name: sourcemaps
path: |
apps/mobile/android/app/build/**/*.map
apps/mobile/native/android/app/build/**/*.map
packages/editor-mobile/sourcemaps/*.map

View File

@@ -5,6 +5,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-22.04
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
@@ -30,7 +31,7 @@ jobs:
npm run bootstrap -- --scope=mobile
- name: Make Gradlew Executable
run: cd apps/mobile/android && chmod +x ./gradlew
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Install CCache
uses: hendrikmuhs/ccache-action@v1.2.11
@@ -78,9 +79,6 @@ jobs:
cd apps/mobile
npx tsc --noEmit
- name: Remove debug keystore
run: rm -rf apps/mobile/android/app/debug.keystore
- name: Build unsigned app bundle
run: yarn release:android:bundle
@@ -91,7 +89,7 @@ jobs:
id: sign_app
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/android/app/build/outputs/bundle/release
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
@@ -106,7 +104,7 @@ jobs:
id: sign_apk
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/android/app/build/outputs/apk/release
releaseDirectory: apps/mobile/native/android/app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.PUBLIC_SIGNING_KEY }}
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
@@ -116,7 +114,7 @@ jobs:
- name: Rename apk files
run: |
cd apps/mobile/android/app/build/outputs/apk/release/
cd apps/mobile/native/android/app/build/outputs/apk/release/
mv app-arm64-v8a-release-unsigned-signed.apk notesnook-arm64-v8a.apk
mv app-armeabi-v7a-release-unsigned-signed.apk notesnook-armeabi-v7a.apk
mv app-x86-release-unsigned-signed.apk notesnook-x86.apk
@@ -137,7 +135,7 @@ jobs:
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/android/releasenotes/
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Create release draft on Github
uses: softprops/action-gh-release@v1
@@ -148,15 +146,15 @@ jobs:
repository: streetwriters/notesnook
token: ${{ secrets.GITHUB_TOKEN }}
files: |
apps/mobile/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/android/app/build/outputs/apk/release/notesnook-x86_64.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
- name: Upload sourcemaps
uses: actions/upload-artifact@v4
with:
name: sourcemaps
path: |
apps/mobile/android/app/build/**/*.map
apps/mobile/native/android/app/build/**/*.map
packages/editor-mobile/sourcemaps/*.map

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.6-beta.0",
"version": "3.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.6-beta.0",
"version": "3.3.5",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.6-beta.0",
"version": "3.3.5",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -1,92 +0,0 @@
/** @type {Detox.DetoxConfig} */
module.exports = {
testRunner: {
args: {
$0: "jest",
config: "e2e/jest.config.js"
},
jest: {
setupTimeout: 120000
}
},
apps: {
"ios.debug": {
type: "ios.app",
binaryPath:
"ios/build/Build/Products/Debug-iphonesimulator/Notesnook.app",
build:
"xcodebuild -workspace ios/Notesnook.xcworkspace -scheme YOUR_APP -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
},
"ios.release": {
type: "ios.app",
binaryPath:
"ios/build/Build/Products/Release-iphonesimulator/Notesnook.app",
build:
"xcodebuild -workspace ios/Notesnook.xcworkspace -scheme YOUR_APP -configuration Release -sdk iphonesimulator -derivedDataPath ios/build"
},
"android.debug": {
type: "android.apk",
binaryPath: "android/app/build/outputs/apk/debug/app-arm64-v8a-debug.apk",
testBinaryPath:
"android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk",
build:
"cd android ; ENVFILE=.env.test ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
reversePorts: [8081]
},
"android.release": {
type: "android.apk",
binaryPath:
"android/app/build/outputs/apk/release/app-arm64-v8a-release.apk",
testBinaryPath:
"android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk",
build:
"cd android ; ENVFILE=.env.test ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release ; cd .."
}
},
devices: {
simulator: {
type: "ios.simulator",
device: {
type: "iPhone 12"
}
},
attached: {
type: "android.attached",
device: {
adbName: ".*"
}
},
emulator: {
type: "android.emulator",
device: {
avdName: "Pixel_5_API_34"
}
}
},
configurations: {
"ios.sim.debug": {
device: "simulator",
app: "ios.debug"
},
"ios.sim.release": {
device: "simulator",
app: "ios.release"
},
"android.att.debug": {
device: "attached",
app: "android.debug"
},
"android.att.release": {
device: "attached",
app: "android.release"
},
"android.emu.debug": {
device: "emulator",
app: "android.debug"
},
"android.emu.release": {
device: "emulator",
app: "android.release"
}
}
};

View File

@@ -4,7 +4,7 @@ artifacts/
#
.DS_Store
android/app/src/main/assets/
native/android/app/src/main/assets/
*Issues.md
build_cache/
#
@@ -16,12 +16,11 @@ build_cache/
.cxx/
*.keystore
!debug.keystore
.kotlin/
# Xcode
#
ios/Pods
ios/DerivedData
native/ios/Pods
native/ios/DerivedData
build/
*.pbxuser
!default.pbxuser
@@ -40,7 +39,7 @@ DerivedData
*.xcuserstate
*.hprof
**/.xcode.env.local
cache
native/cache
# Android/IntelliJ
#
rn-build-deps/
@@ -58,7 +57,7 @@ yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
# fastlane
#
@@ -70,7 +69,7 @@ buck-out/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
vendor
native/vendor
# Bundle artifact
*.jsbundle

View File

@@ -1,27 +0,0 @@
package com.streetwriters.notesnook
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(NNativeModulePackage());
},
)
}
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}

View File

@@ -1,11 +0,0 @@
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'Notesnook'
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
include ":react-native-config"
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')
include ":lazysodium-android"
project(':lazysodium-android').projectDir = new File(rootProject.projectDir, '../node_modules/@ammarahmed/react-native-sodium/android/lazysodium-android/app')

View File

@@ -132,9 +132,7 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
const listener = Appearance.addChangeListener(({ colorScheme }) => {
if (colorScheme && SettingsService.getProperty("useSystemTheme")) {
useThemeStore
.getState()
.setColorScheme(colorScheme as "light" | "dark");
useThemeStore.getState().setColorScheme(colorScheme);
}
});
return () => {

View File

@@ -94,7 +94,9 @@ export async function encryptDatabaseKeyWithPassword(appLockPassword: string) {
}
export async function restoreDatabaseKeyToKeyChain(appLockPassword: string) {
const databaseKeyCipher = CipherStorage.getMap(DB_KEY_CIPHER) as Cipher;
const databaseKeyCipher: Cipher = CipherStorage.getMap(
DB_KEY_CIPHER
) as Cipher;
const databaseKey = (await decrypt(
{
password: appLockPassword
@@ -205,8 +207,9 @@ export async function getDatabaseKey(appLockPassword?: string) {
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials =
await Keychain.getInternetCredentials("notesnook");
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(

View File

@@ -72,8 +72,8 @@ export async function setupDatabase(password?: string) {
({
compress: Gzip.deflate,
decompress: Gzip.inflate
}) as ICompressor,
batchSize: 50,
} as ICompressor),
batchSize: 100,
sqliteOptions: {
dialect: (name) => ({
createDriver: () => {

View File

@@ -115,13 +115,7 @@ export async function downloadFile(
.progress(async (recieved, total) => {
useAttachmentStore
.getState()
.setProgress(
0,
parseInt(total),
filename,
parseInt(recieved),
"download"
);
.setProgress(0, total, filename, recieved, "download");
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
});

View File

@@ -30,7 +30,7 @@ import { AppFontSize } from "../../utils/size";
import { Button } from "../ui/button";
import { strings } from "@notesnook/intl";
export const Announcement = () => {
export const Announcement = ({ color }) => {
const { colors } = useThemeColors();
const [announcements, remove] = useMessageStore((state) => [
state.announcements,
@@ -82,6 +82,7 @@ export const Announcement = () => {
<Button
type="plain"
icon="close"
height={null}
onPress={() => {
remove(announcement.id);
}}
@@ -101,6 +102,7 @@ export const Announcement = () => {
renderItem({
item: item,
index: index,
color: colors[color],
inline: true
})
)}

View File

@@ -19,18 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import Paragraph from "../ui/typography/paragraph";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
export const Body = (props: BodyItemProps) => {
export const Body = ({ text, style = {} }) => {
return (
<Paragraph
style={{
paddingHorizontal: DefaultAppStyles.GAP,
...getStyle(props.item.style)
...getStyle(style)
}}
>
{props.item.text}
{text}
</Paragraph>
);
};

View File

@@ -19,25 +19,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Linking, View } from "react-native";
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
import { useThemeColors } from "@notesnook/theme";
import { eSendEvent } from "../../services/event-manager";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { eCloseAnnouncementDialog } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { sleep } from "../../utils/time";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { allowedOnPlatform, BodyItemProps, getStyle } from "./functions";
import { allowedOnPlatform, getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
import { Action } from "../../stores/use-message-store";
export const Cta = (props: BodyItemProps) => {
export const Cta = ({ actions, style = {}, color, inline }) => {
const { colors } = useThemeColors();
let buttons =
props.item.actions.filter((item) => allowedOnPlatform(item.platforms)) ||
[];
actions.filter((item) => allowedOnPlatform(item.platforms)) || [];
const onPress = async (item: Action) => {
if (!props.inline) {
const onPress = async (item) => {
if (!inline) {
eSendEvent(eCloseAnnouncementDialog);
await sleep(500);
}
@@ -51,14 +50,14 @@ export const Cta = (props: BodyItemProps) => {
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
...getStyle(props.item.style),
flexDirection: props.inline ? "row" : "column",
...getStyle(style),
flexDirection: inline ? "row" : "column",
gap: DefaultAppStyles.GAP_SMALL
}}
>
<SheetProvider context="premium_cta" />
{props.inline ? (
{inline ? (
<>
{buttons.length > 0 &&
buttons.slice(0, 1).map((item) => (
@@ -107,9 +106,11 @@ export const Cta = (props: BodyItemProps) => {
key={item.title}
title={item.title}
buttonType={{
color: colors.primary.accent,
text: colors.primary.accentForeground,
selected: colors.primary.accent,
color: color ? color : colors.primary.accent,
text: color
? colors.static.white
: colors.primary.accentForeground,
selected: color ? color : colors.primary.accent,
opacity: 1
}}
onPress={() => onPress(item)}

View File

@@ -20,20 +20,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { AppFontSize } from "../../utils/size";
import Paragraph from "../ui/typography/paragraph";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
export const Description = (props: BodyItemProps) => {
export const Description = ({ text, style = {}, inline }) => {
return (
<Paragraph
style={{
paddingHorizontal: DefaultAppStyles.GAP,
...getStyle(props.item.style),
textAlign: props.inline ? "left" : props.item.style?.textAlign
...getStyle(style),
textAlign: inline ? "left" : style?.textAlign
}}
size={AppFontSize.sm}
>
{props.item.text}
{text}
</Paragraph>
);
};

View File

@@ -19,8 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment } from "react";
import { View } from "react-native";
import { allowedPlatforms, BodyItem } from "../../stores/use-message-store";
import { DefaultAppStyles } from "../../utils/styles";
import { allowedPlatforms } from "../../stores/use-message-store";
import { ProFeatures } from "../dialogs/result/pro-features";
import { Body } from "./body";
import { Cta } from "./cta";
import { Description } from "./description";
@@ -28,8 +28,9 @@ import { List } from "./list";
import { Photo } from "./photo";
import { SubHeading } from "./subheading";
import { Title } from "./title";
import { DefaultAppStyles } from "../../utils/styles";
export function allowedOnPlatform(platforms: string[]) {
export function allowedOnPlatform(platforms) {
if (!platforms) return true;
return platforms.some((platform) => allowedPlatforms.indexOf(platform) > -1);
}
@@ -40,15 +41,11 @@ export const margins = {
2: 20
};
export const getStyle = (style: BodyItem["style"]) => {
export const getStyle = (style) => {
if (!style) return {};
return {
marginTop: style.marginTop
? margins[style.marginTop as keyof typeof margins] || 0
: 0,
marginBottom: style.marginBottom
? margins[style.marginBottom as keyof typeof margins] || 0
: 0,
marginTop: margins[style.marginTop] || 0,
marginBottom: margins[style.marginBottom] || 0,
textAlign: style.textAlign || "left"
};
};
@@ -61,7 +58,9 @@ const Features = () => {
alignItems: "center",
width: "100%"
}}
></View>
>
<ProFeatures />
</View>
);
};
@@ -77,25 +76,16 @@ const renderItems = {
callToActions: Cta
};
export const renderItem = ({
item,
index,
inline
}: {
item: BodyItem;
index: number;
inline?: boolean;
}) => {
const Item = renderItems[item.type as keyof typeof renderItems] || Fragment;
export const renderItem = ({ item, index, color, inline }) => {
const Item = renderItems[item.type] || Fragment;
return (
<Item
key={item.text || item.src || item.type}
item={item}
{...item}
index={index}
color={color}
inline={inline}
/>
);
};
export type BodyItemProps = { item: BodyItem; index: number; inline?: boolean };

View File

@@ -24,7 +24,7 @@ import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { Announcement, useMessageStore } from "../../stores/use-message-store";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
import {
eCloseAnnouncementDialog,
@@ -97,24 +97,9 @@ import { DefaultAppStyles } from "../../utils/styles";
export const AnnouncementDialog = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [info, setInfo] = useState<Announcement | undefined>(undefined);
const [info, setInfo] = useState();
const remove = useMessageStore((state) => state.remove);
const open = (data: Announcement) => {
setInfo(data);
setImmediate(() => {
setVisible(true);
});
};
const close = useCallback(() => {
if (visible) {
if (info?.id) remove(info?.id);
setInfo(undefined);
setVisible(false);
}
}, [info?.id, remove, visible]);
useEffect(() => {
eSubscribeEvent(eOpenAnnouncementDialog, open);
eSubscribeEvent(eCloseAnnouncementDialog, close);
@@ -124,6 +109,21 @@ export const AnnouncementDialog = () => {
};
}, [close, visible]);
const open = (data) => {
setInfo(data);
setImmediate(() => {
setVisible(true);
});
};
const close = useCallback(() => {
if (visible) {
remove(info?.id);
setInfo(null);
setVisible(false);
}
}, [info?.id, remove, visible]);
return (
<BaseDialog
animated={false}

View File

@@ -21,19 +21,19 @@ import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import Paragraph from "../ui/typography/paragraph";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
export const List = (props: BodyItemProps) => {
export const List = ({ items, listType, style = {} }) => {
return (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
paddingLeft: props.item.listType === "ordered" ? 25 : 25,
...getStyle(props.item.style)
paddingLeft: listType === "ordered" ? 25 : 25,
...getStyle(style)
}}
>
{props.item.items?.map((item, index) => (
{items.map((item, index) => (
<View
key={item.text}
style={{
@@ -41,7 +41,7 @@ export const List = (props: BodyItemProps) => {
flexDirection: "row"
}}
>
{props.item.listType === "ordered" ? (
{listType === "ordered" ? (
<Paragraph
style={{
marginRight: 5

View File

@@ -19,18 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Image } from "react-native";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
export const Photo = (props: BodyItemProps) => {
return props.item.src ? (
export const Photo = ({ src, style = {} }) => {
return src ? (
<Image
source={{ uri: props.item.src }}
source={{ uri: src }}
resizeMode="cover"
style={{
width: "100%",
height: 200,
alignSelf: "center",
...getStyle(props.item.style)
...getStyle(style)
}}
/>
) : null;

View File

@@ -20,19 +20,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { AppFontSize } from "../../utils/size";
import Heading from "../ui/typography/heading";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
export const SubHeading = (props: BodyItemProps) => {
export const SubHeading = ({ text, style = {} }) => {
return (
<Heading
size={AppFontSize.md + 2}
style={{
marginHorizontal: DefaultAppStyles.GAP,
...getStyle(props.item.style)
marginTop: DefaultAppStyles.GAP_VERTICAL,
...getStyle(style)
}}
>
{props.item.text}
{text}
</Heading>
);
};

View File

@@ -19,42 +19,45 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { View } from "react-native";
import { useMessageStore } from "../../stores/use-message-store";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import { BodyItemProps, getStyle } from "./functions";
import { getStyle } from "./functions";
import { DefaultAppStyles } from "../../utils/styles";
export const Title = (props: BodyItemProps) => {
return props.inline ? (
export const Title = ({ text, style = {}, inline }) => {
return inline ? (
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: props.inline ? DefaultAppStyles.GAP_VERTICAL_SMALL : 0
marginBottom: inline ? DefaultAppStyles.GAP_VERTICAL_SMALL : 0
}}
>
<Heading
style={{
paddingHorizontal: DefaultAppStyles.GAP,
marginTop: DefaultAppStyles.GAP_VERTICAL,
...getStyle(props.item.style),
textAlign: props.inline ? "left" : props.item.style?.textAlign,
...getStyle(style),
textAlign: inline ? "left" : style?.textAlign,
flexShrink: 1
}}
size={props.inline ? AppFontSize.md : AppFontSize.xl}
size={inline ? AppFontSize.md : AppFontSize.xl}
>
{props.item.text?.toUpperCase()}
{inline ? text?.toUpperCase() : text}
</Heading>
</View>
) : (
<Heading
style={{
paddingHorizontal: DefaultAppStyles.GAP,
...getStyle(props.item.style)
...getStyle(style),
marginTop: style?.marginTop || DefaultAppStyles.GAP_VERTICAL
}}
>
{props.item.text}
{text}
</Heading>
);
};

View File

@@ -92,7 +92,7 @@ const AppLocked = () => {
const lockApp = useUserStore((state) => state.lockApp);
const deviceMode = useSettingStore((state) => state.deviceMode);
const passwordInputRef = useRef<TextInput>(null);
const password = useRef<string>(undefined);
const password = useRef<string>();
const appState = useAppState();
const lastAppState = useRef<AppStateStatus>(appState);
const biometricUnlockAwaitingUserInput = useRef(false);
@@ -239,8 +239,8 @@ const AppLocked = () => {
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { LegendList } from "@legendapp/list";
import {
Attachment,
FilteredSelector,
@@ -29,9 +28,10 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { ScrollView } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create from "zustand";
import create, { State } from "zustand";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
@@ -39,7 +39,6 @@ import { AttachmentGroupProgress } from "../../screens/settings/attachment-group
import { presentSheet, ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { Header } from "../header";
@@ -51,6 +50,7 @@ import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import { DefaultAppStyles } from "../../utils/styles";
const DEFAULT_SORTING: SortOptions = {
sortBy: "dateEdited",
@@ -65,7 +65,7 @@ type RecheckerProgress = {
filter: string;
};
interface RecheckerState {
interface RecheckerState extends State {
progress: {
[key: string]: RecheckerProgress;
};
@@ -135,9 +135,9 @@ export const AttachmentDialog = ({
const { colors } = useThemeColors();
const [attachments, setAttachments] =
useState<VirtualizedGrouping<Attachment>>();
const attachmentSearchValue = useRef<string>(undefined);
const attachmentSearchValue = useRef<string>();
const [loading, setLoading] = useState(true);
const searchTimer = useRef<NodeJS.Timeout>(undefined);
const searchTimer = useRef<NodeJS.Timeout>();
const [currentFilter, setCurrentFilter] = useState("all");
const rechecker = useRechecker((state) =>
note ? state.progress[note.id] || {} : state.progress.all
@@ -199,7 +199,7 @@ export const AttachmentDialog = ({
}, 300);
};
const renderItem = ({ index }: { item: boolean | number; index: number }) => (
const renderItem = ({ index }: { item: boolean; index: number }) => (
<AttachmentItem
setAttachments={async () => {
setAttachments(await filterAttachments(currentFilter));
@@ -326,6 +326,31 @@ export const AttachmentDialog = ({
title={strings.manageAttachments()}
renderedInRoute="SettingsGroup"
canGoBack
// TODO: Add headerRightButtons
// headerRightButtons={[
// {
// onPress() {
// onCheck();
// },
// title: strings.recheckAll()
// },
// {
// onPress() {
// if (!attachments) return;
// presentDialog({
// title: strings.doActions.download.attachment(
// attachments.placeholders.length
// ),
// positiveText: strings.network.download(),
// positivePress: async () => {
// downloadAttachments(await attachments.ids());
// },
// negativeText: strings.cancel()
// });
// },
// title: strings.downloadAllAttachments()
// }
// ]}
/>
) : (
<View
@@ -509,8 +534,7 @@ export const AttachmentDialog = ({
</ScrollView>
</View>
<LegendList
renderScrollComponent={(props) => <ScrollView {...props} />}
<FlashList
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
ListEmptyComponent={
@@ -544,8 +568,7 @@ export const AttachmentDialog = ({
/>
}
estimatedItemSize={50}
data={loading ? [] : attachments?.placeholders || []}
extraData={attachments}
data={loading ? [] : attachments?.placeholders}
renderItem={renderItem}
/>
</View>

View File

@@ -0,0 +1,114 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import { Toast } from "../toast";
import { initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
export const AuthMode = {
login: 0,
signup: 1,
welcomeSignup: 2,
trialSignup: 3
};
const AuthModal = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
const actionSheetRef = useRef();
useEffect(() => {
eSubscribeEvent(eOpenLoginDialog, open);
eSubscribeEvent(eCloseLoginDialog, close);
return () => {
eUnSubscribeEvent(eOpenLoginDialog, open);
eUnSubscribeEvent(eCloseLoginDialog, close);
};
}, []);
async function open(mode) {
setCurrentAuthMode(mode ? mode : AuthMode.login);
initialAuthMode.current = mode ? mode : AuthMode.login;
setVisible(true);
await sleep(10);
actionSheetRef.current?.show();
}
const close = () => {
useUserStore.setState({
disableAppLockRequests: false
});
actionSheetRef.current?.hide();
setCurrentAuthMode(AuthMode.login);
setVisible(false);
};
return !visible ? null : (
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
onShow={() => {
useUserStore.setState({
disableAppLockRequests: true
});
}}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
useSafeArea={false}
bounce={false}
background={colors.primary.background}
transparent={false}
animated={false}
centered={false}
enableSheetKeyboardHandler
>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
<Toast context="local" />
</BaseDialog>
);
};
export default AuthModal;

View File

@@ -0,0 +1,21 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const SVG = (color) =>
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="1920" height="1080" preserveAspectRatio="none" viewBox="0 0 1920 1080"><g fill="none"><path d="M684.78-215.3C516.78-49.87 619.54 659.12 349.29 666.13 79.03 673.14-116.04 204.76-321.7 180.13" stroke="${color}" stroke-width="2"></path><path d="M1337.71-20.29C1105.2 122.03 1140.53 887.64 809.58 933.71 478.63 979.78 545.52 798.71 281.45 798.71 17.39 798.71-110.41 932.65-246.68 933.71" stroke="${color}" stroke-width="2"></path><path d="M1631.89-52.3C1379.35-21.93 1283.05 511.66 808.42 545.28 333.79 578.9 209.15 894.14-15.05 901.68" stroke="${color}" stroke-width="2"></path><path d="M1523.21-71.7C1244.4-46.38 1056.2 532.95 581.02 533.49 105.84 534.03-109.4 182.98-361.17 177.09" stroke="${color}" stroke-width="2"></path><path d="M1333.35-39.1C1041.86 57 974.42 893.52 550.36 906.48 126.3 919.44-23.8 619.56-232.63 614.88" stroke="${color}" stroke-width="2"></path></g><defs><mask id="SvgjsMask1032"><rect width="1920" height="1080" fill="#ffffff"></rect></mask></defs></svg>`;

View File

@@ -31,13 +31,12 @@ import { Dialog } from "../dialog";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import { TextInput } from "react-native-gesture-handler";
export const ChangePassword = () => {
const passwordInputRef = useRef<TextInput>(null);
const password = useRef<string>(undefined);
const oldPasswordInputRef = useRef<TextInput>(null);
const oldPassword = useRef<string>(undefined);
const passwordInputRef = useRef();
const password = useRef();
const oldPasswordInputRef = useRef();
const oldPassword = useRef();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
@@ -86,7 +85,7 @@ export const ChangePassword = () => {
setLoading(false);
ToastManager.show({
heading: strings.passwordChangeFailed(),
message: (e as Error).message,
message: e.message,
type: "error",
context: "local"
});

View File

@@ -21,7 +21,6 @@ import { createRef } from "react";
import { eSendEvent } from "../../services/event-manager";
import { eCloseLoginDialog } from "../../utils/events";
import Navigation from "../../services/navigation";
import { AuthParams } from "../../stores/use-navigation-store";
export const AuthMode = {
login: 0,
signup: 1,
@@ -30,16 +29,15 @@ export const AuthMode = {
trialSignup: 4
};
export const initialAuthMode = createRef<number>();
initialAuthMode.current = AuthMode.login;
export function hideAuth(context?: AuthParams["context"]) {
export const initialAuthMode = createRef(0);
export function hideAuth(context) {
eSendEvent(eCloseLoginDialog);
if (
initialAuthMode.current === AuthMode.welcomeSignup ||
initialAuthMode.current === AuthMode.welcomeLogin ||
context === "intro"
) {
Navigation.replace("FluidPanelsView", {});
Navigation.replace("FluidPanelsView");
} else {
Navigation.goBack();
}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
@@ -37,8 +37,8 @@ import { DefaultAppStyles } from "../../utils/styles";
export const ForgotPassword = () => {
const { colors } = useThemeColors("sheet");
const email = useRef<string>(undefined);
const emailInputRef = useRef<TextInput>(null);
const email = useRef();
const emailInputRef = useRef();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [sent, setSent] = useState(false);
@@ -57,7 +57,7 @@ export const ForgotPassword = () => {
let lastRecoveryEmailTime = SettingsService.get().lastRecoveryEmailTime;
if (
lastRecoveryEmailTime &&
Date.now() - lastRecoveryEmailTime < 60000 * 3
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
) {
throw new Error(strings.pleaseWaitBeforeSendEmail());
}
@@ -78,7 +78,7 @@ export const ForgotPassword = () => {
setLoading(false);
ToastManager.show({
heading: strings.recoveryEmailFailed(),
message: (e as Error).message,
message: e.message,
type: "error",
context: "local"
});
@@ -93,6 +93,7 @@ export const ForgotPassword = () => {
setSent(false);
setLoading(false);
}}
keyboardShouldPersistTaps="always"
onOpen={() => {
emailInputRef.current?.setNativeProps({
text: email.current

View File

@@ -23,7 +23,6 @@ import { View } from "react-native";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import { hideAuth } from "./common";
import { AuthParams } from "../../stores/use-navigation-store";
export const AuthHeader = (props: { welcome?: boolean }) => {
const { colors } = useThemeColors();
const route = useRoute();
@@ -48,7 +47,7 @@ export const AuthHeader = (props: { welcome?: boolean }) => {
<IconButton
name="arrow-left"
onPress={() => {
hideAuth((route.params as AuthParams)?.context);
hideAuth((route.params as any)?.context);
}}
color={colors.primary.paragraph}
/>

View File

@@ -25,9 +25,8 @@ import { AuthMode, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { useThemeColors } from "@notesnook/theme";
import { NavigationProps } from "../../services/navigation";
const Auth = ({ navigation, route }: NavigationProps<"Auth">) => {
const Auth = ({ navigation, route }) => {
const [currentAuthMode, setCurrentAuthMode] = useState(
route?.params?.mode || AuthMode.login
);
@@ -43,10 +42,14 @@ const Auth = ({ navigation, route }: NavigationProps<"Auth">) => {
currentAuthMode !== AuthMode.welcomeLogin ? (
<Signup
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login changeMode={(mode) => setCurrentAuthMode(mode)} />
<Login
welcome={initialAuthMode.current}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
<Toast context="local" />

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { RouteProp, useRoute } from "@react-navigation/native";
import { useRoute } from "@react-navigation/native";
import React, { useEffect, useState } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
@@ -28,9 +28,7 @@ import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
import Sync from "../../services/sync";
import { RouteParams } from "../../stores/use-navigation-store";
import { useUserStore } from "../../stores/use-user-store";
import { eUserLoggedIn } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
@@ -46,6 +44,7 @@ import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { AuthHeader } from "./header";
import { useLogin } from "./use-login";
import SettingsService from "../../services/settings";
const LoginSteps = {
emailAuth: 1,
@@ -53,14 +52,10 @@ const LoginSteps = {
passwordAuth: 3
};
export const Login = ({
changeMode
}: {
changeMode: (mode: number) => void;
}) => {
export const Login = ({ changeMode }) => {
const { colors } = useThemeColors();
const [focused, setFocused] = useState(false);
const route = useRoute<RouteProp<RouteParams, "Auth">>();
const route = useRoute();
const {
step,
setStep,
@@ -85,7 +80,7 @@ export const Login = ({
if (!PremiumService.get() && !SettingsService.getProperty("serverUrls")) {
Navigation.navigate("PayWall", {
context: "signup",
state: route.params?.state,
state: route.params.state,
canGoBack: false
});
} else {
@@ -120,7 +115,7 @@ export const Login = ({
minHeight: "90%"
}}
nestedScrollEnabled
enableAutomaticScroll={true}
enableAutomaticScroll={false}
keyboardShouldPersistTaps="handled"
>
<View
@@ -141,11 +136,11 @@ export const Login = ({
marginBottom: DefaultAppStyles.GAP_VERTICAL,
borderBottomColor: colors.primary.border,
alignSelf: isTablet ? "center" : undefined,
borderWidth: isTablet ? 1 : undefined,
borderColor: isTablet ? colors.primary.border : undefined,
borderRadius: isTablet ? 20 : undefined,
marginTop: isTablet ? 50 : undefined,
width: !isTablet ? undefined : "50%",
borderWidth: isTablet ? 1 : null,
borderColor: isTablet ? colors.primary.border : null,
borderRadius: isTablet ? 20 : null,
marginTop: isTablet ? 50 : null,
width: !isTablet ? null : "50%",
minHeight: height * 0.4
}}
>
@@ -192,8 +187,8 @@ export const Login = ({
? "50%"
: "49.99%"
: focused
? "100%"
: "99.9%",
? "100%"
: "99.9%",
backgroundColor: colors.primary.background,
alignSelf: "center",
paddingHorizontal: DDS.isTab ? 0 : DefaultAppStyles.GAP,
@@ -245,9 +240,7 @@ export const Login = ({
marginBottom={0}
editable={!loading}
defaultValue={password.current}
onSubmit={() => {
login();
}}
onSubmit={() => login()}
/>
<Button
title={strings.forgotPassword()}
@@ -257,8 +250,8 @@ export const Login = ({
paddingHorizontal: 0
}}
onPress={() => {
if (loading || !email.current) return;
SheetManager.show("forgotpassword_sheet");
if (loading) return;
SheetManager.show("forgotpassword_sheet", email.current);
}}
textStyle={{
textDecorationLine: "underline"

View File

@@ -83,13 +83,20 @@ export const SessionExpired = () => {
});
} catch (e) {
ToastManager.show({
heading: (e as Error).message,
heading: e.message,
type: "error",
context: "local"
});
}
};
useEffect(() => {
const sub = eSubscribeEvent(eLoginSessionExpired, open);
return () => {
sub.unsubscribe?.();
};
}, [visible, open]);
const open = React.useCallback(async () => {
try {
let res = await db.tokenManager.getToken();
@@ -126,13 +133,6 @@ export const SessionExpired = () => {
}
}, [email]);
useEffect(() => {
const sub = eSubscribeEvent(eLoginSessionExpired, open);
return () => {
sub?.unsubscribe?.();
};
}, [visible, open]);
return (
visible && (
<BaseDialog
@@ -191,9 +191,7 @@ export const SessionExpired = () => {
textAlign: "center"
}}
>
{strings.sessionExpiredDesc(
getObfuscatedEmail(email.current as string)
)}
{strings.sessionExpiredDesc(getObfuscatedEmail(email.current))}
</Paragraph>
</View>
@@ -210,9 +208,7 @@ export const SessionExpired = () => {
autoCapitalize="none"
autoCorrect={false}
placeholder={strings.password()}
onSubmit={() => {
login();
}}
onSubmit={() => login()}
/>
) : null}

View File

@@ -19,11 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useContext } from "react";
export const SignupContext = React.createContext<{
signup: () => Promise<boolean | undefined>;
signup?: () => Promise<boolean>;
}>({
signup: async () => {
return false;
}
signup: undefined
});
export const useSignupContext = () => useContext(SignupContext);

View File

@@ -19,14 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { RouteProp, useRoute } from "@react-navigation/native";
import { useRoute } from "@react-navigation/native";
import React, { useRef, useState } from "react";
import {
TextInput,
TouchableOpacity,
View,
useWindowDimensions
} from "react-native";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
@@ -44,7 +39,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AuthHeader } from "./header";
import { SignupContext } from "./signup-context";
import { RouteParams } from "../../stores/use-navigation-store";
import SettingsService from "../../services/settings";
const SignupSteps = {
@@ -53,28 +47,22 @@ const SignupSteps = {
createAccount: 2
};
export const Signup = ({
changeMode,
welcome
}: {
changeMode: (mode: number) => void;
welcome: boolean;
}) => {
export const Signup = ({ changeMode, welcome }) => {
const [currentStep, setCurrentStep] = useState(SignupSteps.signup);
const { colors } = useThemeColors();
const email = useRef<string>(undefined);
const emailInputRef = useRef<TextInput>(null);
const passwordInputRef = useRef<TextInput>(null);
const password = useRef<string>(undefined);
const confirmPasswordInputRef = useRef<TextInput>(null);
const confirmPassword = useRef<string>(undefined);
const email = useRef();
const emailInputRef = useRef();
const passwordInputRef = useRef();
const password = useRef();
const confirmPasswordInputRef = useRef();
const confirmPassword = useRef();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
const setLastSynced = useUserStore((state) => state.setLastSynced);
const { width, height } = useWindowDimensions();
const isTablet = width > 600;
const route = useRoute<RouteProp<RouteParams, "Auth">>();
const route = useRoute();
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
@@ -98,7 +86,7 @@ export const Signup = ({
setLoading(true);
try {
setCurrentStep(SignupSteps.createAccount);
await db.user.signup(email.current!.toLowerCase(), password.current!);
await db.user.signup(email.current.toLowerCase(), password.current);
let user = await db.user.getUser();
setUser(user);
setLastSynced(await db.lastSynced());
@@ -117,7 +105,7 @@ export const Signup = ({
setLoading(false);
ToastManager.show({
heading: strings.signupFailed(),
message: (e as Error).message,
message: e.message,
type: "error",
context: "local"
});
@@ -142,6 +130,7 @@ export const Signup = ({
minHeight: "90%"
}}
nestedScrollEnabled
enableAutomaticScroll={false}
keyboardShouldPersistTaps="handled"
>
<View
@@ -162,11 +151,11 @@ export const Signup = ({
borderBottomWidth: 0.8,
borderBottomColor: colors.primary.border,
alignSelf: isTablet ? "center" : undefined,
borderWidth: isTablet ? 1 : undefined,
borderColor: isTablet ? colors.primary.border : undefined,
borderRadius: isTablet ? 20 : undefined,
marginTop: isTablet ? 50 : undefined,
width: !isTablet ? undefined : "50%",
borderWidth: isTablet ? 1 : null,
borderColor: isTablet ? colors.primary.border : null,
borderRadius: isTablet ? 20 : null,
marginTop: isTablet ? 50 : null,
width: !isTablet ? null : "50%",
minHeight: height * 0.25
}}
>
@@ -277,12 +266,10 @@ export const Signup = ({
autoCorrect={false}
blurOnSubmit={false}
validationType="confirmPassword"
customValidator={() => password.current!}
customValidator={() => password.current}
placeholder={strings.confirmPassword()}
marginBottom={12}
onSubmit={() => {
signup();
}}
onSubmit={signup}
/>
<Button
@@ -339,7 +326,7 @@ export const Signup = ({
<Paragraph
size={AppFontSize.xxs}
onPress={() => {
openLinkInBrowser("https://notesnook.com/tos");
openLinkInBrowser("https://notesnook.com/tos", colors);
}}
style={{
textDecorationLine: "underline"
@@ -353,7 +340,10 @@ export const Signup = ({
<Paragraph
size={AppFontSize.xxs}
onPress={() => {
openLinkInBrowser("https://notesnook.com/privacy");
openLinkInBrowser(
"https://notesnook.com/privacy",
colors
);
}}
style={{
textDecorationLine: "underline"

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { db } from "../../common/database/index";
import useTimer from "../../hooks/use-timer";
@@ -36,44 +36,20 @@ import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
import { presentDialog } from "../dialog/functions";
type MFAInfo = {
primaryMethod: string;
secondaryMethod: string;
token: string;
};
const TwoFactorVerification = ({
onMfaLogin,
mfaInfo,
onCancel
}: {
onMfaLogin: (
login: {
method: string;
code: string;
},
callback: (result: any) => void
) => Promise<void>;
mfaInfo: MFAInfo;
onCancel: () => void;
}) => {
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
const { colors } = useThemeColors();
const code = useRef<string>(undefined);
const [currentMethod, setCurrentMethod] = useState<{
isPrimary: boolean;
method: string | null;
}>({
const code = useRef();
const [currentMethod, setCurrentMethod] = useState({
method: mfaInfo?.primaryMethod,
isPrimary: true
});
const { seconds, start, reset } = useTimer(currentMethod.method!);
const { seconds, start, reset } = useTimer(currentMethod.method);
const [loading, setLoading] = useState(false);
const inputRef = useRef<TextInput>(null);
const inputRef = useRef();
const [sending, setSending] = useState(false);
const onNext = async () => {
if (!code.current || code.current.length < 6 || !currentMethod.method)
return;
if (!code.current || code.current.length < 6) return;
setLoading(true);
inputRef.current?.blur();
await onMfaLogin(
@@ -130,25 +106,26 @@ const TwoFactorVerification = ({
);
};
const onSendCode = useCallback(async () => {
if (seconds || sending) return;
setSending(true);
try {
await db.mfa.sendCode(currentMethod.method as "sms" | "email");
start(60);
setSending(false);
} catch (e) {
setSending(false);
ToastManager.error(e as Error, "Error sending 2FA Code", "local");
}
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
useEffect(() => {
if (currentMethod.method === "sms" || currentMethod.method === "email") {
onSendCode();
}
}, [currentMethod.method, onSendCode]);
const onSendCode = useCallback(async () => {
if (seconds || sending) return;
// TODO
setSending(true);
try {
await db.mfa.sendCode(currentMethod.method, mfaInfo.token);
start(60);
setSending(false);
} catch (e) {
setSending(false);
ToastManager.error(e, "Error sending 2FA Code", "local");
}
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
return (
<ScrollView
keyboardShouldPersistTaps="handled"
@@ -194,11 +171,8 @@ const TwoFactorVerification = ({
textAlign: "center"
}}
>
{currentMethod.method
? strings["2faCodeHelpText"][
currentMethod.method as keyof (typeof strings)["2faCodeHelpText"]
]?.() || strings.select2faCodeHelpText()
: strings.select2faCodeHelpText()}
{strings["2faCodeHelpText"][currentMethod.method]?.() ||
strings.select2faCodeHelpText()}
</Paragraph>
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
@@ -210,7 +184,7 @@ const TwoFactorVerification = ({
? ""
: `${
seconds
? strings.resend2faCode(`${seconds}`)
? strings.resend2faCode(seconds)
: strings.sendCode()
}`
}
@@ -235,6 +209,7 @@ const TwoFactorVerification = ({
textAlign="center"
onChangeText={(value) => {
code.current = value;
//onNext();
}}
cursorColor={colors.selected.accent}
selectionHandleColor={colors.selected.accent}
@@ -278,7 +253,7 @@ const TwoFactorVerification = ({
<Button
title={strings["2faCodeSecondaryMethodText"][
currentMethod.method as keyof (typeof strings)["2faCodeSecondaryMethodText"]
currentMethod.method
]()}
type="plain"
onPress={onRequestSecondaryMethod}
@@ -332,18 +307,7 @@ const TwoFactorVerification = ({
);
};
TwoFactorVerification.present = (
onMfaLogin: (
login: {
method: string;
code: string;
},
callback: (result: any) => void
) => Promise<void>,
data: MFAInfo,
onCancel: () => void,
context?: string
) => {
TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
presentDialog({
component: () => (
<TwoFactorVerification
@@ -353,6 +317,7 @@ TwoFactorVerification.present = (
/>
),
context: context || "two_factor_verify",
disableClosing: true,
transparent: false,
statusBarTranslucent: true
});

View File

@@ -43,8 +43,8 @@ export const useLogin = (
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
const [step, setStep] = useState(LoginSteps.emailAuth);
const email = useRef<string>(undefined);
const password = useRef<string>(undefined);
const email = useRef<string>();
const password = useRef<string>();
const emailInputRef = useRef<TextInput>(null);
const passwordInputRef = useRef<TextInput>(null);

View File

@@ -36,6 +36,7 @@ import { getElevationStyle } from "../../utils/elevation";
import { AppFontSize, normalize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { hexToRGBA, RGB_Linear_Shade } from "../../utils/colors";
import useKeyboard from "../../hooks/use-keyboard";
interface FloatingButtonProps {
onPress: () => void;
@@ -119,16 +120,14 @@ const FloatingButton = ({
style={[
{
position: "absolute",
right: position === "right" ? DefaultAppStyles.GAP - 7 : undefined,
left: position === "left" ? DefaultAppStyles.GAP - 7 : undefined,
bottom: 15,
right: position === "right" ? DefaultAppStyles.GAP : undefined,
left: position === "left" ? DefaultAppStyles.GAP : undefined,
bottom: 20,
zIndex: 10,
padding: 15,
...style
},
animatedStyle
]}
pointerEvents="box-none"
>
<TouchableOpacity
testID={testID || notesnook.buttons.add}
@@ -161,10 +160,10 @@ const FloatingButton = ({
icon
? icon
: route.name === "Notebooks"
? "notebook-plus"
: route.name === "Trash"
? "delete"
: "plus"
? "notebook-plus"
: route.name === "Trash"
? "delete"
: "plus"
}
color={color || colors.primary.accent}
size={size === "small" ? AppFontSize.xl : AppFontSize.xxxl}

View File

@@ -30,7 +30,7 @@ export const Container = ({ children }: PropsWithChildren) => {
const [height, setHeight] = useState(0);
const windowHeightRef = useRef(Dimensions.get("window").height);
const { height: windowHeight } = useWindowDimensions();
const timerRef = useRef<NodeJS.Timeout>(undefined);
const timerRef = useRef<NodeJS.Timeout>();
useEffect(() => {
if (windowHeight !== windowHeightRef.current) {
setHeight(0);

View File

@@ -20,12 +20,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { AnnouncementDialog } from "../announcements";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
import { AppLockPassword } from "../dialogs/applock-password";
import JumpToSectionDialog from "../dialogs/jump-to-section";
import { LoadingDialog } from "../dialogs/loading";
import PDFPreview from "../dialogs/pdf-preview";
import ResultDialog from "../dialogs/result";
import { VaultDialog } from "../dialogs/vault";
import ImagePreview from "../image-preview";
import MergeConflicts from "../merge-conflicts";
@@ -52,6 +54,7 @@ const DialogProvider = () => {
<>
<MergeConflicts />
<RecoveryKeySheet colors={colors} />
<ResultDialog />
<VaultDialog colors={colors} />
<RateAppSheet />
<ImagePreview />

View File

@@ -0,0 +1,52 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { eSendEvent } from "../../services/event-manager";
import {
eCloseActionSheet,
eCloseAddNotebookDialog,
eCloseMoveNoteDialog,
eOpenActionSheet,
eOpenAddNotebookDialog,
eOpenMoveNoteDialog
} from "../../utils/events";
export const ActionSheetEvent = (item, buttons) => {
eSendEvent(eOpenActionSheet, {
item,
buttons
});
};
export const ActionSheetHideEvent = () => {
eSendEvent(eCloseActionSheet);
};
export const moveNoteEvent = () => {
eSendEvent(eOpenMoveNoteDialog);
};
export const moveNoteHideEvent = () => {
eSendEvent(eCloseMoveNoteDialog);
};
export const AddNotebookEvent = (notebook) => {
eSendEvent(eOpenAddNotebookDialog, notebook);
};
export const HideAddNotebookEvent = (notebook) => {
eSendEvent(eCloseAddNotebookDialog, notebook);
};

View File

@@ -201,14 +201,12 @@ export const AppLockPassword = () => {
accountPass
? strings.enterAccountPassword()
: mode === "change"
? keyboardType === "pin"
? strings.newPin()
: strings.newPassword()
: `${
keyboardType === "pin"
? strings.pin()
: strings.password()
}`
? keyboardType === "pin"
? strings.newPin()
: strings.newPassword()
: `${
keyboardType === "pin" ? strings.pin() : strings.password()
}`
}
/>
@@ -385,8 +383,8 @@ export const AppLockPassword = () => {
mode === "remove"
? strings.remove()
: mode === "change"
? strings.change()
: strings.save()
? strings.change()
: strings.save()
}
negativeTitle={strings.cancel()}
positiveType="transparent"

View File

@@ -51,7 +51,7 @@ const ColorPicker = ({
const [selectedColor, setSelectedColor] = useState<string>();
const { colors } = useThemeColors();
const inputRef = useRef<TextInput>(null);
const title = useRef<string>(undefined);
const title = useRef<string>();
const colorRef = useStoredRef("color-ref", "#f0f0f0");
return (

View File

@@ -46,7 +46,7 @@ import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
const JumpToSectionDialog = () => {
const scrollRef = useRef<RefObject<FlatList>>(undefined);
const scrollRef = useRef<RefObject<FlatList>>();
const [data, setData] = useState<VirtualizedGrouping<Item>>();
const { colors } = useThemeColors();
const notes = data;

View File

@@ -50,7 +50,7 @@ export default function Progress() {
const { colors } = useThemeColors();
const [progress, setProgress] = React.useState<string | undefined>();
const [visible, setVisible] = React.useState(false);
const cancelCallback = React.useRef<() => void>(undefined);
const cancelCallback = React.useRef<() => void>();
const [data, setData] = React.useState<{
title?: string;
paragraph?: string;

View File

@@ -0,0 +1,145 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../../utils/elevation";
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
import { AppFontSize } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { ProFeatures } from "./pro-features";
import { DefaultAppStyles } from "../../../utils/styles";
const ResultDialog = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [dialogData, setDialogData] = useState({
title: "",
paragraph: "",
button: ""
});
useEffect(() => {
eSubscribeEvent(eOpenResultDialog, open);
eSubscribeEvent(eCloseResultDialog, close);
return () => {
eUnSubscribeEvent(eOpenResultDialog, open);
eUnSubscribeEvent(eCloseResultDialog, close);
};
}, []);
const open = (data) => {
if (data) {
setDialogData(data);
}
setVisible(true);
};
const close = () => {
setVisible(false);
};
return !visible ? null : (
<BaseDialog visible={true} onRequestClose={close}>
<View
style={{
...getElevationStyle(5),
width: DDS.isTab ? 350 : "85%",
maxHeight: 500,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 20,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading
size={AppFontSize.lg}
textBreakStrategy="balanced"
style={{
alignSelf: "center",
textAlign: "center",
marginTop: DefaultAppStyles.GAP_VERTICAL,
maxWidth: "100%",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
{dialogData.title}
</Heading>
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.md}
style={{
alignSelf: "center",
textAlign: "center",
maxWidth: "80%",
lineHeight: AppFontSize.sm + 5
}}
>
{dialogData.paragraph}
</Paragraph>
<Seperator />
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
alignItems: "center",
width: "100%"
}}
>
<ProFeatures count={4} />
</View>
<Seperator />
<View
style={{
backgroundColor: colors.secondary.background,
width: "100%",
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<Button
title={dialogData.button}
width={null}
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
onPress={close}
/>
</View>
</View>
</BaseDialog>
);
};
export default ResultDialog;

View File

@@ -0,0 +1,104 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { eSendEvent } from "../../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import {
eCloseSheet,
eCloseResultDialog,
eOpenPremiumDialog
} from "../../../utils/events";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
export const ProFeatures = ({ count = 6 }) => {
const { colors } = useThemeColors();
return (
<>
{[
{
content:
"Unlock unlimited notebooks, tags, colors. Organize like a pro"
},
{
content:
"Attach files upto 500MB, upload 4K images with unlimited storage"
},
{
content: "Instantly sync to unlimited devices"
},
{
content: "A private vault to keep everything important always locked"
},
{
content:
"Rich note editing experience with markdown, tables, checklists and more"
},
{
content: "Export your notes in Pdf, markdown and html formats"
}
]
.slice(0, count)
.map((item) => (
<View
key={item.content}
style={{
flexDirection: "row",
width: "100%",
height: 40,
paddingHorizontal: 0,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center",
borderRadius: defaultBorderRadius,
justifyContent: "flex-start"
}}
>
<Icon
size={AppFontSize.lg}
color={colors.primary.accent}
name="check"
/>
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>
{item.content}
</Paragraph>
</View>
))}
<Paragraph
onPress={async () => {
eSendEvent(eCloseResultDialog);
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenPremiumDialog);
}}
size={AppFontSize.xs}
style={{
textDecorationLine: "underline",
color: colors.secondary.paragraph
}}
>
See all features included in Notesnook Pro
</Paragraph>
</>
);
};

View File

@@ -67,7 +67,7 @@ export interface TabsRef {
page: () => FluidTabPage;
setScrollEnabled: () => true;
isDrawerOpen: () => boolean;
node: RefObject<Animated.View | null>;
node: RefObject<Animated.View>;
tabChangedFromSwipeAction: SharedValue<boolean>;
}

View File

@@ -46,7 +46,7 @@ const ImagePreview = () => {
const [visible, setVisible] = useState(false);
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
const imageRef = useRef<ImageAttributes>(null);
const imageRef = useRef<ImageAttributes>();
const insets = useGlobalSafeAreaInsets();
const [showHeader, setShowHeader] = useState(true);

View File

@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { useMessageStore } from "../../../stores/use-message-store";
import { RouteParams } from "../../../stores/use-navigation-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { Announcement } from "../../announcements/announcement";
import { Card } from "../../list/card";
import { RouteParams } from "../../../stores/use-navigation-store";
export type ListHeaderProps = {
noAnnouncement?: boolean;
@@ -46,7 +48,7 @@ export const Header = React.memo(
return (
<>
{announcements.length !== 0 && !noAnnouncement ? (
<Announcement />
<Announcement color={color || colors.primary.accent} />
) : (screen as any) === "Search" ? null : !shouldShow ? (
<>
{messageCard ? (

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import { LegendList, LegendListRenderItemProps } from "@legendapp/list";
import { FlashList } from "@shopify/flash-list";
import React, { useEffect, useRef } from "react";
import {
NativeScrollEvent,
@@ -38,7 +38,6 @@ import { fluidTabsRef } from "../../utils/global-refs";
import { Header } from "../list-items/headers/header";
import { Empty, PlaceholderData } from "./empty";
import { ListItemWrapper } from "./list-item.wrapper";
import { ScrollView } from "react-native-actions-sheet";
type ListProps = {
data: VirtualizedGrouping<Item> | undefined;
@@ -56,13 +55,9 @@ type ListProps = {
id?: string;
};
const onMomentumScrollEnd = () => {
fluidTabsRef.current?.unlock();
};
export default function List(props: ListProps) {
const { colors } = useThemeColors();
const scrollRef = useRef(null);
const scrollRef = useRef();
const [notesListMode, notebooksListMode] = useSettingStore((state) => [
state.settings.notesListMode,
state.settings.notebooksListMode
@@ -77,10 +72,10 @@ export default function List(props: ListProps) {
props.renderedInRoute === "Notes"
? "home"
: props.renderedInRoute === "Favorites"
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
? "favorites"
: props.renderedInRoute === "Trash" || props.dataType === "trash"
? "trash"
: `${props.dataType}s`;
const groupOptions = useGroupOptions(groupType);
@@ -90,18 +85,11 @@ export default function List(props: ListProps) {
});
};
const getItemType = React.useCallback(
(item: number | boolean, index: number) => {
return props.data?.type(index);
},
[]
);
const renderItem = React.useCallback(
(itemProps: LegendListRenderItemProps<any, any>) => {
({ index }: { index: number }) => {
return (
<ListItemWrapper
index={itemProps.index}
index={index}
isSheet={props.isRenderedInActionSheet || false}
items={props.data}
groupOptions={groupOptions}
@@ -145,6 +133,16 @@ export default function List(props: ListProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const styles = {
width: "100%",
minHeight: 1,
minWidth: 1
};
const ListView = props.CustomListComponent
? props.CustomListComponent
: FlashList;
return (
<>
<View
@@ -152,75 +150,74 @@ export default function List(props: ListProps) {
flex: 1
}}
>
<LegendList
ref={scrollRef}
contentContainerStyle={{
flexGrow: 1
}}
extraData={props.data}
testID={notesnook.list.id}
data={props.data?.placeholders || []}
renderScrollComponent={
props.isRenderedInActionSheet
? (props) => <ScrollView {...props} />
: undefined
}
renderItem={renderItem}
onScroll={onListScroll}
nestedScrollEnabled={true}
onMomentumScrollEnd={onMomentumScrollEnd}
getItemType={getItemType}
estimatedItemSize={isCompactModeEnabled ? 60 : 120}
directionalLockEnabled={true}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListEmptyComponent={
<View
style={{
flex: 1
}}
>
<Empty
loading={props.loading}
title={props.headerTitle}
dataType={props.dataType}
{props.data?.placeholders?.length === 0 || !props.data ? (
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
placeholder={props.placeholder}
screen={props.renderedInRoute}
/>
</View>
}
ListFooterComponent={
<View
style={{ height: props.data?.placeholders?.length ? 100 : 0 }}
)}
<Empty
loading={props.loading}
title={props.headerTitle}
dataType={props.dataType}
color={props.customAccentColor}
placeholder={props.placeholder}
screen={props.renderedInRoute}
/>
}
refreshControl={
props.isRenderedInActionSheet ? (
<></>
) : (
<RefreshControl
tintColor={colors.primary.accent}
colors={[colors.primary.accent]}
progressBackgroundColor={colors.secondary.background}
onRefresh={_onRefresh}
refreshing={false}
</>
) : (
<ListView
style={styles}
ref={scrollRef}
testID={notesnook.list.id}
data={props.data?.placeholders || []}
renderItem={renderItem}
onScroll={onListScroll}
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fluidTabsRef.current?.unlock();
}}
getItemType={(item: number, index: number) => {
return props.data?.type(index);
}}
estimatedItemSize={isCompactModeEnabled ? 60 : 120}
directionalLockEnabled={true}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={
<View
style={{ height: props.data?.placeholders?.length ? 100 : 0 }}
/>
)
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
screen={props.renderedInRoute}
}
refreshControl={
props.isRenderedInActionSheet ? null : (
<RefreshControl
tintColor={colors.primary.accent}
colors={[colors.primary.accent]}
progressBackgroundColor={colors.secondary.background}
onRefresh={_onRefresh}
refreshing={false}
/>
)}
</>
}
/>
)
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (
props.CustomLisHeader
) : !props.headerTitle ? null : (
<Header
color={props.customAccentColor}
screen={props.renderedInRoute}
/>
)}
</>
}
/>
)}
</View>
</>
);

View File

@@ -67,16 +67,16 @@ type ListItemWrapperProps<TItem = Item> = {
export function ListItemWrapper(props: ListItemWrapperProps) {
const { items, group, isSheet, index, groupOptions } = props;
const [item, setItem] = useState<Item>();
const tags = useRef<TagsWithDateEdited>(undefined);
const notebooks = useRef<NotebooksWithDateEdited>(undefined);
const reminder = useRef<Reminder>(undefined);
const color = useRef<Color>(undefined);
const tags = useRef<TagsWithDateEdited>();
const notebooks = useRef<NotebooksWithDateEdited>();
const reminder = useRef<Reminder>();
const color = useRef<Color>();
const totalNotes = useRef<number>(0);
const attachmentsCount = useRef(0);
const [groupHeader, setGroupHeader] = useState<GroupHeader>();
const previousIndex = useRef<number>(undefined);
const refreshTimeout = useRef<NodeJS.Timeout>(undefined);
const currentItemId = useRef<string>(undefined);
const previousIndex = useRef<number>();
const refreshTimeout = useRef<NodeJS.Timeout>();
const currentItemId = useRef<string>();
const locked = useRef(false);
const compactMode = useIsCompactModeEnabled(props.dataType as ItemType);

View File

@@ -20,25 +20,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { RefObject, useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { LegendList } from "@legendapp/list";
import { getFormattedDate, getTimeAgo } from "@notesnook/common";
import { HistorySession, Note, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useDBItem } from "../../hooks/use-db-item";
import { presentSheet } from "../../services/event-manager";
import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { Pressable } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import NotePreview from "./preview";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
const HistoryItem = ({
index,
@@ -156,11 +156,9 @@ export default function NoteHistory({
maxHeight: "100%"
}}
>
<LegendList
renderScrollComponent={(props) => <ScrollView {...props} />}
data={history?.placeholders || []}
<FlashList
data={history?.placeholders}
estimatedItemSize={55}
extraData={history}
ListEmptyComponent={
<View
style={{

View File

@@ -744,7 +744,6 @@ const ComparePlans = React.memo(
{["Features", "Free", "Essential", "Pro", "Believer"].map(
(plan, index) => (
<View
key={plan}
style={{
width: index === 0 ? 150 : 120,
paddingHorizontal: 16,
@@ -764,7 +763,7 @@ const ComparePlans = React.memo(
{getFeaturesTable().map((item, keyIndex) => {
return (
<View
key={`${item[0] + item[1]}`}
key={keyIndex + "feature-item"}
style={{
flexDirection: "row",
alignItems: "center",
@@ -834,7 +833,6 @@ const ComparePlans = React.memo(
{["features", "free", "essential", "pro", "believer"].map(
(plan, index) => (
<View
key={plan + "btn"}
style={{
width: index === 0 ? 150 : 120,
paddingHorizontal: 16,
@@ -1006,8 +1004,8 @@ const PricingPlanCard = ({
: "monthly"
}`
: pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: (product?.productId as string)
? (WebPlan?.period as string)
: (product?.productId as string)
);
setStep(Steps.buy);
}}

View File

@@ -17,13 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { LegendList } from "@legendapp/list";
import { useIsFeatureAvailable } from "@notesnook/common";
import { Color, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
import { FlashList } from "@shopify/flash-list";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
@@ -34,11 +32,14 @@ import { useRelationStore } from "../../stores/use-relation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { refreshNotesPage } from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import ColorPicker from "../dialogs/color-picker";
import PaywallSheet from "../sheets/paywall";
import { Button } from "../ui/button";
import NativeTooltip from "../../utils/tooltip";
import { Pressable } from "../ui/pressable";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
const { colors } = useThemeColors();
@@ -170,7 +171,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
}}
/>
) : (
<LegendList
<FlashList
data={colorNotes}
estimatedItemSize={30}
horizontal

View File

@@ -34,14 +34,14 @@ import { DefaultAppStyles } from "../../utils/styles";
export default function Notebooks({ note, close, full }) {
const { colors } = useThemeColors();
async function getNotebooks() {
let filteredNotebooks = await db.relations.to(note, "notebook").resolve();
return filteredNotebooks || [];
}
const [noteNotebooks, setNoteNotebooks] = useState([]);
useEffect(() => {
async function getNotebooks() {
let filteredNotebooks = await db.relations.to(note, "notebook").resolve();
return filteredNotebooks || [];
}
getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
}, []);
});
const navigateNotebook = async (id) => {
let item = await db.notebooks.notebook(id);

View File

@@ -70,6 +70,7 @@ export const Tags = ({ item, close }) => {
export const TagStrip = ({ item, close }) => {
const [tags, setTags] = useState([]);
useEffect(() => {
db.relations
.to(item, "tag")
@@ -77,7 +78,7 @@ export const TagStrip = ({ item, close }) => {
.then((tags) => {
setTags(tags);
});
}, []);
});
return tags?.length > 0 ? (
<View

View File

@@ -20,7 +20,7 @@ import { EVENTS, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { ListRenderItemInfo, View } from "react-native";
import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
@@ -187,7 +187,7 @@ export default function EditorTabs({
]);
const renderTabItem = React.useCallback(
({ item }: ListRenderItemInfo<TabItem>) => {
({ item }: { item: TabItem }) => {
return (
<TabItemComponent
key={item.id}

View File

@@ -118,8 +118,8 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? strings.linkNoteEmptyBlock()
: item.content}
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
<View
@@ -149,12 +149,12 @@ export default function LinkNote(props: {
}) {
const blockLinking = useIsFeatureAvailable("blockLinking");
const { colors } = useThemeColors();
const query = useRef<string>(undefined);
const query = useRef<string>();
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const nodesRef = useRef<ContentBlock[]>([]);
const [nodes, setNodes] = useState<ContentBlock[]>([]);
const inputRef = useRef<TextInput>(null);
const inputRef = useRef<TextInput>();
const [selectedNote, setSelectedNote] = useState<Note>();
const [selectedNodeId, setSelectedNodeId] = useState<string>();

View File

@@ -45,25 +45,6 @@ import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useAsync } from "react-async-hook";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
async function fetchMonographData(noteId: string) {
const monographId = db.monographs.monograph(noteId);
const monograph = monographId
? await db.monographs.get(monographId)
: undefined;
const analyticsFeature = await isFeatureAvailable("monographAnalytics");
const analytics =
monographId && analyticsFeature
? await db.monographs.analytics(monographId)
: undefined;
return {
monograph,
monographId,
analytics
};
}
const PublishNoteSheet = ({
note
@@ -75,20 +56,20 @@ const PublishNoteSheet = ({
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
const downloading = attachmentDownloads?.[`monograph-${note.id}`];
const [selfDestruct, setSelfDestruct] = useState(false);
const isFeatureAvailable = useIsFeatureAvailable("monographAnalytics");
const [isLocked, setIsLocked] = useState(false);
const [monograph, setMonograph] = useState<Monograph>();
const [publishing, setPublishing] = useState(false);
const pwdInput = useRef<TextInput>(null);
const passwordValue = useRef<string>(undefined);
const monographData = useAsync(async () => {
return fetchMonographData(note?.id);
}, []);
const monograph = monographData.result?.monograph;
const publishUrl = monograph && `${hosts.MONOGRAPH_HOST}/${monograph?.id}`;
const isPublished = db.monographs.monograph(note?.id);
const isPublished = !!monograph;
const pwdInput = useRef<TextInput>(null);
const passwordValue = useRef<string>();
useEffect(() => {
(async () => {
const monograph = await db.monographs.get(
db.monographs.monograph(note.id)
);
setMonograph(monograph);
if (monograph) {
setSelfDestruct(!!monograph?.selfDestruct);
if (monograph.password) {
@@ -99,7 +80,7 @@ const PublishNoteSheet = ({
}
}
})();
}, [monograph]);
}, []);
const publishNote = async () => {
if (publishing) return;
@@ -113,7 +94,7 @@ const PublishNoteSheet = ({
password: isLocked ? passwordValue.current : undefined
});
await monographData.execute();
setMonograph(await db.monographs.get(db.monographs.monograph(note.id)));
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
@@ -140,7 +121,7 @@ const PublishNoteSheet = ({
try {
if (note?.id) {
await db.monographs.unpublish(note.id);
monographData.execute();
setMonograph(undefined);
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
@@ -164,15 +145,12 @@ const PublishNoteSheet = ({
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
{isPublished &&
(monographData?.result?.monograph || monographData?.loading) ? null : (
<DialogHeader
title={strings.publishNote()}
paragraph={strings.publishNoteDesc()}
/>
)}
<DialogHeader
title={strings.publishNote()}
paragraph={strings.publishNoteDesc()}
/>
{publishing || monographData.loading ? (
{publishing ? (
<View
style={{
justifyContent: "center",
@@ -198,14 +176,7 @@ const PublishNoteSheet = ({
) : (
<>
{isPublished && publishUrl ? (
<TouchableOpacity
onPress={async () => {
try {
await openLinkInBrowser(publishUrl);
} catch (e) {
console.error(e);
}
}}
<View
style={{
flexDirection: "row",
alignItems: "center",
@@ -227,6 +198,23 @@ const PublishNoteSheet = ({
<Paragraph size={AppFontSize.sm} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl);
} catch (e) {
console.error(e);
}
}}
size={AppFontSize.xs}
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL_SMALL,
color: colors.primary.paragraph
}}
>
<Icon color={colors.primary.accent} name="open-in-new" />{" "}
{strings.openInBrowser()}
</Paragraph>
</View>
<IconButton
@@ -242,7 +230,7 @@ const PublishNoteSheet = ({
size={AppFontSize.lg}
name="content-copy"
/>
</TouchableOpacity>
</View>
) : null}
<TouchableOpacity
@@ -272,9 +260,9 @@ const PublishNoteSheet = ({
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>
<Heading size={AppFontSize.md}>
{strings.monographPassHeading()}
</Paragraph>
</Heading>
<ToggleSwitch
isOn={isLocked}
onColor={colors.primary.accent}
@@ -285,7 +273,7 @@ const PublishNoteSheet = ({
/>
</View>
{/* <Paragraph>{strings.monographPassDesc()}</Paragraph> */}
<Paragraph>{strings.monographPassDesc()}</Paragraph>
{isLocked ? (
<>
@@ -331,9 +319,9 @@ const PublishNoteSheet = ({
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>
<Heading size={AppFontSize.md}>
{strings.monographSelfDestructHeading()}
</Paragraph>
</Heading>
<ToggleSwitch
isOn={selfDestruct}
onColor={colors.primary.accent}
@@ -344,42 +332,10 @@ const PublishNoteSheet = ({
/>
</View>
{/* <Paragraph>{strings.monographSelfDestructDesc()}</Paragraph> */}
<Paragraph>{strings.monographSelfDestructDesc()}</Paragraph>
</View>
</TouchableOpacity>
{isFeatureAvailable?.isAllowed ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.secondary.background,
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
borderRadius: defaultBorderRadius,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>{strings.views()}</Paragraph>
<Paragraph>
{monographData?.result?.analytics?.totalViews || 0}
</Paragraph>
</View>
</View>
</View>
) : null}
<View
style={{
width: "100%",

View File

@@ -96,6 +96,12 @@ class RecoveryKeySheet extends React.Component {
recoveryKeySaved: true
});
clearMessage();
// if (this.signup) {
// this.signup = false;
// setTimeout(() => {
// eSendEvent(eOpenResultDialog);
// }, 500);
// }
};
async componentDidMount() {
eSubscribeEvent(eOpenRecoveryKeyDialog, this.open);

View File

@@ -16,36 +16,33 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { LegendList } from "@legendapp/list";
import {
ContentBlock,
InternalLink,
ItemReference,
Note,
TextSlice,
VirtualizedGrouping,
createInternalLink,
highlightInternalLinks
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { ContentBlock, ItemReference, Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { create } from "zustand";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import create from "zustand";
import { db } from "../../../common/database";
import { useDBItem, useNoteLocked } from "../../../hooks/use-db-item";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnLoadNote } from "../../../utils/events";
import { fluidTabsRef } from "../../../utils/global-refs";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
export const useExpandedStore = create<{
expanded: {
@@ -104,8 +101,8 @@ const ListBlockItem = ({
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? strings.linkNoteEmptyBlock()
: item.content}
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
<View
@@ -404,7 +401,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
const { colors } = useThemeColors();
const [items, setItems] = useState<VirtualizedGrouping<Note>>();
const hasNoRelations = !items || items?.placeholders?.length === 0;
const internalLinks = useRef<InternalLink<"note">[]>([]);
const internalLinks = useRef<InternalLink<"note">[]>();
useEffect(() => {
db.relations?.[tab === 0 ? "from" : "to"]?.(
@@ -504,11 +501,9 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
>
<LegendList
renderScrollComponent={(props) => <ScrollView {...props} />}
<FlashList
bounces={false}
data={items.placeholders || []}
extraData={items}
data={items.placeholders}
renderItem={renderNote}
/>
</View>

View File

@@ -22,6 +22,7 @@ import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
@@ -30,12 +31,13 @@ import {
} from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store";
import { AppFontSize } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import DialogHeader from "../../dialog/dialog-header";
import List from "../../list";
import SheetProvider from "../../sheet-provider";
import { Button, ButtonProps } from "../../ui/button";
import { PressableProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
type RelationsListProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -64,7 +66,9 @@ export const RelationsList = ({
}: RelationsListProps) => {
const updater = useRelationStore((state) => state.updater);
const { colors } = useThemeColors();
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
const hasNoRelations = !items || items?.placeholders?.length === 0;
useEffect(() => {
@@ -120,6 +124,7 @@ export const RelationsList = ({
) : (
<List
data={items}
CustomListComponent={FlashList}
loading={false}
dataType={referenceType as any}
isRenderedInActionSheet={true}

View File

@@ -20,6 +20,7 @@ import dayjs from "dayjs";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
@@ -179,6 +180,7 @@ export default function ReminderNotify({
</Paragraph>
<List
data={references}
CustomListComponent={FlashList}
loading={false}
dataType="note"
isRenderedInActionSheet={true}

View File

@@ -114,8 +114,8 @@ export const UserSheet = () => {
progress ? `(${progress.current})` : ""
}`
: lastSyncStatus === SyncStatus.Failed
? strings.syncFailed()
: strings.synced()}{" "}
? strings.syncFailed()
: strings.synced()}{" "}
{!syncing ? (
<TimeSince
style={{
@@ -139,8 +139,8 @@ export const UserSheet = () => {
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
: isOffline
? colors.static.orange
: colors.success.icon
? colors.static.orange
: colors.success.icon
}
/>
</Paragraph>
@@ -165,8 +165,6 @@ export const UserSheet = () => {
message: strings.notLoggedIn(),
actionText: strings.loginMessageActionText(),
icon: "account-outline",
id: "log-in",
type: "normal",
onPress: () => {
ref.current?.hide();
Navigation.navigate("Auth", {

View File

@@ -16,7 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { create } from "zustand";
import create from "zustand";
export const useSideBarDraggingStore = create(() => ({
dragging: false

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
import { StoreApi, UseBoundStore } from "zustand";
import { UseBoundStore } from "zustand";
import { useTotalNotes } from "../../hooks/use-db-item";
import {
eSubscribeEvent,
@@ -59,7 +59,7 @@ export const NotebookItem = ({
selected?: boolean;
focused?: boolean;
selectionEnabled?: boolean;
selectionStore: UseBoundStore<StoreApi<SelectionStore>>;
selectionStore: UseBoundStore<SelectionStore>;
onItemUpdate: (id?: string) => void;
onPress?: () => void;
onLongPress?: () => void;
@@ -98,8 +98,8 @@ export const NotebookItem = ({
item.depth === 0
? undefined
: item.depth < 6
? 15 * item.depth
: 15 * 5,
? 15 * item.depth
: 15 * 5,
width: "100%",
marginTop: 2
}}
@@ -187,8 +187,8 @@ export const NotebookItem = ({
!item.hasChildren || disableExpand
? "book-outline"
: expanded
? "chevron-down"
: "chevron-right"
? "chevron-down"
: "chevron-right"
}
/>

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { FlashList } from "@shopify/flash-list";
import React, { useEffect, useState } from "react";
import { TextInput, View } from "react-native";
import { db } from "../../common/database";
@@ -39,7 +40,6 @@ import {
useSideMenuNotebookSelectionStore,
useSideMenuNotebookTreeStore
} from "./stores";
import { LegendList } from "@legendapp/list";
useSideMenuNotebookSelectionStore.setState({
multiSelect: true
});
@@ -50,8 +50,8 @@ export const SideMenuNotebooks = () => {
const [isLoading, setIsLoading] = useState(true);
const { colors } = useThemeColors();
const [filteredNotebooks, setFilteredNotebooks] = React.useState(notebooks);
const searchTimer = React.useRef<NodeJS.Timeout>(undefined);
const lastQuery = React.useRef<string>(undefined);
const searchTimer = React.useRef<NodeJS.Timeout>();
const lastQuery = React.useRef<string>();
const loadRootNotebooks = React.useCallback(async () => {
if (!filteredNotebooks) return;
const _notebooks: Notebook[] = [];
@@ -145,11 +145,12 @@ export const SideMenuNotebooks = () => {
/>
) : (
<>
<LegendList
<FlashList
data={tree}
bounces={false}
bouncesZoom={false}
overScrollMode="never"
// keyExtractor={(item,) => item.notebook.id}
estimatedItemSize={30}
ListHeaderComponent={
<View

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Tag, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { FlashList } from "@shopify/flash-list";
import React, { useEffect } from "react";
import { TextInput, View } from "react-native";
import { DatabaseLogger, db } from "../../common/database";
@@ -37,7 +38,6 @@ import Paragraph from "../ui/typography/paragraph";
import { SideMenuHeader } from "./side-menu-header";
import { SideMenuListEmpty } from "./side-menu-list-empty";
import { useSideMenuTagsSelectionStore } from "./stores";
import { LegendList, LegendListRenderItemProps } from "@legendapp/list";
const TagItem = (props: {
tags: VirtualizedGrouping<Tag>;
@@ -188,8 +188,8 @@ export const SideMenuTags = () => {
const { colors } = useThemeColors();
const [filteredTags, setFilteredTags] = React.useState(tags);
const [loading, setLoading] = React.useState(true);
const searchTimer = React.useRef<NodeJS.Timeout>(undefined);
const lastQuery = React.useRef<string>(undefined);
const searchTimer = React.useRef<NodeJS.Timeout>();
const lastQuery = React.useRef<string>();
useEffect(() => {
useSideMenuTagsSelectionStore.setState({
@@ -237,7 +237,7 @@ export const SideMenuTags = () => {
}, [updateTags, isLoading]);
const renderItem = React.useCallback(
(info: LegendListRenderItemProps<any, any>) => {
(info: { index: number }) => {
return <TagItem id={info.index} tags={filteredTags!} />;
},
[filteredTags]
@@ -256,9 +256,8 @@ export const SideMenuTags = () => {
/>
) : (
<>
<LegendList
data={filteredTags?.placeholders || []}
extraData={filteredTags}
<FlashList
data={filteredTags?.placeholders}
bounces={false}
estimatedItemSize={35}
bouncesZoom={false}

View File

@@ -0,0 +1,251 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import React from "react";
import { ActivityIndicator, Image, Platform, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useSyncProgress from "../../hooks/use-sync-progress";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { AuthMode } from "../auth/common";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles";
export const UserStatus = () => {
const { colors, isDark } = useThemeColors();
const user = useUserStore((state) => state.user);
const syncing = useUserStore((state) => state.syncing);
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
const lastSynced = useUserStore((state) => state.lastSynced);
const insets = useGlobalSafeAreaInsets();
const { isInternetReachable } = useNetInfo();
const isOffline = !isInternetReachable;
const { progress } = useSyncProgress();
const userProfile = useUserStore((state) => state.profile);
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingBottom: Platform.OS === "ios" ? insets.bottom / 2 : null,
borderTopWidth: 1,
borderTopColor: colors.primary.border,
backgroundColor: colors.primary.background
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Pressable
onPress={() => {
Navigation.navigate("Settings");
fluidTabsRef.current.closeDrawer();
}}
type="plain"
style={{
flexDirection: "row",
justifyContent: "flex-start",
padding: DefaultAppStyles.GAP,
borderRadius: 0,
alignItems: "center",
gap: 10
}}
>
{userProfile?.profilePicture ? (
<Image
source={{
uri: userProfile?.profilePicture
}}
style={{
width: 35,
height: 35,
borderRadius: 100
}}
/>
) : (
<Icon
name="cog-outline"
size={AppFontSize.lg - 2}
color={colors.secondary.icon}
style={{
paddingLeft: 8
}}
/>
)}
<View
style={{
flexShrink: 1,
flexGrow: 1
}}
>
<Paragraph
numberOfLines={1}
size={AppFontSize.sm}
color={colors.primary.heading}
>
{!user || !userProfile?.fullName
? strings.settings()
: userProfile.fullName}
</Paragraph>
<Paragraph
style={{
flexWrap: "wrap"
}}
size={AppFontSize.xs}
color={colors.secondary.heading}
>
{!user ? (
strings.notLoggedIn()
) : lastSynced && lastSynced !== "Never" ? (
<>
{syncing
? `${strings.syncing()} ${
progress ? `(${progress.current})` : ""
}`
: lastSyncStatus === SyncStatus.Failed
? strings.syncFailed()
: strings.synced()}{" "}
{!syncing ? (
<TimeSince
style={{
fontSize: AppFontSize.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
) : null}
{isOffline ? ` (${strings.offline()})` : ""}
</>
) : (
strings.never()
)}{" "}
<Icon
name="checkbox-blank-circle"
size={9}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
: isOffline
? colors.static.orange
: colors.success.icon
}
/>
</Paragraph>
</View>
<View
style={{
flexDirection: "row",
gap: 0
}}
>
<IconButton
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
useThemeStore.getState().setColorScheme();
}}
name="theme-light-dark"
color={isDark ? colors.primary.accent : colors.primary.icon}
size={AppFontSize.lg}
style={{
borderRadius: 100,
width: 40,
height: 40
}}
/>
<Pressable
style={{
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
if (user) {
Sync.run();
} else {
fluidTabsRef.current?.closeDrawer();
Navigation.navigate("Auth", {
mode: AuthMode.login
});
}
}}
>
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={AppFontSize.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={AppFontSize.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.icon}
name="sync"
size={AppFontSize.lg}
/>
)
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
size={AppFontSize.lg}
name="login"
/>
)}
</Pressable>
</View>
</Pressable>
</View>
</View>
);
};

View File

@@ -46,7 +46,7 @@ import { DefaultAppStyles } from "../../utils/styles";
export const Toast = ({ context = "global" }) => {
const { colors, isDark } = useThemeColors();
const [toastOptions, setToastOptions] = useState<ToastOptions | undefined>();
const hideTimeout = useRef<NodeJS.Timeout | undefined>(undefined);
const hideTimeout = useRef<NodeJS.Timeout | undefined>();
const insets = useGlobalSafeAreaInsets();
const [visible, setVisible] = useState(false);
const toastMessages = useRef<ToastOptions[]>([]);
@@ -158,22 +158,22 @@ export const Toast = ({ context = "global" }) => {
toastOptions.icon
? toastOptions.icon
: toastOptions.type === "success"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
}
size={isFullToastMessage ? AppFontSize.xxxl : AppFontSize.xl}
color={
toastOptions?.icon
? toastOptions?.icon
: toastOptions.type === "error"
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
}
/>

View File

@@ -55,7 +55,6 @@ export interface ButtonProps extends PressableProps {
iconColor?: ColorValue;
iconStyle?: TextStyle;
proTag?: boolean;
allowFontScaling?: boolean;
}
export const Button = ({
height = 45,
@@ -80,7 +79,6 @@ export const Button = ({
fwdRef,
proTag,
iconStyle,
allowFontScaling = true,
...restProps
}: ButtonProps) => {
const { text } = useButton({
@@ -140,7 +138,7 @@ export const Button = ({
{icon && !loading && iconPosition === "left" ? (
<Icon
name={icon}
allowFontScaling={allowFontScaling}
allowFontScaling
style={[{ marginRight: 0 }, iconStyle as any]}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
@@ -152,7 +150,6 @@ export const Button = ({
color={textColor as string}
size={fontSize}
numberOfLines={1}
allowFontScaling={allowFontScaling}
style={[
{
marginLeft: icon || (loading && iconPosition === "left") ? 5 : 0,

View File

@@ -45,7 +45,7 @@ import isURL from "validator/lib/isURL";
import { DefaultAppStyles } from "../../../utils/styles";
interface InputProps extends TextInputProps {
fwdRef?: RefObject<TextInput | null>;
fwdRef?: RefObject<TextInput>;
validationType?:
| "password"
| "email"
@@ -121,8 +121,8 @@ const Input = ({
const color = error
? colors.error.border
: focus
? customColor || colors.selected.border
: colors.primary.border;
? customColor || colors.selected.border
: colors.primary.border;
const validate = async (value: string) => {
if (!validationType) return;
@@ -237,8 +237,8 @@ const Input = ({
onPress && loading ? colors.primary.accent : colors.primary.paragraph,
flexGrow: 1,
flexShrink: 1,
paddingBottom: DefaultAppStyles.GAP_VERTICAL,
paddingTop: DefaultAppStyles.GAP_VERTICAL,
paddingBottom: DefaultAppStyles.GAP_VERTICAL - 4,
paddingTop: DefaultAppStyles.GAP_VERTICAL - 4,
fontFamily: "Inter-Regular",
...(inputStyle as ViewStyle)
};

View File

@@ -44,7 +44,7 @@ export interface PressableProps extends RNPressableProps {
customSelectedColor?: ColorValue;
customAlpha?: number;
customOpacity?: number;
fwdRef?: RefObject<View | null>;
fwdRef?: RefObject<View>;
hidden?: boolean;
}
@@ -268,8 +268,8 @@ export const Pressable = ({
const opacity = customOpacity
? customOpacity
: type === "accent"
? 1
: colorOpacity;
? 1
: colorOpacity;
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
const { fontScale } = useWindowDimensions();
const growFactor = 1 + (fontScale - 1) / 8;

View File

@@ -59,15 +59,16 @@ const SheetWrapper = ({
const insets = useGlobalSafeAreaInsets();
const lockEvents = useRef(false);
const locked = useUserStore((state) => state.appLocked);
let width = dimensions.width > 600 ? 600 : 500;
const isGestureNavigationEnabled =
NotesnookModule.isGestureNavigationEnabled();
const bottomInsets = insets.bottom || (isGestureNavigationEnabled ? 20 : 49);
const style = React.useMemo(() => {
return {
width: largeTablet || smallTablet ? width : "100%",
backgroundColor: colors.primary.background,
zIndex: 10,
paddingTop: 5,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
alignSelf: "center",
@@ -76,7 +77,7 @@ const SheetWrapper = ({
...getContainerBorder(colors.primary.border, 0.5),
borderBottomWidth: 0,
paddingBottom:
Platform.OS === "android" && !bottomInsets
Platform.OS === "android" && !insets.bottom
? isGestureNavigationEnabled
? 0
: 30
@@ -131,10 +132,15 @@ const SheetWrapper = ({
width: 100,
backgroundColor: colors.secondary.background
}}
safeAreaInsets={{
...insets,
bottom: Platform.OS === "android" ? 0 : insets.bottom
}}
statusBarTranslucent
drawUnderStatusBar={true}
containerStyle={style}
gestureEnabled={gestureEnabled}
initialOffsetFromBottom={1}
onPositionChanged={onHasReachedTop}
closeOnTouchBackdrop={closeOnTouchBackdrop}
keyboardHandlerEnabled={
@@ -143,9 +149,14 @@ const SheetWrapper = ({
closeOnPressBack={closeOnTouchBackdrop}
indicatorColor={colors.secondary.background}
onOpen={_onOpen}
keyboardDismissMode="none"
enableGesturesInScrollView={enableGesturesInScrollView}
defaultOverlayOpacity={overlayOpacity}
overlayColor={colors.primary.backdrop}
keyboardShouldPersistTaps="always"
openAnimationConfig={{
friction: 9
}}
ExtraOverlayComponent={
<>
{overlay}
@@ -155,11 +166,10 @@ const SheetWrapper = ({
onClose={_onClose}
>
{children}
{bottomPadding ? (
<View
style={{
height: bottomInsets
height: insets.bottom !== 0 ? insets.bottom : 20
}}
/>
) : null}

View File

@@ -69,7 +69,6 @@ import { eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { resetStoredState } from "./use-stored-state";
export type ActionId =
| "select"
@@ -166,7 +165,7 @@ export const useActions = ({
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(item.id)
);
const processingId = useRef<"shareNote" | "copyContent">(undefined);
const processingId = useRef<"shareNote" | "copyContent">();
const user = useUserStore((state) => state.user);
const [notifPinned, setNotifPinned] = useState<DisplayedNotification>();
const [defaultNotebook, setDefaultNotebook] = useState(
@@ -706,26 +705,6 @@ export const useActions = ({
type: item.type
}
);
resetStoredState(
"app-home-navigtion-key",
isHomepage
? undefined
: {
name:
item.type === "notebook"
? "Notebook"
: item.type === "tag"
? "TaggedNotes"
: item.type === "color"
? "ColorNotes"
: undefined,
params: {
item: item,
id: item.id
}
}
);
}
});
}

View File

@@ -341,36 +341,24 @@ const onSubscriptionError = async (error: RNIap.PurchaseError) => {
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
const setAppMessage = async () => {
const user = await db.user.getUser();
if (!user) {
setLoginMessage();
return;
}
if (!user?.isEmailConfirmed) {
setEmailVerifyMessage();
return;
}
if (await checkForRateAppRequest()) return;
if (
user?.isEmailConfirmed &&
!SettingsService.get().recoveryKeySaved &&
!useMessageStore.getState().message?.visible
) {
setRecoveryKeyMessage();
return;
}
useMessageStore.getState().setAnnouncement();
checkAppUpdateAvailable();
};
const doAppLoadActions = async () => {
if (SettingsService.get().sessionExpired) {
eSendEvent(eLoginSessionExpired);
return;
}
await useMessageStore.getState().setAnnouncement();
notifee.setBadgeCount(0);
if (!(await db.user.getUser())) {
setLoginMessage();
return;
}
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (await checkForRateAppRequest()) return;
if (SettingsService.get().introCompleted) {
useMessageStore.subscribe((state) => {
const dialogs = state.dialogs;
@@ -413,7 +401,7 @@ const checkForRateAppRequest = async () => {
!useMessageStore.getState().message?.visible
) {
setRateAppMessage();
return true;
return false;
}
return false;
};
@@ -441,6 +429,7 @@ const initializeDatabase = async (password?: string) => {
try {
await setupDatabase(password);
await db.init();
initialize();
Sync.run();
} catch (e) {
DatabaseLogger.error(e as Error);
@@ -451,7 +440,6 @@ const initializeDatabase = async (password?: string) => {
if (IsDatabaseMigrationRequired()) return;
if (db.isInitialized) {
await setAppMessage();
useSettingStore.getState().setAppLoading(false);
Notifications.setupReminders(true);
if (SettingsService.get().notifNotes) {
@@ -596,6 +584,8 @@ export const useAppEvents = () => {
syncedOnLaunch.current = true;
return;
}
clearMessage();
subscribeToPurchaseListeners();
if (!isLogin) {
user = await db.user.fetchUser();
@@ -606,11 +596,6 @@ export const useAppEvents = () => {
});
}
if (useMessageStore.getState().message.id === "log-in") {
clearMessage();
setAppMessage();
}
await PremiumService.setPremiumStatus();
if (user?.isEmailConfirmed && !isUserEmailConfirmed) {
setTimeout(() => {
@@ -624,6 +609,16 @@ export const useAppEvents = () => {
ToastManager.error(e as Error, "Error updating user", "global");
}
user = await db.user.getUser();
if (
user?.isEmailConfirmed &&
!SettingsService.get().recoveryKeySaved &&
!useMessageStore.getState().message?.visible
) {
setRecoveryKeyMessage();
}
if (!user?.isEmailConfirmed) setEmailVerifyMessage();
syncedOnLaunch.current = true;
if (!isLogin) {
checkAutoBackup();

View File

@@ -59,8 +59,8 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
onItemUpdated?: (item?: ItemTypeKey[T]) => void
): [ItemTypeKey[T] | undefined, () => void] => {
const [item, setItem] = useState<ItemTypeKey[T]>();
const itemIdRef = useRef<string>(undefined);
const prevIdOrIndexRef = useRef<string | number>(undefined);
const itemIdRef = useRef<string>();
const prevIdOrIndexRef = useRef<string | number>();
if (prevIdOrIndexRef.current !== idOrIndex) {
itemIdRef.current = undefined;

View File

@@ -276,8 +276,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? (product as Plan).price.gross
: (product as Plan).period === "5-year"
? (product as Plan).price.gross
: (product as Plan).price.gross
? (product as Plan).price.gross
: (product as Plan).price.gross
}`;
}
@@ -432,8 +432,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
return period.endsWith("W")
? "week"
: period.endsWith("M")
? "month"
: "year";
? "month"
: "year";
} else {
const unit = (product as RNIap.SubscriptionIOS)
?.subscriptionPeriodUnitIOS;
@@ -475,8 +475,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
type: phase.billingPeriod.endsWith("W")
? "week"
: phase.billingPeriod.endsWith("M")
? "month"
: "year"
? "month"
: "year"
};
} else {
const productIos = product as RNIap.SubscriptionIOS;
@@ -607,8 +607,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
(product as Plan).period === "yearly"
? ((product as Plan).price.gross / 12).toFixed(2)
: (product as Plan).period === "5-year"
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
}`;
}

View File

@@ -26,7 +26,7 @@ import { useEffect, useRef, useState } from "react";
function useRotator<T>(data: T[], interval = 3000): T | null {
//@ts-ignore Added sample() method to Array.prototype to get random value.
const [current, setCurrent] = useState<T>(data.sample());
const intervalRef = useRef<NodeJS.Timeout>(undefined);
const intervalRef = useRef<NodeJS.Timer>();
useEffect(() => {
intervalRef.current = setInterval(() => {

View File

@@ -38,23 +38,12 @@ export function useStoredValue<T>(
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
value: next
value: value
});
});
},
reset() {
MMKV.removeItem(refKey);
setValue(initialValue);
}
};
}
export function resetStoredState(forKey: string, value?: any) {
if (value) {
MMKV.setMap(`storedState:${forKey}`, {
value: value
});
} else {
MMKV.removeItem(`storedState:${forKey}`);
}
}

View File

@@ -31,7 +31,7 @@ function getSecondsLeft(id?: string) {
const useTimer = (initialId?: string) => {
const [id, setId] = useState(initialId);
const [seconds, setSeconds] = useState(getSecondsLeft(id));
const interval = useRef<NodeJS.Timeout>(undefined);
const interval = useRef<NodeJS.Timer>();
const start = (sec: number, currentId = id) => {
if (!currentId) return;

View File

@@ -53,7 +53,7 @@ export const hideAllTooltips = async () => {
*/
const useTooltip = () => {
const { colors, isDark } = useThemeColors();
const parent = useRef(undefined);
const parent = useRef();
const keyboard = useKeyboard();
useEffect(() => {

View File

@@ -93,7 +93,6 @@ export const FluidPanelsView = React.memo(
const insets = useGlobalSafeAreaInsets();
const animatedOpacity = useSharedValue(0);
const animatedTranslateY = useSharedValue(-9999);
const overlayRef = useRef<Animated.View>(null);
const [orientation, setOrientation] = useState<OrientationType>(
Orientation.getInitialOrientation()
@@ -244,8 +243,8 @@ export const FluidPanelsView = React.memo(
current === "tablet"
? size.width * 0.48
: current === "smallTablet"
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
zIndex: null,
paddingHorizontal: 0
}
@@ -295,8 +294,8 @@ export const FluidPanelsView = React.memo(
const nextDeviceMode = DDS.isLargeTablet()
? "tablet"
: DDS.isSmallTab
? "smallTablet"
: "mobile";
? "smallTablet"
: "mobile";
setDeviceMode(nextDeviceMode, size);
},
@@ -499,17 +498,13 @@ export const FluidPanelsView = React.memo(
/>
) : null}
<View
<SafeAreaView
style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right
flex: 1
}}
>
<AppNavigationStack />
</View>
</SafeAreaView>
</ScopedThemeProvider>
</View>

View File

@@ -21,6 +21,7 @@ import { useThemeColors } from "@notesnook/theme";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as React from "react";
import { db } from "../common/database";
import { hideAllTooltips } from "../hooks/use-tooltip";
import SettingsService from "../services/settings";
import useNavigationStore, {
@@ -30,19 +31,10 @@ import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import Navigation from "../services/navigation";
import { isFeatureAvailable } from "@notesnook/common";
import { useStoredValue } from "../hooks/use-stored-state";
import { db } from "../common/database";
import { useIsFeatureAvailable } from "@notesnook/common";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
const DEFAULT_HOME: {
name: string;
params: any;
} = {
name: "Notes",
params: undefined
};
let Notes: any = null;
let Notebook: any = null;
@@ -56,107 +48,101 @@ let ColoredNotes: any = null;
let Archive: any = null;
const AppNavigation = React.memo(
() => {
const customHomepageFeature = useIsFeatureAvailable("customHomepage");
const { colors } = useThemeColors();
const homepageV2 = useSettingStore((state) => state.settings.homepageV2);
const home = useStoredValue(
"app-home-navigtion-key",
!homepageV2 || homepageV2.id === DEFAULT_HOME.name
? DEFAULT_HOME
: undefined
);
const [home, setHome] = React.useState<{
name: string;
params: any;
}>();
const homepageV2 = SettingsService.get().homepageV2;
const loading = useSettingStore((state) => state.isAppLoading);
if (!home.value && !homepageV2) {
home.value = DEFAULT_HOME;
}
React.useEffect(() => {
if (!homepageV2 || loading) return;
(async () => {
isFeatureAvailable("customHomepage").then((value) => {
if (!value.isAllowed) {
home.value = DEFAULT_HOME;
SettingsService.setProperty("homepageV2", undefined);
}
});
if (!home.value) {
switch (homepageV2.type) {
case "notebook":
{
const notebook = await db.notebooks.notebook(homepageV2.id);
if (notebook) {
home.value = {
name: "Notebook",
params: {
item: notebook,
id: notebook.id,
title: notebook.title
}
};
return;
}
}
break;
case "color": {
const color = await db.colors.color(homepageV2.id);
if (color) {
home.value = {
name: "ColoredNotes",
params: {
item: color,
id: color.id,
title: color.title
}
};
return;
}
break;
}
case "tag": {
const tag = await db.tags.tag(homepageV2.id);
if (tag) {
home.value = {
name: "TaggedNotes",
params: {
item: tag,
id: tag.id,
title: tag.title
}
};
return;
}
break;
}
case "default":
{
home.value = DEFAULT_HOME;
}
return;
}
home.value = undefined;
setTimeout(() => {
home.value = DEFAULT_HOME;
if (loading) return;
if (!homepageV2 || !customHomepageFeature?.isAllowed) {
setHome({
name: "Notes",
params: undefined
});
return;
}
switch (homepageV2.type) {
case "notebook":
{
const notebook = await db.notebooks.notebook(homepageV2.id);
if (notebook) {
setHome({
name: "Notebook",
params: {
item: notebook,
id: notebook.id,
title: notebook.title
}
});
return;
}
}
break;
case "color": {
const color = await db.colors.color(homepageV2.id);
if (color) {
setHome({
name: "ColoredNotes",
params: {
item: color,
id: color.id,
title: color.title
}
});
return;
}
break;
}
case "tag": {
const tag = await db.tags.tag(homepageV2.id);
if (tag) {
setHome({
name: "TaggedNotes",
params: {
item: tag,
id: tag.id,
title: tag.title
}
});
return;
}
break;
}
case "default":
{
setHome({
name: homepageV2.id,
params: undefined
});
}
return;
}
setHome({
name: "Notes",
params: undefined
});
})();
}, [homepageV2, loading, home.value]);
}, [homepageV2, loading]);
React.useEffect(() => {
if (!home) return;
useNavigationStore.getState().update(home?.name as keyof RouteParams);
useNavigationStore
.getState()
.update(home.value?.name as keyof RouteParams);
useNavigationStore
.getState()
.setFocusedRouteId(home.value?.params?.id || home.value?.name);
.setFocusedRouteId(home?.params?.id || home?.name);
}, [home]);
return !home.value ? null : (
return !home ? null : (
<AppStack.Navigator
initialRouteName={home.value?.name}
initialRouteName={home.name}
screenOptions={{
headerShown: false,
animation: "none",
@@ -196,9 +182,7 @@ const AppNavigation = React.memo(
TaggedNotes || require("../screens/notes/tagged").default;
return TaggedNotes;
}}
initialParams={
home.value?.name === "TaggedNotes" ? home.value?.params : undefined
}
initialParams={home.name === "TaggedNotes" ? home.params : undefined}
/>
<AppStack.Screen
@@ -208,9 +192,7 @@ const AppNavigation = React.memo(
ColoredNotes || require("../screens/notes/colored").default;
return ColoredNotes;
}}
initialParams={
home.value?.name === "ColoredNotes" ? home.value?.params : undefined
}
initialParams={home.name === "ColoredNotes" ? home.params : undefined}
/>
<AppStack.Screen
@@ -244,9 +226,7 @@ const AppNavigation = React.memo(
Notebook = Notebook || require("../screens/notebook").default;
return Notebook;
}}
initialParams={
home.value?.name === "Notebook" ? home.value?.params : undefined
}
initialParams={home.name === "Notebook" ? home.params : undefined}
/>
<AppStack.Screen

View File

@@ -0,0 +1,51 @@
{
"name": "@notesnook/mobile-app",
"version": "1.0.0",
"main": "./App.js",
"license": "GPL-3.0-or-later",
"dependencies": {
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
"deprecated-react-native-prop-types": "^4.1.0",
"entities": "^3.0.1",
"fflate": "^0.7.3",
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"dayjs": "^1.11.13",
"react-native-actions-sheet": "0.9.7",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-progress": "^5.0.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-reanimated-progress-bar": "1.0.1",
"react-native-swiper-flatlist": "3.2.2",
"timeago.js": "4.0.2",
"toggle-switch-react-native": "3.2.0",
"url": "^0.11.0",
"validator": "^13.5.2",
"zustand": "^3.6.0",
"katex": "0.16.2",
"@readme/data-urls": "3.0.0",
"react-native-wheel-color-picker": "^1.3.1",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@streetwriters/kysely": "^0.27.4",
"pathe": "1.1.2",
"react-native-format-currency": "0.0.5",
"@lingui/core": "5.1.2",
"@lingui/react": "5.1.2",
"react-native-check-version": "^1.3.0",
"react-native-material-menu": "^2.0.0",
"@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@tanstack/react-query": "^4.36.1",
"async-mutex": "0.5.0",
"react-async-hook": "^4.0.0"
},
"sideEffects": false
}

View File

@@ -120,7 +120,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
);
const titleRef = useRef<TextInput>(null);
const descriptionRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>(undefined);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => {
setDatePickerVisibility(true);
@@ -390,64 +390,64 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
recurringMode === RecurringModes.Year
? null
: recurringMode === RecurringModes.Week
? WeekDays.map((item, index) => (
<Button
key={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(index) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index) > -1) {
days.splice(days.indexOf(index), 1);
return [...days];
}
days.push(index);
? WeekDays.map((item, index) => (
<Button
key={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
title={strings.weekDayNamesShort[
index as keyof typeof strings.weekDayNamesShort
]()}
type={
selectedDays.indexOf(index) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index) > -1) {
days.splice(days.indexOf(index), 1);
return [...days];
});
}}
/>
))
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
}
days.push(index + 1);
}
days.push(index);
return [...days];
});
}}
/>
))
: MonthDays.map((item, index) => (
<Button
key={index + "monthday"}
title={index + 1 + ""}
type={
selectedDays.indexOf(index + 1) > -1
? "selected"
: "plain"
}
fontSize={AppFontSize.xs}
style={{
height: 40,
borderRadius: 100,
marginRight: 10
}}
onPress={() => {
setSelectedDays((days) => {
if (days.indexOf(index + 1) > -1) {
days.splice(days.indexOf(index + 1), 1);
return [...days];
});
}}
/>
))}
}
days.push(index + 1);
return [...days];
});
}}
/>
))}
</ScrollView>
</View>
) : null}
@@ -476,7 +476,10 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
date={date}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={isDark ? colors.static.white : colors.static.black}
fadeToColor={colors.primary.background}
theme={isDark ? "dark" : "light"}
androidVariant="nativeAndroid"
is24hourSource="locale"
locale={
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
@@ -532,24 +535,24 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
dayjs(date).format("hh:mm A")
)
: recurringMode === RecurringModes.Year
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
? strings.reminderRepeatStrings.year(
dayjs(date).format("dddd, MMMM D, h:mm A")
)
: selectedDays.length === 7 &&
recurringMode === RecurringModes.Week
? strings.reminderRepeatStrings.week.daily(
dayjs(date).format("hh:mm A")
)
: selectedDays.length === 0
? strings.reminderRepeatStrings[
recurringMode as "week" | "month"
].selectDays()
: strings.reminderRepeatStrings.repeats(
repeatFrequency,
recurringMode as string,
getSelectedDaysText(selectedDays),
dayjs(date).format("hh:mm A")
)}
</Paragraph>
</>
</View>
@@ -589,8 +592,8 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
? "vibrate"
: "volume-high"
}
fontSize={AppFontSize.xs}
height={35}

View File

@@ -37,7 +37,7 @@ export const ProgressBar = () => {
const attachmentProgress = useAttachmentStore((state) => state.progress);
const [progress, setProgress] = useState(0);
const [visible, setVisible] = useState(false);
const timer = useRef<NodeJS.Timeout>(undefined);
const timer = useRef<NodeJS.Timeout>();
const insets = useGlobalSafeAreaInsets();
const [width, setWidth] = useState(0);

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