diff --git a/.gitignore b/.gitignore index f9d3f7e..d06861d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,4 @@ styles.css # Dev artifacts PLAN.md Screenshot_*.png -logo-*.svg +logo-triangle.svg diff --git a/LOGO.md b/LOGO.md index 2d2d4ca..fb971cf 100644 --- a/LOGO.md +++ b/LOGO.md @@ -13,7 +13,7 @@ The logo is inspired by the Google Foobar challenge logo, adapted into a circula | Teal | `#35BEB8` | Wedge section outside the circle | | Background | `#1F1F1F` | Launcher icon background | -## Geometry (Circle Variant — Primary) +## Geometry All measurements relative to a 108x108dp Android adaptive icon viewport, center at (54, 54). @@ -84,28 +84,6 @@ sin(23.5°) = 0.39875 L79.68,65.16 A28,28 0 0,0 79.68,42.84 Z" /> ``` -## Geometry (Triangle Variant — Alternate) - -Three concentric equilateral triangles pointing upward, with a 47-degree wedge cutting through the right edge. - -### Triangle Circumradii (200x200 SVG, center at 100,100) - -| Ring | Outer R | Inner R | Fill | -|----------------|---------|---------|--------------| -| Outer ring | 80 | 65 | Pink | -| Middle ring | 55 | 40 | Pink | -| Inner triangle | 30 | — | Pink (solid) | -| Gap | 65→55 | — | Background | -| Gap | 40→30 | — | Background | - -### Wedge Intersections - -The 47-degree wedge (centered on horizontal-right) intersects each triangle's right edge. The intersection points are computed by solving the parametric line-line intersection of the wedge rays with each triangle edge. - -- **Black section**: wedge intersection with the middle ring (R=40 to R=55) -- **Teal section**: wedge intersection with the outer ring (R=65 to R=80) -- **Inner triangle**: stays fully pink - ## Adaptive Icon Layers | File | Purpose | @@ -123,8 +101,7 @@ Android adaptive icons use a 108dp canvas. The recommended safe zone is a 66dp d ## Files -- `logo-circle.svg` — Circle variant preview -- `logo-triangle.svg` — Triangle variant preview -- `androidApp/src/main/res/drawable/ic_launcher_foreground.xml` — Production circle logo +- `logo-circle.svg` — SVG preview +- `androidApp/src/main/res/drawable/ic_launcher_foreground.xml` — Production Android vector - `androidApp/src/main/res/drawable/ic_launcher_monochrome.xml` — Themed icon silhouette - `androidApp/src/main/res/drawable/ic_launcher_background.xml` — Dark background diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4cbe0c --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +

+ Nikki logo +

+ +# Nikki + +A native Android client for [Memos](https://usememos.com) with integrated Todoist-style task management, built with Compose Multiplatform and inspired by the Windows Phone Metro design language. + +The name comes from the Japanese word 日記 (nikki), meaning diary. + +## Why Nikki + +Memos is a great self-hosted note-taking tool, but its mobile experience is limited to a PWA. Nikki exists to be a proper native client that feels at home on your phone — fast, offline-capable, and opinionated about how notes and tasks should work together. + +### Why Windows Phone + +The Metro design language was the most readable, information-dense, and distraction-free mobile UI ever shipped. Large typography, flat surfaces, no chrome — it respected both content and the person reading it. Nikki borrows that philosophy: panorama pivot navigation, all 20 WP8 accent colors, and the principle that UI should get out of the way. + +### Why Tasks Inside Notes + +Most people track tasks in their notes anyway — `- [ ] buy milk` scattered across memos. Nikki parses those checkboxes and gives them structure: due dates, priorities, reminders, grouping. Your notes stay as plain markdown. The task layer is derived, not duplicated. One source of truth, two ways to see it. + +### The Logo + +Inspired by the Google Foobar challenge logo, adapted into a circle. A pink circle carries a 47-degree annular wedge on the right — black where it overlaps, teal where it extends beyond. Construction details are in [LOGO.md](LOGO.md). + +## Features + +**Memos** +- Full CRUD with the Memos API (create, edit, pin, archive, delete) +- Rich markdown rendering: headings, bold/italic, strikethrough, code blocks, tables, links, task checkboxes +- Media attachments with authenticated image loading +- Emoji reactions and comments +- Visibility control (private, protected, public) +- Pull-to-refresh sync with configurable interval + +**Tasks** +- Todoist-inspired syntax parsed from markdown checkboxes (see [TASK_FORMAT.md](TASK_FORMAT.md)) +- Due dates (ISO and natural: today, tomorrow), times (12h/24h), priorities (p1-p3), reminders, tags +- Group by due date, list, priority, source memo, or status +- Sort by due date or priority +- Parser doctor: inline error/warning detection with typo suggestions +- Android notifications with 4 priority channels (p1 bypasses DND) + +**Explorer** +- Activity calendar with memo density heatmap +- Tag browser with counts +- Date and search filtering +- Archived memos view + +**Offline** +- Room database cache for offline reading +- Pending sync queue for offline edits (auto-drains on reconnect) +- Sync status banner with last-synced time + +**Personalization** +- 3 themes: dark, light, AMOLED black +- 20 WP8 accent colors (lime, green, emerald, teal, cyan, cobalt, indigo, violet, pink, magenta, crimson, red, orange, amber, yellow, brown, olive, steel, mauve, taupe) +- Configurable week start day, default visibility, default reminder + +**Backup** +- Export/import memos as JSON +- Backup rules exclude credentials from cloud backup + +## Requirements + +- Android 8.0+ (API 26) +- A running [Memos](https://github.com/usememos/memos) instance (v0.22+) +- JDK 11+ + +## Building + +```bash +# Clone +git clone https://github.com/avinal/nikki.git +cd nikki + +# Build debug APK +./gradlew :androidApp:assembleDebug + +# Install on connected device +./gradlew :androidApp:installDebug + +# Run tests +./gradlew :composeApp:testDebugUnitTest +``` + +The project uses Gradle 9.4 with the Kotlin Multiplatform plugin. Android Studio or IntelliJ IDEA with the Compose Multiplatform plugin is recommended for development. + +## Installation + +Download the latest APK from [Releases](https://github.com/avinal/nikki/releases), or build from source using the instructions above. + +On first launch, enter your Memos server URL and an access token (generate one in your Memos account settings). + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| UI | Compose Multiplatform 1.11, Material 3 | +| Language | Kotlin 2.3 | +| Networking | Ktor 3.5 | +| Database | Room 2.8 (multiplatform) | +| Image loading | Coil 3.4 | +| Background work | WorkManager + AlarmManager | +| Serialization | kotlinx.serialization | +| Date/time | kotlinx-datetime 0.8 | + +## Project Structure + +``` +nikki/ +├── androidApp/ # Android app shell (MainActivity, receivers) +├── composeApp/ # Kotlin Multiplatform shared code +│ └── src/ +│ ├── commonMain/ # Shared UI, domain, API, parser, database +│ ├── androidMain/ # Android notifications, file picker, alarms +│ └── iosMain/ # iOS stubs (scaffolded, not active) +├── gradle/ # Version catalog and wrapper +├── LOGO.md # Logo construction spec +└── TASK_FORMAT.md # Task syntax reference +``` + +## Disclaimer + +This project was built almost entirely with the help of AI (Claude). I am not an Android developer by trade, and this is my first serious mobile app. Considerable effort has been made to avoid security vulnerabilities, bloat, and deprecated patterns - including a dedicated security review; but gaps may exist. + +If you find a security issue, bug, or anything concerning, please [open an issue](https://github.com/avinal/nikki/issues) or email me at ripple@avinal.space. + +## Acknowledgements + +- [Memos](https://github.com/usememos/memos) by the usememos team — the self-hosted note-taking tool that Nikki connects to +- [JetBrains](https://www.jetbrains.com/compose-multiplatform/) — Compose Multiplatform framework +- The Windows Phone design team — for proving that flat, typography-first UI is timeless +- [Google Foobar](https://foobar.withgoogle.com/) — inspiration for the logo + +## License + +[MIT](LICENSE) diff --git a/TASK_FORMAT.md b/TASK_FORMAT.md new file mode 100644 index 0000000..21dd8eb --- /dev/null +++ b/TASK_FORMAT.md @@ -0,0 +1,150 @@ +# Task Format + +Nikki extracts tasks from standard markdown checkboxes in your memos. Any line matching `- [ ]` or `- [x]` is parsed as a task. Metadata is extracted inline — no special syntax beyond what you'd naturally write. + +## Basic Format + +``` +- [ ] task description [due-date] [due-time] [!reminder] [p#] [#list] +``` + +All metadata fields are optional. Order doesn't matter. The task text is everything that remains after metadata is stripped. + +## Due Dates + +ISO format or natural language. + +| Input | Meaning | +|-------|---------| +| `2026-06-15` | June 15, 2026 | +| `today` | Today's date | +| `tomorrow` | Tomorrow | +| `yesterday` | Yesterday | + +``` +- [ ] submit report 2026-06-15 +- [ ] buy milk today +``` + +## Due Times + +12-hour or 24-hour format. + +| Input | Meaning | +|-------|---------| +| `5pm` | 17:00 | +| `10:30am` | 10:30 | +| `14:00` | 14:00 | +| `9am` | 09:00 | + +``` +- [ ] standup today 9am +- [ ] deploy 2026-06-15 14:00 +``` + +If a time is set without a date, today is assumed (with a warning). + +## Reminders + +How far before the due date/time to send a notification. Prefixed with `!`. + +| Input | Meaning | +|-------|---------| +| `!30min` | 30 minutes before | +| `!1hr` | 1 hour before | +| `!2day` | 2 days before | +| `!1week` | 1 week before | + +``` +- [ ] dentist tomorrow 3pm !1hr +- [ ] taxes 2026-04-15 !1week +``` + +A reminder without a due date or time is flagged as an error — there's nothing to count back from. + +## Priority + +Three levels: p1 (high), p2 (medium), p3 (low). + +| Level | Notification behavior | +|-------|----------------------| +| `p1` | Alarm sound, vibration, bypasses DND | +| `p2` | Notification sound, heads-up | +| `p3` | Notification sound, subtle | + +``` +- [ ] server is down p1 +- [ ] update docs p3 +``` + +Tasks without a priority are treated as unclassified (silent notification). + +## Lists / Tags + +Hashtags assign tasks to lists. A task can belong to multiple lists. + +``` +- [ ] buy groceries #shopping +- [ ] fix login bug #backend #urgent +``` + +If a task has no `#tag`, it inherits the memo-level tags (tags set at the top of the memo). Task-level tags always take priority over memo-level tags. + +## Complete Examples + +```markdown +# Sprint 14 + +- [ ] fix auth timeout 2026-06-10 p1 #backend +- [ ] update onboarding copy tomorrow 5pm !30min p2 #frontend +- [ ] write migration tests 2026-06-12 #backend #testing +- [x] deploy staging +- [ ] review PR today 3pm !15min p2 +- [ ] buy coffee #personal +``` + +This produces 6 tasks. The completed one (`deploy staging`) is tracked but excluded from validation. Tasks can be grouped by due date, list, priority, source memo, or completion status. + +## Validation + +The parser doctor checks incomplete tasks for issues and shows inline warnings/errors in the task view. + +### Errors + +| Issue | Example | Message | +|-------|---------|---------| +| Invalid date | `2026-13-45` | invalid date, use YYYY-MM-DD format | +| Invalid priority | `p5` | invalid priority, only p1, p2, p3 are supported | +| Reminder without due | `!30min` (no date/time) | reminder has no due date or time to count back from | +| Invalid reminder unit | `!5blah` | invalid reminder unit, use min, hr, day, or week | + +### Warnings + +| Issue | Example | Message | +|-------|---------|---------| +| Time without date | `5pm` (no date) | time without date, using today | +| Past date | `2020-01-01` | date is in the past | +| Multiple priorities | `p1 p2` | multiple priorities, using first (p1) | +| Multiple dates | `2026-06-01 2026-07-01` | multiple dates found, using first | +| Multiple reminders | `!30min !1hr` | multiple reminders, using first | +| Typo | `tomorow` | did you mean "tomorrow"? | + +### Typo Detection + +Common misspellings are caught and suggestions offered: + +- `tday`, `todya`, `toaday`, `toady` → today +- `tmrw`, `tomorow`, `tommorow` → tomorrow +- `yestrday`, `ysterday` → yesterday +- Day name typos: `munday`, `tusday`, `wendsday`, `thurday`, `firday`, `saterday`, `sundie`, etc. + +## Notification Behavior + +When a task has both a due date/time and a reminder, two notifications may fire: + +1. **Reminder notification** — fires `!duration` before the due time +2. **Due notification** — fires at the due time itself + +If a task has a date but no time, the notification fires at the default notify time (configurable in settings, default 8pm). + +If a task has neither date nor time, no notification is scheduled. diff --git a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt index d1cbeb9..460c0e1 100644 --- a/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt +++ b/composeApp/src/commonMain/kotlin/com/avinal/memos/api/MemosApiClient.kt @@ -141,7 +141,7 @@ class MemosApiClient( suspend fun listArchivedMemos(): ApiResult = apiCall { httpClient.get(url("/memos")) { parameter("pageSize", 50) - parameter("filter", "state == \"ARCHIVED\"") + parameter("state", "ARCHIVED") }.body() } diff --git a/logo-circle.svg b/logo-circle.svg new file mode 100644 index 0000000..0e42445 --- /dev/null +++ b/logo-circle.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + +