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.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user