Compare commits

..

1 Commits

Author SHA1 Message Date
Ammar Ahmed
bb42a2e1a4 ci: skip testflight 2024-08-08 10:19:10 +05:00
338 changed files with 85890 additions and 21357 deletions

View File

@@ -22,7 +22,7 @@ jobs:
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: "17"
java-version: "11"
architecture: "x64"
- name: Install node modules

View File

@@ -89,13 +89,13 @@ jobs:
- name: CCache Stats After Build
run: ccache -sv
- name: "Upload app to TestFlight"
uses: apple-actions/upload-testflight-build@v1
with:
app-path: Notesnook.ipa
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
api-key-id: ${{ secrets.API_KEY_ID }}
api-private-key: ${{ secrets.API_KEY }}
# - name: "Upload app to TestFlight"
# uses: apple-actions/upload-testflight-build@v1
# with:
# app-path: Notesnook.ipa
# issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
# api-key-id: ${{ secrets.API_KEY_ID }}
# api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
uses: actions/upload-artifact@v4

View File

@@ -1,34 +1,35 @@
{
"name": "@notesnook/desktop",
"version": "3.0.17",
"version": "3.0.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.17",
"version": "3.0.13",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.2.1",
"electron-trpc": "0.6.1",
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "^6.2.1",
"icojs": "^0.19.4",
"icojs": "^0.17.1",
"sodium-native": "^4.1.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "20.14.8",
"@types/yargs": "^17.0.33",
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^30.4.0",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.23.0",
"node-abi": "^3.67.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
"tree-kill": "^1.2.2",
@@ -41,7 +42,6 @@
"../../packages/crypto": {
"name": "@notesnook/crypto",
"version": "2.0.7",
"extraneous": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/sodium": "file:../sodium"
@@ -768,26 +768,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@jimp/bmp": {
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
"integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==",
"dependencies": {
"@jimp/utils": "^0.22.12",
"bmp-js": "^0.1.0"
},
"peerDependencies": {
"@jimp/custom": ">=0.3.5"
}
},
"node_modules/@jimp/utils": {
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
"integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
"dependencies": {
"regenerator-runtime": "^0.13.3"
}
},
"node_modules/@malept/cross-spawn-promise": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
@@ -840,6 +820,10 @@
"node": ">=10"
}
},
"node_modules/@notesnook/crypto": {
"resolved": "../../packages/crypto",
"link": true
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -850,11 +834,6 @@
"node": ">=14"
}
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@@ -867,15 +846,6 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@streetwriters/kysely": {
"version": "0.27.4",
"resolved": "https://registry.npmjs.org/@streetwriters/kysely/-/kysely-0.27.4.tgz",
"integrity": "sha512-c2p+wtX8WESSVjJAc16j4lT44w/g1DvJFEIeKj/CFy36vmi0uq9FOAzxc50VtRc7rp0UlECCfl3Sxd+BTZOHFg==",
"dev": true,
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@@ -903,20 +873,20 @@
}
},
"node_modules/@trpc/client": {
"version": "10.45.2",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.45.2.tgz",
"integrity": "sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==",
"version": "10.38.3",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.38.3.tgz",
"integrity": "sha512-hHPsElTL4sB+UGzuAZ4iwRVHjELrk/Nsl2owsYw9ITJz3EY7VWRsQ6vK48kJvlHPYPhP+efID5UOgyZMs4bFXA==",
"funding": [
"https://trpc.io/sponsor"
],
"peerDependencies": {
"@trpc/server": "10.45.2"
"@trpc/server": "10.38.3"
}
},
"node_modules/@trpc/server": {
"version": "10.45.2",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.45.2.tgz",
"integrity": "sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg==",
"version": "10.38.3",
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.38.3.tgz",
"integrity": "sha512-9s8/kwo2IDB5hwB2SKZZrfevRhdb1f9fdXtIYd3lbQuf2jQaC/LyQuHaIQjDQoUx9updBfsHXcFFPiCP1DL6pg==",
"funding": [
"https://trpc.io/sponsor"
]
@@ -973,13 +943,10 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.14.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz",
"integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==",
"devOptional": true,
"dependencies": {
"undici-types": "~5.26.4"
}
"version": "18.16.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz",
"integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==",
"devOptional": true
},
"node_modules/@types/plist": {
"version": "3.0.2",
@@ -1007,9 +974,9 @@
"optional": true
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
"integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
"version": "17.0.24",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
"integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
@@ -1245,9 +1212,9 @@
]
},
"node_modules/better-sqlite3-multiple-ciphers": {
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-11.2.1.tgz",
"integrity": "sha512-qYZ9GnZMnSOek/d00KnaXsECRFoMrNXSu9ubgeHaQi/5310+OQisaNo7FqBCCQlucPk15+etHgLwNHCeS6QzWQ==",
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-9.5.0.tgz",
"integrity": "sha512-6A9xBRvssONJW07Lur6pmkaUwlCGz1bnjFmXrrAR3qXETUBk92OI4C/+LhMOatn5wQ8AQzLScFZC/CKUpxkTaw==",
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.5.0",
@@ -1929,9 +1896,9 @@
}
},
"node_modules/electron": {
"version": "30.4.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-30.4.0.tgz",
"integrity": "sha512-ric3KLPQ9anXYjtTDkj5NbEcXZqRUwqxrxTviIjLdMdHqd5O+hkSHEzXgbSJUOt+7uw+zZuybn9+IM9y7iEpqg==",
"version": "29.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-29.3.1.tgz",
"integrity": "sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -1988,9 +1955,9 @@
}
},
"node_modules/electron-trpc": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.6.1.tgz",
"integrity": "sha512-Yj13UrjjjOW4+TD06YunAML2tgmtSYJMiFw6E6r1D4in/0EZ2MRwMoQc7AtMPAA6In23e+Kmq71e8h0ir+c3Jg==",
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
"integrity": "sha512-lJTeTwrd/E1C3VGFvdSBQggdmdDeHGrrpnuiB1e3bQR/1f91HmprySMygs9e4i12+CLqR+RIyKTQWLKCNjy5/Q==",
"dependencies": {
"debug": "^4.3.4"
},
@@ -2015,6 +1982,15 @@
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -2169,37 +2145,21 @@
}
},
"node_modules/file-type": {
"version": "19.4.1",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-19.4.1.tgz",
"integrity": "sha512-RuWzwF2L9tCHS76KR/Mdh+DwJZcFCzrhrPXpOw6MlEfl/o31fjpTikzcKlYuyeV7e7ftdCGVJTNOCzkYD/aLbw==",
"version": "16.5.4",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz",
"integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==",
"dependencies": {
"get-stream": "^9.0.1",
"strtok3": "^8.1.0",
"token-types": "^6.0.0",
"uint8array-extras": "^1.3.0"
"readable-web-to-node-stream": "^3.0.0",
"strtok3": "^6.2.4",
"token-types": "^4.1.1"
},
"engines": {
"node": ">=18"
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/file-type/node_modules/get-stream": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
"integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"dependencies": {
"@sec-ant/readable-stream": "^0.4.1",
"is-stream": "^4.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -2620,27 +2580,19 @@
}
},
"node_modules/icojs": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/icojs/-/icojs-0.19.4.tgz",
"integrity": "sha512-86oNepPk2jAmbb96BPeucZI7HoSBobFlXDhhjIbwRb3wkQpvdBO5HO9KtMUNzMFT3qqQZsjLsfW+L0/9Rl9VqA==",
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/icojs/-/icojs-0.17.1.tgz",
"integrity": "sha512-yGqa/9Uq82EAAq5V0Earig1SUubOi5ogRj/XEDiwAv/jewP9p3p/qIu/2zHWG5f8VKxRuNZB5xNgIzDxAxtPQg==",
"dependencies": {
"@jimp/bmp": "^0.22.12",
"bmp-js": "0.1.0",
"decode-ico": "^0.4.1",
"file-type": "^19.0.0",
"file-type": "^16.5.4",
"jpeg-js": "^0.4.4",
"pngjs": "^7.0.0",
"to-data-view": "^2.0.0"
"pngjs": "^6.0.0",
"to-data-view": "^1.1.0"
},
"engines": {
"node": ">=18.17.1"
}
},
"node_modules/icojs/node_modules/to-data-view": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-2.0.0.tgz",
"integrity": "sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=14.19.1"
}
},
"node_modules/iconv-corefoundation": {
@@ -2773,17 +2725,6 @@
"node": ">=0.12.0"
}
},
"node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isbinaryfile": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
@@ -2927,6 +2868,15 @@
"json-buffer": "3.0.1"
}
},
"node_modules/kysely": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.3.tgz",
"integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==",
"dev": true,
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/lazy-val": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
@@ -3105,9 +3055,9 @@
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node_modules/node-abi": {
"version": "3.67.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz",
"integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==",
"version": "3.54.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz",
"integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==",
"dependencies": {
"semver": "^7.3.5"
},
@@ -3125,7 +3075,6 @@
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
"dev": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@@ -3251,11 +3200,11 @@
}
},
"node_modules/peek-readable": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.2.0.tgz",
"integrity": "sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==",
"engines": {
"node": ">=14.16"
"node": ">=8"
},
"funding": {
"type": "github",
@@ -3295,11 +3244,11 @@
}
},
"node_modules/pngjs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
"engines": {
"node": ">=14.19.0"
"node": ">=12.13.0"
}
},
"node_modules/prebuild-install": {
@@ -3440,6 +3389,21 @@
"node": ">= 6"
}
},
"node_modules/readable-web-to-node-stream": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
"integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
"dependencies": {
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -3452,11 +3416,6 @@
"node": ">=8.10.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -3708,6 +3667,15 @@
"npm": ">= 3.0.0"
}
},
"node_modules/sodium-native": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz",
"integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.8.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -3812,15 +3780,15 @@
}
},
"node_modules/strtok3": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-8.1.0.tgz",
"integrity": "sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^5.1.4"
"peek-readable": "^4.1.0"
},
"engines": {
"node": ">=16"
"node": ">=10"
},
"funding": {
"type": "github",
@@ -3950,15 +3918,15 @@
}
},
"node_modules/token-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
"integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz",
"integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
"node": ">=10"
},
"funding": {
"type": "github",
@@ -4021,17 +3989,6 @@
"node": ">=14.17"
}
},
"node_modules/uint8array-extras": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
"integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.14.1.tgz",
@@ -4045,7 +4002,7 @@
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"devOptional": true
"dev": true
},
"node_modules/universalify": {
"version": "2.0.0",

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.17",
"version": "3.0.13",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -11,25 +11,26 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.2.1",
"electron-trpc": "0.6.1",
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "^6.2.1",
"icojs": "^0.19.4",
"icojs": "^0.17.1",
"sodium-native": "^4.1.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "20.14.8",
"@types/yargs": "^17.0.33",
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^30.4.0",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.23.0",
"node-abi": "^3.67.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
"tree-kill": "^1.2.2",
@@ -158,8 +159,7 @@
},
"nsis": {
"oneClick": true,
"createDesktopShortcut": "always",
"deleteAppDataOnUninstall": true
"createDesktopShortcut": "always"
},
"linux": {
"target": [

View File

@@ -94,7 +94,7 @@ async function patchBetterSQLite3() {
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "11.2.2";
json.version = "9.5.1";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { Database, Statement } from "better-sqlite3-multiple-ciphers";
import type { QueryResult } from "@streetwriters/kysely";
import type { QueryResult } from "kysely";
type SQLiteCompatibleType =
| number

View File

@@ -29,37 +29,19 @@ type Progress = { percent: number };
const t = initTRPC.create();
let cancellationToken: CancellationToken | undefined = undefined;
let downloadTimeout: NodeJS.Timeout | undefined = undefined;
export const updaterRouter = t.router({
autoUpdates: t.procedure.query(() => config.automaticUpdates),
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
download: t.procedure.query(async () => {
if (cancellationToken) return;
clearTimeout(downloadTimeout);
await new Promise<string[]>((resolve, reject) => {
downloadTimeout = setTimeout(async () => {
cancellationToken = new CancellationToken();
autoUpdater.isUpdaterActive();
await autoUpdater
.downloadUpdate(cancellationToken)
.then(resolve)
.catch(reject)
.finally(() => (cancellationToken = undefined));
}, 1000);
});
cancellationToken = new CancellationToken();
await autoUpdater
.downloadUpdate(cancellationToken)
.finally(() => (cancellationToken = undefined));
}),
check: t.procedure.query(async () => {
if (cancellationToken) return;
clearTimeout(downloadTimeout);
await new Promise<void>((resolve) => {
downloadTimeout = setTimeout(async () => {
await autoUpdater
.checkForUpdates()
.catch(console.error)
.finally(resolve);
}, 1000);
});
await autoUpdater.checkForUpdates().catch(console.error);
}),
toggleAutoUpdates: t.procedure

View File

@@ -20,14 +20,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { ELECTRON_TRPC_CHANNEL } from "electron-trpc/main";
import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
// import type { NNCrypto } from "@notesnook/crypto";
import type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
import { platform } from "os";
declare global {
var os: () => "mas" | ReturnType<typeof platform>;
var electronTRPC: RendererGlobalElectronTRPC;
// var NativeNNCrypto: (new () => NNCrypto) | undefined;
var NativeNNCrypto: (new () => NNCrypto) | undefined;
}
process.once("loaded", async () => {
@@ -40,5 +40,5 @@ process.once("loaded", async () => {
globalThis.electronTRPC = electronTRPC;
});
// globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeImage, nativeImage } from "electron";
import path from "path";
import { isDevelopment } from "./index";
import { ParsedImage, parseICO } from "icojs";
import { parse, ParsedImage } from "icojs";
import { getSystemTheme } from "./theme";
import { readFile } from "fs/promises";
@@ -71,7 +71,7 @@ export class AssetManager {
`${icon}${prefix}.ico`
);
const icoBuffer = await readFile(icoPath);
const images = await parseICO(icoBuffer, "image/png");
const images = await parse(icoBuffer, "image/png");
ALL_ICONS.push({ id: icon, images, prefix });
}
}

View File

@@ -38,7 +38,7 @@ DerivedData
*.ipa
*.xcuserstate
*.hprof
**/.xcode.env.local
ios/.xcode.env.local
# Android/IntelliJ
#
rn-build-deps/

View File

@@ -0,0 +1,156 @@
/*
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 { Platform } from "react-native";
import { MMKV } from "../database/mmkv";
import { useSettingStore } from "../../stores/use-setting-store";
const WEBSITE_ID = "3c6890ce-8410-49d5-8831-15fb2eb28a21";
const baseUrl = "https://analytics.streetwriters.co/api/collect";
const UA =
Platform.OS === "ios"
? "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1"
: `
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
/**
*
* @param {Routes} route
* @param {Routes[]} conditions
* @returns
*/
async function canUpdateAnalytics(route, conditions = []) {
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
}
if (eventsList && eventsList[route]) {
console.log("analytics: event already sent", route);
return false;
}
if (route !== "/welcome") {
for (let cond of conditions) {
if (!eventsList || !eventsList[cond]) {
console.log("analytics: conditions not met for event", route, cond);
return false;
}
}
}
console.log("analytics: will send event", route);
return true;
}
async function saveAnalytics(route, value = true) {
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
} else {
eventsList = {};
}
eventsList[route] = value;
MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList));
}
/**
*@typedef {"/welcome" | "/home" | "/signup" | "/first-note" | "/account-created" | "/pro-sheet" | "/pro-plans" | "/iap-native" | "/pro-screen" | "/editor" | "/editor-toolbar" | "/properties" | "/sidemenu"} Routes
* @param {Routes} prevRoute
* @param {Routes} route
* @param {Routes[]} conditions
* @param {boolean} once
* @returns
*/
async function pageView(
route,
prevRoute = "",
conditions = ["/welcome"],
once = true
) {
if (__DEV__) return;
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
let body = {
payload: {
website: WEBSITE_ID,
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
hostname: `notesnook-${Platform.OS}`,
language: "en-US",
screen: "1920x1080"
},
type: "pageview"
};
try {
let response = await fetch(baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});
console.log("analytics: event sent", route);
await saveAnalytics(route);
return await response.text();
} catch (e) {
console.log(e);
}
}
async function sendEvent(type, value, once = true) {
if (__DEV__) return;
if (!(await canUpdateAnalytics(type)) && once) return;
let body = {
payload: {
website: WEBSITE_ID,
url: "/",
event_type: type,
event_value: value,
hostname: "notesnook-android-app",
language: "en-US",
screen: "1920x1080"
},
type: "event"
};
try {
let response = await fetch(baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});
console.log(response.status);
return await response.text();
} catch (e) {
console.log(e);
}
}
export default {
sendEvent,
pageView,
saveAnalytics
};

View File

@@ -23,16 +23,34 @@ import { Platform } from "react-native";
import * as Gzip from "react-native-gzip";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import {
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from "@streetwriters/kysely";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import filesystem from "../filesystem";
import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
import SettingsService from "../../services/settings";
database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.43.5:5264",
// AUTH_HOST: "http://192.168.43.5:8264",
// SSE_HOST: "http://192.168.43.5:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.5:9264",
// ISSUES_HOST: "http://192.168.43.5:2624"
}
: {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
export async function setupDatabase(password) {
const key = await getDatabaseKey(password);
@@ -41,15 +59,6 @@ export async function setupDatabase(password) {
console.log("Opening database with key:", !!key);
database.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co",
...(SettingsService.getProperty("serverUrls") || {})
});
database.setup({
storage: Storage,
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,

View File

@@ -18,11 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { initialize } from "@notesnook/core/dist/logger";
import {
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from "@streetwriters/kysely";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import { Platform } from "react-native";
import { setLogger } from ".";
import { RNSqliteDriver } from "./sqlite.kysely";

View File

@@ -17,12 +17,8 @@ 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 type {
DatabaseConnection,
Driver,
QueryResult
} from "@streetwriters/kysely";
import { CompiledQuery } from "@streetwriters/kysely";
import type { DatabaseConnection, Driver, QueryResult } from "kysely";
import { CompiledQuery } from "kysely";
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
type Config = { dbName: string; async: boolean; location: string };

View File

@@ -21,15 +21,15 @@ import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import {
decrypt,
decryptMulti,
deriveCryptoKey,
encrypt,
encryptMulti,
generateCryptoKey,
getCryptoKey,
getRandomBytes,
hash,
removeCryptoKey
removeCryptoKey,
decryptMulti,
encryptMulti
} from "./encryption";
import { MMKV } from "./mmkv";

View File

@@ -20,6 +20,7 @@ import { Dimensions, Image, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "react-native-blob-util";
console.log("hello world");
/**
* Scale down & compress images to screen width
* for loading in editor.

View File

@@ -18,20 +18,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { getFileNameWithExtension } from "@notesnook/core/dist/utils/filename";
import {
getFileNameWithExtension,
isImage,
isDocument
} from "@notesnook/core/dist/utils/filename";
import React from "react";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { subscribe, zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { ToastManager, presentSheet } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import Storage from "../database/storage";
import { createCacheDir, exists } from "./io";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir, exists } from "./io";
import { IOS_APPGROUPID } from "../../utils/constants";
export const FileDownloadStatus = {
Success: 1,
@@ -207,11 +211,7 @@ export default async function downloadAttachment(
try {
await db
.fs()
.downloadFile(
options.groupId || attachment.hash,
attachment.hash,
attachment.chunkSize
);
.downloadFile(options.groupId || attachment.hash, attachment.hash);
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
@@ -259,6 +259,15 @@ export default async function downloadAttachment(
});
}
if (
attachment.dateUploaded &&
!isImage(attachment.mimeType) &&
!isDocument(attachment.mimeType)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
}
@@ -278,6 +287,7 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
DatabaseLogger.error(e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
@@ -286,7 +296,6 @@ export default async function downloadAttachment(
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
}
DatabaseLogger.error(e);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {
throw e;

View File

@@ -17,56 +17,32 @@ 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 hosts from "@notesnook/core/dist/utils/constants";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
import { ABYTES, cacheDir, getUploadedFileSize, parseS3Error } from "./utils";
export async function downloadFile(filename, requestOptions, cancelToken) {
if (!requestOptions) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: No requestOptions`
);
export async function downloadFile(filename, data, cancelToken) {
if (!data) {
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
return false;
}
DatabaseLogger.log(`Downloading ${filename}`);
await createCacheDir();
let { url, headers, chunkSize } = requestOptions;
let tempFilePath = `${cacheDir}/${filename}_temp`;
let originalFilePath = `${cacheDir}/${filename}`;
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
const attachment = await db.attachments.attachment(filename);
const size = await getUploadedFileSize(filename);
if (size === -1) {
const error = `Uploaded file verification failed. (File hash: ${filename})`;
throw new Error(error);
}
if (size === 0) {
const error = `File length is 0. Please upload this file again from the attachment manager. (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
const totalChunks = Math.ceil(size / chunkSize);
const decryptedLength = size - totalChunks * ABYTES;
if (attachment && attachment.size !== decryptedLength) {
const error = `File length mismatch. Expected ${attachment.size} but got ${decryptedLength} bytes. Please upload this file again from the attachment manager. (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
let res = await fetch(url, {
method: "GET",
headers
@@ -87,57 +63,34 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
);
throw new Error("Unable to resolve download url");
}
let totalSize = 0;
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
path: tempFilePath,
IOSBackgroundTask: true,
overwrite: true
path: path,
IOSBackgroundTask: true
})
.fetch("GET", downloadUrl, null)
.progress(async (recieved, total) => {
.progress((recieved, total) => {
useAttachmentStore
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
DatabaseLogger.log(`Download cancelled: ${filename}`);
};
let response = await request;
console.log(response.info().headers);
const contentType =
response.info().headers?.["content-type"] ||
response.info().headers?.["Content-Type"];
if (contentType === "application/xml") {
const error = parseS3Error(await response.text());
throw new Error(`[${error.Code}] ${error.Message}`);
}
await fileCheck(response, totalSize);
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
if (exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
if (!(await exists(filename))) {
throw new Error("File size mismatch");
}
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled" && !e.message.includes("NoSuchKey")) {
if (e.message !== "canceled") {
ToastManager.show({
heading: "Error downloading file",
message: e.message,
@@ -153,8 +106,7 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e, {
url,
headers
@@ -163,23 +115,37 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
}
}
export async function getUploadedFileSize(hash) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
DatabaseLogger.error(e);
return -1;
}
}
export async function checkAttachment(hash) {
const internetState = await NetInfo.fetch();
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
if (!isInternetReachable) return;
if (!isInternetReachable) return { success: true };
const attachment = await db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {
const size = await getUploadedFileSize(hash);
console.log("File Size", size);
if (size === -1) return { success: true };
if (size === 0)
return {
failed: `File length is 0. Please upload this file again from the attachment manager. (File hash: ${hash})`
};
if (size === 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e?.message };
}

View File

@@ -17,22 +17,17 @@ 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 { checkAttachment, downloadFile } from "./download";
import { checkAttachment, downloadFile, getUploadedFileSize } from "./download";
import {
clearFileStorage,
deleteFile,
exists,
readEncrypted,
writeEncryptedBase64,
hashBase64,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
hashBase64
} from "./io";
import { uploadFile } from "./upload";
import { cancelable, getUploadedFileSize } from "./utils";
import { cancelable } from "./utils";
export default {
readEncrypted,
@@ -44,10 +39,5 @@ export default {
exists,
clearFileStorage,
getUploadedFileSize,
checkAttachment,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
checkAttachment
};

View File

@@ -20,10 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { eSendEvent } from "../../services/event-manager";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { ABYTES, cacheDir, cacheDirOld, getRandomId } from "./utils";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
@@ -159,35 +158,7 @@ export async function migrateFilesFromCache() {
}
}
export async function clearCache() {
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
await createCacheDir();
eSendEvent("cache-cleared");
}
export async function deleteCacheFileByPath(path) {
await RNFetchBlob.fs.unlink(path).catch(console.log);
}
export async function deleteCacheFileByName(name) {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${name}`;
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
}
export async function deleteDCacheFiles() {
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache")) {
await RNFetchBlob.fs.unlink(file).catch(console.log);
}
}
}
const ABYTES = 17;
export async function exists(filename) {
let path = `${cacheDir}/${filename}`;
@@ -229,29 +200,3 @@ export async function exists(filename) {
}
return exists;
}
export async function bulkExists(files) {
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
let missingFiles = files.filter((file) => !cacheFiles.includes(file));
if (Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
missingFiles = missingFiles.filter((file) => !appGroupFiles.includes(file));
}
return missingFiles;
}
export async function getCacheSize() {
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);
stat.forEach((s) => {
total += parseInt(s.size);
});
return total;
}

View File

@@ -17,30 +17,32 @@ 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 { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
import { cacheDir, getUploadedFileSize } from "./utils";
import { getUploadedFileSize } from "./download";
import { ToastManager } from "../../services/event-manager";
export async function uploadFile(filename, requestOptions, cancelToken) {
if (!requestOptions) return false;
let { url, headers } = requestOptions;
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
await createCacheDir();
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
const uploadedFileSize = await getUploadedFileSize(filename);
if (uploadedFileSize === -1) {
const error = `Uploaded file verification failed. (File hash: ${filename})`;
throw new Error(error);
DatabaseLogger.log("Upload verification failed.");
return false;
}
if (uploadedFileSize !== 0) {
const isUploaded = uploadedFileSize !== 0;
if (isUploaded) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
@@ -115,6 +117,9 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
);
let attachment = await db.attachments.attachment(filename);
if (!attachment) return result;
if (!isImage(attachment.mimeType) && !isDocument(attachment.mimeType)) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
} else {
const fileInfo = await RNFetchBlob.fs.stat(uploadFilePath);
throw new Error(

View File

@@ -17,13 +17,10 @@ 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 * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { DatabaseLogger, db } from "../database";
import hosts from "@notesnook/core/dist/utils/constants";
export const ABYTES = 17;
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
export const cacheDir =
@@ -37,36 +34,24 @@ export function getRandomId(prefix) {
.replace("0.", prefix || "");
}
/**
*
* @param {string | undefined} data
* @returns
*/
export function parseS3Error(data) {
const xml = typeof data === "string" ? data : null;
export function extractValueFromXmlTag(code, xml) {
if (!xml.includes(code)) return `Unknown ${code}`;
return xml.slice(
xml.indexOf(`<${code}>`) + code.length + 2,
xml.indexOf(`</${code}>`)
);
}
const error = {
Code: "UNKNOWN",
Message: xml
};
try {
if (!xml) return error;
const doc = new DOMParser().parseFromString(xml, "text/xml");
const ErrorElement = doc.getElementsByTagName("Error")[0];
if (!ErrorElement) return error;
for (const child of ErrorElement.children) {
if (
child.textContent &&
(child.tagName === "Code" || child.tagName === "Message")
)
error[child.tagName] = child.textContent;
export async function fileCheck(response, totalSize) {
if (totalSize < 1000) {
let text = await response.text();
if (text.startsWith("<?xml")) {
let errorJson = {
Code: extractValueFromXmlTag("Code", text),
Message: extractValueFromXmlTag("Message", text)
};
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
}
return error;
} catch (e) {
return error;
}
}
@@ -74,11 +59,12 @@ export function cancelable(operation) {
const cancelToken = {
cancel: () => {}
};
return (filename, requestOptions) => {
return (filename, { url, headers }) => {
return {
execute: () => operation(filename, requestOptions, cancelToken),
execute: () => operation(filename, { url, headers }, cancelToken),
cancel: async () => {
await cancelToken.cancel();
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
};
};
@@ -105,21 +91,3 @@ export async function releasePermissions(path) {
}
}
}
export async function getUploadedFileSize(hash) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
DatabaseLogger.error(e);
return -1;
}
}

View File

@@ -118,14 +118,12 @@ const Actions = ({
setLoading({
name: "Run file check"
});
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
if (result.failed) {
db.attachments.markAsFailed(attachment.id, result.failed);
setFailed(result.failed);
const res = await filesystem.checkAttachment(attachment.hash);
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
ToastManager.show({
heading: "File check failed with error: " + result.failed,
heading: "File check failed with error: " + res.failed,
type: "error",
context: "local"
});

View File

@@ -122,11 +122,10 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
const attachment = (await attachments.item(i))?.item;
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
if (result.failed) {
await db.attachments.markAsFailed(attachment.hash, result.failed);
} else {
await db.attachments.markAsFailed(attachment.id);
await db.attachments.markAsFailed(attachment.id, undefined);
}
}
refresh();

View File

@@ -68,8 +68,7 @@ export const ChangePassword = () => {
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (result.error)
throw new Error(`Failed to create backup: ${result.error}`);
if (!result) throw new Error("Failed to create backup");
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);

View File

@@ -35,7 +35,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { hideAuth } from "./common";
import { useSettingStore } from "../../stores/use-setting-store";
import SettingsService from "../../services/settings";
export const Signup = ({ changeMode, trial }) => {
const { colors } = useThemeColors();
@@ -78,7 +77,6 @@ export const Signup = ({ changeMode, trial }) => {
clearMessage();
setEmailVerifyMessage();
hideAuth();
SettingsService.setProperty("encryptedBackup", true);
await sleep(300);
if (trial) {
PremiumService.sheet(null, null, true);

View File

@@ -83,7 +83,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
setLoading(false);
setTimeout(() => {
passwordInputRef.current?.focus();
}, 500);
}, 1);
callback && callback(true);
}
callback && callback(false);

View File

@@ -23,7 +23,6 @@ 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";
@@ -36,7 +35,8 @@ import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import Progress from "../dialogs/progress";
import RestoreDataSheet from "../sheets/restore-data";
import { AppLockPassword } from "../dialogs/applock-password";
const DialogProvider = () => {
const { colors } = useThemeColors();
@@ -52,6 +52,7 @@ const DialogProvider = () => {
<RecoveryKeySheet colors={colors} />
<SheetProvider />
<SheetProvider context="sync_progress" />
<RestoreDataSheet />
<ResultDialog />
<VaultDialog colors={colors} />
<RateAppSheet />
@@ -61,7 +62,6 @@ const DialogProvider = () => {
<SessionExpired />
<PDFPreview />
<JumpToSectionDialog />
<Progress />
</>
);
};

View File

@@ -32,29 +32,21 @@ const DialogContainer = ({
}: ViewProps & {
width?: any;
height?: any;
noBorder?: boolean;
}) => {
const { colors } = useThemeColors();
return (
<View
{...restProps}
style={[
style,
{
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
},
restProps?.noBorder
? {}
: {
...getElevationStyle(5),
...getContainerBorder(colors.secondary.background, 0.8, 0.05)
}
]}
style={{
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12,
...getContainerBorder(colors.secondary.background, 0.8, 0.05)
}}
/>
);
};

View File

@@ -25,7 +25,7 @@ type DialogInfo = {
paragraph?: string;
positiveText?: string;
negativeText?: string;
positivePress?: (...args: any[]) => void;
positivePress?: (value: any) => void;
onClose?: () => void;
positiveType?:
| "transparent"
@@ -45,15 +45,6 @@ type DialogInfo = {
context: "global" | "local" | (string & {});
secureTextEntry?: boolean;
keyboardType?: string;
check?: {
info: string;
type?: string;
defaultValue?: boolean;
};
notice: {
text: string;
type: "alert" | "information";
};
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -37,7 +37,6 @@ import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors";
import { Notice } from "../ui/notice";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
@@ -63,8 +62,7 @@ export const Dialog = ({ context = "global" }) => {
disableBackdropClosing: false,
check: {
info: "Check",
type: "transparent",
defaultValue: false
type: "transparent"
}
});
@@ -88,7 +86,7 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(data.check?.defaultValue);
setChecked(false);
values.current.inputValue = data.defaultValue;
setVisible(true);
},
@@ -182,19 +180,6 @@ export const Dialog = ({ context = "global" }) => {
</View>
) : null}
{dialogInfo?.notice ? (
<View
style={{
paddingHorizontal: 12
}}
>
<Notice
type={dialogInfo.notice.type || "information"}
text={dialogInfo.notice.text}
/>
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
@@ -213,7 +198,7 @@ export const Dialog = ({ context = "global" }) => {
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type || "plain" : "plain"}
type={checked ? dialogInfo.check.type || "selected" : "plain"}
/>
</>
) : null}

View File

@@ -44,7 +44,6 @@ import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
import { deleteCacheFileByPath } from "../../../common/filesystem/io";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
@@ -125,7 +124,6 @@ const PDFPreview = () => {
);
const close = () => {
deleteCacheFileByPath(pdfSource);
setPDFSource(null);
setVisible(false);
setPassword("");

View File

@@ -1,198 +0,0 @@
/*
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 } from "react";
import { View } from "react-native";
import { eSendEvent, eSubscribeEvent } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import BaseDialog from "../../dialog/base-dialog";
import DialogContainer from "../../dialog/dialog-container";
import { Button } from "../../ui/button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
export type ProgressOptions = {
progress?: string;
cancelCallback?: () => void;
title?: string;
paragraph?: string;
fillBackground?: boolean;
canHideProgress?: boolean;
};
export const PROGRESS_EVENTS = {
start: "startProgress",
end: "endProgress",
update: "updateProgress"
};
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>();
const [data, setData] = React.useState<{
title?: string;
paragraph?: string;
fillBackground?: boolean;
canHideProgress?: boolean;
}>();
useEffect(() => {
const events = [
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
setProgress(options.progress);
cancelCallback.current = options.cancelCallback;
console.log("options", options.fillBackground);
setData({
title: options.title,
paragraph: options.paragraph,
fillBackground: options.fillBackground,
canHideProgress: options.canHideProgress
});
setVisible(true);
}),
eSubscribeEvent(PROGRESS_EVENTS.end, () => {
setProgress(undefined);
setVisible(false);
setData(undefined);
cancelCallback.current?.();
cancelCallback.current = undefined;
}),
eSubscribeEvent(PROGRESS_EVENTS.update, (options: ProgressOptions) => {
setProgress(options.progress);
if (options.cancelCallback) {
cancelCallback.current = options.cancelCallback;
}
const data: ProgressOptions = {};
if (options.title) data.title = options.title;
if (options.paragraph) data.paragraph = options.paragraph;
if (options.fillBackground)
data.fillBackground = options.fillBackground;
setData((current) => {
return {
...current,
...data
};
});
})
];
return () => {
events.forEach((event) => event?.unsubscribe());
cancelCallback.current = undefined;
};
}, []);
return !visible ? null : (
<BaseDialog
background={data?.fillBackground ? colors.primary.background : undefined}
visible
>
<DialogContainer
style={{
paddingHorizontal: 12,
paddingBottom: 10
}}
noBorder={data?.fillBackground ? true : false}
>
<Dialog context="local" />
<View
style={{
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 12,
gap: 10,
paddingBottom: 20
}}
>
<Heading
style={{
textAlign: "center"
}}
color={colors.primary.paragraph}
size={SIZE.lg}
>
{data?.title}
</Heading>
<Paragraph
style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
>
{progress ? progress : data?.paragraph}
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
paddingVertical: 12
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
</View>
{data?.canHideProgress ? null : (
<Button
title={cancelCallback.current ? "Cancel" : "Hide"}
type="secondaryAccented"
onPress={() => {
if (cancelCallback.current) {
cancelCallback.current?.();
}
setVisible(false);
setProgress(undefined);
setData(undefined);
}}
width="100%"
/>
)}
</View>
</DialogContainer>
</BaseDialog>
);
}
export function startProgress(options: ProgressOptions) {
eSendEvent(PROGRESS_EVENTS.start, options);
}
export function endProgress() {
eSendEvent(PROGRESS_EVENTS.end);
}
export function updateProgress(options: ProgressOptions) {
eSendEvent(PROGRESS_EVENTS.update, options);
}

View File

@@ -43,6 +43,9 @@ import { eSendEvent } from "../../services/event-manager";
const Intro = ({ navigation }) => {
const { colors } = useThemeColors();
const isTelemetryEnabled = useSettingStore(
(state) => state.settings.telemetry
);
const { width, height } = useWindowDimensions();
const deviceMode = useSettingStore((state) => state.deviceMode);
const insets = useGlobalSafeAreaInsets();
@@ -214,6 +217,44 @@ const Intro = ({ navigation }) => {
type="accent"
title="Get started"
/>
<TouchableOpacity
activeOpacity={1}
style={{
flexDirection: "row",
alignSelf: "center",
width: "90%",
marginBottom: 12,
paddingHorizontal: 12,
justifyContent: "center",
padding: 12,
maxWidth: 500
}}
onPress={() => {
SettingsService.set({ telemetry: !isTelemetryEnabled });
}}
>
<Icon
size={SIZE.md}
name={
isTelemetryEnabled ? "checkbox-marked" : "checkbox-blank-outline"
}
color={
isTelemetryEnabled ? colors.primary.accent : colors.primary.icon
}
/>
<Paragraph
style={{
flexShrink: 1,
marginLeft: 6
}}
size={SIZE.xs}
>
Help improve Notesnook by sending completely anonymized{" "}
<Heading size={SIZE.xs}>private analytics and bug reports.</Heading>
</Paragraph>
</TouchableOpacity>
</View>
</ScrollView>
);

View File

@@ -167,7 +167,7 @@ export const Component = ({ close, promo }) => {
>
(
{Platform.OS === "android"
? pricing.product?.subscriptionOfferDetails[0]?.pricingPhases
? pricing.product?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: pricing.product?.localizedPrice}{" "}
/ mo)

View File

@@ -85,7 +85,7 @@ export const PricingItem = ({
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid | undefined)
?.subscriptionOfferDetails?.[0]?.pricingPhases
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0]?.formattedPrice
: (product.data as RNIap.SubscriptionIOS | undefined)
?.localizedPrice}

View File

@@ -393,7 +393,7 @@ export const PricingPlans = ({
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0]?.pricingPhases
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0]?.formattedPrice
: (product.data as RNIap.SubscriptionIOS)
?.introductoryPrice ||

View File

@@ -95,9 +95,9 @@ export default function Migrate() {
await sleep(1);
const { error, report } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error as Error, "Backup failed");
ToastManager.error(error, "Backup failed");
if (report) {
reportError(error as Error);
reportError(error);
}
setLoading(false);
return;

View File

@@ -0,0 +1,600 @@
/*
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 { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import {
ToastManager,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { refreshAllStores } from "../../../stores/create-db-collection-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { useUserStore } from "../../../stores/use-user-store";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false);
const sheet = useRef();
useEffect(() => {
const open = async () => {
setVisible(true);
setTimeout(() => {
sheet.current?.show();
}, 1);
};
eSubscribeEvent(eOpenRestoreDialog, open);
eSubscribeEvent(eCloseRestoreDialog, close);
return () => {
eUnSubscribeEvent(eOpenRestoreDialog, open);
eUnSubscribeEvent(eCloseRestoreDialog, close);
};
}, [close]);
const close = useCallback(() => {
if (restoring) {
showIsWorking();
return;
}
sheet.current?.hide();
setTimeout(() => {
setVisible(false);
}, 150);
}, [restoring]);
const showIsWorking = () => {
ToastManager.show({
heading: "Restoring Backup",
message: "Your backup data is being restored. please wait.",
type: "error",
context: "local"
});
};
return !visible ? null : (
<SheetWrapper
fwdRef={sheet}
gestureEnabled={!restoring}
closeOnTouchBackdrop={!restoring}
onClose={() => {
setVisible(false);
close();
}}
>
<RestoreDataComponent
close={close}
restoring={restoring}
setRestoring={setRestoring}
actionSheetRef={sheet}
/>
<Toast context="local" />
</SheetWrapper>
);
};
export default RestoreDataSheet;
const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const { colors } = useThemeColors();
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
const [progress, setProgress] = useState();
useEffect(() => {
const subscription = db.eventManager.subscribe(
EVENTS.migrationProgress,
(progress) => {
setProgress(progress);
}
);
return () => {
subscription?.unsubscribe();
};
}, []);
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 1000);
}, []);
const restore = async (item) => {
if (restoring) {
return;
}
try {
const file = Platform.OS === "ios" ? item.path : item.uri;
console.log(file);
if (file.endsWith(".nnbackupz")) {
setRestoring(true);
if (Platform.OS === "android") {
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
console.log("copying");
await copyFileAsync(file, cacheFile);
console.log("copied");
await restoreFromZip(cacheFile);
} else {
await restoreFromZip(file, false);
}
} else if (file.endsWith(".nnbackup")) {
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(file, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(file, "utf8");
}
await restoreFromNNBackup(JSON.parse(backup));
}
} catch (e) {
console.log("error", e);
setRestoring(false);
backupError(e);
}
};
const withPassword = () => {
return new Promise((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
value: password,
isEncryptionKey
});
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
};
const checkBackups = async () => {
try {
let files = [];
if (Platform.OS === "android") {
let backupDirectory = SettingsService.get().backupDirectoryAndroid;
if (backupDirectory) {
setBackupDirectoryAndroid(backupDirectory);
files = await ScopedStorage.listFiles(backupDirectory.uri);
} else {
setLoading(false);
return;
}
} else {
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files
.filter((file) => {
const name = Platform.OS === "android" ? file.name : file.filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
setLoading(false);
} catch (e) {
console.log(e);
setLoading(false);
}
};
const restoreBackup = async (backup, password, key) => {
await db.transaction(async () => {
await db.backup.import(backup, password, key);
});
await db.initCollections();
refreshAllStores();
ToastManager.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
Navigation.queueRoutesForUpdate();
return true;
};
const backupError = (e) => {
console.log(e.stack);
ToastManager.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
/**
*
* @param {string} file
*/
async function restoreFromZip(file, remove) {
try {
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
await unzip(file, zipOutputFolder);
console.log("Unzipped files successfully to", zipOutputFolder);
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
// throw new Error("Backup file is invalid");
// }
await db.transaction(async () => {
let password;
let key;
console.log(
`Found ${backupFiles?.length} files to restore from backup`
);
for (const path of backupFiles) {
if (path === ".nnbackup") continue;
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const parsed = JSON.parse(data);
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
const { value, isEncryptionKey } = await withPassword();
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (!password && !key) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password, key);
console.log("Imported", path);
}
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (remove) {
RNFetchBlob.fs.unlink(file).catch(console.log);
}
refreshAllStores();
Navigation.queueRoutesForUpdate();
setRestoring(false);
close();
ToastManager.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
} catch (e) {
backupError(e);
setRestoring(false);
}
}
/**
*
* @param {string} file
*/
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const { value, isEncryptionKey } = await withPassword();
let key;
let password;
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (key || password) {
try {
await restoreBackup(backup, password, key);
close();
setRestoring(false);
} catch (e) {
setRestoring(false);
backupError(e);
}
} else {
setRestoring(false);
}
} else {
await restoreBackup(backup);
setRestoring(false);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
}
const button = {
title: "Restore from files",
onPress: async () => {
if (restoring) {
return;
}
try {
useUserStore.setState({
disableAppLockRequests: true
});
console.log("disabled...");
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
if (file.name.endsWith(".nnbackupz")) {
setRestoring(true);
await restoreFromZip(file.fileCopyUri, true);
} else if (file.name.endsWith(".nnbackup")) {
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
setRestoring(true);
const data = await fetch(file.uri);
await restoreFromNNBackup(await data.json());
}
} catch (e) {
console.log("error", e.stack);
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
setRestoring(false);
backupError(e);
}
}
};
const renderItem = ({ item, index }) => (
<View
style={{
minHeight: 50,
justifyContent: "space-between",
alignItems: "center",
width: "100%",
borderRadius: 0,
flexDirection: "row",
borderBottomWidth: 0.5,
borderBottomColor: colors.primary.border
}}
>
<View
style={{
maxWidth: "75%"
}}
>
<Paragraph size={SIZE.sm} style={{ width: "100%", maxWidth: "100%" }}>
{getFormattedDate(item?.lastModified * 1)}
</Paragraph>
<Paragraph size={SIZE.xs}>
{(item.filename || item.name).replace(".nnbackup", "")}
</Paragraph>
</View>
<Button
title="Restore"
height={30}
type="accent"
style={{
borderRadius: 100,
paddingHorizontal: 12
}}
fontSize={SIZE.sm - 1}
onPress={() => restore(item, index)}
/>
</View>
);
return (
<>
<View>
<Dialog context="local" />
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 8,
paddingRight: 8,
alignItems: "center",
paddingTop: restoring ? 8 : 0
}}
>
<DialogHeader
title="Backups"
paragraph={`All the backups are stored in ${
Platform.OS === "ios"
? "File Manager/Notesnook/Backups"
: "selected backups folder."
}`}
button={button}
/>
</View>
<Seperator half />
<FlatList
ListEmptyComponent={
!restoring ? (
loading ? (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 100
}}
>
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.lg}
/>
</View>
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 100
}}
>
{Platform.OS === "android" && !backupDirectoryAndroid ? (
<>
<Button
title="Select backups folder"
icon="folder"
onPress={async () => {
let folder = await ScopedStorage.openDocumentTree(
true
);
let subfolder;
if (folder.name !== "Notesnook backups") {
subfolder = await ScopedStorage.createDirectory(
folder.uri,
"Notesnook backups"
);
} else {
subfolder = folder;
}
SettingsService.set({
backupDirectoryAndroid: subfolder
});
setBackupDirectoryAndroid(subfolder);
setLoading(true);
checkBackups();
}}
style={{
marginTop: 10,
paddingHorizontal: 12
}}
height={30}
width={null}
/>
<Paragraph
style={{
textAlign: "center",
marginTop: 5
}}
size={SIZE.xs}
textBreakStrategy="balanced"
color={colors.secondary.paragraph}
>
Select the folder that includes your backup files to
list them here.
</Paragraph>
</>
) : (
<Paragraph color={colors.secondary.paragraph}>
No backups found
</Paragraph>
)}
</View>
)
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 200
}}
>
<ActivityIndicator color={colors.primary.accent} />
<Paragraph color={colors.secondary.paragraph}>
Restoring {progress ? progress?.collection : null}
{progress ? `(${progress.current}/${progress.total}) ` : null}
...Please wait.
</Paragraph>
</View>
)
}
keyExtractor={(item) => item.name || item.filename}
style={{
paddingHorizontal: 12
}}
data={restoring || loading ? [] : files}
renderItem={renderItem}
ListFooterComponent={
restoring || loading || files.length === 0 ? null : (
<View
style={{
height: 200
}}
/>
)
}
/>
</View>
</>
);
};

View File

@@ -112,7 +112,7 @@ export const Button = ({
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading || restProps.disabled}
disabled={loading}
type={type}
accentColor={accentColor}
accentText={accentText}
@@ -132,7 +132,6 @@ export const Button = ({
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
opacity: restProps?.disabled ? 0.5 : 1,
...(style as ViewStyle)
}}
>

View File

@@ -41,7 +41,6 @@ import { SIZE } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
import phone from "phone";
import isURL from "validator/lib/isURL";
interface InputProps extends TextInputProps {
fwdRef?: RefObject<TextInput>;
@@ -50,8 +49,7 @@ interface InputProps extends TextInputProps {
| "email"
| "confirmPassword"
| "username"
| "phonenumber"
| "url";
| "phonenumber";
loading?: boolean;
onSubmit?: (
event: NativeSyntheticEvent<TextInputSubmitEditingEventData>
@@ -153,9 +151,6 @@ const Input = ({
case "confirmPassword":
isError = customValidator && value === customValidator();
break;
case "url":
isError = isURL(value);
break;
case "phonenumber": {
const result = phone(value, {
strictDetection: true,

View File

@@ -26,7 +26,10 @@ import {
} from "@notesnook/core/dist/common";
import { EventManagerSubscription } from "@notesnook/core/dist/utils/event-manager";
import notifee from "@notifee/react-native";
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
import NetInfo, {
NetInfoState,
NetInfoSubscription
} from "@react-native-community/netinfo";
import React, { useCallback, useEffect, useRef } from "react";
import {
AppState,
@@ -44,7 +47,6 @@ import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { initializeLogger } from "../common/database/logger";
import { MMKV } from "../common/database/mmkv";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
@@ -95,7 +97,7 @@ import {
import { getGithubVersion } from "../utils/github-version";
import { tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { NotesnookModule } from "../utils/notesnook-module";
import { initializeLogger } from "../common/database/logger";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -255,9 +257,9 @@ async function saveEditorState() {
movedAway: editorState().movedAway,
timestamp: Date.now()
});
NotesnookModule.setAppState(state);
MMKV.setString("appState", state);
} else {
NotesnookModule.setAppState("");
MMKV.removeItem("appState");
}
}
@@ -360,7 +362,6 @@ const initializeDatabase = async (password?: string) => {
try {
await setupDatabase(password);
await db.init();
Sync.run();
} catch (e) {
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, "Error initializing database", "global");
@@ -390,15 +391,18 @@ export const useAppEvents = () => {
]);
const syncedOnLaunch = useRef(false);
const refValues = useRef<
Partial<{
subsriptionSuccessListener: EmitterSubscription;
subsriptionErrorListener: EmitterSubscription;
isUserReady: boolean;
prevState: AppStateStatus;
showingDialog: boolean;
removeInternetStateListener: NetInfoSubscription;
isReconnecting: boolean;
initialUrl: string;
backupDidWait: boolean;
isConnectingSSE: boolean;
}>
>({});
@@ -461,31 +465,14 @@ export const useAppEvents = () => {
const user = await db.user.getUser();
if (PremiumService.get() && user) {
if (
SettingsService.get().backupDirectoryAndroid ||
Platform.OS !== "android"
await BackupService.checkBackupRequired(SettingsService.get().reminder)
) {
const partialBackup = await BackupService.checkBackupRequired(
SettingsService.get().reminder,
"lastBackupDate"
);
const fullBackup = await BackupService.checkBackupRequired(
SettingsService.get().fullBackupReminder,
"lastFullBackupDate"
);
await sleep(2000);
if (partialBackup) {
await BackupService.run();
}
if (fullBackup) {
await BackupService.run(true, undefined, "full");
}
}
if (SettingsService.getProperty("offlineMode")) {
db.attachments.cacheAttachments().catch(console.log);
if (
!SettingsService.get().backupDirectoryAndroid &&
Platform.OS === "android"
)
return;
sleep(2000).then(() => BackupService.run());
}
}
}, []);
@@ -546,6 +533,7 @@ export const useAppEvents = () => {
setRecoveryKeyMessage();
}
if (!user?.isEmailConfirmed) setEmailVerifyMessage();
refValues.current.isUserReady = true;
syncedOnLaunch.current = true;
if (!isLogin) {
@@ -619,21 +607,20 @@ export const useAppEvents = () => {
}, [onSyncComplete, onUserUpdated]);
useEffect(() => {
const onInternetStateChanged = async () => {
const onInternetStateChanged = async (state: NetInfoState) => {
if (!syncedOnLaunch.current) return;
Sync.run("global", false, "full");
reconnectSSE();
reconnectSSE(state);
};
const onAppStateChanged = async (state: AppStateStatus) => {
if (state === "active") {
notifee.setBadgeCount(0);
updateStatusBarColor();
checkAutoBackup();
Sync.run("global", false, "full");
reconnectSSE();
await reconnectSSE();
await checkForShareExtensionLaunchedInBackground();
NotesnookModule.setAppState("");
MMKV.removeItem("appState");
let user = await db.user.getUser();
if (user && !user?.isEmailConfirmed) {
try {
@@ -717,15 +704,42 @@ export const useAppEvents = () => {
}
}, [appLocked, syncing, checkAutoBackup]);
async function reconnectSSE() {
async function reconnectSSE(connection?: NetInfoState) {
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
return;
if (useSettingStore.getState().appDidEnterBackgroundForAction) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
console.log("AppDidEnterForegroundAfterAction");
return;
}
if (SettingsService.get().sessionExpired) {
refValues.current.isReconnecting = false;
return;
}
refValues.current.isReconnecting = true;
let connectionState = connection;
try {
if (refValues.current?.isConnectingSSE) return;
refValues.current.isConnectingSSE = true;
await db.connectSSE();
refValues.current.isConnectingSSE = false;
if (!connectionState) {
connectionState = await NetInfo.fetch();
}
const user = await db.user.getUser();
if (
user &&
connectionState.isConnected &&
connectionState.isInternetReachable
) {
await db.connectSSE();
} else {
useUserStore.getState().setSyncing(false);
await db.syncer.stop();
}
refValues.current.isReconnecting = false;
} catch (e) {
refValues.current.isConnectingSSE = false;
DatabaseLogger.error(e as Error);
refValues.current.isReconnecting = false;
}
}

View File

@@ -16,7 +16,7 @@
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "0.9.7",
"react-native-actions-sheet": "0.9.6",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -36,11 +36,11 @@
"react-native-wheel-color-picker": "^1.3.1",
"tinycolor2": "1.6.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"@streetwriters/kysely": "^0.27.4",
"@tanstack/react-query": "^4.29.19",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"@trpc/server": "10.38.3",
"kysely": "^0.26.3",
"pathe": "1.1.2"
},
"sideEffects": false

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { basename } from "pathe";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker, {
@@ -42,6 +41,7 @@ import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { basename } from "pathe";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
@@ -123,13 +123,13 @@ const file = async (fileOptions: PickerOptions) => {
file.name,
fileOptions
))
) {
throw new Error("Failed to attach file");
}
)
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
if (!fileOptions.tabId) return;
if (
fileOptions.tabId !== undefined &&
fileOptions.tabId &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
@@ -158,16 +158,16 @@ const file = async (fileOptions: PickerOptions) => {
fileOptions.tabId
);
}
} else {
throw new Error("Failed to attach file, no tabId is set");
}
eSendEvent(eCloseSheet);
setTimeout(() => {
eSendEvent(eCloseSheet);
}, 1000);
} catch (e) {
eSendEvent(eCloseSheet);
ToastManager.show({
heading: "Failed to attach file",
message: (e as Error).message,
heading: (e as Error).message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
});
@@ -203,6 +203,7 @@ const camera = async (options: PickerOptions) => {
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};

View File

@@ -660,9 +660,9 @@ export const useEditor = (
lock.current = true;
// Handle this case where note was locked on another device and synced.
const locked = note
? await db.vaults.itemExists(note as ItemReference)
: false;
const locked = await db.vaults.itemExists(
currentNotes.current[noteId] as ItemReference
);
if (note) {
if (!locked && tab?.noteLocked) {

View File

@@ -31,7 +31,6 @@ import { useTabStore } from "./use-tab-store";
import { parseInternalLink } from "@notesnook/core";
import { eOnLoadNote } from "../../../utils/events";
import { db } from "../../../common/database";
import { NotesnookModule } from "../../../utils/notesnook-module";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -166,7 +165,7 @@ const canRestoreAppState = (appState: AppState) => {
let appState: AppState | undefined;
export function getAppState() {
if (appState && canRestoreAppState(appState)) return appState as AppState;
const json = NotesnookModule.getAppState();
const json = MMKV.getString("appState");
if (json) {
appState = JSON.parse(json) as AppState;
if (canRestoreAppState(appState)) {
@@ -181,7 +180,7 @@ export function getAppState() {
export function clearAppState() {
appState = undefined;
NotesnookModule.setAppState("");
MMKV.removeItem("appState");
}
export async function openInternalLink(url: string) {

View File

@@ -32,12 +32,9 @@ import {
TimeFormatPicker,
TrashIntervalPicker,
BackupReminderPicker,
ApplockTimerPicker,
BackupWithAttachmentsReminderPicker
ApplockTimerPicker
} from "./picker/pickers";
import ThemeSelector from "./theme-selector";
import { RestoreBackup } from "./restore-backup";
import { ServersConfiguration } from "./server-config";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
@@ -54,8 +51,5 @@ export const components: { [name: string]: ReactElement } = {
"date-format-selector": <DateFormatPicker />,
"time-format-selector": <TimeFormatPicker />,
"theme-selector": <ThemeSelector />,
"applock-timer": <ApplockTimerPicker />,
autobackupsattachments: <BackupWithAttachmentsReminderPicker />,
backuprestore: <RestoreBackup />,
"server-config": <ServersConfiguration />
"applock-timer": <ApplockTimerPicker />
};

View File

@@ -18,22 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React from "react";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import BaseDialog from "../../components/dialog/base-dialog";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { SectionGroup } from "./section-group";
import { settingsGroups } from "./settings-data";
import { RouteParams, SettingSection } from "./types";
import SettingsUserSection from "./user-section";
import { Header } from "../../components/header";
const keyExtractor = (item: SettingSection) => item.id;
const Home = ({
navigation
}: NativeStackScreenProps<RouteParams, "SettingsHome">) => {
const { colors } = useThemeColors();
const [loading, setLoading] = useState(false);
useNavigationFocus(navigation, {
onFocus: () => {
useNavigationStore.getState().setFocusedRouteId("Settings");
@@ -49,6 +62,16 @@ const Home = ({
<SectionGroup item={item} />
);
useEffect(() => {
function settingsLoading(state: boolean) {
setLoading(state || true);
}
eSubscribeEvent("settings-loading", settingsLoading);
return () => {
eUnSubscribeEvent("settings-loading", settingsLoading);
};
}, []);
return (
<>
<Header
@@ -58,6 +81,57 @@ const Home = ({
id="Settings"
/>
<DelayLayout delay={300} type="settings">
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
justifyContent: "center",
alignItems: "center"
}}
>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading color={colors.primary.paragraph} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.secondary.paragraph}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
marginTop: 15
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
</View>
</View>
</View>
</BaseDialog>
)}
<Animated.FlatList
entering={FadeInDown}
data={settingsGroups}

View File

@@ -121,8 +121,8 @@ export const BackupReminderPicker = createSettingsPicker({
SettingsService.set({ reminder: item });
},
formatValue: (item) => {
return item === "useroff" || item === "off" || item === "never"
? "Off"
return item === "useroff"
? "Never"
: item.slice(0, 1).toUpperCase() + item.slice(1);
},
getItemKey: (item) => item,
@@ -140,31 +140,6 @@ export const BackupReminderPicker = createSettingsPicker({
}
});
export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.fullBackupReminder,
updateValue: (item) => {
SettingsService.set({ fullBackupReminder: item });
},
formatValue: (item) => {
return item === "useroff" || item === "off" || item === "never"
? "Off"
: item.slice(0, 1).toUpperCase() + item.slice(1);
},
getItemKey: (item) => item,
options: ["never", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "never";
}
});
export const ApplockTimerPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.appLockTimer,
updateValue: (item) => {

View File

@@ -1,558 +0,0 @@
/*
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 { formatBytes, getFormattedDate } from "@notesnook/common";
import { LegacyBackupFile } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, Platform, View } from "react-native";
import RNFetchBlob, { ReactNativeBlobUtilStat } from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { DatabaseLogger, db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { deleteCacheFileByName } from "../../../common/filesystem/io";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import { presentDialog } from "../../../components/dialog/functions";
import {
endProgress,
startProgress,
updateProgress
} from "../../../components/dialogs/progress";
import { Button } from "../../../components/ui/button";
import Heading from "../../../components/ui/typography/heading";
import Paragraph from "../../../components/ui/typography/paragraph";
import { SectionItem } from "../../../screens/settings/section-item";
import { ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { refreshAllStores } from "../../../stores/create-db-collection-store";
import { useUserStore } from "../../../stores/use-user-store";
import { SIZE } from "../../../utils/size";
type PasswordOrKey = { password?: string; encryptionKey?: string };
const withPassword = () => {
return new Promise<PasswordOrKey>((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve({});
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
encryptionKey: isEncryptionKey ? password : undefined,
password: isEncryptionKey ? undefined : password
});
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
};
const restoreBackup = async (options: {
uri: string;
deleteFile?: boolean;
}) => {
try {
const isLegacyBackup = options.uri.endsWith(".nnbackup");
startProgress({
title: "Restoring backup",
paragraph: "Preparing to restore backup file...",
canHideProgress: false
});
let filePath = options.uri;
let deleteBackupFile = options.deleteFile;
if (!isLegacyBackup) {
if (Platform.OS === "android") {
updateProgress({
progress: `Copying backup file to cache...`
});
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
if (filePath.startsWith("content://")) {
await copyFileAsync(filePath, cacheFile);
} else {
await RNFetchBlob.fs.cp(filePath, cacheFile);
}
filePath = cacheFile;
deleteBackupFile = true;
}
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
updateProgress({
progress: `Extracting files from backup...`
});
await unzip(filePath, zipOutputFolder);
const extractedBackupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
const extractedAttachments = extractedBackupFiles.includes("attachments")
? await RNFetchBlob.fs.ls(`${zipOutputFolder}/attachments`)
: [];
const attachmentsKeyPath: any = extractedAttachments?.find(
(path) => path === ".attachments_key"
);
const attachmentsKey = attachmentsKeyPath
? await JSON.parse(
await RNFetchBlob.fs.readFile(
`${zipOutputFolder}/attachments/${attachmentsKeyPath}`,
"utf8"
)
)
: undefined;
let count = 0;
await db.transaction(async () => {
let passwordOrKey: PasswordOrKey;
for (const path of extractedBackupFiles) {
if (path === ".nnbackup" || path === "attachments") continue;
updateProgress({
progress: `Restoring data (${count++}/${
extractedBackupFiles.length
})`
});
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const backup = JSON.parse(data);
const isEncryptedBackup = backup.encrypted;
passwordOrKey = !isEncryptedBackup
? ({} as PasswordOrKey)
: await withPassword();
if (
isEncryptedBackup &&
!passwordOrKey?.encryptionKey &&
!passwordOrKey?.password
) {
endProgress();
throw new Error("Failed to decrypt backup");
}
await db.backup.import(backup, {
...passwordOrKey,
attachmentsKey: attachmentsKey
});
}
});
await db.initCollections();
count = 0;
for (const path of extractedAttachments) {
if (path === ".attachments_key") continue;
updateProgress({
progress: `Restoring attachments (${count++}/${
extractedAttachments.length
})`
});
const hash = path;
const attachment = await db.attachments.attachment(hash as string);
if (!attachment) continue;
console.log("Saving attachment file", hash);
await deleteCacheFileByName(hash);
await RNFetchBlob.fs.cp(
`${zipOutputFolder}/attachments/${hash}`,
`${cacheDir}/${hash}`
);
}
updateProgress({
progress: `Cleaning up...`
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (Platform.OS === "android" || deleteBackupFile) {
RNFetchBlob.fs.unlink(filePath).catch(console.log);
}
} else {
updateProgress({
progress: `Reading backup file...`
});
const rawData =
Platform.OS === "android"
? await ScopedStorage.readFile(filePath, "utf8")
: await RNFetchBlob.fs.readFile(filePath, "utf8");
const backup: LegacyBackupFile = JSON.parse(rawData) as LegacyBackupFile;
const isEncryptedBackup =
typeof backup.data !== "string" && backup.data.cipher;
updateProgress({
progress: isEncryptedBackup
? `Backup is encrypted, decrypting...`
: "Preparing to restore backup file..."
});
const { encryptionKey, password } = isEncryptedBackup
? ({} as PasswordOrKey)
: await withPassword();
if (isEncryptedBackup && !encryptionKey && !password) {
endProgress();
throw new Error("Failed to decrypt backup");
}
await db.transaction(async () => {
updateProgress({
progress: "Restoring backup..."
});
await db.backup.import(backup, {
encryptionKey,
password
});
});
endProgress();
}
ToastManager.show({
heading: "Backup restored successfully",
type: "success"
});
await db.initCollections();
refreshAllStores();
Navigation.queueRoutesForUpdate();
endProgress();
} catch (e) {
endProgress();
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, `Failed to restore backup`);
}
};
const BACKUP_FILES_CACHE: (ReactNativeBlobUtilStat | ScopedStorage.FileType)[] =
[];
export const RestoreBackup = () => {
const { colors } = useThemeColors();
const [files, setFiles] =
useState<(ReactNativeBlobUtilStat | ScopedStorage.FileType)[]>(
BACKUP_FILES_CACHE
);
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] =
useState<ScopedStorage.FileType>();
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 1000);
}, []);
const checkBackups = async () => {
try {
let files: (ReactNativeBlobUtilStat | ScopedStorage.FileType)[] = [];
if (Platform.OS === "android") {
const backupDirectory = SettingsService.get().backupDirectoryAndroid;
if (backupDirectory) {
setBackupDirectoryAndroid(backupDirectory);
files = await ScopedStorage.listFiles(backupDirectory.uri);
} else {
setLoading(false);
return;
}
} else {
const path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files
.filter((file) => {
const name =
Platform.OS === "android"
? (file as ScopedStorage.FileType).name
: (file as ReactNativeBlobUtilStat).filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
const timeA = a.lastModified;
const timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
BACKUP_FILES_CACHE.splice(0, BACKUP_FILES_CACHE.length, ...files);
} catch (e) {
e;
} finally {
setLoading(false);
}
};
const renderItem = React.useCallback(
({
item,
index
}: {
item: ReactNativeBlobUtilStat | ScopedStorage.FileType;
index: number;
}) => <BackupItem item={item} index={index} />,
[]
);
return (
<>
<FlatList
data={[0]}
renderItem={() => {
return (
<View>
<SectionItem
item={{
id: "restore-from-files",
name: "Restore from files",
icon: "folder",
modifer: async () => {
useUserStore.setState({
disableAppLockRequests: true
});
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
restoreBackup({
uri:
Platform.OS === "android"
? (("file://" + file.fileCopyUri) as string)
: (file.fileCopyUri as string),
deleteFile: true
});
},
description:
"Select a backup file from your device to restore backup"
}}
/>
{Platform.OS === "android" ? (
<SectionItem
item={{
id: "select-backup-folder",
name: "Select folder with backup files",
icon: "folder",
modifer: async () => {
const folder = await ScopedStorage.openDocumentTree(true);
let subfolder;
if (folder.name !== "Notesnook backups") {
subfolder = await ScopedStorage.createDirectory(
folder.uri,
"Notesnook backups"
);
} else {
subfolder = folder;
}
SettingsService.set({
backupDirectoryAndroid: subfolder
});
setBackupDirectoryAndroid(subfolder);
setLoading(true);
checkBackups();
},
description:
"Select folder where Notesnook backup files are stored to view and restore them from the app"
}}
/>
) : null}
<FlatList
ListHeaderComponent={
<View
style={{
backgroundColor: colors.primary.background,
marginBottom: 10
}}
>
<Heading color={colors.primary.accent} size={SIZE.xs}>
RECENT BACKUPS
</Heading>
</View>
}
stickyHeaderIndices={[0]}
ListEmptyComponent={
loading ? (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 300,
paddingHorizontal: 50
}}
>
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.lg}
/>
</View>
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
gap: 12,
height: 300,
paddingHorizontal: 50
}}
>
<Paragraph
style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
>
No backups were found.{" "}
{!backupDirectoryAndroid
? `Please select a folder with backup files to view them here.`
: null}
</Paragraph>
</View>
)
}
windowSize={2}
keyExtractor={(item) =>
(item as ScopedStorage.FileType).name ||
(item as ReactNativeBlobUtilStat).filename
}
style={{
paddingHorizontal: 12
}}
ListFooterComponent={
<View
style={{
height: 200
}}
/>
}
data={files}
renderItem={renderItem}
/>
</View>
);
}}
/>
</>
);
};
const BackupItem = ({
item,
index
}: {
item: ReactNativeBlobUtilStat | ScopedStorage.FileType;
index: number;
}) => {
const { colors } = useThemeColors();
const isLegacyBackup =
item.path?.endsWith(".nnbackup") ||
(item as ScopedStorage.FileType).uri?.endsWith(".nnbackup");
const itemName = (
(item as ReactNativeBlobUtilStat).filename ||
(item as ScopedStorage.FileType).name
)
.replace(".nnbackupz", "")
.replace(".nnbackup", "");
return (
<View
style={{
justifyContent: "space-between",
alignItems: "center",
width: "100%",
borderRadius: 0,
flexDirection: "row",
borderBottomWidth: 0.5,
borderBottomColor: colors.primary.border,
paddingVertical: 12
}}
>
<View>
<Paragraph size={SIZE.sm}>{itemName}</Paragraph>
<Paragraph
size={SIZE.xs}
color={colors.secondary.paragraph}
style={{ width: "100%", maxWidth: "100%" }}
>
Created on {getFormattedDate(item?.lastModified, "date-time")}
{isLegacyBackup ? "(Legacy backup)" : ""} (
{formatBytes((item as ReactNativeBlobUtilStat).size)})
</Paragraph>
</View>
<Button
title="Restore"
type="secondaryAccented"
style={{
paddingHorizontal: 12,
height: 35
}}
onPress={() => {
presentDialog({
title: `Restore ${itemName}`,
paragraph: `Are you sure you want to restore this backup?`,
positiveText: "Restore",
negativeText: "Cancel",
positivePress: async () => {
console.log("file path", (item as ScopedStorage.FileType).uri);
restoreBackup({
uri:
Platform.OS === "android"
? (item as ScopedStorage.FileType).uri
: (item as ReactNativeBlobUtilStat).path
});
}
});
}}
/>
</View>
);
};

View File

@@ -1,213 +0,0 @@
/*
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 { isServerCompatible } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import { View } from "react-native";
import { presentDialog } from "../../components/dialog/functions";
import { Button } from "../../components/ui/button";
import Input from "../../components/ui/input";
import { Notice } from "../../components/ui/notice";
import Paragraph from "../../components/ui/typography/paragraph";
import SettingsService from "../../services/settings";
import { HostId, HostIds } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { ToastManager } from "../../services/event-manager";
export const ServerIds = ["notesnook-sync", "auth", "sse"] as const;
export type ServerId = (typeof ServerIds)[number];
type Server = {
id: ServerId;
host: HostId;
title: string;
example: string;
description: string;
};
type VersionResponse = {
version: number;
id: string;
instance: string;
};
const SERVERS: Server[] = [
{
id: "notesnook-sync",
host: "API_HOST",
title: "Sync server",
example: "http://localhost:4326",
description: "Server used to sync your notes & other data between devices."
},
{
id: "auth",
host: "AUTH_HOST",
title: "Auth server",
example: "http://localhost:5326",
description: "Server used for login/sign up and authentication."
},
{
id: "sse",
host: "SSE_HOST",
title: "Events server",
example: "http://localhost:7326",
description: "Server used to receive important notifications & events."
}
];
export function ServersConfiguration() {
const { colors } = useThemeColors();
const [error, setError] = useState<string>();
const [success, setSuccess] = useState<boolean>();
const [urls, setUrls] = useState<Partial<Record<HostId, string>>>(
SettingsService.getProperty("serverUrls") || {}
);
const isLoggedIn = useUserStore((state) => !!state.user);
return (
<View
style={{
paddingHorizontal: 12,
gap: 12,
marginTop: 12
}}
>
{isLoggedIn ? (
<Notice
text="You must log out in order to change/reset server URLs."
type="alert"
/>
) : null}
<View style={{ flexDirection: "column" }}>
{SERVERS.map((server) => (
<Input
key={server.id}
editable={!isLoggedIn}
placeholder={`${server.id} e.g. ${server.example}`}
validationType="url"
defaultValue={urls[server.host]}
errorMessage="Please enter a valid URL."
onChangeText={(value) =>
setUrls((s) => {
s[server.host] = value;
return s;
})
}
/>
))}
{error ? (
<Paragraph
style={{
paddingVertical: 12
}}
color={colors.error.paragraph}
>
{error}
</Paragraph>
) : null}
{success === true ? (
<Paragraph
style={{
paddingVertical: 12
}}
color={colors.success.paragraph}
>
Connected to all servers sucessfully.
</Paragraph>
) : null}
<View style={{ marginTop: 1, justifyContent: "flex-end", gap: 12 }}>
<Button
disabled={isLoggedIn}
type="secondary"
width="100%"
onPress={async () => {
try {
for (const host of HostIds) {
const url = urls[host];
const server = SERVERS.find((s) => s.host === host)!;
if (!url) throw new Error("All server urls are required.");
const version = await fetch(`${url}/version`)
.then((r) => r.json() as Promise<VersionResponse>)
.catch(() => undefined);
if (!version)
throw new Error(`Could not connect to ${server.title}.`);
if (version.id !== server.id)
throw new Error(
`The URL you have given (${url}) does not point to the ${server.title}.`
);
if (!isServerCompatible(version.version)) {
throw new Error(
`The server version is not compatible with the app.`
);
}
}
setSuccess(true);
setError(undefined);
} catch (e) {
setError((e as Error).message);
}
}}
title="Test connection"
/>
<Button
type="accent"
disabled={isLoggedIn}
width="100%"
onPress={async () => {
if (!success) {
ToastManager.show({
heading: "Test connection before changing server urls"
});
return;
}
SettingsService.setProperty(
"serverUrls",
urls as Record<HostId, string>
);
presentDialog({
title: "Server url changed",
paragraph: "Restart the app for changes to take effect.",
negativeText: "Done"
});
}}
title="Save"
/>
<Button
disabled={isLoggedIn}
type="error"
width="100%"
title="Reset server urls"
onPress={async () => {
if (isLoggedIn) return;
SettingsService.setProperty("serverUrls", undefined);
presentDialog({
title: "Server urls reset",
paragraph: "Restart the app for changes to take effect.",
negativeText: "Done"
});
}}
/>
</View>
</View>
</View>
);
}

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatBytes } from "@notesnook/common";
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
@@ -25,19 +24,12 @@ import { Appearance, Linking, Platform } from "react-native";
import { getVersion } from "react-native-device-info";
import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import ScreenGuardModule from "react-native-screenguard";
import { DatabaseLogger, db } from "../../common/database";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import filesystem from "../../common/filesystem";
import { AttachmentDialog } from "../../components/attachments";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
import {
endProgress,
startProgress,
updateProgress
} from "../../components/dialogs/progress";
import { ChangeEmail } from "../../components/sheets/change-email";
import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
@@ -50,7 +42,6 @@ import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet
} from "../../services/event-manager";
@@ -69,7 +60,8 @@ import {
eCloseSheet,
eCloseSimpleDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
} from "../../utils/events";
import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time";
@@ -78,6 +70,7 @@ import { useDragState } from "./editor/state";
import { verifyUser, verifyUserWithApplock } from "./functions";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import ScreenGuardModule from "react-native-screenguard";
type User = any;
@@ -338,144 +331,45 @@ export const settingsGroups: SettingSection[] = [
},
description: "Verify your subscription to Notesnook Pro"
},
{
id: "clear-cache",
name: "Clear cache",
icon: "delete",
modifer: async () => {
presentDialog({
title: "Clear cache",
paragraph: "Are you sure you want to clear the cache?",
positiveText: "Clear",
positivePress: async () => {
filesystem.clearCache();
ToastManager.show({
heading: "Cache cleared",
message: "All cached attachments have been removed",
type: "success"
});
}
});
},
description(current) {
return `Clear all cached attachments. Current cache size: ${
current as number
}`;
},
useHook: () => {
const [cacheSize, setCacheSize] = React.useState(0);
React.useEffect(() => {
filesystem.getCacheSize().then(setCacheSize).catch(console.log);
const sub = eSubscribeEvent("cache-cleared", () => {
setCacheSize(0);
});
return () => {
sub?.unsubscribe();
};
}, []);
return formatBytes(cacheSize);
}
},
{
id: "logout",
name: "Logout",
name: "Log out",
description:
"Logging out will clear all data stored on this device.",
"Logging out will clear all data stored on THIS DEVICE.",
icon: "logout",
modifer: async () => {
const hasUnsyncedChanges = await db.hasUnsyncedChanges();
modifer: () => {
presentDialog({
title: "Logout",
paragraph:
"Are you sure you want to logout and clear all data stored on this device?",
"Logging out will clear all data stored on THIS DEVICE. Make sure you have synced all your changes before logging out.",
positiveText: "Logout",
check: {
info: "Take a backup before logging out",
defaultValue: true
},
notice: hasUnsyncedChanges
? {
text: "You have unsynced notes. Take a backup or sync your notes to avoid losing your critical data.",
type: "alert"
}
: undefined,
positivePress: async (_, takeBackup) => {
eSendEvent(eCloseSimpleDialog);
setTimeout(async () => {
try {
startProgress({
fillBackground: true,
title: "Logging out",
canHideProgress: true,
paragraph:
"Please wait while we log out and clear app data."
});
Navigation.navigate("Notes");
if (takeBackup) {
updateProgress({
progress: "Taking a backup of your notes"
});
try {
const result = await BackupService.run(
false,
"local",
"partial"
);
if (result.error) throw result.error as Error;
} catch (e) {
DatabaseLogger.error(e);
const error = e;
const canLogout = await new Promise((resolve) => {
presentDialog({
context: "local",
title: "Backup failed",
paragraph: `${
(error as Error).message
}. Do you want to continue logging out?`,
positiveText: "Continue",
positivePress: () => {
resolve(true);
},
onClose: () => {
resolve(false);
}
});
});
if (!canLogout) {
endProgress();
return;
}
}
}
updateProgress({
progress: "Logging out... please wait"
});
positivePress: async () => {
try {
eSendEvent(eCloseSimpleDialog);
setTimeout(async () => {
eSendEvent("settings-loading", true);
Navigation.popToTop();
await db.user?.logout();
setLoginMessage();
await PremiumService.setPremiumStatus();
await BiometricService.resetCredentials();
MMKV.clearStore();
clearAllStores();
setImmediate(() => {
refreshAllStores();
});
refreshAllStores();
Navigation.queueRoutesForUpdate();
SettingsService.resetSettings();
useUserStore.getState().setUser(null);
useUserStore.getState().setSyncing(false);
endProgress();
} catch (e) {
DatabaseLogger.error(e);
ToastManager.error(e as Error, "Error logging out");
endProgress();
}
}, 300);
Navigation.goBack();
Navigation.popToTop();
setTimeout(() => {
eSendEvent("settings-loading", false);
}, 3000);
}, 300);
} catch (e) {
ToastManager.error(e as Error, "Error logging out");
eSendEvent("settings-loading", false);
}
}
});
}
@@ -501,18 +395,12 @@ export const settingsGroups: SettingSection[] = [
try {
const verified = await db.user?.verifyPassword(value);
if (verified) {
setTimeout(async () => {
startProgress({
title: "Deleting account",
paragraph: "Please wait while we delete your account"
});
Navigation.navigate("Notes");
await db.user?.deleteUser(value);
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
}, 300);
eSendEvent("settings-loading", true);
await db.user?.deleteUser(value);
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
} else {
ToastManager.show({
heading: "Incorrect password",
@@ -523,9 +411,9 @@ export const settingsGroups: SettingSection[] = [
});
}
endProgress();
eSendEvent("settings-loading", false);
} catch (e) {
endProgress();
eSendEvent("settings-loading", false);
console.log(e);
ToastManager.error(
e as Error,
@@ -546,25 +434,6 @@ export const settingsGroups: SettingSection[] = [
type: "screen",
icon: "autorenew",
sections: [
{
id: "offline-mode",
name: "Full offline mode",
description: "Download everything including attachments on sync",
type: "switch",
property: "offlineMode",
modifer: () => {
const current = SettingsService.get().offlineMode;
if (current) {
SettingsService.setProperty("offlineMode", false);
db.fs().cancel("offline-mode");
return;
}
PremiumService.verify(() => {
SettingsService.setProperty("offlineMode", true);
db.attachments.cacheAttachments().catch(console.log);
});
}
},
{
id: "auto-sync",
name: "Disable auto sync",
@@ -820,14 +689,6 @@ export const settingsGroups: SettingSection[] = [
type: "switch"
}
]
},
{
id: "servers",
type: "screen",
name: "Servers",
description: "Configure server URLs for Notesnook",
icon: "server",
component: "server-config"
}
]
},
@@ -835,6 +696,15 @@ export const settingsGroups: SettingSection[] = [
id: "privacy-security",
name: "Privacy and security",
sections: [
{
id: "temeltery",
type: "switch",
name: "Telemetry",
icon: "radar",
description:
"Contribute towards a better Notesnook. All tracking information is anonymous.",
property: "telemetry"
},
{
id: "marketing-emails",
type: "switch",
@@ -1171,8 +1041,7 @@ export const settingsGroups: SettingSection[] = [
{
id: "backup-now",
name: "Backup now",
description:
"Take a partial backup of your data that does not include attachments",
description: "Create a backup of your data",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
@@ -1183,41 +1052,14 @@ export const settingsGroups: SettingSection[] = [
verifyUser(null, () => BackupService.run(true));
}
},
{
id: "backup-now",
name: "Backup now with attachments",
hidden: () => !useUserStore.getState().user,
description: "Take a full backup of your data with all attachments",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
await BackupService.run(true, undefined, "full");
return;
}
verifyUser(null, () =>
BackupService.run(true, undefined, "full")
);
}
},
{
id: "auto-backups",
type: "component",
name: "Automatic backups",
description:
"Set the interval to create a partial backup (without attachments) automatically.",
"Backup your data once every week or daily automatically.",
component: "autobackups"
},
{
id: "auto-backups-with-attachments",
type: "component",
hidden: () => !useUserStore.getState().user,
name: "Automatic backups with attachments",
description: `Set the interval to create a backup (with attachments) automatically.
NOTE: Creating a backup with attachments can take a while, and also fail completely. The app will try to resume/restart the backup in case of interruptions.`,
component: "autobackupsattachments"
},
{
id: "select-backup-dir",
name: "Select backup directory",
@@ -1309,8 +1151,9 @@ NOTE: Creating a backup with attachments can take a while, and also fail complet
id: "restore-backup",
name: "Restore backup",
description: "Restore backup from phone storage.",
type: "screen",
component: "backuprestore"
modifer: () => {
eSendEvent(eOpenRestoreDialog);
}
},
{
id: "export-notes",
@@ -1490,9 +1333,9 @@ NOTE: Creating a backup with attachments can take a while, and also fail complet
}
},
{
id: "join-mastodon",
id: "join-mastodom",
name: "Follow us on Mastodon",
description: "We are on mastodon",
description: "We are on mastodom",
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(

View File

@@ -90,7 +90,7 @@ export const Subscription = () => {
function getPrice() {
return Platform.OS === "android"
? monthlyPlan?.product?.subscriptionOfferDetails[0]?.pricingPhases
? monthlyPlan?.product?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0]?.formattedPrice
: monthlyPlan?.product?.localizedPrice;
}

View File

@@ -16,15 +16,14 @@ 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 BackgroundFetch from "@ammarahmed/react-native-background-fetch";
import { AppRegistry, AppState, Platform } from "react-native";
import { Platform } from "react-native";
import {
beginBackgroundTask,
endBackgroundTask
} from "react-native-begin-background-task";
import BackgroundFetch from "@ammarahmed/react-native-background-fetch";
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { deleteDCacheFiles } from "../common/filesystem/io";
import { useUserStore } from "../stores/use-user-store";
import { AppState, AppRegistry } from "react-native";
import Notifications from "./notifications";
import SettingsService from "./settings";
@@ -115,19 +114,15 @@ async function onBackgroundSyncStarted() {
await db.init();
}
const user = await db.user?.getUser();
if (user && !useUserStore.getState().syncing) {
useUserStore.getState().setSyncing(true);
if (user) {
await db.sync({
type: "full",
force: false
});
useUserStore.getState().setSyncing(false);
}
await Notifications.setupReminders();
deleteDCacheFiles();
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
} catch (e) {
useUserStore.getState().setSyncing(false);
DatabaseLogger.error(e as Error);
console.log("BACKGROUND SYNC ERROR", (e as Error).message);
}
@@ -137,13 +132,11 @@ const onBoot = async () => {
try {
if (!SettingsService.getProperty("backgroundSync")) {
DatabaseLogger.info("BACKGROUND SYNC ON BOOT DISABLED");
return;
}
DatabaseLogger.info("BOOT TASK STARTED");
if (!db.isInitialized) {
await setupDatabase();
await db.init();
}

View File

@@ -17,8 +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 { sanitizeFilename } from "@notesnook/common";
import { formatDate } from "@notesnook/core/dist/utils/date";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
@@ -27,14 +25,8 @@ import Share from "react-native-share";
import { zip } from "react-native-zip-archive";
import { DatabaseLogger, db } from "../common/database";
import storage from "../common/database/storage";
import filesystem from "../common/filesystem";
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
import { presentDialog } from "../components/dialog/functions";
import {
endProgress,
startProgress,
updateProgress
} from "../components/dialogs/progress";
import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
@@ -45,14 +37,14 @@ const MS_WEEK = MS_DAY * 7;
const MONTH = MS_DAY * 30;
async function getDirectoryAndroid() {
const folder = await ScopedStorage.openDocumentTree(true);
let folder = await ScopedStorage.openDocumentTree(true);
if (!folder) return null;
let subfolder;
if (!folder.name.includes("Notesnook backups")) {
const files = await ScopedStorage.listFiles(folder.uri);
for (const file of files) {
if (file.type === "directory" && file.name === "Notesnook backups") {
subfolder = file;
let folderFiles = await ScopedStorage.listFiles(folder.uri);
for (let f of folderFiles) {
if (f.type === "directory" && f.name === "Notesnook backups") {
subfolder = f;
}
}
if (!subfolder) {
@@ -75,10 +67,10 @@ async function checkBackupDirExists(reset = false, context = "global") {
let dir = SettingsService.get().backupDirectoryAndroid;
if (reset) dir = null;
if (dir) {
const allDirs = await ScopedStorage.getPersistedUriPermissions();
let allDirs = await ScopedStorage.getPersistedUriPermissions();
let exists = allDirs.findIndex((d) => {
return d === dir?.uri || dir?.uri.includes(d);
}) as number | boolean;
return d === dir.uri || dir.uri.includes(d);
});
exists = exists !== -1;
dir = exists ? dir : null;
}
@@ -108,7 +100,7 @@ async function checkBackupDirExists(reset = false, context = "global") {
return dir;
}
async function presentBackupCompleteSheet(backupFilePath: string) {
async function presentBackupCompleteSheet(backupFilePath) {
presentSheet({
title: "Backup complete",
icon: "cloud-upload",
@@ -131,7 +123,7 @@ async function presentBackupCompleteSheet(backupFilePath: string) {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
} as any).catch(console.log);
}).catch(console.log);
}
},
actionText: "Share"
@@ -150,10 +142,10 @@ async function presentBackupCompleteSheet(backupFilePath: string) {
});
}
async function updateNextBackupTime(type: "full" | "partial") {
async function updateNextBackupTime() {
SettingsService.set({
nextBackupRequestTime: Date.now() + 86400000 * 3,
[type === "full" ? "lastFullBackupDate" : "lastBackupDate"]: Date.now()
lastBackupDate: Date.now()
});
}
/**
@@ -161,124 +153,77 @@ async function updateNextBackupTime(type: "full" | "partial") {
* @param {string=} context
* @returns {Promise<{path?: string, error?: Error, report?: boolean}}>
*/
async function run(
progress = false,
context?: string,
backupType: "full" | "partial" = "partial"
) {
console.log("Creating backup:", backupType, progress, context);
const androidBackupDirectory = (await checkBackupDirExists(
false,
context
)) as ScopedStorage.FileType;
async function run(progress = false, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
if (!androidBackupDirectory)
return {
error: new Error("Backup directory not selected"),
report: false
};
let path;
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups");
}
const backupFileName = sanitizeFilename(
`${formatDate(Date.now(), {
type: "date-time",
dateFormat: "YYYY-MM-DD",
timeFormat: "24-hour"
})}-${new Date().getSeconds()}${backupType === "full" ? "-full" : ""}`,
{ replacement: "-" }
);
if (progress) {
startProgress({
title: `Creating ${backupType === "full" ? "a full " : ""}backup`,
paragraph: `Please wait while we create a backup of your data. This may take a few minutes.`,
progress: undefined
presentSheet({
title: "Backing up your data",
paragraph:
"All your backups are stored in 'Phone Storage/Notesnook/backups/' folder",
progress: true
});
}
const zipSourceFolder = `${cacheDir}/backup_temp`;
let path;
let backupFileName = "notesnook_backup_" + Date.now();
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
}
const zipSourceFolder = `${cacheDir}/${backupFileName}`;
const zipOutputFile =
Platform.OS === "ios"
? `${path}/${backupFileName}.nnbackupz`
: `${cacheDir}/${backupFileName}.nnbackupz`;
await RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
if (await RNFetchBlob.fs.exists(zipSourceFolder))
await RNFetchBlob.fs.unlink(zipSourceFolder);
} catch (e) {}
const attachmentsDir = zipSourceFolder + "/attachments";
if (backupType === "full") {
await RNFetchBlob.fs.mkdir(attachmentsDir);
}
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
const user = await db.user.getUser();
for await (const file of db.backup.export({
type: "mobile",
encrypt: SettingsService.get().encryptedBackup && !!user,
mode: backupType
})) {
if (file.type === "file") {
updateProgress({
progress: `Writing backup chunk of size... ${file?.data?.length}`
});
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
} else if (file.type === "attachment") {
updateProgress({
progress: `Saving attachments in backup... ${file.hash}`
});
if (await filesystem.exists(file.hash)) {
await RNFetchBlob.fs.cp(
`${cacheDir}/${file.hash}`,
`${attachmentsDir}/${file.hash}`
);
}
}
for await (const file of db.backup.export(
"mobile",
SettingsService.get().encryptedBackup && user
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
}
updateProgress({
progress: "Creating backup zip file..."
});
await zip(zipSourceFolder, zipOutputFile);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
androidBackupDirectory?.uri,
androidBackupDirectory.uri,
`${backupFileName}.nnbackupz`,
"application/nnbackupz"
);
console.log("Copying zip file...");
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
console.log("Copied zip file...");
console.log("Copyied zip file...");
path = file.uri;
} else {
path = zipOutputFile;
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
updateNextBackupTime();
updateNextBackupTime(backupType || "partial");
if (progress) {
endProgress();
}
const canShowCompletionStatus =
let showBackupCompleteSheet =
progress && SettingsService.get().showBackupCompleteSheet;
if (context) {
@@ -289,12 +234,15 @@ async function run(
await sleep(300);
if (canShowCompletionStatus) {
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(path);
} else {
progress && eSendEvent(eCloseSheet);
}
ToastManager.show({
heading: "Backup successful",
message: "Your backup is stored in Notesnook folder on your phone.",
type: "success",
context: "global"
});
@@ -303,26 +251,16 @@ async function run(
path: path
};
} catch (e) {
ToastManager.error(e as Error, "Backup failed", context || "global");
ToastManager.error(e, "Backup failed", context || "global");
if (
(e as Error)?.message?.includes("android.net.Uri") &&
androidBackupDirectory
) {
if (e?.message?.includes("android.net.Uri") && androidBackupDirectory) {
SettingsService.setProperty("backupDirectoryAndroid", null);
return run(progress, context, backupType);
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
return run(progress, context);
}
DatabaseLogger.error(e);
await sleep(300);
if (progress) {
endProgress();
}
progress && eSendEvent(eCloseSheet);
return {
error: e,
report: true
@@ -330,41 +268,34 @@ async function run(
}
}
async function checkBackupRequired(
type: "daily" | "off" | "useroff" | "weekly" | "monthly" | "never",
lastBackupDateType: "lastBackupDate" | "lastFullBackupDate" = "lastBackupDate"
) {
console.log(lastBackupDateType, type);
if (type === "off" || type === "useroff" || type === "never" || !type) return;
const now = Date.now();
const lastBackupDate = SettingsService.getProperty(lastBackupDateType) as
| number
| "never";
async function getLastBackupDate() {
return SettingsService.get().lastBackupDate;
}
if (
lastBackupDate === undefined ||
lastBackupDate === "never" ||
lastBackupDate === 0
) {
async function checkBackupRequired(type) {
if (type === "off" || type === "useroff") return;
let now = Date.now();
let lastBackupDate = await getLastBackupDate();
if (!lastBackupDate || lastBackupDate === "never") {
return true;
}
lastBackupDate = parseInt(lastBackupDate);
if (type === "daily" && lastBackupDate + MS_DAY < now) {
DatabaseLogger.info(`Daily backup started: ${type}`);
DatabaseLogger.info("Daily backup started");
return true;
} else if (type === "weekly" && lastBackupDate + MS_WEEK < now) {
DatabaseLogger.info(`Weekly backup started: ${type}`);
DatabaseLogger.info("Weekly backup started");
return true;
} else if (type === "monthly" && lastBackupDate + MONTH < now) {
DatabaseLogger.info(`Monthly backup started: ${type}`);
DatabaseLogger.info("Monthly backup started");
return true;
}
return false;
}
const checkAndRun = async () => {
const settings = SettingsService.get();
if (await checkBackupRequired(settings?.reminder)) {
let settings = SettingsService.get();
if (await checkBackupRequired(settings.reminder)) {
try {
await run();
} catch (e) {

View File

@@ -35,21 +35,22 @@ import dayjs, { Dayjs } from "dayjs";
import { encodeNonAsciiHTML } from "entities";
import { Platform } from "react-native";
import { db, setupDatabase } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import { presentDialog } from "../components/dialog/functions";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import { editorState } from "../screens/editor/tiptap/utils";
import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useUserStore } from "../stores/use-user-store";
import { eOnLoadNote } from "../utils/events";
import { tabBarRef } from "../utils/global-refs";
import { convertNoteToText } from "../utils/note-to-text";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import Navigation from "./navigation";
import SettingsService from "./settings";
import { useUserStore } from "../stores/use-user-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
let pinned: DisplayedNotification[] = [];
@@ -125,7 +126,7 @@ const onEvent = async ({ type, detail }: Event) => {
if (type === EventType.PRESS) {
notifee.decrementBadgeCount();
if (notification?.data?.type === "quickNote") return;
NotesnookModule.setAppState("");
MMKV.removeItem("appState");
if (notification?.data?.type === "reminder" && notification?.id) {
const reminder = await db.reminders?.reminder(
notification.id?.split("_")[0]
@@ -409,7 +410,9 @@ async function loadNote(id: string, jump: boolean) {
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}
NotesnookModule.setAppState(
MMKV.setString(
"appState",
JSON.stringify({
editing: true,
movedAway: false,

View File

@@ -30,7 +30,6 @@ import { scale, updateSize } from "../utils/size";
import { DatabaseLogger } from "../common/database";
import { useUserStore } from "../stores/use-user-store";
import ScreenGuardModule from "react-native-screenguard";
function reset() {
const settings = get();
if (settings.reminder !== "off" && settings.reminder !== "useroff") {
@@ -41,15 +40,10 @@ function reset() {
}
function resetSettings() {
const settings: SettingStore["settings"] = {
...defaultSettings,
introCompleted: true,
serverUrls: get().serverUrls,
backupDirectoryAndroid: undefined
};
MMKV.setString("appSettings", JSON.stringify(settings));
set(settings);
MMKV.setString(
"appSettings",
JSON.stringify({ ...defaultSettings, introCompleted: true })
);
init();
}

View File

@@ -30,9 +30,7 @@ export const ignoredMessages = [
"Sync already running",
"Not allowed to start service intent",
"WebSocket failed to connect",
"Failed to start the HttpConnection before",
"Could not connect to the Sync server",
"Network request failed"
"Failed to start the HttpConnection before"
];
let pendingSync: any = undefined;
let syncTimer: NodeJS.Timeout;
@@ -63,45 +61,58 @@ const run = async (
clearTimeout(syncTimer);
syncTimer = setTimeout(async () => {
const status = await NetInfo.fetch();
const userstore = useUserStore.getState();
userstore.setSyncing(true);
const user = await db.user.getUser();
if (!status.isInternetReachable) {
DatabaseLogger.warn("Internet not reachable");
}
if (!user || SettingsService.get().disableSync) {
userstore.setSyncing(false);
if (
!user ||
!status.isInternetReachable ||
SettingsService.get().disableSync
) {
initAfterSync();
pendingSync = undefined;
return onCompleted?.(SyncStatus.Failed);
}
userstore.setSyncing(true);
let error: Error | undefined = undefined;
let error = null;
try {
await BackgroundSync.doInBackground(async () => {
try {
await db.sync({
type: type,
force: forced,
offlineMode: SettingsService.get().offlineMode
force: forced
});
} catch (e) {
error = e as Error;
error = e;
}
});
if (error) throw error;
if (error) {
throw error;
}
} catch (e) {
error = e as Error;
DatabaseLogger.error(error, "[Client] Failed to sync");
error = e;
if (
!ignoredMessages.find((message) => error?.message?.includes(message)) &&
userstore.user
!ignoredMessages.find((message) =>
(e as Error).message?.includes(message)
) &&
userstore.user &&
status.isConnected &&
status.isInternetReachable
) {
const status = await NetInfo.fetch();
userstore.setSyncing(false, SyncStatus.Failed);
if (status.isConnected && status.isInternetReachable) {
ToastManager.error(e as Error, "Sync failed", context);
}
}
DatabaseLogger.error(e, "[Client] Failed to sync");
} finally {
initAfterSync();
userstore.setSyncing(
@@ -109,14 +120,16 @@ const run = async (
error ? SyncStatus.Failed : SyncStatus.Passed
);
onCompleted?.(error ? SyncStatus.Failed : SyncStatus.Passed);
if (pendingSync)
Sync.run(
pendingSync.context,
pendingSync.forced,
pendingSync.type,
pendingSync.onCompleted,
pendingSync.lastSyncTime
);
setImmediate(() => {
if (pendingSync)
Sync.run(
pendingSync.context,
pendingSync.forced,
pendingSync.type,
pendingSync.onCompleted,
pendingSync.lastSyncTime
);
});
}
}, 300);
};

View File

@@ -25,8 +25,6 @@ import { FileType } from "react-native-scoped-storage";
import create, { State } from "zustand";
import { ThemeDark, ThemeLight, ThemeDefinition } from "@notesnook/theme";
import { Reminder } from "@notesnook/core/dist/types";
export const HostIds = ["API_HOST", "AUTH_HOST", "SSE_HOST"] as const;
export type HostId = (typeof HostIds)[number];
export type Settings = {
showToolbarOnTop?: boolean;
@@ -34,8 +32,7 @@ export type Settings = {
fontScale?: number;
forcePortraitOnTablet?: boolean;
useSystemTheme?: boolean;
reminder: "daily" | "off" | "useroff" | "weekly" | "monthly";
fullBackupReminder: "never" | "weekly" | "monthly";
reminder?: string;
encryptedBackup?: boolean;
homepage?: string;
sort?: string;
@@ -45,6 +42,7 @@ export type Settings = {
appLockTimer: number;
appLockEnabled?: boolean;
appLockMode?: "none" | "background" | "launch";
telemetry?: boolean;
notebooksListMode?: "normal" | "compact";
notesListMode?: "normal" | "compact";
devMode?: boolean;
@@ -54,8 +52,8 @@ export type Settings = {
rateApp?: boolean | number;
migrated?: boolean;
introCompleted?: boolean;
nextBackupRequestTime?: number;
lastBackupDate?: number;
nextBackupRequestTime?: number | undefined;
lastBackupDate?: number | undefined;
userEmailConfirmed?: boolean;
recoveryKeySaved?: boolean;
backupDirectoryAndroid?: FileType | null;
@@ -84,10 +82,6 @@ export type Settings = {
backgroundSync?: boolean;
applockKeyboardType: "numeric" | "default";
settingsVersion?: number;
backupType: "full" | "partial";
offlineMode?: boolean;
lastFullBackupDate?: number;
serverUrls?: Record<HostId, string>;
};
type DimensionsType = {
@@ -133,12 +127,6 @@ export const defaultSettings: SettingStore["settings"] = {
applockKeyboardType: "numeric",
appLockTimer: 0,
showToolbarOnTop: false,
disableAutoSync: false,
disableRealtimeSync: false,
disableSync: false,
appLockEnabled: false,
backupDirectoryAndroid: null,
offlineMode: false,
showKeyboardOnOpen: false,
fontScale: 1,
forcePortraitOnTablet: false,
@@ -151,6 +139,7 @@ export const defaultSettings: SettingStore["settings"] = {
screenshotMode: true,
privacyScreen: false,
appLockMode: "none",
telemetry: false,
notebooksListMode: "normal",
notesListMode: "normal",
devMode: false,
@@ -161,7 +150,7 @@ export const defaultSettings: SettingStore["settings"] = {
migrated: false,
introCompleted: Config.isTesting ? true : false,
nextBackupRequestTime: undefined,
lastBackupDate: 0,
lastBackupDate: undefined,
userEmailConfirmed: false,
recoveryKeySaved: false,
showBackupCompleteSheet: true,
@@ -182,10 +171,7 @@ export const defaultSettings: SettingStore["settings"] = {
biometricsAuthEnabled: false,
appLockHasPasswordSecurity: false,
backgroundSync: true,
settingsVersion: 0,
backupType: "partial",
fullBackupReminder: "never",
lastFullBackupDate: 0
settingsVersion: 0
};
export const useSettingStore = create<SettingStore>((set, get) => ({

View File

@@ -23,17 +23,13 @@ interface NotesnookModuleInterface {
getActivityName: () => Promise<string>;
setBackgroundColor: (color: string) => void;
setSecureMode: (enabled: boolean) => void;
setAppState: (appState: string) => void;
getAppState: () => string;
}
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
ios: {
getActivityName: () => {},
setBackgroundColor: () => {},
setSecureMode: () => {},
setAppState: () => {},
getAppState: () => {}
setSecureMode: () => {}
},
android: NativeModules.NNativeModule
});

View File

@@ -1,7 +1,4 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
# bound in the template on Cocoapods with next React Native release.
gem 'cocoapods', '>= 1.13', '< 1.15'
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
gem 'cocoapods', '~> 1.12'

View File

@@ -1,7 +1,5 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
import com.android.build.OutputFile
@@ -49,6 +47,7 @@ react {
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
hermesFlags = ["-O", "-output-source-map"]
}
println(projectDir);
/**
* Set this to true to create two separate APKs instead of one:
@@ -97,14 +96,10 @@ def getNpmVersion() {
return jsonPackage["version"]
}
def isBuildingAAB = gradle.startParameter.getTaskNames().any { it.contains("bundleRelease") }
android {
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -116,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3027
versionCode 3020
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -168,12 +163,8 @@ android {
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
jniLibs {
useLegacyPackaging isBuildingAAB == false
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
@@ -187,15 +178,9 @@ android {
defaultConfig.versionCode * 5 + versionCodes.get(abi)
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
} else {
if (isBuildingAAB) {
output.versionCodeOverride = 4 * 1048576 + defaultConfig.versionCode
println("AAB version code: ${output.versionCodeOverride}");
} else {
output.versionCodeOverride =
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
}
}
}
@@ -218,7 +203,13 @@ dependencies {
androidTestImplementation('com.wix:detox:+')
implementation 'androidx.appcompat:appcompat:1.1.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {

View File

@@ -2,9 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.streetwriters.notesnook;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View File

@@ -18,7 +18,6 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:node="remove" />
<queries>
<intent>

View File

@@ -3,7 +3,6 @@ package com.streetwriters.notesnook;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.app.ActivityManager;
import com.facebook.react.HeadlessJsTaskService;
@@ -20,12 +19,8 @@ public class BootRecieverService extends BroadcastReceiver {
network connections
**/
Intent serviceIntent = new Intent(context, BootTaskService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
HeadlessJsTaskService.acquireWakeLockNow(context);
context.startService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}

View File

@@ -1,24 +1,15 @@
package com.streetwriters.notesnook;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;
public class BootTaskService extends HeadlessJsTaskService {
Notification notification;
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Log.d("BootTask", "Task Started");
return new HeadlessJsTaskConfig(
"com.streetwriters.notesnook.BOOT_TASK",
Arguments.createMap(),
@@ -26,31 +17,4 @@ public class BootTaskService extends HeadlessJsTaskService {
false // optional: defines whether or not the task is allowed in foreground. Default is false
);
}
@Override
public void onHeadlessJsTaskFinish(int taskId) {
super.onHeadlessJsTaskFinish(taskId);
Log.d("BootTask", "Task completed");
stopSelf();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
"Default",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
notification = new NotificationCompat.Builder(this, channel.getId())
.setContentTitle("Sync on boot")
.setContentText("")
.setSmallIcon(R.drawable.ic_stat_name)
.build();
startForeground(1, notification);
}
return super.onStartCommand(intent, flags, startId);
}
}

View File

@@ -0,0 +1,65 @@
package com.streetwriters.notesnook;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.List;
import androidx.multidex.MultiDexApplication;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
public class MainApplication extends MultiDexApplication implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new NNativeModulePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
com.streetwriters.notesnook.ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
}

View File

@@ -1,44 +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.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(NNativeModulePackage());
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}

View File

@@ -1,7 +1,5 @@
package com.streetwriters.notesnook;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Build;
import android.service.quicksettings.Tile;
@@ -11,24 +9,11 @@ import androidx.annotation.RequiresApi;
@RequiresApi(api = Build.VERSION_CODES.N)
public class NotesnookTileService extends TileService {
@SuppressLint("StartActivityAndCollapseDeprecated")
@Override
public void onClick() {
super.onClick();
Intent intent = new Intent(this.getApplicationContext(), ShareActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(
PendingIntent.getActivity(
this.getApplicationContext(),
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
);
} else {
startActivityAndCollapse(intent);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAndCollapse(intent);
}
}

View File

@@ -1,12 +1,17 @@
package com.streetwriters.notesnook;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.util.Log;
import com.ammarahmed.mmkv.MMKV;
public class OnClearFromRecentService extends Service {
static {
System.loadLibrary("rnmmkv");
}
@Override
public IBinder onBind(Intent intent) {
return null;
@@ -25,12 +30,15 @@ public class OnClearFromRecentService extends Service {
@Override
public void onTaskRemoved(Intent rootIntent) {
try {
SharedPreferences appStateDetails = getApplicationContext().getSharedPreferences("appStateDetails", MODE_PRIVATE);
SharedPreferences.Editor edit = appStateDetails.edit();
edit.remove("appState");
edit.apply();
MMKV.initialize(getApplicationContext());
MMKV mmkv = MMKV.mmkvWithID("default",MMKV.SINGLE_PROCESS_MODE);
mmkv.removeValueForKey("appState");
stopSelf();
} catch (UnsatisfiedLinkError | Exception e) {
} catch (UnsatisfiedLinkError e) {
} catch (Exception e) {
}
//System.exit(0);
}
}

View File

@@ -1,10 +1,7 @@
package com.streetwriters.notesnook;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.util.Log;
import android.view.WindowManager;
import com.facebook.react.bridge.Promise;
@@ -70,21 +67,6 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
}
}
@ReactMethod
public void setAppState(final String appState) {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = appStateDetails.edit();
edit.putString("appState", appState);
edit.apply();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String getAppState() {
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
String appStateValue = appStateDetails.getString("appState", "");
return appStateValue.isEmpty() ? null : appStateValue;
}
}

View File

@@ -67,6 +67,7 @@ public class ShareApplication extends MultiDexApplication implements ReactApplic
@Override
public void onCreate() {
super.onCreate();
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
}

View File

@@ -3,11 +3,11 @@ import org.apache.tools.ant.taskdefs.condition.Os
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 23
compileSdkVersion = 34
targetSdkVersion = 34
kotlinVersion = '1.9.22'
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
ext.kotlinVersion = '1.8.0'
androidXAnnotation = "1.1.0"
androidXCoreVersion = "1.7.0"
androidXCore = "1.7.0"
@@ -23,11 +23,10 @@ buildscript {
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
apply plugin: "com.facebook.react.rootproject"
allprojects {
repositories {
@@ -39,9 +38,9 @@ allprojects {
maven { url 'https://www.jitpack.io' }
}
// tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// kotlinOptions {
// jvmTarget = "11"
// }
// }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "11"
}
}
}

View File

@@ -24,6 +24,9 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.182.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64

View File

@@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -83,8 +83,10 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -131,13 +133,10 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -145,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -153,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -198,15 +197,11 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

View File

@@ -1,6 +1,3 @@
- Fix crash when launching Notesnook from Notification tiles
- Fix app crashing on background on boot
- Fix self-hosted server urls not working
- Bug fixes and small improvements
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -2,7 +2,7 @@ const env = process.env.BABEL_ENV || process.env.NODE_ENV;
const configs = {
env: {
development: {
presets: ['module:@react-native/babel-preset'],
presets: ['module:metro-react-native-babel-preset'],
plugins: [
'@babel/plugin-transform-named-capturing-groups-regex',
'react-native-reanimated/plugin',
@@ -18,7 +18,7 @@ const configs = {
]
},
test: {
presets: ['module:@react-native/babel-preset'],
presets: ['module:metro-react-native-babel-preset'],
plugins: [
'@babel/plugin-transform-named-capturing-groups-regex',
'react-native-reanimated/plugin',
@@ -40,7 +40,7 @@ const configs = {
},
production: {
presets: ['module:@react-native/babel-preset'],
presets: ['module:metro-react-native-babel-preset'],
plugins: [
'transform-remove-console',
'@babel/plugin-transform-named-capturing-groups-regex',

View File

@@ -11,10 +11,6 @@ import NetInfo from "@react-native-community/netinfo";
import { enableFreeze } from "react-native-screens";
import {BackgroundSync} from '../app/services/background-sync';
BackgroundSync.registerHeadlessTask();
BackgroundSync.start();
Notifications.init();
enableFreeze(true);
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
@@ -23,7 +19,8 @@ NetInfo.configure({
return response?.status >= 200 && response?.status < 300;
}
});
BackgroundSync.start();
Notifications.init();
const appName = appJson.name;
if (Config.isTesting) {

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -14,7 +14,6 @@
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* NotesnookTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* NotesnookTests.m */; };
2E4BF736C2EB268ED3FD9C29 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */; };
6510E6D72877215700DACAA9 /* build.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6510E6D62877215700DACAA9 /* build.bundle */; };
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */; };
@@ -43,7 +42,6 @@
87CE5B71DC72F3D95B7B1AA5 /* libPods-Notesnook-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */; };
A79A93DC9D0CAE8703462151 /* libPods-Notesnook-NotesnookTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 068FA9BDFA207A50B0A9E597 /* libPods-Notesnook-NotesnookTests.a */; };
C619D096A9DE2070DBEAC70F /* libPods-Make Note.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */; };
E7F9D56665C2F19F4A97BF49 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */; };
F50BF8BC21F8B58EB2B5A31A /* libPods-Notesnook-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 16C0296DE7A2B4102817F5C1 /* libPods-Notesnook-tvOS.a */; };
/* End PBXBuildFile section */
@@ -112,7 +110,6 @@
2D02E4901E0B4A5D006451C7 /* Notesnook-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Notesnook-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
41D57E9831C724F59F18FBAE /* libPods-Notesnook-Add to Notes.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-Add to Notes.a"; sourceTree = BUILT_PRODUCTS_DIR; };
47AE097A087A651BD60CAD7B /* Pods-Notesnook-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-Make Note/Pods-Notesnook-Make Note.release.xcconfig"; sourceTree = "<group>"; };
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
4BD34AE0C0FD2416E48803B8 /* libPods-Notesnook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook.a"; sourceTree = BUILT_PRODUCTS_DIR; };
4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Make Note.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6510E6D62877215700DACAA9 /* build.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = build.bundle; path = "../../../../packages/editor-mobile/build.bundle"; sourceTree = "<group>"; };
@@ -152,7 +149,6 @@
88211800FA8BF60638327ADE /* Pods-Make Note.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.debug.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.debug.xcconfig"; sourceTree = "<group>"; };
A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A84F3F0D50641D1CD4569B0E /* Pods-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.release.xcconfig"; sourceTree = "<group>"; };
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
B9989E5C777BB11343A62D09 /* Pods-Notesnook-NotesnookTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-NotesnookTests.debug.xcconfig"; path = "Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests.debug.xcconfig"; sourceTree = "<group>"; };
D1710CA05FED221CC56D2BE1 /* Pods-Notesnook-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-tvOSTests/Pods-Notesnook-tvOSTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -247,7 +243,6 @@
13B07FB71A68108700A75B9A /* main.m */,
65E0340A257B9FF100793428 /* File.swift */,
65E03409257B9FF100793428 /* Notesnook-Bridging-Header.h */,
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */,
);
name = Notesnook;
sourceTree = "<group>";
@@ -320,7 +315,6 @@
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
DA84645424CFBF19A44D64B3 /* Pods */,
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -373,7 +367,6 @@
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
819D480147D052141ACC55A9 /* [CP] Copy Pods Resources */,
310C868ED8D8299D75460154 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -397,7 +390,6 @@
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
240525450DF9ABA0F332B52E /* [CP] Copy Pods Resources */,
65A7F34B255687E600699170 /* Embed App Extensions */,
48C834D962D612F18A0388B3 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -564,7 +556,6 @@
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */,
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */,
E7F9D56665C2F19F4A97BF49 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -573,7 +564,6 @@
buildActionMask = 2147483647;
files = (
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,
2E4BF736C2EB268ED3FD9C29 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -652,13 +642,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -702,42 +692,6 @@
shellPath = /bin/sh;
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh";
};
310C868ED8D8299D75460154 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
48C834D962D612F18A0388B3 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
52B9E42E0FD77D180139A8C7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -809,13 +763,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -852,11 +806,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Make Note/Pods-Make Note-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -1061,7 +1015,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1135,7 +1089,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1166,7 +1120,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1240,7 +1194,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1399,7 +1353,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1411,7 +1365,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1442,7 +1396,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1455,7 +1409,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1485,7 +1439,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1559,7 +1513,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1590,7 +1544,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2114;
CURRENT_PROJECT_VERSION = 2109;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1665,7 +1619,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.0.12;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1684,7 +1638,7 @@
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1742,13 +1696,8 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
};
name = Debug;
};
@@ -1758,7 +1707,7 @@
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1809,13 +1758,8 @@
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -19,16 +19,9 @@
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self bundleURL];
}
- (NSURL *)bundleURL
{
#if DEBUG
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

View File

@@ -71,11 +71,14 @@
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>Take a picture and add it to your note.</string>
@@ -112,7 +115,7 @@
<string>BootSplash</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -14,7 +14,16 @@ require Pod::Executable.execute_command('node', ['-p',
platform :ios, min_ios_version_supported
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
@@ -29,14 +38,22 @@ pod 'MMKV'
target 'Notesnook' do
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => true,
:fabric_enabled => false,
:hermes_enabled => false,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
# :flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
@@ -54,7 +71,7 @@ target 'Make Note' do
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => true,
:hermes_enabled => false,
:fabric_enabled => false,
# Enables Flipper.
#
@@ -101,9 +118,9 @@ post_install do |installer|
react_native_path = "../../node_modules/react-native",
# Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => false,
# :ccache_enabled => true
:mac_catalyst_enabled => false
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
@@ -113,7 +130,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == "react-native-quick-sqlite" then
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -4,16 +4,12 @@
"version": "1.0.0",
"main": "index.js",
"license": "GPL-3.0-or-later",
"overrides": {
"react-native": "0.74.5",
"react": "18.2.0"
},
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^4.1.1",
"@callstack/repack": "^3.2.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "6.6.0",
@@ -24,8 +20,9 @@
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "6.6.2",
"@sayem314/react-native-keep-awake": "^1.0.4",
"@shopify/flash-list": "1.4.0",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native": "0.72.0",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-background-actions": "^2.6.6",
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
@@ -39,32 +36,31 @@
"react-native-exit-app": "https://github.com/ammarahm-ed/react-native-exit-app.git",
"react-native-file-viewer": "^2.1.1",
"react-native-fingerprint-scanner": "https://github.com/ammarahm-ed/react-native-fingerprint-scanner.git",
"react-native-gesture-handler": "^2.18.0",
"react-native-reanimated": "3.14.0",
"react-native-safe-area-context": "^4.10.8",
"react-native-screens": "^3.33.0",
"react-native-webview": "^13.10.0",
"@shopify/flash-list": "1.7.0",
"react-native-vector-icons": "9.2.0",
"react-native-gesture-handler": "^2.12.0",
"react-native-get-random-values": "^1.7.0",
"react-native-gzip": "1.1.0",
"react-native-html-to-pdf-lite": "^0.9.1",
"react-native-iap": "12.15.1",
"react-native-iap": "12.11.0",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.2",
"react-native-mmkv-storage": "^0.10.0-alpha.12",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
"react-native-pdf": "6.6.2",
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
"react-native-reanimated": "3.3.0",
"react-native-safe-area-context": "^4.3.1",
"react-native-scoped-storage": "^1.9.5",
"react-native-screens": "^3.13.1",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^7.2.0",
"react-native-svg": "^12.3.0",
"react-native-swiper-flatlist": "3.2.2",
"react-native-tooltips": "^1.0.3",
"react-native-vector-icons": "9.2.0",
"react-native-webview": "^13.10.0",
"react-native-zip-archive": "6.0.9",
"react-native-quick-sqlite": "^8.0.6",
"react-native-theme-switch-animation": "^0.6.0",
@@ -80,19 +76,19 @@
"@babel/plugin-transform-private-methods": "^7.22.5",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "0.74.86",
"@react-native/metro-config": "0.74.86",
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.72.6",
"@tsconfig/react-native": "^3.0.2",
"@types/html-to-text": "^8.0.1",
"@types/jest": "^29.2.1",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.2.6",
"@types/react": "^18.0.24",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"babel-jest": "^29.6.3",
"babel-jest": "^29.6.2",
"babel-loader": "^8.2.5",
"babel-plugin-module-resolver": "^4.1.0",
"babel-plugin-transform-remove-console": "6.9.4",
@@ -104,12 +100,12 @@
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"expect": "^29.6.2",
"jest": "^29.6.3",
"jest": "^29.6.2",
"jest-circus": "^29.6.2",
"jest-image-snapshot": "^6.2.0",
"@react-native/babel-preset": "0.74.86",
"metro-react-native-babel-preset": "0.76.5",
"pixelmatch": "^5.3.0",
"prettier": "2.8.8",
"prettier": "^2.4.1",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-bundle-visualizer": "^3.1.1",
"react-native-cli-bump-version": "^1.3.0",

View File

@@ -36,10 +36,4 @@ if (isGithubRelease) {
}
}
const repackCommands = require('@callstack/repack/commands');
config.commands = repackCommands.filter((command) =>
command.name.startsWith('webpack')
),
module.exports = config;

View File

@@ -26,7 +26,7 @@ module.exports = (env) => {
mode = "development",
context = __dirname,
entry = "./index.js",
platform = process.env.PLATFORM,
platform,
minimize = mode === "production",
devServer = undefined,
bundleFilename = undefined,
@@ -118,8 +118,7 @@ module.exports = (env) => {
*/
output: {
clean: true,
hashFunction: 'xxhash64',
path: path.join(__dirname, 'build/generated', platform),
path: path.join(__dirname, "build/generated", platform),
filename: "index.bundle",
chunkFilename: "[name].chunk.bundle",
publicPath: Repack.getPublicPath({ platform, devServer }),
@@ -173,7 +172,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: ["module:@react-native/babel-preset"],
presets: ["module:metro-react-native-babel-preset"],
plugins: [
"react-native-reanimated/plugin",
"@babel/plugin-transform-named-capturing-groups-regex",
@@ -185,7 +184,7 @@ module.exports = (env) => {
{
test: /\.mjs$|cjs$|js$|jsx$|ts$|tsx$/,
include: [
/node_modules(.*[/\\])+react-native/,
/node_modules(.*[/\\])+react/,
/node_modules(.*[/\\])+@react-native/,
/node_modules(.*[/\\])+@react-navigation/,
/node_modules(.*[/\\])+@react-native-community/,
@@ -221,7 +220,6 @@ module.exports = (env) => {
/node_modules(.*[/\\])+whatwg-url/,
/node_modules(.*[/\\])+react-native-url-polyfill/,
/node_modules(.*[/\\])+diffblazer/,
/node_modules(.*[/\\])+react-freeze/,
],
use: {
loader: "babel-loader",
@@ -232,7 +230,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: ["module:@react-native/babel-preset"],
presets: ["module:metro-react-native-babel-preset"],
plugins: [
"react-native-reanimated/plugin",
"@babel/plugin-transform-named-capturing-groups-regex",
@@ -260,7 +258,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: [["module:@react-native/babel-preset"]],
presets: [["module:metro-react-native-babel-preset"]],
plugins:
devServer && devServer.hmr
? [

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.17",
"version": "3.0.12",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -25,15 +25,10 @@
},
"devDependencies": {
"fonteditor-core": "^2.1.11",
"listr": "^0.14.3",
"otplib": "12.0.1",
"patch-package": "7.0.0",
"react-refresh": "0.14.0"
},
"overrides": {
"react-native": "0.74.5",
"react": "18.2.0"
},
"dependencies": {
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
@@ -44,8 +39,6 @@
"@notesnook/themes-server": "file:../../servers/themes",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.74.5",
"react-native-mmkv-storage": "^0.10.2",
"react-native-actions-sheet": "^0.9.7"
"react-native": "0.72.0"
}
}

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/@callstack/repack/android/build.gradle b/node_modules/@callstack/repack/android/build.gradle
index e41f2f3..b04735e 100644
--- a/node_modules/@callstack/repack/android/build.gradle
+++ b/node_modules/@callstack/repack/android/build.gradle
@@ -29,7 +29,7 @@ android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
buildToolsVersion getExtOrDefault('buildToolsVersion')
defaultConfig {
- minSdkVersion 16
+ minSdkVersion 21
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"

View File

@@ -1,11 +0,0 @@
diff --git a/node_modules/@react-native/community-cli-plugin/dist/utils/loadMetroConfig.js b/node_modules/@react-native/community-cli-plugin/dist/utils/loadMetroConfig.js
index 636c0d8..87f4ee6 100644
--- a/node_modules/@react-native/community-cli-plugin/dist/utils/loadMetroConfig.js
+++ b/node_modules/@react-native/community-cli-plugin/dist/utils/loadMetroConfig.js
@@ -105,5 +105,6 @@ This warning will be removed in future (https://github.com/facebook/metro/issues
...options,
});
const overrideConfig = getOverrideConfig(ctx, config);
+ if (!overrideConfig.resolver.resolveRequest) delete overrideConfig.resolver.resolveRequest;
return (0, _metroConfig.mergeConfig)(config, overrideConfig);
}

View File

@@ -0,0 +1,24 @@
diff --git a/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.d.ts b/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.d.ts
index 6cbcc22..62d5422 100644
--- a/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.d.ts
+++ b/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.d.ts
@@ -12,6 +12,7 @@ export interface ConfigOptionsT {
reporter?: any;
config?: string;
}
+
/**
* Load Metro config.
*
diff --git a/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.js b/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.js
index f26ef52..233a7c9 100644
--- a/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.js
+++ b/node_modules/@react-native-community/cli-plugin-metro/build/tools/loadMetroConfig.js
@@ -82,6 +82,7 @@ async function loadMetroConfig(ctx, options = {}) {
(0, _getDefaultMetroConfig.default)(ctx));
return (0, _metroConfig().mergeConfig)(loadedConfig, overrideConfig);
}
+ if (!overrideConfig.resolver.resolveRequest) delete overrideConfig.resolver.resolveRequest;
return (0, _metroConfig().mergeConfig)(await (0, _metroConfig().loadConfig)({
cwd: ctx.root,
...options

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