Documentation Index
Fetch the complete documentation index at: https://docs.scrollless.in/llms.txt
Use this file to discover all available pages before exploring further.
Summary
The application follows a Clean Architecture approach using MVVM (Model-View-ViewModel) with Jetpack Compose for the UI. It relies heavily on a “Local-First” data strategy where the Room database is the single source of truth for the UI, while background services and workers synchronize data from Android system APIs (Accessibility, UsageStats) into that database. The codebase uses Hilt for dependency injection and Coroutines/Flow for asynchronous operations.Feature Structure
Most features (screens) are loosely grouped by domain but physically located inui/ packages. A typical feature consists of:
- Screen (Composable): Accepts a ViewModel and Navigation Controller.
- ViewModel: Holds
StateFlowfor UI state, interacts with Repositories. - Repository: Fetches data from DB or System.
Screen Implementation Pattern
Screens typically collect state usingcollectAsStateWithLifecycle() and pass events back to the ViewModel.
ViewModel Pattern
ViewModels useStateFlow to expose UI state. They often combine multiple data streams (e.g., Usage Data + App Icons + Filter Settings) into a single UI State object.
Conventions:
- Hilt Injection: All ViewModels are annotated with
@HiltViewModel. - Flow Combination: Heavy use of
combineto merge database updates with user filters (e.g.,filterSetinScrollDataRepositoryImpl). - Coroutines:
viewModelScopeis used for launching suspend functions.
Core Architecture: The Tracking Pipeline
The app’s core function—tracking usage and scrolling—uses a specific pipeline to move data from the Android System to the UI.1. Data Ingestion (The Write Path)
Data enters the system through two primary channels:- Real-time (Accessibility):
ScrollTrackServicecaptures scroll events and window changes. It writes raw events directly toraw_app_eventsviaRawAppEventDao. - Historical/Backup (UsageStats):
UsageStatsWorkerpolls Android’sUsageStatsManagerto fill in gaps or handle apps where accessibility is disabled.
2. Data Processing (The Aggregation Path)
Raw events are too heavy for direct UI consumption. The app uses a “Process and Summarize” strategy.- DailyDataProcessor: A logic class that takes raw events and calculates:
- Total Screen Time
- Unlock Counts
- Scroll Distance (Pixels -> Meters conversion)
- Trigger: This runs via
DailyProcessingWorker(periodic) orScrollDataRepository.processAndSummarizeDate(on app open). - Output: Writes to summary tables (
daily_app_usage,daily_device_summary).
3. Real-Time Limit Enforcement
To block apps instantly, the app cannot wait for database writes. It uses a Usage Projection Engine. Formula:Total Usage=Persisted DB Usage+Pending Buffer+Live Session Duration
- Persisted: Data already saved in
daily_app_usage. - Buffer: Sessions that finished in the last few seconds but aren’t in the DB yet.
- Live:
ActiveSessionTrackermeasures the current foreground session usingMonotonicClock.
Dependency Injection (Hilt)
The app uses Hilt for DI. The configuration is centralized indi/.
| Module | File | Purpose |
|---|---|---|
| AppSingletonProvides | AppSingletonProvides.kt | Provides global singletons: AppDatabase, DAOs, Retrofit, WorkManager, Clock. |
| AppSingletonBinds | AppSingletonProvides.kt | Binds interface implementations (e.g., LimitsRepositoryImpl to LimitsRepository). |
| ViewModelModules | ViewModelModules.kt | Provides ViewModel-scoped delegates (e.g., LimitViewModelDelegate). |
@Singleton: Used for Repositories, Database, and “Monitor” classes (LimitMonitor,NudgeMonitor) that must maintain state across the app lifecycle.@ApplicationScope: A custom qualifier for aCoroutineScopethat survives UI destruction (used for fire-and-forget DB writes).
Navigation
Navigation is handled by Jetpack Navigation Compose inNavGraph.kt.
- Routes: Defined as a sealed class
ScreenRoutes. - Arguments: Passed via route strings (e.g.,
app_detail/{packageName}). - Graph Structure:
DASHBOARD_GRAPH: Main tabs (Dashboard, Usage, Unlocks).SETTINGS_GRAPH: Settings, About, FAQ.LIMITS_GRAPH: Limit management.CREATE_LIMIT_FLOW: A nested graph for the multi-step limit creation wizard.
Background Work
The app employs a hybrid approach for background tasks:| Component | Type | Use Case | Implementation |
|---|---|---|---|
| ScrollTrackService | Accessibility Service | Real-time scrolling, app switching detection, overlay drawing. | ScrollTrackService.kt |
| AppTrackerService | Foreground Service | Fallback usage tracking if Accessibility is disabled. | AppTrackerService.kt |
| LimitEnforcementWorker | Periodic Worker | Safety net to re-check limits every 15 mins. | LimitEnforcementWorker.kt |
| DailyProcessingWorker | Periodic Worker | Aggregates raw data into summaries, runs nightly. | DailyProcessingWorker.kt |
| AppMetadataSyncWorker | OneTime Worker | Fetches icons/labels when a new app is installed. | AppMetadataSyncWorker.kt |
Data Layer Conventions
Repository Pattern
Repositories act as the gatekeepers. They exposeFlow<T> for data that changes (UI) and suspend functions for one-off operations.
- Naming:
[Feature]Repository(Interface) ->[Feature]RepositoryImpl(Implementation). - Source of Truth: Almost always the local Database. Network calls (like
AppCategoryRepository) fetch data and immediately write to the DB; the UI observes the DB, not the network.
Database (Room)
- Entities: Located in
db/. - DAOs: Provide
Flowreturn types for reactive updates. - Transactions:
appDatabase.withTransaction { ... }is used heavily inScrollDataRepositoryImplto ensure data consistency when summarizing daily stats.