mirror of
https://github.com/PhilKes/NotallyX.git
synced 2025-06-30 05:09:53 +00:00
Compare commits
150 commits
Author | SHA1 | Date | |
---|---|---|---|
|
d00300fa0e | ||
|
f13e8227ca | ||
|
11e472cd30 | ||
|
4cc957ccd4 | ||
|
86b74762c5 | ||
|
118285545a | ||
|
402baf8056 | ||
|
de27d40880 | ||
|
66ce623e85 | ||
|
3c2400c7e6 | ||
|
c64a7b2ed7 | ||
|
fb687856f1 | ||
|
23d678c8a3 | ||
|
ade08b52ed | ||
|
62a35132e0 | ||
|
9fbe5a6b94 | ||
|
cf7f6f9dda | ||
|
01ac48f930 | ||
|
015f43e94b | ||
|
830fb6a75c | ||
|
c34ee3633e | ||
|
628bd9d564 | ||
|
3e889879fb | ||
|
b191618a46 | ||
|
5cbc62bdf7 | ||
|
d1e5770180 | ||
|
29cee8faf4 | ||
|
4f993af93f | ||
|
0f0eb80e9b | ||
|
1314ab4437 | ||
|
39022edfab | ||
|
157ecb1b13 | ||
|
fb35ffdac4 | ||
|
0fee25f022 | ||
|
2341c30586 | ||
|
e553e78efb | ||
|
724d08507a | ||
|
771546a0cb | ||
|
1a6d4083e4 | ||
|
3ac63349d8 | ||
|
06c48ab8d9 | ||
|
8d20f26eae | ||
|
66f0d33cd4 | ||
|
6d81b6f7c0 | ||
|
2c1f5d5338 | ||
|
adb981d76c | ||
|
73a0345fe4 | ||
|
0407f2bdc4 | ||
|
212c354072 | ||
|
07ff5691e2 | ||
|
20cf84ab69 | ||
|
6bfa013a6c | ||
|
66a7b02c69 | ||
|
62035091f5 | ||
|
209e19d690 | ||
|
899f2c0f9a | ||
|
3a0b699c82 | ||
|
18947835f1 | ||
|
5c0ea100ee | ||
|
392a060329 | ||
|
1aa5e2c9e7 | ||
|
0d86d7aad8 | ||
|
772a43de31 | ||
|
ad9b410d45 | ||
|
6cf2a8ce1f | ||
|
c33b639a07 | ||
|
61112a18d2 | ||
|
30f889f3db | ||
|
b95782e53a | ||
|
19df4b817f | ||
|
333b57c29d | ||
|
87124c32f4 | ||
|
f4a7074811 | ||
|
1f6afb03d4 | ||
|
56683d5255 | ||
|
e24f630acf | ||
|
6b3fec40eb | ||
|
205116ac60 | ||
|
ffeecdf1ca | ||
|
8d6b318e3b | ||
|
8d477e4366 | ||
|
79eacb079d | ||
|
c122c2cc48 | ||
|
f97f99ded0 | ||
|
506bc5c362 | ||
|
8784145b83 | ||
|
7b1aa83fca | ||
|
f9ea26f1fa | ||
|
54d835e40b | ||
|
9fd735ba95 | ||
|
b5e13ce73a | ||
|
c586eab072 | ||
|
860db3e6bb | ||
|
883998e27f | ||
|
4924ee46ec | ||
|
ad5ad25e11 | ||
|
93119098bc | ||
|
764c562859 | ||
|
b1bf6bc12c | ||
|
44d19341b1 | ||
|
be734e080c | ||
|
8a4e2f9a92 | ||
|
48c07dfe0f | ||
|
e09b0f44b7 | ||
|
155d4c1cd9 | ||
|
8e3110f077 | ||
|
17a3eda124 | ||
|
7edbcccbe4 | ||
|
f4bfa7ccb8 | ||
|
ae8bc8e859 | ||
|
8af22e1e88 | ||
|
a57628dc7a | ||
|
d239f20e6f | ||
|
2ddfd5adb9 | ||
|
b7b0b48c62 | ||
|
cc64bad689 | ||
|
f4de4133ed | ||
|
21707dfe08 | ||
|
d2ba38a20e | ||
|
fe6d6eca8b | ||
|
c5cfa7a6c9 | ||
|
5711830b0f | ||
|
dae19f07e0 | ||
|
36f3cc284f | ||
|
f38f813af6 | ||
|
6b80b37714 | ||
|
6c78204111 | ||
|
900defa670 | ||
|
a925712e1f | ||
|
11d1d1fcc5 | ||
|
67e2a35c8f | ||
|
11eeb0d5bc | ||
|
259e223637 | ||
|
d7ad549878 | ||
|
86998a84a0 | ||
|
ac2b87bba1 | ||
|
13e7b5ac1e | ||
|
5ac794885f | ||
|
320c9048a5 | ||
|
0cb3fa92df | ||
|
2bd5c575fe | ||
|
dbe1b0726b | ||
|
dad74ace96 | ||
|
467f877dcc | ||
|
3e4acd9355 | ||
|
715d1aba1d | ||
|
000bfe7466 | ||
|
b0d3cde257 | ||
|
d330f93f00 | ||
|
f64267c226 |
148 changed files with 300906 additions and 981 deletions
56
.github/workflows/deploy.yaml
vendored
Normal file
56
.github/workflows/deploy.yaml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- documentation/**
|
||||||
|
# Review gh actions docs if you want to further define triggers, paths, etc
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Docusaurus
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache-dependency-path: documentation/yarn.lock
|
||||||
|
cache: yarn
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: documentation
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
- name: Build website
|
||||||
|
working-directory: documentation
|
||||||
|
run: yarn build
|
||||||
|
|
||||||
|
- name: Upload Build Artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: documentation/build
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||||
|
permissions:
|
||||||
|
pages: write # to deploy to Pages
|
||||||
|
id-token: write # to verify the deployment originates from an appropriate source
|
||||||
|
|
||||||
|
# Deploy to the github-pages environment
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
82
CHANGELOG.md
82
CHANGELOG.md
|
@ -1,5 +1,87 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [v7.4.0](https://github.com/PhilKes/NotallyX/tree/v7.4.0) (2025-04-18)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.3.1...v7.4.0)
|
||||||
|
|
||||||
|
### Added Features
|
||||||
|
|
||||||
|
- Don't force capitalization when adding a label [\#532](https://github.com/PhilKes/NotallyX/issues/532)
|
||||||
|
- Add a screen protection against screenshot attempts [\#386](https://github.com/PhilKes/NotallyX/issues/386)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Share pure text note error [\#544](https://github.com/PhilKes/NotallyX/issues/544)
|
||||||
|
- Crash when deleting checked items in a list [\#539](https://github.com/PhilKes/NotallyX/issues/539)
|
||||||
|
- Keyboard don't open after closing it on Android 7 [\#537](https://github.com/PhilKes/NotallyX/issues/537)
|
||||||
|
- Unable to open links before changing view mode [\#527](https://github.com/PhilKes/NotallyX/issues/527)
|
||||||
|
- Reminder popup cut on small screens [\#522](https://github.com/PhilKes/NotallyX/issues/522)
|
||||||
|
- Auto Backup failed [\#514](https://github.com/PhilKes/NotallyX/issues/514)
|
||||||
|
|
||||||
|
## [v7.3.1](https://github.com/PhilKes/NotallyX/tree/v7.3.1) (2025-04-08)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.3.0...v7.3.1)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Button to close note search doesn't work [\#519](https://github.com/PhilKes/NotallyX/issues/519)
|
||||||
|
- app crashes when pressing label [\#517](https://github.com/PhilKes/NotallyX/issues/517)
|
||||||
|
|
||||||
|
## [v7.3.0](https://github.com/PhilKes/NotallyX/tree/v7.3.0) (2025-04-07)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.2.1...v7.3.0)
|
||||||
|
|
||||||
|
### Added Features
|
||||||
|
|
||||||
|
- Persist viewMode of each note individually [\#497](https://github.com/PhilKes/NotallyX/issues/497)
|
||||||
|
- Read-only mode by default and new notes [\#495](https://github.com/PhilKes/NotallyX/issues/495)
|
||||||
|
- Hide notes based on labels [\#401](https://github.com/PhilKes/NotallyX/issues/401)
|
||||||
|
- An archived note should be visible in its label's folder. [\#398](https://github.com/PhilKes/NotallyX/issues/398)
|
||||||
|
- Sharing notes from the app [\#380](https://github.com/PhilKes/NotallyX/issues/380)
|
||||||
|
- Add support for json notes import [\#377](https://github.com/PhilKes/NotallyX/issues/377)
|
||||||
|
- Sharing images to the app [\#281](https://github.com/PhilKes/NotallyX/issues/281)
|
||||||
|
- Strikethrough checked items lists [\#250](https://github.com/PhilKes/NotallyX/issues/250)
|
||||||
|
- Click on list element to check it [\#248](https://github.com/PhilKes/NotallyX/issues/248)
|
||||||
|
- Add long press actions to undo/redo buttons [\#244](https://github.com/PhilKes/NotallyX/issues/244)
|
||||||
|
- Convert Note \<=\> List [\#190](https://github.com/PhilKes/NotallyX/issues/190)
|
||||||
|
- Edit labels inside notes [\#180](https://github.com/PhilKes/NotallyX/issues/180)
|
||||||
|
- Support Wallpaper color themes [\#175](https://github.com/PhilKes/NotallyX/issues/175)
|
||||||
|
- View Mode [\#76](https://github.com/PhilKes/NotallyX/issues/76)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Android 7.0 Navigation bar color issue [\#515](https://github.com/PhilKes/NotallyX/issues/515)
|
||||||
|
- Search Mode loop for Android \< 9.0 [\#508](https://github.com/PhilKes/NotallyX/issues/508)
|
||||||
|
- BaseNote.viewMode database migration error [\#505](https://github.com/PhilKes/NotallyX/issues/505)
|
||||||
|
- New list items can't be added with linebreak/enter [\#496](https://github.com/PhilKes/NotallyX/issues/496)
|
||||||
|
- Undo changes more than the last changed character [\#472](https://github.com/PhilKes/NotallyX/issues/472)
|
||||||
|
- Auto Backup failed [\#468](https://github.com/PhilKes/NotallyX/issues/468)
|
||||||
|
- Amount of backups to keep in periodic backups are not respected if nextcloud mount is used. [\#133](https://github.com/PhilKes/NotallyX/issues/133)
|
||||||
|
|
||||||
|
## [v7.2.1](https://github.com/PhilKes/NotallyX/tree/v7.2.1) (2025-03-18)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.2.0...v7.2.1)
|
||||||
|
|
||||||
|
### Added Features
|
||||||
|
|
||||||
|
- Note not automatically saved when App is killed by system [\#446](https://github.com/PhilKes/NotallyX/issues/446)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Auto Backup failed [\#456](https://github.com/PhilKes/NotallyX/issues/456)
|
||||||
|
|
||||||
|
## [v7.2.1](https://github.com/PhilKes/NotallyX/tree/v7.2.1) (2025-03-18)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.2.0...v7.2.1)
|
||||||
|
|
||||||
|
### Added Features
|
||||||
|
|
||||||
|
- Note not automatically saved when App is killed by system [\#446](https://github.com/PhilKes/NotallyX/issues/446)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Auto Backup failed [\#456](https://github.com/PhilKes/NotallyX/issues/456)
|
||||||
|
|
||||||
## [v7.2.0](https://github.com/PhilKes/NotallyX/tree/v7.2.0) (2025-03-08)
|
## [v7.2.0](https://github.com/PhilKes/NotallyX/tree/v7.2.0) (2025-03-08)
|
||||||
|
|
||||||
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.1.0...v7.2.0)
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.1.0...v7.2.0)
|
||||||
|
|
|
@ -190,7 +190,7 @@ dependencies {
|
||||||
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
||||||
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
||||||
implementation("androidx.work:work-runtime:2.9.1")
|
implementation("androidx.work:work-runtime:2.9.1")
|
||||||
|
implementation("androidx.biometric:biometric:1.1.0")
|
||||||
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
||||||
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
||||||
implementation("com.github.bumptech.glide:glide:4.15.1")
|
implementation("com.github.bumptech.glide:glide:4.15.1")
|
||||||
|
|
271494
app/obfuscation/mapping.txt
Normal file
271494
app/obfuscation/mapping.txt
Normal file
File diff suppressed because it is too large
Load diff
12
app/proguard-rules.pro
vendored
12
app/proguard-rules.pro
vendored
|
@ -11,14 +11,12 @@
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
# public *;
|
# public *;
|
||||||
#}
|
#}
|
||||||
|
-keepattributes LineNumberTable,SourceFile
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
-renamesourcefileattribute SourceFile
|
-renamesourcefileattribute SourceFile
|
||||||
|
-dontobfuscate
|
||||||
|
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable
|
||||||
|
-printmapping obfuscation/mapping.txt
|
||||||
|
|
||||||
-keep class ** extends androidx.navigation.Navigator
|
-keep class ** extends androidx.navigation.Navigator
|
||||||
-keep class ** implements org.ocpsoft.prettytime.TimeUnit
|
-keep class ** implements org.ocpsoft.prettytime.TimeUnit
|
||||||
|
|
||||||
|
|
164
app/schemas/com.philkes.notallyx.data.NotallyDatabase/9.json
Normal file
164
app/schemas/com.philkes.notallyx.data.NotallyDatabase/9.json
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 9,
|
||||||
|
"identityHash": "042b20b5b4cfc8415e6cf6348196e869",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "BaseNote",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` TEXT NOT NULL, `folder` TEXT NOT NULL, `color` TEXT NOT NULL, `title` TEXT NOT NULL, `pinned` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `modifiedTimestamp` INTEGER NOT NULL, `labels` TEXT NOT NULL, `body` TEXT NOT NULL, `spans` TEXT NOT NULL, `items` TEXT NOT NULL, `images` TEXT NOT NULL, `files` TEXT NOT NULL, `audios` TEXT NOT NULL, `reminders` TEXT NOT NULL, `viewMode` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "folder",
|
||||||
|
"columnName": "folder",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "color",
|
||||||
|
"columnName": "color",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pinned",
|
||||||
|
"columnName": "pinned",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "modifiedTimestamp",
|
||||||
|
"columnName": "modifiedTimestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "labels",
|
||||||
|
"columnName": "labels",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "body",
|
||||||
|
"columnName": "body",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "spans",
|
||||||
|
"columnName": "spans",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "items",
|
||||||
|
"columnName": "items",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "images",
|
||||||
|
"columnName": "images",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "files",
|
||||||
|
"columnName": "files",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "audios",
|
||||||
|
"columnName": "audios",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reminders",
|
||||||
|
"columnName": "reminders",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "viewMode",
|
||||||
|
"columnName": "viewMode",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_BaseNote_id_folder_pinned_timestamp_labels",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id",
|
||||||
|
"folder",
|
||||||
|
"pinned",
|
||||||
|
"timestamp",
|
||||||
|
"labels"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_BaseNote_id_folder_pinned_timestamp_labels` ON `${TABLE_NAME}` (`id`, `folder`, `pinned`, `timestamp`, `labels`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "Label",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT NOT NULL, PRIMARY KEY(`value`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"value"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '042b20b5b4cfc8415e6cf6348196e869')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,18 +6,15 @@
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.ACCESS_NETWORK_STATE"
|
android:name="android.permission.ACCESS_NETWORK_STATE"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -71,9 +68,30 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="*/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="text/*" />
|
||||||
|
<data android:scheme="content" android:mimeType="text/*" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="application/json" />
|
||||||
|
<data android:scheme="content" android:mimeType="application/json" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="application/xml" />
|
||||||
|
<data android:scheme="content" android:mimeType="application/xml" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".presentation.activity.note.ViewImageActivity" />
|
<activity android:name=".presentation.activity.note.ViewImageActivity" />
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package com.philkes.notallyx
|
package com.philkes.notallyx
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import com.philkes.notallyx.presentation.setEnabledSecureFlag
|
||||||
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||||
|
@ -32,7 +36,7 @@ import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class NotallyXApplication : Application() {
|
class NotallyXApplication : Application(), Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
private lateinit var biometricLockObserver: Observer<BiometricLock>
|
private lateinit var biometricLockObserver: Observer<BiometricLock>
|
||||||
private lateinit var preferences: NotallyXPreferences
|
private lateinit var preferences: NotallyXPreferences
|
||||||
|
@ -42,10 +46,16 @@ class NotallyXApplication : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
registerActivityLifecycleCallbacks(this)
|
||||||
if (isTestRunner()) return
|
if (isTestRunner()) return
|
||||||
|
|
||||||
preferences = NotallyXPreferences.getInstance(this)
|
preferences = NotallyXPreferences.getInstance(this)
|
||||||
|
if (preferences.useDynamicColors.value) {
|
||||||
|
if (DynamicColors.isDynamicColorAvailable()) {
|
||||||
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTheme(R.style.AppTheme)
|
||||||
|
}
|
||||||
preferences.theme.observeForeverWithPrevious { (oldTheme, theme) ->
|
preferences.theme.observeForeverWithPrevious { (oldTheme, theme) ->
|
||||||
when (theme) {
|
when (theme) {
|
||||||
Theme.DARK ->
|
Theme.DARK ->
|
||||||
|
@ -60,7 +70,7 @@ class NotallyXApplication : Application() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (oldTheme != null) {
|
if (oldTheme != null) {
|
||||||
WidgetProvider.updateWidgets(this)
|
WidgetProvider.updateWidgets(this, locked = locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,4 +172,20 @@ class NotallyXApplication : Application() {
|
||||||
return Build.FINGERPRINT.equals("robolectric", ignoreCase = true)
|
return Build.FINGERPRINT.equals("robolectric", ignoreCase = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
|
activity.setEnabledSecureFlag(preferences.secureFlag.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivityResumed(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivityPaused(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Color
|
import com.philkes.notallyx.data.model.Color
|
||||||
import com.philkes.notallyx.data.model.Converters
|
import com.philkes.notallyx.data.model.Converters
|
||||||
import com.philkes.notallyx.data.model.Label
|
import com.philkes.notallyx.data.model.Label
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.toColorString
|
import com.philkes.notallyx.data.model.toColorString
|
||||||
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
||||||
|
@ -32,7 +33,7 @@ import net.sqlcipher.database.SQLiteDatabase
|
||||||
import net.sqlcipher.database.SupportFactory
|
import net.sqlcipher.database.SupportFactory
|
||||||
|
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@Database(entities = [BaseNote::class, Label::class], version = 8)
|
@Database(entities = [BaseNote::class, Label::class], version = 9)
|
||||||
abstract class NotallyDatabase : RoomDatabase() {
|
abstract class NotallyDatabase : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun getLabelDao(): LabelDao
|
abstract fun getLabelDao(): LabelDao
|
||||||
|
@ -131,6 +132,7 @@ abstract class NotallyDatabase : RoomDatabase() {
|
||||||
Migration6,
|
Migration6,
|
||||||
Migration7,
|
Migration7,
|
||||||
Migration8,
|
Migration8,
|
||||||
|
Migration9,
|
||||||
)
|
)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
SQLiteDatabase.loadLibs(context)
|
SQLiteDatabase.loadLibs(context)
|
||||||
|
@ -254,17 +256,21 @@ abstract class NotallyDatabase : RoomDatabase() {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(cursor.getColumnIndexOrThrow("id"))
|
val id = cursor.getLong(cursor.getColumnIndexOrThrow("id"))
|
||||||
val colorString = cursor.getString(cursor.getColumnIndexOrThrow("color"))
|
val colorString = cursor.getString(cursor.getColumnIndexOrThrow("color"))
|
||||||
val color =
|
val color = Color.valueOfOrDefault(colorString)
|
||||||
try {
|
|
||||||
Color.valueOf(colorString)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Color.DEFAULT
|
|
||||||
}
|
|
||||||
val hexColor = color.toColorString()
|
val hexColor = color.toColorString()
|
||||||
db.execSQL("UPDATE BaseNote SET color = ? WHERE id = ?", arrayOf(hexColor, id))
|
db.execSQL("UPDATE BaseNote SET color = ? WHERE id = ?", arrayOf(hexColor, id))
|
||||||
}
|
}
|
||||||
cursor.close()
|
cursor.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Migration9 : Migration(8, 9) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"ALTER TABLE `BaseNote` ADD COLUMN `viewMode` TEXT NOT NULL DEFAULT '${NoteViewMode.EDIT.name}'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ interface BaseNoteDao {
|
||||||
@Query("SELECT id, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'")
|
@Query("SELECT id, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'")
|
||||||
suspend fun getAllReminders(): List<NoteIdReminder>
|
suspend fun getAllReminders(): List<NoteIdReminder>
|
||||||
|
|
||||||
@Query("SELECT color FROM BaseNote WHERE id = :id ") fun getColorOfNote(id: Long): String
|
@Query("SELECT color FROM BaseNote WHERE id = :id ") fun getColorOfNote(id: Long): String?
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT id, title, type, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'"
|
"SELECT id, title, type, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'"
|
||||||
|
@ -163,14 +163,14 @@ interface BaseNoteDao {
|
||||||
* directly on the LiveData to filter the results accordingly.
|
* directly on the LiveData to filter the results accordingly.
|
||||||
*/
|
*/
|
||||||
fun getBaseNotesByLabel(label: String): LiveData<List<BaseNote>> {
|
fun getBaseNotesByLabel(label: String): LiveData<List<BaseNote>> {
|
||||||
val result = getBaseNotesByLabel(label, Folder.NOTES)
|
val result = getBaseNotesByLabel(label, setOf(Folder.NOTES, Folder.ARCHIVED))
|
||||||
return result.map { list -> list.filter { baseNote -> baseNote.labels.contains(label) } }
|
return result.map { list -> list.filter { baseNote -> baseNote.labels.contains(label) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT * FROM BaseNote WHERE folder = :folder AND labels LIKE '%' || :label || '%' ORDER BY pinned DESC, timestamp DESC"
|
"SELECT * FROM BaseNote WHERE folder IN (:folders) AND labels LIKE '%' || :label || '%' ORDER BY folder DESC, pinned DESC, timestamp DESC"
|
||||||
)
|
)
|
||||||
fun getBaseNotesByLabel(label: String, folder: Folder): LiveData<List<BaseNote>>
|
fun getBaseNotesByLabel(label: String, folders: Collection<Folder>): LiveData<List<BaseNote>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT * FROM BaseNote WHERE folder = :folder AND labels == '[]' ORDER BY pinned DESC, timestamp DESC"
|
"SELECT * FROM BaseNote WHERE folder = :folder AND labels == '[]' ORDER BY pinned DESC, timestamp DESC"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.NotallyDatabase
|
import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.imports.evernote.EvernoteImporter
|
import com.philkes.notallyx.data.imports.evernote.EvernoteImporter
|
||||||
import com.philkes.notallyx.data.imports.google.GoogleKeepImporter
|
import com.philkes.notallyx.data.imports.google.GoogleKeepImporter
|
||||||
|
import com.philkes.notallyx.data.imports.txt.JsonImporter
|
||||||
import com.philkes.notallyx.data.imports.txt.PlainTextImporter
|
import com.philkes.notallyx.data.imports.txt.PlainTextImporter
|
||||||
import com.philkes.notallyx.data.model.Audio
|
import com.philkes.notallyx.data.model.Audio
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
|
@ -39,6 +40,7 @@ class NotesImporter(private val app: Application, private val database: NotallyD
|
||||||
ImportSource.GOOGLE_KEEP -> GoogleKeepImporter()
|
ImportSource.GOOGLE_KEEP -> GoogleKeepImporter()
|
||||||
ImportSource.EVERNOTE -> EvernoteImporter()
|
ImportSource.EVERNOTE -> EvernoteImporter()
|
||||||
ImportSource.PLAIN_TEXT -> PlainTextImporter()
|
ImportSource.PLAIN_TEXT -> PlainTextImporter()
|
||||||
|
ImportSource.JSON -> JsonImporter()
|
||||||
}.import(app, uri, tempDir, progress)
|
}.import(app, uri, tempDir, progress)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "import: failed", e)
|
Log.e(TAG, "import: failed", e)
|
||||||
|
@ -153,6 +155,13 @@ enum class ImportSource(
|
||||||
null,
|
null,
|
||||||
R.drawable.text_file,
|
R.drawable.text_file,
|
||||||
),
|
),
|
||||||
|
JSON(
|
||||||
|
R.string.json_files,
|
||||||
|
FOLDER_OR_FILE_MIMETYPE,
|
||||||
|
R.string.json_files_help,
|
||||||
|
null,
|
||||||
|
R.drawable.file_json,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
const val FOLDER_OR_FILE_MIMETYPE = "FOLDER_OR_FILE"
|
const val FOLDER_OR_FILE_MIMETYPE = "FOLDER_OR_FILE"
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.utils.log
|
import com.philkes.notallyx.utils.log
|
||||||
import com.philkes.notallyx.utils.startsWithAnyOf
|
import com.philkes.notallyx.utils.startsWithAnyOf
|
||||||
|
@ -155,6 +156,7 @@ fun EvernoteNote.mapToBaseNote(): BaseNote {
|
||||||
files = files,
|
files = files,
|
||||||
audios = audios,
|
audios = audios,
|
||||||
reminders = mutableListOf(),
|
reminders = mutableListOf(),
|
||||||
|
NoteViewMode.EDIT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.utils.listFilesRecursive
|
import com.philkes.notallyx.utils.listFilesRecursive
|
||||||
import com.philkes.notallyx.utils.log
|
import com.philkes.notallyx.utils.log
|
||||||
|
@ -162,6 +163,7 @@ class GoogleKeepImporter : ExternalImporter {
|
||||||
files = files,
|
files = files,
|
||||||
audios = audios,
|
audios = audios,
|
||||||
reminders = mutableListOf(),
|
reminders = mutableListOf(),
|
||||||
|
NoteViewMode.EDIT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.philkes.notallyx.data.imports.txt
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.philkes.notallyx.data.imports.ExternalImporter
|
||||||
|
import com.philkes.notallyx.data.imports.ImportProgress
|
||||||
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
|
import com.philkes.notallyx.data.model.toBaseNote
|
||||||
|
import com.philkes.notallyx.utils.MIME_TYPE_JSON
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
|
class JsonImporter : ExternalImporter {
|
||||||
|
|
||||||
|
override fun import(
|
||||||
|
app: Application,
|
||||||
|
source: Uri,
|
||||||
|
destination: File,
|
||||||
|
progress: MutableLiveData<ImportProgress>?,
|
||||||
|
): Pair<List<BaseNote>, File?> {
|
||||||
|
val notes = mutableListOf<BaseNote>()
|
||||||
|
fun readJsonFiles(file: DocumentFile) {
|
||||||
|
when {
|
||||||
|
file.isDirectory -> {
|
||||||
|
file.listFiles().forEach { readJsonFiles(it) }
|
||||||
|
}
|
||||||
|
file.isFile -> {
|
||||||
|
if (file.type != MIME_TYPE_JSON) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val fileNameWithoutExtension = file.name?.substringBeforeLast(".") ?: ""
|
||||||
|
val content =
|
||||||
|
app.contentResolver.openInputStream(file.uri)?.use { inputStream ->
|
||||||
|
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||||
|
reader.readText()
|
||||||
|
}
|
||||||
|
} ?: ""
|
||||||
|
notes.add(content.toBaseNote().copy(id = 0L, title = fileNameWithoutExtension))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val file =
|
||||||
|
if (source.pathSegments.firstOrNull() == "tree") {
|
||||||
|
DocumentFile.fromTreeUri(app, source)
|
||||||
|
} else DocumentFile.fromSingleUri(app, source)
|
||||||
|
file?.let { readJsonFiles(it) }
|
||||||
|
return Pair(notes, null)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,11 +9,11 @@ import com.philkes.notallyx.data.imports.ImportProgress
|
||||||
import com.philkes.notallyx.data.model.BaseNote
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.utils.MIME_TYPE_JSON
|
import com.philkes.notallyx.utils.MIME_TYPE_JSON
|
||||||
import java.io.BufferedReader
|
import com.philkes.notallyx.utils.readFileContents
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
|
||||||
|
|
||||||
class PlainTextImporter : ExternalImporter {
|
class PlainTextImporter : ExternalImporter {
|
||||||
|
|
||||||
|
@ -35,12 +35,7 @@ class PlainTextImporter : ExternalImporter {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val fileNameWithoutExtension = file.name?.substringBeforeLast(".") ?: ""
|
val fileNameWithoutExtension = file.name?.substringBeforeLast(".") ?: ""
|
||||||
var content =
|
var content = app.contentResolver.readFileContents(file.uri)
|
||||||
app.contentResolver.openInputStream(file.uri)?.use { inputStream ->
|
|
||||||
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
|
||||||
reader.readText()
|
|
||||||
}
|
|
||||||
} ?: ""
|
|
||||||
val listItems = mutableListOf<ListItem>()
|
val listItems = mutableListOf<ListItem>()
|
||||||
content.findListSyntaxRegex()?.let { listSyntaxRegex ->
|
content.findListSyntaxRegex()?.let { listSyntaxRegex ->
|
||||||
listItems.addAll(content.extractListItems(listSyntaxRegex))
|
listItems.addAll(content.extractListItems(listSyntaxRegex))
|
||||||
|
@ -65,6 +60,7 @@ class PlainTextImporter : ExternalImporter {
|
||||||
files = listOf(),
|
files = listOf(),
|
||||||
audios = listOf(),
|
audios = listOf(),
|
||||||
reminders = listOf(),
|
reminders = listOf(),
|
||||||
|
NoteViewMode.EDIT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ data class BaseNote(
|
||||||
val files: List<FileAttachment>,
|
val files: List<FileAttachment>,
|
||||||
val audios: List<Audio>,
|
val audios: List<Audio>,
|
||||||
val reminders: List<Reminder>,
|
val reminders: List<Reminder>,
|
||||||
|
val viewMode: NoteViewMode,
|
||||||
) : Item {
|
) : Item {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -53,6 +54,7 @@ data class BaseNote(
|
||||||
if (files != other.files) return false
|
if (files != other.files) return false
|
||||||
if (audios != other.audios) return false
|
if (audios != other.audios) return false
|
||||||
if (reminders != other.reminders) return false
|
if (reminders != other.reminders) return false
|
||||||
|
if (viewMode != other.viewMode) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -73,6 +75,7 @@ data class BaseNote(
|
||||||
result = 31 * result + files.hashCode()
|
result = 31 * result + files.hashCode()
|
||||||
result = 31 * result + audios.hashCode()
|
result = 31 * result + audios.hashCode()
|
||||||
result = 31 * result + reminders.hashCode()
|
result = 31 * result + reminders.hashCode()
|
||||||
|
result = 31 * result + viewMode.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,13 @@ enum class Color {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun allColorStrings() = entries.map { it.toColorString() }.toList()
|
fun allColorStrings() = entries.map { it.toColorString() }.toList()
|
||||||
|
|
||||||
|
fun valueOfOrDefault(value: String) =
|
||||||
|
try {
|
||||||
|
Color.valueOf(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
DEFAULT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ object Converters {
|
||||||
|
|
||||||
@TypeConverter fun labelsToJson(labels: List<String>) = JSONArray(labels).toString()
|
@TypeConverter fun labelsToJson(labels: List<String>) = JSONArray(labels).toString()
|
||||||
|
|
||||||
@TypeConverter fun jsonToLabels(json: String) = JSONArray(json).iterable<String>().toList()
|
@TypeConverter fun jsonToLabels(json: String) = jsonToLabels(JSONArray(json))
|
||||||
|
|
||||||
|
fun jsonToLabels(jsonArray: JSONArray) = jsonArray.iterable<String>().toList()
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun filesToJson(files: List<FileAttachment>): String {
|
fun filesToJson(files: List<FileAttachment>): String {
|
||||||
|
@ -24,10 +26,10 @@ object Converters {
|
||||||
return JSONArray(objects).toString()
|
return JSONArray(objects).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter fun jsonToFiles(json: String) = jsonToFiles(JSONArray(json))
|
||||||
fun jsonToFiles(json: String): List<FileAttachment> {
|
|
||||||
val iterable = JSONArray(json).iterable<JSONObject>()
|
fun jsonToFiles(jsonArray: JSONArray): List<FileAttachment> {
|
||||||
return iterable.map { jsonObject ->
|
return jsonArray.iterable<JSONObject>().map { jsonObject ->
|
||||||
val localName = getSafeLocalName(jsonObject)
|
val localName = getSafeLocalName(jsonObject)
|
||||||
val originalName = getSafeOriginalName(jsonObject)
|
val originalName = getSafeOriginalName(jsonObject)
|
||||||
val mimeType = jsonObject.getString("mimeType")
|
val mimeType = jsonObject.getString("mimeType")
|
||||||
|
@ -47,10 +49,10 @@ object Converters {
|
||||||
return JSONArray(objects).toString()
|
return JSONArray(objects).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter fun jsonToAudios(json: String) = jsonToAudios(JSONArray(json))
|
||||||
fun jsonToAudios(json: String): List<Audio> {
|
|
||||||
val iterable = JSONArray(json).iterable<JSONObject>()
|
fun jsonToAudios(json: JSONArray): List<Audio> {
|
||||||
return iterable.map { jsonObject ->
|
return json.iterable<JSONObject>().map { jsonObject ->
|
||||||
val name = jsonObject.getString("name")
|
val name = jsonObject.getString("name")
|
||||||
val duration = jsonObject.getSafeLong("duration")
|
val duration = jsonObject.getSafeLong("duration")
|
||||||
val timestamp = jsonObject.getLong("timestamp")
|
val timestamp = jsonObject.getLong("timestamp")
|
||||||
|
@ -58,31 +60,63 @@ object Converters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter fun jsonToSpans(json: String) = jsonToSpans(JSONArray(json))
|
||||||
fun jsonToSpans(json: String): List<SpanRepresentation> {
|
|
||||||
val iterable = JSONArray(json).iterable<JSONObject>()
|
fun jsonToSpans(jsonArray: JSONArray): List<SpanRepresentation> {
|
||||||
return iterable.map { jsonObject ->
|
return jsonArray
|
||||||
val bold = jsonObject.getSafeBoolean("bold")
|
.iterable<JSONObject>()
|
||||||
val link = jsonObject.getSafeBoolean("link")
|
.map { jsonObject ->
|
||||||
val linkData = jsonObject.getSafeString("linkData")
|
val bold = jsonObject.getSafeBoolean("bold")
|
||||||
val italic = jsonObject.getSafeBoolean("italic")
|
val link = jsonObject.getSafeBoolean("link")
|
||||||
val monospace = jsonObject.getSafeBoolean("monospace")
|
val linkData = jsonObject.getSafeString("linkData")
|
||||||
val strikethrough = jsonObject.getSafeBoolean("strikethrough")
|
val italic = jsonObject.getSafeBoolean("italic")
|
||||||
val start = jsonObject.getInt("start")
|
val monospace = jsonObject.getSafeBoolean("monospace")
|
||||||
val end = jsonObject.getInt("end")
|
val strikethrough = jsonObject.getSafeBoolean("strikethrough")
|
||||||
SpanRepresentation(start, end, bold, link, linkData, italic, monospace, strikethrough)
|
try {
|
||||||
}
|
val start = jsonObject.getInt("start")
|
||||||
|
val end = jsonObject.getInt("end")
|
||||||
|
SpanRepresentation(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
bold,
|
||||||
|
link,
|
||||||
|
linkData,
|
||||||
|
italic,
|
||||||
|
monospace,
|
||||||
|
strikethrough,
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun spansToJson(list: List<SpanRepresentation>) = spansToJSONArray(list).toString()
|
fun spansToJson(list: List<SpanRepresentation>) = spansToJSONArray(list).toString()
|
||||||
|
|
||||||
@TypeConverter
|
fun spansToJSONArray(list: List<SpanRepresentation>): JSONArray {
|
||||||
fun jsonToItems(json: String): List<ListItem> {
|
val objects =
|
||||||
val iterable = JSONArray(json).iterable<JSONObject>()
|
list.map { representation ->
|
||||||
return iterable.map { jsonObject ->
|
val jsonObject = JSONObject()
|
||||||
val body = jsonObject.getString("body")
|
jsonObject.put("bold", representation.bold)
|
||||||
val checked = jsonObject.getBoolean("checked")
|
jsonObject.put("link", representation.link)
|
||||||
|
jsonObject.put("linkData", representation.linkData)
|
||||||
|
jsonObject.put("italic", representation.italic)
|
||||||
|
jsonObject.put("monospace", representation.monospace)
|
||||||
|
jsonObject.put("strikethrough", representation.strikethrough)
|
||||||
|
jsonObject.put("start", representation.start)
|
||||||
|
jsonObject.put("end", representation.end)
|
||||||
|
}
|
||||||
|
return JSONArray(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter fun jsonToItems(json: String) = jsonToItems(JSONArray(json))
|
||||||
|
|
||||||
|
fun jsonToItems(json: JSONArray): List<ListItem> {
|
||||||
|
return json.iterable<JSONObject>().map { jsonObject ->
|
||||||
|
val body = jsonObject.getSafeString("body") ?: ""
|
||||||
|
val checked = jsonObject.getSafeBoolean("checked")
|
||||||
val isChild = jsonObject.getSafeBoolean("isChild")
|
val isChild = jsonObject.getSafeBoolean("isChild")
|
||||||
val order = jsonObject.getSafeInt("order")
|
val order = jsonObject.getSafeInt("order")
|
||||||
ListItem(body, checked, isChild, order, mutableListOf())
|
ListItem(body, checked, isChild, order, mutableListOf())
|
||||||
|
@ -103,39 +137,25 @@ object Converters {
|
||||||
return JSONArray(objects)
|
return JSONArray(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun spansToJSONArray(list: List<SpanRepresentation>): JSONArray {
|
|
||||||
val objects =
|
|
||||||
list.map { representation ->
|
|
||||||
val jsonObject = JSONObject()
|
|
||||||
jsonObject.put("bold", representation.bold)
|
|
||||||
jsonObject.put("link", representation.link)
|
|
||||||
jsonObject.put("linkData", representation.linkData)
|
|
||||||
jsonObject.put("italic", representation.italic)
|
|
||||||
jsonObject.put("monospace", representation.monospace)
|
|
||||||
jsonObject.put("strikethrough", representation.strikethrough)
|
|
||||||
jsonObject.put("start", representation.start)
|
|
||||||
jsonObject.put("end", representation.end)
|
|
||||||
}
|
|
||||||
return JSONArray(objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun remindersToJson(reminders: List<Reminder>): String {
|
fun remindersToJson(reminders: List<Reminder>) = remindersToJSONArray(reminders).toString()
|
||||||
|
|
||||||
|
fun remindersToJSONArray(reminders: List<Reminder>): JSONArray {
|
||||||
val objects =
|
val objects =
|
||||||
reminders.map { reminder ->
|
reminders.map { reminder ->
|
||||||
JSONObject().apply {
|
JSONObject().apply {
|
||||||
put("id", reminder.id) // Store date as long timestamp
|
put("id", reminder.id) // Store date as long timestamp
|
||||||
put("dateTime", reminder.dateTime.time) // Store date as long timestamp
|
put("dateTime", reminder.dateTime.time) // Store date as long timestamp
|
||||||
put("repetition", reminder.repetition?.let { repetitionToJson(it) })
|
put("repetition", reminder.repetition?.let { repetitionToJsonObject(it) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JSONArray(objects).toString()
|
return JSONArray(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter fun jsonToReminders(json: String) = jsonToReminders(JSONArray(json))
|
||||||
fun jsonToReminders(json: String): List<Reminder> {
|
|
||||||
val iterable = JSONArray(json).iterable<JSONObject>()
|
fun jsonToReminders(jsonArray: JSONArray): List<Reminder> {
|
||||||
return iterable.map { jsonObject ->
|
return jsonArray.iterable<JSONObject>().map { jsonObject ->
|
||||||
val id = jsonObject.getLong("id")
|
val id = jsonObject.getLong("id")
|
||||||
val dateTime = Date(jsonObject.getLong("dateTime"))
|
val dateTime = Date(jsonObject.getLong("dateTime"))
|
||||||
val repetition = jsonObject.getSafeString("repetition")?.let { jsonToRepetition(it) }
|
val repetition = jsonObject.getSafeString("repetition")?.let { jsonToRepetition(it) }
|
||||||
|
@ -145,10 +165,14 @@ object Converters {
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun repetitionToJson(repetition: Repetition): String {
|
fun repetitionToJson(repetition: Repetition): String {
|
||||||
|
return repetitionToJsonObject(repetition).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun repetitionToJsonObject(repetition: Repetition): JSONObject {
|
||||||
val jsonObject = JSONObject()
|
val jsonObject = JSONObject()
|
||||||
jsonObject.put("value", repetition.value)
|
jsonObject.put("value", repetition.value)
|
||||||
jsonObject.put("unit", repetition.unit.name) // Store the TimeUnit as a string
|
jsonObject.put("unit", repetition.unit.name) // Store the TimeUnit as a string
|
||||||
return jsonObject.toString()
|
return jsonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
|
|
@ -5,5 +5,14 @@ import java.io.Serializable
|
||||||
enum class Folder : Serializable {
|
enum class Folder : Serializable {
|
||||||
NOTES,
|
NOTES,
|
||||||
DELETED,
|
DELETED,
|
||||||
ARCHIVED,
|
ARCHIVED;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOfOrDefault(value: String) =
|
||||||
|
try {
|
||||||
|
valueOf(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
NOTES
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,7 @@ data class ListItem(
|
||||||
if (other !is ListItem) {
|
if (other !is ListItem) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return (this.id == other.id &&
|
return (this.body == other.body &&
|
||||||
this.body == other.body &&
|
|
||||||
this.order == other.order &&
|
this.order == other.order &&
|
||||||
this.checked == other.checked &&
|
this.checked == other.checked &&
|
||||||
this.isChild == other.isChild)
|
this.isChild == other.isChild)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.text.Html
|
||||||
import androidx.core.text.toHtml
|
import androidx.core.text.toHtml
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.dao.NoteIdReminder
|
import com.philkes.notallyx.data.dao.NoteIdReminder
|
||||||
|
import com.philkes.notallyx.data.model.BaseNote.Companion.COLOR_DEFAULT
|
||||||
import com.philkes.notallyx.presentation.applySpans
|
import com.philkes.notallyx.presentation.applySpans
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -12,6 +13,7 @@ import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
private const val NOTE_URL_PREFIX = "note://"
|
private const val NOTE_URL_PREFIX = "note://"
|
||||||
|
@ -41,7 +43,15 @@ fun String.getNoteTypeFromUrl(): Type {
|
||||||
|
|
||||||
val FileAttachment.isImage: Boolean
|
val FileAttachment.isImage: Boolean
|
||||||
get() {
|
get() {
|
||||||
return mimeType.startsWith("image/")
|
return mimeType.isImageMimeType
|
||||||
|
}
|
||||||
|
val String.isImageMimeType: Boolean
|
||||||
|
get() {
|
||||||
|
return startsWith("image/")
|
||||||
|
}
|
||||||
|
val String.isAudioMimeType: Boolean
|
||||||
|
get() {
|
||||||
|
return startsWith("audio/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BaseNote.toTxt(includeTitle: Boolean = true, includeCreationDate: Boolean = true) =
|
fun BaseNote.toTxt(includeTitle: Boolean = true, includeCreationDate: Boolean = true) =
|
||||||
|
@ -70,7 +80,8 @@ fun BaseNote.toJson(): String {
|
||||||
.put("color", color)
|
.put("color", color)
|
||||||
.put("title", title)
|
.put("title", title)
|
||||||
.put("pinned", pinned)
|
.put("pinned", pinned)
|
||||||
.put("date-created", timestamp)
|
.put("timestamp", timestamp)
|
||||||
|
.put("modifiedTimestamp", modifiedTimestamp)
|
||||||
.put("labels", JSONArray(labels))
|
.put("labels", JSONArray(labels))
|
||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
|
@ -83,10 +94,85 @@ fun BaseNote.toJson(): String {
|
||||||
jsonObject.put("items", Converters.itemsToJSONArray(items))
|
jsonObject.put("items", Converters.itemsToJSONArray(items))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
jsonObject.put("reminders", Converters.remindersToJSONArray(reminders))
|
||||||
|
jsonObject.put("viewMode", viewMode.name)
|
||||||
return jsonObject.toString(2)
|
return jsonObject.toString(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.toBaseNote(): BaseNote {
|
||||||
|
val jsonObject = JSONObject(this)
|
||||||
|
val id = jsonObject.getLongOrDefault("id", -1L)
|
||||||
|
val type = Type.valueOfOrDefault(jsonObject.getStringOrDefault("type", Type.NOTE.name))
|
||||||
|
val folder = Folder.valueOfOrDefault(jsonObject.getStringOrDefault("folder", Folder.NOTES.name))
|
||||||
|
val color =
|
||||||
|
jsonObject.getStringOrDefault("color", COLOR_DEFAULT).takeIf { it.isValid() }
|
||||||
|
?: COLOR_DEFAULT
|
||||||
|
val title = jsonObject.getStringOrDefault("title", "")
|
||||||
|
val pinned = jsonObject.getBooleanOrDefault("pinned", false)
|
||||||
|
val timestamp = jsonObject.getLongOrDefault("timestamp", System.currentTimeMillis())
|
||||||
|
val modifiedTimestamp = jsonObject.getLongOrDefault("modifiedTimestamp", timestamp)
|
||||||
|
val labels = Converters.jsonToLabels(jsonObject.getArrayOrEmpty("labels"))
|
||||||
|
val body = jsonObject.getStringOrDefault("body", "")
|
||||||
|
val spans = Converters.jsonToSpans(jsonObject.getArrayOrEmpty("spans"))
|
||||||
|
val items = Converters.jsonToItems(jsonObject.getArrayOrEmpty("items"))
|
||||||
|
val images = Converters.jsonToFiles(jsonObject.getArrayOrEmpty("images"))
|
||||||
|
val files = Converters.jsonToFiles(jsonObject.getArrayOrEmpty("files"))
|
||||||
|
val audios = Converters.jsonToAudios(jsonObject.getArrayOrEmpty("audios"))
|
||||||
|
val reminders = Converters.jsonToReminders(jsonObject.getArrayOrEmpty("reminders"))
|
||||||
|
val viewMode = NoteViewMode.valueOfOrDefault(jsonObject.getStringOrDefault("viewMode", ""))
|
||||||
|
return BaseNote(
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
folder,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
pinned,
|
||||||
|
timestamp,
|
||||||
|
modifiedTimestamp,
|
||||||
|
labels,
|
||||||
|
body,
|
||||||
|
spans,
|
||||||
|
items,
|
||||||
|
images,
|
||||||
|
files,
|
||||||
|
audios,
|
||||||
|
reminders,
|
||||||
|
viewMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.getStringOrDefault(key: String, defaultValue: String): String {
|
||||||
|
return try {
|
||||||
|
getString(key)
|
||||||
|
} catch (exception: JSONException) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.getArrayOrEmpty(key: String): JSONArray {
|
||||||
|
return try {
|
||||||
|
getJSONArray(key)
|
||||||
|
} catch (exception: JSONException) {
|
||||||
|
JSONArray("[]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.getBooleanOrDefault(key: String, defaultValue: Boolean): Boolean {
|
||||||
|
return try {
|
||||||
|
getBoolean(key)
|
||||||
|
} catch (exception: JSONException) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.getLongOrDefault(key: String, defaultValue: Long): Long {
|
||||||
|
return try {
|
||||||
|
getLong(key)
|
||||||
|
} catch (exception: JSONException) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun BaseNote.toHtml(showDateCreated: Boolean) = buildString {
|
fun BaseNote.toHtml(showDateCreated: Boolean) = buildString {
|
||||||
val date = DateFormat.getDateInstance(DateFormat.FULL).format(timestamp)
|
val date = DateFormat.getDateInstance(DateFormat.FULL).format(timestamp)
|
||||||
val title = Html.escapeHtml(title)
|
val title = Html.escapeHtml(title)
|
||||||
|
@ -222,3 +308,15 @@ fun List<ListItem>.toText() = buildString {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Collection<ListItem>.deepCopy() = map { it.copy(children = mutableListOf()) }
|
fun Collection<ListItem>.deepCopy() = map { it.copy(children = mutableListOf()) }
|
||||||
|
|
||||||
|
fun ColorString.isValid() =
|
||||||
|
when (this) {
|
||||||
|
COLOR_DEFAULT -> true
|
||||||
|
else ->
|
||||||
|
try {
|
||||||
|
android.graphics.Color.parseColor(this)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.philkes.notallyx.data.model
|
||||||
|
|
||||||
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.presentation.viewmodel.preference.StaticTextProvider
|
||||||
|
|
||||||
|
enum class NoteViewMode(override val textResId: Int) : StaticTextProvider {
|
||||||
|
READ_ONLY(R.string.read_only),
|
||||||
|
EDIT(R.string.edit);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOfOrDefault(value: String) =
|
||||||
|
try {
|
||||||
|
NoteViewMode.valueOf(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
EDIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,5 +2,14 @@ package com.philkes.notallyx.data.model
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
NOTE,
|
NOTE,
|
||||||
LIST,
|
LIST;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun valueOfOrDefault(value: String) =
|
||||||
|
try {
|
||||||
|
Type.valueOf(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
NOTE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.text.TextWatcher
|
||||||
import android.text.style.CharacterStyle
|
import android.text.style.CharacterStyle
|
||||||
import android.text.style.StrikethroughSpan
|
import android.text.style.StrikethroughSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
|
import android.text.style.SuggestionSpan
|
||||||
import android.text.style.TypefaceSpan
|
import android.text.style.TypefaceSpan
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
@ -55,13 +56,16 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
import androidx.core.text.getSpans
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import androidx.core.view.marginTop
|
import androidx.core.view.marginTop
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
@ -87,12 +91,16 @@ import com.philkes.notallyx.data.imports.ImportStage
|
||||||
import com.philkes.notallyx.data.model.BaseNote
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.SpanRepresentation
|
import com.philkes.notallyx.data.model.SpanRepresentation
|
||||||
|
import com.philkes.notallyx.databinding.DialogInputBinding
|
||||||
import com.philkes.notallyx.databinding.DialogProgressBinding
|
import com.philkes.notallyx.databinding.DialogProgressBinding
|
||||||
import com.philkes.notallyx.databinding.LabelBinding
|
import com.philkes.notallyx.databinding.LabelBinding
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.MainActivity
|
||||||
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
||||||
import com.philkes.notallyx.presentation.view.misc.Progress
|
import com.philkes.notallyx.presentation.view.misc.Progress
|
||||||
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
||||||
|
import com.philkes.notallyx.presentation.view.note.listitem.adapter.ListItemVH
|
||||||
|
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
|
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
|
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
|
||||||
import com.philkes.notallyx.utils.changehistory.ChangeHistory
|
import com.philkes.notallyx.utils.changehistory.ChangeHistory
|
||||||
|
@ -114,7 +122,7 @@ fun String.applySpans(representations: List<SpanRepresentation>): Editable {
|
||||||
->
|
->
|
||||||
try {
|
try {
|
||||||
if (bold) {
|
if (bold) {
|
||||||
editable.setSpan(StyleSpan(Typeface.BOLD), start, end)
|
editable.setSpan(createBoldSpan(), start, end)
|
||||||
}
|
}
|
||||||
if (italic) {
|
if (italic) {
|
||||||
editable.setSpan(StyleSpan(Typeface.ITALIC), start, end)
|
editable.setSpan(StyleSpan(Typeface.ITALIC), start, end)
|
||||||
|
@ -136,6 +144,13 @@ fun String.applySpans(representations: List<SpanRepresentation>): Editable {
|
||||||
return editable
|
return editable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createBoldSpan() =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
StyleSpan(Typeface.BOLD, 700)
|
||||||
|
} else {
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts or removes spans based on the selection range.
|
* Adjusts or removes spans based on the selection range.
|
||||||
*
|
*
|
||||||
|
@ -226,14 +241,23 @@ fun ViewGroup.addIconButton(
|
||||||
title: Int,
|
title: Int,
|
||||||
drawable: Int,
|
drawable: Int,
|
||||||
marginStart: Int = 10,
|
marginStart: Int = 10,
|
||||||
onClick: ((item: View) -> Unit)? = null,
|
onLongClick: View.OnLongClickListener? = null,
|
||||||
): View {
|
onClick: View.OnClickListener? = null,
|
||||||
|
): ImageButton {
|
||||||
val view =
|
val view =
|
||||||
ImageButton(ContextThemeWrapper(context, R.style.AppTheme)).apply {
|
ImageButton(ContextThemeWrapper(context, R.style.AppTheme)).apply {
|
||||||
setImageResource(drawable)
|
setImageResource(drawable)
|
||||||
contentDescription = context.getString(title)
|
val titleText = context.getString(title)
|
||||||
setBackgroundResource(R.color.Transparent)
|
contentDescription = titleText
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
tooltipText = titleText
|
||||||
|
}
|
||||||
|
val outValue = TypedValue()
|
||||||
|
context.theme.resolveAttribute(android.R.attr.actionBarItemBackground, outValue, true)
|
||||||
|
setBackgroundResource(outValue.resourceId)
|
||||||
|
setOnLongClickListener(onLongClick)
|
||||||
setOnClickListener(onClick)
|
setOnClickListener(onClick)
|
||||||
|
|
||||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
adjustViewBounds = true
|
adjustViewBounds = true
|
||||||
layoutParams =
|
layoutParams =
|
||||||
|
@ -281,11 +305,11 @@ fun EditText.createListTextWatcherWithHistory(
|
||||||
onTextChanged: ((text: CharSequence, start: Int, count: Int) -> Boolean)? = null,
|
onTextChanged: ((text: CharSequence, start: Int, count: Int) -> Boolean)? = null,
|
||||||
) =
|
) =
|
||||||
object : TextWatcher {
|
object : TextWatcher {
|
||||||
private lateinit var stateBefore: EditTextState
|
|
||||||
private var ignoreOriginalChange: Boolean = false
|
private var ignoreOriginalChange: Boolean = false
|
||||||
|
private lateinit var textBefore: Editable
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
stateBefore = EditTextState(getText()!!.clone(), selectionStart)
|
textBefore = text.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
@ -293,13 +317,14 @@ fun EditText.createListTextWatcherWithHistory(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
val textAfter = s!!.clone()
|
||||||
|
if (textAfter.hasNotChanged(textBefore)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!ignoreOriginalChange) {
|
if (!ignoreOriginalChange) {
|
||||||
listManager.changeText(
|
listManager.changeText(
|
||||||
positionGetter.invoke(),
|
positionGetter.invoke(),
|
||||||
EditTextState(getText()!!.clone(), selectionStart),
|
EditTextState(textAfter, selectionStart),
|
||||||
before = stateBefore,
|
|
||||||
editText = this@createListTextWatcherWithHistory,
|
|
||||||
listener = this,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,6 +348,9 @@ fun StylableEditTextWithHistory.createTextWatcherWithHistory(
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
val textAfter = requireNotNull(s).clone()
|
val textAfter = requireNotNull(s).clone()
|
||||||
|
if (textAfter.hasNotChanged(stateBefore.text)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
updateModel.invoke(textAfter)
|
updateModel.invoke(textAfter)
|
||||||
changeHistory.push(
|
changeHistory.push(
|
||||||
EditTextWithHistoryChange(
|
EditTextWithHistoryChange(
|
||||||
|
@ -335,6 +363,10 @@ fun StylableEditTextWithHistory.createTextWatcherWithHistory(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Editable.hasNotChanged(before: Editable): Boolean {
|
||||||
|
return toString() == before.toString() && getSpans<SuggestionSpan>().isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
fun Editable.clone(): Editable = Editable.Factory.getInstance().newEditable(this)
|
fun Editable.clone(): Editable = Editable.Factory.getInstance().newEditable(this)
|
||||||
|
|
||||||
fun View.getString(id: Int, vararg formatArgs: String): String {
|
fun View.getString(id: Int, vararg formatArgs: String): String {
|
||||||
|
@ -357,12 +389,12 @@ fun RadioGroup.checkedTag(): Any {
|
||||||
return this.findViewById<RadioButton?>(this.checkedRadioButtonId).tag
|
return this.findViewById<RadioButton?>(this.checkedRadioButtonId).tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.showKeyboard(view: View) {
|
fun Context.showKeyboard(view: View) {
|
||||||
ContextCompat.getSystemService(this, InputMethodManager::class.java)
|
ContextCompat.getSystemService(this, InputMethodManager::class.java)
|
||||||
?.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
?.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.hideKeyboard(view: View) {
|
fun Context.hideKeyboard(view: View) {
|
||||||
ContextCompat.getSystemService(this, InputMethodManager::class.java)
|
ContextCompat.getSystemService(this, InputMethodManager::class.java)
|
||||||
?.hideSoftInputFromWindow(view.windowToken, 0)
|
?.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
}
|
}
|
||||||
|
@ -506,6 +538,64 @@ fun Activity.checkAlarmPermission(
|
||||||
} else onSuccess()
|
} else onSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Activity.setEnabledSecureFlag(enabled: Boolean) {
|
||||||
|
if (enabled) {
|
||||||
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.displayEditLabelDialog(
|
||||||
|
oldValue: String,
|
||||||
|
model: BaseNoteModel,
|
||||||
|
onUpdateLabel: ((oldLabel: String, newLabel: String) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
requireContext().displayEditLabelDialog(oldValue, model, layoutInflater, onUpdateLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.displayEditLabelDialog(
|
||||||
|
oldValue: String,
|
||||||
|
model: BaseNoteModel,
|
||||||
|
onUpdateLabel: ((oldLabel: String, newLabel: String) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
displayEditLabelDialog(oldValue, model, layoutInflater, onUpdateLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.displayEditLabelDialog(
|
||||||
|
oldValue: String,
|
||||||
|
model: BaseNoteModel,
|
||||||
|
layoutInflater: LayoutInflater,
|
||||||
|
onUpdateLabel: ((oldLabel: String, newLabel: String) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val dialogBinding = DialogInputBinding.inflate(layoutInflater)
|
||||||
|
dialogBinding.EditText.setText(oldValue)
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setView(dialogBinding.root)
|
||||||
|
.setTitle(R.string.edit_label)
|
||||||
|
.setCancelButton()
|
||||||
|
.setPositiveButton(R.string.save) { dialog, _ ->
|
||||||
|
val value = dialogBinding.EditText.text.toString().trim()
|
||||||
|
if (value.isNotEmpty()) {
|
||||||
|
model.updateLabel(oldValue, value) { success ->
|
||||||
|
if (success) {
|
||||||
|
onUpdateLabel?.invoke(oldValue, value)
|
||||||
|
dialog.dismiss()
|
||||||
|
} else showToast(R.string.label_exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.showAndFocus(dialogBinding.EditText, allowFullSize = true) { positiveButton ->
|
||||||
|
dialogBinding.EditText.doAfterTextChanged { text ->
|
||||||
|
positiveButton.isEnabled = !text.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
positiveButton.isEnabled = oldValue.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatTimestamp(timestamp: Long, dateFormat: DateFormat): String {
|
private fun formatTimestamp(timestamp: Long, dateFormat: DateFormat): String {
|
||||||
val date = Date(timestamp)
|
val date = Date(timestamp)
|
||||||
return when (dateFormat) {
|
return when (dateFormat) {
|
||||||
|
@ -566,11 +656,16 @@ fun @receiver:ColorInt Int.withAlpha(alpha: Float): Int {
|
||||||
fun Context.getColorFromAttr(@AttrRes attr: Int): Int {
|
fun Context.getColorFromAttr(@AttrRes attr: Int): Int {
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
val resolved = theme.resolveAttribute(attr, typedValue, true)
|
val resolved = theme.resolveAttribute(attr, typedValue, true)
|
||||||
if (resolved) {
|
if (!resolved) {
|
||||||
return typedValue.data // Returns the color as an Int
|
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException("Attribute not found in current theme")
|
throw IllegalArgumentException("Attribute not found in current theme")
|
||||||
}
|
}
|
||||||
|
return if (typedValue.resourceId != 0) {
|
||||||
|
// It's a reference (@color/something), resolve it properly
|
||||||
|
ContextCompat.getColor(this, typedValue.resourceId)
|
||||||
|
} else {
|
||||||
|
// It's a direct color value
|
||||||
|
typedValue.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.setControlsContrastColorForAllViews(
|
fun View.setControlsContrastColorForAllViews(
|
||||||
|
@ -740,16 +835,7 @@ fun Context.getContrastFontColor(@ColorInt backgroundColor: Int): Int {
|
||||||
else ContextCompat.getColor(this, R.color.TextLight)
|
else ContextCompat.getColor(this, R.color.TextLight)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun @receiver:ColorInt Int.isLightColor(): Boolean {
|
fun @receiver:ColorInt Int.isLightColor() = ColorUtils.calculateLuminance(this) > 0.5
|
||||||
val red = android.graphics.Color.red(this) / 255.0
|
|
||||||
val green = android.graphics.Color.green(this) / 255.0
|
|
||||||
val blue = android.graphics.Color.blue(this) / 255.0
|
|
||||||
|
|
||||||
// Calculate relative luminance
|
|
||||||
val luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue
|
|
||||||
|
|
||||||
return luminance > 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MaterialAlertDialogBuilder.setCancelButton(listener: DialogInterface.OnClickListener? = null) =
|
fun MaterialAlertDialogBuilder.setCancelButton(listener: DialogInterface.OnClickListener? = null) =
|
||||||
setNegativeButton(R.string.cancel, listener)
|
setNegativeButton(R.string.cancel, listener)
|
||||||
|
@ -786,12 +872,20 @@ fun Context.showToast(message: CharSequence) =
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ViewGroup.addFastScroll(context: Context) {
|
fun Context.restartApplication(
|
||||||
FastScrollerBuilder(this)
|
fragmentIdToOpen: Int? = null,
|
||||||
.useMd2Style()
|
extra: Pair<String, Boolean>? = null,
|
||||||
.setTrackDrawable(ContextCompat.getDrawable(context, R.drawable.scroll_track)!!)
|
) {
|
||||||
.setPadding(0, 0, 2.dp, 0)
|
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
||||||
.build()
|
val componentName = intent!!.component
|
||||||
|
val mainIntent =
|
||||||
|
Intent.makeRestartActivityTask(componentName).apply {
|
||||||
|
fragmentIdToOpen?.let { putExtra(MainActivity.EXTRA_FRAGMENT_TO_OPEN, it) }
|
||||||
|
extra?.let { (key, value) -> putExtra(key, value) }
|
||||||
|
}
|
||||||
|
mainIntent.setPackage(packageName)
|
||||||
|
startActivity(mainIntent)
|
||||||
|
Runtime.getRuntime().exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
|
@ -802,6 +896,14 @@ fun Context.extractColor(color: String): Int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ViewGroup.addFastScroll(context: Context) {
|
||||||
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setTrackDrawable(ContextCompat.getDrawable(context, R.drawable.scroll_track)!!)
|
||||||
|
.setPadding(0, 0, 2.dp, 0)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
fun Window.setLightStatusAndNavBar(value: Boolean, view: View = decorView) {
|
fun Window.setLightStatusAndNavBar(value: Boolean, view: View = decorView) {
|
||||||
val windowInsetsControllerCompat = WindowInsetsControllerCompat(this, view)
|
val windowInsetsControllerCompat = WindowInsetsControllerCompat(this, view)
|
||||||
windowInsetsControllerCompat.isAppearanceLightStatusBars = value
|
windowInsetsControllerCompat.isAppearanceLightStatusBars = value
|
||||||
|
@ -813,6 +915,8 @@ fun ChipGroup.bindLabels(
|
||||||
textSize: TextSize,
|
textSize: TextSize,
|
||||||
paddingTop: Boolean,
|
paddingTop: Boolean,
|
||||||
color: Int? = null,
|
color: Int? = null,
|
||||||
|
onClick: ((label: String) -> Unit)? = null,
|
||||||
|
onLongClick: ((label: String) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
if (labels.isEmpty()) {
|
if (labels.isEmpty()) {
|
||||||
visibility = View.GONE
|
visibility = View.GONE
|
||||||
|
@ -830,6 +934,13 @@ fun ChipGroup.bindLabels(
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, labelSize)
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, labelSize)
|
||||||
text = label
|
text = label
|
||||||
color?.let { setControlsContrastColorForAllViews(it) }
|
color?.let { setControlsContrastColorForAllViews(it) }
|
||||||
|
onClick?.let { setOnClickListener { it(label) } }
|
||||||
|
onLongClick?.let {
|
||||||
|
setOnLongClickListener {
|
||||||
|
it(label)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -856,6 +967,29 @@ fun RecyclerView.initListView(context: Context) {
|
||||||
setPadding(0, 0, 0, 0)
|
setPadding(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val RecyclerView.focusedViewHolder
|
||||||
|
get() =
|
||||||
|
focusedChild?.let { view ->
|
||||||
|
val position = getChildAdapterPosition(view)
|
||||||
|
if (position == RecyclerView.NO_POSITION) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
findViewHolderForAdapterPosition(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RecyclerView.showKeyboardOnFocusedItem() {
|
||||||
|
(focusedViewHolder as? ListItemVH)?.let {
|
||||||
|
it.binding.root.context?.showKeyboard(it.binding.EditText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RecyclerView.hideKeyboardOnFocusedItem() {
|
||||||
|
(focusedViewHolder as? ListItemVH)?.let {
|
||||||
|
it.binding.root.context?.hideKeyboard(it.binding.EditText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun MaterialAutoCompleteTextView.select(value: CharSequence) {
|
fun MaterialAutoCompleteTextView.select(value: CharSequence) {
|
||||||
setText(value, false)
|
setText(value, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,10 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
|
if (
|
||||||
|
preferences.biometricLock.value == BiometricLock.ENABLED &&
|
||||||
|
notallyXApplication.locked.value
|
||||||
|
) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,6 @@ import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.NotallyDatabase
|
import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.model.BaseNote
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.Type
|
|
||||||
import com.philkes.notallyx.data.model.toText
|
|
||||||
import com.philkes.notallyx.databinding.ActivityMainBinding
|
import com.philkes.notallyx.databinding.ActivityMainBinding
|
||||||
import com.philkes.notallyx.presentation.activity.LockedActivity
|
import com.philkes.notallyx.presentation.activity.LockedActivity
|
||||||
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
||||||
|
@ -40,7 +38,6 @@ import com.philkes.notallyx.presentation.activity.main.fragment.NotallyFragment
|
||||||
import com.philkes.notallyx.presentation.activity.note.EditListActivity
|
import com.philkes.notallyx.presentation.activity.note.EditListActivity
|
||||||
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
|
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
|
||||||
import com.philkes.notallyx.presentation.add
|
import com.philkes.notallyx.presentation.add
|
||||||
import com.philkes.notallyx.presentation.applySpans
|
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
import com.philkes.notallyx.presentation.movedToResId
|
import com.philkes.notallyx.presentation.movedToResId
|
||||||
import com.philkes.notallyx.presentation.setCancelButton
|
import com.philkes.notallyx.presentation.setCancelButton
|
||||||
|
@ -78,6 +75,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
var getCurrentFragmentNotes: (() -> Collection<BaseNote>?)? = null
|
var getCurrentFragmentNotes: (() -> Collection<BaseNote>?)? = null
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
|
baseModel.keyword = ""
|
||||||
return navController.navigateUp(configuration)
|
return navController.navigateUp(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +93,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
|
|
||||||
val fragmentIdToLoad = intent.getIntExtra(EXTRA_FRAGMENT_TO_OPEN, -1)
|
val fragmentIdToLoad = intent.getIntExtra(EXTRA_FRAGMENT_TO_OPEN, -1)
|
||||||
if (fragmentIdToLoad != -1) {
|
if (fragmentIdToLoad != -1) {
|
||||||
navController.navigate(fragmentIdToLoad, Bundle())
|
navController.navigate(fragmentIdToLoad, intent.extras)
|
||||||
} else if (savedInstanceState == null) {
|
} else if (savedInstanceState == null) {
|
||||||
navigateToStartView()
|
navigateToStartView()
|
||||||
}
|
}
|
||||||
|
@ -107,7 +105,10 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
if (baseModel.actionMode.enabled.value) {
|
if (baseModel.actionMode.enabled.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isStartViewFragment) {
|
if (
|
||||||
|
!isStartViewFragment &&
|
||||||
|
!intent.getBooleanExtra(EXTRA_SKIP_START_VIEW_ON_BACK, false)
|
||||||
|
) {
|
||||||
navigateToStartView()
|
navigateToStartView()
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
|
@ -192,7 +193,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
.setCheckable(true)
|
.setCheckable(true)
|
||||||
.setIcon(R.drawable.settings)
|
.setIcon(R.drawable.settings)
|
||||||
}
|
}
|
||||||
baseModel.preferences.labelsHiddenInNavigation.observe(this) { hiddenLabels ->
|
baseModel.preferences.labelsHidden.observe(this) { hiddenLabels ->
|
||||||
hideLabelsInNavigation(hiddenLabels, baseModel.preferences.maxLabels.value)
|
hideLabelsInNavigation(hiddenLabels, baseModel.preferences.maxLabels.value)
|
||||||
}
|
}
|
||||||
baseModel.preferences.maxLabels.observe(this) { maxLabels ->
|
baseModel.preferences.maxLabels.observe(this) { maxLabels ->
|
||||||
|
@ -241,10 +242,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
} else null
|
} else null
|
||||||
configuration = AppBarConfiguration(binding.NavigationView.menu, binding.DrawerLayout)
|
configuration = AppBarConfiguration(binding.NavigationView.menu, binding.DrawerLayout)
|
||||||
setupActionBarWithNavController(navController, configuration)
|
setupActionBarWithNavController(navController, configuration)
|
||||||
hideLabelsInNavigation(
|
hideLabelsInNavigation(baseModel.preferences.labelsHidden.value, maxLabelsToDisplay)
|
||||||
baseModel.preferences.labelsHiddenInNavigation.value,
|
|
||||||
maxLabelsToDisplay,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToLabel(label: String) {
|
private fun navigateToLabel(label: String) {
|
||||||
|
@ -321,12 +319,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
|
|
||||||
private fun share() {
|
private fun share() {
|
||||||
val baseNote = baseModel.actionMode.getFirstNote()
|
val baseNote = baseModel.actionMode.getFirstNote()
|
||||||
val body =
|
this.shareNote(baseNote)
|
||||||
when (baseNote.type) {
|
|
||||||
Type.NOTE -> baseNote.body.applySpans(baseNote.spans)
|
|
||||||
Type.LIST -> baseNote.items.toText()
|
|
||||||
}
|
|
||||||
this.shareNote(baseNote.title, body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteForever() {
|
private fun deleteForever() {
|
||||||
|
@ -689,5 +682,6 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_FRAGMENT_TO_OPEN = "notallyx.intent.extra.FRAGMENT_TO_OPEN"
|
const val EXTRA_FRAGMENT_TO_OPEN = "notallyx.intent.extra.FRAGMENT_TO_OPEN"
|
||||||
|
const val EXTRA_SKIP_START_VIEW_ON_BACK = "notallyx.intent.extra.SKIP_START_VIEW_ON_BACK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
@ -19,6 +18,7 @@ import com.philkes.notallyx.databinding.DialogInputBinding
|
||||||
import com.philkes.notallyx.databinding.FragmentNotesBinding
|
import com.philkes.notallyx.databinding.FragmentNotesBinding
|
||||||
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
||||||
import com.philkes.notallyx.presentation.add
|
import com.philkes.notallyx.presentation.add
|
||||||
|
import com.philkes.notallyx.presentation.displayEditLabelDialog
|
||||||
import com.philkes.notallyx.presentation.initListView
|
import com.philkes.notallyx.presentation.initListView
|
||||||
import com.philkes.notallyx.presentation.setCancelButton
|
import com.philkes.notallyx.presentation.setCancelButton
|
||||||
import com.philkes.notallyx.presentation.showAndFocus
|
import com.philkes.notallyx.presentation.showAndFocus
|
||||||
|
@ -77,7 +77,7 @@ class LabelsFragment : Fragment(), LabelListener {
|
||||||
|
|
||||||
override fun onEdit(position: Int) {
|
override fun onEdit(position: Int) {
|
||||||
labelAdapter?.currentList?.get(position)?.let { (label, _) ->
|
labelAdapter?.currentList?.get(position)?.let { (label, _) ->
|
||||||
displayEditLabelDialog(label)
|
displayEditLabelDialog(label, model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,13 +87,13 @@ class LabelsFragment : Fragment(), LabelListener {
|
||||||
|
|
||||||
override fun onToggleVisibility(position: Int) {
|
override fun onToggleVisibility(position: Int) {
|
||||||
labelAdapter?.currentList?.get(position)?.let { value ->
|
labelAdapter?.currentList?.get(position)?.let { value ->
|
||||||
val hiddenLabels = model.preferences.labelsHiddenInNavigation.value.toMutableSet()
|
val hiddenLabels = model.preferences.labelsHidden.value.toMutableSet()
|
||||||
if (value.visibleInNavigation) {
|
if (value.visibleInNavigation) {
|
||||||
hiddenLabels.add(value.label)
|
hiddenLabels.add(value.label)
|
||||||
} else {
|
} else {
|
||||||
hiddenLabels.remove(value.label)
|
hiddenLabels.remove(value.label)
|
||||||
}
|
}
|
||||||
model.savePreference(model.preferences.labelsHiddenInNavigation, hiddenLabels)
|
model.savePreference(model.preferences.labelsHidden, hiddenLabels)
|
||||||
|
|
||||||
val currentList = labelAdapter!!.currentList.toMutableList()
|
val currentList = labelAdapter!!.currentList.toMutableList()
|
||||||
currentList[position] =
|
currentList[position] =
|
||||||
|
@ -104,7 +104,7 @@ class LabelsFragment : Fragment(), LabelListener {
|
||||||
|
|
||||||
private fun setupObserver() {
|
private fun setupObserver() {
|
||||||
model.labels.observe(viewLifecycleOwner) { labels ->
|
model.labels.observe(viewLifecycleOwner) { labels ->
|
||||||
val hiddenLabels = model.preferences.labelsHiddenInNavigation.value
|
val hiddenLabels = model.preferences.labelsHidden.value
|
||||||
val labelsData = labels.map { label -> LabelData(label, !hiddenLabels.contains(label)) }
|
val labelsData = labels.map { label -> LabelData(label, !hiddenLabels.contains(label)) }
|
||||||
labelAdapter?.submitList(labelsData)
|
labelAdapter?.submitList(labelsData)
|
||||||
binding?.ImageView?.isVisible = labels.isEmpty()
|
binding?.ImageView?.isVisible = labels.isEmpty()
|
||||||
|
@ -148,37 +148,4 @@ class LabelsFragment : Fragment(), LabelListener {
|
||||||
.setCancelButton()
|
.setCancelButton()
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayEditLabelDialog(oldValue: String) {
|
|
||||||
val dialogBinding = DialogInputBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
dialogBinding.EditText.setText(oldValue)
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setView(dialogBinding.root)
|
|
||||||
.setTitle(R.string.edit_label)
|
|
||||||
.setCancelButton()
|
|
||||||
.setPositiveButton(R.string.save) { dialog, _ ->
|
|
||||||
val value = dialogBinding.EditText.text.toString().trim()
|
|
||||||
if (value.isNotEmpty()) {
|
|
||||||
model.updateLabel(oldValue, value) { success ->
|
|
||||||
if (success) {
|
|
||||||
dialog.dismiss()
|
|
||||||
} else
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.label_exists,
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.showAndFocus(dialogBinding.EditText, allowFullSize = true) { positiveButton ->
|
|
||||||
dialogBinding.EditText.doAfterTextChanged { text ->
|
|
||||||
positiveButton.isEnabled = !text.isNullOrEmpty()
|
|
||||||
}
|
|
||||||
positiveButton.isEnabled = oldValue.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,27 +195,20 @@ abstract class NotallyFragment : Fragment(), ItemListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doAfterTextChanged { text ->
|
doAfterTextChanged { text ->
|
||||||
model.keyword = requireNotNull(text).trim().toString()
|
val isSearchFragment = navController.currentDestination?.id == R.id.Search
|
||||||
if (
|
if (isSearchFragment) {
|
||||||
model.keyword.isNotEmpty() &&
|
model.keyword = requireNotNull(text).trim().toString()
|
||||||
navController.currentDestination?.id != R.id.Search
|
|
||||||
) {
|
|
||||||
val bundle =
|
|
||||||
Bundle().apply {
|
|
||||||
putSerializable(EXTRA_INITIAL_FOLDER, model.folder.value)
|
|
||||||
putSerializable(EXTRA_INITIAL_LABEL, model.currentLabel)
|
|
||||||
}
|
|
||||||
navController.navigate(R.id.Search, bundle)
|
|
||||||
}
|
}
|
||||||
}
|
if (text?.isNotEmpty() == true && !isSearchFragment) {
|
||||||
setOnFocusChangeListener { v, hasFocus ->
|
setText("")
|
||||||
if (hasFocus && navController.currentDestination?.id != R.id.Search) {
|
model.keyword = text.trim().toString()
|
||||||
val bundle =
|
navController.navigate(
|
||||||
|
R.id.Search,
|
||||||
Bundle().apply {
|
Bundle().apply {
|
||||||
putSerializable(EXTRA_INITIAL_FOLDER, model.folder.value)
|
putSerializable(EXTRA_INITIAL_FOLDER, model.folder.value)
|
||||||
putSerializable(EXTRA_INITIAL_LABEL, model.currentLabel)
|
putSerializable(EXTRA_INITIAL_LABEL, model.currentLabel)
|
||||||
}
|
},
|
||||||
navController.navigate(R.id.Search, bundle)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +236,8 @@ abstract class NotallyFragment : Fragment(), ItemListener {
|
||||||
maxItems.value,
|
maxItems.value,
|
||||||
maxLines.value,
|
maxLines.value,
|
||||||
maxTitle.value,
|
maxTitle.value,
|
||||||
labelsHiddenInOverview.value,
|
labelTagsHiddenInOverview.value,
|
||||||
|
imagesHiddenInOverview.value,
|
||||||
),
|
),
|
||||||
model.imageRoot,
|
model.imageRoot,
|
||||||
this@NotallyFragment,
|
this@NotallyFragment,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.View
|
||||||
import androidx.core.os.BundleCompat
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
|
|
||||||
class SearchFragment : NotallyFragment() {
|
class SearchFragment : NotallyFragment() {
|
||||||
|
@ -44,6 +45,9 @@ class SearchFragment : NotallyFragment() {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
}
|
}
|
||||||
} else binding?.ChipGroup?.isVisible = false
|
} else binding?.ChipGroup?.isVisible = false
|
||||||
|
getObservable().observe(viewLifecycleOwner) { items ->
|
||||||
|
model.actionMode.updateSelected(items?.filterIsInstance<BaseNote>()?.map { it.id })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackground() = R.drawable.search
|
override fun getBackground() = R.drawable.search
|
||||||
|
|
|
@ -6,15 +6,17 @@ import android.net.Uri
|
||||||
import android.text.method.PasswordTransformationMethod
|
import android.text.method.PasswordTransformationMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
import com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.databinding.ChoiceItemBinding
|
import com.philkes.notallyx.databinding.ChoiceItemBinding
|
||||||
import com.philkes.notallyx.databinding.DialogDateFormatBinding
|
|
||||||
import com.philkes.notallyx.databinding.DialogNotesSortBinding
|
import com.philkes.notallyx.databinding.DialogNotesSortBinding
|
||||||
import com.philkes.notallyx.databinding.DialogPreferenceBooleanBinding
|
import com.philkes.notallyx.databinding.DialogPreferenceBooleanBinding
|
||||||
|
import com.philkes.notallyx.databinding.DialogPreferenceEnumWithToggleBinding
|
||||||
import com.philkes.notallyx.databinding.DialogSelectionBoxBinding
|
import com.philkes.notallyx.databinding.DialogSelectionBoxBinding
|
||||||
import com.philkes.notallyx.databinding.DialogTextInputBinding
|
import com.philkes.notallyx.databinding.DialogTextInputBinding
|
||||||
import com.philkes.notallyx.databinding.PreferenceBinding
|
import com.philkes.notallyx.databinding.PreferenceBinding
|
||||||
|
@ -41,6 +43,7 @@ import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortPreferenc
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
|
import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.StringPreference
|
import com.philkes.notallyx.presentation.viewmodel.preference.StringPreference
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.TextProvider
|
import com.philkes.notallyx.presentation.viewmodel.preference.TextProvider
|
||||||
|
import com.philkes.notallyx.presentation.viewmodel.preference.Theme
|
||||||
import com.philkes.notallyx.utils.canAuthenticateWithBiometrics
|
import com.philkes.notallyx.utils.canAuthenticateWithBiometrics
|
||||||
import com.philkes.notallyx.utils.toReadablePath
|
import com.philkes.notallyx.utils.toReadablePath
|
||||||
|
|
||||||
|
@ -198,28 +201,35 @@ fun PreferenceBinding.setup(
|
||||||
Value.text = dateFormatValue.getText(context)
|
Value.text = dateFormatValue.getText(context)
|
||||||
|
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
val layout = DialogDateFormatBinding.inflate(layoutInflater, null, false)
|
val layout = DialogPreferenceEnumWithToggleBinding.inflate(layoutInflater, null, false)
|
||||||
|
layout.EnumHint.apply {
|
||||||
|
setText(R.string.date_format_hint)
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
DateFormat.entries.forEachIndexed { idx, dateFormat ->
|
DateFormat.entries.forEachIndexed { idx, dateFormat ->
|
||||||
ChoiceItemBinding.inflate(layoutInflater).root.apply {
|
ChoiceItemBinding.inflate(layoutInflater).root.apply {
|
||||||
id = idx
|
id = idx
|
||||||
text = dateFormat.getText(context)
|
text = dateFormat.getText(context)
|
||||||
tag = dateFormat
|
tag = dateFormat
|
||||||
layout.DateFormatRadioGroup.addView(this)
|
layout.EnumRadioGroup.addView(this)
|
||||||
if (dateFormat == dateFormatValue) {
|
if (dateFormat == dateFormatValue) {
|
||||||
layout.DateFormatRadioGroup.check(this.id)
|
layout.EnumRadioGroup.check(this.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.ApplyToNoteView.isChecked = applyToNoteViewValue
|
layout.Toggle.apply {
|
||||||
|
setText(R.string.date_format_apply_in_note_view)
|
||||||
|
isChecked = applyToNoteViewValue
|
||||||
|
}
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(dateFormatPreference.titleResId)
|
.setTitle(dateFormatPreference.titleResId)
|
||||||
.setView(layout.root)
|
.setView(layout.root)
|
||||||
.setPositiveButton(R.string.save) { dialog, _ ->
|
.setPositiveButton(R.string.save) { dialog, _ ->
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
val dateFormat = layout.DateFormatRadioGroup.checkedTag() as DateFormat
|
val dateFormat = layout.EnumRadioGroup.checkedTag() as DateFormat
|
||||||
val applyToNoteView = layout.ApplyToNoteView.isChecked
|
val applyToNoteView = layout.Toggle.isChecked
|
||||||
onSave(dateFormat, applyToNoteView)
|
onSave(dateFormat, applyToNoteView)
|
||||||
}
|
}
|
||||||
.setCancelButton()
|
.setCancelButton()
|
||||||
|
@ -227,6 +237,52 @@ fun PreferenceBinding.setup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PreferenceBinding.setup(
|
||||||
|
themePreference: EnumPreference<Theme>,
|
||||||
|
themeValue: Theme,
|
||||||
|
useDynamicColorsValue: Boolean,
|
||||||
|
context: Context,
|
||||||
|
layoutInflater: LayoutInflater,
|
||||||
|
onSave: (theme: Theme, useDynamicColors: Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Title.setText(themePreference.titleResId!!)
|
||||||
|
|
||||||
|
Value.text = themeValue.getText(context)
|
||||||
|
|
||||||
|
root.setOnClickListener {
|
||||||
|
val layout = DialogPreferenceEnumWithToggleBinding.inflate(layoutInflater, null, false)
|
||||||
|
Theme.entries.forEachIndexed { idx, theme ->
|
||||||
|
ChoiceItemBinding.inflate(layoutInflater).root.apply {
|
||||||
|
id = idx
|
||||||
|
text = theme.getText(context)
|
||||||
|
tag = theme
|
||||||
|
layout.EnumRadioGroup.addView(this)
|
||||||
|
if (theme == themeValue) {
|
||||||
|
layout.EnumRadioGroup.check(this.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.Toggle.apply {
|
||||||
|
isVisible = DynamicColors.isDynamicColorAvailable()
|
||||||
|
setText(R.string.theme_use_dynamic_colors)
|
||||||
|
isChecked = useDynamicColorsValue
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(themePreference.titleResId)
|
||||||
|
.setView(layout.root)
|
||||||
|
.setPositiveButton(R.string.save) { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
val theme = layout.EnumRadioGroup.checkedTag() as Theme
|
||||||
|
val useDynamicColors = layout.Toggle.isChecked
|
||||||
|
onSave(theme, useDynamicColors)
|
||||||
|
}
|
||||||
|
.setCancelButton()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun PreferenceBinding.setup(
|
fun PreferenceBinding.setup(
|
||||||
preference: BooleanPreference,
|
preference: BooleanPreference,
|
||||||
value: Boolean,
|
value: Boolean,
|
||||||
|
|
|
@ -31,7 +31,9 @@ import com.philkes.notallyx.data.imports.txt.APPLICATION_TEXT_MIME_TYPES
|
||||||
import com.philkes.notallyx.data.model.toText
|
import com.philkes.notallyx.data.model.toText
|
||||||
import com.philkes.notallyx.databinding.DialogTextInputBinding
|
import com.philkes.notallyx.databinding.DialogTextInputBinding
|
||||||
import com.philkes.notallyx.databinding.FragmentSettingsBinding
|
import com.philkes.notallyx.databinding.FragmentSettingsBinding
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.MainActivity
|
||||||
import com.philkes.notallyx.presentation.setCancelButton
|
import com.philkes.notallyx.presentation.setCancelButton
|
||||||
|
import com.philkes.notallyx.presentation.setEnabledSecureFlag
|
||||||
import com.philkes.notallyx.presentation.setupImportProgressDialog
|
import com.philkes.notallyx.presentation.setupImportProgressDialog
|
||||||
import com.philkes.notallyx.presentation.setupProgressDialog
|
import com.philkes.notallyx.presentation.setupProgressDialog
|
||||||
import com.philkes.notallyx.presentation.showAndFocus
|
import com.philkes.notallyx.presentation.showAndFocus
|
||||||
|
@ -51,6 +53,7 @@ import com.philkes.notallyx.utils.MIME_TYPE_JSON
|
||||||
import com.philkes.notallyx.utils.MIME_TYPE_ZIP
|
import com.philkes.notallyx.utils.MIME_TYPE_ZIP
|
||||||
import com.philkes.notallyx.utils.backup.exportPreferences
|
import com.philkes.notallyx.utils.backup.exportPreferences
|
||||||
import com.philkes.notallyx.utils.catchNoBrowserInstalled
|
import com.philkes.notallyx.utils.catchNoBrowserInstalled
|
||||||
|
import com.philkes.notallyx.utils.getExtraBooleanFromBundleOrIntent
|
||||||
import com.philkes.notallyx.utils.getLastExceptionLog
|
import com.philkes.notallyx.utils.getLastExceptionLog
|
||||||
import com.philkes.notallyx.utils.getLogFile
|
import com.philkes.notallyx.utils.getLogFile
|
||||||
import com.philkes.notallyx.utils.getUriForFile
|
import com.philkes.notallyx.utils.getUriForFile
|
||||||
|
@ -95,7 +98,13 @@ class SettingsFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
setupActivityResultLaunchers()
|
setupActivityResultLaunchers()
|
||||||
savedInstanceState?.getBoolean(EXTRA_SHOW_IMPORT_BACKUPS_FOLDER, false)?.let {
|
val showImportBackupsFolder =
|
||||||
|
getExtraBooleanFromBundleOrIntent(
|
||||||
|
savedInstanceState,
|
||||||
|
EXTRA_SHOW_IMPORT_BACKUPS_FOLDER,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
showImportBackupsFolder.let {
|
||||||
if (it) {
|
if (it) {
|
||||||
model.refreshBackupsFolder(
|
model.refreshBackupsFolder(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
|
@ -238,9 +247,27 @@ class SettingsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.observe(viewLifecycleOwner) { value ->
|
theme.merge(useDynamicColors).observe(viewLifecycleOwner) {
|
||||||
binding.Theme.setup(theme, value, requireContext()) { newValue ->
|
(themeValue, useDynamicColorsValue) ->
|
||||||
model.savePreference(theme, newValue)
|
binding.Theme.setup(
|
||||||
|
theme,
|
||||||
|
themeValue,
|
||||||
|
useDynamicColorsValue,
|
||||||
|
requireContext(),
|
||||||
|
layoutInflater,
|
||||||
|
) { newThemeValue, newUseDynamicColorsValue ->
|
||||||
|
model.savePreference(theme, newThemeValue)
|
||||||
|
model.savePreference(useDynamicColors, newUseDynamicColorsValue)
|
||||||
|
val packageManager = requireContext().packageManager
|
||||||
|
val intent = packageManager.getLaunchIntentForPackage(requireContext().packageName)
|
||||||
|
val componentName = intent!!.component
|
||||||
|
val mainIntent =
|
||||||
|
Intent.makeRestartActivityTask(componentName).apply {
|
||||||
|
putExtra(MainActivity.EXTRA_FRAGMENT_TO_OPEN, R.id.Settings)
|
||||||
|
}
|
||||||
|
mainIntent.setPackage(requireContext().packageName)
|
||||||
|
requireContext().startActivity(mainIntent)
|
||||||
|
Runtime.getRuntime().exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,15 +340,26 @@ class SettingsFragment : Fragment() {
|
||||||
MaxLabels.setup(maxLabels, requireContext()) { newValue ->
|
MaxLabels.setup(maxLabels, requireContext()) { newValue ->
|
||||||
model.savePreference(maxLabels, newValue)
|
model.savePreference(maxLabels, newValue)
|
||||||
}
|
}
|
||||||
labelsHiddenInOverview.observe(viewLifecycleOwner) { value ->
|
labelTagsHiddenInOverview.observe(viewLifecycleOwner) { value ->
|
||||||
binding.LabelsHiddenInOverview.setup(
|
binding.LabelsHiddenInOverview.setup(
|
||||||
labelsHiddenInOverview,
|
labelTagsHiddenInOverview,
|
||||||
value,
|
value,
|
||||||
requireContext(),
|
requireContext(),
|
||||||
layoutInflater,
|
layoutInflater,
|
||||||
R.string.labels_hidden_in_overview,
|
R.string.labels_hidden_in_overview,
|
||||||
) { enabled ->
|
) { enabled ->
|
||||||
model.savePreference(labelsHiddenInOverview, enabled)
|
model.savePreference(labelTagsHiddenInOverview, enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imagesHiddenInOverview.observe(viewLifecycleOwner) { value ->
|
||||||
|
binding.ImagesHiddenInOverview.setup(
|
||||||
|
imagesHiddenInOverview,
|
||||||
|
value,
|
||||||
|
requireContext(),
|
||||||
|
layoutInflater,
|
||||||
|
R.string.images_hidden_in_overview,
|
||||||
|
) { enabled ->
|
||||||
|
model.savePreference(imagesHiddenInOverview, enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,7 +452,7 @@ class SettingsFragment : Fragment() {
|
||||||
when (selectedImportSource.mimeType) {
|
when (selectedImportSource.mimeType) {
|
||||||
FOLDER_OR_FILE_MIMETYPE ->
|
FOLDER_OR_FILE_MIMETYPE ->
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.plain_text_files)
|
.setTitle(selectedImportSource.displayNameResId)
|
||||||
.setItems(
|
.setItems(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
getString(R.string.folder),
|
getString(R.string.folder),
|
||||||
|
@ -566,6 +604,14 @@ class SettingsFragment : Fragment() {
|
||||||
model.savePreference(backupPassword, newValue)
|
model.savePreference(backupPassword, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secureFlag.observe(viewLifecycleOwner) { value ->
|
||||||
|
binding.SecureFlag.setup(secureFlag, value, requireContext(), layoutInflater) { newValue
|
||||||
|
->
|
||||||
|
model.savePreference(secureFlag, newValue)
|
||||||
|
activity?.setEnabledSecureFlag(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotallyXPreferences.setupSettings(binding: FragmentSettingsBinding) {
|
private fun NotallyXPreferences.setupSettings(binding: FragmentSettingsBinding) {
|
||||||
|
@ -616,12 +662,11 @@ class SettingsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.AutoSaveAfterIdle.setupAutoSaveIdleTime(
|
AutoSaveAfterIdle.setupAutoSaveIdleTime(autoSaveAfterIdleTime, requireContext()) {
|
||||||
autoSaveAfterIdleTime,
|
newValue ->
|
||||||
requireContext(),
|
|
||||||
) { newValue ->
|
|
||||||
model.savePreference(autoSaveAfterIdleTime, newValue)
|
model.savePreference(autoSaveAfterIdleTime, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearData.setOnClickListener {
|
ClearData.setOnClickListener {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setMessage(R.string.clear_data_message)
|
.setMessage(R.string.clear_data_message)
|
||||||
|
|
|
@ -17,9 +17,11 @@ import android.util.TypedValue
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.ViewGroup.VISIBLE
|
import android.view.ViewGroup.VISIBLE
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ImageButton
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
@ -39,10 +41,14 @@ import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.model.Audio
|
import com.philkes.notallyx.data.model.Audio
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.data.model.toText
|
import com.philkes.notallyx.data.model.isImageMimeType
|
||||||
import com.philkes.notallyx.databinding.ActivityEditBinding
|
import com.philkes.notallyx.databinding.ActivityEditBinding
|
||||||
import com.philkes.notallyx.presentation.activity.LockedActivity
|
import com.philkes.notallyx.presentation.activity.LockedActivity
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.MainActivity
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.MainActivity.Companion.EXTRA_FRAGMENT_TO_OPEN
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.MainActivity.Companion.EXTRA_SKIP_START_VIEW_ON_BACK
|
||||||
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
import com.philkes.notallyx.presentation.activity.main.fragment.DisplayLabelFragment.Companion.EXTRA_DISPLAYED_LABEL
|
||||||
import com.philkes.notallyx.presentation.activity.note.SelectLabelsActivity.Companion.EXTRA_SELECTED_LABELS
|
import com.philkes.notallyx.presentation.activity.note.SelectLabelsActivity.Companion.EXTRA_SELECTED_LABELS
|
||||||
import com.philkes.notallyx.presentation.activity.note.reminders.RemindersActivity
|
import com.philkes.notallyx.presentation.activity.note.reminders.RemindersActivity
|
||||||
|
@ -50,9 +56,11 @@ import com.philkes.notallyx.presentation.add
|
||||||
import com.philkes.notallyx.presentation.addFastScroll
|
import com.philkes.notallyx.presentation.addFastScroll
|
||||||
import com.philkes.notallyx.presentation.addIconButton
|
import com.philkes.notallyx.presentation.addIconButton
|
||||||
import com.philkes.notallyx.presentation.bindLabels
|
import com.philkes.notallyx.presentation.bindLabels
|
||||||
|
import com.philkes.notallyx.presentation.displayEditLabelDialog
|
||||||
import com.philkes.notallyx.presentation.displayFormattedTimestamp
|
import com.philkes.notallyx.presentation.displayFormattedTimestamp
|
||||||
import com.philkes.notallyx.presentation.extractColor
|
import com.philkes.notallyx.presentation.extractColor
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
|
import com.philkes.notallyx.presentation.hideKeyboard
|
||||||
import com.philkes.notallyx.presentation.isLightColor
|
import com.philkes.notallyx.presentation.isLightColor
|
||||||
import com.philkes.notallyx.presentation.setCancelButton
|
import com.philkes.notallyx.presentation.setCancelButton
|
||||||
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
||||||
|
@ -79,6 +87,7 @@ import com.philkes.notallyx.presentation.widget.WidgetProvider
|
||||||
import com.philkes.notallyx.utils.FileError
|
import com.philkes.notallyx.utils.FileError
|
||||||
import com.philkes.notallyx.utils.backup.exportNotes
|
import com.philkes.notallyx.utils.backup.exportNotes
|
||||||
import com.philkes.notallyx.utils.changehistory.ChangeHistory
|
import com.philkes.notallyx.utils.changehistory.ChangeHistory
|
||||||
|
import com.philkes.notallyx.utils.getMimeType
|
||||||
import com.philkes.notallyx.utils.getUriForFile
|
import com.philkes.notallyx.utils.getUriForFile
|
||||||
import com.philkes.notallyx.utils.log
|
import com.philkes.notallyx.utils.log
|
||||||
import com.philkes.notallyx.utils.mergeSkipFirst
|
import com.philkes.notallyx.utils.mergeSkipFirst
|
||||||
|
@ -117,6 +126,10 @@ abstract class EditActivity(private val type: Type) :
|
||||||
protected var colorInt: Int = -1
|
protected var colorInt: Int = -1
|
||||||
protected var inputMethodManager: InputMethodManager? = null
|
protected var inputMethodManager: InputMethodManager? = null
|
||||||
|
|
||||||
|
protected lateinit var toggleViewMode: ImageButton
|
||||||
|
protected val canEdit
|
||||||
|
get() = notallyModel.viewMode.value == NoteViewMode.EDIT
|
||||||
|
|
||||||
private val autoSaveHandler = Handler(Looper.getMainLooper())
|
private val autoSaveHandler = Handler(Looper.getMainLooper())
|
||||||
private val autoSaveRunnable = Runnable {
|
private val autoSaveRunnable = Runnable {
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
@ -175,11 +188,15 @@ abstract class EditActivity(private val type: Type) :
|
||||||
if (persistedId == null || notallyModel.originalNote == null) {
|
if (persistedId == null || notallyModel.originalNote == null) {
|
||||||
notallyModel.setState(id)
|
notallyModel.setState(id)
|
||||||
}
|
}
|
||||||
if (notallyModel.isNewNote && intent.action == Intent.ACTION_SEND) {
|
if (notallyModel.isNewNote) {
|
||||||
handleSharedNote()
|
when (intent.action) {
|
||||||
} else if (notallyModel.isNewNote) {
|
Intent.ACTION_SEND,
|
||||||
intent.getStringExtra(EXTRA_DISPLAYED_LABEL)?.let {
|
Intent.ACTION_SEND_MULTIPLE -> handleSharedNote()
|
||||||
notallyModel.setLabels(listOf(it))
|
Intent.ACTION_VIEW -> handleViewNote()
|
||||||
|
else ->
|
||||||
|
intent.getStringExtra(EXTRA_DISPLAYED_LABEL)?.let {
|
||||||
|
notallyModel.setLabels(listOf(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +226,18 @@ abstract class EditActivity(private val type: Type) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun toggleCanEdit(mode: NoteViewMode) {
|
||||||
|
binding.EnterTitle.apply {
|
||||||
|
if (isFocused) {
|
||||||
|
when {
|
||||||
|
mode == NoteViewMode.EDIT -> showKeyboard(this)
|
||||||
|
else -> hideKeyboard(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCanEdit(mode == NoteViewMode.EDIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
autoSaveHandler.removeCallbacks(autoSaveRunnable)
|
autoSaveHandler.removeCallbacks(autoSaveRunnable)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
@ -476,7 +505,19 @@ abstract class EditActivity(private val type: Type) :
|
||||||
binding.BottomAppBarCenter.apply {
|
binding.BottomAppBarCenter.apply {
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
undo =
|
undo =
|
||||||
addIconButton(R.string.undo, R.drawable.undo, marginStart = 2) {
|
addIconButton(
|
||||||
|
R.string.undo,
|
||||||
|
R.drawable.undo,
|
||||||
|
marginStart = 2,
|
||||||
|
onLongClick = {
|
||||||
|
try {
|
||||||
|
changeHistory.undoAll()
|
||||||
|
} catch (e: ChangeHistory.ChangeHistoryException) {
|
||||||
|
application.log(TAG, throwable = e)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
changeHistory.undo()
|
changeHistory.undo()
|
||||||
} catch (e: ChangeHistory.ChangeHistoryException) {
|
} catch (e: ChangeHistory.ChangeHistoryException) {
|
||||||
|
@ -486,7 +527,19 @@ abstract class EditActivity(private val type: Type) :
|
||||||
.apply { isEnabled = changeHistory.canUndo.value }
|
.apply { isEnabled = changeHistory.canUndo.value }
|
||||||
|
|
||||||
redo =
|
redo =
|
||||||
addIconButton(R.string.redo, R.drawable.redo, marginStart = 2) {
|
addIconButton(
|
||||||
|
R.string.redo,
|
||||||
|
R.drawable.redo,
|
||||||
|
marginStart = 2,
|
||||||
|
onLongClick = {
|
||||||
|
try {
|
||||||
|
changeHistory.redoAll()
|
||||||
|
} catch (e: ChangeHistory.ChangeHistoryException) {
|
||||||
|
application.log(TAG, throwable = e)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
changeHistory.redo()
|
changeHistory.redo()
|
||||||
} catch (e: ChangeHistory.ChangeHistoryException) {
|
} catch (e: ChangeHistory.ChangeHistoryException) {
|
||||||
|
@ -497,14 +550,31 @@ abstract class EditActivity(private val type: Type) :
|
||||||
}
|
}
|
||||||
binding.BottomAppBarRight.apply {
|
binding.BottomAppBarRight.apply {
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
addIconButton(R.string.more, R.drawable.more_vert, marginStart = 0) {
|
|
||||||
MoreNoteBottomSheet(this@EditActivity, createFolderActions(), colorInt)
|
addToggleViewMode()
|
||||||
|
addIconButton(R.string.tap_for_more_options, R.drawable.more_vert, marginStart = 0) {
|
||||||
|
MoreNoteBottomSheet(
|
||||||
|
this@EditActivity,
|
||||||
|
createNoteTypeActions() + createFolderActions(),
|
||||||
|
colorInt,
|
||||||
|
)
|
||||||
.show(supportFragmentManager, MoreNoteBottomSheet.TAG)
|
.show(supportFragmentManager, MoreNoteBottomSheet.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setBottomAppBarColor(colorInt)
|
setBottomAppBarColor(colorInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun ViewGroup.addToggleViewMode() {
|
||||||
|
toggleViewMode =
|
||||||
|
addIconButton(R.string.edit, R.drawable.visibility) {
|
||||||
|
notallyModel.viewMode.value =
|
||||||
|
when (notallyModel.viewMode.value) {
|
||||||
|
NoteViewMode.EDIT -> NoteViewMode.READ_ONLY
|
||||||
|
NoteViewMode.READ_ONLY -> NoteViewMode.EDIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun createFolderActions() =
|
protected fun createFolderActions() =
|
||||||
when (notallyModel.folder) {
|
when (notallyModel.folder) {
|
||||||
Folder.NOTES ->
|
Folder.NOTES ->
|
||||||
|
@ -544,12 +614,68 @@ abstract class EditActivity(private val type: Type) :
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun createNoteTypeActions() =
|
||||||
|
when (notallyModel.type) {
|
||||||
|
Type.NOTE ->
|
||||||
|
listOf(
|
||||||
|
Action(R.string.convert_to_list_note, R.drawable.convert_to_text) { _ ->
|
||||||
|
convertTo(Type.LIST)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Type.LIST ->
|
||||||
|
listOf(
|
||||||
|
Action(R.string.convert_to_text_note, R.drawable.convert_to_text) { _ ->
|
||||||
|
convertTo(Type.NOTE)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertTo(type: Type) {
|
||||||
|
updateModel()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
notallyModel.convertTo(type)
|
||||||
|
val intent =
|
||||||
|
Intent(
|
||||||
|
this@EditActivity,
|
||||||
|
when (type) {
|
||||||
|
Type.NOTE -> EditNoteActivity::class.java
|
||||||
|
Type.LIST -> EditListActivity::class.java
|
||||||
|
},
|
||||||
|
)
|
||||||
|
intent.putExtra(EXTRA_SELECTED_BASE_NOTE, notallyModel.id)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract fun configureUI()
|
abstract fun configureUI()
|
||||||
|
|
||||||
open fun setupListeners() {
|
open fun setupListeners() {
|
||||||
binding.EnterTitle.initHistory(changeHistory) { text ->
|
binding.EnterTitle.initHistory(changeHistory) { text ->
|
||||||
notallyModel.title = text.trim().toString()
|
notallyModel.title = text.trim().toString()
|
||||||
}
|
}
|
||||||
|
notallyModel.viewMode.observe(this) { value ->
|
||||||
|
toggleViewMode.apply {
|
||||||
|
setImageResource(
|
||||||
|
when (value) {
|
||||||
|
NoteViewMode.READ_ONLY -> R.drawable.edit
|
||||||
|
else -> R.drawable.visibility
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
tooltipText =
|
||||||
|
getString(
|
||||||
|
when (value) {
|
||||||
|
NoteViewMode.READ_ONLY -> R.string.edit
|
||||||
|
else -> R.string.read_only
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value?.let { toggleCanEdit(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setStateFromModel(savedInstanceState: Bundle?) {
|
open fun setStateFromModel(savedInstanceState: Bundle?) {
|
||||||
|
@ -566,26 +692,90 @@ abstract class EditActivity(private val type: Type) :
|
||||||
} else DateFormat.ABSOLUTE
|
} else DateFormat.ABSOLUTE
|
||||||
binding.Date.displayFormattedTimestamp(date, dateFormat, datePrefixResId)
|
binding.Date.displayFormattedTimestamp(date, dateFormat, datePrefixResId)
|
||||||
binding.EnterTitle.setText(notallyModel.title)
|
binding.EnterTitle.setText(notallyModel.title)
|
||||||
|
bindLabels()
|
||||||
|
setColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindLabels() {
|
||||||
binding.LabelGroup.bindLabels(
|
binding.LabelGroup.bindLabels(
|
||||||
notallyModel.labels,
|
notallyModel.labels,
|
||||||
notallyModel.textSize,
|
notallyModel.textSize,
|
||||||
paddingTop = true,
|
paddingTop = true,
|
||||||
colorInt,
|
colorInt,
|
||||||
|
onClick = { label ->
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString(EXTRA_DISPLAYED_LABEL, label)
|
||||||
|
startActivity(
|
||||||
|
Intent(this, MainActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_FRAGMENT_TO_OPEN, R.id.DisplayLabel)
|
||||||
|
putExtra(EXTRA_DISPLAYED_LABEL, label)
|
||||||
|
putExtra(EXTRA_SKIP_START_VIEW_ON_BACK, true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClick = { label ->
|
||||||
|
displayEditLabelDialog(label, baseModel) { oldLabel, newLabel ->
|
||||||
|
notallyModel.labels.apply {
|
||||||
|
remove(oldLabel)
|
||||||
|
add(newLabel)
|
||||||
|
}
|
||||||
|
bindLabels()
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
setColor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSharedNote() {
|
private fun handleSharedNote() {
|
||||||
val title = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
val title = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
|
|
||||||
val string = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val string = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
val files =
|
||||||
|
IntentCompat.getParcelableArrayListExtra(intent, Intent.EXTRA_STREAM, Uri::class.java)
|
||||||
|
?: IntentCompat.getParcelableExtra(intent, Intent.EXTRA_STREAM, Uri::class.java)
|
||||||
|
?.let { listOf(it) }
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
notallyModel.body = Editable.Factory.getInstance().newEditable(string)
|
notallyModel.body = Editable.Factory.getInstance().newEditable(string)
|
||||||
}
|
}
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
notallyModel.title = title
|
notallyModel.title = title
|
||||||
}
|
}
|
||||||
|
files?.let {
|
||||||
|
val filesByType =
|
||||||
|
it.groupBy { uri ->
|
||||||
|
getMimeType(uri)?.let { mimeType ->
|
||||||
|
if (mimeType.isImageMimeType) {
|
||||||
|
NotallyModel.FileType.IMAGE
|
||||||
|
} else {
|
||||||
|
NotallyModel.FileType.ANY
|
||||||
|
}
|
||||||
|
} ?: NotallyModel.FileType.ANY
|
||||||
|
}
|
||||||
|
filesByType[NotallyModel.FileType.IMAGE]?.let { images ->
|
||||||
|
notallyModel.addImages(images.toTypedArray())
|
||||||
|
}
|
||||||
|
filesByType[NotallyModel.FileType.ANY]?.let { otherFiles ->
|
||||||
|
notallyModel.addFiles(otherFiles.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleViewNote() {
|
||||||
|
val text =
|
||||||
|
intent.data?.let { uri ->
|
||||||
|
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
inputStream.bufferedReader().readText()
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
showToast(R.string.cant_load_file)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
val title = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
|
if (text != null) {
|
||||||
|
notallyModel.body = Editable.Factory.getInstance().newEditable(text)
|
||||||
|
}
|
||||||
|
if (title != null) {
|
||||||
|
notallyModel.title = title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
|
@ -702,12 +892,7 @@ abstract class EditActivity(private val type: Type) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun share() {
|
override fun share() {
|
||||||
val body =
|
this.shareNote(notallyModel.getBaseNote())
|
||||||
when (type) {
|
|
||||||
Type.NOTE -> notallyModel.body
|
|
||||||
Type.LIST -> notallyModel.items.toMutableList().toText()
|
|
||||||
}
|
|
||||||
this.shareNote(notallyModel.title, body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun export(mimeType: ExportMimeType) {
|
override fun export(mimeType: ExportMimeType) {
|
||||||
|
@ -910,7 +1095,9 @@ abstract class EditActivity(private val type: Type) :
|
||||||
colorInt = extractColor(notallyModel.color)
|
colorInt = extractColor(notallyModel.color)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
window.statusBarColor = colorInt
|
window.statusBarColor = colorInt
|
||||||
window.navigationBarColor = colorInt
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
window.navigationBarColor = colorInt
|
||||||
|
}
|
||||||
window.setLightStatusAndNavBar(colorInt.isLightColor())
|
window.setLightStatusAndNavBar(colorInt.isLightColor())
|
||||||
}
|
}
|
||||||
binding.apply {
|
binding.apply {
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package com.philkes.notallyx.presentation.activity.note
|
package com.philkes.notallyx.presentation.activity.note
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SortedList
|
import androidx.recyclerview.widget.SortedList
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.presentation.addIconButton
|
import com.philkes.notallyx.presentation.addIconButton
|
||||||
|
import com.philkes.notallyx.presentation.hideKeyboardOnFocusedItem
|
||||||
import com.philkes.notallyx.presentation.setOnNextAction
|
import com.philkes.notallyx.presentation.setOnNextAction
|
||||||
|
import com.philkes.notallyx.presentation.showKeyboardOnFocusedItem
|
||||||
import com.philkes.notallyx.presentation.view.note.action.MoreListActions
|
import com.philkes.notallyx.presentation.view.note.action.MoreListActions
|
||||||
import com.philkes.notallyx.presentation.view.note.action.MoreListBottomSheet
|
import com.philkes.notallyx.presentation.view.note.action.MoreListBottomSheet
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
||||||
|
@ -69,6 +73,21 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toggleCanEdit(mode: NoteViewMode) {
|
||||||
|
super.toggleCanEdit(mode)
|
||||||
|
when (mode) {
|
||||||
|
NoteViewMode.EDIT -> binding.MainListView.showKeyboardOnFocusedItem()
|
||||||
|
NoteViewMode.READ_ONLY -> binding.MainListView.hideKeyboardOnFocusedItem()
|
||||||
|
}
|
||||||
|
adapter?.viewMode = mode
|
||||||
|
adapterChecked?.viewMode = mode
|
||||||
|
binding.AddItem.visibility =
|
||||||
|
when (mode) {
|
||||||
|
NoteViewMode.EDIT -> View.VISIBLE
|
||||||
|
else -> View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun deleteChecked() {
|
override fun deleteChecked() {
|
||||||
listManager.deleteCheckedItems()
|
listManager.deleteCheckedItems()
|
||||||
}
|
}
|
||||||
|
@ -85,8 +104,13 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
||||||
super.initBottomMenu()
|
super.initBottomMenu()
|
||||||
binding.BottomAppBarRight.apply {
|
binding.BottomAppBarRight.apply {
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
addIconButton(R.string.more, R.drawable.more_vert, marginStart = 0) {
|
addToggleViewMode()
|
||||||
MoreListBottomSheet(this@EditListActivity, createFolderActions(), colorInt)
|
addIconButton(R.string.tap_for_more_options, R.drawable.more_vert, marginStart = 0) {
|
||||||
|
MoreListBottomSheet(
|
||||||
|
this@EditListActivity,
|
||||||
|
createNoteTypeActions() + createFolderActions(),
|
||||||
|
colorInt,
|
||||||
|
)
|
||||||
.show(supportFragmentManager, MoreListBottomSheet.TAG)
|
.show(supportFragmentManager, MoreListBottomSheet.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,12 @@ import android.widget.LinearLayout
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.data.model.createNoteUrl
|
import com.philkes.notallyx.data.model.createNoteUrl
|
||||||
import com.philkes.notallyx.data.model.getNoteIdFromUrl
|
import com.philkes.notallyx.data.model.getNoteIdFromUrl
|
||||||
|
@ -38,7 +40,9 @@ import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companio
|
||||||
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companion.EXTRA_PICKED_NOTE_TYPE
|
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companion.EXTRA_PICKED_NOTE_TYPE
|
||||||
import com.philkes.notallyx.presentation.add
|
import com.philkes.notallyx.presentation.add
|
||||||
import com.philkes.notallyx.presentation.addIconButton
|
import com.philkes.notallyx.presentation.addIconButton
|
||||||
|
import com.philkes.notallyx.presentation.createBoldSpan
|
||||||
import com.philkes.notallyx.presentation.dp
|
import com.philkes.notallyx.presentation.dp
|
||||||
|
import com.philkes.notallyx.presentation.hideKeyboard
|
||||||
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
||||||
import com.philkes.notallyx.presentation.setOnNextAction
|
import com.philkes.notallyx.presentation.setOnNextAction
|
||||||
import com.philkes.notallyx.presentation.showKeyboard
|
import com.philkes.notallyx.presentation.showKeyboard
|
||||||
|
@ -65,8 +69,6 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
override fun configureUI() {
|
override fun configureUI() {
|
||||||
binding.EnterTitle.setOnNextAction { binding.EnterBody.requestFocus() }
|
binding.EnterTitle.setOnNextAction { binding.EnterBody.requestFocus() }
|
||||||
|
|
||||||
setupEditor()
|
|
||||||
|
|
||||||
if (notallyModel.isNewNote) {
|
if (notallyModel.isNewNote) {
|
||||||
binding.EnterBody.requestFocus()
|
binding.EnterBody.requestFocus()
|
||||||
}
|
}
|
||||||
|
@ -78,6 +80,17 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
setupActivityResultLaunchers()
|
setupActivityResultLaunchers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toggleCanEdit(mode: NoteViewMode) {
|
||||||
|
super.toggleCanEdit(mode)
|
||||||
|
textFormatMenu.isVisible = mode == NoteViewMode.EDIT
|
||||||
|
when {
|
||||||
|
mode == NoteViewMode.EDIT -> showKeyboard(binding.EnterBody)
|
||||||
|
binding.EnterBody.isFocused -> hideKeyboard(binding.EnterBody)
|
||||||
|
}
|
||||||
|
binding.EnterBody.setCanEdit(mode == NoteViewMode.EDIT)
|
||||||
|
setupEditor()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.apply {
|
outState.apply {
|
||||||
|
@ -172,78 +185,8 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
|
|
||||||
private fun setupEditor() {
|
private fun setupEditor() {
|
||||||
setupMovementMethod()
|
setupMovementMethod()
|
||||||
|
|
||||||
binding.EnterBody.customSelectionActionModeCallback =
|
binding.EnterBody.customSelectionActionModeCallback =
|
||||||
object : ActionMode.Callback {
|
if (canEdit) {
|
||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = false
|
|
||||||
|
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
|
||||||
binding.EnterBody.isActionModeOn = true
|
|
||||||
// Try block is there because this will crash on MiUI as Xiaomi has a broken
|
|
||||||
// ActionMode implementation
|
|
||||||
try {
|
|
||||||
menu?.apply {
|
|
||||||
add(R.string.link, 0, showAsAction = MenuItem.SHOW_AS_ACTION_NEVER) {
|
|
||||||
binding.EnterBody.showAddLinkDialog(
|
|
||||||
this@EditNoteActivity,
|
|
||||||
mode = mode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
add(R.string.bold, 0, showAsAction = MenuItem.SHOW_AS_ACTION_NEVER) {
|
|
||||||
binding.EnterBody.applySpan(StyleSpan(Typeface.BOLD))
|
|
||||||
mode?.finish()
|
|
||||||
}
|
|
||||||
add(R.string.italic, 0, showAsAction = MenuItem.SHOW_AS_ACTION_NEVER) {
|
|
||||||
binding.EnterBody.applySpan(StyleSpan(Typeface.ITALIC))
|
|
||||||
mode?.finish()
|
|
||||||
}
|
|
||||||
add(
|
|
||||||
R.string.monospace,
|
|
||||||
0,
|
|
||||||
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
|
||||||
) {
|
|
||||||
binding.EnterBody.applySpan(TypefaceSpan("monospace"))
|
|
||||||
mode?.finish()
|
|
||||||
}
|
|
||||||
add(
|
|
||||||
R.string.strikethrough,
|
|
||||||
0,
|
|
||||||
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
|
||||||
) {
|
|
||||||
binding.EnterBody.applySpan(StrikethroughSpan())
|
|
||||||
mode?.finish()
|
|
||||||
}
|
|
||||||
add(
|
|
||||||
R.string.clear_formatting,
|
|
||||||
0,
|
|
||||||
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
|
||||||
) {
|
|
||||||
binding.EnterBody.clearFormatting()
|
|
||||||
mode?.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
exception.printStackTrace()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
|
||||||
binding.EnterBody.isActionModeOn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.ContentLayout.setOnClickListener {
|
|
||||||
binding.EnterBody.apply {
|
|
||||||
requestFocus()
|
|
||||||
setSelection(length())
|
|
||||||
showKeyboard(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
binding.EnterBody.customInsertionActionModeCallback =
|
|
||||||
object : ActionMode.Callback {
|
object : ActionMode.Callback {
|
||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
||||||
|
|
||||||
|
@ -255,8 +198,54 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
// ActionMode implementation
|
// ActionMode implementation
|
||||||
try {
|
try {
|
||||||
menu?.apply {
|
menu?.apply {
|
||||||
add(R.string.link_note, 0, order = Menu.CATEGORY_CONTAINER + 1) {
|
add(
|
||||||
linkNote(pickNoteNewActivityResultLauncher)
|
R.string.link,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.showAddLinkDialog(
|
||||||
|
this@EditNoteActivity,
|
||||||
|
mode = mode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
R.string.bold,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.applySpan(createBoldSpan())
|
||||||
|
mode?.finish()
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
R.string.italic,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.applySpan(StyleSpan(Typeface.ITALIC))
|
||||||
|
mode?.finish()
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
R.string.monospace,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.applySpan(TypefaceSpan("monospace"))
|
||||||
|
mode?.finish()
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
R.string.strikethrough,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.applySpan(StrikethroughSpan())
|
||||||
|
mode?.finish()
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
R.string.clear_formatting,
|
||||||
|
0,
|
||||||
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
|
) {
|
||||||
|
binding.EnterBody.clearFormatting()
|
||||||
mode?.finish()
|
mode?.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,26 +259,69 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
binding.EnterBody.isActionModeOn = false
|
binding.EnterBody.isActionModeOn = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
binding.EnterBody.customInsertionActionModeCallback =
|
||||||
|
if (canEdit) {
|
||||||
|
object : ActionMode.Callback {
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = false
|
||||||
|
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
binding.EnterBody.isActionModeOn = true
|
||||||
|
// Try block is there because this will crash on MiUI as Xiaomi has a
|
||||||
|
// broken
|
||||||
|
// ActionMode implementation
|
||||||
|
try {
|
||||||
|
menu?.apply {
|
||||||
|
add(
|
||||||
|
R.string.link_note,
|
||||||
|
0,
|
||||||
|
order = Menu.CATEGORY_CONTAINER + 1,
|
||||||
|
) {
|
||||||
|
linkNote(pickNoteNewActivityResultLauncher)
|
||||||
|
mode?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
exception.printStackTrace()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
binding.EnterBody.isActionModeOn = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
binding.EnterBody.setOnSelectionChange { selStart, selEnd ->
|
if (canEdit) {
|
||||||
if (selEnd - selStart > 0) {
|
binding.EnterBody.setOnSelectionChange { selStart, selEnd ->
|
||||||
if (!textFormatMenu.isEnabled) {
|
if (selEnd - selStart > 0) {
|
||||||
initBottomTextFormattingMenu()
|
if (!textFormatMenu.isEnabled) {
|
||||||
|
initBottomTextFormattingMenu()
|
||||||
|
}
|
||||||
|
textFormatMenu.isEnabled = true
|
||||||
|
textFormattingAdapter?.updateTextFormattingToggles(selStart, selEnd)
|
||||||
|
} else {
|
||||||
|
if (textFormatMenu.isEnabled) {
|
||||||
|
initBottomMenu()
|
||||||
|
}
|
||||||
|
textFormatMenu.isEnabled = false
|
||||||
}
|
}
|
||||||
textFormatMenu.isEnabled = true
|
|
||||||
textFormattingAdapter?.updateTextFormattingToggles(selStart, selEnd)
|
|
||||||
} else {
|
|
||||||
if (textFormatMenu.isEnabled) {
|
|
||||||
initBottomMenu()
|
|
||||||
}
|
|
||||||
textFormatMenu.isEnabled = false
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
binding.EnterBody.setOnSelectionChange { _, _ -> }
|
||||||
}
|
}
|
||||||
binding.ContentLayout.setOnClickListener {
|
binding.ContentLayout.setOnClickListener {
|
||||||
binding.EnterBody.apply {
|
binding.EnterBody.apply {
|
||||||
requestFocus()
|
requestFocus()
|
||||||
setSelection(length())
|
if (canEdit) {
|
||||||
showKeyboard(this)
|
setSelection(length())
|
||||||
|
showKeyboard(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,19 +398,23 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
val movementMethod = LinkMovementMethod { span ->
|
val movementMethod = LinkMovementMethod { span ->
|
||||||
val items =
|
val items =
|
||||||
if (span.url.isNoteUrl()) {
|
if (span.url.isNoteUrl()) {
|
||||||
arrayOf(
|
if (canEdit) {
|
||||||
getString(R.string.remove_link),
|
arrayOf(
|
||||||
getString(R.string.change_note),
|
getString(R.string.open_note),
|
||||||
getString(R.string.edit),
|
getString(R.string.remove_link),
|
||||||
getString(R.string.open_note),
|
getString(R.string.change_note),
|
||||||
)
|
getString(R.string.edit),
|
||||||
|
)
|
||||||
|
} else arrayOf(getString(R.string.open_note))
|
||||||
} else {
|
} else {
|
||||||
arrayOf(
|
if (canEdit) {
|
||||||
getString(R.string.remove_link),
|
arrayOf(
|
||||||
getString(R.string.copy),
|
getString(R.string.open_link),
|
||||||
getString(R.string.edit),
|
getString(R.string.copy),
|
||||||
getString(R.string.open_link),
|
getString(R.string.remove_link),
|
||||||
)
|
getString(R.string.edit),
|
||||||
|
)
|
||||||
|
} else arrayOf(getString(R.string.open_link), getString(R.string.copy))
|
||||||
}
|
}
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(
|
.setTitle(
|
||||||
|
@ -390,35 +426,16 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
)
|
)
|
||||||
.setItems(items) { _, which ->
|
.setItems(items) { _, which ->
|
||||||
when (which) {
|
when (which) {
|
||||||
0 -> {
|
0 -> openLink(span)
|
||||||
binding.EnterBody.removeSpanWithHistory(
|
|
||||||
span,
|
|
||||||
span.url.isNoteUrl() ||
|
|
||||||
span.url == binding.EnterBody.getSpanText(span),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
1 ->
|
1 ->
|
||||||
if (span.url.isNoteUrl()) {
|
if (span.url.isNoteUrl()) {
|
||||||
selectedSpan = span
|
removeLink(span)
|
||||||
linkNote(pickNoteUpdateActivityResultLauncher)
|
} else copyLink(span)
|
||||||
} else {
|
2 ->
|
||||||
copyToClipBoard(span.url)
|
if (span.url.isNoteUrl()) {
|
||||||
showToast(R.string.copied_link)
|
changeNoteLink(span)
|
||||||
}
|
} else removeLink(span)
|
||||||
|
3 -> editLink(span)
|
||||||
2 -> {
|
|
||||||
binding.EnterBody.showEditDialog(span)
|
|
||||||
}
|
|
||||||
|
|
||||||
3 -> {
|
|
||||||
span.url?.let {
|
|
||||||
if (it.isNoteUrl()) {
|
|
||||||
span.navigateToNote()
|
|
||||||
} else {
|
|
||||||
openLink(span.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
@ -426,6 +443,37 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
binding.EnterBody.movementMethod = movementMethod
|
binding.EnterBody.movementMethod = movementMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openLink(span: URLSpan) {
|
||||||
|
span.url?.let {
|
||||||
|
if (it.isNoteUrl()) {
|
||||||
|
span.navigateToNote()
|
||||||
|
} else {
|
||||||
|
openLink(span.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun editLink(span: URLSpan) {
|
||||||
|
binding.EnterBody.showEditDialog(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changeNoteLink(span: URLSpan) {
|
||||||
|
selectedSpan = span
|
||||||
|
linkNote(pickNoteUpdateActivityResultLauncher)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyLink(span: URLSpan) {
|
||||||
|
copyToClipBoard(span.url)
|
||||||
|
showToast(R.string.copied_link)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeLink(span: URLSpan) {
|
||||||
|
binding.EnterBody.removeSpanWithHistory(
|
||||||
|
span,
|
||||||
|
span.url.isNoteUrl() || span.url == binding.EnterBody.getSpanText(span),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun openLink(url: String) {
|
private fun openLink(url: String) {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri).wrapWithChooser(this)
|
val intent = Intent(Intent.ACTION_VIEW, uri).wrapWithChooser(this)
|
||||||
|
|
|
@ -51,7 +51,8 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ItemLis
|
||||||
maxItems.value,
|
maxItems.value,
|
||||||
maxLines.value,
|
maxLines.value,
|
||||||
maxTitle.value,
|
maxTitle.value,
|
||||||
labelsHiddenInOverview.value,
|
labelTagsHiddenInOverview.value,
|
||||||
|
imagesHiddenInOverview.value,
|
||||||
),
|
),
|
||||||
application.getExternalImagesDirectory(),
|
application.getExternalImagesDirectory(),
|
||||||
this@PickNoteActivity,
|
this@PickNoteActivity,
|
||||||
|
@ -71,6 +72,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ItemLis
|
||||||
|
|
||||||
val pinned = Header(getString(R.string.pinned))
|
val pinned = Header(getString(R.string.pinned))
|
||||||
val others = Header(getString(R.string.others))
|
val others = Header(getString(R.string.others))
|
||||||
|
val archived = Header(getString(R.string.archived))
|
||||||
|
|
||||||
database.observe(this) {
|
database.observe(this) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
@ -78,7 +80,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ItemLis
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val raw =
|
val raw =
|
||||||
it.getBaseNoteDao().getAllNotes().filter { it.id != excludedNoteId }
|
it.getBaseNoteDao().getAllNotes().filter { it.id != excludedNoteId }
|
||||||
BaseNoteModel.transform(raw, pinned, others)
|
BaseNoteModel.transform(raw, pinned, others, archived)
|
||||||
}
|
}
|
||||||
adapter.submitList(notes)
|
adapter.submitList(notes)
|
||||||
binding.EmptyView.visibility =
|
binding.EmptyView.visibility =
|
||||||
|
|
|
@ -31,7 +31,6 @@ import com.philkes.notallyx.presentation.bindLabels
|
||||||
import com.philkes.notallyx.presentation.displayFormattedTimestamp
|
import com.philkes.notallyx.presentation.displayFormattedTimestamp
|
||||||
import com.philkes.notallyx.presentation.dp
|
import com.philkes.notallyx.presentation.dp
|
||||||
import com.philkes.notallyx.presentation.extractColor
|
import com.philkes.notallyx.presentation.extractColor
|
||||||
import com.philkes.notallyx.presentation.getColorFromAttr
|
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
||||||
import com.philkes.notallyx.presentation.view.misc.ItemListener
|
import com.philkes.notallyx.presentation.view.misc.ItemListener
|
||||||
|
@ -46,6 +45,7 @@ data class BaseNoteVHPreferences(
|
||||||
val maxLines: Int,
|
val maxLines: Int,
|
||||||
val maxTitleLines: Int,
|
val maxTitleLines: Int,
|
||||||
val hideLabels: Boolean,
|
val hideLabels: Boolean,
|
||||||
|
val hideImages: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
class BaseNoteVH(
|
class BaseNoteVH(
|
||||||
|
@ -203,21 +203,15 @@ class BaseNoteVH(
|
||||||
|
|
||||||
private fun setColor(color: String) {
|
private fun setColor(color: String) {
|
||||||
binding.root.apply {
|
binding.root.apply {
|
||||||
if (color == BaseNote.COLOR_DEFAULT) {
|
val colorInt = context.extractColor(color)
|
||||||
setCardBackgroundColor(0)
|
setCardBackgroundColor(colorInt)
|
||||||
setControlsContrastColorForAllViews(context.getColorFromAttr(R.attr.colorSurface))
|
setControlsContrastColorForAllViews(colorInt)
|
||||||
} else {
|
|
||||||
val colorInt = context.extractColor(color)
|
|
||||||
setCardBackgroundColor(colorInt)
|
|
||||||
setControlsContrastColorForAllViews(colorInt)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setImages(images: List<FileAttachment>, mediaRoot: File?) {
|
private fun setImages(images: List<FileAttachment>, mediaRoot: File?) {
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
if (images.isNotEmpty()) {
|
if (images.isNotEmpty() && !preferences.hideImages) {
|
||||||
ImageView.visibility = VISIBLE
|
ImageView.visibility = VISIBLE
|
||||||
Message.visibility = GONE
|
Message.visibility = GONE
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
package com.philkes.notallyx.presentation.view.misc
|
package com.philkes.notallyx.presentation.view.misc
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.text.method.KeyListener
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.appcompat.widget.AppCompatEditText
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
import com.philkes.notallyx.presentation.clone
|
import com.philkes.notallyx.presentation.clone
|
||||||
|
import com.philkes.notallyx.presentation.showKeyboard
|
||||||
|
|
||||||
open class EditTextWithWatcher(context: Context, attrs: AttributeSet) :
|
open class EditTextWithWatcher(context: Context, attrs: AttributeSet) :
|
||||||
AppCompatEditText(context, attrs) {
|
AppCompatEditText(context, attrs) {
|
||||||
var textWatcher: TextWatcher? = null
|
var textWatcher: TextWatcher? = null
|
||||||
private var onSelectionChange: ((selStart: Int, selEnd: Int) -> Unit)? = null
|
private var onSelectionChange: ((selStart: Int, selEnd: Int) -> Unit)? = null
|
||||||
|
private var keyListenerInstance: KeyListener? = null
|
||||||
|
|
||||||
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
||||||
super.onSelectionChanged(selStart, selEnd)
|
super.onSelectionChanged(selStart, selEnd)
|
||||||
|
@ -30,33 +34,60 @@ open class EditTextWithWatcher(context: Context, attrs: AttributeSet) :
|
||||||
super.setText(text, BufferType.EDITABLE)
|
super.setText(text, BufferType.EDITABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCanEdit(value: Boolean) {
|
||||||
|
if (!value) {
|
||||||
|
clearFocus()
|
||||||
|
}
|
||||||
|
keyListener?.let { keyListenerInstance = it }
|
||||||
|
keyListener = if (value) keyListenerInstance else null // Disables text editing
|
||||||
|
isCursorVisible = true
|
||||||
|
isFocusable = value
|
||||||
|
isFocusableInTouchMode = value
|
||||||
|
setTextIsSelectable(true)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
|
||||||
|
setOnClickListener {
|
||||||
|
if (value) {
|
||||||
|
context.showKeyboard(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnFocusChangeListener { v, hasFocus ->
|
||||||
|
if (hasFocus && value) {
|
||||||
|
context.showKeyboard(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
"You should not access text Editable directly, use other member functions to edit/read text properties.",
|
"You should not access text Editable directly, use other member functions to edit/read text properties.",
|
||||||
replaceWith = ReplaceWith("changeText/applyWithoutTextWatcher/..."),
|
replaceWith = ReplaceWith("changeText/applyWithoutTextWatcher/..."),
|
||||||
)
|
)
|
||||||
override fun getText(): Editable? {
|
override fun getText(): Editable? {
|
||||||
return super.getText()
|
return getTextSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTextClone(): Editable {
|
fun getTextClone(): Editable {
|
||||||
return super.getText()!!.clone()
|
return getTextSafe().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyWithoutTextWatcher(
|
fun applyWithoutTextWatcher(
|
||||||
callback: EditTextWithWatcher.() -> Unit
|
callback: EditTextWithWatcher.() -> Unit
|
||||||
): Pair<Editable, Editable> {
|
): Pair<Editable, Editable> {
|
||||||
val textBefore = super.getText()!!.clone()
|
val textBefore = getTextClone()
|
||||||
val editTextWatcher = textWatcher
|
val editTextWatcher = textWatcher
|
||||||
editTextWatcher?.let { removeTextChangedListener(it) }
|
editTextWatcher?.let { removeTextChangedListener(it) }
|
||||||
callback()
|
callback()
|
||||||
editTextWatcher?.let { addTextChangedListener(it) }
|
editTextWatcher?.let { addTextChangedListener(it) }
|
||||||
return Pair(textBefore, super.getText()!!.clone())
|
return Pair(textBefore, getTextClone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeText(callback: (text: Editable) -> Unit): Pair<Editable, Editable> {
|
fun changeText(callback: (text: Editable) -> Unit): Pair<Editable, Editable> {
|
||||||
return applyWithoutTextWatcher { callback(super.getText()!!) }
|
return applyWithoutTextWatcher { callback(getTextSafe()!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getTextSafe() = super.getText() ?: Editable.Factory.getInstance().newEditable("")
|
||||||
|
|
||||||
fun focusAndSelect(
|
fun focusAndSelect(
|
||||||
start: Int = selectionStart,
|
start: Int = selectionStart,
|
||||||
end: Int = selectionEnd,
|
end: Int = selectionEnd,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.text.style.TypefaceSpan
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.presentation.createBoldSpan
|
||||||
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
||||||
|
|
||||||
class TextFormattingAdapter(
|
class TextFormattingAdapter(
|
||||||
|
@ -35,7 +36,7 @@ class TextFormattingAdapter(
|
||||||
private val bold: Toggle =
|
private val bold: Toggle =
|
||||||
Toggle(R.string.bold, R.drawable.format_bold, false) {
|
Toggle(R.string.bold, R.drawable.format_bold, false) {
|
||||||
if (!it.checked) {
|
if (!it.checked) {
|
||||||
editText.applySpan(StyleSpan(Typeface.BOLD))
|
editText.applySpan(createBoldSpan())
|
||||||
} else {
|
} else {
|
||||||
editText.clearFormatting(type = StylableEditTextWithHistory.TextStyleType.BOLD)
|
editText.clearFormatting(type = StylableEditTextWithHistory.TextStyleType.BOLD)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class MoreNoteBottomSheet(
|
||||||
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
|
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
callbacks.export(mimeType)
|
callbacks.export(mimeType)
|
||||||
fragment.hide()
|
fragment.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import com.philkes.notallyx.data.model.ListItem
|
||||||
import com.philkes.notallyx.data.model.deepCopy
|
import com.philkes.notallyx.data.model.deepCopy
|
||||||
import com.philkes.notallyx.data.model.findChild
|
import com.philkes.notallyx.data.model.findChild
|
||||||
import com.philkes.notallyx.data.model.plus
|
import com.philkes.notallyx.data.model.plus
|
||||||
|
import com.philkes.notallyx.data.model.shouldParentBeChecked
|
||||||
|
import com.philkes.notallyx.data.model.shouldParentBeUnchecked
|
||||||
import com.philkes.notallyx.utils.filter
|
import com.philkes.notallyx.utils.filter
|
||||||
import com.philkes.notallyx.utils.forEach
|
import com.philkes.notallyx.utils.forEach
|
||||||
import com.philkes.notallyx.utils.indices
|
import com.philkes.notallyx.utils.indices
|
||||||
|
@ -149,9 +151,20 @@ fun <R> List<R>.getOrNull(index: Int) = if (lastIndex >= index) this[index] else
|
||||||
fun Collection<ListItem>.init(resetIds: Boolean = true): List<ListItem> {
|
fun Collection<ListItem>.init(resetIds: Boolean = true): List<ListItem> {
|
||||||
val initializedItems = deepCopy()
|
val initializedItems = deepCopy()
|
||||||
initList(initializedItems, resetIds)
|
initList(initializedItems, resetIds)
|
||||||
|
checkBrokenList(initializedItems)
|
||||||
return initializedItems
|
return initializedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkBrokenList(list: List<ListItem>) {
|
||||||
|
list.forEach { listItem ->
|
||||||
|
if (listItem.shouldParentBeChecked()) {
|
||||||
|
listItem.checked = true
|
||||||
|
} else if (listItem.shouldParentBeUnchecked()) {
|
||||||
|
listItem.checked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initList(list: List<ListItem>, resetIds: Boolean) {
|
private fun initList(list: List<ListItem>, resetIds: Boolean) {
|
||||||
if (resetIds) {
|
if (resetIds) {
|
||||||
list.forEachIndexed { index, item -> item.id = index }
|
list.forEachIndexed { index, item -> item.id = index }
|
||||||
|
@ -250,7 +263,7 @@ fun SortedList<ListItem>.findParentsByChecked(checked: Boolean): List<ListItem>
|
||||||
fun SortedList<ListItem>.deleteCheckedItems() {
|
fun SortedList<ListItem>.deleteCheckedItems() {
|
||||||
mapIndexed { index, listItem -> Pair(index, listItem) }
|
mapIndexed { index, listItem -> Pair(index, listItem) }
|
||||||
.filter { it.second.checked }
|
.filter { it.second.checked }
|
||||||
.sortedBy { it.second.isChild }
|
.sortedBy { !it.second.isChild }
|
||||||
.forEach { remove(it.second) }
|
.forEach { remove(it.second) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.philkes.notallyx.presentation.view.note.listitem
|
package com.philkes.notallyx.presentation.view.note.listitem
|
||||||
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
@ -84,28 +83,28 @@ class ListManager(
|
||||||
|
|
||||||
internal fun setState(state: ListState) {
|
internal fun setState(state: ListState) {
|
||||||
adapter.submitList(state.items) {
|
adapter.submitList(state.items) {
|
||||||
// Focus item's EditText and set cursor position
|
state.focusedItemPos?.let { itemPos -> focusItem(itemPos, state.cursorPos) }
|
||||||
state.focusedItemPos?.let { itemPos ->
|
}
|
||||||
recyclerView.post {
|
this.itemsChecked?.setItems(state.checkedItems!!)
|
||||||
if (itemPos in 0..items.size) {
|
}
|
||||||
recyclerView.smoothScrollToPosition(itemPos)
|
|
||||||
(recyclerView.findViewHolderForAdapterPosition(itemPos) as? ListItemVH?)
|
private fun focusItem(itemPos: Int, cursorPos: Int?) {
|
||||||
?.let { viewHolder ->
|
// Focus item's EditText and set cursor position
|
||||||
inputMethodManager?.let { inputManager ->
|
recyclerView.post {
|
||||||
val maxCursorPos = viewHolder.binding.EditText.length()
|
if (itemPos in 0..items.size) {
|
||||||
viewHolder.focusEditText(
|
recyclerView.smoothScrollToPosition(itemPos)
|
||||||
selectionStart =
|
(recyclerView.findViewHolderForAdapterPosition(itemPos) as? ListItemVH?)?.let {
|
||||||
state.cursorPos?.coerceIn(0, maxCursorPos)
|
viewHolder ->
|
||||||
?: maxCursorPos,
|
inputMethodManager?.let { inputManager ->
|
||||||
inputMethodManager = inputManager,
|
val maxCursorPos = viewHolder.binding.EditText.length()
|
||||||
)
|
viewHolder.focusEditText(
|
||||||
}
|
selectionStart = cursorPos?.coerceIn(0, maxCursorPos) ?: maxCursorPos,
|
||||||
}
|
inputMethodManager = inputManager,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.itemsChecked?.setItems(state.checkedItems!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(
|
fun add(
|
||||||
|
@ -282,23 +281,15 @@ class ListManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeText(
|
fun changeText(position: Int, value: EditTextState, pushChange: Boolean = true) {
|
||||||
position: Int,
|
val stateBefore = getState()
|
||||||
value: EditTextState,
|
|
||||||
before: EditTextState? = null,
|
|
||||||
pushChange: Boolean = true,
|
|
||||||
editText: EditText?,
|
|
||||||
listener: TextWatcher?,
|
|
||||||
) {
|
|
||||||
// if(!pushChange) {
|
// if(!pushChange) {
|
||||||
endSearch?.invoke()
|
endSearch?.invoke()
|
||||||
// }
|
// }
|
||||||
val item = items[position]
|
val item = items[position]
|
||||||
item.body = value.text.toString()
|
item.body = value.text.toString()
|
||||||
if (pushChange) {
|
if (pushChange) {
|
||||||
changeHistory.push(
|
changeHistory.push(ListEditTextChange(stateBefore, getState(), this))
|
||||||
ListEditTextChange(editText!!, position, before!!, value, listener!!, this)
|
|
||||||
)
|
|
||||||
// TODO: fix focus change
|
// TODO: fix focus change
|
||||||
// refreshSearch?.invoke(editText)
|
// refreshSearch?.invoke(editText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.core.widget.NestedScrollView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SortedList
|
import androidx.recyclerview.widget.SortedList
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||||
|
@ -38,6 +39,12 @@ class CheckedListItemAdapter(
|
||||||
override fun getItem(position: Int): ListItem = list[position]
|
override fun getItem(position: Int): ListItem = list[position]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viewMode: NoteViewMode = NoteViewMode.EDIT
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun setList(list: SortedList<ListItem>) {
|
internal fun setList(list: SortedList<ListItem>) {
|
||||||
this.list = list
|
this.list = list
|
||||||
}
|
}
|
||||||
|
@ -51,7 +58,7 @@ class CheckedListItemAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ListItemVH, position: Int) {
|
override fun onBindViewHolder(holder: ListItemVH, position: Int) {
|
||||||
itemAdapterBase.onBindViewHolder(holder, position)
|
itemAdapterBase.onBindViewHolder(holder, position, viewMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
import com.philkes.notallyx.presentation.view.note.listitem.HighlightText
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||||
|
@ -37,6 +38,12 @@ class ListItemAdapter(
|
||||||
override fun getItem(position: Int): ListItem = this@ListItemAdapter.getItem(position)
|
override fun getItem(position: Int): ListItem = this@ListItemAdapter.getItem(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viewMode: NoteViewMode = NoteViewMode.EDIT
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var items: MutableList<ListItem>
|
lateinit var items: MutableList<ListItem>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -55,7 +62,7 @@ class ListItemAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ListItemVH, position: Int) {
|
override fun onBindViewHolder(holder: ListItemVH, position: Int) {
|
||||||
itemAdapterBase.onBindViewHolder(holder, position)
|
itemAdapterBase.onBindViewHolder(holder, position, viewMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.core.widget.NestedScrollView
|
||||||
import androidx.recyclerview.widget.NestedScrollViewItemTouchHelper
|
import androidx.recyclerview.widget.NestedScrollViewItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.databinding.RecyclerListItemBinding
|
import com.philkes.notallyx.databinding.RecyclerListItemBinding
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListItemDragCallback
|
import com.philkes.notallyx.presentation.view.note.listitem.ListItemDragCallback
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
||||||
|
@ -40,7 +41,7 @@ abstract class ListItemAdapterBase(
|
||||||
touchHelper.attachToRecyclerView(recyclerView)
|
touchHelper.attachToRecyclerView(recyclerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBindViewHolder(holder: ListItemVH, position: Int) {
|
fun onBindViewHolder(holder: ListItemVH, position: Int, viewMode: NoteViewMode) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
holder.bind(
|
holder.bind(
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
@ -48,6 +49,7 @@ abstract class ListItemAdapterBase(
|
||||||
position,
|
position,
|
||||||
highlights[position],
|
highlights[position],
|
||||||
preferences.listItemSorting.value,
|
preferences.listItemSorting.value,
|
||||||
|
viewMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package com.philkes.notallyx.presentation.view.note.listitem.adapter
|
package com.philkes.notallyx.presentation.view.note.listitem.adapter
|
||||||
|
|
||||||
|
import android.graphics.Paint
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener
|
import android.widget.CompoundButton.OnCheckedChangeListener
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
@ -18,6 +21,7 @@ import cn.leaqi.drawer.SwipeDrawer.STATE_OPEN
|
||||||
import com.philkes.notallyx.data.imports.txt.extractListItems
|
import com.philkes.notallyx.data.imports.txt.extractListItems
|
||||||
import com.philkes.notallyx.data.imports.txt.findListSyntaxRegex
|
import com.philkes.notallyx.data.imports.txt.findListSyntaxRegex
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.databinding.RecyclerListItemBinding
|
import com.philkes.notallyx.databinding.RecyclerListItemBinding
|
||||||
import com.philkes.notallyx.presentation.clone
|
import com.philkes.notallyx.presentation.clone
|
||||||
import com.philkes.notallyx.presentation.createListTextWatcherWithHistory
|
import com.philkes.notallyx.presentation.createListTextWatcherWithHistory
|
||||||
|
@ -29,6 +33,7 @@ import com.philkes.notallyx.presentation.view.note.listitem.firstBodyOrEmptyStri
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
|
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
|
||||||
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
|
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
|
||||||
import com.philkes.notallyx.utils.changehistory.EditTextState
|
import com.philkes.notallyx.utils.changehistory.EditTextState
|
||||||
|
import com.philkes.notallyx.utils.copyToClipBoard
|
||||||
|
|
||||||
class ListItemVH(
|
class ListItemVH(
|
||||||
val binding: RecyclerListItemBinding,
|
val binding: RecyclerListItemBinding,
|
||||||
|
@ -45,11 +50,6 @@ class ListItemVH(
|
||||||
binding.EditText.apply {
|
binding.EditText.apply {
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, body)
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, body)
|
||||||
|
|
||||||
setOnNextAction {
|
|
||||||
val position = bindingAdapterPosition + 1
|
|
||||||
listManager.add(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
textWatcher =
|
textWatcher =
|
||||||
createListTextWatcherWithHistory(
|
createListTextWatcherWithHistory(
|
||||||
listManager,
|
listManager,
|
||||||
|
@ -61,10 +61,6 @@ class ListItemVH(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnFocusChangeListener { _, hasFocus ->
|
|
||||||
binding.Delete.visibility = if (hasFocus) VISIBLE else INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.DragHandle.setOnTouchListener { _, event ->
|
binding.DragHandle.setOnTouchListener { _, event ->
|
||||||
|
@ -95,20 +91,21 @@ class ListItemVH(
|
||||||
position: Int,
|
position: Int,
|
||||||
highlights: List<ListItemHighlight>?,
|
highlights: List<ListItemHighlight>?,
|
||||||
autoSort: ListItemSort,
|
autoSort: ListItemSort,
|
||||||
|
viewMode: NoteViewMode,
|
||||||
) {
|
) {
|
||||||
updateEditText(item, position)
|
updateEditText(item, position, viewMode)
|
||||||
|
|
||||||
updateCheckBox(item, position)
|
updateCheckBox(item, position)
|
||||||
|
|
||||||
updateDeleteButton(item, position)
|
updateDeleteButton(item, position, viewMode)
|
||||||
|
|
||||||
updateSwipe(item.isChild, position != 0 && !item.checked)
|
updateSwipe(item.isChild, viewMode == NoteViewMode.EDIT && position != 0 && !item.checked)
|
||||||
binding.DragHandle.apply {
|
binding.DragHandle.apply {
|
||||||
visibility =
|
visibility =
|
||||||
if (item.checked && autoSort == ListItemSort.AUTO_SORT_BY_CHECKED) {
|
when {
|
||||||
INVISIBLE
|
viewMode != NoteViewMode.EDIT -> GONE
|
||||||
} else {
|
item.checked && autoSort == ListItemSort.AUTO_SORT_BY_CHECKED -> INVISIBLE
|
||||||
VISIBLE
|
else -> VISIBLE
|
||||||
}
|
}
|
||||||
contentDescription = "Drag$position"
|
contentDescription = "Drag$position"
|
||||||
}
|
}
|
||||||
|
@ -130,9 +127,14 @@ class ListItemVH(
|
||||||
binding.EditText.focusAndSelect(selectionStart, selectionEnd, inputMethodManager)
|
binding.EditText.focusAndSelect(selectionStart, selectionEnd, inputMethodManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDeleteButton(item: ListItem, position: Int) {
|
private fun updateDeleteButton(item: ListItem, position: Int, viewMode: NoteViewMode) {
|
||||||
binding.Delete.apply {
|
binding.Delete.apply {
|
||||||
visibility = if (item.checked) VISIBLE else INVISIBLE
|
visibility =
|
||||||
|
when {
|
||||||
|
viewMode != NoteViewMode.EDIT -> GONE
|
||||||
|
item.checked -> VISIBLE
|
||||||
|
else -> INVISIBLE
|
||||||
|
}
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
listManager.delete(absoluteAdapterPosition, inCheckedList = inCheckedList)
|
listManager.delete(absoluteAdapterPosition, inCheckedList = inCheckedList)
|
||||||
}
|
}
|
||||||
|
@ -140,10 +142,50 @@ class ListItemVH(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateEditText(item: ListItem, position: Int) {
|
private fun updateEditText(item: ListItem, position: Int, viewMode: NoteViewMode) {
|
||||||
binding.EditText.apply {
|
binding.EditText.apply {
|
||||||
setText(item.body)
|
setText(item.body)
|
||||||
isEnabled = !item.checked
|
paintFlags =
|
||||||
|
if (item.checked) {
|
||||||
|
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||||
|
} else {
|
||||||
|
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||||
|
}
|
||||||
|
alpha = if (item.checked) 0.5f else 1.0f
|
||||||
|
contentDescription = "EditText$position"
|
||||||
|
if (viewMode == NoteViewMode.EDIT) {
|
||||||
|
setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
binding.Delete.visibility = if (hasFocus) VISIBLE else INVISIBLE
|
||||||
|
}
|
||||||
|
binding.Content.descendantFocusability = ViewGroup.FOCUS_BEFORE_DESCENDANTS
|
||||||
|
} else {
|
||||||
|
onFocusChangeListener = null
|
||||||
|
binding.Content.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
||||||
|
}
|
||||||
|
setCanEdit(viewMode == NoteViewMode.EDIT)
|
||||||
|
isFocusable = !item.checked
|
||||||
|
when (viewMode) {
|
||||||
|
NoteViewMode.EDIT -> {
|
||||||
|
setOnClickListener(null)
|
||||||
|
setOnLongClickListener(null)
|
||||||
|
}
|
||||||
|
NoteViewMode.READ_ONLY -> {
|
||||||
|
setOnClickListener {
|
||||||
|
if (absoluteAdapterPosition != NO_POSITION) {
|
||||||
|
listManager.changeChecked(
|
||||||
|
absoluteAdapterPosition,
|
||||||
|
!item.checked,
|
||||||
|
inCheckedList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnLongClickListener {
|
||||||
|
context?.copyToClipBoard(item.body)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnNextAction { listManager.add(bindingAdapterPosition + 1) }
|
||||||
setOnKeyListener { _, keyCode, event ->
|
setOnKeyListener { _, keyCode, event ->
|
||||||
if (
|
if (
|
||||||
event.action == KeyEvent.ACTION_DOWN &&
|
event.action == KeyEvent.ACTION_DOWN &&
|
||||||
|
@ -162,7 +204,6 @@ class ListItemVH(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentDescription = "EditText$position"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,12 +211,12 @@ class ListItemVH(
|
||||||
|
|
||||||
private fun updateCheckBox(item: ListItem, position: Int) {
|
private fun updateCheckBox(item: ListItem, position: Int) {
|
||||||
if (checkBoxListener == null) {
|
if (checkBoxListener == null) {
|
||||||
checkBoxListener = OnCheckedChangeListener { buttonView, isChecked ->
|
checkBoxListener = OnCheckedChangeListener { _, isChecked ->
|
||||||
buttonView!!.setOnCheckedChangeListener(null)
|
binding.CheckBox.setOnCheckedChangeListener(null)
|
||||||
if (absoluteAdapterPosition != NO_POSITION) {
|
if (absoluteAdapterPosition != NO_POSITION) {
|
||||||
listManager.changeChecked(absoluteAdapterPosition, isChecked, inCheckedList)
|
listManager.changeChecked(absoluteAdapterPosition, isChecked, inCheckedList)
|
||||||
}
|
}
|
||||||
buttonView.setOnCheckedChangeListener(checkBoxListener)
|
binding.CheckBox.setOnCheckedChangeListener(checkBoxListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.CheckBox.apply {
|
binding.CheckBox.apply {
|
||||||
|
@ -235,13 +276,7 @@ class ListItemVH(
|
||||||
private fun EditText.changeText(position: Int, after: CharSequence) {
|
private fun EditText.changeText(position: Int, after: CharSequence) {
|
||||||
setText(after)
|
setText(after)
|
||||||
val stateAfter = EditTextState(editableText.clone(), selectionStart)
|
val stateAfter = EditTextState(editableText.clone(), selectionStart)
|
||||||
listManager.changeText(
|
listManager.changeText(position, stateAfter, pushChange = false)
|
||||||
position,
|
|
||||||
stateAfter,
|
|
||||||
pushChange = false,
|
|
||||||
editText = null,
|
|
||||||
listener = null,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelection() = with(binding.EditText) { Pair(selectionStart, selectionEnd) }
|
fun getSelection() = with(binding.EditText) { Pair(selectionStart, selectionEnd) }
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
@ -13,6 +12,7 @@ import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
@ -38,7 +38,9 @@ import com.philkes.notallyx.data.model.Item
|
||||||
import com.philkes.notallyx.data.model.Label
|
import com.philkes.notallyx.data.model.Label
|
||||||
import com.philkes.notallyx.data.model.SearchResult
|
import com.philkes.notallyx.data.model.SearchResult
|
||||||
import com.philkes.notallyx.data.model.toNoteIdReminders
|
import com.philkes.notallyx.data.model.toNoteIdReminders
|
||||||
|
import com.philkes.notallyx.presentation.activity.main.fragment.settings.SettingsFragment.Companion.EXTRA_SHOW_IMPORT_BACKUPS_FOLDER
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
|
import com.philkes.notallyx.presentation.restartApplication
|
||||||
import com.philkes.notallyx.presentation.setCancelButton
|
import com.philkes.notallyx.presentation.setCancelButton
|
||||||
import com.philkes.notallyx.presentation.showToast
|
import com.philkes.notallyx.presentation.showToast
|
||||||
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
||||||
|
@ -114,6 +116,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
|
|
||||||
private val pinned = Header(app.getString(R.string.pinned))
|
private val pinned = Header(app.getString(R.string.pinned))
|
||||||
private val others = Header(app.getString(R.string.others))
|
private val others = Header(app.getString(R.string.others))
|
||||||
|
private val archived = Header(app.getString(R.string.archived))
|
||||||
|
|
||||||
val preferences = NotallyXPreferences.getInstance(app)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
|
||||||
|
@ -126,6 +129,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
val actionMode = ActionMode()
|
val actionMode = ActionMode()
|
||||||
|
|
||||||
internal var showRefreshBackupsFolderAfterThemeChange = false
|
internal var showRefreshBackupsFolderAfterThemeChange = false
|
||||||
|
private var labelsHiddenObserver: Observer<Set<String>>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
NotallyDatabase.getDatabase(app).observeForever(::init)
|
NotallyDatabase.getDatabase(app).observeForever(::init)
|
||||||
|
@ -149,11 +153,12 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
allNotes = baseNoteDao.getAllAsync()
|
allNotes = baseNoteDao.getAllAsync()
|
||||||
allNotes!!.observeForever(allNotesObserver!!)
|
allNotes!!.observeForever(allNotesObserver!!)
|
||||||
|
|
||||||
if (baseNotes == null) {
|
labelsHiddenObserver?.let { preferences.labelsHidden.removeObserver(it) }
|
||||||
baseNotes = Content(baseNoteDao.getFrom(Folder.NOTES), ::transform)
|
labelsHiddenObserver = Observer { labelsHidden ->
|
||||||
} else {
|
baseNotes = null
|
||||||
baseNotes!!.setObserver(baseNoteDao.getFrom(Folder.NOTES))
|
initBaseNotes(labelsHidden)
|
||||||
}
|
}
|
||||||
|
preferences.labelsHidden.observeForever(labelsHiddenObserver!!)
|
||||||
|
|
||||||
if (deletedNotes == null) {
|
if (deletedNotes == null) {
|
||||||
deletedNotes = Content(baseNoteDao.getFrom(Folder.DELETED), ::transform)
|
deletedNotes = Content(baseNoteDao.getFrom(Folder.DELETED), ::transform)
|
||||||
|
@ -187,6 +192,18 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initBaseNotes(labelsHidden: Set<String>) {
|
||||||
|
val overviewNotes =
|
||||||
|
baseNoteDao.getFrom(Folder.NOTES).map { list ->
|
||||||
|
list.filter { baseNote -> baseNote.labels.none { labelsHidden.contains(it) } }
|
||||||
|
}
|
||||||
|
if (baseNotes == null) {
|
||||||
|
baseNotes = Content(overviewNotes, ::transform)
|
||||||
|
} else {
|
||||||
|
baseNotes!!.setObserver(overviewNotes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getNotesByLabel(label: String): Content {
|
fun getNotesByLabel(label: String): Content {
|
||||||
if (labelCache[label] == null) {
|
if (labelCache[label] == null) {
|
||||||
labelCache[label] = Content(baseNoteDao.getBaseNotesByLabel(label), ::transform)
|
labelCache[label] = Content(baseNoteDao.getBaseNotesByLabel(label), ::transform)
|
||||||
|
@ -198,7 +215,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
return Content(baseNoteDao.getBaseNotesWithoutLabel(Folder.NOTES), ::transform)
|
return Content(baseNoteDao.getBaseNotesWithoutLabel(Folder.NOTES), ::transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transform(list: List<BaseNote>) = transform(list, pinned, others)
|
private fun transform(list: List<BaseNote>) = transform(list, pinned, others, archived)
|
||||||
|
|
||||||
fun disableBackups() {
|
fun disableBackups() {
|
||||||
val value = preferences.backupsFolder.value
|
val value = preferences.backupsFolder.value
|
||||||
|
@ -318,7 +335,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
fun importZipBackup(uri: Uri, password: String) {
|
fun importZipBackup(uri: Uri, password: String) {
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
app.showToast(R.string.invalid_backup)
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupDir = app.getBackupDir()
|
val backupDir = app.getBackupDir()
|
||||||
|
@ -330,7 +347,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
fun importXmlBackup(uri: Uri) {
|
fun importXmlBackup(uri: Uri) {
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
app.showToast(R.string.invalid_backup)
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(exceptionHandler) {
|
viewModelScope.launch(exceptionHandler) {
|
||||||
|
@ -348,18 +365,15 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
|
|
||||||
fun importFromOtherApp(uri: Uri, importSource: ImportSource) {
|
fun importFromOtherApp(uri: Uri, importSource: ImportSource) {
|
||||||
val database = NotallyDatabase.getDatabase(app, observePreferences = false).value
|
val database = NotallyDatabase.getDatabase(app, observePreferences = false).value
|
||||||
|
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
if (throwable is ImportException) {
|
|
||||||
throwable.textResId
|
|
||||||
} else R.string.invalid_backup,
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
|
if (throwable is ImportException) {
|
||||||
|
app.showToast(throwable.textResId)
|
||||||
|
} else {
|
||||||
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(exceptionHandler) {
|
viewModelScope.launch(exceptionHandler) {
|
||||||
val importedNotes =
|
val importedNotes =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -536,7 +550,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
|
|
||||||
fun deleteLabel(value: String) {
|
fun deleteLabel(value: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) { commonDao.deleteLabel(value) }
|
viewModelScope.launch(Dispatchers.IO) { commonDao.deleteLabel(value) }
|
||||||
val labelsHiddenPreference = preferences.labelsHiddenInNavigation
|
val labelsHiddenPreference = preferences.labelsHidden
|
||||||
val labelsHidden = labelsHiddenPreference.value.toMutableSet()
|
val labelsHidden = labelsHiddenPreference.value.toMutableSet()
|
||||||
if (labelsHidden.contains(value)) {
|
if (labelsHidden.contains(value)) {
|
||||||
labelsHidden.remove(value)
|
labelsHidden.remove(value)
|
||||||
|
@ -552,7 +566,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
|
|
||||||
fun updateLabel(oldValue: String, newValue: String, onComplete: (success: Boolean) -> Unit) {
|
fun updateLabel(oldValue: String, newValue: String, onComplete: (success: Boolean) -> Unit) {
|
||||||
executeAsyncWithCallback({ commonDao.updateLabel(oldValue, newValue) }, onComplete)
|
executeAsyncWithCallback({ commonDao.updateLabel(oldValue, newValue) }, onComplete)
|
||||||
val labelsHiddenPreference = preferences.labelsHiddenInNavigation
|
val labelsHiddenPreference = preferences.labelsHidden
|
||||||
val labelsHidden = labelsHiddenPreference.value.toMutableSet()
|
val labelsHidden = labelsHiddenPreference.value.toMutableSet()
|
||||||
if (labelsHidden.contains(oldValue)) {
|
if (labelsHidden.contains(oldValue)) {
|
||||||
labelsHidden.remove(oldValue)
|
labelsHidden.remove(oldValue)
|
||||||
|
@ -595,6 +609,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
clearPersistedUriPermissions(backupsFolder)
|
clearPersistedUriPermissions(backupsFolder)
|
||||||
}
|
}
|
||||||
callback()
|
callback()
|
||||||
|
app.restartApplication(R.id.Settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importPreferences(
|
fun importPreferences(
|
||||||
|
@ -607,6 +622,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
val oldBackupsFolder = preferences.backupsFolder.value
|
val oldBackupsFolder = preferences.backupsFolder.value
|
||||||
val dataInPublicFolderBefore = preferences.dataInPublicFolder.value
|
val dataInPublicFolderBefore = preferences.dataInPublicFolder.value
|
||||||
val themeBefore = preferences.theme.value
|
val themeBefore = preferences.theme.value
|
||||||
|
val useDynamicColorsBefore = preferences.useDynamicColors.value
|
||||||
val oldStartView = preferences.startView.value
|
val oldStartView = preferences.startView.value
|
||||||
|
|
||||||
val success = preferences.import(context, uri)
|
val success = preferences.import(context, uri)
|
||||||
|
@ -618,6 +634,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
finishImportPreferences(
|
finishImportPreferences(
|
||||||
oldBackupsFolder,
|
oldBackupsFolder,
|
||||||
themeBefore,
|
themeBefore,
|
||||||
|
useDynamicColorsBefore,
|
||||||
oldStartView,
|
oldStartView,
|
||||||
context,
|
context,
|
||||||
askForUriPermissions,
|
askForUriPermissions,
|
||||||
|
@ -631,6 +648,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
finishImportPreferences(
|
finishImportPreferences(
|
||||||
oldBackupsFolder,
|
oldBackupsFolder,
|
||||||
themeBefore,
|
themeBefore,
|
||||||
|
useDynamicColorsBefore,
|
||||||
oldStartView,
|
oldStartView,
|
||||||
context,
|
context,
|
||||||
askForUriPermissions,
|
askForUriPermissions,
|
||||||
|
@ -644,15 +662,18 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
private fun finishImportPreferences(
|
private fun finishImportPreferences(
|
||||||
oldBackupsFolder: String,
|
oldBackupsFolder: String,
|
||||||
themeBefore: Theme,
|
themeBefore: Theme,
|
||||||
|
useDynamicColorsBefore: Boolean,
|
||||||
oldStartView: String,
|
oldStartView: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
askForUriPermissions: (uri: Uri) -> Unit,
|
askForUriPermissions: (uri: Uri) -> Unit,
|
||||||
callback: () -> Unit,
|
callback: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val backupFolder = preferences.backupsFolder.getFreshValue()
|
val backupFolder = preferences.backupsFolder.getFreshValue()
|
||||||
|
val hasUseDynamicColorsChange =
|
||||||
|
useDynamicColorsBefore != preferences.useDynamicColors.getFreshValue()
|
||||||
if (oldBackupsFolder != backupFolder) {
|
if (oldBackupsFolder != backupFolder) {
|
||||||
showRefreshBackupsFolderAfterThemeChange = true
|
showRefreshBackupsFolderAfterThemeChange = true
|
||||||
if (themeBefore == preferences.theme.getFreshValue()) {
|
if (themeBefore == preferences.theme.getFreshValue() && !hasUseDynamicColorsChange) {
|
||||||
refreshBackupsFolder(context, backupFolder, askForUriPermissions)
|
refreshBackupsFolder(context, backupFolder, askForUriPermissions)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -664,6 +685,9 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
}
|
}
|
||||||
preferences.theme.refresh()
|
preferences.theme.refresh()
|
||||||
callback()
|
callback()
|
||||||
|
if (showRefreshBackupsFolderAfterThemeChange) {
|
||||||
|
app.restartApplication(R.id.Settings, EXTRA_SHOW_IMPORT_BACKUPS_FOLDER to true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshBackupsFolder(
|
fun refreshBackupsFolder(
|
||||||
|
@ -716,24 +740,35 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
const val CURRENT_LABEL_EMPTY = ""
|
const val CURRENT_LABEL_EMPTY = ""
|
||||||
val CURRENT_LABEL_NONE: String? = null
|
val CURRENT_LABEL_NONE: String? = null
|
||||||
|
|
||||||
fun transform(list: List<BaseNote>, pinned: Header, others: Header): List<Item> {
|
fun transform(
|
||||||
|
list: List<BaseNote>,
|
||||||
|
pinned: Header,
|
||||||
|
others: Header,
|
||||||
|
archived: Header,
|
||||||
|
): List<Item> {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return list
|
return list
|
||||||
} else {
|
} else {
|
||||||
val firstNote = list[0]
|
val firstPinnedNote = list.indexOfFirst { baseNote -> baseNote.pinned }
|
||||||
return if (firstNote.pinned) {
|
val firstUnpinnedNote =
|
||||||
val newList = ArrayList<Item>(list.size + 2)
|
list.indexOfFirst { baseNote ->
|
||||||
newList.add(pinned)
|
!baseNote.pinned && baseNote.folder != Folder.ARCHIVED
|
||||||
|
|
||||||
val firstUnpinnedNote = list.indexOfFirst { baseNote -> !baseNote.pinned }
|
|
||||||
list.forEachIndexed { index, baseNote ->
|
|
||||||
if (index == firstUnpinnedNote) {
|
|
||||||
newList.add(others)
|
|
||||||
}
|
|
||||||
newList.add(baseNote)
|
|
||||||
}
|
}
|
||||||
newList
|
val mutableList: MutableList<Item> = list.toMutableList()
|
||||||
} else list
|
if (firstPinnedNote != -1) {
|
||||||
|
mutableList.add(firstPinnedNote, pinned)
|
||||||
|
if (firstUnpinnedNote != -1) {
|
||||||
|
mutableList.add(firstUnpinnedNote + 1, others)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val firstArchivedNote =
|
||||||
|
mutableList.indexOfFirst { item ->
|
||||||
|
item is BaseNote && item.folder == Folder.ARCHIVED
|
||||||
|
}
|
||||||
|
if (firstArchivedNote != -1) {
|
||||||
|
mutableList.add(firstArchivedNote, archived)
|
||||||
|
}
|
||||||
|
return mutableList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,14 @@ import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.NotallyDatabase
|
import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.dao.BaseNoteDao
|
import com.philkes.notallyx.data.dao.BaseNoteDao
|
||||||
import com.philkes.notallyx.data.dao.NoteIdReminder
|
import com.philkes.notallyx.data.dao.NoteIdReminder
|
||||||
|
import com.philkes.notallyx.data.imports.txt.extractListItems
|
||||||
|
import com.philkes.notallyx.data.imports.txt.findListSyntaxRegex
|
||||||
import com.philkes.notallyx.data.model.Audio
|
import com.philkes.notallyx.data.model.Audio
|
||||||
import com.philkes.notallyx.data.model.BaseNote
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Reminder
|
import com.philkes.notallyx.data.model.Reminder
|
||||||
import com.philkes.notallyx.data.model.SpanRepresentation
|
import com.philkes.notallyx.data.model.SpanRepresentation
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
|
@ -84,10 +87,13 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
var body: Editable = SpannableStringBuilder()
|
var body: Editable = SpannableStringBuilder()
|
||||||
|
|
||||||
val items = ArrayList<ListItem>()
|
val items = ArrayList<ListItem>()
|
||||||
|
|
||||||
val images = NotNullLiveData<List<FileAttachment>>(emptyList())
|
val images = NotNullLiveData<List<FileAttachment>>(emptyList())
|
||||||
val files = NotNullLiveData<List<FileAttachment>>(emptyList())
|
val files = NotNullLiveData<List<FileAttachment>>(emptyList())
|
||||||
val audios = NotNullLiveData<List<Audio>>(emptyList())
|
val audios = NotNullLiveData<List<Audio>>(emptyList())
|
||||||
|
|
||||||
val reminders = NotNullLiveData<List<Reminder>>(emptyList())
|
val reminders = NotNullLiveData<List<Reminder>>(emptyList())
|
||||||
|
val viewMode = NotNullLiveData(NoteViewMode.EDIT)
|
||||||
|
|
||||||
val addingFiles = MutableLiveData<Progress>()
|
val addingFiles = MutableLiveData<Progress>()
|
||||||
val eventBus = MutableLiveData<Event<List<FileError>>>()
|
val eventBus = MutableLiveData<Event<List<FileError>>>()
|
||||||
|
@ -246,6 +252,7 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
files.value = baseNote.files
|
files.value = baseNote.files
|
||||||
audios.value = baseNote.audios
|
audios.value = baseNote.audios
|
||||||
reminders.value = baseNote.reminders
|
reminders.value = baseNote.reminders
|
||||||
|
viewMode.value = baseNote.viewMode
|
||||||
} else {
|
} else {
|
||||||
originalNote = createBaseNote()
|
originalNote = createBaseNote()
|
||||||
app.showToast(R.string.cant_find_note)
|
app.showToast(R.string.cant_find_note)
|
||||||
|
@ -343,6 +350,7 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
files.value,
|
files.value,
|
||||||
audios.value,
|
audios.value,
|
||||||
reminders.value,
|
reminders.value,
|
||||||
|
viewMode.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +449,34 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
withContext(Dispatchers.IO) { baseNoteDao.updateReminders(id, updatedReminders) }
|
withContext(Dispatchers.IO) { baseNoteDao.updateReminders(id, updatedReminders) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun convertTo(noteType: Type) {
|
||||||
|
when (noteType) {
|
||||||
|
Type.NOTE -> {
|
||||||
|
body = SpannableStringBuilder(items.joinToString(separator = "\n") { it.body })
|
||||||
|
type = Type.NOTE
|
||||||
|
setItems(ArrayList())
|
||||||
|
}
|
||||||
|
Type.LIST -> {
|
||||||
|
val text = body.toString()
|
||||||
|
val listSyntaxRegex =
|
||||||
|
text.findListSyntaxRegex(checkContains = true, plainNewLineAllowed = true)
|
||||||
|
if (listSyntaxRegex != null) {
|
||||||
|
setItems(text.extractListItems(listSyntaxRegex))
|
||||||
|
} else {
|
||||||
|
setItems(
|
||||||
|
text.lines().mapIndexed { idx, itemText ->
|
||||||
|
ListItem(itemText, false, false, idx, mutableListOf())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type = Type.LIST
|
||||||
|
body = SpannableStringBuilder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cache.list = ArrayList()
|
||||||
|
saveNote(checkBackupOnSave = false)
|
||||||
|
}
|
||||||
|
|
||||||
enum class FileType {
|
enum class FileType {
|
||||||
IMAGE,
|
IMAGE,
|
||||||
ANY,
|
ANY,
|
||||||
|
|
|
@ -29,6 +29,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val theme = createEnumPreference(preferences, "theme", Theme.FOLLOW_SYSTEM, R.string.theme)
|
val theme = createEnumPreference(preferences, "theme", Theme.FOLLOW_SYSTEM, R.string.theme)
|
||||||
|
val useDynamicColors = BooleanPreference("useDynamicColors", preferences, false)
|
||||||
val textSize =
|
val textSize =
|
||||||
createEnumPreference(preferences, "textSize", TextSize.MEDIUM, R.string.text_size)
|
createEnumPreference(preferences, "textSize", TextSize.MEDIUM, R.string.text_size)
|
||||||
val dateFormat =
|
val dateFormat =
|
||||||
|
@ -75,15 +76,21 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
10,
|
10,
|
||||||
R.string.max_lines_to_display_title,
|
R.string.max_lines_to_display_title,
|
||||||
)
|
)
|
||||||
val labelsHiddenInNavigation =
|
val labelsHidden = StringSetPreference("labelsHiddenInNavigation", preferences, setOf())
|
||||||
StringSetPreference("labelsHiddenInNavigation", preferences, setOf())
|
val labelTagsHiddenInOverview =
|
||||||
val labelsHiddenInOverview =
|
|
||||||
BooleanPreference(
|
BooleanPreference(
|
||||||
"labelsHiddenInOverview",
|
"labelsHiddenInOverview",
|
||||||
preferences,
|
preferences,
|
||||||
false,
|
false,
|
||||||
R.string.labels_hidden_in_overview_title,
|
R.string.labels_hidden_in_overview_title,
|
||||||
)
|
)
|
||||||
|
val imagesHiddenInOverview =
|
||||||
|
BooleanPreference(
|
||||||
|
"imagesHiddenInOverview",
|
||||||
|
preferences,
|
||||||
|
false,
|
||||||
|
R.string.images_hidden_in_overview_title,
|
||||||
|
)
|
||||||
val maxLabels =
|
val maxLabels =
|
||||||
IntPreference(
|
IntPreference(
|
||||||
"maxLabelsInNavigation",
|
"maxLabelsInNavigation",
|
||||||
|
@ -135,6 +142,8 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
val fallbackDatabaseEncryptionKey by lazy {
|
val fallbackDatabaseEncryptionKey by lazy {
|
||||||
ByteArrayPreference("fallback_database_encryption_key", encryptedPreferences, ByteArray(0))
|
ByteArrayPreference("fallback_database_encryption_key", encryptedPreferences, ByteArray(0))
|
||||||
}
|
}
|
||||||
|
val secureFlag =
|
||||||
|
BooleanPreference("secureFlag", preferences, false, R.string.disallow_screenshots)
|
||||||
|
|
||||||
val dataInPublicFolder =
|
val dataInPublicFolder =
|
||||||
BooleanPreference("dataOnExternalStorage", preferences, false, R.string.data_in_public)
|
BooleanPreference("dataOnExternalStorage", preferences, false, R.string.data_in_public)
|
||||||
|
@ -203,7 +212,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
context.importPreferences(uri, preferences.edit()).also { reload() }
|
context.importPreferences(uri, preferences.edit()).also { reload() }
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
preferences.edit().clear().apply()
|
preferences.edit().clear().commit()
|
||||||
encryptedPreferences.edit().clear().apply()
|
encryptedPreferences.edit().clear().apply()
|
||||||
backupsFolder.refresh()
|
backupsFolder.refresh()
|
||||||
dataInPublicFolder.refresh()
|
dataInPublicFolder.refresh()
|
||||||
|
@ -223,13 +232,15 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
maxItems,
|
maxItems,
|
||||||
maxLines,
|
maxLines,
|
||||||
maxTitle,
|
maxTitle,
|
||||||
labelsHiddenInNavigation,
|
secureFlag,
|
||||||
labelsHiddenInOverview,
|
labelsHidden,
|
||||||
|
labelTagsHiddenInOverview,
|
||||||
maxLabels,
|
maxLabels,
|
||||||
periodicBackups,
|
periodicBackups,
|
||||||
backupPassword,
|
backupPassword,
|
||||||
backupOnSave,
|
backupOnSave,
|
||||||
autoSaveAfterIdleTime,
|
autoSaveAfterIdleTime,
|
||||||
|
imagesHiddenInOverview,
|
||||||
)
|
)
|
||||||
.forEach { it.refresh() }
|
.forEach { it.refresh() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.philkes.notallyx.NotallyXApplication
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.NotallyDatabase
|
import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.dao.BaseNoteDao
|
import com.philkes.notallyx.data.dao.BaseNoteDao
|
||||||
|
@ -42,9 +43,10 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
super.onReceive(context, intent)
|
super.onReceive(context, intent)
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_NOTES_MODIFIED -> {
|
ACTION_NOTES_MODIFIED -> {
|
||||||
|
val app = context.applicationContext as NotallyXApplication
|
||||||
val noteIds = intent.getLongArrayExtra(EXTRA_MODIFIED_NOTES)
|
val noteIds = intent.getLongArrayExtra(EXTRA_MODIFIED_NOTES)
|
||||||
if (noteIds != null) {
|
if (noteIds != null) {
|
||||||
updateWidgets(context, noteIds)
|
updateWidgets(context, noteIds, locked = app.locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_OPEN_NOTE -> openActivity(context, intent, EditNoteActivity::class.java)
|
ACTION_OPEN_NOTE -> openActivity(context, intent, EditNoteActivity::class.java)
|
||||||
|
@ -85,7 +87,8 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
baseNoteDao.updateChecked(noteId, childrenPositions + position, checked!!)
|
baseNoteDao.updateChecked(noteId, childrenPositions + position, checked!!)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
updateWidgets(context, longArrayOf(noteId))
|
val app = context.applicationContext as NotallyXApplication
|
||||||
|
updateWidgets(context, longArrayOf(noteId), locked = app.locked.value)
|
||||||
pendingResult.finish()
|
pendingResult.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,19 +138,19 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray,
|
appWidgetIds: IntArray,
|
||||||
) {
|
) {
|
||||||
val app = context.applicationContext as Application
|
val app = context.applicationContext as NotallyXApplication
|
||||||
val preferences = NotallyXPreferences.getInstance(app)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
|
||||||
appWidgetIds.forEach { id ->
|
appWidgetIds.forEach { id ->
|
||||||
val noteId = preferences.getWidgetData(id)
|
val noteId = preferences.getWidgetData(id)
|
||||||
val noteType = preferences.getWidgetNoteType(id) ?: return
|
val noteType = preferences.getWidgetNoteType(id) ?: return
|
||||||
updateWidget(app, appWidgetManager, id, noteId, noteType)
|
updateWidget(app, appWidgetManager, id, noteId, noteType, locked = app.locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean = false) {
|
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean) {
|
||||||
val app = context.applicationContext as Application
|
val app = context.applicationContext as Application
|
||||||
val preferences = NotallyXPreferences.getInstance(app)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
|
||||||
|
@ -181,65 +184,90 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
||||||
intent.embedIntentExtras()
|
intent.embedIntentExtras()
|
||||||
|
|
||||||
if (!locked) {
|
MainScope().launch {
|
||||||
val database = NotallyDatabase.getDatabase(context).value
|
val database = NotallyDatabase.getDatabase(context).value
|
||||||
MainScope().launch {
|
val color =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) { database.getBaseNoteDao().getColorOfNote(noteId) }
|
||||||
val color = database.getBaseNoteDao().getColorOfNote(noteId)
|
if (color == null) {
|
||||||
val preferences = NotallyXPreferences.getInstance(context)
|
val app = context.applicationContext as Application
|
||||||
val (backgroundColor, _) = context.extractWidgetColors(color, preferences)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
val view =
|
preferences.deleteWidget(id)
|
||||||
RemoteViews(context.packageName, R.layout.widget).apply {
|
val view =
|
||||||
setRemoteAdapter(R.id.ListView, intent)
|
RemoteViews(context.packageName, R.layout.widget).apply {
|
||||||
setEmptyView(R.id.ListView, R.id.Empty)
|
setRemoteAdapter(R.id.ListView, intent)
|
||||||
setOnClickPendingIntent(
|
setEmptyView(R.id.ListView, R.id.Empty)
|
||||||
R.id.Empty,
|
setOnClickPendingIntent(
|
||||||
Intent(context, WidgetProvider::class.java)
|
R.id.Empty,
|
||||||
.apply {
|
Intent(context, WidgetProvider::class.java)
|
||||||
action = ACTION_SELECT_NOTE
|
.apply {
|
||||||
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
action = ACTION_SELECT_NOTE
|
||||||
}
|
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||||
.asPendingIntent(context),
|
}
|
||||||
)
|
.asPendingIntent(context),
|
||||||
setPendingIntentTemplate(
|
)
|
||||||
R.id.ListView,
|
setPendingIntentTemplate(
|
||||||
Intent(context, WidgetProvider::class.java)
|
R.id.ListView,
|
||||||
.asPendingIntent(context),
|
Intent(context, WidgetProvider::class.java).asPendingIntent(context),
|
||||||
)
|
)
|
||||||
|
|
||||||
noteType?.let {
|
|
||||||
setOnClickPendingIntent(
|
|
||||||
R.id.Layout,
|
|
||||||
Intent(context, WidgetProvider::class.java)
|
|
||||||
.setOpenNoteIntent(noteType, noteId)
|
|
||||||
.asPendingIntent(context),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setInt(R.id.Layout, "setBackgroundColor", backgroundColor)
|
|
||||||
}
|
|
||||||
manager.updateAppWidget(id, view)
|
|
||||||
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val view =
|
|
||||||
RemoteViews(context.packageName, R.layout.widget_locked).apply {
|
|
||||||
noteType?.let {
|
|
||||||
val lockedPendingIntent =
|
|
||||||
context.getOpenNotePendingIntent(noteId, noteType)
|
|
||||||
setOnClickPendingIntent(R.id.Layout, lockedPendingIntent)
|
|
||||||
setOnClickPendingIntent(R.id.Text, lockedPendingIntent)
|
|
||||||
}
|
}
|
||||||
setTextViewCompoundDrawablesRelative(
|
|
||||||
R.id.Text,
|
manager.updateAppWidget(id, view)
|
||||||
0,
|
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
||||||
R.drawable.lock_big,
|
return@launch
|
||||||
0,
|
}
|
||||||
0,
|
if (!locked) {
|
||||||
)
|
val view =
|
||||||
}
|
RemoteViews(context.packageName, R.layout.widget).apply {
|
||||||
manager.updateAppWidget(id, view)
|
setRemoteAdapter(R.id.ListView, intent)
|
||||||
|
setEmptyView(R.id.ListView, R.id.Empty)
|
||||||
|
setOnClickPendingIntent(
|
||||||
|
R.id.Empty,
|
||||||
|
Intent(context, WidgetProvider::class.java)
|
||||||
|
.apply {
|
||||||
|
action = ACTION_SELECT_NOTE
|
||||||
|
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
}
|
||||||
|
.asPendingIntent(context),
|
||||||
|
)
|
||||||
|
setPendingIntentTemplate(
|
||||||
|
R.id.ListView,
|
||||||
|
Intent(context, WidgetProvider::class.java).asPendingIntent(context),
|
||||||
|
)
|
||||||
|
|
||||||
|
val preferences = NotallyXPreferences.getInstance(context)
|
||||||
|
val (backgroundColor, _) =
|
||||||
|
context.extractWidgetColors(color, preferences)
|
||||||
|
noteType?.let {
|
||||||
|
setOnClickPendingIntent(
|
||||||
|
R.id.Layout,
|
||||||
|
Intent(context, WidgetProvider::class.java)
|
||||||
|
.setOpenNoteIntent(noteType, noteId)
|
||||||
|
.asPendingIntent(context),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setInt(R.id.Layout, "setBackgroundColor", backgroundColor)
|
||||||
|
}
|
||||||
|
manager.updateAppWidget(id, view)
|
||||||
|
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
||||||
|
} else {
|
||||||
|
val view =
|
||||||
|
RemoteViews(context.packageName, R.layout.widget_locked).apply {
|
||||||
|
noteType?.let {
|
||||||
|
val lockedPendingIntent =
|
||||||
|
context.getOpenNotePendingIntent(noteId, noteType)
|
||||||
|
setOnClickPendingIntent(R.id.Layout, lockedPendingIntent)
|
||||||
|
setOnClickPendingIntent(R.id.Text, lockedPendingIntent)
|
||||||
|
}
|
||||||
|
setTextViewCompoundDrawablesRelative(
|
||||||
|
R.id.Text,
|
||||||
|
0,
|
||||||
|
R.drawable.lock_big,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
manager.updateAppWidget(id, view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,13 @@ class ActionMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSelected(availableItemIds: List<Long>?) {
|
||||||
|
selectedNotes.keys
|
||||||
|
.filter { availableItemIds?.contains(it) == false }
|
||||||
|
.forEach { selectedNotes.remove(it) }
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun isEnabled() = enabled.value
|
fun isEnabled() = enabled.value
|
||||||
|
|
||||||
// We assume selectedNotes.size is 1
|
// We assume selectedNotes.size is 1
|
||||||
|
|
|
@ -80,7 +80,7 @@ private fun Context.createReminderAlarmIntent(noteId: Long, reminderId: Long): P
|
||||||
intent.putExtra(ReminderReceiver.EXTRA_NOTE_ID, noteId)
|
intent.putExtra(ReminderReceiver.EXTRA_NOTE_ID, noteId)
|
||||||
return PendingIntent.getBroadcast(
|
return PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
0,
|
(noteId.toString() + reminderId.toString()).toInt(),
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.philkes.notallyx.utils
|
package com.philkes.notallyx.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
@ -12,11 +11,11 @@ import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.hardware.biometrics.BiometricManager
|
import android.hardware.biometrics.BiometricManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
|
@ -33,16 +32,19 @@ import com.philkes.notallyx.BuildConfig
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.model.BaseNote
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
|
import com.philkes.notallyx.data.model.toText
|
||||||
import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_SELECTED_BASE_NOTE
|
import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_SELECTED_BASE_NOTE
|
||||||
import com.philkes.notallyx.presentation.activity.note.EditListActivity
|
import com.philkes.notallyx.presentation.activity.note.EditListActivity
|
||||||
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
|
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
|
||||||
import com.philkes.notallyx.presentation.showToast
|
import com.philkes.notallyx.presentation.showToast
|
||||||
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
|
||||||
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStreamReader
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
import java.lang.UnsupportedOperationException
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -96,7 +98,7 @@ fun ClipboardManager.getLatestText(): CharSequence? {
|
||||||
return primaryClip?.let { if (it.itemCount > 0) it.getItemAt(0)!!.text else null }
|
return primaryClip?.let { if (it.itemCount > 0) it.getItemAt(0)!!.text else null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.copyToClipBoard(text: CharSequence) {
|
fun Context.copyToClipBoard(text: CharSequence) {
|
||||||
ContextCompat.getSystemService(this, ClipboardManager::class.java)?.let {
|
ContextCompat.getSystemService(this, ClipboardManager::class.java)?.let {
|
||||||
val clip = ClipData.newPlainText("label", text)
|
val clip = ClipData.newPlainText("label", text)
|
||||||
it.setPrimaryClip(clip)
|
it.setPrimaryClip(clip)
|
||||||
|
@ -121,30 +123,15 @@ fun Context.getFileName(uri: Uri): String? =
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.canAuthenticateWithBiometrics(): Int {
|
fun Context.canAuthenticateWithBiometrics(): Int {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val biometricManager = androidx.biometric.BiometricManager.from(this)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
val authenticators =
|
||||||
val keyguardManager = ContextCompat.getSystemService(this, KeyguardManager::class.java)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val packageManager: PackageManager = this.packageManager
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
|
||||||
}
|
|
||||||
if (keyguardManager?.isKeyguardSecure == false) {
|
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
|
||||||
}
|
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
||||||
val biometricManager: BiometricManager =
|
|
||||||
this.getSystemService(BiometricManager::class.java)
|
|
||||||
return biometricManager.canAuthenticate()
|
|
||||||
} else {
|
} else {
|
||||||
val biometricManager: BiometricManager =
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
this.getSystemService(BiometricManager::class.java)
|
|
||||||
return biometricManager.canAuthenticate(
|
|
||||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
return biometricManager.canAuthenticate(authenticators)
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getUriForFile(file: File): Uri =
|
fun Context.getUriForFile(file: File): Uri =
|
||||||
|
@ -152,6 +139,8 @@ fun Context.getUriForFile(file: File): Uri =
|
||||||
|
|
||||||
private val LOG_DATE_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
|
private val LOG_DATE_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH)
|
||||||
|
|
||||||
|
fun Context.getMimeType(uri: Uri) = contentResolver.getType(uri)
|
||||||
|
|
||||||
fun ContextWrapper.log(
|
fun ContextWrapper.log(
|
||||||
tag: String,
|
tag: String,
|
||||||
msg: String? = null,
|
msg: String? = null,
|
||||||
|
@ -166,8 +155,7 @@ fun ContextWrapper.log(
|
||||||
fun ContextWrapper.getLastExceptionLog(): String? {
|
fun ContextWrapper.getLastExceptionLog(): String? {
|
||||||
val logFile = getLogFile()
|
val logFile = getLogFile()
|
||||||
if (logFile.exists()) {
|
if (logFile.exists()) {
|
||||||
val logContents = logFile.readText().substringAfterLast("[Start]")
|
return logFile.readText().substringAfterLast("[Start]")
|
||||||
return URLEncoder.encode(logContents, StandardCharsets.UTF_8.toString())
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -197,16 +185,24 @@ fun Context.logToFile(
|
||||||
val logFile =
|
val logFile =
|
||||||
folder.findFile(fileName).let {
|
folder.findFile(fileName).let {
|
||||||
if (it == null || !it.exists()) {
|
if (it == null || !it.exists()) {
|
||||||
folder.createFile("text/plain", fileName)
|
folder.createFile("text/plain", fileName.removeSuffix(".txt"))
|
||||||
} else if (it.isLargerThanKb(MAX_LOGS_FILE_SIZE_KB)) {
|
} else if (it.isLargerThanKb(MAX_LOGS_FILE_SIZE_KB)) {
|
||||||
it.delete()
|
it.delete()
|
||||||
folder.createFile("text/plain", fileName)
|
folder.createFile("text/plain", fileName.removeSuffix(".txt"))
|
||||||
} else it
|
} else it
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile?.let { file ->
|
logFile?.let { file ->
|
||||||
val contentResolver = contentResolver
|
val contentResolver = contentResolver
|
||||||
val outputStream = contentResolver.openOutputStream(file.uri, "wa")
|
val (outputStream, logFileContents) =
|
||||||
|
try {
|
||||||
|
Pair(contentResolver.openOutputStream(file.uri, "wa"), null)
|
||||||
|
} catch (e: UnsupportedOperationException) {
|
||||||
|
Pair(
|
||||||
|
contentResolver.openOutputStream(file.uri, "w"),
|
||||||
|
contentResolver.readFileContents(file.uri),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
outputStream?.use { output ->
|
outputStream?.use { output ->
|
||||||
val writer = PrintWriter(OutputStreamWriter(output, Charsets.UTF_8))
|
val writer = PrintWriter(OutputStreamWriter(output, Charsets.UTF_8))
|
||||||
|
@ -214,6 +210,7 @@ fun Context.logToFile(
|
||||||
val formatter = DateFormat.getDateTimeInstance()
|
val formatter = DateFormat.getDateTimeInstance()
|
||||||
val time = formatter.format(System.currentTimeMillis())
|
val time = formatter.format(System.currentTimeMillis())
|
||||||
|
|
||||||
|
logFileContents?.let { writer.println(it) }
|
||||||
if (throwable != null || stackTrace != null) {
|
if (throwable != null || stackTrace != null) {
|
||||||
writer.println("[Start]")
|
writer.println("[Start]")
|
||||||
}
|
}
|
||||||
|
@ -243,6 +240,17 @@ fun Fragment.reportBug(stackTrace: String?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Fragment.getExtraBooleanFromBundleOrIntent(
|
||||||
|
bundle: Bundle?,
|
||||||
|
key: String,
|
||||||
|
defaultValue: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
return bundle.getExtraBooleanOrDefault(
|
||||||
|
key,
|
||||||
|
activity?.intent?.getBooleanExtra(key, defaultValue) ?: defaultValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.reportBug(stackTrace: String?) {
|
fun Context.reportBug(stackTrace: String?) {
|
||||||
catchNoBrowserInstalled { startActivity(createReportBugIntent(stackTrace)) }
|
catchNoBrowserInstalled { startActivity(createReportBugIntent(stackTrace)) }
|
||||||
}
|
}
|
||||||
|
@ -279,16 +287,34 @@ fun Context.createReportBugIntent(
|
||||||
.wrapWithChooser(this)
|
.wrapWithChooser(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.shareNote(title: String, body: CharSequence) {
|
fun ContextWrapper.shareNote(note: BaseNote) {
|
||||||
val text = body.truncate(150_000)
|
val body =
|
||||||
|
when (note.type) {
|
||||||
|
Type.NOTE -> note.body
|
||||||
|
Type.LIST -> note.items.toMutableList().toText()
|
||||||
|
}
|
||||||
|
val filesUris =
|
||||||
|
note.images
|
||||||
|
.map { File(getExternalImagesDirectory(), it.localName) }
|
||||||
|
.map { getUriForFile(it) }
|
||||||
|
shareNote(note.title, body, filesUris)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.shareNote(title: String, body: CharSequence, imageUris: List<Uri>) {
|
||||||
|
val text = body.truncate(150_000)
|
||||||
val intent =
|
val intent =
|
||||||
Intent(Intent.ACTION_SEND)
|
Intent(if (imageUris.size > 1) Intent.ACTION_SEND_MULTIPLE else Intent.ACTION_SEND)
|
||||||
.apply {
|
.apply {
|
||||||
type = "text/plain"
|
type = if (imageUris.isEmpty()) "text/*" else "image/*"
|
||||||
putExtra(Intent.EXTRA_TEXT, text.toString())
|
putExtra(Intent.EXTRA_TEXT, text.toString())
|
||||||
putExtra(Intent.EXTRA_TITLE, title)
|
putExtra(Intent.EXTRA_TITLE, title)
|
||||||
putExtra(Intent.EXTRA_SUBJECT, title)
|
putExtra(Intent.EXTRA_SUBJECT, title)
|
||||||
|
if (imageUris.size > 1) {
|
||||||
|
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(imageUris))
|
||||||
|
} else if (imageUris.isNotEmpty()) {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, imageUris.first())
|
||||||
|
}
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
.wrapWithChooser(this)
|
.wrapWithChooser(this)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
@ -400,3 +426,12 @@ fun Activity.resetApplication() {
|
||||||
startActivity(resetApplicationIntent)
|
startActivity(resetApplicationIntent)
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Bundle?.getExtraBooleanOrDefault(key: String, defaultValue: Boolean): Boolean {
|
||||||
|
return this?.getBoolean(key, defaultValue) ?: defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ContentResolver.readFileContents(uri: Uri) =
|
||||||
|
openInputStream(uri)?.use { inputStream ->
|
||||||
|
BufferedReader(InputStreamReader(inputStream)).use { reader -> reader.readText() }
|
||||||
|
} ?: ""
|
||||||
|
|
|
@ -149,7 +149,7 @@ fun Context.getExportedPath() = getEmptyFolder("exported")
|
||||||
|
|
||||||
fun ContextWrapper.getLogsDir() = File(filesDir, "logs").also { it.mkdir() }
|
fun ContextWrapper.getLogsDir() = File(filesDir, "logs").also { it.mkdir() }
|
||||||
|
|
||||||
const val APP_LOG_FILE_NAME = "Log.v1.txt"
|
const val APP_LOG_FILE_NAME = "notallyx-logs.txt"
|
||||||
|
|
||||||
fun ContextWrapper.getLogFile(): File {
|
fun ContextWrapper.getLogFile(): File {
|
||||||
return File(getLogsDir(), APP_LOG_FILE_NAME)
|
return File(getLogsDir(), APP_LOG_FILE_NAME)
|
||||||
|
|
|
@ -124,8 +124,8 @@ fun ContextWrapper.createBackup(): Result {
|
||||||
try {
|
try {
|
||||||
val formatter = SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.ENGLISH)
|
val formatter = SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.ENGLISH)
|
||||||
val backupFilePrefix = PERIODIC_BACKUP_FILE_PREFIX
|
val backupFilePrefix = PERIODIC_BACKUP_FILE_PREFIX
|
||||||
val name = "$backupFilePrefix${formatter.format(System.currentTimeMillis())}"
|
val name = "$backupFilePrefix${formatter.format(System.currentTimeMillis())}.zip"
|
||||||
log(msg = "Creating '$uri/$name.zip'...")
|
log(msg = "Creating '$uri/$name'...")
|
||||||
val zipUri = requireNotNull(folder.createFile(MIME_TYPE_ZIP, name)).uri
|
val zipUri = requireNotNull(folder.createFile(MIME_TYPE_ZIP, name)).uri
|
||||||
val exportedNotes = app.exportAsZip(zipUri, password = preferences.backupPassword.value)
|
val exportedNotes = app.exportAsZip(zipUri, password = preferences.backupPassword.value)
|
||||||
log(msg = "Exported $exportedNotes notes")
|
log(msg = "Exported $exportedNotes notes")
|
||||||
|
@ -174,7 +174,7 @@ fun ContextWrapper.autoBackupOnSave(backupPath: String, password: String, savedN
|
||||||
backupFile = folder.createFile(MIME_TYPE_ZIP, ON_SAVE_BACKUP_FILE)
|
backupFile = folder.createFile(MIME_TYPE_ZIP, ON_SAVE_BACKUP_FILE)
|
||||||
exportAsZip(backupFile!!.uri, password = password)
|
exportAsZip(backupFile!!.uri, password = password)
|
||||||
} else {
|
} else {
|
||||||
NotallyDatabase.getDatabase(this, observePreferences = false).value.checkpoint()
|
val (_, file) = copyDatabase()
|
||||||
val files =
|
val files =
|
||||||
with(savedNote) {
|
with(savedNote) {
|
||||||
images.map {
|
images.map {
|
||||||
|
@ -192,10 +192,7 @@ fun ContextWrapper.autoBackupOnSave(backupPath: String, password: String, savedN
|
||||||
audios.map {
|
audios.map {
|
||||||
BackupFile(SUBFOLDER_AUDIOS, File(getExternalAudioDirectory(), it.name))
|
BackupFile(SUBFOLDER_AUDIOS, File(getExternalAudioDirectory(), it.name))
|
||||||
} +
|
} +
|
||||||
BackupFile(
|
BackupFile(null, file)
|
||||||
null,
|
|
||||||
NotallyDatabase.getCurrentDatabaseFile(this@autoBackupOnSave),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
exportToZip(backupFile.uri, files, password)
|
exportToZip(backupFile.uri, files, password)
|
||||||
|
@ -417,7 +414,7 @@ fun ContextWrapper.copyDatabase(): Pair<NotallyDatabase, File> {
|
||||||
val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!)
|
val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!)
|
||||||
val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value)
|
val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value)
|
||||||
val decryptedFile = File(cacheDir, DATABASE_NAME)
|
val decryptedFile = File(cacheDir, DATABASE_NAME)
|
||||||
decryptDatabase(this, passphrase, decryptedFile, databaseFile)
|
decryptDatabase(this, passphrase, databaseFile, decryptedFile)
|
||||||
Pair(database, decryptedFile)
|
Pair(database, decryptedFile)
|
||||||
} else {
|
} else {
|
||||||
val dbFile = File(cacheDir, DATABASE_NAME)
|
val dbFile = File(cacheDir, DATABASE_NAME)
|
||||||
|
@ -517,13 +514,14 @@ fun exportPdfFile(
|
||||||
total: Int? = null,
|
total: Int? = null,
|
||||||
duplicateFileCount: Int = 1,
|
duplicateFileCount: Int = 1,
|
||||||
) {
|
) {
|
||||||
val filePath = "$fileName.${ExportMimeType.PDF.fileExtension}"
|
val validFileName = fileName.ifBlank { app.getString(R.string.note) }
|
||||||
|
val filePath = "$validFileName.${ExportMimeType.PDF.fileExtension}"
|
||||||
if (folder.findFile(filePath)?.exists() == true) {
|
if (folder.findFile(filePath)?.exists() == true) {
|
||||||
return exportPdfFile(
|
return exportPdfFile(
|
||||||
app,
|
app,
|
||||||
note,
|
note,
|
||||||
folder,
|
folder,
|
||||||
"${fileName.removeTrailingParentheses()} ($duplicateFileCount)",
|
"${validFileName.removeTrailingParentheses()} ($duplicateFileCount)",
|
||||||
pdfPrintListener,
|
pdfPrintListener,
|
||||||
progress,
|
progress,
|
||||||
counter,
|
counter,
|
||||||
|
@ -581,8 +579,9 @@ suspend fun exportPlainTextFile(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
val validFileName = fileName.takeIf { it.isNotBlank() } ?: app.getString(R.string.note)
|
||||||
val file =
|
val file =
|
||||||
folder.createFile(exportType.mimeType, fileName)?.let {
|
folder.createFile(exportType.mimeType, validFileName)?.let {
|
||||||
app.contentResolver.openOutputStream(it.uri)?.use { stream ->
|
app.contentResolver.openOutputStream(it.uri)?.use { stream ->
|
||||||
OutputStreamWriter(stream).use { writer ->
|
OutputStreamWriter(stream).use { writer ->
|
||||||
writer.write(
|
writer.write(
|
||||||
|
|
|
@ -22,11 +22,13 @@ import com.philkes.notallyx.data.model.Converters
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.Label
|
import com.philkes.notallyx.data.model.Label
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.data.model.parseToColorString
|
import com.philkes.notallyx.data.model.parseToColorString
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
import com.philkes.notallyx.presentation.showToast
|
import com.philkes.notallyx.presentation.showToast
|
||||||
import com.philkes.notallyx.presentation.viewmodel.NotallyModel.FileType
|
import com.philkes.notallyx.presentation.viewmodel.NotallyModel.FileType
|
||||||
|
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||||
import com.philkes.notallyx.utils.FileError
|
import com.philkes.notallyx.utils.FileError
|
||||||
import com.philkes.notallyx.utils.SUBFOLDER_AUDIOS
|
import com.philkes.notallyx.utils.SUBFOLDER_AUDIOS
|
||||||
import com.philkes.notallyx.utils.SUBFOLDER_FILES
|
import com.philkes.notallyx.utils.SUBFOLDER_FILES
|
||||||
|
@ -43,6 +45,8 @@ import com.philkes.notallyx.utils.log
|
||||||
import com.philkes.notallyx.utils.mimeTypeToFileExtension
|
import com.philkes.notallyx.utils.mimeTypeToFileExtension
|
||||||
import com.philkes.notallyx.utils.rename
|
import com.philkes.notallyx.utils.rename
|
||||||
import com.philkes.notallyx.utils.scheduleNoteReminders
|
import com.philkes.notallyx.utils.scheduleNoteReminders
|
||||||
|
import com.philkes.notallyx.utils.security.SQLCipherUtils
|
||||||
|
import com.philkes.notallyx.utils.security.decryptDatabase
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -85,12 +89,31 @@ suspend fun ContextWrapper.importZip(
|
||||||
NotallyDatabase.DATABASE_NAME,
|
NotallyDatabase.DATABASE_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dbFile = File(databaseFolder, NotallyDatabase.DATABASE_NAME)
|
||||||
|
val state = SQLCipherUtils.getDatabaseState(dbFile)
|
||||||
|
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
||||||
|
val fallbackEncryptionKey =
|
||||||
|
NotallyXPreferences.getInstance(this@importZip)
|
||||||
|
.fallbackDatabaseEncryptionKey
|
||||||
|
.value
|
||||||
|
if (fallbackEncryptionKey != null) {
|
||||||
|
val dbFileDecrypted =
|
||||||
|
File(databaseFolder, "${NotallyDatabase.DATABASE_NAME}-decrypted")
|
||||||
|
decryptDatabase(
|
||||||
|
this@importZip,
|
||||||
|
fallbackEncryptionKey,
|
||||||
|
dbFile,
|
||||||
|
dbFileDecrypted,
|
||||||
|
)
|
||||||
|
dbFile = dbFileDecrypted
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Backup contains encrypted database and 'fallbackDatabaseEncryptionKey' has no value!"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
val database =
|
val database =
|
||||||
SQLiteDatabase.openDatabase(
|
SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READONLY)
|
||||||
File(databaseFolder, NotallyDatabase.DATABASE_NAME).path,
|
|
||||||
null,
|
|
||||||
SQLiteDatabase.OPEN_READONLY,
|
|
||||||
)
|
|
||||||
|
|
||||||
val labelCursor = database.query("Label", null, null, null, null, null, null)
|
val labelCursor = database.query("Label", null, null, null, null, null, null)
|
||||||
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)
|
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)
|
||||||
|
@ -176,14 +199,11 @@ suspend fun ContextWrapper.importZip(
|
||||||
showToast(message)
|
showToast(message)
|
||||||
} catch (e: ZipException) {
|
} catch (e: ZipException) {
|
||||||
if (e.type == ZipException.Type.WRONG_PASSWORD) {
|
if (e.type == ZipException.Type.WRONG_PASSWORD) {
|
||||||
|
log(TAG, throwable = e)
|
||||||
showToast(R.string.wrong_password)
|
showToast(R.string.wrong_password)
|
||||||
} else {
|
} else {
|
||||||
log(TAG, throwable = e)
|
throw e
|
||||||
showToast(R.string.invalid_backup)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
showToast(R.string.invalid_backup)
|
|
||||||
log(TAG, throwable = e)
|
|
||||||
} finally {
|
} finally {
|
||||||
importingBackup?.value = ImportProgress(inProgress = false)
|
importingBackup?.value = ImportProgress(inProgress = false)
|
||||||
}
|
}
|
||||||
|
@ -249,8 +269,8 @@ private fun Cursor.toBaseNote(): BaseNote {
|
||||||
else -> throw IllegalArgumentException("pinned must be 0 or 1")
|
else -> throw IllegalArgumentException("pinned must be 0 or 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
val type = Type.valueOf(typeTmp)
|
val type = Type.valueOfOrDefault(typeTmp)
|
||||||
val folder = Folder.valueOf(folderTmp)
|
val folder = Folder.valueOfOrDefault(folderTmp)
|
||||||
|
|
||||||
val labels = Converters.jsonToLabels(labelsTmp)
|
val labels = Converters.jsonToLabels(labelsTmp)
|
||||||
val spans = Converters.jsonToSpans(spansTmp)
|
val spans = Converters.jsonToSpans(spansTmp)
|
||||||
|
@ -280,6 +300,11 @@ private fun Cursor.toBaseNote(): BaseNote {
|
||||||
Converters.jsonToReminders(getString(remindersIndex))
|
Converters.jsonToReminders(getString(remindersIndex))
|
||||||
} else emptyList()
|
} else emptyList()
|
||||||
|
|
||||||
|
val viewModeIndex = getColumnIndex("viewMode")
|
||||||
|
val viewMode =
|
||||||
|
if (viewModeIndex != -1) {
|
||||||
|
NoteViewMode.valueOfOrDefault(getString(viewModeIndex))
|
||||||
|
} else NoteViewMode.EDIT
|
||||||
return BaseNote(
|
return BaseNote(
|
||||||
0,
|
0,
|
||||||
type,
|
type,
|
||||||
|
@ -297,6 +322,7 @@ private fun Cursor.toBaseNote(): BaseNote {
|
||||||
files,
|
files,
|
||||||
audios,
|
audios,
|
||||||
reminders,
|
reminders,
|
||||||
|
viewMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,8 +356,7 @@ fun Context.importPreferences(jsonFile: Uri, to: SharedPreferences.Editor): Bool
|
||||||
else -> to.putString(key, value.toString())
|
else -> to.putString(key, value.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
to.apply()
|
return to.commit()
|
||||||
return true
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (this is ContextWrapper) {
|
if (this is ContextWrapper) {
|
||||||
log(TAG, "Import preferences from '$jsonFile' failed", throwable = e)
|
log(TAG, "Import preferences from '$jsonFile' failed", throwable = e)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.Label
|
import com.philkes.notallyx.data.model.Label
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.SpanRepresentation
|
import com.philkes.notallyx.data.model.SpanRepresentation
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
import com.philkes.notallyx.data.model.parseToColorString
|
import com.philkes.notallyx.data.model.parseToColorString
|
||||||
|
@ -113,6 +114,7 @@ private fun XmlPullParser.parseBaseNote(rootTag: String, folder: Folder): BaseNo
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
NoteViewMode.EDIT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,12 @@ class ChangeHistory {
|
||||||
makeListAction.redo()
|
makeListAction.redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun redoAll() {
|
||||||
|
while (stackPointer.value < changeStack.lastIndex) {
|
||||||
|
redo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun undo() {
|
fun undo() {
|
||||||
if (stackPointer.value < 0) {
|
if (stackPointer.value < 0) {
|
||||||
throw ChangeHistoryException("There is no Change to undo!}")
|
throw ChangeHistoryException("There is no Change to undo!}")
|
||||||
|
@ -45,6 +51,12 @@ class ChangeHistory {
|
||||||
stackPointer.value -= 1
|
stackPointer.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun undoAll() {
|
||||||
|
while (stackPointer.value >= 0) {
|
||||||
|
undo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
stackPointer.value = -1
|
stackPointer.value = -1
|
||||||
changeStack.clear()
|
changeStack.clear()
|
||||||
|
|
|
@ -1,33 +1,7 @@
|
||||||
package com.philkes.notallyx.utils.changehistory
|
package com.philkes.notallyx.utils.changehistory
|
||||||
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.text.style.CharacterStyle
|
|
||||||
import android.widget.EditText
|
|
||||||
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
import com.philkes.notallyx.presentation.view.note.listitem.ListManager
|
||||||
|
import com.philkes.notallyx.presentation.view.note.listitem.ListState
|
||||||
|
|
||||||
open class ListEditTextChange(
|
open class ListEditTextChange(old: ListState, new: ListState, listManager: ListManager) :
|
||||||
private val editText: EditText,
|
ListBatchChange(old, new, listManager)
|
||||||
position: Int,
|
|
||||||
before: EditTextState,
|
|
||||||
after: EditTextState,
|
|
||||||
private val listener: TextWatcher,
|
|
||||||
private val listManager: ListManager,
|
|
||||||
) : ListPositionValueChange<EditTextState>(after, before, position) {
|
|
||||||
|
|
||||||
override fun update(position: Int, value: EditTextState, isUndo: Boolean) {
|
|
||||||
listManager.changeText(
|
|
||||||
position,
|
|
||||||
value,
|
|
||||||
pushChange = false,
|
|
||||||
editText = editText,
|
|
||||||
listener = listener,
|
|
||||||
)
|
|
||||||
editText.apply {
|
|
||||||
removeTextChangedListener(listener)
|
|
||||||
text = value.text.withoutSpans<CharacterStyle>()
|
|
||||||
requestFocus()
|
|
||||||
setSelection(value.cursorPos)
|
|
||||||
addTextChangedListener(listener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ fun decryptDatabase(context: ContextWrapper, passphrase: ByteArray) {
|
||||||
fun decryptDatabase(
|
fun decryptDatabase(
|
||||||
context: Context,
|
context: Context,
|
||||||
passphrase: ByteArray,
|
passphrase: ByteArray,
|
||||||
decryptedFile: File,
|
|
||||||
databaseFile: File,
|
databaseFile: File,
|
||||||
|
decryptedFile: File,
|
||||||
) {
|
) {
|
||||||
val state = SQLCipherUtils.getDatabaseState(databaseFile)
|
val state = SQLCipherUtils.getDatabaseState(databaseFile)
|
||||||
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
||||||
|
|
|
@ -4,15 +4,13 @@ import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.hardware.biometrics.BiometricManager
|
|
||||||
import android.hardware.biometrics.BiometricPrompt
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CancellationSignal
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
@ -27,7 +25,7 @@ fun Activity.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
this,
|
this as FragmentActivity,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -48,7 +46,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
requireContext(),
|
activity!!,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -60,7 +58,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
|
|
||||||
private fun showBiometricOrPinPrompt(
|
private fun showBiometricOrPinPrompt(
|
||||||
isForDecrypt: Boolean,
|
isForDecrypt: Boolean,
|
||||||
context: Context,
|
context: FragmentActivity,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
titleResId: Int,
|
titleResId: Int,
|
||||||
descriptionResId: Int? = null,
|
descriptionResId: Int? = null,
|
||||||
|
@ -69,142 +67,55 @@ private fun showBiometricOrPinPrompt(
|
||||||
onFailure: (errorCode: Int?) -> Unit,
|
onFailure: (errorCode: Int?) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
|
||||||
// Android 11+ with BiometricPrompt and Authenticators
|
|
||||||
val prompt =
|
|
||||||
BiometricPrompt.Builder(context)
|
|
||||||
.apply {
|
|
||||||
setTitle(context.getString(titleResId))
|
|
||||||
descriptionResId?.let {
|
|
||||||
setDescription(context.getString(descriptionResId))
|
|
||||||
}
|
|
||||||
setAllowedAuthenticators(
|
|
||||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
val cipher =
|
|
||||||
if (isForDecrypt) {
|
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
|
||||||
} else {
|
|
||||||
getInitializedCipherForEncryption()
|
|
||||||
}
|
|
||||||
prompt.authenticate(
|
|
||||||
BiometricPrompt.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
context.mainExecutor,
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: BiometricPrompt.AuthenticationResult?
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
onFailure.invoke(errorCode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q -> {
|
|
||||||
// Android 10: Use BiometricPrompt without Authenticators
|
|
||||||
val prompt =
|
|
||||||
BiometricPrompt.Builder(context)
|
|
||||||
.apply {
|
|
||||||
setTitle(context.getString(titleResId))
|
|
||||||
descriptionResId?.let {
|
|
||||||
setDescription(context.getString(descriptionResId))
|
|
||||||
}
|
|
||||||
setNegativeButton(
|
|
||||||
context.getString(R.string.cancel),
|
|
||||||
context.mainExecutor,
|
|
||||||
) { _, _ ->
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
val cipher =
|
|
||||||
if (isForDecrypt) {
|
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
|
||||||
} else {
|
|
||||||
getInitializedCipherForEncryption()
|
|
||||||
}
|
|
||||||
prompt.authenticate(
|
|
||||||
BiometricPrompt.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
context.mainExecutor,
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: BiometricPrompt.AuthenticationResult?
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
onFailure.invoke(errorCode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
||||||
val fingerprintManager =
|
val promptInfo =
|
||||||
ContextCompat.getSystemService(context, FingerprintManager::class.java)
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
if (
|
.apply {
|
||||||
fingerprintManager?.isHardwareDetected == true &&
|
setTitle(context.getString(titleResId))
|
||||||
fingerprintManager.hasEnrolledFingerprints()
|
descriptionResId?.let {
|
||||||
) {
|
setDescription(context.getString(descriptionResId))
|
||||||
val cipher =
|
}
|
||||||
if (isForDecrypt) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
setAllowedAuthenticators(
|
||||||
} else {
|
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
getInitializedCipherForEncryption()
|
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setNegativeButtonText(context.getString(R.string.cancel))
|
||||||
|
setAllowedAuthenticators(
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
val cipher =
|
||||||
|
if (isForDecrypt) {
|
||||||
|
getInitializedCipherForDecryption(iv = cipherIv!!)
|
||||||
|
} else {
|
||||||
|
getInitializedCipherForEncryption()
|
||||||
|
}
|
||||||
|
val authCallback =
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationSucceeded(
|
||||||
|
result: BiometricPrompt.AuthenticationResult
|
||||||
|
) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
onSuccess.invoke(result.cryptoObject!!.cipher!!)
|
||||||
}
|
}
|
||||||
fingerprintManager.authenticate(
|
|
||||||
FingerprintManager.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
0,
|
|
||||||
object : FingerprintManager.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: FingerprintManager.AuthenticationResult?
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
override fun onAuthenticationFailed() {
|
||||||
super.onAuthenticationFailed()
|
super.onAuthenticationFailed()
|
||||||
onFailure.invoke(null)
|
onFailure.invoke(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
errorCode: Int,
|
super.onAuthenticationError(errorCode, errString)
|
||||||
errString: CharSequence?,
|
onFailure.invoke(errorCode)
|
||||||
) {
|
}
|
||||||
super.onAuthenticationError(errorCode, errString)
|
}
|
||||||
onFailure.invoke(errorCode)
|
val prompt =
|
||||||
}
|
BiometricPrompt(context, ContextCompat.getMainExecutor(context), authCallback)
|
||||||
},
|
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||||
null,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
promptPinAuthentication(context, activityResultLauncher, titleResId, onFailure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -214,14 +125,6 @@ private fun showBiometricOrPinPrompt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCancellationSignal(context: Context): CancellationSignal {
|
|
||||||
return CancellationSignal().apply {
|
|
||||||
setOnCancelListener {
|
|
||||||
Toast.makeText(context, R.string.biometrics_failure, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun promptPinAuthentication(
|
private fun promptPinAuthentication(
|
||||||
context: Context,
|
context: Context,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
|
|
|
@ -287,9 +287,10 @@ public class SQLCipherUtils {
|
||||||
|
|
||||||
final SQLiteStatement st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''");
|
final SQLiteStatement st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''");
|
||||||
|
|
||||||
if (!decryptedFile.exists()) {
|
if(decryptedFile.exists()){
|
||||||
decryptedFile.createNewFile();
|
decryptedFile.delete();
|
||||||
}
|
}
|
||||||
|
decryptedFile.createNewFile();
|
||||||
st.bindString(1, decryptedFile.getAbsolutePath());
|
st.bindString(1, decryptedFile.getAbsolutePath());
|
||||||
st.execute();
|
st.execute();
|
||||||
|
|
||||||
|
|
10
app/src/main/res/drawable/convert_to_text.xml
Normal file
10
app/src/main/res/drawable/convert_to_text.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M400,680L560,680L560,600L400,600L400,680ZM400,520L680,520L680,440L400,440L400,520ZM280,360L680,360L680,280L280,280L280,360ZM480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480ZM80,880L80,800L182,800Q134,777 104.5,732Q75,687 75,630Q75,551 130.5,495.5Q186,440 265,440L265,520Q220,520 187.5,552Q155,584 155,630Q155,669 179,699Q203,729 240,737L240,640L320,640L320,880L80,880ZM400,840L400,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,360L120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L400,840Z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/file_json.xml
Normal file
10
app/src/main/res/drawable/file_json.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M190,600L260,600Q277,600 288.5,588.5Q300,577 300,560L300,360L240,360L240,550L200,550L200,500L150,500L150,560Q150,577 161.5,588.5Q173,600 190,600ZM367,600L427,600Q444,600 455.5,588.5Q467,577 467,560L467,500Q467,483 455.5,471.5Q444,460 427,460L377,460L377,410L417,410L417,430L467,430L467,400Q467,383 455.5,371.5Q444,360 427,360L367,360Q350,360 338.5,371.5Q327,383 327,400L327,460Q327,477 338.5,488.5Q350,500 367,500L417,500L417,550L377,550L377,530L327,530L327,560Q327,577 338.5,588.5Q350,600 367,600ZM543,540L543,420L583,420L583,540L543,540ZM533,600L593,600Q610,600 621.5,588.5Q633,577 633,560L633,400Q633,383 621.5,371.5Q610,360 593,360L533,360Q516,360 504.5,371.5Q493,383 493,400L493,560Q493,577 504.5,588.5Q516,600 533,600ZM660,600L710,600L710,495L750,600L800,600L800,360L750,360L750,465L710,360L660,360L660,600ZM120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L120,800ZM120,720L840,720Q840,720 840,720Q840,720 840,720L840,240Q840,240 840,240Q840,240 840,240L120,240Q120,240 120,240Q120,240 120,240L120,720Q120,720 120,720Q120,720 120,720ZM120,720Q120,720 120,720Q120,720 120,720L120,240Q120,240 120,240Q120,240 120,240L120,240Q120,240 120,240Q120,240 120,240L120,720Q120,720 120,720Q120,720 120,720Z"/>
|
||||||
|
</vector>
|
|
@ -22,8 +22,8 @@
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:contentDescription="@string/label_visibility"
|
android:contentDescription="@string/labels_hidden_in_overview_title"
|
||||||
android:tooltipText="@string/label_visibility"
|
android:tooltipText="@string/labels_hidden_in_overview_title"
|
||||||
app:srcCompat="@drawable/visibility"
|
app:srcCompat="@drawable/visibility"
|
||||||
app:tint="?attr/colorControlNormal" />
|
app:tint="?attr/colorControlNormal" />
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingStart="14dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingEnd="4dp"
|
android:paddingEnd="4dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,5 @@
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/EditText"
|
android:id="@+id/EditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content" />
|
||||||
android:inputType="textCapWords" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
|
@ -10,25 +10,25 @@
|
||||||
>
|
>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/EnumHint"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/date_format_hint"
|
|
||||||
style="@style/MaterialAlertDialog.Material3.Body.Text"
|
style="@style/MaterialAlertDialog.Material3.Body.Text"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
android:id="@+id/DateFormatRadioGroup"
|
android:id="@+id/EnumRadioGroup"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<com.google.android.material.checkbox.MaterialCheckBox
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
android:id="@+id/ApplyToNoteView"
|
android:id="@+id/Toggle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/date_format_apply_in_note_view"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,55 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/RepetitionOptions"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/None"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:checked="true"
|
android:scrollbarAlwaysDrawVerticalTrack="true">
|
||||||
android:text="@string/none" />
|
|
||||||
|
|
||||||
<RadioButton
|
<RadioGroup
|
||||||
android:id="@+id/Daily"
|
android:id="@+id/RepetitionOptions"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/daily" />
|
android:paddingTop="2dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/Weekly"
|
android:id="@+id/None"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/weekly" />
|
android:checked="true"
|
||||||
|
android:text="@string/none" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/Monthly"
|
android:id="@+id/Daily"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/monthly" />
|
android:text="@string/daily" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:id="@+id/Yearly"
|
android:id="@+id/Weekly"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/yearly" />
|
android:text="@string/weekly" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/Monthly"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/monthly" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/Yearly"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/yearly" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/Custom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/custom" />
|
||||||
|
</RadioGroup>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
android:id="@+id/Custom"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/custom" />
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,9 @@
|
||||||
app:fastScrollVerticalTrackDrawable="@drawable/scroll_track"
|
app:fastScrollVerticalTrackDrawable="@drawable/scroll_track"
|
||||||
app:fastScrollHorizontalThumbDrawable="@drawable/scroll_thumb"
|
app:fastScrollHorizontalThumbDrawable="@drawable/scroll_thumb"
|
||||||
app:fastScrollVerticalThumbDrawable="@drawable/scroll_thumb"
|
app:fastScrollVerticalThumbDrawable="@drawable/scroll_thumb"
|
||||||
/>
|
>
|
||||||
|
<requestFocus />
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ImageView"
|
android:id="@+id/ImageView"
|
||||||
|
|
|
@ -88,6 +88,10 @@
|
||||||
android:id="@+id/LabelsHiddenInOverview"
|
android:id="@+id/LabelsHiddenInOverview"
|
||||||
layout="@layout/preference" />
|
layout="@layout/preference" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/ImagesHiddenInOverview"
|
||||||
|
layout="@layout/preference" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
|
@ -173,6 +177,10 @@
|
||||||
android:id="@+id/BackupPassword"
|
android:id="@+id/BackupPassword"
|
||||||
layout="@layout/preference" />
|
layout="@layout/preference" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/SecureFlag"
|
||||||
|
layout="@layout/preference" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:contentDescription="@string/label_visibility"
|
android:contentDescription="@string/labels_hidden_in_overview_title"
|
||||||
app:srcCompat="@drawable/visibility"
|
app:srcCompat="@drawable/visibility"
|
||||||
app:tint="?attr/colorControlNormal" />
|
app:tint="?attr/colorControlNormal" />
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,11 @@
|
||||||
<string name="auto_backup_last">Poslední záloha</string>
|
<string name="auto_backup_last">Poslední záloha</string>
|
||||||
<string name="auto_backup_on_save">Automaticky zálohovat při ukončení poznámky</string>
|
<string name="auto_backup_on_save">Automaticky zálohovat při ukončení poznámky</string>
|
||||||
<string name="auto_backup_on_save_hint">Pokud tuto možnost povolíte, záloha („NotallyX_AutoBackup.zip“) bude automaticky vytvořena ve zvoleném „Adresáři záloh“ pokaždé, když bude poznámka uložena. Mějte na paměti, že to může ovlivnit výkon</string>
|
<string name="auto_backup_on_save_hint">Pokud tuto možnost povolíte, záloha („NotallyX_AutoBackup.zip“) bude automaticky vytvořena ve zvoleném „Adresáři záloh“ pokaždé, když bude poznámka uložena. Mějte na paměti, že to může ovlivnit výkon</string>
|
||||||
<string name="auto_backup_period">Frekvence automatického zálohování</string>
|
|
||||||
<string name="auto_backups_folder">Adresář pro zálohy</string>
|
<string name="auto_backups_folder">Adresář pro zálohy</string>
|
||||||
<string name="auto_backups_folder_hint">Adresář pro uložení automatických záloh.</string>
|
<string name="auto_backups_folder_hint">Adresář pro uložení automatických záloh.</string>
|
||||||
<string name="auto_backups_folder_rechoose">Vyberte znovu Adresář pro zálohy a udělte tak znovu NotallyX oprávnění do něj zapisovat.\nPro přeskočení můžete stisknout tlačítko Zrušit</string>
|
<string name="auto_backups_folder_rechoose">Vyberte znovu Adresář pro zálohy a udělte tak znovu NotallyX oprávnění do něj zapisovat.\nPro přeskočení můžete stisknout tlačítko Zrušit</string>
|
||||||
<string name="auto_backups_folder_set">Nejprve nastavte adresář pro zálohy</string>
|
<string name="auto_backups_folder_set">Nejprve nastavte adresář pro zálohy</string>
|
||||||
|
<string name="auto_save_after_idle_time">Automatické uložení poznámky při nečinnosti </string>
|
||||||
<string name="auto_sort_by_checked">Seřadit zaškrtnuté položky na konec</string>
|
<string name="auto_sort_by_checked">Seřadit zaškrtnuté položky na konec</string>
|
||||||
<string name="back">Zpět</string>
|
<string name="back">Zpět</string>
|
||||||
<string name="backup">Zálohování</string>
|
<string name="backup">Zálohování</string>
|
||||||
|
@ -38,6 +38,7 @@
|
||||||
<string name="backup_period_days">Frekvence automatického zálohování v dnech</string>
|
<string name="backup_period_days">Frekvence automatického zálohování v dnech</string>
|
||||||
<string name="backup_periodic">Periodické zálohy</string>
|
<string name="backup_periodic">Periodické zálohy</string>
|
||||||
<string name="backup_periodic_hint">Pokud tuto možnost povolíte, zálohy budou automaticky vytvářeny ve zvoleném adresáři záloh. Nemusí to fungovat, pokud máte povolený úsporný režim</string>
|
<string name="backup_periodic_hint">Pokud tuto možnost povolíte, zálohy budou automaticky vytvářeny ve zvoleném adresáři záloh. Nemusí to fungovat, pokud máte povolený úsporný režim</string>
|
||||||
|
<string name="behaviour">Chování</string>
|
||||||
<string name="biometric_lock">Uzamknout aplikaci pomocí biometrických údajů zařízení nebo kódu PIN</string>
|
<string name="biometric_lock">Uzamknout aplikaci pomocí biometrických údajů zařízení nebo kódu PIN</string>
|
||||||
<string name="biometrics_disable_success">Biometrický zámek/PIN byl vypnut</string>
|
<string name="biometrics_disable_success">Biometrický zámek/PIN byl vypnut</string>
|
||||||
<string name="biometrics_failure">Ověření pomocí biometrických údajů / kódu PIN selhalo</string>
|
<string name="biometrics_failure">Ověření pomocí biometrických údajů / kódu PIN selhalo</string>
|
||||||
|
@ -78,6 +79,8 @@
|
||||||
<string name="color_exists">Tato barva již existuje!</string>
|
<string name="color_exists">Tato barva již existuje!</string>
|
||||||
<string name="content_density">Kompaktnost obsahu</string>
|
<string name="content_density">Kompaktnost obsahu</string>
|
||||||
<string name="continue_">Pokračovat</string>
|
<string name="continue_">Pokračovat</string>
|
||||||
|
<string name="convert_to_list_note">Převést na seznam</string>
|
||||||
|
<string name="convert_to_text_note">Převést na textovou poznámku</string>
|
||||||
<string name="copied_link">Odkaz zkopírován do schránky</string>
|
<string name="copied_link">Odkaz zkopírován do schránky</string>
|
||||||
<string name="copy">Kopírovat</string>
|
<string name="copy">Kopírovat</string>
|
||||||
<string name="crash_message">Došlo k neočekávané chybě. \nOmluvte prosím vzniklé nepříjemnosti. </string>
|
<string name="crash_message">Došlo k neočekávané chybě. \nOmluvte prosím vzniklé nepříjemnosti. </string>
|
||||||
|
@ -120,6 +123,7 @@
|
||||||
<string name="disable_lock_description">Toto rovněž dešifruje databázi</string>
|
<string name="disable_lock_description">Toto rovněž dešifruje databázi</string>
|
||||||
<string name="disable_lock_title">Vypnout zámek pomocí biometrických údajů/PIN</string>
|
<string name="disable_lock_title">Vypnout zámek pomocí biometrických údajů/PIN</string>
|
||||||
<string name="disabled">Zakázat</string>
|
<string name="disabled">Zakázat</string>
|
||||||
|
<string name="disallow_screenshots">Zakázat pořizování snímků obrazovky</string>
|
||||||
<string name="discard">Zahodit</string>
|
<string name="discard">Zahodit</string>
|
||||||
<string name="display_text">Text k zobrazení</string>
|
<string name="display_text">Text k zobrazení</string>
|
||||||
<string name="donate">Podpořit</string>
|
<string name="donate">Podpořit</string>
|
||||||
|
@ -159,6 +163,8 @@
|
||||||
<string name="help">Nápověda</string>
|
<string name="help">Nápověda</string>
|
||||||
<string name="hours">Hodiny</string>
|
<string name="hours">Hodiny</string>
|
||||||
<string name="image_format_not_supported">Formát obrázku není podporován</string>
|
<string name="image_format_not_supported">Formát obrázku není podporován</string>
|
||||||
|
<string name="images_hidden_in_overview">Pokud tuto funkci povolíte, obrázky poznámek budou v přehledu skryty.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Skrýt obrázky v přehledu</string>
|
||||||
<string name="import_action">Importovat</string>
|
<string name="import_action">Importovat</string>
|
||||||
<string name="import_backup">Importovat zálohu</string>
|
<string name="import_backup">Importovat zálohu</string>
|
||||||
<string name="import_backup_password_hint">Pokud vaše záloha není chráněna heslem, jednoduše stiskněte tlačítko Importovat, jinak zadejte správné heslo.</string>
|
<string name="import_backup_password_hint">Pokud vaše záloha není chráněna heslem, jednoduše stiskněte tlačítko Importovat, jinak zadejte správné heslo.</string>
|
||||||
|
@ -187,6 +193,8 @@
|
||||||
<string name="invalid_link">Zkopírovat platný odkaz do schránky</string>
|
<string name="invalid_link">Zkopírovat platný odkaz do schránky</string>
|
||||||
<string name="italic">Kurzíva</string>
|
<string name="italic">Kurzíva</string>
|
||||||
<string name="item">Položka</string>
|
<string name="item">Položka</string>
|
||||||
|
<string name="json_files">JSON soubory</string>
|
||||||
|
<string name="json_files_help">Pro import poznámek z JSON souborů (jednotlivý soubor nebo složka) klikněte na Importovat. Každý platný JSON soubor je importován jako samostatná poznámka, název souboru se stane názvem poznámky. </string>
|
||||||
<string name="label_exists">Štítek již existuje</string>
|
<string name="label_exists">Štítek již existuje</string>
|
||||||
<string name="label_visibility">Skrýt/zobrazit štítek na navigačním panelu</string>
|
<string name="label_visibility">Skrýt/zobrazit štítek na navigačním panelu</string>
|
||||||
<string name="labels">Štítky</string>
|
<string name="labels">Štítky</string>
|
||||||
|
@ -242,6 +250,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">Pokud automatické zálohování selže, můžete obdržet oznámení, pokud NotallyX udělíte oprávnění k zasílání oznámení</string>
|
<string name="please_grant_notally_notification_auto_backup">Pokud automatické zálohování selže, můžete obdržet oznámení, pokud NotallyX udělíte oprávnění k zasílání oznámení</string>
|
||||||
<string name="previous">Předchozí</string>
|
<string name="previous">Předchozí</string>
|
||||||
<string name="rate">Ohodnotit aplikaci</string>
|
<string name="rate">Ohodnotit aplikaci</string>
|
||||||
|
<string name="read_only">Pouze pro čtení</string>
|
||||||
<string name="ready_to_record">Připraveno k záznamu</string>
|
<string name="ready_to_record">Připraveno k záznamu</string>
|
||||||
<string name="record_audio">Spustit zvukový záznam</string>
|
<string name="record_audio">Spustit zvukový záznam</string>
|
||||||
<string name="recording">Nahrávání…</string>
|
<string name="recording">Nahrávání…</string>
|
||||||
|
@ -297,6 +306,7 @@
|
||||||
<string name="text_default">Výchozí</string>
|
<string name="text_default">Výchozí</string>
|
||||||
<string name="text_size">Velikost písma</string>
|
<string name="text_size">Velikost písma</string>
|
||||||
<string name="theme">Téma</string>
|
<string name="theme">Téma</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Použít barvy z tapety</string>
|
||||||
<string name="title">Název</string>
|
<string name="title">Název</string>
|
||||||
<string name="to_record_audio">Chcete-li nahrávat zvuk, povolte NotallyX přístup k mikrofonu. Klepněte na Nastavení > Oprávnění a zapněte Mikrofon.</string>
|
<string name="to_record_audio">Chcete-li nahrávat zvuk, povolte NotallyX přístup k mikrofonu. Klepněte na Nastavení > Oprávnění a zapněte Mikrofon.</string>
|
||||||
<string name="unarchive">Zrušit archivaci</string>
|
<string name="unarchive">Zrušit archivaci</string>
|
||||||
|
|
|
@ -24,11 +24,11 @@
|
||||||
<string name="auto_backup_last">Letztes Backup</string>
|
<string name="auto_backup_last">Letztes Backup</string>
|
||||||
<string name="auto_backup_on_save">Autom. Backup beim Notiz Verlassen</string>
|
<string name="auto_backup_on_save">Autom. Backup beim Notiz Verlassen</string>
|
||||||
<string name="auto_backup_on_save_hint">Ist dies aktiviert, wird jedesmal wenn eine Notiz verlassen wird autom. ein Backup (\"NotallyX_AutoBackup.zip\") im \"Backups Ordner\" erstellt.\nDies kann die App-Performance beeinträchtigen</string>
|
<string name="auto_backup_on_save_hint">Ist dies aktiviert, wird jedesmal wenn eine Notiz verlassen wird autom. ein Backup (\"NotallyX_AutoBackup.zip\") im \"Backups Ordner\" erstellt.\nDies kann die App-Performance beeinträchtigen</string>
|
||||||
<string name="auto_backup_period">Intervall für autom. Backups</string>
|
|
||||||
<string name="auto_backups_folder">Backups Ordner</string>
|
<string name="auto_backups_folder">Backups Ordner</string>
|
||||||
<string name="auto_backups_folder_hint">Ordner in dem alle autom. Backups erstellt werden</string>
|
<string name="auto_backups_folder_hint">Ordner in dem alle autom. Backups erstellt werden</string>
|
||||||
<string name="auto_backups_folder_rechoose">Der Backups Ordner muss erneut ausgewählt werden, damit NotallyX Schreibrechte dafür erhält.\nÜber Abbrechen kann der Import dieser Einstellung übersprungen werden.</string>
|
<string name="auto_backups_folder_rechoose">Der Backups Ordner muss erneut ausgewählt werden, damit NotallyX Schreibrechte dafür erhält.\nÜber Abbrechen kann der Import dieser Einstellung übersprungen werden.</string>
|
||||||
<string name="auto_backups_folder_set">Stelle erst ein Backups Ordner ein</string>
|
<string name="auto_backups_folder_set">Stelle erst ein Backups Ordner ein</string>
|
||||||
|
<string name="auto_save_after_idle_time">Autom. Speichern nach X Sekunden</string>
|
||||||
<string name="auto_sort_by_checked">Abgehakte Elemente ans Ende sortieren</string>
|
<string name="auto_sort_by_checked">Abgehakte Elemente ans Ende sortieren</string>
|
||||||
<string name="back">Zurück</string>
|
<string name="back">Zurück</string>
|
||||||
<string name="backup">Backup</string>
|
<string name="backup">Backup</string>
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
<string name="backup_period_days">Auto. Backup Intervall in Tagen</string>
|
<string name="backup_period_days">Auto. Backup Intervall in Tagen</string>
|
||||||
<string name="backup_periodic">Periodische Backups</string>
|
<string name="backup_periodic">Periodische Backups</string>
|
||||||
<string name="backup_periodic_hint">Ist dies aktiviert, werden autom. Backups im eingestellten Backups Ordner erstellt. \nDies könnte fehlschlagen wenn Energiesparmodus aktiviert ist</string>
|
<string name="backup_periodic_hint">Ist dies aktiviert, werden autom. Backups im eingestellten Backups Ordner erstellt. \nDies könnte fehlschlagen wenn Energiesparmodus aktiviert ist</string>
|
||||||
|
<string name="behaviour">Funktionsweise</string>
|
||||||
<string name="biometric_lock">App per Biometrie/PIN sperren</string>
|
<string name="biometric_lock">App per Biometrie/PIN sperren</string>
|
||||||
<string name="biometrics_disable_success">Biometrische/PIN Sperre deaktiviert</string>
|
<string name="biometrics_disable_success">Biometrische/PIN Sperre deaktiviert</string>
|
||||||
<string name="biometrics_failure">Fehler bei Authentifizierung per Biometrie/PIN</string>
|
<string name="biometrics_failure">Fehler bei Authentifizierung per Biometrie/PIN</string>
|
||||||
|
@ -75,6 +76,8 @@
|
||||||
<string name="color_exists">Diese Farbe existiert bereits!</string>
|
<string name="color_exists">Diese Farbe existiert bereits!</string>
|
||||||
<string name="content_density">Notizvorschau</string>
|
<string name="content_density">Notizvorschau</string>
|
||||||
<string name="continue_">Weiter</string>
|
<string name="continue_">Weiter</string>
|
||||||
|
<string name="convert_to_list_note">Zu Liste konvertieren</string>
|
||||||
|
<string name="convert_to_text_note">Zu Text-Notiz konvertieren</string>
|
||||||
<string name="copied_link">Link kopiert</string>
|
<string name="copied_link">Link kopiert</string>
|
||||||
<string name="copy">Kopieren</string>
|
<string name="copy">Kopieren</string>
|
||||||
<string name="crash_message">Ein unerwarteter Fehler ist aufgetreten.\nEntschuldigung für die Unannehmlichkeiten.\n</string>
|
<string name="crash_message">Ein unerwarteter Fehler ist aufgetreten.\nEntschuldigung für die Unannehmlichkeiten.\n</string>
|
||||||
|
@ -116,6 +119,7 @@
|
||||||
<string name="disable_lock_description">Dies entschlüsselt außerdem die Datenbank</string>
|
<string name="disable_lock_description">Dies entschlüsselt außerdem die Datenbank</string>
|
||||||
<string name="disable_lock_title">Deaktivierte Biometrie/PIN Sperre</string>
|
<string name="disable_lock_title">Deaktivierte Biometrie/PIN Sperre</string>
|
||||||
<string name="disabled">Deaktiviert</string>
|
<string name="disabled">Deaktiviert</string>
|
||||||
|
<string name="disallow_screenshots">Screenshots verbieten</string>
|
||||||
<string name="discard">Verwerfen</string>
|
<string name="discard">Verwerfen</string>
|
||||||
<string name="display_text">Anzeigetext</string>
|
<string name="display_text">Anzeigetext</string>
|
||||||
<string name="donate">Spende</string>
|
<string name="donate">Spende</string>
|
||||||
|
@ -155,6 +159,8 @@
|
||||||
<string name="help">Hilfe</string>
|
<string name="help">Hilfe</string>
|
||||||
<string name="hours">Stunden</string>
|
<string name="hours">Stunden</string>
|
||||||
<string name="image_format_not_supported">Bildformat nicht unterstützt</string>
|
<string name="image_format_not_supported">Bildformat nicht unterstützt</string>
|
||||||
|
<string name="images_hidden_in_overview">Ist dies aktiviert, werden die Bilder der Notizen in der Übersicht ausgeblendet</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Verberge Bilder in Übersicht</string>
|
||||||
<string name="import_action">Import</string>
|
<string name="import_action">Import</string>
|
||||||
<string name="import_backup">Backup importieren</string>
|
<string name="import_backup">Backup importieren</string>
|
||||||
<string name="import_backup_password_hint">Falls das Backup nicht passwortgeschützt ist, drücken Sie einfach auf Importieren, andernfalls geben Sie das richtige Passwort ein.</string>
|
<string name="import_backup_password_hint">Falls das Backup nicht passwortgeschützt ist, drücken Sie einfach auf Importieren, andernfalls geben Sie das richtige Passwort ein.</string>
|
||||||
|
@ -182,6 +188,8 @@
|
||||||
<string name="invalid_link">Kopiere einen validen Link</string>
|
<string name="invalid_link">Kopiere einen validen Link</string>
|
||||||
<string name="italic">Kursiv</string>
|
<string name="italic">Kursiv</string>
|
||||||
<string name="item">Eintrag</string>
|
<string name="item">Eintrag</string>
|
||||||
|
<string name="json_files">JSON Dateien</string>
|
||||||
|
<string name="json_files_help">Um deine JSON-Notizen (einzele Datei oder Ordner) zu importieren, klicke Import.\nJede valide Datei wird als einzelne Notiz importiert, der Dateiname wird zum Notiz-Titel.</string>
|
||||||
<string name="label_exists">Label existiert bereits</string>
|
<string name="label_exists">Label existiert bereits</string>
|
||||||
<string name="label_visibility">Zeige/Verberge das Label in der Navigation</string>
|
<string name="label_visibility">Zeige/Verberge das Label in der Navigation</string>
|
||||||
<string name="labels">Labels</string>
|
<string name="labels">Labels</string>
|
||||||
|
@ -236,6 +244,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">Wenn ein autom. Backup fehlschlägt kann eine Benachrichtigung geschickt werden, wenn du NotallyX erlaubst Benachrichtigungen zu schicken</string>
|
<string name="please_grant_notally_notification_auto_backup">Wenn ein autom. Backup fehlschlägt kann eine Benachrichtigung geschickt werden, wenn du NotallyX erlaubst Benachrichtigungen zu schicken</string>
|
||||||
<string name="previous">Vorherige</string>
|
<string name="previous">Vorherige</string>
|
||||||
<string name="rate">App bewerten</string>
|
<string name="rate">App bewerten</string>
|
||||||
|
<string name="read_only">Lesemodus</string>
|
||||||
<string name="ready_to_record">Bereit zum Aufnehmen</string>
|
<string name="ready_to_record">Bereit zum Aufnehmen</string>
|
||||||
<string name="record_audio">Sprachnotiz aufnehmen</string>
|
<string name="record_audio">Sprachnotiz aufnehmen</string>
|
||||||
<string name="recording">Nimmt auf...</string>
|
<string name="recording">Nimmt auf...</string>
|
||||||
|
@ -290,6 +299,7 @@
|
||||||
<string name="text_default">Standard</string>
|
<string name="text_default">Standard</string>
|
||||||
<string name="text_size">Textgröße</string>
|
<string name="text_size">Textgröße</string>
|
||||||
<string name="theme">Design</string>
|
<string name="theme">Design</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Systemhintergrundfarben verwenden</string>
|
||||||
<string name="title">Titel</string>
|
<string name="title">Titel</string>
|
||||||
<string name="to_record_audio">Um Sprachnotizen zu erstellen, braucht NotallyX Zugriff auf dein Mikrofon. Klicke Einstellunge > Berechtigungen und aktiviere Mikrofon</string>
|
<string name="to_record_audio">Um Sprachnotizen zu erstellen, braucht NotallyX Zugriff auf dein Mikrofon. Klicke Einstellunge > Berechtigungen und aktiviere Mikrofon</string>
|
||||||
<string name="unarchive">Archivierung aufheben</string>
|
<string name="unarchive">Archivierung aufheben</string>
|
||||||
|
|
|
@ -24,11 +24,11 @@
|
||||||
<string name="auto_backup_last">Última copia de seguridad</string>
|
<string name="auto_backup_last">Última copia de seguridad</string>
|
||||||
<string name="auto_backup_on_save">Copia de seguridad automática al salir una nota</string>
|
<string name="auto_backup_on_save">Copia de seguridad automática al salir una nota</string>
|
||||||
<string name="auto_backup_on_save_hint">Al habilitar esta opción, se crea automáticamente una copia de seguridad (\"NotallyX_AutoBackup.zip\") en la \"Directorio de copias de seguridad\" configurada cada vez que se guarda una nota.\nTenga en cuenta que esto puede afectar el rendimiento.</string>
|
<string name="auto_backup_on_save_hint">Al habilitar esta opción, se crea automáticamente una copia de seguridad (\"NotallyX_AutoBackup.zip\") en la \"Directorio de copias de seguridad\" configurada cada vez que se guarda una nota.\nTenga en cuenta que esto puede afectar el rendimiento.</string>
|
||||||
<string name="auto_backup_period">Periodo de copia automática</string>
|
|
||||||
<string name="auto_backups_folder">Directorio de copias de seguridad</string>
|
<string name="auto_backups_folder">Directorio de copias de seguridad</string>
|
||||||
<string name="auto_backups_folder_hint">Carpeta donde se guardan las copias de seguridad automáticas.</string>
|
<string name="auto_backups_folder_hint">Carpeta donde se guardan las copias de seguridad automáticas.</string>
|
||||||
<string name="auto_backups_folder_rechoose">Debe volver a elegir su directorio de copias de seguridad para que NotallyX tenga permiso para escribir en ella.\nTambién puede presionar Cancelar para omitir la importación del valor del directorio de copias de seguridad</string>
|
<string name="auto_backups_folder_rechoose">Debe volver a elegir su directorio de copias de seguridad para que NotallyX tenga permiso para escribir en ella.\nTambién puede presionar Cancelar para omitir la importación del valor del directorio de copias de seguridad</string>
|
||||||
<string name="auto_backups_folder_set">Establece primero la carpeta de copia en opción anterior</string>
|
<string name="auto_backups_folder_set">Establece primero la carpeta de copia en opción anterior</string>
|
||||||
|
<string name="auto_save_after_idle_time">Autoguardar nota después de un lapso de inactividad</string>
|
||||||
<string name="auto_sort_by_checked">Ordenar con los elementos marcados al final</string>
|
<string name="auto_sort_by_checked">Ordenar con los elementos marcados al final</string>
|
||||||
<string name="back">Volver</string>
|
<string name="back">Volver</string>
|
||||||
<string name="backup">Respaldar</string>
|
<string name="backup">Respaldar</string>
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
<string name="backup_period_days">Periodo de copia automática en días</string>
|
<string name="backup_period_days">Periodo de copia automática en días</string>
|
||||||
<string name="backup_periodic">Copias de seguridad periódicas</string>
|
<string name="backup_periodic">Copias de seguridad periódicas</string>
|
||||||
<string name="backup_periodic_hint">Al habilitar esta opción, las copias de seguridad se crean automáticamente en el directorio de copias de seguridad configurada.\nEs posible que esto no funcione si tiene habilitado el modo de ahorro de energía.</string>
|
<string name="backup_periodic_hint">Al habilitar esta opción, las copias de seguridad se crean automáticamente en el directorio de copias de seguridad configurada.\nEs posible que esto no funcione si tiene habilitado el modo de ahorro de energía.</string>
|
||||||
|
<string name="behaviour">Comportamiento</string>
|
||||||
<string name="biometric_lock">Bloquear aplicación con dispositivo biométrico o con PIN</string>
|
<string name="biometric_lock">Bloquear aplicación con dispositivo biométrico o con PIN</string>
|
||||||
<string name="biometrics_disable_success">El bloqueo biométrico/PIN ha sido deshabilitado</string>
|
<string name="biometrics_disable_success">El bloqueo biométrico/PIN ha sido deshabilitado</string>
|
||||||
<string name="biometrics_failure">Falló la autenticación biómetrica/PIN</string>
|
<string name="biometrics_failure">Falló la autenticación biómetrica/PIN</string>
|
||||||
|
@ -71,9 +72,12 @@
|
||||||
<string name="clear_data_message">Todas las notas, imágenes, ficheros, audios serán borrados permanentemente</string>
|
<string name="clear_data_message">Todas las notas, imágenes, ficheros, audios serán borrados permanentemente</string>
|
||||||
<string name="clear_formatting">Borrar formato</string>
|
<string name="clear_formatting">Borrar formato</string>
|
||||||
<string name="cleared_data">Se han limpiado todos los datos</string>
|
<string name="cleared_data">Se han limpiado todos los datos</string>
|
||||||
|
<string name="color">Color</string>
|
||||||
<string name="color_exists">¡Este color ya existe!</string>
|
<string name="color_exists">¡Este color ya existe!</string>
|
||||||
<string name="content_density">Densidad de contenido</string>
|
<string name="content_density">Densidad de contenido</string>
|
||||||
<string name="continue_">Continuar</string>
|
<string name="continue_">Continuar</string>
|
||||||
|
<string name="convert_to_list_note">Convertir en lista</string>
|
||||||
|
<string name="convert_to_text_note">Convertir en nota de texto</string>
|
||||||
<string name="copied_link">Enlace copiado al portapapeles</string>
|
<string name="copied_link">Enlace copiado al portapapeles</string>
|
||||||
<string name="copy">Copiar</string>
|
<string name="copy">Copiar</string>
|
||||||
<string name="crash_message">Se produjo un error inesperado.\nDisculpe las molestias.</string>
|
<string name="crash_message">Se produjo un error inesperado.\nDisculpe las molestias.</string>
|
||||||
|
@ -115,6 +119,7 @@
|
||||||
<string name="disable_lock_description">Esto también descifrará la base de datos</string>
|
<string name="disable_lock_description">Esto también descifrará la base de datos</string>
|
||||||
<string name="disable_lock_title">Deshabilitar bloqueo biométrico/PIN</string>
|
<string name="disable_lock_title">Deshabilitar bloqueo biométrico/PIN</string>
|
||||||
<string name="disabled">Deshabilitado</string>
|
<string name="disabled">Deshabilitado</string>
|
||||||
|
<string name="disallow_screenshots">No permitir capturas de pantalla</string>
|
||||||
<string name="discard">Descartar</string>
|
<string name="discard">Descartar</string>
|
||||||
<string name="display_text">Texto a pantalla</string>
|
<string name="display_text">Texto a pantalla</string>
|
||||||
<string name="donate">Hacer donación</string>
|
<string name="donate">Hacer donación</string>
|
||||||
|
@ -154,6 +159,8 @@
|
||||||
<string name="help">Ayuda</string>
|
<string name="help">Ayuda</string>
|
||||||
<string name="hours">Horas</string>
|
<string name="hours">Horas</string>
|
||||||
<string name="image_format_not_supported">Formato de imagen no soportado</string>
|
<string name="image_format_not_supported">Formato de imagen no soportado</string>
|
||||||
|
<string name="images_hidden_in_overview">Al habilitar esta opción, se ocultarán las imágenes de notas de la descripción general.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Ocultar imágenes en vista general</string>
|
||||||
<string name="import_action">Importar</string>
|
<string name="import_action">Importar</string>
|
||||||
<string name="import_backup">Importar copia de seguridad</string>
|
<string name="import_backup">Importar copia de seguridad</string>
|
||||||
<string name="import_backup_password_hint">Si su copia de seguridad no está protegida con contraseña, simplemente presione Importar, de lo contrario ingrese la contraseña correcta.</string>
|
<string name="import_backup_password_hint">Si su copia de seguridad no está protegida con contraseña, simplemente presione Importar, de lo contrario ingrese la contraseña correcta.</string>
|
||||||
|
@ -181,6 +188,8 @@
|
||||||
<string name="invalid_link">Copiar un enlace válido en tu portapapeles</string>
|
<string name="invalid_link">Copiar un enlace válido en tu portapapeles</string>
|
||||||
<string name="italic">Cursiva</string>
|
<string name="italic">Cursiva</string>
|
||||||
<string name="item">Elemento</string>
|
<string name="item">Elemento</string>
|
||||||
|
<string name="json_files">Ficheros JSON</string>
|
||||||
|
<string name="json_files_help">Para importar tus notas desde ficheros JSON (fichero único o carpeta), pincha en Importar. Cada fichero fichero JSON válido se importa como nota separada, el nombre del fichero será el título de la nota.</string>
|
||||||
<string name="label_exists">Etiqueta existe</string>
|
<string name="label_exists">Etiqueta existe</string>
|
||||||
<string name="label_visibility">Ocultar/mostrar etiqueta en el panel de navegación</string>
|
<string name="label_visibility">Ocultar/mostrar etiqueta en el panel de navegación</string>
|
||||||
<string name="labels">Etiquetas</string>
|
<string name="labels">Etiquetas</string>
|
||||||
|
@ -235,6 +244,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">Si falla una copia de seguridad automática, puede recibir una notificación, si otorga permiso a NotallyX para enviar notificaciones</string>
|
<string name="please_grant_notally_notification_auto_backup">Si falla una copia de seguridad automática, puede recibir una notificación, si otorga permiso a NotallyX para enviar notificaciones</string>
|
||||||
<string name="previous">Anterior</string>
|
<string name="previous">Anterior</string>
|
||||||
<string name="rate">Calificar esta app</string>
|
<string name="rate">Calificar esta app</string>
|
||||||
|
<string name="read_only">Solo lectura</string>
|
||||||
<string name="ready_to_record">Preparado para grabar</string>
|
<string name="ready_to_record">Preparado para grabar</string>
|
||||||
<string name="record_audio">Grabar audio</string>
|
<string name="record_audio">Grabar audio</string>
|
||||||
<string name="recording">Grabando…</string>
|
<string name="recording">Grabando…</string>
|
||||||
|
@ -289,6 +299,7 @@
|
||||||
<string name="text_default">Por defecto</string>
|
<string name="text_default">Por defecto</string>
|
||||||
<string name="text_size">Tamaño de texto</string>
|
<string name="text_size">Tamaño de texto</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Usar color de fondo del sistema</string>
|
||||||
<string name="title">Título</string>
|
<string name="title">Título</string>
|
||||||
<string name="to_record_audio">Para grabar audio, permita que NotallyX acceda a su micrófono. Pulsa Configuración > Permisos y active el micrófono.</string>
|
<string name="to_record_audio">Para grabar audio, permita que NotallyX acceda a su micrófono. Pulsa Configuración > Permisos y active el micrófono.</string>
|
||||||
<string name="unarchive">Desarchivar</string>
|
<string name="unarchive">Desarchivar</string>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<string name="auto_backups_folder_hint">Dossier dans lequel toutes les sauvegardes automatiques seront stockées.</string>
|
<string name="auto_backups_folder_hint">Dossier dans lequel toutes les sauvegardes automatiques seront stockées.</string>
|
||||||
<string name="auto_backups_folder_rechoose">Vous devez re-sélectionner votre dossier de sauvegarde afin que NotallyX ait l\'autorisation d\'y écrire.\nVous pouvez également appuyer sur Annuler pour ignorer l\'importation du chemin du dossier de sauvegarde.</string>
|
<string name="auto_backups_folder_rechoose">Vous devez re-sélectionner votre dossier de sauvegarde afin que NotallyX ait l\'autorisation d\'y écrire.\nVous pouvez également appuyer sur Annuler pour ignorer l\'importation du chemin du dossier de sauvegarde.</string>
|
||||||
<string name="auto_backups_folder_set">Configurer d\'abord le Dossier des sauvegardes</string>
|
<string name="auto_backups_folder_set">Configurer d\'abord le Dossier des sauvegardes</string>
|
||||||
|
<string name="auto_save_after_idle_time">Enregistrer la note après un temps d’inactivité défini</string>
|
||||||
<string name="auto_sort_by_checked">Trier les éléments cochés à la fin</string>
|
<string name="auto_sort_by_checked">Trier les éléments cochés à la fin</string>
|
||||||
<string name="back">Retour</string>
|
<string name="back">Retour</string>
|
||||||
<string name="backup">Sauvegarde</string>
|
<string name="backup">Sauvegarde</string>
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
<string name="backup_period_days">Fréquence de sauvegarde périodique en jours</string>
|
<string name="backup_period_days">Fréquence de sauvegarde périodique en jours</string>
|
||||||
<string name="backup_periodic">Sauvegardes périodiques</string>
|
<string name="backup_periodic">Sauvegardes périodiques</string>
|
||||||
<string name="backup_periodic_hint">En activant cette option, des sauvegardes sont automatiquement créées dans le Dossier de sauvegardes configuré.\nCela peut ne pas fonctionner si le mode économie d\'énergie est activé. </string>
|
<string name="backup_periodic_hint">En activant cette option, des sauvegardes sont automatiquement créées dans le Dossier de sauvegardes configuré.\nCela peut ne pas fonctionner si le mode économie d\'énergie est activé. </string>
|
||||||
|
<string name="behaviour">Comportement</string>
|
||||||
<string name="biometric_lock">Verrouiller l\'application avec la biométrie ou le code PIN de l\'appareil</string>
|
<string name="biometric_lock">Verrouiller l\'application avec la biométrie ou le code PIN de l\'appareil</string>
|
||||||
<string name="biometrics_disable_success">Le verrouillage biométrique/code PIN a été désactivé</string>
|
<string name="biometrics_disable_success">Le verrouillage biométrique/code PIN a été désactivé</string>
|
||||||
<string name="biometrics_failure">Échec de l\'authentification biométrique/code PIN</string>
|
<string name="biometrics_failure">Échec de l\'authentification biométrique/code PIN</string>
|
||||||
|
@ -74,6 +76,8 @@
|
||||||
<string name="color_exists">Cette couleur existe déjà !</string>
|
<string name="color_exists">Cette couleur existe déjà !</string>
|
||||||
<string name="content_density">Densité d\'affichage</string>
|
<string name="content_density">Densité d\'affichage</string>
|
||||||
<string name="continue_">Continuer</string>
|
<string name="continue_">Continuer</string>
|
||||||
|
<string name="convert_to_list_note">Convertir en liste</string>
|
||||||
|
<string name="convert_to_text_note">Convertir en note simple</string>
|
||||||
<string name="copied_link">Lien copié dans le presse-papier</string>
|
<string name="copied_link">Lien copié dans le presse-papier</string>
|
||||||
<string name="copy">Copier</string>
|
<string name="copy">Copier</string>
|
||||||
<string name="crash_message">Une erreur inattendue s\'est produite.\nDésolé pour le désagrément.</string>
|
<string name="crash_message">Une erreur inattendue s\'est produite.\nDésolé pour le désagrément.</string>
|
||||||
|
@ -115,6 +119,7 @@
|
||||||
<string name="disable_lock_description">La base de donnée sera aussi décryptée</string>
|
<string name="disable_lock_description">La base de donnée sera aussi décryptée</string>
|
||||||
<string name="disable_lock_title">Désactiver le verrouillage biométrique/code PIN</string>
|
<string name="disable_lock_title">Désactiver le verrouillage biométrique/code PIN</string>
|
||||||
<string name="disabled">Désactivé</string>
|
<string name="disabled">Désactivé</string>
|
||||||
|
<string name="disallow_screenshots">Bloquer les captures d’écran</string>
|
||||||
<string name="discard">Supprimer</string>
|
<string name="discard">Supprimer</string>
|
||||||
<string name="display_text">Texte à afficher</string>
|
<string name="display_text">Texte à afficher</string>
|
||||||
<string name="donate">Faire un don</string>
|
<string name="donate">Faire un don</string>
|
||||||
|
@ -154,6 +159,8 @@
|
||||||
<string name="help">Aide</string>
|
<string name="help">Aide</string>
|
||||||
<string name="hours">Heures</string>
|
<string name="hours">Heures</string>
|
||||||
<string name="image_format_not_supported">Format d\'image non supporté</string>
|
<string name="image_format_not_supported">Format d\'image non supporté</string>
|
||||||
|
<string name="images_hidden_in_overview">En activant cette option, les images des notes seront masquées dans l\'aperçu</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Masquer les images dans la vue d\'ensemble</string>
|
||||||
<string name="import_action">Importer</string>
|
<string name="import_action">Importer</string>
|
||||||
<string name="import_backup">Importer une sauvegarde</string>
|
<string name="import_backup">Importer une sauvegarde</string>
|
||||||
<string name="import_backup_password_hint">Si votre sauvegarde n\'est pas protégée par mot de passe, cliquez seulement sur \"Importer une sauvegarde\", sinon entrez le mot de passe correspondant.</string>
|
<string name="import_backup_password_hint">Si votre sauvegarde n\'est pas protégée par mot de passe, cliquez seulement sur \"Importer une sauvegarde\", sinon entrez le mot de passe correspondant.</string>
|
||||||
|
@ -181,6 +188,8 @@
|
||||||
<string name="invalid_link">Copier un lien valide dans le presse-papier</string>
|
<string name="invalid_link">Copier un lien valide dans le presse-papier</string>
|
||||||
<string name="italic">Italique</string>
|
<string name="italic">Italique</string>
|
||||||
<string name="item">Élément</string>
|
<string name="item">Élément</string>
|
||||||
|
<string name="json_files">Fichiers JSON</string>
|
||||||
|
<string name="json_files_help">Pour importer vos notes depuis des fichiers JSON (fichier unique ou dossier), cliquez sur Importer. Chaque fichier JSON valide sera importé en tant que note individuelle et son nom de fichier deviendra le titre de la note.</string>
|
||||||
<string name="label_exists">L\'étiquette existe déjà</string>
|
<string name="label_exists">L\'étiquette existe déjà</string>
|
||||||
<string name="label_visibility">Masquer/Afficher l\'étiquette dans le panneau de navigation</string>
|
<string name="label_visibility">Masquer/Afficher l\'étiquette dans le panneau de navigation</string>
|
||||||
<string name="labels">Étiquettes</string>
|
<string name="labels">Étiquettes</string>
|
||||||
|
@ -235,6 +244,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">Si une sauvegarde automatique échoue, vous pouvez recevoir une notification en accordant à NotallyX l\'autorisation d\'envoyer des notifications</string>
|
<string name="please_grant_notally_notification_auto_backup">Si une sauvegarde automatique échoue, vous pouvez recevoir une notification en accordant à NotallyX l\'autorisation d\'envoyer des notifications</string>
|
||||||
<string name="previous">Précédent</string>
|
<string name="previous">Précédent</string>
|
||||||
<string name="rate">Noter cette application</string>
|
<string name="rate">Noter cette application</string>
|
||||||
|
<string name="read_only">Lecture seule</string>
|
||||||
<string name="ready_to_record">Prêt à enregistrer</string>
|
<string name="ready_to_record">Prêt à enregistrer</string>
|
||||||
<string name="record_audio">Enregistrer un audio</string>
|
<string name="record_audio">Enregistrer un audio</string>
|
||||||
<string name="recording">Enregistrement en cours…</string>
|
<string name="recording">Enregistrement en cours…</string>
|
||||||
|
@ -289,6 +299,7 @@
|
||||||
<string name="text_default">Défaut</string>
|
<string name="text_default">Défaut</string>
|
||||||
<string name="text_size">Taille du texte</string>
|
<string name="text_size">Taille du texte</string>
|
||||||
<string name="theme">Thème</string>
|
<string name="theme">Thème</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Utiliser les couleurs de fond d’écran du système</string>
|
||||||
<string name="title">Titre</string>
|
<string name="title">Titre</string>
|
||||||
<string name="to_record_audio">Pour enregistrer de l\'audio, autorisez NotallyX à accéder à votre microphone. Cliquez sur Paramètres > Autorisations et activez le microphone.</string>
|
<string name="to_record_audio">Pour enregistrer de l\'audio, autorisez NotallyX à accéder à votre microphone. Cliquez sur Paramètres > Autorisations et activez le microphone.</string>
|
||||||
<string name="unarchive">Restaurer</string>
|
<string name="unarchive">Restaurer</string>
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
<string name="auto_backup_last">Ultimo backup</string>
|
<string name="auto_backup_last">Ultimo backup</string>
|
||||||
<string name="auto_backup_on_save">Backup automatico dopo l\'uscita dalla nota</string>
|
<string name="auto_backup_on_save">Backup automatico dopo l\'uscita dalla nota</string>
|
||||||
<string name="auto_backup_on_save_hint">Abilitando questa opzione, un backup (NotallyX_AutoBackup.zip) verrà creato automaticamente nella \"Cartella di Backup\" configurata ogni volta che una nota viene salvata.\nTieni presente che questa operazione potrebbe influire sulle prestazioni</string>
|
<string name="auto_backup_on_save_hint">Abilitando questa opzione, un backup (NotallyX_AutoBackup.zip) verrà creato automaticamente nella \"Cartella di Backup\" configurata ogni volta che una nota viene salvata.\nTieni presente che questa operazione potrebbe influire sulle prestazioni</string>
|
||||||
<string name="auto_backup_period">Frequenza dei backup automatici</string>
|
|
||||||
<string name="auto_backups_folder">Cartella backup</string>
|
<string name="auto_backups_folder">Cartella backup</string>
|
||||||
<string name="auto_backups_folder_hint">Cartella in cui tutti i backup automatici verranno salvati.</string>
|
<string name="auto_backups_folder_hint">Cartella in cui tutti i backup automatici verranno salvati.</string>
|
||||||
<string name="auto_backups_folder_rechoose">Devi selezionare nuovamente la tua cartella di backup affinché NotallyX abbia l\'autorizzazione per scriverci.\nPuoi anche premere annulla per saltare l\'importazione del valore della cartella di backup.</string>
|
<string name="auto_backups_folder_rechoose">Devi selezionare nuovamente la tua cartella di backup affinché NotallyX abbia l\'autorizzazione per scriverci.\nPuoi anche premere annulla per saltare l\'importazione del valore della cartella di backup.</string>
|
||||||
|
@ -154,6 +153,8 @@
|
||||||
<string name="help">Aiuto</string>
|
<string name="help">Aiuto</string>
|
||||||
<string name="hours">Ore</string>
|
<string name="hours">Ore</string>
|
||||||
<string name="image_format_not_supported">Formato immagine non supportato</string>
|
<string name="image_format_not_supported">Formato immagine non supportato</string>
|
||||||
|
<string name="images_hidden_in_overview">Abilitando questa opzione le immagini delle note verranno nascoste nella panoramica.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Nascondi immagini nella panoramica</string>
|
||||||
<string name="import_action">Importa</string>
|
<string name="import_action">Importa</string>
|
||||||
<string name="import_backup">Importa backup</string>
|
<string name="import_backup">Importa backup</string>
|
||||||
<string name="import_backup_password_hint">Se il tuo backup non è protetto da password premi semplicemente Importa, altrimenti inserisci la password corretta.</string>
|
<string name="import_backup_password_hint">Se il tuo backup non è protetto da password premi semplicemente Importa, altrimenti inserisci la password corretta.</string>
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
<string name="attach_file">Bestand bijvoegen</string>
|
<string name="attach_file">Bestand bijvoegen</string>
|
||||||
<string name="audio_recordings">Audio-opnames</string>
|
<string name="audio_recordings">Audio-opnames</string>
|
||||||
<string name="auto_backup">Automatische back-up</string>
|
<string name="auto_backup">Automatische back-up</string>
|
||||||
<string name="auto_backup_period">Periode van automatische back-up</string>
|
|
||||||
<string name="auto_sort_by_checked">Sorteer de aangevinkte items tot het einde</string>
|
<string name="auto_sort_by_checked">Sorteer de aangevinkte items tot het einde</string>
|
||||||
<string name="backup">Back-up</string>
|
<string name="backup">Back-up</string>
|
||||||
<string name="backup_password">Backup Wachtwoord</string>
|
<string name="backup_password">Backup Wachtwoord</string>
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
<string name="archive">Zarchwizuj</string>
|
<string name="archive">Zarchwizuj</string>
|
||||||
<string name="archived">Zarchiwizowane</string>
|
<string name="archived">Zarchiwizowane</string>
|
||||||
<plurals name="archived_selected_notes">
|
<plurals name="archived_selected_notes">
|
||||||
|
<item quantity="few">Zarchiwizowano %1$d notatki</item>
|
||||||
|
<item quantity="many">Zarchiwizowano %1$d notatek</item>
|
||||||
<item quantity="one">Zarchiwizowano %1$d notatkę</item>
|
<item quantity="one">Zarchiwizowano %1$d notatkę</item>
|
||||||
<item quantity="other">Zarchiwizowano %1$d notatek</item>
|
<item quantity="other">Zarchiwizowano %1$d notatek</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -24,11 +26,11 @@
|
||||||
<string name="auto_backup_last">Ostatnia kopia zapasowa</string>
|
<string name="auto_backup_last">Ostatnia kopia zapasowa</string>
|
||||||
<string name="auto_backup_on_save">Kopia zapasowa po wyjściu z notatki</string>
|
<string name="auto_backup_on_save">Kopia zapasowa po wyjściu z notatki</string>
|
||||||
<string name="auto_backup_on_save_hint">Po włączeniu tej opcji kopia zapasowa („NotallyX_AutoBackup.zip”) jest automatycznie tworzona w skonfigurowanym „Folderze kopii zapasowych” za każdym razem, gdy zapisywana jest notatka.\nPamiętaj, że może mieć to wpływ na wydajność</string>
|
<string name="auto_backup_on_save_hint">Po włączeniu tej opcji kopia zapasowa („NotallyX_AutoBackup.zip”) jest automatycznie tworzona w skonfigurowanym „Folderze kopii zapasowych” za każdym razem, gdy zapisywana jest notatka.\nPamiętaj, że może mieć to wpływ na wydajność</string>
|
||||||
<string name="auto_backup_period">Okres tworzenia automatycznych kopii zapasowych</string>
|
|
||||||
<string name="auto_backups_folder">Folder kopii zapasowych</string>
|
<string name="auto_backups_folder">Folder kopii zapasowych</string>
|
||||||
<string name="auto_backups_folder_hint">Folder, w którym będą przechowywane wszystkie automatyczne kopie zapasowe.</string>
|
<string name="auto_backups_folder_hint">Folder, w którym będą przechowywane wszystkie automatyczne kopie zapasowe.</string>
|
||||||
<string name="auto_backups_folder_rechoose">Musisz ponownie wybrać folder kopii zapasowych, aby NotallyX miał uprawnienia do zapisu w nim.\nMożesz także nacisnąć Anuluj, aby pominąć importowanie zawartości folderu kopii zapasowych</string>
|
<string name="auto_backups_folder_rechoose">Musisz ponownie wybrać folder kopii zapasowych, aby NotallyX miał uprawnienia do zapisu w nim.\nMożesz także nacisnąć Anuluj, aby pominąć importowanie zawartości folderu kopii zapasowych</string>
|
||||||
<string name="auto_backups_folder_set">Najpierw skonfiguruj folder kopii zapasowych powyżej</string>
|
<string name="auto_backups_folder_set">Najpierw skonfiguruj folder kopii zapasowych powyżej</string>
|
||||||
|
<string name="auto_save_after_idle_time">Automatycznie zapisz notatkę po określonym czasie bezczynności</string>
|
||||||
<string name="auto_sort_by_checked">Przenieś zaznaczone elementy na koniec</string>
|
<string name="auto_sort_by_checked">Przenieś zaznaczone elementy na koniec</string>
|
||||||
<string name="back">Cofnij</string>
|
<string name="back">Cofnij</string>
|
||||||
<string name="backup">Kopia zapasowa</string>
|
<string name="backup">Kopia zapasowa</string>
|
||||||
|
@ -37,6 +39,7 @@
|
||||||
<string name="backup_period_days">Częstotliwość tworzenia automatycznych kopii zapasowych w dniach</string>
|
<string name="backup_period_days">Częstotliwość tworzenia automatycznych kopii zapasowych w dniach</string>
|
||||||
<string name="backup_periodic">Powtarzalne kopie zapasowe</string>
|
<string name="backup_periodic">Powtarzalne kopie zapasowe</string>
|
||||||
<string name="backup_periodic_hint">Po włączeniu tej opcji kopie zapasowe są automatycznie tworzone w skonfigurowanym folderze kopii zapasowych.\nMoże to nie działać, jeśli włączony jest tryb oszczędzania energii</string>
|
<string name="backup_periodic_hint">Po włączeniu tej opcji kopie zapasowe są automatycznie tworzone w skonfigurowanym folderze kopii zapasowych.\nMoże to nie działać, jeśli włączony jest tryb oszczędzania energii</string>
|
||||||
|
<string name="behaviour">Zachowanie</string>
|
||||||
<string name="biometric_lock">Zablokuj aplikację za pomocą danych biometrycznych lub PIN-u urządzenia</string>
|
<string name="biometric_lock">Zablokuj aplikację za pomocą danych biometrycznych lub PIN-u urządzenia</string>
|
||||||
<string name="biometrics_disable_success">Blokada biometryczna/PIN została wyłączona</string>
|
<string name="biometrics_disable_success">Blokada biometryczna/PIN została wyłączona</string>
|
||||||
<string name="biometrics_failure">Nie udało się uwierzytelnić za pomocą biometrii/PIN-u</string>
|
<string name="biometrics_failure">Nie udało się uwierzytelnić za pomocą biometrii/PIN-u</string>
|
||||||
|
@ -47,6 +50,8 @@
|
||||||
<string name="calculating">Obliczanie…</string>
|
<string name="calculating">Obliczanie…</string>
|
||||||
<string name="cancel">Anuluj</string>
|
<string name="cancel">Anuluj</string>
|
||||||
<plurals name="cant_add_files">
|
<plurals name="cant_add_files">
|
||||||
|
<item quantity="few">Nie można dodać %1$d plików</item>
|
||||||
|
<item quantity="many">Nie można dodać %1$d plików</item>
|
||||||
<item quantity="one">Nie można dodać %1$d pliku</item>
|
<item quantity="one">Nie można dodać %1$d pliku</item>
|
||||||
<item quantity="other">Nie można dodać %1$d plików</item>
|
<item quantity="other">Nie można dodać %1$d plików</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -73,9 +78,12 @@
|
||||||
<string name="clear_data_message">Wszystkie notatki, obrazy, pliki i nagrania zostaną bezpowrotnie usunięte</string>
|
<string name="clear_data_message">Wszystkie notatki, obrazy, pliki i nagrania zostaną bezpowrotnie usunięte</string>
|
||||||
<string name="clear_formatting">Usuń formatowanie</string>
|
<string name="clear_formatting">Usuń formatowanie</string>
|
||||||
<string name="cleared_data">Wszystkie dane zostały usunięte</string>
|
<string name="cleared_data">Wszystkie dane zostały usunięte</string>
|
||||||
|
<string name="color">Kolor</string>
|
||||||
<string name="color_exists">Ten kolor już istnieje!</string>
|
<string name="color_exists">Ten kolor już istnieje!</string>
|
||||||
<string name="content_density">Zagęszczenie treści</string>
|
<string name="content_density">Zagęszczenie treści</string>
|
||||||
<string name="continue_">Kontynuuj</string>
|
<string name="continue_">Kontynuuj</string>
|
||||||
|
<string name="convert_to_list_note">Zamień na listę</string>
|
||||||
|
<string name="convert_to_text_note">Zamień na notatkę</string>
|
||||||
<string name="copied_link">Skopiowano link do schowka</string>
|
<string name="copied_link">Skopiowano link do schowka</string>
|
||||||
<string name="copy">Skopiuj</string>
|
<string name="copy">Skopiuj</string>
|
||||||
<string name="crash_message">Wystąpił nieoczekiwany błąd.\nPrzepraszam za niedogodności.</string>
|
<string name="crash_message">Wystąpił nieoczekiwany błąd.\nPrzepraszam za niedogodności.</string>
|
||||||
|
@ -106,6 +114,8 @@
|
||||||
<string name="delete_selected_notes">Usunąć zaznaczone notatki\?</string>
|
<string name="delete_selected_notes">Usunąć zaznaczone notatki\?</string>
|
||||||
<string name="deleted">Usunięte</string>
|
<string name="deleted">Usunięte</string>
|
||||||
<plurals name="deleted_selected_notes">
|
<plurals name="deleted_selected_notes">
|
||||||
|
<item quantity="few">Usunięto %1$d notatki</item>
|
||||||
|
<item quantity="many">Usunięto %1$d notatek</item>
|
||||||
<item quantity="one">Usunięto %1$d notatkę</item>
|
<item quantity="one">Usunięto %1$d notatkę</item>
|
||||||
<item quantity="other">Usunięto %1$d notatek</item>
|
<item quantity="other">Usunięto %1$d notatek</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -137,7 +147,7 @@
|
||||||
<string name="error_while_renaming_file">Błąd podczas zmiany nazwy pliku</string>
|
<string name="error_while_renaming_file">Błąd podczas zmiany nazwy pliku</string>
|
||||||
<string name="error_while_renaming_image">Błąd podczas zmiany nazwy obrazu</string>
|
<string name="error_while_renaming_image">Błąd podczas zmiany nazwy obrazu</string>
|
||||||
<string name="evernote">Evernote</string>
|
<string name="evernote">Evernote</string>
|
||||||
<string name="evernote_help">Aby zaimportować notatki z Evernote, musisz wyeksportować notatnik Evernote jako ENEX. Kliknij Pomoc, aby uzyskać więcej informacji.\nJeśli masz już plik ENEX, kliknij Importuj i wybierz go.</string>
|
<string name="evernote_help">Aby zaimportować notatki z Evernote, musisz wyeksportować notatnik Evernote jako ENEX. Kliknij Pomoc, aby uzyskać więcej informacji.\nJeśli masz już plik ENEX, kliknij Przywracanie i wybierz go.</string>
|
||||||
<string name="every">Każde</string>
|
<string name="every">Każde</string>
|
||||||
<string name="export">Eksportuj</string>
|
<string name="export">Eksportuj</string>
|
||||||
<string name="export_backup">Wykonaj kopię zapasową</string>
|
<string name="export_backup">Wykonaj kopię zapasową</string>
|
||||||
|
@ -151,11 +161,13 @@
|
||||||
<string name="folder">Folder</string>
|
<string name="folder">Folder</string>
|
||||||
<string name="follow_system">Systemowy</string>
|
<string name="follow_system">Systemowy</string>
|
||||||
<string name="google_keep">Google Keep</string>
|
<string name="google_keep">Google Keep</string>
|
||||||
<string name="google_keep_help">Aby zaimportować notatki z Google Keep, musisz pobrać plik ZIP Google Takeout. Wybierz tylko dane „Keep”. Kliknij Pomoc, aby uzyskać więcej informacji.\nJeśli masz już plik ZIP Takeout, kliknij Importuj i wybierz plik ZIP.</string>
|
<string name="google_keep_help">Aby zaimportować notatki z Google Keep, musisz pobrać plik ZIP Google Takeout. Wybierz tylko dane „Keep”. Kliknij Pomoc, aby uzyskać więcej informacji.\nJeśli masz już plik ZIP Takeout, kliknij Przywracanie i wybierz plik ZIP.</string>
|
||||||
<string name="grid">Siatka</string>
|
<string name="grid">Siatka</string>
|
||||||
<string name="help">Pomoc</string>
|
<string name="help">Pomoc</string>
|
||||||
<string name="hours">Godzin</string>
|
<string name="hours">Godzin</string>
|
||||||
<string name="image_format_not_supported">Format obrazu nie jest obsługiwany</string>
|
<string name="image_format_not_supported">Format obrazu nie jest obsługiwany</string>
|
||||||
|
<string name="images_hidden_in_overview">Po włączeniu tej opcji obrazy notatek będą ukryte w przeglądzie</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Ukryj obrazy w przeglądzie</string>
|
||||||
<string name="import_action">Przywracanie</string>
|
<string name="import_action">Przywracanie</string>
|
||||||
<string name="import_backup">Przywróć kopię zapasową</string>
|
<string name="import_backup">Przywróć kopię zapasową</string>
|
||||||
<string name="import_backup_password_hint">Jeśli kopia zapasowa nie jest chroniona hasłem, po prostu naciśnij Importuj, w przeciwnym razie wprowadź prawidłowe hasło.</string>
|
<string name="import_backup_password_hint">Jeśli kopia zapasowa nie jest chroniona hasłem, po prostu naciśnij Importuj, w przeciwnym razie wprowadź prawidłowe hasło.</string>
|
||||||
|
@ -167,6 +179,8 @@
|
||||||
<string name="imported_files">Przywrócono pliki</string>
|
<string name="imported_files">Przywrócono pliki</string>
|
||||||
<string name="imported_notes">Przywrócono notatki</string>
|
<string name="imported_notes">Przywrócono notatki</string>
|
||||||
<plurals name="imported_notes">
|
<plurals name="imported_notes">
|
||||||
|
<item quantity="few">Przywrócono %1$s notatki</item>
|
||||||
|
<item quantity="many">Przywrócono %1$s notatek</item>
|
||||||
<item quantity="one">Przywrócono %1$s notatkę</item>
|
<item quantity="one">Przywrócono %1$s notatkę</item>
|
||||||
<item quantity="other">Przywrócono %1$s notatek</item>
|
<item quantity="other">Przywrócono %1$s notatek</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -183,6 +197,8 @@
|
||||||
<string name="invalid_link">Skopiuj właściwy link do schowka</string>
|
<string name="invalid_link">Skopiuj właściwy link do schowka</string>
|
||||||
<string name="italic">Kursywa</string>
|
<string name="italic">Kursywa</string>
|
||||||
<string name="item">Element</string>
|
<string name="item">Element</string>
|
||||||
|
<string name="json_files">Pliki JSON</string>
|
||||||
|
<string name="json_files_help">Aby zaimportować notatki z plików JSON (pojedynczy plik lub folder), kliknij Przywracanie. Każdy prawidłowy plik JSON jest importowany jako osobna notatka, a nazwa pliku staje się tytułem notatki.</string>
|
||||||
<string name="label_exists">Etykieta już istnieje</string>
|
<string name="label_exists">Etykieta już istnieje</string>
|
||||||
<string name="label_visibility">Ukryj/pokaż etykietę w panelu nawigacyjnym</string>
|
<string name="label_visibility">Ukryj/pokaż etykietę w panelu nawigacyjnym</string>
|
||||||
<string name="labels">Etykieta</string>
|
<string name="labels">Etykieta</string>
|
||||||
|
@ -206,11 +222,13 @@
|
||||||
<string name="medium">Średni</string>
|
<string name="medium">Średni</string>
|
||||||
<string name="minutes">Minut</string>
|
<string name="minutes">Minut</string>
|
||||||
<string name="modified_date">Zmodyfikowano</string>
|
<string name="modified_date">Zmodyfikowano</string>
|
||||||
<string name="monospace">Monospace</string>
|
<string name="monospace">Stała szerokość</string>
|
||||||
<string name="monthly">Miesięcznie</string>
|
<string name="monthly">Miesięcznie</string>
|
||||||
<string name="months">Miesięcy</string>
|
<string name="months">Miesięcy</string>
|
||||||
<string name="more">%1$d więcej</string>
|
<string name="more">%1$d więcej</string>
|
||||||
<plurals name="more_files">
|
<plurals name="more_files">
|
||||||
|
<item quantity="few">…%1$d pliki więcej</item>
|
||||||
|
<item quantity="many">…%1$d plików więcej</item>
|
||||||
<item quantity="one">…%1$d plik więcej</item>
|
<item quantity="one">…%1$d plik więcej</item>
|
||||||
<item quantity="other">…%1$d plików więcej</item>
|
<item quantity="other">…%1$d plików więcej</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -229,7 +247,7 @@
|
||||||
<string name="pin">Przypnij</string>
|
<string name="pin">Przypnij</string>
|
||||||
<string name="pinned">Przypięte</string>
|
<string name="pinned">Przypięte</string>
|
||||||
<string name="plain_text_files">Zwykłe pliki tekstowe</string>
|
<string name="plain_text_files">Zwykłe pliki tekstowe</string>
|
||||||
<string name="plain_text_files_help">Aby zaimportować Notatki z plików tekstowych (pojedynczy plik lub folder), kliknij Importuj. Każdy plik jest importowany jako osobna notatka, a nazwa pliku staje się tytułem notatki. Jeśli treść tekstu zaczyna się od składni listy (np. Markdown „- [x]”, składnia NotallyX „[✓]” lub „*”, „-”), zostanie ona przekonwertowana na notatkę listy.</string>
|
<string name="plain_text_files_help">Aby zaimportować Notatki z plików tekstowych (pojedynczy plik lub folder), kliknij Przywracanie. Każdy plik jest importowany jako osobna notatka, a nazwa pliku staje się tytułem notatki. Jeśli treść tekstu zaczyna się od składni listy (np. Markdown „- [x]”, składnia NotallyX „[✓]” lub „*”, „-”), zostanie ona przekonwertowana na notatkę listy.</string>
|
||||||
<string name="play">Nagrywaj</string>
|
<string name="play">Nagrywaj</string>
|
||||||
<string name="please_grant_notally_alarm">Zezwól NotallyX na wysyłanie przypomnień</string>
|
<string name="please_grant_notally_alarm">Zezwól NotallyX na wysyłanie przypomnień</string>
|
||||||
<string name="please_grant_notally_audio">Zezwól NotallyX na nagrywanie dźwięku. Nagrania nigdy nie opuszczą Twojego urządzenia</string>
|
<string name="please_grant_notally_audio">Zezwól NotallyX na nagrywanie dźwięku. Nagrania nigdy nie opuszczą Twojego urządzenia</string>
|
||||||
|
@ -237,6 +255,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">Jeśli automatyczna kopia zapasowa nie powiedzie się, możesz otrzymać powiadomienie, jeśli zezwolisz NotallyX na wysyłanie powiadomień</string>
|
<string name="please_grant_notally_notification_auto_backup">Jeśli automatyczna kopia zapasowa nie powiedzie się, możesz otrzymać powiadomienie, jeśli zezwolisz NotallyX na wysyłanie powiadomień</string>
|
||||||
<string name="previous">Wstecz</string>
|
<string name="previous">Wstecz</string>
|
||||||
<string name="rate">Oceń aplikację</string>
|
<string name="rate">Oceń aplikację</string>
|
||||||
|
<string name="read_only">Tylko do odczytu</string>
|
||||||
<string name="ready_to_record">Oczekiwanie na nagrywanie</string>
|
<string name="ready_to_record">Oczekiwanie na nagrywanie</string>
|
||||||
<string name="record_audio">Nagrywaj</string>
|
<string name="record_audio">Nagrywaj</string>
|
||||||
<string name="recording">Nagrywanie…</string>
|
<string name="recording">Nagrywanie…</string>
|
||||||
|
@ -255,6 +274,8 @@
|
||||||
<string name="restart_app">Uruchom ponownie aplikację</string>
|
<string name="restart_app">Uruchom ponownie aplikację</string>
|
||||||
<string name="restore">Przywróć</string>
|
<string name="restore">Przywróć</string>
|
||||||
<plurals name="restored_selected_notes">
|
<plurals name="restored_selected_notes">
|
||||||
|
<item quantity="few">Przywrócono %1$d notatki</item>
|
||||||
|
<item quantity="many">Przywrócono %1$d notatek</item>
|
||||||
<item quantity="one">Przywrócono %1$d notatkę</item>
|
<item quantity="one">Przywrócono %1$d notatkę</item>
|
||||||
<item quantity="other">Przywrócono %1$d notatek</item>
|
<item quantity="other">Przywrócono %1$d notatek</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
@ -291,10 +312,13 @@
|
||||||
<string name="text_default">Domyślny</string>
|
<string name="text_default">Domyślny</string>
|
||||||
<string name="text_size">Rozmiar tekstu</string>
|
<string name="text_size">Rozmiar tekstu</string>
|
||||||
<string name="theme">Motyw</string>
|
<string name="theme">Motyw</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Użyj kolorów tapety systemu</string>
|
||||||
<string name="title">Tytuł</string>
|
<string name="title">Tytuł</string>
|
||||||
<string name="to_record_audio">Aby nagrywać dźwięk, zezwól NotallyX na dostęp do swojego mikrofonu. Dotknij Ustawienia > Uprawnienia i włącz Mikrofon</string>
|
<string name="to_record_audio">Aby nagrywać dźwięk, zezwól NotallyX na dostęp do swojego mikrofonu. Dotknij Ustawienia > Uprawnienia i włącz Mikrofon</string>
|
||||||
<string name="unarchive">Cofnij archiwizację</string>
|
<string name="unarchive">Cofnij archiwizację</string>
|
||||||
<plurals name="unarchived_selected_notes">
|
<plurals name="unarchived_selected_notes">
|
||||||
|
<item quantity="few">Odarchiwizowano %1$d notatki</item>
|
||||||
|
<item quantity="many">Odarchiwizowano %1$d notatek</item>
|
||||||
<item quantity="one">Odarchiwizowano %1$d notatkę</item>
|
<item quantity="one">Odarchiwizowano %1$d notatkę</item>
|
||||||
<item quantity="other">Odarchiwizowano %1$d notatek</item>
|
<item quantity="other">Odarchiwizowano %1$d notatek</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
|
@ -1,77 +1,334 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="about">О приложении</string>
|
<string name="about">О приложении</string>
|
||||||
|
<string name="add_images">Добавить изображение</string>
|
||||||
<string name="add_item">Добавить пункт</string>
|
<string name="add_item">Добавить пункт</string>
|
||||||
<string name="add_label">Добавить метку</string>
|
<string name="add_label">Добавить метку</string>
|
||||||
|
<string name="add_reminder">Добавить напоминание</string>
|
||||||
|
<string name="adding_files">Добавление файлов</string>
|
||||||
|
<string name="adding_images">Добавление изображений</string>
|
||||||
|
<string name="all">Все</string>
|
||||||
<string name="appearance">Внешний вид</string>
|
<string name="appearance">Внешний вид</string>
|
||||||
<string name="archive">Архивировать</string>
|
<string name="archive">Архивировать</string>
|
||||||
<string name="archived">Архив</string>
|
<string name="archived">Архив</string>
|
||||||
|
<plurals name="archived_selected_notes">
|
||||||
|
<item quantity="few">Архивировано %1$d заметки</item>
|
||||||
|
<item quantity="one">Архивирована %1$d заметка</item>
|
||||||
|
<item quantity="other">Архивировано %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="ascending">По возрастанию</string>
|
||||||
|
<string name="attach_file">Прикрепить файл</string>
|
||||||
|
<string name="audio_recordings">Аудиозаписи</string>
|
||||||
|
<string name="auto_backup">Автобэкапы</string>
|
||||||
|
<string name="auto_backup_error_message">Ошибка при авто-резервной копии: \n\'%1$s\'\nПроверьте настройки или сообщите об ошибке</string>
|
||||||
|
<string name="auto_backup_failed">Сбой авто-резервирования NotallyX</string>
|
||||||
|
<string name="auto_backup_last">Последняя резервная копия</string>
|
||||||
|
<string name="auto_backup_on_save">Создавать резервную копию при выходе (авто)</string>
|
||||||
|
<string name="auto_backup_on_save_hint">При включении этой функции в папке резервных копий (Папка резервных копий) автоматически создаётся архив NotallyX_AutoBackup.zip при выходе из заметки.\nЭто может повлиять на производительность</string>
|
||||||
|
<string name="auto_backups_folder">Папка резервных копий</string>
|
||||||
|
<string name="auto_backups_folder_hint">Папка, в которой будут храниться резервные копии</string>
|
||||||
|
<string name="auto_backups_folder_rechoose">Необходимо повторно указать папку для резервных копий для доступа NotallyX.\nМожно отменить и пропустить импорт папки резервных копий</string>
|
||||||
|
<string name="auto_backups_folder_set">Сначала укажите папку для резервных копий</string>
|
||||||
|
<string name="auto_save_after_idle_time">Автосохранение заметки после указанного времени бездействия</string>
|
||||||
|
<string name="auto_sort_by_checked">Сортировать отмеченные элементы в конец</string>
|
||||||
|
<string name="back">Назад</string>
|
||||||
<string name="backup">Резервная копия</string>
|
<string name="backup">Резервная копия</string>
|
||||||
|
<string name="backup_password">Пароль для резервной копии</string>
|
||||||
|
<string name="backup_password_hint">При включении этой функции все новые ZIP-архивы резервных копий буду зашифрованы и защищены паролем</string>
|
||||||
|
<string name="backup_period_days">Интервал авторезервирования (в днях)</string>
|
||||||
|
<string name="backup_periodic">Переодические резервные копии</string>
|
||||||
|
<string name="backup_periodic_hint">При включении этой функции резервные копии будут автоматически сохраняться в указанную папку.\nФункция может не работать при включенном режиме энергосбережения</string>
|
||||||
|
<string name="behaviour">Биометрия</string>
|
||||||
|
<string name="biometric_lock">Блокировать приложение с помощью биометрии или PIN-кода</string>
|
||||||
|
<string name="biometrics_disable_success">Биометрическая защита/PIN-код отключены</string>
|
||||||
|
<string name="biometrics_failure">Ошибка аутентификации по Биометрии/PIN-коду</string>
|
||||||
|
<string name="biometrics_no_support">На устройстве нет биометрических функций</string>
|
||||||
|
<string name="biometrics_not_setup">Биометрия/PIN-код ещё не настроены</string>
|
||||||
|
<string name="biometrics_setup_success">Защита по Биометрии/PIN-коду включена</string>
|
||||||
<string name="bold">Жирный</string>
|
<string name="bold">Жирный</string>
|
||||||
|
<string name="calculating">Вычисление…</string>
|
||||||
<string name="cancel">Отменить</string>
|
<string name="cancel">Отменить</string>
|
||||||
|
<plurals name="cant_add_files">
|
||||||
|
<item quantity="few">Невозможно добавить %1$d файла</item>
|
||||||
|
<item quantity="one">Невозможно добавить %1$d файл</item>
|
||||||
|
<item quantity="other">Невозможно добавить %1$d файлов</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="cant_add_images">
|
||||||
|
<item quantity="few">Невозможно добавить %1$d изображения</item>
|
||||||
|
<item quantity="one">Невозможно добавить %1$d изображение</item>
|
||||||
|
<item quantity="other">Невозможно добавить %1$d изображений</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="cant_find_folder">Папка не найдена. Возможно, она была перемещена или удалена</string>
|
||||||
|
<string name="cant_find_note">Заметка не найдена. Возможно она была удалена</string>
|
||||||
|
<string name="cant_load_file">Файл не загружен. Возможно, он был перемещён или удалён</string>
|
||||||
|
<string name="cant_load_image">Изображение не загружено. Проверьте, не было ли оно удалено</string>
|
||||||
<string name="cant_open_link">Невозможно открыть ссылку</string>
|
<string name="cant_open_link">Невозможно открыть ссылку</string>
|
||||||
<string name="change_color">Изменить цвет</string>
|
<string name="change_color">Изменить цвет</string>
|
||||||
|
<string name="change_color_message">Выберите цвет или создайте новый.\nЦвет можно изменить долгим нажатием.</string>
|
||||||
|
<string name="change_note">Изменить заметку</string>
|
||||||
|
<string name="check_all_items">Выделить всё</string>
|
||||||
|
<string name="choose_another_folder">Выбрать другую папку</string>
|
||||||
|
<string name="choose_folder">Выбрать папку</string>
|
||||||
|
<string name="choose_other_app">Выберите приложение для импорта</string>
|
||||||
|
<string name="clear">Очистить</string>
|
||||||
|
<string name="clear_data">Очистить данные</string>
|
||||||
|
<string name="clear_data_message">Все Заметки, Изображения, Файлы и Аудиозаписи будут навсегда удалены</string>
|
||||||
<string name="clear_formatting">Стандартный стиль</string>
|
<string name="clear_formatting">Стандартный стиль</string>
|
||||||
|
<string name="cleared_data">Все данные удалены</string>
|
||||||
|
<string name="color">Цвет</string>
|
||||||
|
<string name="color_exists">Этот цвет уже существует!</string>
|
||||||
<string name="content_density">Плотность контента</string>
|
<string name="content_density">Плотность контента</string>
|
||||||
|
<string name="continue_">Продолжить</string>
|
||||||
|
<string name="convert_to_list_note">Конвертировать в Список</string>
|
||||||
|
<string name="convert_to_text_note">Конвертировать в Заметку</string>
|
||||||
|
<string name="copied_link">Ссылка скопирована в буфер обмена</string>
|
||||||
|
<string name="copy">Копировать</string>
|
||||||
|
<string name="crash_message">Произошла непредвиденная ошибка.\nПриносим извинения за причиненные неудобства.</string>
|
||||||
|
<string name="create_github_issue">Сообщить об ошибке на GitHub</string>
|
||||||
|
<string name="creation_date">Создано</string>
|
||||||
|
<string name="custom">Пользовтаельский</string>
|
||||||
|
<string name="daily">Дни</string>
|
||||||
<string name="dark">Тёмная</string>
|
<string name="dark">Тёмная</string>
|
||||||
|
<string name="data_in_public">Сохранять данные в общедоступной папке</string>
|
||||||
|
<string name="data_in_public_message">При включении этой функции внутренняя база данных приложения будет перенесена в его общедоступную папку (Android/media/com.philkes.notallyx).\nЭто позволит синхронизировать данные NotallyX между устройствами с помощью приложений для синхронизации файлов.</string>
|
||||||
<string name="date">Дата</string>
|
<string name="date">Дата</string>
|
||||||
<string name="date_format">Формат даты</string>
|
<string name="date_format">Формат даты</string>
|
||||||
|
<string name="date_format_apply_in_note_view">Применять и в просмотре заметки</string>
|
||||||
|
<string name="date_format_hint">Применяет выбранный формат даты в общем списке заметок</string>
|
||||||
|
<string name="days">Дней</string>
|
||||||
<string name="delete">Удалить</string>
|
<string name="delete">Удалить</string>
|
||||||
<string name="delete_all">Удалить всё</string>
|
<string name="delete_all">Удалить всё</string>
|
||||||
<string name="delete_all_notes">Удалить все заметки\?</string>
|
<string name="delete_all_notes">Удалить все заметки\?</string>
|
||||||
|
<string name="delete_audio_recording_forever">Удалить все аудиозаписи\?</string>
|
||||||
|
<string name="delete_checked_items">Удалить выбранные элементы</string>
|
||||||
|
<string name="delete_color_message">Каким цветом заменить текущий цвет заметок\?</string>
|
||||||
|
<string name="delete_file">Удалить файл \'%1$s\'\?</string>
|
||||||
<string name="delete_forever">Удалить навсегда</string>
|
<string name="delete_forever">Удалить навсегда</string>
|
||||||
|
<string name="delete_image_forever">Удалить изображение навсегда\?</string>
|
||||||
<string name="delete_label">Удалить метку\?</string>
|
<string name="delete_label">Удалить метку\?</string>
|
||||||
<string name="delete_note_forever">Удалить заметку навсегда\?</string>
|
<string name="delete_note_forever">Удалить заметку навсегда\?</string>
|
||||||
|
<string name="delete_reminder">Удалить напоминание\?</string>
|
||||||
|
<string name="delete_selected_notes">Удалить выбранные заметки\?</string>
|
||||||
<string name="deleted">Удалённые</string>
|
<string name="deleted">Удалённые</string>
|
||||||
|
<plurals name="deleted_selected_notes">
|
||||||
|
<item quantity="few">Удалено %1$d заметки</item>
|
||||||
|
<item quantity="one">Удалена %1$d заметка</item>
|
||||||
|
<item quantity="other">Удалено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="deleting_files">Удаление файлов</string>
|
||||||
|
<string name="deleting_images">Удаление изображений</string>
|
||||||
|
<string name="descending">По убыванию</string>
|
||||||
|
<string name="disable">Отключить</string>
|
||||||
|
<string name="disable_data_in_public">Переместить данные во внутреннюю папку</string>
|
||||||
|
<string name="disable_lock_description">Это также расшифрует базу данных</string>
|
||||||
|
<string name="disable_lock_title">Отключить защиту по Биометрии/PIN-коду</string>
|
||||||
|
<string name="disabled">Отключено</string>
|
||||||
|
<string name="disallow_screenshots">Запретить создание скриншотов</string>
|
||||||
|
<string name="discard">Отменить</string>
|
||||||
|
<string name="display_text">Текст для отображения</string>
|
||||||
|
<string name="donate">Сделать пожертвование</string>
|
||||||
<string name="drag_handle">Перетащить</string>
|
<string name="drag_handle">Перетащить</string>
|
||||||
<string name="edit">Редактировать</string>
|
<string name="edit">Редактировать</string>
|
||||||
|
<string name="edit_color">Изменить цвет</string>
|
||||||
<string name="edit_label">Изменить метку</string>
|
<string name="edit_label">Изменить метку</string>
|
||||||
<string name="empty_labels">Еще нет меток, создать\?</string>
|
<string name="edit_link">Изменить ссылку</string>
|
||||||
|
<string name="edit_reminders">Изменить напоминания</string>
|
||||||
|
<string name="elapsed">Просроченные</string>
|
||||||
|
<string name="empty_labels">Еще нет меток. Создать\?</string>
|
||||||
<string name="empty_list">Пустой список</string>
|
<string name="empty_list">Пустой список</string>
|
||||||
<string name="empty_note">Пустая заметка</string>
|
<string name="empty_note">Пустая заметка</string>
|
||||||
<string name="export">Экспортировать</string>
|
<string name="empty_reminders">Напоминаний нет. Создать\?</string>
|
||||||
<string name="export_backup">Экспортировать</string>
|
<string name="enable_lock_description">Это также зашифрует базу данных</string>
|
||||||
|
<string name="enable_lock_title">Включить защиту по Биометрии/PIN-коду</string>
|
||||||
|
<string name="enabled">Включено</string>
|
||||||
|
<string name="error_while_renaming_file">Ошибка переименования файла</string>
|
||||||
|
<string name="error_while_renaming_image">Ошибка переименования изображения</string>
|
||||||
|
<string name="evernote">Evernote</string>
|
||||||
|
<string name="evernote_help">Чтобы импортировать заметки из Evernote, экспортируйте блокнот в формате ENEX. Нажмите \"Помощь\" для инструкций.\n\nЕсли файл ENEX уже готов, выберите \"Импорт\"</string>
|
||||||
|
<string name="every">Каждый</string>
|
||||||
|
<string name="export">Экспорт</string>
|
||||||
|
<string name="export_backup">Экспорт резервной копии</string>
|
||||||
|
<string name="export_settings">Экспорт настроек</string>
|
||||||
|
<string name="export_settings_failure">Не удалось экспортировать настройки. Возможно, указан недопустимый путь</string>
|
||||||
|
<string name="export_settings_message">Все настройки будут сохранены в JSON-файл для последующего импорта.\n\nОбратите внимание: зашифрованные данные (пароль авторезерва и биометрический ключ) не экспортируются.</string>
|
||||||
|
<string name="export_settings_success">Настройки успешно экспортированы</string>
|
||||||
|
<string name="exporting_backup">Экспорт резервной копии</string>
|
||||||
|
<string name="extracted_files">Файлы извлечены</string>
|
||||||
<string name="filter">Фильтры</string>
|
<string name="filter">Фильтры</string>
|
||||||
|
<string name="folder">Папка</string>
|
||||||
<string name="follow_system">Как в системе</string>
|
<string name="follow_system">Как в системе</string>
|
||||||
|
<string name="google_keep">Google Keep</string>
|
||||||
|
<string name="google_keep_help">Для импорта из Google Keep скачайте архив Takeout (только данные \"Keep\"). Нажмите \"Помощь\" для подробностей.\n\nЕсли архив уже есть, выберите его через \"Импорт\"</string>
|
||||||
<string name="grid">Сетка</string>
|
<string name="grid">Сетка</string>
|
||||||
<string name="import_backup">Импортировать</string>
|
<string name="help">Помощь</string>
|
||||||
|
<string name="hours">Часов</string>
|
||||||
|
<string name="image_format_not_supported">Формат изображения не поддерживыется</string>
|
||||||
|
<string name="images_hidden_in_overview">Если эта функция включена, изображения заметок будут скрыты из общего списка.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Скрыть изображения в общем списке</string>
|
||||||
|
<string name="import_action">Импорт</string>
|
||||||
|
<string name="import_backup">Импорт резервной копии</string>
|
||||||
|
<string name="import_backup_password_hint">Если резервная копия не защищена паролем, нажмите \"Импорт\". В противном случае введите пароль.</string>
|
||||||
|
<string name="import_other">Импорт заметок из других приложений</string>
|
||||||
|
<string name="import_settings">Импорт настроек</string>
|
||||||
|
<string name="import_settings_failure">Не удалось импортировать настройки. Вы выбрали правильный файл\?</string>
|
||||||
|
<string name="import_settings_message">Для импорта настроек выберите корректный JSON-файл настроек NotallyX. </string>
|
||||||
|
<string name="import_settings_success">Настройки успешно импортированы</string>
|
||||||
|
<string name="imported_files">Файлы импортированы</string>
|
||||||
|
<string name="imported_notes">Заметки импортированы</string>
|
||||||
|
<plurals name="imported_notes">
|
||||||
|
<item quantity="few">Импортировано %1$s заметки</item>
|
||||||
|
<item quantity="one">Импортирована %1$s заметка</item>
|
||||||
|
<item quantity="other">Импортировано %1$s заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="importing_backup">Импорт резервной копии</string>
|
||||||
|
<string name="insert_an_sd_card_audio">Вставьте SD-карту для записи аудио</string>
|
||||||
|
<string name="insert_an_sd_card_files">Для добавления файлов вставьте SD-карту</string>
|
||||||
|
<string name="insert_an_sd_card_images">Вставьте SD-карту, чтобы загрузить изображение</string>
|
||||||
|
<string name="install_a_browser">Чтобы открыть ссылку, установите браузер</string>
|
||||||
|
<string name="install_an_email">Установите клиент почты для отправки отзыва</string>
|
||||||
|
<string name="invalid_backup">Некорректная резервная копия</string>
|
||||||
|
<string name="invalid_evernote">Недопустимый файл Evernote (ENEX)</string>
|
||||||
|
<string name="invalid_google_keep">Некорректный ZIP-архив Google Takeout</string>
|
||||||
|
<string name="invalid_image">Изображение повреждено</string>
|
||||||
|
<string name="invalid_link">Скопируйте рабочую ссылку в буфер обмена</string>
|
||||||
<string name="italic">Курсив</string>
|
<string name="italic">Курсив</string>
|
||||||
<string name="item">Пункт</string>
|
<string name="item">Пункт</string>
|
||||||
|
<string name="json_files">JSON файлы</string>
|
||||||
|
<string name="json_files_help">Для импорта заметок из JSON-файлов (отдельный файл или папка) нажмите \"Импорт\". Каждый корректный JSON-файл будет преобразован в отдельную заметку, где имя файла станет её заголовком</string>
|
||||||
<string name="label_exists">Метка уже существует</string>
|
<string name="label_exists">Метка уже существует</string>
|
||||||
|
<string name="label_visibility">Скрыть/показать метку в панели навигации</string>
|
||||||
<string name="labels">Метки</string>
|
<string name="labels">Метки</string>
|
||||||
|
<string name="labels_hidden_in_overview">При включении этой функции метки заметок будут скрыты в общем списке</string>
|
||||||
|
<string name="labels_hidden_in_overview_title">Скрывать метки в общем списке</string>
|
||||||
|
<string name="large">Большой</string>
|
||||||
<string name="libraries">Библиотеки</string>
|
<string name="libraries">Библиотеки</string>
|
||||||
<string name="light">Светлая</string>
|
<string name="light">Светлая</string>
|
||||||
<string name="link">Ссылка</string>
|
<string name="link">Ссылка</string>
|
||||||
|
<string name="link_note">Связать заметку</string>
|
||||||
<string name="list">Список</string>
|
<string name="list">Список</string>
|
||||||
|
<string name="list_item_auto_sort">Сортировать элементы списка</string>
|
||||||
|
<string name="locked">Заблокировано</string>
|
||||||
|
<string name="make_feature_request">Запросить новую функцию</string>
|
||||||
<string name="make_list">Создать список</string>
|
<string name="make_list">Создать список</string>
|
||||||
|
<string name="max_backups">Лимит резервных копий</string>
|
||||||
<string name="max_items_to_display">Максимум отображаемых пунктов в списке</string>
|
<string name="max_items_to_display">Максимум отображаемых пунктов в списке</string>
|
||||||
|
<string name="max_labels_to_display">Максимум отображаемых меток</string>
|
||||||
<string name="max_lines_to_display">Максимум отображаемых строк в заметке</string>
|
<string name="max_lines_to_display">Максимум отображаемых строк в заметке</string>
|
||||||
|
<string name="max_lines_to_display_title">Максимум отображаемых строк в заголовке</string>
|
||||||
|
<string name="medium">Средний</string>
|
||||||
|
<string name="minutes">Минуты</string>
|
||||||
|
<string name="modified_date">Изменено</string>
|
||||||
<string name="monospace">Моноширинный</string>
|
<string name="monospace">Моноширинный</string>
|
||||||
|
<string name="monthly">Ежемесячно</string>
|
||||||
|
<string name="months">Месяцы</string>
|
||||||
|
<string name="more">Ещё %1$d</string>
|
||||||
|
<plurals name="more_files">
|
||||||
|
<item quantity="few">...ещё %1$d файла</item>
|
||||||
|
<item quantity="one">...ещё %1$d файл</item>
|
||||||
|
<item quantity="other">...ещё %1$d файлов</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="new_color">Новый цвет</string>
|
||||||
|
<string name="next">Следующий</string>
|
||||||
|
<string name="no_auto_sort">Без автосортировки</string>
|
||||||
<string name="none">Отсутствует</string>
|
<string name="none">Отсутствует</string>
|
||||||
<string name="note">Заметка</string>
|
<string name="note">Заметка</string>
|
||||||
<string name="notes">Заметки</string>
|
<string name="notes">Заметки</string>
|
||||||
|
<string name="notes_sorted_by">Сортировать заметки по</string>
|
||||||
<string name="open_link">Открыть ссылку</string>
|
<string name="open_link">Открыть ссылку</string>
|
||||||
|
<string name="open_note">Открыть заметку</string>
|
||||||
<string name="others">Другие</string>
|
<string name="others">Другие</string>
|
||||||
|
<string name="pause">Пауза</string>
|
||||||
|
<string name="paused">Приостановлено</string>
|
||||||
<string name="pin">Закрепить</string>
|
<string name="pin">Закрепить</string>
|
||||||
<string name="pinned">Закреплённые</string>
|
<string name="pinned">Закреплённые</string>
|
||||||
|
<string name="plain_text_files">Текстовые файлы</string>
|
||||||
|
<string name="plain_text_files_help">Для импорта заметок из текстовых файлов (отдельный файл или папка) нажмите \"Импорт\". Каждый файл станет отдельной заметкой — имя файла будет её заголовком. Если текст начинается с элементов списка (например, Markdown \"- [x]\", синтаксис NotallyX \"[~/\", или \"*\", ~), он преобразуется в заметку-список.</string>
|
||||||
|
<string name="play">Играть</string>
|
||||||
|
<string name="please_grant_notally_alarm">Разрешите NotallyX отправлять напоминания</string>
|
||||||
|
<string name="please_grant_notally_audio">Разрешите доступ к микрофону.\nЗаписи остаются на вашем устройстве</string>
|
||||||
|
<string name="please_grant_notally_notification">Разрешите отправку уведомлений</string>
|
||||||
|
<string name="please_grant_notally_notification_auto_backup">При сбое авторезервирования вы получите уведомление, если разрешите их отправку. </string>
|
||||||
|
<string name="previous">Предыдущий</string>
|
||||||
<string name="rate">Оценить приложение</string>
|
<string name="rate">Оценить приложение</string>
|
||||||
|
<string name="read_only">Только чтение</string>
|
||||||
|
<string name="ready_to_record">Готово к записи</string>
|
||||||
|
<string name="record_audio">Запись аудио</string>
|
||||||
|
<string name="recording">Запись…</string>
|
||||||
|
<string name="redo">Вернуть</string>
|
||||||
|
<string name="reminder_no_repetition">Без повтора</string>
|
||||||
|
<string name="reminders">Напоминания</string>
|
||||||
|
<string name="remove_link">Удалить ссылку</string>
|
||||||
|
<string name="repetition">Повтор</string>
|
||||||
|
<string name="repetition_custom">Свой вариант повтора</string>
|
||||||
|
<string name="repetition_value_hint">Значение</string>
|
||||||
|
<string name="report_bug">Сообщить об ошибке/баге</string>
|
||||||
|
<string name="report_crash">Отправить отчёт об ошибке</string>
|
||||||
|
<string name="reset_settings">Сбросить настройки</string>
|
||||||
|
<string name="reset_settings_message">Все настройки будут сброшены к значениям по умолчанию</string>
|
||||||
|
<string name="reset_settings_success">Все настройки успешно сброшены</string>
|
||||||
|
<string name="restart_app">Перезапустить приложение</string>
|
||||||
<string name="restore">Восстановить</string>
|
<string name="restore">Восстановить</string>
|
||||||
|
<plurals name="restored_selected_notes">
|
||||||
|
<item quantity="few">Восстановлено %1$d заметки</item>
|
||||||
|
<item quantity="one">Восстановлена %1$d заметка</item>
|
||||||
|
<item quantity="other">Восстановлено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="resume">Повторить</string>
|
||||||
<string name="save">Сохранить</string>
|
<string name="save">Сохранить</string>
|
||||||
|
<string name="save_recording">Сохранить аудиозапись\?</string>
|
||||||
<string name="save_to_device">Сохранить на устройстве</string>
|
<string name="save_to_device">Сохранить на устройстве</string>
|
||||||
<string name="saved_to_device">Сохранено на устройстве</string>
|
<string name="saved_to_device">Сохранено на устройстве</string>
|
||||||
<string name="saved_to_notally">Сохранено в NotallyX</string>
|
<string name="saved_to_notally">Сохранено в NotallyX</string>
|
||||||
<string name="search">Поиск</string>
|
<string name="search">Поиск</string>
|
||||||
|
<string name="security">Безопасность</string>
|
||||||
|
<string name="select_all">Выбрать всё</string>
|
||||||
|
<string name="select_labels">Выбрать метки</string>
|
||||||
|
<string name="select_note">Выбрать заметки</string>
|
||||||
|
<string name="send_feedback">Сообщить о проблеме</string>
|
||||||
<string name="settings">Настройки</string>
|
<string name="settings">Настройки</string>
|
||||||
<string name="share">Поделиться</string>
|
<string name="share">Поделиться</string>
|
||||||
|
<string name="skip">Пропустить</string>
|
||||||
|
<string name="small">Маленький</string>
|
||||||
<string name="something_went_wrong">Что-то пошло не так. Пожалуйста, повторите попытку</string>
|
<string name="something_went_wrong">Что-то пошло не так. Пожалуйста, повторите попытку</string>
|
||||||
|
<string name="sort_direction">Порядок сортировки</string>
|
||||||
|
<string name="source_code">Исходный код</string>
|
||||||
|
<string name="start">Старт</string>
|
||||||
|
<string name="start_view">Стартовый экран</string>
|
||||||
|
<string name="start_view_hint">Выберите, какой экран/ярлык показывать при запуске.\nПо умолчанию — основной список заметок</string>
|
||||||
|
<string name="stop">Стоп</string>
|
||||||
<string name="strikethrough">Перечёркнутый</string>
|
<string name="strikethrough">Перечёркнутый</string>
|
||||||
<string name="take_note">Создать заметку</string>
|
<string name="take_note">Создать заметку</string>
|
||||||
|
<string name="tap_for_more_options">Нажмите для подробностей</string>
|
||||||
|
<string name="tap_to_set_up">Нажмите для настройки</string>
|
||||||
|
<string name="text_default">По умолчанию</string>
|
||||||
|
<string name="text_size">Размер текста</string>
|
||||||
<string name="theme">Тема</string>
|
<string name="theme">Тема</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Использовать цвета обоев</string>
|
||||||
<string name="title">Заголовок</string>
|
<string name="title">Заголовок</string>
|
||||||
|
<string name="to_record_audio">Что-бы записывать аудиозаписи, разрешите NotallyX доступ к микрофону</string>
|
||||||
<string name="unarchive">Разархивировать</string>
|
<string name="unarchive">Разархивировать</string>
|
||||||
|
<plurals name="unarchived_selected_notes">
|
||||||
|
<item quantity="few">Извлечено %1$d заметки</item>
|
||||||
|
<item quantity="one">Извлечено %1$d заметка</item>
|
||||||
|
<item quantity="other">Извлечено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="uncheck_all_items">Очистить выбор</string>
|
||||||
|
<string name="undo">Вернуть</string>
|
||||||
|
<string name="unknown_error">Неизвестная ошибка</string>
|
||||||
|
<string name="unknown_name">Неизвестное имя</string>
|
||||||
|
<string name="unlabeled">Без названия</string>
|
||||||
|
<string name="unlock">Разблокировка по Биометрии/PIN-коду</string>
|
||||||
|
<string name="unlock_with_biometrics_not_setup">Ранее вы включили биометрическую защиту, но сейчас на устройстве не настроены биометрия или PIN-код.\n\nНажмите «Отключить», чтобы снять блокировку, или настройте биометрию/PIN-код заново</string>
|
||||||
<string name="unpin">Открепить</string>
|
<string name="unpin">Открепить</string>
|
||||||
|
<string name="upcoming">Предстоящие</string>
|
||||||
|
<string name="updated_link">Обновить ссылку</string>
|
||||||
<string name="view">Вид</string>
|
<string name="view">Вид</string>
|
||||||
<string name="view_file">Посмотреть файл</string>
|
<string name="view_file">Посмотреть файл</string>
|
||||||
<string name="view_note">Посмотреть заметку…</string>
|
<string name="view_note">Посмотреть заметку…</string>
|
||||||
|
<string name="weekly">Еженедельный</string>
|
||||||
|
<string name="weeks">Недели</string>
|
||||||
|
<string name="wrong_password">Неверный пароль</string>
|
||||||
|
<string name="yearly">Ежегодный</string>
|
||||||
|
<string name="years">Года</string>
|
||||||
<string name="your_notes_associated">Ваши заметки, связанные с этой меткой, не будут удалены</string>
|
<string name="your_notes_associated">Ваши заметки, связанные с этой меткой, не будут удалены</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -51,8 +51,6 @@
|
||||||
|
|
||||||
<item name="android:statusBarColor">?attr/colorSurface</item>
|
<item name="android:statusBarColor">?attr/colorSurface</item>
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
|
||||||
|
|
||||||
<item name="android:windowBackground">?attr/colorSurface</item>
|
<item name="android:windowBackground">?attr/colorSurface</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
|
@ -54,4 +54,9 @@
|
||||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
<item name="android:windowLightNavigationBar">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||||
|
<item name="bottomSheetStyle">@style/ModalBottomSheet</item>
|
||||||
|
<item name="android:windowIsFloating">false</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
|
@ -24,7 +24,6 @@
|
||||||
<string name="auto_backup_last">上次备份</string>
|
<string name="auto_backup_last">上次备份</string>
|
||||||
<string name="auto_backup_on_save">保存笔记时自动备份</string>
|
<string name="auto_backup_on_save">保存笔记时自动备份</string>
|
||||||
<string name="auto_backup_on_save_hint">启用本选项后,每当保存笔记时都会在所配置的”备份文件夹“中自动创建一个备份文件(”NotallyX_AutoBackup.zip)。请注意这可能影响性能</string>
|
<string name="auto_backup_on_save_hint">启用本选项后,每当保存笔记时都会在所配置的”备份文件夹“中自动创建一个备份文件(”NotallyX_AutoBackup.zip)。请注意这可能影响性能</string>
|
||||||
<string name="auto_backup_period">自动备份周期</string>
|
|
||||||
<string name="auto_backups_folder">备份文件夹</string>
|
<string name="auto_backups_folder">备份文件夹</string>
|
||||||
<string name="auto_backups_folder_hint">保存所有自动备份文件的文件夹</string>
|
<string name="auto_backups_folder_hint">保存所有自动备份文件的文件夹</string>
|
||||||
<string name="auto_backups_folder_rechoose">你需要重新选择备份文件夹,这样 NotallyX 有写入它的权限。你也可以按:取消“跳过导入备份文件夹的值</string>
|
<string name="auto_backups_folder_rechoose">你需要重新选择备份文件夹,这样 NotallyX 有写入它的权限。你也可以按:取消“跳过导入备份文件夹的值</string>
|
||||||
|
@ -77,6 +76,8 @@
|
||||||
<string name="color_exists">此颜色已存在!</string>
|
<string name="color_exists">此颜色已存在!</string>
|
||||||
<string name="content_density">内容密度</string>
|
<string name="content_density">内容密度</string>
|
||||||
<string name="continue_">继续</string>
|
<string name="continue_">继续</string>
|
||||||
|
<string name="convert_to_list_note">转换到清单</string>
|
||||||
|
<string name="convert_to_text_note">转换到文本笔记</string>
|
||||||
<string name="copied_link">链接已复制到剪贴板</string>
|
<string name="copied_link">链接已复制到剪贴板</string>
|
||||||
<string name="copy">复制</string>
|
<string name="copy">复制</string>
|
||||||
<string name="crash_message">发生了意外错误。抱歉造成不变</string>
|
<string name="crash_message">发生了意外错误。抱歉造成不变</string>
|
||||||
|
@ -117,6 +118,7 @@
|
||||||
<string name="disable_lock_description">这也会解密数据据</string>
|
<string name="disable_lock_description">这也会解密数据据</string>
|
||||||
<string name="disable_lock_title">停用生物特征/PIN锁</string>
|
<string name="disable_lock_title">停用生物特征/PIN锁</string>
|
||||||
<string name="disabled">禁用</string>
|
<string name="disabled">禁用</string>
|
||||||
|
<string name="disallow_screenshots">禁止截屏</string>
|
||||||
<string name="discard">取消</string>
|
<string name="discard">取消</string>
|
||||||
<string name="display_text">要展示的我呢本</string>
|
<string name="display_text">要展示的我呢本</string>
|
||||||
<string name="donate">捐赠</string>
|
<string name="donate">捐赠</string>
|
||||||
|
@ -156,6 +158,8 @@
|
||||||
<string name="help">帮助</string>
|
<string name="help">帮助</string>
|
||||||
<string name="hours">小时</string>
|
<string name="hours">小时</string>
|
||||||
<string name="image_format_not_supported">不支持该图片格式</string>
|
<string name="image_format_not_supported">不支持该图片格式</string>
|
||||||
|
<string name="images_hidden_in_overview">如果启用此选项,则注释的图像将不会显示在概览中。</string>
|
||||||
|
<string name="images_hidden_in_overview_title">在概览中隐藏图片</string>
|
||||||
<string name="import_action">导入</string>
|
<string name="import_action">导入</string>
|
||||||
<string name="import_backup">导入备份</string>
|
<string name="import_backup">导入备份</string>
|
||||||
<string name="import_backup_password_hint">如果你的备份文件没有密码保护,只需按下“导入”即可。如有,起输入正确的密码</string>
|
<string name="import_backup_password_hint">如果你的备份文件没有密码保护,只需按下“导入”即可。如有,起输入正确的密码</string>
|
||||||
|
@ -183,6 +187,8 @@
|
||||||
<string name="invalid_link">复制有效链接到你的剪贴板</string>
|
<string name="invalid_link">复制有效链接到你的剪贴板</string>
|
||||||
<string name="italic">斜体</string>
|
<string name="italic">斜体</string>
|
||||||
<string name="item">项目</string>
|
<string name="item">项目</string>
|
||||||
|
<string name="json_files">JSON 文件</string>
|
||||||
|
<string name="json_files_help">要从 JSON 文件(单一文件或文件夹)导入你的笔记,单击“导入”。每个有效的 JSON 文件会被导入成不同笔记,原始文件名成为笔记的标题。</string>
|
||||||
<string name="label_exists">标签已存在</string>
|
<string name="label_exists">标签已存在</string>
|
||||||
<string name="label_visibility">在导航面板中隐藏/显示标签</string>
|
<string name="label_visibility">在导航面板中隐藏/显示标签</string>
|
||||||
<string name="labels">标签</string>
|
<string name="labels">标签</string>
|
||||||
|
@ -237,6 +243,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">如果自动备份失败,你会收到一则通知(前提是你授予了 NotallyX 发送通知的权限)</string>
|
<string name="please_grant_notally_notification_auto_backup">如果自动备份失败,你会收到一则通知(前提是你授予了 NotallyX 发送通知的权限)</string>
|
||||||
<string name="previous">前一个</string>
|
<string name="previous">前一个</string>
|
||||||
<string name="rate">给应用打分</string>
|
<string name="rate">给应用打分</string>
|
||||||
|
<string name="read_only">只读</string>
|
||||||
<string name="ready_to_record">准备好录音了</string>
|
<string name="ready_to_record">准备好录音了</string>
|
||||||
<string name="record_audio">录音</string>
|
<string name="record_audio">录音</string>
|
||||||
<string name="recording">录音中…</string>
|
<string name="recording">录音中…</string>
|
||||||
|
@ -291,6 +298,7 @@
|
||||||
<string name="text_default">默认</string>
|
<string name="text_default">默认</string>
|
||||||
<string name="text_size">文本大小</string>
|
<string name="text_size">文本大小</string>
|
||||||
<string name="theme">主题</string>
|
<string name="theme">主题</string>
|
||||||
|
<string name="theme_use_dynamic_colors">使用系统的壁纸颜色</string>
|
||||||
<string name="title">标题</string>
|
<string name="title">标题</string>
|
||||||
<string name="to_record_audio">要录音,请允许 NotallyX 访问你的麦克风。轻按“设置”>“权限”,打开麦克风</string>
|
<string name="to_record_audio">要录音,请允许 NotallyX 访问你的麦克风。轻按“设置”>“权限”,打开麦克风</string>
|
||||||
<string name="unarchive">取消存档</string>
|
<string name="unarchive">取消存档</string>
|
||||||
|
@ -304,13 +312,13 @@
|
||||||
<string name="unknown_name">未知名称</string>
|
<string name="unknown_name">未知名称</string>
|
||||||
<string name="unlabeled">未加标签</string>
|
<string name="unlabeled">未加标签</string>
|
||||||
<string name="unlock">通过生物特征/PIN解除锁定</string>
|
<string name="unlock">通过生物特征/PIN解除锁定</string>
|
||||||
<string name="unlock_with_biometrics_not_setup">你先前已启用了生物特征锁,但生物特征/PIN目前在你的设备上没有配置好。如果你虚妄停用生物特征锁,请按下“停用”,不然请为你的设备配置生物特征/PIN</string>
|
<string name="unlock_with_biometrics_not_setup">你先前已启用了生物特征锁,但生物特征/PIN目前在你的设备上没有配置好。如果你希望停用生物特征锁,请按下“停用”,不然请为你的设备配置生物特征/PIN</string>
|
||||||
<string name="unpin">取消置顶</string>
|
<string name="unpin">取消置顶</string>
|
||||||
<string name="upcoming">即将到来</string>
|
<string name="upcoming">即将到来</string>
|
||||||
<string name="updated_link">更新了链接</string>
|
<string name="updated_link">更新了链接</string>
|
||||||
<string name="view">视图</string>
|
<string name="view">视图</string>
|
||||||
<string name="view_file">查看文件</string>
|
<string name="view_file">查看文件</string>
|
||||||
<string name="view_note">查看笔记…</string>
|
<string name="view_note">查看笔记</string>
|
||||||
<string name="weekly">一周</string>
|
<string name="weekly">一周</string>
|
||||||
<string name="weeks">周</string>
|
<string name="weeks">周</string>
|
||||||
<string name="wrong_password">提供的密码错误</string>
|
<string name="wrong_password">提供的密码错误</string>
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
<string name="auto_backup_last">最近的備份</string>
|
<string name="auto_backup_last">最近的備份</string>
|
||||||
<string name="auto_backup_on_save">筆記上的備份會自動儲存</string>
|
<string name="auto_backup_on_save">筆記上的備份會自動儲存</string>
|
||||||
<string name="auto_backup_on_save_hint">透過啟用此功能,每當儲存筆記時,都會在選擇的\"備份資料夾\"中自動建立備份(\"NotallyX_AutoBackup.zip\")。\n請注意這可能會影響效能</string>
|
<string name="auto_backup_on_save_hint">透過啟用此功能,每當儲存筆記時,都會在選擇的\"備份資料夾\"中自動建立備份(\"NotallyX_AutoBackup.zip\")。\n請注意這可能會影響效能</string>
|
||||||
<string name="auto_backup_period">自動備份週期</string>
|
|
||||||
<string name="auto_backups_folder">備份資料夾</string>
|
<string name="auto_backups_folder">備份資料夾</string>
|
||||||
<string name="auto_backups_folder_hint">所有自動備份都儲存於其中的資料夾。</string>
|
<string name="auto_backups_folder_hint">所有自動備份都儲存於其中的資料夾。</string>
|
||||||
<string name="auto_backups_folder_rechoose">您需要重新選擇備份資料夾,以便 NotallyX 有權限寫入該資料夾。\n您也可以按取消跳過匯入備份資料夾</string>
|
<string name="auto_backups_folder_rechoose">您需要重新選擇備份資料夾,以便 NotallyX 有權限寫入該資料夾。\n您也可以按取消跳過匯入備份資料夾</string>
|
||||||
|
@ -135,6 +134,8 @@
|
||||||
<string name="help">幫助</string>
|
<string name="help">幫助</string>
|
||||||
<string name="hours">小時</string>
|
<string name="hours">小時</string>
|
||||||
<string name="image_format_not_supported">不支持的圖片格式</string>
|
<string name="image_format_not_supported">不支持的圖片格式</string>
|
||||||
|
<string name="images_hidden_in_overview">啟用此功能後,筆記的圖像將隱藏在概覽中。</string>
|
||||||
|
<string name="images_hidden_in_overview_title">在概覽中隱藏圖片</string>
|
||||||
<string name="import_action">匯入</string>
|
<string name="import_action">匯入</string>
|
||||||
<string name="import_backup">匯入備份</string>
|
<string name="import_backup">匯入備份</string>
|
||||||
<string name="import_backup_password_hint">如果您的備份沒有密碼保護,只需按匯入,否則請輸入正確的密碼。</string>
|
<string name="import_backup_password_hint">如果您的備份沒有密碼保護,只需按匯入,否則請輸入正確的密碼。</string>
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
<string name="auto_backup_last">Last Backup</string>
|
<string name="auto_backup_last">Last Backup</string>
|
||||||
<string name="auto_backup_on_save">Backup on exit Note automatically</string>
|
<string name="auto_backup_on_save">Backup on exit Note automatically</string>
|
||||||
<string name="auto_backup_on_save_hint">By enabling this, a backup (\"NotallyX_AutoBackup.zip\") is automatically created in the configured \"Backups Folder\" whenever a note is exited.\nBe aware this might affect performance</string>
|
<string name="auto_backup_on_save_hint">By enabling this, a backup (\"NotallyX_AutoBackup.zip\") is automatically created in the configured \"Backups Folder\" whenever a note is exited.\nBe aware this might affect performance</string>
|
||||||
<string name="auto_backup_period">Auto backup period</string>
|
|
||||||
<string name="auto_backups_folder">Backups Folder</string>
|
<string name="auto_backups_folder">Backups Folder</string>
|
||||||
<string name="auto_backups_folder_hint">Folder in which all auto backups will be stored in.</string>
|
<string name="auto_backups_folder_hint">Folder in which all auto backups will be stored in.</string>
|
||||||
<string name="auto_backups_folder_rechoose">You need to re-choose your Backups Folder so that NotallyX has permission to write to it.\nYou can also press cancel to skip importing Backups Folder value</string>
|
<string name="auto_backups_folder_rechoose">You need to re-choose your Backups Folder so that NotallyX has permission to write to it.\nYou can also press cancel to skip importing Backups Folder value</string>
|
||||||
|
@ -78,6 +77,8 @@
|
||||||
<string name="color_exists">This color already exists!</string>
|
<string name="color_exists">This color already exists!</string>
|
||||||
<string name="content_density">Content density</string>
|
<string name="content_density">Content density</string>
|
||||||
<string name="continue_">Continue</string>
|
<string name="continue_">Continue</string>
|
||||||
|
<string name="convert_to_list_note">Convert to List</string>
|
||||||
|
<string name="convert_to_text_note">Convert to Text Note</string>
|
||||||
<string name="copied_link">Copied Link to Clipboard</string>
|
<string name="copied_link">Copied Link to Clipboard</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="count" translatable="false">%1$d / %2$d</string>
|
<string name="count" translatable="false">%1$d / %2$d</string>
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
<string name="disable_lock_description">This will also decrypt the database</string>
|
<string name="disable_lock_description">This will also decrypt the database</string>
|
||||||
<string name="disable_lock_title">Disable lock via Biometric/PIN</string>
|
<string name="disable_lock_title">Disable lock via Biometric/PIN</string>
|
||||||
<string name="disabled">Disabled</string>
|
<string name="disabled">Disabled</string>
|
||||||
|
<string name="disallow_screenshots">Disallow Screenshots</string>
|
||||||
<string name="discard">Discard</string>
|
<string name="discard">Discard</string>
|
||||||
<string name="display_text">Text to display</string>
|
<string name="display_text">Text to display</string>
|
||||||
<string name="donate">Make a Donation</string>
|
<string name="donate">Make a Donation</string>
|
||||||
|
@ -159,6 +161,8 @@
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
<string name="hours">Hours</string>
|
<string name="hours">Hours</string>
|
||||||
<string name="image_format_not_supported">Image format not supported</string>
|
<string name="image_format_not_supported">Image format not supported</string>
|
||||||
|
<string name="images_hidden_in_overview">By enabling this, the notes’ images will be hidden in the overview</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Hide Images in Overview</string>
|
||||||
<string name="import_action">Import</string>
|
<string name="import_action">Import</string>
|
||||||
<string name="import_backup">Import backup</string>
|
<string name="import_backup">Import backup</string>
|
||||||
<string name="import_backup_password_hint">If your backup is not password-protected simply press Import, otherwise enter the correct password.</string>
|
<string name="import_backup_password_hint">If your backup is not password-protected simply press Import, otherwise enter the correct password.</string>
|
||||||
|
@ -186,6 +190,8 @@
|
||||||
<string name="invalid_link">Copy a valid link into your clipboard</string>
|
<string name="invalid_link">Copy a valid link into your clipboard</string>
|
||||||
<string name="italic">Italic</string>
|
<string name="italic">Italic</string>
|
||||||
<string name="item">Item</string>
|
<string name="item">Item</string>
|
||||||
|
<string name="json_files">JSON Files</string>
|
||||||
|
<string name="json_files_help">In order to import your Notes from JSON files (single file or folder), click Import. Every valid JSON file is imported as a separate note, the file’s name becomes the note’s title.</string>
|
||||||
<string name="label_exists">Label exists</string>
|
<string name="label_exists">Label exists</string>
|
||||||
<string name="label_visibility">Hide/Show the label in the navigation panel</string>
|
<string name="label_visibility">Hide/Show the label in the navigation panel</string>
|
||||||
<string name="labels">Labels</string>
|
<string name="labels">Labels</string>
|
||||||
|
@ -240,6 +246,7 @@
|
||||||
<string name="please_grant_notally_notification_auto_backup">If an auto backup fails you can receive a notification, if you grant NotallyX permission to send notifications</string>
|
<string name="please_grant_notally_notification_auto_backup">If an auto backup fails you can receive a notification, if you grant NotallyX permission to send notifications</string>
|
||||||
<string name="previous">Previous</string>
|
<string name="previous">Previous</string>
|
||||||
<string name="rate">Rate this app</string>
|
<string name="rate">Rate this app</string>
|
||||||
|
<string name="read_only">Read Only</string>
|
||||||
<string name="ready_to_record">Ready to record</string>
|
<string name="ready_to_record">Ready to record</string>
|
||||||
<string name="record_audio">Record audio</string>
|
<string name="record_audio">Record audio</string>
|
||||||
<string name="recording">Recording…</string>
|
<string name="recording">Recording…</string>
|
||||||
|
@ -294,6 +301,7 @@
|
||||||
<string name="text_default">Default</string>
|
<string name="text_default">Default</string>
|
||||||
<string name="text_size">Text size</string>
|
<string name="text_size">Text size</string>
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Use system\'s wallpaper colors</string>
|
||||||
<string name="title">Title</string>
|
<string name="title">Title</string>
|
||||||
<string name="to_record_audio">To record audio, allow NotallyX access to your microphone. Tap Settings > Permissions and turn Microphone on</string>
|
<string name="to_record_audio">To record audio, allow NotallyX access to your microphone. Tap Settings > Permissions and turn Microphone on</string>
|
||||||
<string name="unarchive">Unarchive</string>
|
<string name="unarchive">Unarchive</string>
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
|
|
||||||
<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||||
<item name="bottomSheetStyle">@style/ModalBottomSheet</item>
|
<item name="bottomSheetStyle">@style/ModalBottomSheet</item>
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ModalBottomSheet" parent="Widget.Material3.BottomSheet.Modal">
|
<style name="ModalBottomSheet" parent="Widget.Material3.BottomSheet.Modal">
|
||||||
|
|
|
@ -46,6 +46,24 @@ class ChangeHistoryTest {
|
||||||
assertTrue(changeHistory.canRedo.value)
|
assertTrue(changeHistory.canRedo.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test undoAll when stack has multiple changes`() {
|
||||||
|
val change = mock<Change>()
|
||||||
|
val change1 = mock<Change>()
|
||||||
|
val change2 = mock<Change>()
|
||||||
|
|
||||||
|
changeHistory.push(change)
|
||||||
|
changeHistory.push(change1)
|
||||||
|
changeHistory.push(change2)
|
||||||
|
changeHistory.undoAll()
|
||||||
|
|
||||||
|
verify(change).undo()
|
||||||
|
verify(change1).undo()
|
||||||
|
verify(change2).undo()
|
||||||
|
assertFalse(changeHistory.canUndo.value)
|
||||||
|
assertTrue(changeHistory.canRedo.value)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test redo when stack has one change`() {
|
fun `test redo when stack has one change`() {
|
||||||
val change = mock<Change>()
|
val change = mock<Change>()
|
||||||
|
@ -59,6 +77,25 @@ class ChangeHistoryTest {
|
||||||
assertFalse(changeHistory.canRedo.value)
|
assertFalse(changeHistory.canRedo.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test redoAll when stack has multiple changes`() {
|
||||||
|
val change = mock<Change>()
|
||||||
|
val change1 = mock<Change>()
|
||||||
|
val change2 = mock<Change>()
|
||||||
|
|
||||||
|
changeHistory.push(change)
|
||||||
|
changeHistory.push(change1)
|
||||||
|
changeHistory.push(change2)
|
||||||
|
changeHistory.undoAll()
|
||||||
|
changeHistory.redoAll()
|
||||||
|
|
||||||
|
verify(change2).redo()
|
||||||
|
verify(change1).redo()
|
||||||
|
verify(change).redo()
|
||||||
|
assertTrue(changeHistory.canUndo.value)
|
||||||
|
assertFalse(changeHistory.canRedo.value)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test canUndo and canRedo logic`() {
|
fun `test canUndo and canRedo logic`() {
|
||||||
val change = mock<Change>()
|
val change = mock<Change>()
|
||||||
|
|
|
@ -157,6 +157,7 @@ class NotesImporterTest {
|
||||||
ImportSource.GOOGLE_KEEP -> File(tempDir, "Takeout.zip")
|
ImportSource.GOOGLE_KEEP -> File(tempDir, "Takeout.zip")
|
||||||
ImportSource.EVERNOTE -> File(tempDir, "Notebook.enex")
|
ImportSource.EVERNOTE -> File(tempDir, "Notebook.enex")
|
||||||
ImportSource.PLAIN_TEXT -> File(tempDir, "text.txt")
|
ImportSource.PLAIN_TEXT -> File(tempDir, "text.txt")
|
||||||
|
ImportSource.JSON -> File(tempDir, "text.json")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.FileAttachment
|
import com.philkes.notallyx.data.model.FileAttachment
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
import com.philkes.notallyx.data.model.ListItem
|
import com.philkes.notallyx.data.model.ListItem
|
||||||
|
import com.philkes.notallyx.data.model.NoteViewMode
|
||||||
import com.philkes.notallyx.data.model.Reminder
|
import com.philkes.notallyx.data.model.Reminder
|
||||||
import com.philkes.notallyx.data.model.SpanRepresentation
|
import com.philkes.notallyx.data.model.SpanRepresentation
|
||||||
import com.philkes.notallyx.data.model.Type
|
import com.philkes.notallyx.data.model.Type
|
||||||
|
@ -252,6 +253,7 @@ class GoogleKeepImporterTest {
|
||||||
files,
|
files,
|
||||||
audios,
|
audios,
|
||||||
reminders,
|
reminders,
|
||||||
|
NoteViewMode.EDIT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
package com.philkes.notallyx.data.model
|
||||||
|
|
||||||
|
import com.philkes.notallyx.test.createListItem
|
||||||
|
import java.util.Date
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mockito.anyString
|
||||||
|
import org.mockito.Mockito.mockStatic
|
||||||
|
|
||||||
|
class ModelExtensionsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Json toBaseNote()`() {
|
||||||
|
val json =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"type": "LIST",
|
||||||
|
"color": "#E2F6D3",
|
||||||
|
"title": "Test",
|
||||||
|
"pinned": false,
|
||||||
|
"timestamp": 1742822848689,
|
||||||
|
"modifiedTimestamp": 1742823434623,
|
||||||
|
"labels": [
|
||||||
|
"TestLabel"
|
||||||
|
],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"body": "A",
|
||||||
|
"checked": false,
|
||||||
|
"isChild": false,
|
||||||
|
"order": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "B",
|
||||||
|
"checked": true,
|
||||||
|
"isChild": true,
|
||||||
|
"order": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "C",
|
||||||
|
"checked": false,
|
||||||
|
"isChild": true,
|
||||||
|
"order": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "D",
|
||||||
|
"checked": false,
|
||||||
|
"isChild": false,
|
||||||
|
"order": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "E",
|
||||||
|
"checked": true,
|
||||||
|
"isChild": false,
|
||||||
|
"order": 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reminders": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"dateTime": 1742822940000,
|
||||||
|
"repetition": "{\"value\":1,\"unit\":\"DAYS\"}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"viewMode": "READ_ONLY"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
val colorMock = mockStatic(android.graphics.Color::class.java)
|
||||||
|
colorMock.`when`<Int> { android.graphics.Color.parseColor(anyString()) }.thenReturn(1)
|
||||||
|
|
||||||
|
val baseNote = json.toBaseNote()
|
||||||
|
|
||||||
|
assertEquals("Test", baseNote.title)
|
||||||
|
assertEquals(false, baseNote.pinned)
|
||||||
|
assertEquals("#E2F6D3", baseNote.color)
|
||||||
|
assertEquals(Folder.NOTES, baseNote.folder)
|
||||||
|
assertEquals(
|
||||||
|
mutableListOf(
|
||||||
|
ListItem("A", false, false, 0, mutableListOf()),
|
||||||
|
ListItem("B", true, true, 1, mutableListOf()),
|
||||||
|
ListItem("C", false, true, 2, mutableListOf()),
|
||||||
|
ListItem("D", false, false, 3, mutableListOf()),
|
||||||
|
ListItem("E", true, false, 4, mutableListOf()),
|
||||||
|
),
|
||||||
|
baseNote.items,
|
||||||
|
)
|
||||||
|
assertEquals(1, baseNote.reminders.size)
|
||||||
|
assertEquals(1742822848689, baseNote.timestamp)
|
||||||
|
assertEquals(1742823434623, baseNote.modifiedTimestamp)
|
||||||
|
assertEquals(NoteViewMode.READ_ONLY, baseNote.viewMode)
|
||||||
|
assertEquals(listOf("TestLabel"), baseNote.labels)
|
||||||
|
assertEquals(Repetition(1, RepetitionTimeUnit.DAYS), baseNote.reminders[0].repetition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `BaseNote toJson()`() {
|
||||||
|
val baseNote =
|
||||||
|
BaseNote(
|
||||||
|
id = 1,
|
||||||
|
Type.LIST,
|
||||||
|
Folder.DELETED,
|
||||||
|
"#E2F6D3",
|
||||||
|
"Title",
|
||||||
|
true,
|
||||||
|
12354632465L,
|
||||||
|
945869546L,
|
||||||
|
listOf("label"),
|
||||||
|
"Body",
|
||||||
|
listOf(SpanRepresentation(0, 10, bold = true)),
|
||||||
|
mutableListOf(
|
||||||
|
createListItem("Item1", true, false),
|
||||||
|
createListItem("Item2", true, true),
|
||||||
|
),
|
||||||
|
listOf(FileAttachment("localImage", "originalImage", "image/jpeg")),
|
||||||
|
listOf(FileAttachment("localFile", "originalFile", "text/plain")),
|
||||||
|
listOf(Audio("audio", 10L, 12312334L)),
|
||||||
|
listOf(Reminder(1, Date(1743253506957), Repetition(10, RepetitionTimeUnit.WEEKS))),
|
||||||
|
NoteViewMode.READ_ONLY,
|
||||||
|
)
|
||||||
|
|
||||||
|
val json = baseNote.toJson()
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"reminders": [{
|
||||||
|
"dateTime": 1743253506957,
|
||||||
|
"id": 1,
|
||||||
|
"repetition": {
|
||||||
|
"unit": "WEEKS",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"pinned": true,
|
||||||
|
"color": "#E2F6D3",
|
||||||
|
"modifiedTimestamp": 945869546,
|
||||||
|
"type": "LIST",
|
||||||
|
"title": "Title",
|
||||||
|
"viewMode": "READ_ONLY",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"checked": true,
|
||||||
|
"body": "Item1",
|
||||||
|
"isChild": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checked": true,
|
||||||
|
"body": "Item2",
|
||||||
|
"isChild": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": 12354632465,
|
||||||
|
"labels": ["label"]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
.trimStart(),
|
||||||
|
json,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
20
documentation/.gitignore
vendored
Normal file
20
documentation/.gitignore
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.docusaurus
|
||||||
|
.cache-loader
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
12
documentation/blog/2019-05-28-first-blog-post.md
Normal file
12
documentation/blog/2019-05-28-first-blog-post.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
slug: first-blog-post
|
||||||
|
title: First Blog Post
|
||||||
|
authors: [slorber, yangshun]
|
||||||
|
tags: [hola, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet...
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
44
documentation/blog/2019-05-29-long-blog-post.md
Normal file
44
documentation/blog/2019-05-29-long-blog-post.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
slug: long-blog-post
|
||||||
|
title: Long Blog Post
|
||||||
|
authors: yangshun
|
||||||
|
tags: [hello, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the summary of a very long blog post,
|
||||||
|
|
||||||
|
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
24
documentation/blog/2021-08-01-mdx-blog-post.mdx
Normal file
24
documentation/blog/2021-08-01-mdx-blog-post.mdx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
slug: mdx-blog-post
|
||||||
|
title: MDX Blog Post
|
||||||
|
authors: [slorber]
|
||||||
|
tags: [docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Use the power of React to create interactive blog posts.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
{/* truncate */}
|
||||||
|
|
||||||
|
For example, use JSX to create an interactive button:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
29
documentation/blog/2021-08-26-welcome/index.md
Normal file
29
documentation/blog/2021-08-26-welcome/index.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
slug: welcome
|
||||||
|
title: Welcome
|
||||||
|
authors: [slorber, yangshun]
|
||||||
|
tags: [facebook, hello, docusaurus]
|
||||||
|
---
|
||||||
|
|
||||||
|
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
|
||||||
|
|
||||||
|
Here are a few tips you might find useful.
|
||||||
|
|
||||||
|
<!-- truncate -->
|
||||||
|
|
||||||
|
Simply add Markdown files (or folders) to the `blog` directory.
|
||||||
|
|
||||||
|
Regular blog authors can be added to `authors.yml`.
|
||||||
|
|
||||||
|
The blog post date can be extracted from filenames, such as:
|
||||||
|
|
||||||
|
- `2019-05-30-welcome.md`
|
||||||
|
- `2019-05-30-welcome/index.md`
|
||||||
|
|
||||||
|
A blog post folder can be convenient to co-locate blog post images:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The blog supports tags as well!
|
||||||
|
|
||||||
|
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
|
23
documentation/blog/authors.yml
Normal file
23
documentation/blog/authors.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
yangshun:
|
||||||
|
name: Yangshun Tay
|
||||||
|
title: Front End Engineer @ Facebook
|
||||||
|
url: https://github.com/yangshun
|
||||||
|
image_url: https://github.com/yangshun.png
|
||||||
|
page: true
|
||||||
|
socials:
|
||||||
|
x: yangshunz
|
||||||
|
github: yangshun
|
||||||
|
|
||||||
|
slorber:
|
||||||
|
name: Sébastien Lorber
|
||||||
|
title: Docusaurus maintainer
|
||||||
|
url: https://sebastienlorber.com
|
||||||
|
image_url: https://github.com/slorber.png
|
||||||
|
page:
|
||||||
|
# customize the url of the author page at /blog/authors/<permalink>
|
||||||
|
permalink: '/all-sebastien-lorber-articles'
|
||||||
|
socials:
|
||||||
|
x: sebastienlorber
|
||||||
|
linkedin: sebastienlorber
|
||||||
|
github: slorber
|
||||||
|
newsletter: https://thisweekinreact.com
|
19
documentation/blog/tags.yml
Normal file
19
documentation/blog/tags.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
facebook:
|
||||||
|
label: Facebook
|
||||||
|
permalink: /facebook
|
||||||
|
description: Facebook tag description
|
||||||
|
|
||||||
|
hello:
|
||||||
|
label: Hello
|
||||||
|
permalink: /hello
|
||||||
|
description: Hello tag description
|
||||||
|
|
||||||
|
docusaurus:
|
||||||
|
label: Docusaurus
|
||||||
|
permalink: /docusaurus
|
||||||
|
description: Docusaurus tag description
|
||||||
|
|
||||||
|
hola:
|
||||||
|
label: Hola
|
||||||
|
permalink: /hola
|
||||||
|
description: Hola tag description
|
47
documentation/docs/get-started.md
Normal file
47
documentation/docs/get-started.md
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tutorial Intro
|
||||||
|
|
||||||
|
Let's discover **Docusaurus in less than 5 minutes**.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Get started by **creating a new site**.
|
||||||
|
|
||||||
|
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
|
||||||
|
|
||||||
|
### What you'll need
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
|
||||||
|
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||||
|
|
||||||
|
## Generate a new site
|
||||||
|
|
||||||
|
Generate a new Docusaurus site using the **classic template**.
|
||||||
|
|
||||||
|
The classic template will automatically be added to your project after you run the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm init docusaurus@latest my-website classic
|
||||||
|
```
|
||||||
|
|
||||||
|
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
|
||||||
|
|
||||||
|
The command also installs all necessary dependencies you need to run Docusaurus.
|
||||||
|
|
||||||
|
## Start your site
|
||||||
|
|
||||||
|
Run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd my-website
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
|
||||||
|
|
||||||
|
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
|
||||||
|
|
||||||
|
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
|
8
documentation/docs/tutorial-basics/_category_.json
Normal file
8
documentation/docs/tutorial-basics/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"label": "Tutorial - Basics",
|
||||||
|
"position": 2,
|
||||||
|
"link": {
|
||||||
|
"type": "generated-index",
|
||||||
|
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue