mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
docs(notifications): plan a per-kind beautiful push-notification redesign
Adds amethyst/plans/2026-07-23-push-notification-redesign.md: a rendering/UX design for the Android tray notifications that gives each Nostr notification kind (zap, reaction, reply, mention, DM, repost, nutzap, media, git, badge, chess) its own accent color, status-bar icon, and notification style (MessagingStyle / BigPictureStyle / colorized zap card), plus aggregation, Conversations/Bubbles, and richer actions. Also documents the spec+builder refactor, channel restructuring, icon/string work, parity gaps to close (nutzap/onchain/repost/badge), a phased rollout, and testing. Indexes the plan in amethyst/plans/README.md.
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
# Push Notification Redesign — beautiful, modern, per-kind tray notifications
|
||||
|
||||
**Date:** 2026-07-23
|
||||
**Goal:** Replace the flat, mostly-`BigTextStyle`, single-accent tray
|
||||
notifications with a per-kind design system: distinct accent colors, status-bar
|
||||
icons, notification *styles* (MessagingStyle / BigPictureStyle / colorized zap
|
||||
cards), smart aggregation, and Conversation-section integration — so every kind
|
||||
of Nostr notification (zap, reaction, reply, mention, DM, repost, nutzap, media,
|
||||
git, badge, chess…) reads at a glance and looks native to modern Android
|
||||
(12–15).
|
||||
|
||||
This is a **rendering / UX** plan. The delivery/transport layers (FCM,
|
||||
UnifiedPush, the always-on relay service, gift-wrap unwrap, account matching,
|
||||
mute/age/self gates) are untouched — everything below happens after
|
||||
`EventNotificationConsumer.dispatchForAccount()` decides an event is notifiable.
|
||||
|
||||
---
|
||||
|
||||
## 1. Current state (what we render today)
|
||||
|
||||
**Pipeline (unchanged by this plan):**
|
||||
`PushNotificationReceiverService` (FCM) / `PushMessageReceiver` (UnifiedPush) /
|
||||
`NotificationRelayService` (always-on) → `PushWrapDecryptor.unwrapAndFeed` →
|
||||
`LocalCache` → `NotificationDispatcher` (kind + age + `tagsAnEventByUser` gate) →
|
||||
`EventNotificationConsumer.dispatchForAccount()` → per-kind `notify*()` →
|
||||
`NotificationUtils` (`NotificationCompat.Builder`).
|
||||
|
||||
**What the tray looks like now:**
|
||||
|
||||
| Channel | Importance | Style used | Accent | Small icon | Actions |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| DMs (`PrivateMessagesID`) | HIGH | `MessagingStyle` ✅ | none | amethyst logo | Reply, Mark-read |
|
||||
| Zaps (`ZapsID`) | DEFAULT | `BigTextStyle` | none | amethyst logo | — |
|
||||
| Reactions (`ReactionsID`) | DEFAULT | `BigTextStyle` (+emoji avatar badge) | none | amethyst logo | — |
|
||||
| Replies (`RepliesID`) | DEFAULT | `BigTextStyle` | none | amethyst logo | Reply (inline) |
|
||||
| Mentions (`MentionsID`) | DEFAULT | `BigTextStyle` | none | amethyst logo | — |
|
||||
| Chess (`ChessID`) | DEFAULT | `BigTextStyle` | none | amethyst logo | — |
|
||||
|
||||
**Problems this plan fixes:**
|
||||
|
||||
1. **No color identity.** Every notification uses the same monochrome amethyst
|
||||
status-bar icon and no `setColor()`. A zap, a like, and a git PR are visually
|
||||
indistinguishable in the shade until you read the text.
|
||||
2. **Only DMs get a real style.** Replies, mentions, public-chat, group chat all
|
||||
fall back to `BigTextStyle` even though they are conversations that
|
||||
`MessagingStyle` renders far better (avatar + name + threaded messages, and
|
||||
it's what Android promotes into the Conversations section).
|
||||
3. **Media notifications are text-only.** A `PictureEvent` / `VideoEvent`
|
||||
mention says "X mentioned you" with no preview. `BigPictureStyle` should show
|
||||
the actual image.
|
||||
4. **A dozen kinds masquerade as "mentions."** Picture, video (×4), poll, git
|
||||
(×4), highlight, long-form, wiki all route to the identical
|
||||
`notifyMention()` → "X mentioned you". They deserve distinct titles/icons.
|
||||
5. **No aggregation.** The in-app feed rolls up "5 people reacted / zapped your
|
||||
note" into one `MultiSetCard`; the tray posts N separate notifications. This
|
||||
is the biggest source of notification spam.
|
||||
6. **Kind gaps vs. the in-app feed.** The push `when(event)` in
|
||||
`dispatchForAccount` does **not** route `NutzapEvent` (9321), `OnchainZapEvent`
|
||||
(8333), `RepostEvent`/`GenericRepostEvent` (6/16), `BadgeAwardEvent` (8),
|
||||
`LiveActivitiesChatMessageEvent` (1311), or NIP-C7 `ChatEvent` (9) — all of
|
||||
which *do* appear in the in-app Notifications tab. (`NotificationDispatcher`'s
|
||||
`NOTIFICATION_KINDS` also has to grow for these to arrive at all.)
|
||||
7. **Zaps look plain.** A zap — the app's signature interaction — renders as
|
||||
grey `BigTextStyle` text. It should be a colorized gold card with the amount
|
||||
as the hero.
|
||||
|
||||
---
|
||||
|
||||
## 2. Design principles ("beautiful & modern" made concrete)
|
||||
|
||||
Modern Android notification design (Material 3, API 26–35) gives us six levers.
|
||||
The redesign uses all six, per kind:
|
||||
|
||||
- **P1 — Accent color (`setColor`) + status-bar icon per category.** Each
|
||||
category gets a monochrome small icon and a brand-consistent accent so the
|
||||
shade is scannable pre-read. Palette anchored on existing tokens
|
||||
(`Color.kt`): zaps `BitcoinOrange 0xFFF7931A`, reactions a heart-red, replies
|
||||
& mentions amethyst `Purple200/500`, DMs a messaging blue-green, git a slate,
|
||||
badges a gold. Reserve `setColorized(true)` (full-surface color) for the two
|
||||
highest-signal kinds only — **zaps** and **incoming calls** (calls already
|
||||
effectively do this via `CallStyle`).
|
||||
- **P2 — Right style per kind.** `MessagingStyle` for anything conversational
|
||||
(DMs, replies, mentions-that-are-replies, public/group chat); `BigPictureStyle`
|
||||
for media; a colorized `BigTextStyle` "amount card" for zaps/nutzaps;
|
||||
`InboxStyle` for aggregated roll-ups; plain for the rest.
|
||||
- **P3 — Aggregation that mirrors the in-app `MultiSetCard`.** Reactions,
|
||||
reposts, and zaps on the *same target note* collapse into one updating
|
||||
notification: "🤙 Alice, Bob & 3 others liked your post." Reuse the exact
|
||||
bucketing rules from `CardFeedContentState.convertToCard` (per-target,
|
||||
per-day, chunk-of-30) so tray and feed agree.
|
||||
- **P4 — Conversations & People (API 30+).** Publish a dynamic
|
||||
`ShortcutInfoCompat` per chat partner / thread, tag DM & reply notifications
|
||||
with `setShortcutId` + `addPerson`, and attach `BubbleMetadata`. This moves
|
||||
chats into the dedicated Conversations section, enables bubbles, and gives
|
||||
long-press "priority/silence" controls — the single most "modern" upgrade.
|
||||
- **P5 — Circular avatars + emoji/kind badges.** Circle-crop the large-icon
|
||||
avatar (today it's the raw square bitmap); keep the existing NIP-30
|
||||
custom-emoji corner-badge overlay and extend the same overlay helper to stamp
|
||||
a small kind glyph (bolt/heart/reply) so the avatar itself signals the kind.
|
||||
- **P6 — Richer, semantic actions.** Reply (have it), **Mark-read** (have it for
|
||||
DMs — extend to all), plus per-kind: **Like back** / **Zap back** on
|
||||
reaction & zap notifications, **View thread** / **Mute thread** on replies &
|
||||
mentions. All via `SEMANTIC_ACTION_*` so Wear/Auto/Assistant render them
|
||||
correctly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Proposed architecture — a per-kind renderer registry
|
||||
|
||||
Today the logic is split between a large `when(event)` in
|
||||
`EventNotificationConsumer` and six near-duplicate `send*Notification` builders
|
||||
in `NotificationUtils`. We refactor to a **spec + one builder** shape (the same
|
||||
idea the desktop path already uses via `notifKindFor` + `buildSpec` in
|
||||
`DesktopNotificationAutoDispatcher`).
|
||||
|
||||
**New: `NotificationCategory` (enum) —** the visual identity, replacing the six
|
||||
ad-hoc channels. Each carries: channel id/name/importance, accent color,
|
||||
small-icon drawable, default style, and default group key.
|
||||
|
||||
```
|
||||
enum class NotificationCategory {
|
||||
DIRECT_MESSAGE, // DMs, group chat — HIGH, blue-green, MessagingStyle, Conversation
|
||||
REPLY, // replies to me — DEFAULT, purple, MessagingStyle, per-thread group
|
||||
MENTION, // text mention/quote — DEFAULT, purple, plain/MessagingStyle
|
||||
ZAP, // ln + nutzap + onchain — DEFAULT, gold, colorized amount card, aggregated
|
||||
REACTION, // likes/emoji — LOW, heart-red, aggregated InboxStyle
|
||||
REPOST, // boosts — LOW, green, aggregated
|
||||
MEDIA, // picture/video mention — DEFAULT, purple, BigPictureStyle
|
||||
ARTICLE, // long-form/wiki/highlight mention — DEFAULT, purple, plain
|
||||
CODE, // git issue/patch/PR — DEFAULT, slate, plain
|
||||
BADGE, // NIP-58 award — DEFAULT, gold, BigPictureStyle (badge image)
|
||||
CHESS, // game events — DEFAULT, brown, plain
|
||||
// calls keep their own CallNotifier / CallStyle path (already modern)
|
||||
}
|
||||
```
|
||||
|
||||
**New: `NotificationSpec` (data class) —** the fully-resolved, ready-to-render
|
||||
description of one notification, produced per event:
|
||||
|
||||
```
|
||||
data class NotificationSpec(
|
||||
val category: NotificationCategory,
|
||||
val dedupeId: String, // event id (existing id.hashCode() keying)
|
||||
val title: String,
|
||||
val body: String,
|
||||
val timestamp: Long,
|
||||
val author: PersonRef?, // name + avatar url → Person / large icon
|
||||
val bigPictureUrl: String?, // media / badge image
|
||||
val badgeUrl: String?, // NIP-30 emoji / kind glyph overlay
|
||||
val deepLinkUri: String, // existing nostr:...?account=...&scrollTo=...
|
||||
val groupKey: String, // aggregation bucket (per-thread / per-target)
|
||||
val actions: List<NotifAction>,// Reply / ZapBack / Mute / MarkRead …
|
||||
val aggregatable: Boolean, // reactions/zaps/reposts → roll-up
|
||||
)
|
||||
```
|
||||
|
||||
**New: `NotificationSpecFactory` —** one `fun specFor(event, account, ctx):
|
||||
NotificationSpec?` that owns all the per-kind text/style decisions currently
|
||||
scattered across `notify()/notifyReply()/notifyMention()/notifyZap()`. Returns
|
||||
`null` when the event shouldn't notify. This is the single place a future kind
|
||||
gets added.
|
||||
|
||||
**Rewritten: `NotificationUtils.render(spec)` —** one builder that reads the
|
||||
spec, selects the style, applies accent/icon/colorized, attaches actions and
|
||||
Person/shortcut, and handles aggregation + group summaries. The six
|
||||
`send*Notification` functions collapse into this.
|
||||
|
||||
**Kept as-is:** `EventNotificationConsumer.dispatchForAccount` remains the gate
|
||||
(mute/age/self/known-room checks) but its tail becomes
|
||||
`NotificationUtils.render(NotificationSpecFactory.specFor(event, account, ctx))`.
|
||||
|
||||
This keeps the *policy* (who gets notified) exactly where it is and isolates the
|
||||
*presentation* (how it looks) into a testable, table-driven unit.
|
||||
|
||||
---
|
||||
|
||||
## 4. Per-kind visual specification (the heart of the redesign)
|
||||
|
||||
| Kind(s) | Category | Style | Accent / icon | Title → Body | Actions |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| NIP-17 `ChatMessageEvent` (14), file (15), NIP-04 `PrivateDmEvent` (4), Marmot group | DIRECT_MESSAGE | `MessagingStyle` + Conversation shortcut + Bubble | blue-green / `chat` | sender → message | Reply, Mark-read |
|
||||
| `TextNoteEvent`(1)/`CommentEvent`(1111)/`ChannelMessageEvent`(42) **reply to me** | REPLY | `MessagingStyle` (parent as prior message) | purple / `reply` | "X replied" → excerpt (+ quoted parent) | Reply, View thread, Mute thread |
|
||||
| Same kinds, **mention/quote only** | MENTION | plain (BigText) | purple / `alternate_email` | "X mentioned you" → excerpt | View, Mute thread |
|
||||
| `LnZapEvent`(9735) | ZAP | **colorized** amount card (BigText) | gold / `bolt` | "⚡ 2,100 sats from X" → zap comment + zapped-note excerpt | Zap back, View |
|
||||
| `NutzapEvent`(9321) *(new)* | ZAP | colorized amount card | gold / `bolt` (cashu tint) | "🥜 X nutzapped you 2,100 sats" → … | Zap back, View |
|
||||
| `OnchainZapEvent`(8333) *(new)* | ZAP | colorized amount card | gold / `bolt` | "⛓ X sent an onchain zap" → … | View |
|
||||
| `ReactionEvent`(7) | REACTION | aggregated `InboxStyle` | heart-red / `favorite` | "🤙 X & N others liked your post" → note excerpt | Like back, View |
|
||||
| `RepostEvent`(6)/`GenericRepostEvent`(16) *(new)* | REPOST | aggregated | green / `repeat` | "X & N others reposted you" → excerpt | View |
|
||||
| `PictureEvent`(20)/`VideoNormal`/`Short`/`Vertical`/`Horizontal` | MEDIA | `BigPictureStyle` | purple / `image` | "X shared a photo/video" → caption, image inline | View |
|
||||
| `PollEvent`/`ZapPollEvent` | MENTION | plain | purple / `ballot` | "X asked a question" → poll title | Vote, View |
|
||||
| `HighlightEvent`(9802) | ARTICLE | plain | purple / `format_quote` | "X highlighted your article" → highlighted text | View |
|
||||
| `LongTextNoteEvent`(30023)/`WikiNoteEvent`(30818) | ARTICLE | plain | purple / `article` | "X mentioned you in an article" → title | View |
|
||||
| `GitIssueEvent`/`GitPatchEvent`/`GitPullRequestEvent`/`…Update` | CODE | plain | slate / `merge` | "X opened an issue/PR" → subject | View |
|
||||
| `BadgeAwardEvent`(8) *(new)* | BADGE | `BigPictureStyle` (badge art) | gold / `award_star` | "You earned a badge" → badge name, image | View |
|
||||
| `LiveChessGameAcceptEvent`/`LiveChessMoveEvent` | CHESS | plain | brown / `chess` | "X accepted your challenge" / "your turn" | Open board |
|
||||
| `CallOfferEvent` | (unchanged — `CallNotifier`/`CallStyle`) | — | — | — | Accept/Reject |
|
||||
|
||||
Titles/bodies stay in `strings.xml` (translatable) with plurals for the
|
||||
aggregate counts (see §7).
|
||||
|
||||
---
|
||||
|
||||
## 5. Channel restructuring
|
||||
|
||||
Channels are a **user-visible, migration-sensitive** surface (users may have
|
||||
customized importance/sound). Plan:
|
||||
|
||||
- **Keep** the six existing channel IDs (DMs, Zaps, Reactions, Replies,
|
||||
Mentions, Chess) — never rename an ID, it orphans user settings.
|
||||
- **Add** channels for the newly-rendered categories that don't map cleanly onto
|
||||
an existing one: **Reposts** (`RepostsID`, LOW), **Media** (`MediaID`,
|
||||
DEFAULT), **Git/Code** (`CodeID`, DEFAULT), **Badges** (`BadgesID`, DEFAULT).
|
||||
Nutzaps/onchain fold into the existing **Zaps** channel; articles/highlights
|
||||
fold into **Mentions**.
|
||||
- **Group channels** with `NotificationChannelGroup` ("Social", "Payments",
|
||||
"Messages", "Developer") so the system settings page and our in-app
|
||||
`NotificationSettingsScreen` read as organized rather than a flat list of 10.
|
||||
- Register every new channel in `NotificationChannels.contentChannels` (drives
|
||||
the settings UI) with its Material Symbol + name, matching the existing
|
||||
`Entry` pattern.
|
||||
- Reactions/Reposts drop to `IMPORTANCE_LOW` (silent, no heads-up) — they're
|
||||
ambient; zaps/DMs/replies stay DEFAULT/HIGH.
|
||||
|
||||
---
|
||||
|
||||
## 6. Capability upgrades (implementation notes)
|
||||
|
||||
- **Accent + icon:** add monochrome vector status-bar icons under
|
||||
`amethyst/src/main/res/drawable/` (they must be white-on-transparent per
|
||||
Android's small-icon rules — *not* the app logo). `builder.setColor(accent)`;
|
||||
`setColorized(true)` only for ZAP + CALL. Store accent per category on the
|
||||
enum.
|
||||
- **MessagingStyle for replies/mentions/chat:** generalize the existing
|
||||
`sendDMNotificationStyled` `MessagingStyle` construction into
|
||||
`render()` for every conversational category. For replies, add the parent note
|
||||
as an earlier `addMessage(...)` from the original author so the thread shows
|
||||
context. Reuse the existing `Person` + avatar `IconCompat` code.
|
||||
- **BigPictureStyle:** in `render()`, when `spec.bigPictureUrl != null`, load it
|
||||
via the existing Coil `loadBitmap` helper and
|
||||
`NotificationCompat.BigPictureStyle().bigPicture(bmp)` with the avatar as
|
||||
large icon; collapse to `BigTextStyle` if the image fails to load.
|
||||
- **Aggregation:** introduce a small `NotificationAggregator` keyed by
|
||||
`groupKey` (per-target-note for reactions/zaps/reposts). On each new event it
|
||||
reads `activeNotifications` for the group, recomputes the roll-up
|
||||
(participants + count), and re-`notify()`s the same id with an updated
|
||||
`InboxStyle`/title — mirroring `CardFeedContentState`'s per-target buckets.
|
||||
Falls back to a single notification below the threshold. Keeps the existing
|
||||
`sendGroupSummary` behavior for cross-target bundling.
|
||||
- **Circular avatars:** add a `circleCrop(bitmap)` step in `loadBitmap` (reuse
|
||||
the `Canvas` approach already in `overlayBadge`). Kind-glyph overlay reuses
|
||||
`overlayBadge`.
|
||||
- **Conversations/Bubbles:** publish `ShortcutInfoCompat` (long-lived, with the
|
||||
partner `Person`) when a DM/reply notification posts; set `shortcutId`,
|
||||
`addPerson`, and `BubbleMetadata` pointing at a
|
||||
`MainActivity`/`CallActivity`-style chat entry. Guard by API level.
|
||||
- **Dedup / dismiss-on-read:** unchanged (`isDuplicate`,
|
||||
`dismissNotificationForEvent` by `eventId.hashCode()`); aggregation reuses a
|
||||
stable per-group id so updates replace rather than stack.
|
||||
|
||||
---
|
||||
|
||||
## 7. Icons & strings work (required side-tasks)
|
||||
|
||||
- **Icons (two kinds):**
|
||||
1. **Status-bar small icons** are Android drawables (white glyphs) — new
|
||||
vector XML in `res/drawable/`. *Not* Material Symbols font glyphs.
|
||||
2. **Settings-list icons** in `NotificationChannels` use `MaterialSymbols`. If
|
||||
any new `MaterialSymbol("\uXXXX")` codepoint is introduced (e.g. `Repeat`,
|
||||
`Merge`, `AwardStar`, `Ballot`, `Image`) that isn't already referenced,
|
||||
**regenerate the subset font**: `./tools/material-symbols-subset/subset.sh`
|
||||
and commit `material_symbols_outlined.ttf` — otherwise it renders as tofu.
|
||||
- **Strings:** new titles per kind (repost, media, badge, poll, highlight, git,
|
||||
nutzap, onchain) as translatable resources; aggregate counts become
|
||||
`<plurals>` (`"%1$s & %2$d others liked your post"`) with the CLDR-category
|
||||
discipline from `res/CLAUDE.md` (Slavic/Baltic/Arabic `few`/`many`/`zero`).
|
||||
Use the `pluralStringRes(ctx, …)` helper (non-Compose) in the service path.
|
||||
|
||||
---
|
||||
|
||||
## 8. Parity gaps to close (behavioral, not just visual)
|
||||
|
||||
For these to *render*, two lists must include the kind **and**
|
||||
`NotificationSpecFactory` must handle it:
|
||||
|
||||
- `NotificationDispatcher.NOTIFICATION_KINDS` (Android gate) — add
|
||||
`NutzapEvent`, `OnchainZapEvent`, `RepostEvent`, `GenericRepostEvent`,
|
||||
`BadgeAwardEvent`, `LiveActivitiesChatMessageEvent`, NIP-C7 `ChatEvent`.
|
||||
- `commons/.../NotificationKinds.SUBSCRIPTION_KINDS` (relay REQ list) — must
|
||||
already carry them so they arrive; verify against
|
||||
`NotificationKindsContractTest` and extend if needed.
|
||||
- Keep the in-app `NotificationFeedFilter.NOTIFICATION_KINDS` and the push path
|
||||
in lockstep — the contract test should assert push-rendered ⊆ in-app-shown.
|
||||
|
||||
Decision needed (flagged for the maintainer, §11): reposts/reactions are
|
||||
*aggregate-only* by design — confirm we want them in the tray at all (many users
|
||||
consider like/repost notifications noise; hence `IMPORTANCE_LOW` + off-by-default
|
||||
mirroring the desktop `KindToggles` where reaction/repost default off).
|
||||
|
||||
---
|
||||
|
||||
## 9. Phased implementation
|
||||
|
||||
- **Phase 0 — Refactor to spec+builder (no visual change).** Introduce
|
||||
`NotificationCategory`, `NotificationSpec`, `NotificationSpecFactory`,
|
||||
`NotificationUtils.render(spec)`; port the six existing kinds onto it. Prove
|
||||
parity with existing behavior via unit tests. *No user-visible diff.*
|
||||
- **Phase 1 — Color & icons.** Accent + status-bar icon per category, circular
|
||||
avatars, colorized zap card. Highest visual ROI, lowest risk.
|
||||
- **Phase 2 — Styles.** MessagingStyle for replies/mentions/chat;
|
||||
BigPictureStyle for media & badges.
|
||||
- **Phase 3 — Aggregation.** Roll-up for reactions/reposts/zaps via
|
||||
`NotificationAggregator`; new Reposts channel; LOW importance for ambient
|
||||
kinds.
|
||||
- **Phase 4 — Parity kinds.** Route nutzap, onchain zap, repost, badge, live
|
||||
chat; grow the dispatcher/subscription kind lists; strings + plurals.
|
||||
- **Phase 5 — Conversations/Bubbles/People + richer actions.** Shortcuts,
|
||||
bubbles, Zap-back / Like-back / Mute-thread actions.
|
||||
- **Phase 6 — Channel groups + settings polish.** `NotificationChannelGroup`s;
|
||||
update `NotificationSettingsScreen`.
|
||||
|
||||
Each phase is independently shippable and revertible.
|
||||
|
||||
---
|
||||
|
||||
## 10. Testing
|
||||
|
||||
- **JVM unit tests** for `NotificationSpecFactory.specFor(...)` — table-driven,
|
||||
one fixture event per kind asserting category/title/body/style/actions. This
|
||||
is the regression net that lets the six-kind refactor land safely.
|
||||
- **Contract test** extending `NotificationKindsContractTest`: every kind the
|
||||
factory produces a spec for is in `NOTIFICATION_KINDS` and `SUBSCRIPTION_KINDS`.
|
||||
- **Aggregation tests:** feed 1 → 5 reactions on one note, assert a single
|
||||
updating notification with correct count/participants (mirror
|
||||
`CardFeedContentState` test fixtures if they exist).
|
||||
- **Manual matrix** on Android 12 / 14 / 15 (and one heavily-skinned OEM):
|
||||
screenshot each category light+dark, verify color/icon/heads-up/lockscreen
|
||||
(public version) and Conversation-section placement for DMs.
|
||||
- **`amy`**: no CLI surface for tray rendering, but the spec factory is pure
|
||||
JVM and can be exercised headless.
|
||||
|
||||
---
|
||||
|
||||
## 11. Risks & decisions for the maintainer
|
||||
|
||||
- **Notification volume.** Aggregation + LOW-importance reactions/reposts are
|
||||
the mitigation; still, **confirm** reactions/reposts belong in the tray
|
||||
(default-off recommended, matching desktop).
|
||||
- **Small-icon assets.** Must be true monochrome white glyphs; the current
|
||||
colored amethyst logo is *not* a valid small icon and is the reason the shade
|
||||
looks flat today. Needs new drawables.
|
||||
- **Channel proliferation.** Ten channels risks a cluttered settings page —
|
||||
hence `NotificationChannelGroup`s; alternatively keep the six and only vary
|
||||
color/icon/style within them (color/style don't require a new channel; only
|
||||
independent user importance/sound control does). *Recommend: add only
|
||||
Reposts + Media + Badges channels, fold the rest.*
|
||||
- **Font subset drift.** Any new Material Symbol must trigger `subset.sh` or it
|
||||
renders as tofu — easy to forget; call it out in the PR checklist.
|
||||
- **Marmot/encrypted content.** MessagingStyle/Bubbles must not leak decrypted
|
||||
DM/group text to the lockscreen public version — the existing
|
||||
`setPublicVersion` "New notification arrived" placeholder must be preserved for
|
||||
all private categories.
|
||||
|
||||
---
|
||||
|
||||
## Survey (existing components reused vs. new)
|
||||
|
||||
- **Reused as-is:** the entire transport + gate layer (`PushWrapDecryptor`,
|
||||
`NotificationDispatcher` gates, `EventNotificationConsumer` mute/age/self/
|
||||
known-room checks), Coil `loadBitmap`, `overlayBadge`, `isDuplicate`,
|
||||
`dismissNotificationForEvent`, `NotificationReplyReceiver` (extended for
|
||||
new actions), `NotificationChannels`/`NotificationSettingsScreen` structure.
|
||||
- **Extracted/generalized:** the `MessagingStyle` construction from
|
||||
`sendDMNotificationStyled` becomes the shared conversational renderer; the six
|
||||
`send*Notification` builders collapse into `render(spec)`.
|
||||
- **Genuinely new (Android-only, correct module):** `NotificationCategory`,
|
||||
`NotificationSpec`, `NotificationSpecFactory`, `NotificationAggregator`,
|
||||
status-bar drawables, channel groups, shortcut/bubble publishing.
|
||||
- **Aggregation logic** is a port of `CardFeedContentState.convertToCard`
|
||||
bucketing — same rules, tray output instead of `Card`s. Consider extracting
|
||||
the bucketing to `commons` so feed and tray share one implementation.
|
||||
@@ -10,6 +10,7 @@ _Audited 2026-06-30. 21 plans: 19 shipped (archived), 1 in-progress, 1 queued, 0
|
||||
## Queued
|
||||
| Plan | Summary |
|
||||
| ---- | ------- |
|
||||
| [2026-07-23-push-notification-redesign.md](2026-07-23-push-notification-redesign.md) | Per-kind tray notification redesign — accent colors, status-bar icons, MessagingStyle/BigPictureStyle/colorized zap cards, aggregation, Conversations/Bubbles; closes nutzap/onchain/repost/badge parity gaps. |
|
||||
| [2026-06-20-napplet-inter-applet.md](2026-06-20-napplet-inter-applet.md) | NAP-INC / NAP-INTENT inter-applet messaging — deferred; prerequisites (multi-applet hosting, archetype registry, `MESSAGING` capability) not yet built. |
|
||||
|
||||
## Archived (shipped)
|
||||
|
||||
Reference in New Issue
Block a user