Persist the exploration, proposal, spec, design, tasks, and verify/archive reports produced during the multi-device EQ, alarm-countdown, and notification-visual-polish SDD cycles.
6.5 KiB
Design: Notification Visual Polish
Technical Approach
Presentation-only wiring. Add one hand-authored monochrome <vector> 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 <vector> 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):
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M7,18h2L9,6L7,6v12zM3,14h2v-4L3,10v4zM11,20h2L13,4h-2v16zM19,10v4h2v-4h-2zM15,18h2L17,6h-2v12z" />
</vector>
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).