- Kotlin 97%
- HTML 3%
| .github | ||
| .superpowers/brainstorm | ||
| app | ||
| docs/superpowers | ||
| gradle/wrapper | ||
| pics | ||
| tools | ||
| .gitignore | ||
| AGENTS.md | ||
| AUDIT_REPORT.md | ||
| build.gradle.kts | ||
| CHANGELOG.md | ||
| CHANGELOG_AGENT.md | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| gradle.properties | ||
| gradlew | ||
| ISA.md | ||
| LICENSE | ||
| MONETIZATION.md | ||
| PLAY_CONSOLE_CHECKLIST.md | ||
| PRIVACY.md | ||
| README.md | ||
| settings.gradle.kts | ||
| STORE_LISTING.md | ||
| TERMS.md | ||
| UX_RECOMMENDATIONS.md | ||
Nib
A fast, private Android client for Ghost CMS. Write a thought, attach an image or a link, and publish to your own blog in seconds — or queue it offline and let Nib sync when you're back online. No Nib account, no middleman, no tracking.
Screenshots
Features
- Ghost Admin API — Direct integration via JWT authentication (HS256); your credentials stay on-device
- Offline-first — Posts are saved locally and synced when connectivity returns
- Multi-account — Connect up to 5 Ghost blogs and switch between them, fully isolated
- Media attachments — Images, video, audio, and files
- Link previews — Automatic Open Graph metadata extraction (title, description, thumbnail)
- Hashtag support — Auto-extracted hashtags converted to Ghost tags
- Scheduled publishing — Set a future publish date for your posts
- Titleless or titled — Post untitled micro-notes or full titled articles
- Newsletter & members — Send posts as newsletters and manage subscribers
- Static pages & tag management — Full Ghost administration surfaces
- Statistics dashboard — Exact post counts across every Ghost pagination page, word counts, reading time, tag breakdown
- Editorial timeline — Dense, title-aware feed with search, status filters, sorting, and swipe actions
- Encrypted credentials — API keys stored with AES-256-GCM via AndroidX Security
- Background sync — WorkManager upload queue with exponential backoff
- What's New — A one-time modal after an app update highlights everything new since you last opened Nib; re-openable any time from Settings
- Per-account emoji — Give each connected blog its own emoji avatar in place of the coloured initial (Pro)
- Nib Pro — Monthly, yearly, or lifetime; Settings-only paywall with restore, plan change, pending-payment, grace-period, and account-hold handling
- Material 3 UI — Clean design with Light/Dark/System themes and a theme accent: free Dynamic (Material You) colour or a Pro preset accent, independent of light/dark
Monetization
Nib Pro is one paid tier with three ways to pay for it: a nib_pro subscription (monthly and yearly base plans) or the one-time nib_pro_lifetime purchase. No ads, no third-party billing SDK, no tracking.
Writing, editing, publishing, deleting, drafts, the offline queue, and one Ghost blog are free and always will be. Pro adds up to five blogs, scheduled publishing, newsletters, members, pages, full tag management, and cosmetic extras (a forced preset theme accent — Dynamic/Material You stays free — and per-account emoji avatars).
Anyone who installed Nib before versionCode 10 keeps every capability they already had, permanently — enforcement is grandfathered by first-seen version.
See MONETIZATION.md for the decision and architecture, and PLAY_CONSOLE_CHECKLIST.md for the release gates.
Current release status
- Version: 0.10.0 (
versionCode 19) - Platform: Android 8.0+ (
minSdk 26,targetSdk 36) - Validation: debug + release unit tests, lint-vital with 0 errors, and debug/release APK plus release AAB builds passing
- Signing: release artifacts are unsigned by default; supply a keystore via
nib.*Gradle properties (see below) to produce a signed AAB - Public monetized rollout: gated on Play product setup and the internal-track billing QA matrix in PLAY_CONSOLE_CHECKLIST.md
The detailed evidence, known limitations, and manual release gates are tracked in AUDIT_REPORT.md and CHANGELOG_AGENT.md.
Architecture
MVVM with Repository pattern, single-module Gradle project. Main package: com.nib.microblog.
com.nib.microblog/
├── data/
│ ├── api/ # Retrofit client, JWT auth, interceptors
│ ├── db/ # Room database, DAOs, migrations
│ ├── model/ # GhostPost (API), LocalPost (DB), FeedPost (UI), PostStats, GhostAccount
│ └── repository/ # PostRepository, OpenGraphFetcher
├── monetization/ # Play Billing, entitlement state, restore, aggregate analytics
├── ui/
│ ├── animation/ # NibMotion shared animation specs
│ ├── common/ # Shared UI policy and dirty-state helpers
│ ├── components/ # Reusable composables (dialogs, placeholders)
│ ├── composer/ # Post creation with media, links, hashtags, scheduling
│ ├── detail/ # Full post view with compact overflow actions
│ ├── feed/ # Editorial post timeline with search and filtering
│ ├── members/ # Ghost members/subscribers management
│ ├── newsletter/ # Newsletter configuration and subscriber info
│ ├── pages/ # Static pages CRUD
│ ├── preview/ # HTML post preview
│ ├── settings/ # App settings, account management, theme toggle
│ ├── setup/ # Configuration wizard and add-account flow
│ ├── stats/ # Statistics dashboard
│ ├── tags/ # Tag management
│ ├── navigation/ # Compose Navigation with bottom nav bar
│ └── theme/ # Material 3 theming (Light/Dark/System + Dynamic/preset accent)
└── worker/ # PostUploadWorker (WorkManager)
Data flow: Compose UI → ViewModel → Repository → Room (local) + Retrofit (remote). Posts are persisted to Room first, then queued for upload via WorkManager.
Getting started
Prerequisites
- Android Studio Hedgehog (2023.1.1) or later
- JDK 17 or 21 (JDK 26 is not supported by the current Gradle toolchain)
- Android SDK 36
- A Ghost blog with Admin API access
Ghost setup
- In your Ghost admin panel, go to Settings → Integrations
- Create a new Custom Integration
- Copy the Admin API Key (format:
id:secret) - Note your Ghost blog URL (e.g.
https://yourblog.com)
Build and run
git clone https://github.com/pawelorzech/Nib.git
cd Nib
./gradlew app:assembleDebug
Install the debug APK on your device or emulator, then follow the setup wizard to connect your Ghost blog.
Building
./gradlew app:assembleDebug # Debug APK
./gradlew app:assembleRelease # Minified release APK
./gradlew app:bundleRelease # Release AAB for Google Play
Signing a release
Release artifacts are unsigned unless a keystore is configured. Add these to ~/.gradle/gradle.properties (never commit them):
nib.storeFile=/absolute/path/to/nib-release.keystore
nib.storePassword=…
nib.keyAlias=nib
nib.keyPassword=…
Generate a keystore once with:
keytool -genkeypair -v -keystore nib-release.keystore \
-alias nib -keyalg RSA -keysize 2048 -validity 10000
With the properties present, ./gradlew app:bundleRelease produces a signed AAB ready for the Play Console.
Testing
./gradlew test # Run all tests
./gradlew app:testDebugUnitTest # Debug variant only
The suite covers authentication, account isolation, database migrations, background queue recovery, content rendering, billing entitlement and lifecycle behavior, repositories, preferences, and UI policy helpers.
Agent guidance
- AGENTS.md — Codex repository instructions and engineering invariants
- CLAUDE.md — Claude Code repository instructions
The two guides intentionally mirror project facts and should be updated together whenever commands, architecture, versioning, or release constraints change.
Audit and release documentation: AUDIT_REPORT.md, UX_RECOMMENDATIONS.md, STORE_LISTING.md, PRIVACY.md, and TERMS.md.
Tech stack
| Layer | Technology |
|---|---|
| UI | Jetpack Compose, Material 3 |
| Navigation | Compose Navigation |
| Architecture | MVVM, StateFlow, Repository pattern |
| Networking | Retrofit 2, OkHttp 4 |
| Images | Coil |
| Media | Media3 ExoPlayer |
| Database | Room |
| Background | WorkManager |
| Auth | JJWT (HS256), EncryptedSharedPreferences |
| Link parsing | Jsoup |
| Monetization | Google Play Billing (one-time Pro purchase) |
| Testing | JUnit 4, Robolectric, MockWebServer |
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the PolyForm Noncommercial License 1.0.0 — you can use, modify, and share it for any non-commercial purpose. Commercial use requires permission from the author.
Author
Paweł Orzech — orzech.me