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.
The module follows a strict pattern. The UI observes state from the PostBoxViewModel, which orchestrates data from local persistence and system services.
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.”
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.
The ViewModel performs significant data shaping using :
Grouping: Raw records are grouped by packageName.
Mapping: Each group is transformed into a PostBoxOverviewItem.
Sorting: Items are sorted by notification count so the most urgent apps appear first.
Time Calculation: The nextDeliveryTimeLabel() combines the user’s batch schedule with the current system time to show exactly when the next “delivery” will occur.
Copy
// Example of the transformation logic in the ViewModelfun 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 } }}