Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 10 additions & 7 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ body:
id: app-version
attributes:
label: Encly version
description: About screen, e.g. 2.0.0. Add where you installed it from (GitHub Releases, F-Droid, IzzyOnDroid, Google Play, own build).
placeholder: 2.0.0 (GitHub Releases)
description: Settings → About, e.g. 2.0.0.
placeholder: 2.0.0
validations:
required: true
- type: input
Expand All @@ -32,13 +32,16 @@ body:
validations:
required: true
- type: dropdown
id: upgrade
id: source
attributes:
label: Did this start after updating from Encly 1.x?
label: Installed from
options:
- "No / fresh install"
- "Yes, after upgrading from 1.x"
- "Not sure"
- "GitHub Releases or Obtainium (fdroid APK)"
- "GitHub Releases or Obtainium (play APK)"
- "F-Droid"
- "Google Play"
- "Built from source"
- "Other / not sure"
validations:
required: true
- type: textarea
Expand Down
164 changes: 91 additions & 73 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,105 +11,123 @@ IzzyOnDroid) and used as the GitHub Release notes.

## [Unreleased]

## [2.0.0] - 2026-09-24
## [2.0.0] - 2026-09-25

versionCode 20000.

The first public release. Encly 2.0 is rebuilt around a new encrypted vault, with a PIN bound to
the phone's secure hardware, auto-lock, encrypted backups, a new design and nine languages. It
ships on GitHub Releases first; F-Droid and Google Play follow.

### ⚠️ Breaking: new vault format

- Storage moves to the **v2 vault**: SQLCipher is keyed by a random 256-bit data key (DEK) that
is wrapped separately by the PIN (PBKDF2-HMAC-SHA256, 600,000 iterations + AES-256-GCM), by an
optional auth-bound biometric Keystore key, and by an optional BIP39 recovery seed.
- SQLCipher is now keyed by a random 256-bit data key (DEK). The PIN, an optional biometric
Keystore key and an optional 12-word recovery phrase each wrap that key in their own
AES-256-GCM slot.
- **1.x data is not migrated.** 1.x had no public users; uninstall it before installing 2.0.
First-run setup still refuses to create a vault over an encrypted database it cannot open,
and asks for an explicit wipe instead.
First-run setup refuses to create a vault over an encrypted database it cannot open, and asks
for an explicit wipe instead.

### Added

- Encrypted backups (Settings → Backup): export and import of all notes, tasks and tags as one
AES-256-GCM file keyed by the recovery seed (HKDF-SHA256, random salt and nonce per file),
saved through the system file picker. Merge or replace on import, in one transaction.
"Restore from backup" in onboarding recreates a vault on a new phone from the file and the
12 words. A vault without a recovery seed can add one before its first export.
- Mandatory 6-digit PIN; optional Class 3 biometric unlock bound to a `BiometricPrompt.CryptoObject`.
- The database is closed and the in-memory key zeroized when the app goes to the background.
- Privacy policy, published at <https://pasichdev.xyz/apps/encly/privacy-policy/> (mirrored in
[PRIVACY.md](PRIVACY.md)) and linked from the About screen and both store listings.
- `fdroid` and `play` distribution flavors (same application ID). The `fdroid` flavor has no
Google Play "Rate app" link and no baseline profile, for reproducible builds.
- Signed release workflow: tag `vX.Y.Z` builds, signs (when secrets exist), and publishes APKs
with `SHA256SUMS`.
- Store listings for Google Play and F-Droid in all 9 languages under `fastlane/metadata/android`,
a Play App Bundle of the `play` flavor (`bundlePlayRelease`, signed with the same key, all
languages in the base module so the in-app language picker keeps working), and a Play upload
workflow that is disabled until explicitly enabled. Release builds no longer embed VCS info.
- The interface is available in English (now the default), Ukrainian, German, French, Spanish,
Italian, Polish, Portuguese and Dutch, with an in-app language picker (Settings → Language,
including "System default"); on Android 13+ the choice also appears in the system's per-app
language settings. A unit test and lint (`MissingTranslation` as an error) keep every locale
complete.
- Settings → Security: create a recovery phrase later (after the PIN, with the same three-word
check as onboarding), and "Erase all data" behind the PIN and an explicit confirmation.
- New brand: an "E" lettermark launcher icon with a themed (monochrome) layer, used in the app in
place of the old lock tile, and one icon set drawn in the app's stroke.
- Unlock animation: after a PIN or fingerprint unlock the logo tile grows to fill the window and
the notes list slides in under it, without the lock screen flashing.
- Recovery phrase entry as 12 numbered word cells, shared by onboarding, recovery from the lock
screen, backup import and phrase confirmation, with paste, a per-word BIP39 check and the
checksum check.
- Editor toolbar: a button that hides the keyboard and brings it back to the block you were
writing in (also on OEM keyboards that ignore one of the two Android APIs).
- Link blocks show as offline cards: the host as the title and the rest of the address under it.
Nothing is fetched to build them.
- Onboarding rebuilt as one flow with progress: welcome, PIN with optional fingerprint, why the
recovery phrase matters, write it down, check three words. "I have a backup" leads to restore;
"PIN only, no backups" skips the phrase.
- A rebuilt Support page and an updated FAQ; donations (Ko-fi) appear only in the F-Droid build.
- Tag drag-to-reorder, designed empty states, and a discard confirmation in the editor.
**Security**

- Mandatory 6-digit PIN, bound to this phone: PBKDF2-HMAC-SHA256 (600,000 iterations) is mixed
with an HMAC from a non-exportable Android Keystore key (StrongBox where the phone has one), so
PIN guesses can only run on the device. Encly stores no PIN hash. Wrong PINs lock the app out
from the 5th miss, doubling up to 24 hours; a reboot or a clock change does not shorten it.
- Optional Class 3 biometric unlock, bound to a `BiometricPrompt.CryptoObject`.
- Auto-lock: after you leave the app, the database is closed and its key wiped from memory once
the delay chosen in Settings → Security has passed (immediately, 15 s by default, 30 s, 1 min
or 2 min). Turning the screen off or locking the phone locks Encly at once.
- A new Security page: an encryption status card, "How Encly protects your notes", biometric
unlock, the auto-lock delay, strict keyboard privacy, creating or replacing the recovery
phrase, and a danger zone with **Erase all data** (hold for 5 s, confirm, then PIN or
fingerprint).
- Optional 12-word BIP39 recovery phrase: unlocks the vault when the PIN is forgotten, and is
the key to encrypted backups. It can be created during setup or later, and replaced.
- Encrypted backups (Settings → Backup): all notes, tasks and tags in one AES-256-GCM file keyed
by the recovery phrase, saved wherever you choose through the system file picker. Import merges
or replaces in one transaction, and "Restore from backup" in setup rebuilds a vault on a new
phone from the file and the 12 words.
- Strict keyboard privacy (opt-in): text fields ask the keyboard for no suggestions and no cloud
prediction. Every field already asks it not to learn from what you type.
- Autofill is excluded, other apps' overlays are hidden (Android 12+), screen content is marked
sensitive for accessibility services (Android 14+), and anything copied is marked sensitive
and cleared from the clipboard after 60 s.

**Design and editing**

- A new design: one type scale, spacing and component set across every screen; five colour
themes (Paper, Forest, Ocean, Graphite, Midnight), System / Light / Dark mode, dynamic colour
on Android 12+, and three bundled font sets (Editorial, Modern, Technical).
- Page transitions on Material's shared X axis, and an unlock animation in which the logo tile
grows to fill the window on the first unlock after launch (later unlocks cross-fade).
- A new "E" launcher icon with a themed (monochrome) layer.
- Onboarding as one flow with progress: welcome, PIN with optional fingerprint, why the recovery
phrase matters, write it down, check three words. "I have a backup" leads to restore; "PIN
only, no backups" skips the phrase.
- Recovery phrase entry as 12 numbered cells with paste, a per-word BIP39 check and the checksum
check, used everywhere the phrase is typed.
- Editor: Enter splits a block at the cursor and Backspace merges, Markdown-style shortcuts
(`# `, `- `, `1. `), multi-line paste into blocks, per-word undo, a button that hides the
keyboard and brings it back to the block you were writing in, and a discard confirmation.
- Link blocks show as offline cards (host as the title, the rest of the address under it);
nothing is fetched to build them. Only `http`, `https` and `mailto` links are accepted.
- Tag drag-to-reorder and designed empty states.
- Nine languages: English (now the default), Ukrainian, German, French, Spanish, Italian,
Polish, Portuguese and Dutch, with an in-app picker (Settings → Language, including "System
default").
- A rebuilt Support page, an updated FAQ and an open-source licenses page. Donations (Ko-fi)
appear only in the `fdroid` flavor.

**Distribution**

- `fdroid` and `play` flavors with the same application ID. The `fdroid` flavor has no Google
Play link and no baseline profile, for reproducible builds.
- Release workflow: a `vX.Y.Z` tag builds both flavors unsigned, signs them with `apksigner`,
checks the signing certificate against the fingerprint published in the README, and publishes
the APKs with `SHA256SUMS`. It fails rather than publish an unsigned APK.
- Store listings, screenshots and release notes for all nine languages under
`fastlane/metadata/android`.
- A privacy policy, published at <https://pasichdev.xyz/apps/encly/privacy-policy/> and linked
from the About screen and the store listings.

### Changed

- Fonts (Playfair Display, Source Sans 3, IBM Plex Sans, Poppins) are bundled in the APK instead
of being downloaded through Google Play Services.
- Settings → Appearance: five colour themes (Paper, Forest, Ocean, Graphite, Midnight), a
System / Light / Dark mode and an app-wide font choice (Editorial, Modern, Technical).
- Feedback goes to the GitHub issue tracker instead of a third-party form service.
- `FLAG_SECURE` is always on and applied before the first frame.
- All text fields ask the keyboard not to learn from input (`IME_FLAG_NO_PERSONALIZED_LEARNING`).
- Build: one Kotlin version through a Gradle version catalog, pinned Gradle wrapper checksum.
- Search covers every note whatever tag chip is selected (not notes under a hidden tag), lists
every match instead of the first five, and ignores checkbox markers and separator lines.
Note text is parsed once per change, off the main thread; an unreadable note no longer breaks
the list or search.
- The editor toolbar's move up, move down and delete buttons act on the current block.
- New design system: Playfair Display, Source Sans 3 and IBM Plex Sans on one type scale, shared
spacing, shapes and components; every screen restyled (lock, notes, editor, tasks, trash,
tags, settings, backup, dialogs). Tasks are always in the drawer.
- Editor: fields own their text, so fast typing no longer resets; new blocks go after the block
being edited; hardware Enter adds no stray line break; quotes end with a closing mark.
- Feedback goes to the GitHub issue tracker instead of a third-party form service.
- Search covers every note whatever tag is selected, lists every match, and ignores checkbox
markers and separator lines; an unreadable note no longer breaks the list or search.
- Unlocking returns to the note that was open.
- Editor: fast typing no longer resets a field, new blocks go after the block being edited,
hardware Enter adds no stray line break, and quotes end with a closing mark.
- Result messages on the backup and security screens stay visible until read.
- Store listings: texts, feature graphics and screenshots for all 9 languages.
- Build: one Kotlin version through a Gradle version catalog and a pinned Gradle wrapper
checksum.

### Removed

- Plaintext note sharing, clipboard copy of notes, seed export and calendar export. (A link
block's address can still be copied, on request and marked sensitive.)
- The `ui-text-google-fonts` dependency and its Google Play Services font provider.
- Unused libraries.
- The hidden screen-protection preference (screen protection cannot be turned off) and other
unused code; debug logging calls (release builds strip the remaining failure logs).
- Plaintext note sharing, copying whole notes to the clipboard, seed export, calendar export and
task reminders. Encly posts no notifications. (A link block's address can still be copied, on
request and marked sensitive.)
- The Google Play Services font provider (`ui-text-google-fonts`).
- `androidx.security:security-crypto`, which is deprecated upstream.
- The hidden screen-protection preference: screen protection can no longer be turned off.

### Security

- Android backup and device-to-device transfer are disabled for all vault data.
- See [SECURITY.md](SECURITY.md) for the full threat model and known limitations.
- The full threat model and known limitations are in
[SECURITY.md](https://github.com/pasichDev/Encly/blob/main/SECURITY.md).

## [1.1.1] - v1
## [1.1.1]

Last release of the v1 storage format (versionCode 30): SQLCipher key derived from a
Keystore-sealed seed hash, optional 4-digit PIN. Superseded by 2.0.0.
The last release of the old storage format (versionCode 30): the SQLCipher key was derived from a
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.0.0...HEAD
[2.0.0]: https://github.com/pasichDev/Encly/releases/tag/v2.0.0
[1.1.1]: https://github.com/pasichDev/Encly/tree/main
23 changes: 14 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ 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`
(UI) and a `motivation.xml` (home-screen quotes).
(most of the UI) and a `strings_security.xml` (security settings).

**Fixing a translation:** edit the string in `values-<lang>/strings.xml` and open a PR. Keep the
key; only change the text.
**Fixing a translation:** edit the string in `values-<lang>/strings.xml` (or
`strings_security.xml`) and open a PR. Keep the key; only change the text.

**Adding a string (code change):** add it to `values/strings.xml` in English and to *every*
`values-<lang>/strings.xml`, even if only with an English placeholder you flag in the PR. Use it
**Adding a string (code change):** add it in English to `values/strings.xml` (or
`strings_security.xml`) and to the same file in *every* `values-<lang>/`, even if only with an
English placeholder you flag in the PR. Use it
with `stringResource(R.string.…)` in Compose. Text produced outside the UI (ViewModels,
managers, validators) must not be resolved there: return a resource id or a `UiText`
(`core/common/UiText.kt`) and resolve it in the UI, so it follows the in-app language. Never
Expand All @@ -119,7 +120,7 @@ lower.

**Adding a language:**

1. copy `values/strings.xml` and `values/motivation.xml` to `values-<lang>/` and translate them,
1. copy `values/strings.xml` and `values/strings_security.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 Expand Up @@ -173,8 +174,10 @@ upload this key instead of letting Google generate one).
1. Bump `VERSION_MAJOR/MINOR/PATCH` in `version.properties`. `versionCode` follows
automatically (`MAJOR*10000 + MINOR*100 + PATCH`) and must only grow.
2. Move `[Unreleased]` in `CHANGELOG.md` to a dated `## [X.Y.Z] - YYYY-MM-DD` section whose first
line is `versionCode N.` (F-Droid's update check reads the version from these two lines; the
unit tests fail when they disagree with `version.properties`).
line is `versionCode N.` F-Droid's update check reads the version name and code from these two
lines, because it cannot evaluate `version.properties`; no test checks them, so compare them
with `version.properties` by hand. The release workflow uses this section as the GitHub Release
notes, so use absolute links in it.
3. Write the store release notes (≤ 500 characters each) to
`fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt` for **every** locale folder
(`en-US`, `uk`, `de-DE`, `fr-FR`, `es-ES`, `it-IT`, `pl-PL`, `pt-PT`, `nl-NL`);
Expand All @@ -184,7 +187,9 @@ upload this key instead of letting Google generate one).
[release workflow](.github/workflows/release.yml) refuses a tag that does not match
`version.properties`, fails when a signing secret or `ENCLY_CERT_SHA256` is missing, builds
both flavors unsigned, signs them with `apksigner` in a separate step (Gradle never sees
the keystore), checks the certificate against `ENCLY_CERT_SHA256`, and publishes the APKs with `SHA256SUMS` and R8 mapping files.
the keystore), checks the certificate against `ENCLY_CERT_SHA256`, and publishes the APKs
with `SHA256SUMS` (signed as `SHA256SUMS.asc` when the GPG secrets are set) and the R8
mapping files.
6. Google Play: either upload `app-play-release.aab` by hand
(`./gradlew :app:bundlePlayRelease` with the `ENCLY_*` variables set), or, once enabled, run
the [Publish to Google Play](.github/workflows/publish-play.yml) workflow with the tag; it
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2022 pasichDev
Copyright 2022-2026 pasichDev

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Loading
Loading