mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 02:29:18 +02:00
Compare commits
2 Commits
fix-file-e
...
mobile-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7fe799f22 | ||
|
|
d0e5bec780 |
6
.github/workflows/android.e2e.yml
vendored
6
.github/workflows/android.e2e.yml
vendored
@@ -55,13 +55,13 @@ jobs:
|
||||
- name: Detox build
|
||||
run: |
|
||||
yarn build:android
|
||||
ls apps/mobile/android/app/build/outputs/apk
|
||||
ls apps/mobile/android/app/build/outputs/apk/release
|
||||
ls apps/mobile/native/android/app/build/outputs/apk
|
||||
ls apps/mobile/native/android/app/build/outputs/apk/release
|
||||
|
||||
- name: Get device name
|
||||
id: device
|
||||
run: |
|
||||
AVD_NAME=$(node -p "require('./apps/mobile/.detoxrc.js').devices.emulator.device.avdName")
|
||||
AVD_NAME=$(node -p "require('./apps/mobile/native/.detoxrc.js').devices.emulator.device.avdName")
|
||||
echo "AVD_NAME=$AVD_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: AVD cache
|
||||
|
||||
46
.github/workflows/android.publish.internal.yml
vendored
46
.github/workflows/android.publish.internal.yml
vendored
@@ -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
|
||||
|
||||
46
.github/workflows/android.publish.yml
vendored
46
.github/workflows/android.publish.yml
vendored
@@ -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
|
||||
@@ -24,29 +25,13 @@ jobs:
|
||||
java-version: "17"
|
||||
architecture: "x64"
|
||||
|
||||
- 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
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
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
|
||||
@@ -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,26 +135,26 @@ 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
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ steps.package-version.outputs.version}}-android
|
||||
name: Notesnook Android v${{ steps.package-version.outputs.version}}
|
||||
tag_name: ${{ steps.package-version.outputs.current-version}}-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
|
||||
|
||||
- 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
|
||||
|
||||
2
.github/workflows/desktop.tests.yml
vendored
2
.github/workflows/desktop.tests.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
test-macos-x64:
|
||||
name: Test macOS x64
|
||||
needs: build
|
||||
runs-on: macos-15-intel
|
||||
runs-on: macos-13
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
|
||||
23
.github/workflows/ios.publish.yml
vendored
23
.github/workflows/ios.publish.yml
vendored
@@ -4,7 +4,7 @@ on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-26
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: "26.1.1"
|
||||
xcode-version: "16.1"
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.11
|
||||
with:
|
||||
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/ios/Podfile.lock')) }}
|
||||
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/native/ios/Podfile.lock')) }}
|
||||
max-size: 1500M
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ccache-
|
||||
@@ -55,13 +55,14 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
id: pods-cache
|
||||
with:
|
||||
path: apps/mobile/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
|
||||
path: apps/mobile/native/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/native/ios/Podfile.lock') }}
|
||||
|
||||
- name: Install Pods
|
||||
run: |
|
||||
cd apps/mobile/ios
|
||||
pod install
|
||||
cd apps/mobile/native/ios
|
||||
bundle install
|
||||
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install
|
||||
|
||||
- name: Check for typescript errors
|
||||
run: |
|
||||
@@ -78,9 +79,9 @@ jobs:
|
||||
bundle-identifier: org.streetwriters.notesnook
|
||||
scheme: Notesnook
|
||||
configuration: "Release"
|
||||
export-options: apps/mobile/ios/ExportOptions.plist
|
||||
project-path: apps/mobile/ios/Notesnook.xcodeproj
|
||||
workspace-path: apps/mobile/ios/Notesnook.xcworkspace
|
||||
export-options: apps/mobile/native/ios/ExportOptions.plist
|
||||
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
|
||||
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
|
||||
update-targets: |
|
||||
Notesnook
|
||||
Make Note
|
||||
@@ -116,5 +117,5 @@ jobs:
|
||||
name: Notesnook.zip
|
||||
path: |
|
||||
Notesnook.ipa
|
||||
apps/mobile/ios/**/*.map
|
||||
apps/mobile/native/ios/**/*.map
|
||||
packages/editor-mobile/sourcemaps/*.map
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -11,5 +11,5 @@
|
||||
"cache": true,
|
||||
"cacheStrategy": "content"
|
||||
},
|
||||
"git.rebaseWhenSync": false
|
||||
"git.rebaseWhenSync": true
|
||||
}
|
||||
|
||||
689
apps/desktop/package-lock.json
generated
689
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.7",
|
||||
"version": "3.3.5",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
@@ -36,8 +36,8 @@
|
||||
"electron-trpc": "0.7.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"icojs": "^0.19.5",
|
||||
"sqlite-better-trigram": "0.0.5",
|
||||
"sqlite3-fts5-html": "^0.0.6",
|
||||
"sqlite-better-trigram": "0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.4",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "3.24.3"
|
||||
|
||||
@@ -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 17 Pro Max"
|
||||
}
|
||||
},
|
||||
attached: {
|
||||
type: "android.attached",
|
||||
device: {
|
||||
adbName: ".*"
|
||||
}
|
||||
},
|
||||
emulator: {
|
||||
type: "android.emulator",
|
||||
device: {
|
||||
avdName: "Pixel_5_API_36"
|
||||
}
|
||||
}
|
||||
},
|
||||
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"
|
||||
}
|
||||
}
|
||||
};
|
||||
13
apps/mobile/.gitignore
vendored
13
apps/mobile/.gitignore
vendored
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -1,28 +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
|
||||
import org.wonday.orientation.OrientationActivityLifecycle;
|
||||
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()
|
||||
registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
|
||||
loadReactNative(this)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
- Get an overview of your year with Notesnook with Wrapped 2025
|
||||
- Cell selection and column resizing in tables
|
||||
- Added support for uploading large files in background
|
||||
- Bug fixes and minor improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -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')
|
||||
@@ -51,9 +51,8 @@ const { appLockEnabled, appLockMode } = SettingsService.get();
|
||||
if (appLockEnabled || appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
RNBootSplash.hide({
|
||||
fade: true
|
||||
});
|
||||
|
||||
RNBootSplash.hide();
|
||||
const App = (props: { configureMode: "note-preview" }) => {
|
||||
useAppEvents();
|
||||
//@ts-ignore
|
||||
@@ -64,7 +63,7 @@ const App = (props: { configureMode: "note-preview" }) => {
|
||||
setTimeout(async () => {
|
||||
await Notifications.get();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote();
|
||||
Notifications.pinQuickNote(true);
|
||||
}
|
||||
TipManager.init();
|
||||
}, 100);
|
||||
@@ -133,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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function downloadFile(
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
|
||||
const totalChunks = Math.ceil(size / chunkSize);
|
||||
const decryptedLength = size - totalChunks * ABYTES;
|
||||
|
||||
if (attachment && attachment.size !== decryptedLength) {
|
||||
@@ -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}`);
|
||||
});
|
||||
|
||||
@@ -222,21 +222,15 @@ export async function deleteCacheFileByName(name: string) {
|
||||
}
|
||||
|
||||
export async function deleteDCacheFiles() {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (
|
||||
file.includes("_dcache") ||
|
||||
file.startsWith("NN_") ||
|
||||
file.endsWith(".pdf")
|
||||
) {
|
||||
await RNFetchBlob.fs.unlink(file).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.includes("_dcache") || file.startsWith("NN_")) {
|
||||
await RNFetchBlob.fs.unlink(file).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCachePathForFile(filename: string) {
|
||||
|
||||
@@ -81,7 +81,7 @@ export async function uploadFile(
|
||||
let attachmentInfo = await db.attachments.attachment(filename);
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Starting upload of ${filename} at path: ${fileInfo.path} ${fileInfo.size}`
|
||||
`Starting upload of ${filename} at path: ${fileInfo.path}`
|
||||
);
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
@@ -113,7 +113,15 @@ export async function uploadFile(
|
||||
: attachmentInfo?.filename || "file",
|
||||
enabled: true,
|
||||
enableRingTone: true,
|
||||
autoClear: true
|
||||
autoClear: true,
|
||||
onProgressTitle: `Uploading ${attachmentInfo?.filename || "file"}`,
|
||||
onCancelledTitle: "Upload cancelled",
|
||||
onProgressMessage: "Uploading file",
|
||||
onErrorTitle: "Uploading failed",
|
||||
onErrorMessage: "Uploading failed",
|
||||
onCancelledMessage: "Cancelled",
|
||||
onCompleteTitle: "Upload complete",
|
||||
onCompleteMessage: "Uploading"
|
||||
}
|
||||
}).onChange((event) => {
|
||||
switch (event.status) {
|
||||
@@ -139,6 +147,7 @@ export async function uploadFile(
|
||||
break;
|
||||
}
|
||||
});
|
||||
console.log("STARTING UPLOAD");
|
||||
const result = await upload.start();
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
@@ -155,16 +155,16 @@ export async function checkUpload(
|
||||
expectedSize: number
|
||||
) {
|
||||
const size = await getUploadedFileSize(filename);
|
||||
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
|
||||
const totalChunks = Math.ceil(size / chunkSize);
|
||||
const decryptedLength = size - totalChunks * ABYTES;
|
||||
const error =
|
||||
size === 0
|
||||
? `File size is 0.`
|
||||
: size === -1
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -189,7 +189,3 @@ export async function checkAndCreateDir(path: string) {
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
export const santizeUri = (uri: string) => {
|
||||
return Platform.OS === "ios" ? decodeURI(uri).replace("file:///", "/") : uri;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
})
|
||||
)}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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)}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
114
apps/mobile/app/components/auth/auth-modal.js
Normal file
114
apps/mobile/app/components/auth/auth-modal.js
Normal 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;
|
||||
21
apps/mobile/app/components/auth/background.js
Normal file
21
apps/mobile/app/components/auth/background.js
Normal 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>`;
|
||||
@@ -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"
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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" />
|
||||
@@ -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"
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -324,9 +311,7 @@ export const Signup = ({
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
width: DDS.isTab ? "50%" : "100%",
|
||||
alignSelf: "center"
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
@@ -341,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"
|
||||
@@ -355,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"
|
||||
@@ -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
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -42,7 +42,9 @@ export default function DelayLayout({
|
||||
...props
|
||||
}: IDelayLayoutProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const loading = useDelayLayout(props.delay === undefined ? 200 : props.delay);
|
||||
const loading = useDelayLayout(
|
||||
!props.delay || props.delay < 300 ? 0 : props.delay
|
||||
);
|
||||
const Placeholder = placeholder[props.type || "default"];
|
||||
|
||||
return loading || props.wait ? (
|
||||
|
||||
@@ -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 />
|
||||
52
apps/mobile/app/components/dialog-provider/recievers.js
Normal file
52
apps/mobile/app/components/dialog-provider/recievers.js
Normal 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);
|
||||
};
|
||||
@@ -136,12 +136,12 @@ const BaseDialog = ({
|
||||
backgroundColor: background
|
||||
? background
|
||||
: transparent
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
enabled={!floating && !avoidKeyboardResize}
|
||||
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
|
||||
behavior="padding"
|
||||
>
|
||||
<BouncingView
|
||||
@@ -155,8 +155,8 @@ const BaseDialog = ({
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,7 +23,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import Orientation from "react-native-orientation-locker";
|
||||
import Pdf from "react-native-pdf";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { deleteCacheFileByPath, exists } from "../../../common/filesystem/io";
|
||||
@@ -44,7 +43,6 @@ import SheetProvider from "../../sheet-provider";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
|
||||
const WIN_WIDTH = Dimensions.get("window").width;
|
||||
const WIN_HEIGHT = Dimensions.get("window").height;
|
||||
@@ -119,11 +117,8 @@ const PDFPreview = () => {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
let path = `${cacheDir}/${attachment.filename}`;
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
snapshotValue.current = snapshot.current;
|
||||
await ReactNativeBlobUtil.fs
|
||||
.mv(`${cacheDir}/${uri}`, path)
|
||||
.catch(console.log);
|
||||
setPDFSource("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
@@ -132,7 +127,7 @@ const PDFPreview = () => {
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
deleteCacheFileByPath(pdfSource.replace("file://", ""));
|
||||
deleteCacheFileByPath(pdfSource);
|
||||
setPDFSource(null);
|
||||
setVisible(false);
|
||||
setPassword("");
|
||||
@@ -162,12 +157,7 @@ const PDFPreview = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog
|
||||
animation="fade"
|
||||
visible={true}
|
||||
onRequestClose={close}
|
||||
useSafeArea={false}
|
||||
>
|
||||
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
|
||||
<SheetProvider context={attachment?.hash} />
|
||||
<Dialog context={attachment?.hash} />
|
||||
|
||||
@@ -233,48 +223,45 @@ const PDFPreview = () => {
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: DefaultAppStyles.GAP_SMALL
|
||||
alignItems: "center",
|
||||
marginRight: 12
|
||||
}}
|
||||
>
|
||||
<View
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
color: colors.primary.paragraph,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "Inter-Regular"
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "Inter-Regular"
|
||||
}}
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(
|
||||
parseInt(event.nativeEvent.text)
|
||||
);
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.static.white}>
|
||||
/{numPages}
|
||||
</Paragraph>
|
||||
</View>
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.static.white}>/{numPages}</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.static.white}
|
||||
name="download"
|
||||
@@ -282,16 +269,6 @@ const PDFPreview = () => {
|
||||
downloadAttachment(attachment.hash, false);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
color={colors.static.white}
|
||||
name="open-in-new"
|
||||
onPress={() => {
|
||||
FileViewer.open(pdfSource, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{pdfSource ? (
|
||||
@@ -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;
|
||||
|
||||
145
apps/mobile/app/components/dialogs/result/index.js
Normal file
145
apps/mobile/app/components/dialogs/result/index.js
Normal 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;
|
||||
104
apps/mobile/app/components/dialogs/result/pro-features.js
Normal file
104
apps/mobile/app/components/dialogs/result/pro-features.js
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -96,22 +96,22 @@ export class VaultDialog extends Component {
|
||||
this.title = !this.state.novault
|
||||
? strings.createVault()
|
||||
: this.state.fingerprintAccess
|
||||
? strings.vaultFingerprintUnlock()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revokeVaultFingerprintUnlock()
|
||||
: this.state.changePassword
|
||||
? strings.changeVaultPassword()
|
||||
: this.state.noteLocked
|
||||
? this.state.deleteNote
|
||||
? strings.deleteNote()
|
||||
: this.state.share
|
||||
? strings.shareNote()
|
||||
: this.state.copyNote
|
||||
? strings.copyNote()
|
||||
: this.state.goToEditor
|
||||
? strings.goToEditor()
|
||||
: strings.goToEditor()
|
||||
: strings.lockNote();
|
||||
? strings.vaultFingerprintUnlock()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revokeVaultFingerprintUnlock()
|
||||
: this.state.changePassword
|
||||
? strings.changeVaultPassword()
|
||||
: this.state.noteLocked
|
||||
? this.state.deleteNote
|
||||
? strings.deleteNote()
|
||||
: this.state.share
|
||||
? strings.shareNote()
|
||||
: this.state.copyNote
|
||||
? strings.copyNote()
|
||||
: this.state.goToEditor
|
||||
? strings.goToEditor()
|
||||
: strings.goToEditor()
|
||||
: strings.lockNote();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -337,13 +337,12 @@ export class VaultDialog extends Component {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
eSendEvent("vaultUpdated");
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.close();
|
||||
}, 100);
|
||||
this.close();
|
||||
} else {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
@@ -354,6 +353,9 @@ export class VaultDialog extends Component {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
};
|
||||
|
||||
clearVault = async () => {
|
||||
@@ -872,24 +874,24 @@ export class VaultDialog extends Component {
|
||||
deleteVault
|
||||
? strings.delete()
|
||||
: clearVault
|
||||
? strings.clear()
|
||||
: fingerprintAccess
|
||||
? strings.enable()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revoke()
|
||||
: changePassword
|
||||
? strings.change()
|
||||
: this.state.noteLocked
|
||||
? deleteNote
|
||||
? strings.delete()
|
||||
: share
|
||||
? strings.share()
|
||||
: goToEditor
|
||||
? strings.open()
|
||||
: strings.unlock()
|
||||
: !note.id
|
||||
? strings.create()
|
||||
: strings.lock()
|
||||
? strings.clear()
|
||||
: fingerprintAccess
|
||||
? strings.enable()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revoke()
|
||||
: changePassword
|
||||
? strings.change()
|
||||
: this.state.noteLocked
|
||||
? deleteNote
|
||||
? strings.delete()
|
||||
: share
|
||||
? strings.share()
|
||||
: goToEditor
|
||||
? strings.open()
|
||||
: strings.unlock()
|
||||
: !note.id
|
||||
? strings.create()
|
||||
: strings.lock()
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
@@ -31,7 +31,6 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
SharedValue,
|
||||
SnappySpringConfig,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -68,7 +67,7 @@ export interface TabsRef {
|
||||
page: () => FluidTabPage;
|
||||
setScrollEnabled: () => true;
|
||||
isDrawerOpen: () => boolean;
|
||||
node: RefObject<Animated.View | null>;
|
||||
node: RefObject<Animated.View>;
|
||||
tabChangedFromSwipeAction: SharedValue<boolean>;
|
||||
}
|
||||
|
||||
@@ -111,6 +110,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
const containerWidth = widths
|
||||
? widths.sidebar + widths.list + widths.editor
|
||||
: dimensions.width;
|
||||
|
||||
const drawerPosition = 0;
|
||||
const homePosition = widths.sidebar;
|
||||
const editorPosition = widths.sidebar + widths.list;
|
||||
@@ -354,10 +354,13 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
const finalValue = isSwipeLeft
|
||||
? translateX.value - velocityX / 40.0
|
||||
: translateX.value + velocityX / 40.0;
|
||||
const velocity = velocityX / 5000;
|
||||
|
||||
const animationConfig: WithSpringConfig = {
|
||||
velocity: velocity,
|
||||
...SnappySpringConfig
|
||||
velocity: velocityX,
|
||||
mass: 0.5,
|
||||
overshootClamping: true,
|
||||
damping: 800,
|
||||
stiffness: 800
|
||||
};
|
||||
|
||||
if (finalValue < homePosition) {
|
||||
@@ -440,7 +443,15 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
width: containerWidth,
|
||||
flexDirection: "row"
|
||||
},
|
||||
animatedStyles
|
||||
deviceMode === "tablet"
|
||||
? {
|
||||
transform: [
|
||||
{
|
||||
translateX: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
: animatedStyles
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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,
|
||||
@@ -994,7 +992,7 @@ const PricingPlanCard = ({
|
||||
PremiumService.get() &&
|
||||
(pricingPlans?.user?.subscription?.productId ===
|
||||
(product as RNIap.Subscription)?.productId ||
|
||||
pricingPlans?.user?.subscription?.productId?.startsWith(
|
||||
pricingPlans?.user?.subscription?.productId.startsWith(
|
||||
(product as RNIap.Subscription)?.productId
|
||||
));
|
||||
pricingPlans?.selectPlan(
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -352,7 +352,7 @@ const ProductItem = (props: {
|
||||
props.pricingPlans.isSubscribed() &&
|
||||
(props.pricingPlans.user?.subscription?.productId ===
|
||||
(product as RNIap.Subscription)?.productId ||
|
||||
props.pricingPlans.user?.subscription?.productId?.startsWith(
|
||||
props.pricingPlans.user?.subscription?.productId.startsWith(
|
||||
(product as RNIap.Subscription)?.productId
|
||||
) ||
|
||||
props.pricingPlans.user?.subscription?.productId ===
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -103,7 +103,7 @@ export function PlanLimits() {
|
||||
|
||||
if (
|
||||
user?.subscription.plan !== SubscriptionPlan.FREE &&
|
||||
user?.subscription.productId?.includes("5year")
|
||||
user?.subscription.productId.includes("5year")
|
||||
) {
|
||||
ToastManager.show({
|
||||
message:
|
||||
|
||||
@@ -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%",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,6 @@ import { Button } from "../ui/button";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../sheets/paywall";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
const renderScene = SceneMap({
|
||||
home: SideMenuHome,
|
||||
notebooks: SideMenuNotebooks,
|
||||
@@ -69,7 +68,6 @@ const renderScene = SceneMap({
|
||||
export const SideMenu = React.memo(
|
||||
function SideMenu() {
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [index, setIndex] = React.useState(
|
||||
SettingsService.getProperty("defaultSidebarTab")
|
||||
);
|
||||
@@ -89,13 +87,10 @@ export const SideMenu = React.memo(
|
||||
]);
|
||||
|
||||
return (
|
||||
<View
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: insets.top,
|
||||
paddingBottom: insets.bottom,
|
||||
paddingLeft: insets.left
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<TabView
|
||||
@@ -108,7 +103,7 @@ export const SideMenu = React.memo(
|
||||
animationEnabled={false}
|
||||
lazy
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
},
|
||||
() => true
|
||||
@@ -356,8 +351,9 @@ const TabBar = (
|
||||
color={colors.primary.icon}
|
||||
onPress={async () => {
|
||||
if (props.navigationState.index === 1) {
|
||||
const notebooksFeature =
|
||||
await isFeatureAvailable("notebooks");
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
return;
|
||||
|
||||
@@ -104,12 +104,15 @@ function _MenuItem({
|
||||
const _onPress = () => {
|
||||
if (useSideBarDraggingStore.getState().dragging) return;
|
||||
if (item.onPress) return item.onPress(item);
|
||||
Navigation.closeDrawer();
|
||||
|
||||
if (useNavigationStore.getState().currentRoute !== item.id) {
|
||||
Navigation.navigate(item.id as keyof RouteParams, {
|
||||
canGoBack: false
|
||||
});
|
||||
}
|
||||
setImmediate(() => {
|
||||
Navigation.closeDrawer();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -151,8 +154,8 @@ function _MenuItem({
|
||||
item.icon === "crown"
|
||||
? colors.static.yellow
|
||||
: isFocused
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
size={AppFontSize.md}
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { SubscriptionPlan } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { DraxProvider, DraxScrollView } from "react-native-drax";
|
||||
@@ -141,35 +140,19 @@ export function SideMenuHome() {
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
{dayjs().month() !== 11 ? (
|
||||
<>
|
||||
{(subscriptionType === SubscriptionPlan.FREE ||
|
||||
!subscriptionType ||
|
||||
!user) &&
|
||||
!SettingsService.getProperty("serverUrls") ? (
|
||||
<Button
|
||||
title={pro.title}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
type="accent"
|
||||
onPress={pro.onPress}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
{(subscriptionType === SubscriptionPlan.FREE ||
|
||||
!subscriptionType ||
|
||||
!user) &&
|
||||
!SettingsService.getProperty("serverUrls") ? (
|
||||
<Button
|
||||
title={`Wrapped ${dayjs().year()} 🎉`}
|
||||
title={pro.title}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
bold
|
||||
type="secondaryAccented"
|
||||
onPress={() => {
|
||||
Navigation.navigate("Wrapped");
|
||||
}}
|
||||
type="accent"
|
||||
onPress={pro.onPress}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
251
apps/mobile/app/components/side-menu/user-status.js
Normal file
251
apps/mobile/app/components/side-menu/user-status.js
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user