Skip to main content

Overview

The Post Box is a core feature designed to combat notification fatigue. Instead of receiving constant interruptions, users can configure specific apps to have their notifications “held” and delivered in batches at set times throughout the day. To the user, this manifests as a focused, high-quality interface where they can review missed alerts in a “Treemap” overview or a “Card Stack” detail view, allowing them to process information intentionally rather than reactively.

Architecture & Data Flow

The module follows a strict pattern. The UI observes state from the PostBoxViewModel, which orchestrates data from local persistence and system services.

Key Components

ComponentResponsibility
PostBoxActivityThe entry point that toggles between the Overview (Treemap) and Detail (Card Stack) screens.
PostBoxViewModelManages the business logic, including calculating the “Next Delivery” label and transforming raw DB records into UI-ready items.
AppMetadataRepositoryHandles the retrieval and caching of app icons and labels, ensuring the UI remains performant without constant queries.
SettingsRepositoryManages user preferences for which apps are batched and the specific of scheduled delivery times.
AppSnackbarHostA specialized UI component that provides “Undo” capabilities for dismissed notifications, intelligently avoiding overlaps with the navigation bar.

Operational Logic

1. Initialization & Filtering

When the user opens the Post Box, the PostBoxViewModel queries the for notifications that are:
  1. Batched: Specifically marked for delayed delivery.
  2. Undelivered: Not yet seen or dismissed by the user.
  3. Recent: Within a 24-hour cutoff to ensure relevance.

2. The Overview (Treemap)

The overview uses a Treemap visualization. This isn’t just aesthetic; it’s a data-driven representation where the size of each block corresponds to the volume of notifications from that specific app. This helps users visually identify which apps are the “loudest.”

3. The Detail View (Card Stack)

When an app is selected, the UI transitions to a horizontal card stack.
  • Gestures: Users can swipe left/right to navigate or use the “Done” action to dismiss.
  • Haptics: The module uses a to provide physical confirmation for swipes and clicks, enhancing the “analogue” feel of the app.
  • Deep Linking: The open() function attempts to trigger the notification’s original PendingIntent. If that fails, it falls back to launching the app’s main activity.

Data Engineering & Transformation

Source Tables

  • NotificationRecord: The primary source for titles, text, and timestamps.
  • AppMetadata: Used to join package names with user-friendly names and local icon file paths.

Transformation Pipeline

The ViewModel performs significant data shaping using :
  1. Grouping: Raw records are grouped by packageName.
  2. Mapping: Each group is transformed into a PostBoxOverviewItem.
  3. Sorting: Items are sorted by notification count so the most urgent apps appear first.
  4. Time Calculation: The nextDeliveryTimeLabel() combines the user’s batch schedule with the current system time to show exactly when the next “delivery” will occur.
// Example of the transformation logic in the ViewModel
fun loadOverview(): Flow<List<PostBoxOverviewItem>> {
    return notificationDao.getUndeliveredBatchedSinceFlow(cutoff).map { pending ->
        pending.groupBy { it.packageName }.map { (pkg, list) ->
            PostBoxOverviewItem(
                packageName = pkg,
                appName = meta?.appName ?: fallbackLabel(pkg),
                count = list.size
            )
        }.sortedByDescending { it.count }
    }
}

Dependencies

  • Hilt: Used for of the Repositories and DAOs.
  • Room: Provides the reactive Flow streams from the local SQLite database.
  • Coil: Used for asynchronous loading of app icons from the internal storage cache.
  • SharedPreferences: Accessed via SettingsRepository to store the list of “Batched” package names.
Last modified on January 22, 2026