Skip to content

hpdowd/nextkeep

Repository files navigation

NextKeep

A clean, Google Keep–styled Android client for Nextcloud Notes.

Canonical source: git.henrydowd.dev/henry/nextkeep. The GitHub repo is a mirror.

Offline-first: all reads come from a local Room database; edits are saved locally immediately and synced to the server in the background. Works fully offline and pushes queued changes on the next sync.

Built by AI

NextKeep was written end-to-end by Claude (Anthropic) running in Claude Code. The architecture, the Kotlin/Jetpack-Compose implementation, the offline-first sync engine, the dependency-free markdown engine, the QR login, the unit tests, the release-signing setup, and this README were all generated by the model. A human gave the product direction in plain English ("a clean Keep-styled Nextcloud Notes client", "add QR login", "render markdown in the list", …) and reviewed the result; the model made the design decisions and wrote every line.

It was also developed the way a person would, not just emitted in one shot:

  • Verified on-device. The model installed an Android emulator, built and installed the app, and drove the real flows — login, sync, editing, markdown rendering, the QR screen — taking screenshots to check its own work.
  • Caught and fixed a real bug that way. Tapping a formatting button on an empty line did nothing (a vacuous-truth bug in the list-prefix toggle); it was found by using the app on the emulator, fixed, and covered with a regression test.
  • Tested. Pure logic (content↔title/body mapping, markdown transforms, QR parsing) is covered by JVM unit tests (./gradlew :app:testDebugUnitTest).

As with any AI-generated code: it builds, the core flows are verified, and the design is documented below — but review it before trusting it with data you care about, and read the Known limitations section.

Features

  • Keep-style UI — staggered two-column note grid, pill search bar, Material 3 with dynamic color (Material You) on Android 12+, light/dark theme, and a themed (monochrome) launcher icon that tints to the system palette on Android 13+.
  • Pinned notes — mapped to Nextcloud favorites, shown in a "Pinned" section.
  • Labels — Nextcloud categories shown as filter chips; notes are tinted with a Keep-style pastel per category.
  • Markdown — Notes are markdown (as Nextcloud stores them). The editor has a formatting toolbar (headings, bold, italic, bullet/numbered/checklist, quote, indent/outdent) and an edit⇄preview toggle. List cards and the editor preview render markdown; checkboxes are tappable (toggle directly on a card or in preview) and pressing Enter auto-continues lists. See markdown/ (MarkdownEditing, parseMarkdownBlocks, MarkdownText) — dependency-free, unit-tested.
  • QR login — "Scan login QR code" reads Nextcloud's nc://login/... code (server + user + app password) and connects, via CameraX + ZXing (offline, no Google Play dependency). See qr/.
  • Editor — title + body with autosave (debounced 400 ms), pin, share, delete (with an Undo snackbar), label editing, "Edited x ago" footer.
  • Settings — theme (System/Light/Dark/AMOLED black), font size, grid columns (1–3), and sort order. Theme and font scale apply app-wide via the Material 3 typography.
  • Share into NextKeep — share text from any app to create a note.
  • App lock — optional biometric / device-credential lock (BiometricPrompt), re-locking when the app is backgrounded.
  • Sync — pull-to-refresh two-way sync; local-pending changes win and are pushed first, deletes use tombstones (with an Undo window before they're pushed), notes deleted on the server while edited locally are recreated so no edits are lost. Unchanged notes are skipped on pull (collection ETag → 304, plus per-note etag/ modified checks). Edits use If-Match; a 412 conflict keeps both versions (the server's, plus the local edit as a "(conflict)" copy). A WorkManager job syncs periodically in the background.

Login

  1. In Nextcloud: Settings → Security → Devices & sessions → Create new app password.
  2. In NextKeep: enter the server address (e.g. cloud.example.com), your username, and the app password — or tap Scan login QR code and scan a Nextcloud login QR code to fill and connect in one step.

The Notes app must be installed on the server (API v1). Credentials are encrypted with an Android Keystore (AES-GCM) key before being written to app-private DataStore (see CryptoManager).

Building

Requirements: JDK 17–21 (newer JDKs such as 25/26 are rejected by AGP 8.7) and the Android SDK (platform 35, build-tools 35.0.0). Neither path is committed — both are machine-specific, so set them once per machine:

  • JDK — if your system java is already 17–21, you're done. If it's newer (e.g. JDK 26), don't edit the committed gradle.properties; point Gradle's daemon at a 17–21 JDK in your user-global config, ~/.gradle/gradle.properties:
    org.gradle.java.home=/path/to/jdk-21
    Gradle's launcher still starts under your newer default JDK, but the build runs on this one.
  • Android SDK — create local.properties (gitignored) at the repo root:
    sdk.dir=/home/you/Android/Sdk
./gradlew :app:assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk
adb install app/build/outputs/apk/debug/app-debug.apk

Toolchain setup on a fresh Linux machine

No Android Studio required — a JDK plus the command-line SDK tools is enough:

# 1) A JDK 17–21. Use your distro package, SDKMAN!, or a Temurin tarball. E.g. Arch:
sudo pacman -S jdk21-openjdk            # -> /usr/lib/jvm/java-21-openjdk
#    If your default java is newer, add its path to ~/.gradle/gradle.properties
#    as org.gradle.java.home=... (see above).

# 2) Android command-line tools -> ~/Android/Sdk  (grab the current zip URL from
#    https://developer.android.com/studio#command-tools)
mkdir -p ~/Android/Sdk/cmdline-tools && cd ~/Android/Sdk/cmdline-tools
curl -fLO https://dl.google.com/android/repository/commandlinetools-linux-XXXXXXXX_latest.zip
unzip -q commandlinetools-linux-*_latest.zip && mv cmdline-tools latest
yes | latest/bin/sdkmanager --sdk_root="$HOME/Android/Sdk" --licenses
latest/bin/sdkmanager --sdk_root="$HOME/Android/Sdk" \
  "platform-tools" "platforms;android-35" "build-tools;35.0.0"

# 3) Point the build at the SDK
echo "sdk.dir=$HOME/Android/Sdk" > local.properties

Building a release APK

Release builds are signed from keystore.properties at the repo root (gitignored). A keystore (nextkeep-release.jks) is already generated with a placeholder password — fine for personal sideloading. To use your own key instead:

keytool -genkeypair -v -keystore nextkeep-release.jks -storetype PKCS12 \
  -alias nextkeep -keyalg RSA -keysize 2048 -validity 10000

then update storeFile/storePassword/keyAlias/keyPassword in keystore.properties. If that file is absent, the release build falls back to the debug key.

./gradlew :app:assembleRelease
# APK: app/build/outputs/apk/release/app-release.apk

Continuous builds (download the APK without a local toolchain)

.github/workflows/build.yml builds the app in the cloud on every push and on version tags, then publishes the APK so you can download it from a browser on any machine — no JDK or Android SDK needed locally. The same file runs on two backends:

  • GitHub — push or mirror the repo to GitHub.com; it builds on free hosted runners. Grab the APK from the run's Artifacts, and pushing a tag like v1.0 also attaches it to a Release.
  • Gitea / Forgejo Actions — a self-hosted Gitea/Forgejo reads the same .github/workflows/. It needs Actions enabled and a registered act_runner (Docker); then the APK is downloadable from the run's Artifacts.

Architecture

ui/            Compose screens + ViewModels (login, notes grid, editor, settings, lock)
markdown/      Editing transforms, block parser, and renderer (dependency-free)
qr/            nc://login parser + CameraX/ZXing QR scanner
data/
  local/       Room: NoteEntity (with dirty/deleted flags), NoteDao, NotesDatabase
  remote/      Retrofit client for the Notes API v1, basic-auth interceptor
  AccountStore  credentials, encrypted via CryptoManager (Keystore AES-GCM)
  SettingsStore DataStore-backed app settings
  SyncWorker    periodic background sync (WorkManager)
  NotesRepository  offline-first store + two-way sync engine

Notable design points:

  • The Notes API derives a note's title from the first line of content, so the Keep-style separate title field is joined/split transparently during sync (NotesRepository.joinContent/splitContent).
  • No DI framework — a small AppContainer on the Application class; ViewModels get it via viewModelFactory initializers.
  • Min SDK 26, target SDK 35.

Known limitations / future work

  • Bound by the Notes API v1. A note is a markdown file with title/category/ favorite/modified — so there is no server support for per-note color, reminders, image attachments, or archive. Those Keep features would be local-only or aren't feasible.
  • Release builds are HTTPS-only (usesCleartextTraffic=false); a debug-only manifest overlay (src/debug/AndroidManifest.xml) re-enables cleartext so debug builds can hit a local test server. If you self-host on plain http, build debug or relax the release flag.
  • App lock needs Android 12+ for the credential fallback (BIOMETRIC_WEAK or DEVICE_CREDENTIAL); on older versions the toggle is unavailable unless a biometric is enrolled.
  • The markdown renderer covers a common subset (headings, lists, task lists, quotes, fenced code, dividers, inline bold/italic/code/strikethrough/links) — no tables or deeply nested lists.

Tests

./gradlew :app:testDebugUnitTest runs the JVM unit tests:

  • ContentMappingTest — title/body ⇄ content round-trip (guards against notes being silently mutated during sync).
  • QrLoginParseTest — parsing of nc://login/... QR codes (any field order, URLs with ports/paths).
  • MarkdownTest — the formatting transforms (MarkdownEditing), block parser, and plain-text stripper.

For manual testing without a real server, tools/mock_notes_server.py is a tiny in-memory mock of the Notes API v1 that seeds a few markdown notes:

python3 tools/mock_notes_server.py 8088        # on the host
# in the app (debug build) log in to http://10.0.2.2:8088 from an emulator

About

A Google Keep–styled Android client for Nextcloud Notes. Kotlin · Jetpack Compose · Material 3 · offline-first.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors