Files
pluriwave/openspec/changes/notification-visual-polish/design.md
T
FreeTLab bccc5c48b8
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
docs(openspec): add SDD artifact trail for recent alarm and EQ changes
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.
2026-07-04 12:42:11 +02:00

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.initAudioService.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).