Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3fd6b34
chore: start next version dependency and security updates
pasichDev Oct 14, 2026
9433e1b
chore: keep release planning outside repository
pasichDev Oct 14, 2026
128adaa
feat: add compact note spacing and complete dependency integration
pasichDev Oct 14, 2026
57b060c
fix: preserve note header spacing in compact mode
pasichDev Oct 14, 2026
bbe9f3f
refactor(security): bind vault operations to database session
pasichDev Oct 14, 2026
ad3080f
docs: translate Encly on Hosted Weblate
pasichDev Oct 14, 2026
426af53
feat(security): add duress PIN with a separate decoy vault
pasichDev Oct 14, 2026
c5819be
feat(import): add an offline Import notes screen for other apps
pasichDev Oct 14, 2026
2ebf9af
feat(editor): add writing comfort tools
pasichDev Oct 15, 2026
a1a2e53
Merge remote-tracking branch 'origin/main' into feat/next-version-iss…
pasichDev Oct 15, 2026
c2a8abe
fix(security): bound PIN guesses around the duress PIN and hide the d…
pasichDev Oct 15, 2026
7b50741
fix(import): keep media out of the limits and fix review findings
pasichDev Oct 15, 2026
ef6a290
test: raise app coverage and gate it at 85 percent
pasichDev Oct 15, 2026
68f7a62
fix(navigation): keep the back gesture on the shared-axis motion
pasichDev Oct 15, 2026
a8f284b
feat(import): show supported apps as compact expandable cards
pasichDev Oct 15, 2026
0366c48
fix(import): hide select-all when every note is already in the vault
pasichDev Oct 15, 2026
502353f
refactor(import): name the source card for what it is and document th…
pasichDev Oct 15, 2026
efdb390
build(deps): align Compose on the BOM and update libraries to latest …
pasichDev Oct 15, 2026
5ce0af5
release: 2.2.0, note import, a duress PIN and editor upgrades
pasichDev Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/wrapper-validation@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0

- name: Set up JDK 21
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0

- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
Expand All @@ -74,16 +74,24 @@ jobs:
- name: Check merged manifest has no INTERNET permission
run: |
set -euo pipefail
# AGP has used both directory names; a check that finds no manifest must fail, not pass.
manifests=$(find app/build/intermediates/merged_manifest app/build/intermediates/merged_manifests \
-name AndroidManifest.xml -path '*fdroidRelease*' 2>/dev/null || true)
if [ -z "$manifests" ]; then
echo "::error::No merged fdroidRelease manifest found to check"
exit 1
fi
found=0
for m in $(find app/build/intermediates/merged_manifests -name AndroidManifest.xml -path '*elease*'); do
for m in $manifests; do
echo "Checking $m"
if grep -q 'android.permission.INTERNET' "$m"; then
echo "::error file=$m::INTERNET permission found in merged release manifest"
found=1
fi
done
exit $found

# For information only: posts (and keeps updating) one PR comment with the core/security
# For information only: posts (and keeps updating) one PR comment with the app
# coverage. It never fails the build.
- name: Coverage comment
if: github.event_name == 'pull_request'
Expand All @@ -92,7 +100,7 @@ jobs:
with:
paths: app/build/reports/kover/reportFdroidDebug.xml
token: ${{ secrets.GITHUB_TOKEN }}
title: 'Coverage (core/security)'
title: 'Coverage'
update-comment: true
min-coverage-overall: 0
min-coverage-changed-files: 0
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish-play.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
ref: refs/tags/${{ inputs.ref }}

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/wrapper-validation@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0

- name: Tag must match version.properties
id: version
Expand Down Expand Up @@ -97,13 +97,13 @@ jobs:
exit $missing

- name: Set up JDK 21
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
with:
cache-read-only: true

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/wrapper-validation@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0

- name: Tag must match version.properties
id: version
Expand Down Expand Up @@ -71,13 +71,13 @@ jobs:
exit $missing

- name: Set up JDK 21
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'

- name: Set up Gradle
uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
with:
cache-read-only: true

Expand Down
54 changes: 53 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,57 @@ IzzyOnDroid) and used as the GitHub Release notes.

## [Unreleased]

## [2.2.0] - 2026-10-15

versionCode 20200.

Notes can be imported from other apps, an optional duress PIN opens a separate decoy vault, and
the editor gets slash commands, smarter paste, find in note and templates. The database and the
backup format do not change.

### Added

- Import notes (Settings → Import): bring notes from Google Keep, Standard Notes, Joplin, Notesnook or
Markdown/TXT files, fully offline. The format is recognised from the files, you review and tick the
notes first, and a repeated import adds nothing. Photos and other attachments are left out and
listed without counting toward the size limit, so large Takeouts and Markdown vaults import.
- Reading density for notes (Compact, Default, Comfortable) from the editor's More actions, with a
persistent setting; Default is the existing layout and the stored note never changes.
- Slash commands: type `/` in an empty paragraph to pick a block kind (text, headings, checklist,
lists, quote, link, divider) from a small filtered list. Not available in Simple Edit.
- Smarter paste: pasted text keeps its paragraphs (a blank line starts a new one, a single line
break stays inside it) and Markdown headings, lists, checkboxes, quotes and rules become blocks.
Pasted after existing text, the first line stays plain text. Nothing is rewritten or dropped,
and one undo takes the whole paste back.
- Block actions: duplicate a block from the toolbar, the block sheets and TalkBack, next to move,
delete and convert.
- Find in this note (More actions, or Ctrl/Cmd+F): highlights matches, shows "n of m" and jumps
between them without changing the note. The query is never saved and is cleared when the vault locks.
- Start from template: an optional Meeting, Journal, Project notes or Checklist outline for a new,
empty note. Blank stays the default.
- Optional 15- and 30-minute auto-lock delays, with a warning about longer background sessions.
- Duress PIN (Settings → Security): a third PIN that opens a separate, fully usable decoy vault
with its own encryption key and database, from the same lock screen and with the same unlock.
The real vault stays closed and unchanged, and nothing in the decoy vault leads back to it.
Unlike the wipe PIN it erases nothing, and typing it between wrong guesses never lifts the
limit on guesses at the real PIN. It does not hide the real vault from a forensic
examination (see SECURITY.md).

### Changed

- Pasting several lines of plain text keeps paragraphs (a blank line starts a new block) instead
of creating one block per line.
- The predictive back gesture plays the same sliding motion as the back button instead of
shrinking the screen.
- Dependencies updated to their latest stable versions, Compose through its BOM; the app now
compiles against SDK 37 (minimum and target SDK unchanged).

### ⚠️ Compatibility

- Going back to 2.1.0 keeps the vault and its backups readable: neither the database nor the
backup format changes. The duress PIN only works in 2.2.0 and later, and changing the PIN in an
older version turns it off (see SECURITY.md → Duress PIN).

## [2.1.0] - 2026-10-05

versionCode 20100.
Expand Down Expand Up @@ -194,7 +245,8 @@ The last release of the old storage format (versionCode 30): the SQLCipher key w
Keystore-sealed seed hash, with an optional 4-digit PIN. It had no public users; superseded by
2.0.0.

[Unreleased]: https://github.com/pasichDev/Encly/compare/v2.1.0...HEAD
[Unreleased]: https://github.com/pasichDev/Encly/compare/v2.2.0...HEAD
[2.2.0]: https://github.com/pasichDev/Encly/releases/tag/v2.2.0
[2.1.0]: https://github.com/pasichDev/Encly/releases/tag/v2.1.0
[2.0.1]: https://github.com/pasichDev/Encly/releases/tag/v2.0.1
[2.0.0]: https://github.com/pasichDev/Encly/releases/tag/v2.0.0
17 changes: 9 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ Run the same gates as CI:
./gradlew assembleRelease # R8 build
```

- Add or extend unit tests for behaviour you change. Code in `core/security` has a minimum line
coverage gate (Kover, see `app/build.gradle.kts`); do not lower it to get a PR through.
- Add or extend unit tests for behaviour you change. The app has a minimum line coverage gate of
85% (Kover, generated code excluded, see `app/build.gradle.kts`); do not lower it to get a PR
through.
- For storage, Keystore or SQLCipher changes also run the instrumented tests on an emulator:
`./gradlew :app:connectedFdroidDebugAndroidTest` (they wipe the debug app's data).
- New detekt or lint findings must be fixed, not added to `app/detekt-baseline.xml` or
Expand Down Expand Up @@ -104,8 +105,8 @@ purpose.

Every user-visible string lives in string resources. English is the default
(`app/src/main/res/values/`); each translation is a `values-<lang>/` folder with a `strings.xml`
(most of the UI), a `strings_security.xml` (security settings) and a `strings_handoff.xml` (the
import from My Notes).
(most of the UI), a `strings_security.xml` (security settings), a `strings_handoff.xml` (the
import from My Notes) and a `strings_import.xml` (Import notes from other apps).

**Translating on Weblate (recommended):** the project is on
[Hosted Weblate](https://hosted.weblate.org/projects/encly/), one component per file (*App*,
Expand All @@ -116,8 +117,8 @@ it never pushes to `main`. That pull request runs the same CI as any other, so i
when every check below passes.

**Fixing a translation in Git:** edit the string in `values-<lang>/strings.xml` (or
`strings_security.xml`, `strings_handoff.xml`) and open a PR. Keep the key; only change the
text. If a Weblate pull request is open, say so in yours; Weblate picks up merged changes from
`strings_security.xml`, `strings_handoff.xml`, `strings_import.xml`) and open a PR. Keep the key;
only change the text. If a Weblate pull request is open, say so in yours; Weblate picks up merged changes from
`main` on its own.

**For maintainers:** Weblate pulls `main` on every push (GitHub webhook
Expand All @@ -138,8 +139,8 @@ lower.

**Adding a language:**

1. start it on Weblate, or copy `values/strings.xml`, `values/strings_security.xml` and
`values/strings_handoff.xml` to `values-<lang>/` and translate them,
1. start it on Weblate, or copy `values/strings.xml`, `values/strings_security.xml`,
`values/strings_handoff.xml` and `values/strings_import.xml` to `values-<lang>/` and translate them,
leaving out the entries marked `translatable="false"`;
2. add `<locale android:name="<lang>" />` to `res/xml/locales_config.xml`;
3. add an entry to `AppLanguage` (`core/locale/AppLanguage.kt`) and its own-language name as a
Expand Down
12 changes: 12 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ Encly makes while reading it is deleted right away (or, if Encly is closed in th
time it starts); the imported notes are stored encrypted like everything else. Nothing is sent over the network, and Encly sends nothing back to My Notes
except how many items were imported. Attachments and images stay in My Notes.

## Import notes from other apps

**Settings → Import notes** reads export files of Google Keep (Takeout), Standard Notes
(decrypted backup), Joplin (JEX or RAW), Notesnook (Markdown or plain-text export) and plain
Markdown/text notes such as an Obsidian vault. You choose the files or folder in the system file
picker; Encly reads only what you picked, only while the app is unlocked, and only on this
phone. Nothing is uploaded, no account is needed, and no network is used. The files are read
into memory, never copied to disk, and the files themselves are left unchanged. You see the list
of notes before anything is saved, pick which ones to import, and the selected notes are stored
encrypted in the vault that is open. Encrypted exports of those apps are not read; export them
in a readable format first.

## Deleting your data

Uninstalling Encly, or clearing its storage in Android settings, permanently deletes all of
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Every release ships a `SHA256SUMS` file (and `SHA256SUMS.asc` when it is GPG-sig

```bash
sha256sum -c --ignore-missing SHA256SUMS # APK matches the published checksum
apksigner verify --print-certs Encly-2.1.0-fdroid.apk
apksigner verify --print-certs Encly-2.2.0-fdroid.apk
```

`apksigner` (Android SDK build-tools) must print this release signing certificate SHA-256
Expand Down Expand Up @@ -181,7 +181,7 @@ links, no baseline profile) and `play`.
```bash
./gradlew assembleFdroidDebug # debug APK (com.pasich.encly.debug)
./gradlew :app:testFdroidDebugUnitTest # JVM unit tests
./gradlew :app:koverVerifyFdroidDebug # coverage gate for core/security
./gradlew :app:koverVerifyFdroidDebug # coverage gate (whole app, 85%)
./gradlew :app:detekt :app:lintFdroidRelease # static analysis
./gradlew spotlessApply # format Kotlin/Gradle (ktlint; spotlessCheck in CI)
./gradlew assembleRelease # both release APKs, R8 enabled
Expand Down
Loading
Loading