Code Conventions

How to write code that fits Scanly's architecture.

Layer rules

LayerMay callMust not call
feature/domain/usecase/, core/ui/Room DAOs, DataStore, filesystem
domain/Repository interfacesAndroid framework, Room, Compose
data/Room, files, network, core/Compose, ViewModels
core/Other core/Feature screens, ViewModels

Rule of thumb: if a ViewModel needs new I/O, add a use case and repository method — do not inject a DAO.

Adding a new screen

  1. Destination — *Destination object with routePattern and route() factory.
  2. ViewModel — @HiltViewModel, inject use cases, expose StateFlow UI state.
  3. Screen — Compose UI in feature/{name}/, wrap in ScanlyTheme.
  4. Navigation — register composable in ScanlyNavHost.kt.
  5. Use case — add in domain/usecase/ for new business logic.
  6. Repository — extend interface + implement in data/ for new I/O.
  7. DI — bind in appropriate di/*Module.kt.
  8. Tests — unit tests for logic; instrumented for critical UI.
  9. Docs — update relevant docs/ pages for behavior changes.

Room migrations

When changing schema:

  1. Update entities in data/local/db/entity/.
  2. Add Migration_X_Y in ScanlyDatabase.kt.
  3. Bump @Database(version = ...).
  4. Register migration in DatabaseModule.
  5. Test on device with data from previous version.
  6. Update ../releases.md, ../../CHANGELOG.md, ../../VERSION.md.

Existing migrations: 1→2 (preferred filter preset on documents), 2→3 (document groups), 3→4 (page filter adjustments), 4→5 (page filter intensity; legacy preset remap).

Image processing rules

  • Never overwrite raw/ files.
  • Regenerate processed/ and thumbs/ on every edit.
  • Model assets: corner variants (document_corners_*.tflite) and scanly_document_gate_float16.tflite under app/src/main/assets/models/ — see models/README.txt.
  • Keep noCompress += "tflite" in app/build.gradle.kts.
  • Mark NEEDS_REVIEW when processing cannot produce a quad.
  • Filters defined in PageFilterPreset; OpenCvPageFilterProcessor is the facade over core/processing/filter/ (analyze → background model → look → engine); strength is PageFilterAdjustments.intensity; optional tonal tweaks via PageFilterAdjustmentsApplier.
  • Crop AI Detect goes through DetectDocumentCornersUseCase / PageImageProcessor.detectDocumentCorners (do not call detectors from Compose).
  • Live vs post models and the document gate are Settings preferences; do not hard-code a single model path in UI code.
  • Editor sub-tools: Filters and Adjust are full-screen overlays sharing PageEditorViewModel; Crop is a NavHost sibling route.

Storage layout

Do not change on-disk paths without a migration plan:

files/documents/{documentId}/raw|processed|thumbs/
cache/exports/
cache/library-archive/
files/library-archive-journal/

User-visible paths (via MediaStore or SAF):

Downloads/Scanly/           # Default export base
Downloads/Scanly/backup/    # Reserved for .scanly library archives

Dependency management

  1. Add version + library to gradle/libs.versions.toml.
  2. Reference via libs.* in app/build.gradle.kts.
  3. Update app/src/main/assets/settings/licenses.json for new third-party libs.
  4. Update ../reference/tech-stack.md.

UI conventions

  • Wrap screens in ScanlyTheme.
  • Reuse feature/components/ and core/ui/ before one-off layouts.
  • Use AdaptiveLayout for phone vs tablet.
  • Theme via AppSettingsViewModel + DataStore.

Kotlin style

  • Official Kotlin style (kotlin.code.style=official in gradle.properties).
  • Match existing naming and file organization in the package you're editing.

Documentation updates

ChangeUpdate
User-facing behavior../../README.md, ../../CHANGELOG.md
Architecture / data flowRelevant docs/architecture/ or docs/data/ page
Processing pipelinedocs/processing/ pages
Build workflowThis file or setup.md
Public release../../VERSION.md, ../releases.md
Agent guidance../../Agents.md

View source on GitHub · Markdown