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%
Find a file
Jonathan Cremin 2e4166a1dc
All checks were successful
Build & test / build (push) Successful in 3m5s
fix: strip versionNameSuffix from User-Agent
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.
2026-05-18 20:22:38 +01:00
.forgejo docs: add screenshot to README 2026-04-26 20:36:51 +01:00
app fix: strip versionNameSuffix from User-Agent 2026-05-18 20:22:38 +01:00
gradle feat: cross-device sync via self-hosted Supabase 2026-05-03 15:54:31 +01:00
inkventory_icon feat: refresh launcher icons with mono and alternative variants 2026-05-03 15:53:15 +01:00
supabase/migrations feat: cross-device sync via self-hosted Supabase 2026-05-03 15:54:31 +01:00
.gitignore feat: cross-device sync via self-hosted Supabase 2026-05-03 15:54:31 +01:00
build.gradle.kts feat: initial commit 2026-04-26 11:00:07 +01:00
CLAUDE.md feat: initial commit 2026-04-26 11:00:07 +01:00
compose_stability.conf feat: initial commit 2026-04-26 11:00:07 +01:00
Containerfile ci: add containerised builds with APK publishing 2026-04-26 18:28:34 +01:00
gradle.properties feat: initial commit 2026-04-26 11:00:07 +01:00
gradlew feat: initial commit 2026-04-26 11:00:07 +01:00
gradlew.bat feat: initial commit 2026-04-26 11:00:07 +01:00
README.md docs: add screenshot to README 2026-04-26 20:36:51 +01:00
settings.gradle.kts feat: initial commit 2026-04-26 11:00:07 +01:00

Inkventory

Scan your physical comic collection into a pocketable inventory. Native Android, Kotlin, Jetpack Compose + Material 3.

Inkventory library screen

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

  1. Install JDK 17+ (e.g. sdk install java 17.0.11-tem).
  2. Install Android Studio (Ladybug or newer) with Android SDK Platform 35.
  3. Bootstrap the Gradle wrapper JAR — this repo ships gradle-wrapper.properties but not the binary. From the repo root, run once:
    gradle wrapper --gradle-version 8.13
    
    (Or open the project in Android Studio, which will set up the wrapper for you.)
  4. 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 + nullable remoteId so a future sync engine can bolt on without a schema rewrite.
  • MetadataSource interface (in data/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 @IntoSet in di/MetadataModule.kt.
  • GoogleBooksCoverSource is a separate, non-MetadataSource helper. It is not registered in the Set<MetadataSource> fan-out; instead it is injected directly into ResolveCodeUseCase and ManualMatchViewModel and used only as a cover-image fallback when the primary source returns a candidate with no coverUrl.
  • Scan flow: CameraX preview + ImageAnalysis with a single BarcodeAnalyzer that 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 as ScannedCode.Isbn; otherwise as ScannedCode.Barcode. The first confident hit wins and is handed to ResolveCodeUseCase, 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.
  • ScannedCode sealed type (Barcode / Isbn) carries through the entire scan→lookup pipeline. ResolveCodeUseCase sets isbn or barcode on the resulting candidates accordingly, and applies a Google Books cover fallback for ISBN scans. AddComicUseCase preserves both fields through the fetchDetails enrichment 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/