Scan your physical comic collection into a pocketable inventory. Native Android, Kotlin, Jetpack Compose + Material 3.
- Kotlin 85.9%
- Python 13.2%
- Dockerfile 0.9%
|
All checks were successful
Build & test / build (push) Successful in 3m5s
Open Library's bot filter 403s any request whose User-Agent contains the word "benchmark", causing all ISBN lookups in the benchmark build to fail. Strip everything after the first '-' in BuildConfig.VERSION_NAME so every build variant sends the same clean version string. |
||
|---|---|---|
| .forgejo | ||
| app | ||
| gradle | ||
| inkventory_icon | ||
| supabase/migrations | ||
| .gitignore | ||
| build.gradle.kts | ||
| CLAUDE.md | ||
| compose_stability.conf | ||
| Containerfile | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
Inkventory
Scan your physical comic collection into a pocketable inventory. Native Android, Kotlin, Jetpack Compose + Material 3.
Status
v0.1 — core flows complete and in use.
- Unified scan (CameraX + ML Kit): barcode detection and text recognition run in parallel on each frame. EAN-13 barcodes that pass ISBN-13 checksum are promoted to ISBN automatically; OCR pulls ISBNs from printed text. UPC barcodes go to ComicVine; ISBN codes go to Open Library (and get a Google Books cover fallback).
- Manual search + candidate picker when a scan is ambiguous or the comic has no barcode.
- Local library grid sorted by date added (newest first), with search and comic detail view.
- ComicVine API key entry (Open Library and Google Books need no key).
- Library export and import via
Downloads/inkventory-*.json.
First-time setup
- Install JDK 17+ (e.g.
sdk install java 17.0.11-tem). - Install Android Studio (Ladybug or newer) with Android SDK Platform 35.
- Bootstrap the Gradle wrapper JAR — this repo ships
gradle-wrapper.propertiesbut not the binary. From the repo root, run once:
(Or open the project in Android Studio, which will set up the wrapper for you.)gradle wrapper --gradle-version 8.13 - Get a free ComicVine API key: create an account at https://comicvine.gamespot.com/api/, paste the key into Settings → ComicVine API key on first run.
Build & install
./gradlew assembleDebug # build debug apk
./gradlew installDebug # install debug on attached device/emulator
./gradlew installBenchmark # install R8-optimised build (debug-signed) for perf testing
./gradlew testDebugUnitTest # run unit tests
./gradlew connectedDebugAndroidTest # run Room repo tests on a device
Debug and benchmark builds use applicationIdSuffix (.debug / .benchmark) so they coexist with each other on the same device. The debug build uses an alternative launcher icon to tell them apart.
The emulator's camera is unreliable for barcodes — use a real device for the scan flow.
Architecture
ui → domain (use cases) → data (repository) → {local (Room) | remote (MetadataSource, GoogleBooksCoverSource)}
- MVVM with Hilt DI and Jetpack Compose.
- Room for the local library (schema v2). Every row has
updatedAt+ nullableremoteIdso a future sync engine can bolt on without a schema rewrite. MetadataSourceinterface (indata/remote/) is the pluggable seam for metadata providers. ComicVine (singles, UPC) and Open Library (trades/GNs, ISBN) ship today; GCD / LoCG / etc. just need new implementations bound via@IntoSetindi/MetadataModule.kt.GoogleBooksCoverSourceis a separate, non-MetadataSourcehelper. It is not registered in theSet<MetadataSource>fan-out; instead it is injected directly intoResolveCodeUseCaseandManualMatchViewModeland used only as a cover-image fallback when the primary source returns a candidate with nocoverUrl.- Scan flow:
CameraXpreview +ImageAnalysiswith a singleBarcodeAnalyzerthat runs ML Kit barcode detection and text recognition on each frame in parallel. Detected barcodes are inspected: if the raw value is a valid 978/979-prefixed ISBN-13 (or ISBN-10) it is emitted asScannedCode.Isbn; otherwise asScannedCode.Barcode. The first confident hit wins and is handed toResolveCodeUseCase, which fans out to every configured source (ComicVine skips non-UPC-length codes; Open Library only runs on valid ISBNs). Zero or >1 results land on the Manual Match screen so the user can disambiguate. ScannedCodesealed type (Barcode/Isbn) carries through the entire scan→lookup pipeline.ResolveCodeUseCasesetsisbnorbarcodeon the resulting candidates accordingly, and applies a Google Books cover fallback for ISBN scans.AddComicUseCasepreserves both fields through thefetchDetailsenrichment step.
Scope notes for v1
Out of scope by design: condition/grading/CGC, purchase price & market value, reading lists, custom tags, cover uploads, cloud sync, auth. The data layer and source interface are structured so any of these can be added without rewriting existing code.
Project layout
app/src/main/java/com/inkventory/
├── InkventoryApplication.kt, MainActivity.kt
├── ui/theme/ ui/navigation/ ui/component/
├── ui/screen/{library,scan,match,detail,settings}/
├── domain/{model,usecase}/
├── data/{local,remote,scanner,preferences,repository}/
└── di/