Skip to main content

Overview

The navigation module (and its associated UI features) serves as the architectural backbone of the ScrollLess application. It is responsible for orchestrating the across five primary domains: Dashboard, Insights, Limits, App Details, and Settings. The feature’s primary value proposition is transforming abstract “screen time” into physical metrics (e.g., “You scrolled 2.5 kilometers today”) and providing the tools (Limits/Nudges) to modify that behavior.

Architecture & Data Flow

ScrollLess employs a robust reactive architecture. Data originates in local databases, is processed by specialized , and is finally synthesized into UI-ready models by ViewModels.

The State Synthesis Pipeline

A critical pattern observed in ScrollDetailViewModel and TodaySummaryViewModel is the use of combine and flatMapLatest. These ViewModels do not just fetch data; they perform “Data Join” operations in memory:
  1. Raw Usage: How many milliseconds was the app open?
  2. Scroll Metrics: How many pixels/micrometers were traversed?
  3. Metadata: What is the app’s name and icon?
  4. Limits: Is there an active restriction on this app?

Key Components

ComponentResponsibility
AppNavigationHostThe central router defining graph routes and nested flows (e.g., the CREATE_LIMIT_FLOW_ROUTE).
TodaySummaryViewModelAggregates “Today vs. Yesterday” comparisons for the main dashboard.
InsightsViewModelThe “Analyst” component. It calculates complex routines like “Rise and Shine” (first app used) and “Night Owl” habits.
LimitsViewModelManages the lifecycle of .
CalibrationViewModelA specialized utility that maps hardware pixels to physical distance (meters) using user-inputted DPI scaling.

Operational Logic

1. Initialization & Onboarding

The AppNavigationHost acts as a gatekeeper. Upon launch, it queries the SettingsRepository for onboardingCompleted. If false, the user is funneled into the SetupScreen. Once true, the DASHBOARD_GRAPH_ROUTE becomes the default entry point.

2. The “Zero-Lag” Insight System

The InsightsViewModel implements a pre-fetching strategy. In its init block, it calls calculateAllHistories(), which populates a ConcurrentHashMap cache. This ensures that when a user taps an insight card, the detailed historical chart opens instantly without a loading spinner.

3. Dynamic Limit Enforcement

Limits are handled via a LimitViewModelDelegate. This allows multiple screens (Dashboard, App Detail, Scroll Detail) to share the same logic for “Quick Limits.” When a user sets a limit, the delegate interacts with the LimitsRepository, which likely triggers a task or a background service to monitor usage.

Data Engineering & Transformation

The module performs significant data transformation to ensure user-friendliness:
  • Scroll Normalization: Raw scroll data is often stored in pixels. The ConversionUtil uses the calibratedDpi (from CalibrationScreen) to convert these into micrometers.
  • Trend Analysis: The TrendAnalyzer (used in InsightsViewModel) looks at 7-day windows to determine if a user’s “Context Switching” or “Notification Response Time” is improving or degrading.
  • Treemap Logic: The NotificationTreemapScreen uses a custom algorithm to group low-frequency apps into an “Other” category, ensuring the UI remains readable while still accounting for 100% of notification volume.

Dependencies

  • Jetpack Navigation: Handles the backstack and argument passing (e.g., passing packageName to AppDetailScreen).
  • Hilt: Provides for all ViewModels.
  • Coil: Used for asynchronous image loading of app icons from the filesystem.
  • Kotlin Coroutines/Flow: The primary mechanism for asynchronous data streaming from the DB to the UI.
// Example of the complex state combining found in the module
val uiState: StateFlow<ScrollDetailUiState> = combine(
    _selectedDate,
    _period,
    repository.getTotalScrollPerDay(),
    visibleAppsFlow,
    allLimitedApps
) { ... }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ScrollDetailUiState())
---
**Architect's Note**: The separation of `CreateLimitGroupScreen` and `EditLimitGroupScreen` into distinct routes, despite sharing a ViewModel, is a deliberate choice to prevent "State Bleeding" where data from a previous edit session might accidentally populate a new creation form.
---
Last modified on January 22, 2026