Navigation

All routes and user flows in Scanly v1.0.17.

Navigation is implemented with Navigation Compose in ScanlyNavHost.kt. Route helpers follow the *Destination object pattern with routePattern and route() factory functions.

Form factorChrome
PhoneScaffold + bottom NavigationBar
TabletDestination-owned NavigationRail (92 dp) with app logo on top-level screens
  • Start destination: home
  • Tab switches: no transition animation
  • Detail pushes: short fade with a small forward offset; back navigation reverses the motion
  • Large-screen stability: the tablet rail is composed inside each top-level destination, so entering or leaving a detail route never resizes the NavHost mid-transition
  • Top inset: each screen applies status-bar padding once (not doubled by the activity shell)

Top-level tabs

RouteScreenIn bottom nav / rail
homeHome dashboardYes
libraryFull libraryYes
toolsTools hub (Scan, PDF, Utilities)Yes
settingsSettingsYes

Legacy placeholder routes

These top-level routes still exist but show FeaturePlaceholderScreen — they are sprint-era stubs, not active flows:

RouteNote
cameraUse camera/session/{documentId} instead
reviewReview happens in document detail and page preview
editorUse editor/page/{pageId} instead

Typed routes (active flows)

Route patternHelper objectScreen
document/{documentId}DocumentDestinationDocument detail
library/searchLibrarySearchDestinationLibrary name search across documents and folders; owns separate query state
camera/session/{documentId}?replacePageId={pageId}ScanSessionDestinationScan session
preview/page/{pageId}PageImagePreviewDestinationPage preview
editor/page/{pageId}PageEditorDestinationPage editor (preview, filters/adjust overlays, retake, delete)
crop/page/{pageId}PageCropDestinationAI detect, rotate, four-point crop, reset, apply

Editor overlays (not NavHost routes): FilterPickerScreen and FilterCustomizeScreen share PageEditorViewModel and replace the editor content in place (same pattern as a full-screen mode, not a bottom sheet). | group/{groupId} | GroupDetailDestination | Group detail | | legal/{documentType} | LegalDocumentDestination | Privacy or terms viewer | | settings/appearance | SettingsAppearanceDestination | Theme + pure black | | settings/detection | SettingsDetectionDestination | Models, gate, benchmark link | | settings/widgets | SettingsWidgetsDestination | Pin home-screen widgets | | settings/about | SettingsAboutDestination | Version, updates, legal, support | | settings/faq | SettingsFaqDestination | FAQ sub-screen | | settings/licenses | SettingsLicensesDestination | Open-source licenses | | settings/storage | SettingsStorageDestination | Storage & backup | | settings/model-benchmark | SettingsModelBenchmarkDestination | Temporary local model comparison | | tools/qr | ToolsQrDestination | QR scan, typed result, create, and history (in-route screens) | | tools/pdf/reader?filePath={filePath}&fileName={fileName} | ToolsPdfReaderDestination | PDF reader; optional app-owned result file opens directly | | tools/pdf/merge | ToolsPdfMergeDestination | PDF merge | | tools/pdf/compress | ToolsPdfCompressDestination | PDF compress | | tools/pdf/password | ToolsPdfPasswordDestination | PDF password protect/remove | | tools/pdf/watermark | ToolsPdfWatermarkDestination | PDF text watermark |

Back behavior

  • When opened from Tools, Back from a PDF reader returns to the file picker; Back from that picker exits to Tools. A PDF preview opened from a PDF tool returns to that tool's result screen. Choose another opens a temporary picker, where Back returns to the current reader.
  • In PDF compression, Back from options returns to the file picker; Back from that picker exits to Tools. Change opens a temporary picker, where Back returns to options.
  • In watermarking, Back from the editor returns to the picker; Change opens a temporary picker, where Back returns to the current editor.
  • Android Back closes the editor's full-screen filter and adjustment modes before leaving the editor. It is blocked while a page is being captured, cropped/saved, filter-saved, or processed by a PDF tool.

Scan session arguments

  • documentId (required) — target document
  • replacePageId (optional) — when set, capture replaces this page instead of adding a new one. On complete, navigates to editor/page/{replacePageId}.

Settings sub-screen ViewModel sharing

Settings sub-screens that edit preferences (appearance, detection, widgets, about, faq, licenses, storage) share SettingsViewModel via the parent settings back stack entry. The model benchmark owns a separate ViewModel because its runs and results are temporary.

External launch actions (widgets & shortcuts)

MainActivity accepts explicit launch actions from widgets and launcher shortcuts (singleTop). LaunchActionViewModel queues the action until onboarding is complete, then redirects:

ActionIntent actionRedirect
Scanin.c1ph3rj.scanly.action.SCANSuggest title → create document → camera/session/{documentId}
Importin.c1ph3rj.scanly.action.IMPORTSystem photo picker → import pipeline → document/{documentId}
QRin.c1ph3rj.scanly.action.QRTools tab → tools/qr?mode=scan
Libraryin.c1ph3rj.scanly.action.LIBRARYTop-level library

Config: AndroidManifest.xml receivers + res/xml/shortcuts.xml; providers live under feature/widget/.

Widget / shortcut → scan

Widget or quick action (Scan)
  └─► MainActivity (action.SCAN)
        └─► (after onboarding) suggest title + create document
              └─► camera/session/{newDocId}

User flow diagrams

User flow diagrams

App startup

Launch
  └─► Onboarding required?
        ├─ Yes → OnboardingScreen → complete → Home
        └─ No  → Home
              └─► (background) update check if cooldown expired

Create and scan

Home / Library
  └─► Create + Scan (default name prefilled; clear or Suggest name)
        └─► camera/session/{newDocId}
              └─► capture page(s)
                    └─► document/{docId}

Edit a page

document/{docId}
  └─► preview/page/{pageId}
        ├─► overflow: Share / Edit / Retake / Delete
        └─► editor/page/{pageId}
              ├─► Filters  → full-screen FilterPickerScreen (live preview + presets; Done → editor)
              ├─► Adjust   → full-screen FilterCustomizeScreen (sliders + compare; Done → editor)
              ├─► crop/page/{pageId}
              │     ├─► AI Detect / rotate / handles / Reset
              │     └─► Done → reprocess crop+rotation → navigateUp → editor
              ├─► editor Done → reprocess filter + adjustments → back to preview/detail
              └─► retake → camera/session/{docId}?replacePageId={pageId}
                              └─► editor/page/{pageId}  (replacement complete)
Home (new doc) or document/{docId} (add pages)
  └─► photo picker (≤10 images)
        └─► import pipeline (same as capture finalize)
              └─► document/{docId}

Group workflow

Library (Folders filter) or Home (recent groups)
  └─► group/{groupId}
        ├─► document/{docId}  (open member)
        ├─► rename / delete group
        ├─► add/remove documents
        └─► export merged PDF or zipped PDFs

Export

document/{docId} or group/{groupId}
  └─► export sheet (PDF options: password, page numbers, orientation, size, margins)
        ├─► Save → configured Downloads/SAF destination (unique filenames)
        └─► Share → cache/exports → FileProvider share sheet

Library backup and restore

settings → settings/storage
  ├─► Back up library → WorkManager foreground job → {destination}/backup/*.scanly
  └─► Restore → pick .scanly in backup folder → Replace or Merge as copies

Settings

settings
  ├─► Look & feel: theme mode + pure black (AMOLED) toggle
  ├─► settings/storage (usage, destination, backup/restore, clear data)
  ├─► Document detection: automatic or manual live/post models
  ├─► Document detection: physical-document gate on/off
  ├─► settings/model-benchmark (gate + four corner models on local images)
  ├─► settings/faq
  ├─► legal/{PRIVACY} or legal/{TERMS}
  ├─► settings/licenses
  └─► check for updates → AppUpdateDialog (channel-specific)

Onboarding gate

MainActivity uses AnimatedContent to switch between onboarding states:

StateUI
LOADINGBlank / loading
REQUIREDOnboardingScreen
COMPLETEScanlyNavHost

Automatic update checks run on ON_START only when onboarding is COMPLETE.

View source on GitHub · Markdown