# Design: Notification Visual Polish ## Technical Approach Presentation-only wiring. Add one hand-authored monochrome `` drawable (`ic_stat_pluriwave.xml`, Material `graphic_eq` equalizer glyph, Apache-2.0) at `res/drawable/`, then reference it from all 4 notification builders. On the 3 Kotlin alarm builders, swap the generic `android.R.drawable.*` small icon for `R.drawable.ic_stat_pluriwave` and add `.setColor(brand cyan)`. On the Dart audio builder, set `androidNotificationIcon` to a named const. No spec-level alarm behavior changes; maps directly to the proposal's "single legible on-brand identity" intent. ## Architecture Decisions | Decision | Choice | Alternatives rejected | Rationale | |----------|--------|-----------------------|-----------| | Icon asset format | Single `` XML in `res/drawable/` | Multi-density mipmap PNGs; trace from existing PNG sheet | Vector is density-independent (one file, no `-hdpi/-xxhdpi` variants); no image tooling exists in this env; PNG tracing impractical | | Glyph | Material `graphic_eq` (5 equalizer bars, white fill) | Bespoke soundwave/concentric-arc | Apache-2.0 licensed, path data publicly known and verified well-formed in 24x24, bold bars legible at status-bar size | | Kotlin color constant location | New `object NotificationBrand` in package `es.freetimelab.pluriwave`, `@ColorInt const val CYAN` | 3x duplicated `Color.parseColor("#21D4D9")` literals; per-file companion const; XML color resource | Mirrors the established `AlarmNotificationStrings` singleton precedent (shared native notification constants in one package object). Single source of truth, zero duplication, compile-time int (no `parseColor` runtime cost) | | Same color across all 3 alarm builders | Single shared cyan `#21D4D9` | Coral for fire/ringing | Pre-notice + snooze already share one channel; introducing coral adds a second "meaning" needing design sign-off (out of scope); cyan matches shipped audio notification | | `R` class access in Kotlin | Reference `R.drawable.ic_stat_pluriwave` with NO new import | `import es.freetimelab.pluriwave.R` | All 3 files are in package `es.freetimelab.pluriwave`; the generated `R` is same-package, resolvable unqualified | | Dart icon value | Named top-level const `androidNotificationIconResource = 'drawable/ic_stat_pluriwave'` | Inline string literal | Matches existing `notificationColor: PluriWaveTokens.brand` named-value pattern; lets `notification_color_test.dart` assert it without a widget pump | ## Data Flow Notification build (native, per builder): ensureChannel ─→ NotificationCompat.Builder(ctx, CHANNEL_ID) .setSmallIcon(R.drawable.ic_stat_pluriwave) ← vector .setColor(NotificationBrand.CYAN) ← cyan tint .build() ─→ NotificationManagerCompat.notify(...) Dart audio: `configuracionAudioService` (with `androidNotificationIcon`) → `AudioService.init` → `AudioService.java` splits `"drawable/ic_stat_pluriwave"` on `/`, resolves via `getIdentifier("ic_stat_pluriwave","drawable",pkg)` → same vector drawable. ## File Changes | File | Action | Description | |------|--------|-------------| | `android/app/src/main/res/drawable/ic_stat_pluriwave.xml` | Create | Monochrome 24x24 vector, `graphic_eq` path, `#FFFFFF` fill | | `android/app/src/main/kotlin/es/freetimelab/pluriwave/NotificationBrand.kt` | Create | `object NotificationBrand { const val CYAN = 0xFF21D4D9.toInt() }` | | `.../PluriWaveAlarmReceiver.kt` | Modify | L184 `setSmallIcon(android.R.drawable.ic_dialog_info)` → `setSmallIcon(R.drawable.ic_stat_pluriwave)`; insert `.setColor(NotificationBrand.CYAN)` | | `.../AlarmScheduler.kt` | Modify | L580 `setSmallIcon(android.R.drawable.ic_lock_idle_alarm)` → `setSmallIcon(R.drawable.ic_stat_pluriwave)`; insert `.setColor(NotificationBrand.CYAN)` | | `.../PluriWaveAlarmService.kt` | Modify | L390 `setSmallIcon(android.R.drawable.ic_lock_idle_alarm)` → `setSmallIcon(R.drawable.ic_stat_pluriwave)`; insert `.setColor(NotificationBrand.CYAN)` | | `lib/main.dart` | Modify | Add `androidNotificationIconResource` const; add `androidNotificationIcon: androidNotificationIconResource` to `configuracionAudioService` | | `test/tema/notification_color_test.dart` | Modify | Add test asserting `androidNotificationIcon == 'drawable/ic_stat_pluriwave'` | ## Interfaces / Contracts Exact `ic_stat_pluriwave.xml` content (verified well-formed, 24x24 Material `graphic_eq`): `NotificationBrand.kt`: package es.freetimelab.pluriwave import androidx.annotation.ColorInt object NotificationBrand { @ColorInt const val CYAN: Int = 0xFF21D4D9.toInt() } Kotlin builder edit shape (each file, `.setColor` placed right after `.setSmallIcon`): .setSmallIcon(R.drawable.ic_stat_pluriwave) .setColor(NotificationBrand.CYAN) Dart (`lib/main.dart`), const above `configuracionAudioService` + one added field: const androidNotificationIconResource = 'drawable/ic_stat_pluriwave'; // ...inside AudioServiceConfig(...): androidNotificationIcon: androidNotificationIconResource, ## Testing Strategy | Layer | What to Test | Approach | |-------|-------------|----------| | Unit (Dart) | `androidNotificationIcon == 'drawable/ic_stat_pluriwave'` | Extend `test/tema/notification_color_test.dart`, same const-assertion style; `flutter test` | | Static | `flutter analyze` clean; XML/Kotlin compile | analyzer + build sanity | | Manual/on-device | Small icon legible in status bar; cyan tint applied on all 4 notifications | On-device QA (no native test harness exists) | Exact test to add: test('AudioServiceConfig usa el icono monocromo de marca', () { expect( configuracionAudioService.androidNotificationIcon, 'drawable/ic_stat_pluriwave', ); }); ## Migration / Rollout No migration required. Additive vector + new `object` file; call-site edits are reversible via `git revert`. No persisted state, schema, or channel changes. ## Open Questions - [ ] `NotificationCompat.setColor()` is advisory — final tint fidelity across Android versions confirmed only by on-device QA (accepted, not blocking).