A reading list that keeps itself, ranked by what you actually read.
An AI agent checks your inbox and files what's worth keeping into Notion;
blogs you follow add their own posts the same way.
See every screen · How it fits together · Run it
Monochrome by default. One accent, spent on purpose. Under 4 MB, after R8.
Newsletters and blogs file themselves into one Notion database — ranked by what you actually read, not by when it arrived — then you set a twenty-five minute timer and read, on Paper Black or Ink, the same page with the colour drained out.
- Curated through Notion. Claude files inbox newsletters straight into
the reading list; blogs with a working feed are fetched by the app and
never touch Notion. Either way, only rows ticked
Savedreach the phone — see docs/architecture.md for the schema and sync rules. Following a blog writes it back to Notion too, so it's there to edit next time you open it. - Save it however it reaches you — the Chrome share sheet, or the paste field on Saved behind Add.
- Reads offline. Every screen renders from local storage; a feed with full-content RSS is cached whole at sync time, so most articles were already on the phone before you tapped them.
- Saved is a queue and a record. Unread leads; what you've read stays under its own heading. Filter to either, or search a title, host or topic.
- On-device summaries and reading aloud. Long articles get a one-tap summary via Gemini Nano; the phone can also read one aloud with its own text-to-speech. Both are absent on hardware that can't run them, and a read that can't happen says why instead of staying silent.
- A focus timer that behaves like an alarm, not a widget — a real system notification and vibration when the interval ends, phone face-down.
Android is the app, built and tested day to day. iOS draws its own UI in SwiftUI over the same Kotlin.
git clone https://github.com/MKS-01/duskread.git && cd duskread
./gradlew :androidApp:installDebug # ~5 s once warmThat's the whole setup — Notion is optional, and connecting it is one pasted token in Settings.
| JDK | 17 or newer |
| Android | 12 and up (minSdk 31, compile/target 36) |
| Gradle | 9.3.1, via the wrapper |
| For summaries | A phone with AICore — Pixel 9+, Galaxy S24+ and similar. No emulator has it |
| For iOS | Xcode and XcodeGen; iOS 18 and up |
iOS
cd iosApp && xcodegen generate && cd .. # needs an Xcode host
open iosApp/iosApp.xcodeprojThere's no Gradle run task for it — Compose Multiplatform only builds a framework, which the generated Xcode project links. First build is ten minutes or more; incremental after that.
One commonMain source set holds the libraries, the ranking, the sync and the
Compose UI itself, shared between Android and iOS; storage, audio, the
summariser, the focus timer and the HTTP client are expect/actual pairs
behind it, on Kotlin 2.3 and
Ktor 3.1.
Android draws that Compose UI directly with
Compose Multiplatform 1.11
and Haze for the floating-bar blur; iOS
is the one platform that composes its own, in
SwiftUI over iosMain/bridge/,
taking its colours, type, spacing, motion and icon paths from the same Kotlin
token layer rather than a second copy of them. Summaries run on-device via
ML Kit GenAI / Gemini Nano; reading aloud
is Android's own TextToSpeech, and AVSpeechSynthesizer on iOS through the
same shared Flow<SpeechProgress> contract; the optional Readback tab queries
readback's library.db read-only,
Android-only for now. No navigation library, no ViewModel, no DI, no
database — a ceiling chosen on purpose.
./gradlew ktlintCheck # several rules deliberately off, see .editorconfig
./gradlew ktlintFormatNo tests yet — a build that succeeds is the start of checking a change, not the end of it.
The design system — Paper Black and Ink, and every token behind them. docs/architecture.md — how the pieces connect, the Notion schema, and the end-to-end flows.
MIT — see LICENSE.
A personal project, built for learning and for its own author to use
Built agent-first with Claude Code
The synced-audio Readback tab is a separate, optional open-source project: readback

