Settings and Updates

User preferences, bundled support content, export destination, and the optional app update flow.

DataStore: scanly_settings

Managed by DefaultSettingsRepository (data/settings/).

KeyTypeDefaultPurpose
theme_modeString"system"SYSTEM, LIGHT, or DARK (ThemeMode.storageValue)
pure_black_enabledBooleanfalsePure black Material 3 surfaces when dark theme is active (AMOLED)
onboarding_completedBooleanfalseFirst-run gate flag
export_tree_uriString?nullPersisted SAF tree URI for custom export/backup base
export_tree_labelString?nullDisplay name for custom export folder
live_detection_modelString"legacy"Manual live-preview corner model (lite / standard / accurate=High / legacy=Accurate)
post_processing_modelString"legacy"Manual captured-image corner model (same storage values)
automatic_document_model_selectionBooleantrueCalibrate Lite/Standard/High on device; lock manual selectors
document_gate_enabledBooleantrueRun or bypass the physical-document semantic gate in both pipelines

When export_tree_uri and export_tree_label are both set, ExportDestination.CustomTree is used; otherwise ExportDestination.DefaultDownloadsScanly (Downloads/Scanly).

Theme / look & feel flow

SettingsViewModel.setThemeMode() / setPureBlackEnabled()
  → SetThemeModeUseCase / SetPureBlackEnabledUseCase
  → SettingsRepository → DataStore write

AppSettingsViewModel (MainActivity)
  → ObserveThemeModeUseCase + ObservePureBlackEnabledUseCase
  → ScanlyTheme(darkTheme = resolved, pureBlack = preference)

Pure black only changes surface colors when dark theme is active (Dark mode, or System when the OS is dark). The preference can still be toggled while Light is selected and takes effect later.

Document detection preferences

ControlBehavior
Automatic model selectionBenchmarks Lite/Standard/High once per process; assigns live and post models under latency budgets
Live preview modelManual pick when automatic is off; chip + bottom-sheet picker in Settings
Post-processing modelIndependent manual pick for capture/import finalize
Physical-document gateEnable/bypass gate inference for live + post pipelines
Model benchmarkTemporary local image run; does not persist results

Bundled assets

AssetPurpose
assets/settings/faqs.jsonFAQ entries for Settings FAQ sub-screen
assets/settings/licenses.jsonThird-party license disclosures
assets/models/document_corners_lite.tfliteLite corner model (224)
assets/models/document_corners_standard.tfliteStandard corner model (288)
assets/models/document_corners_accurate.tfliteHigh corner model (384 regression)
assets/models/document_corners_float16.tfliteAccurate corner model (YOLO-pose; formerly Legacy)
assets/models/scanly_document_gate_float16.tflitePhysical-document semantic gate
assets/models/README.txtModel placement and contract notes
assets/adi-registration.propertiesModel registration metadata

LoadSettingsContentUseCase parses FAQ and license JSON into SettingsContent domain model.

Settings screen structure

Main Settings is a hub with a short list of destinations. Advanced controls live one level down.

Hub rowRouteContents
Appearancesettings/appearanceTheme mode, pure black
Storage & backupsettings/storageUsage, backup/restore, destination, empty cleanup, clear all
Document detectionsettings/detectionAutomatic/manual models, gate, benchmark link
Widgetssettings/widgetsPin Actions / Scan / QR
Help & FAQsettings/faqfaqs.json
Aboutsettings/aboutVersion, updates, support links, legal, licenses
NestedRouteNotes
Model benchmarksettings/model-benchmarkFrom Document detection
Legal documentslegal/{documentType}From About
Licensessettings/licensesFrom About

App update flow

Components

ClassRole
GitHubAppUpdateRepositoryCompares the installed version with the latest GitHub release
PlayStoreAppUpdateRepositoryChecks Google Play for update availability
DefaultPlayInAppUpdateCoordinatorStarts, resumes, and completes Play in-app updates
NoOpPlayInAppUpdateCoordinatorNo-op coordinator for debug/githubRelease builds
GitHubReleaseUpdateRepositoryFetches the latest GitHub release and notes
DistributionAppUpdateModuleBuild-type-specific binding that selects the authoritative update repository
DefaultAppUpdatePromptRepositoryStores dialog cooldown timestamp
CheckForAppUpdateUseCaseDelegates to AppUpdateRepository
AppUpdateViewModelOrchestrates automatic and manual checks
AppUpdateDialogUI overlay with release notes
FlexibleUpdateSnackbarHostPrompts restart after flexible update download
AppUpdateDialogCooldown6-hour rate limit
PlayInAppUpdatePolicyChooses flexible vs immediate update type
ReleaseMarkdownParses release body for dialog display

Flavor-specific source sets

Source setUpdate binding
app/src/debug/GitHub updates + no-op Play coordinator
app/src/githubRelease/GitHub updates + no-op Play coordinator
app/src/playStoreRelease/Play Store updates + real Play coordinator

Update sources

SourcePurpose
githubRelease build typeGitHub Releases API is authoritative for availability and release notes
playStoreRelease build typeGoogle Play is authoritative for availability/download/install; GitHub optionally enriches notes

Cooldown DataStore: scanly_update_prompt

KeyTypePurpose
last_update_dialog_shown_at_millisLongTimestamp of last dialog display

Dialog shown at most once every 6 hours for automatic checks (since v1.0.7).

Update check triggers

TriggerWhen
AutomaticMainActivity ON_START, only after onboarding complete
ManualSettings "Check for updates" button

Distribution channels

VariantAvailability checkUpdate action
githubReleaseCompare installed versionName with the latest GitHub release tagOpen the GitHub release page
playStoreReleaseGoogle Play In-App Update APIFlexible or immediate Play update flow

Both variants use the same application ID and release version. BuildConfig.UPDATE_CHANNEL and build-type-specific Hilt modules keep the updater fixed to the artifact that was built.

Play Store update types

TypeWhen used
FlexibleDefault optional updates; downloads in background and prompts restart
ImmediateHigh-priority updates (inAppUpdatePriority >= 4) or stalled immediate flows

High-priority immediate updates launched automatically on ON_START skip the custom dialog and open the Play Store flow directly.

Download action

User taps Update in the dialog. The GitHub build opens the corresponding GitHub release page. The Play Store build launches the Google Play in-app update flow; flexible updates show a restart snackbar after download completes.

Flow diagram

ON_START / Settings button
  → CheckForAppUpdateUseCase
    → Build-type-specific AppUpdateRepository
      → github: GitHubAppUpdateRepository
        → GitHubReleaseUpdateRepository.fetchLatestReleaseNotes()
        → compare installed version with release tag
      → playStore: PlayStoreAppUpdateRepository
        → DefaultPlayInAppUpdateCoordinator.refreshAvailability()
        → GitHubReleaseUpdateRepository.fetchLatestReleaseNotes() (optional)
  → If update available AND cooldown OK
    → AppUpdateDialog
  → User taps Update
    → github: open GitHub release page
    → playStore: Play in-app update flow
  → playStore ON_RESUME
    → resume stalled immediate update or show flexible restart snackbar

Testing notes

Play in-app updates only work for playStoreRelease builds installed from Google Play (internal, closed, open, or production tracks). Sideloaded githubRelease builds use GitHub Releases and do not call Play update lifecycle operations.

View source on GitHub · Markdown