Android Auto no longer copies the launcher icon as placeholder art; it rotates through the same 4 on-brand station_art assets the phone UI already uses, keyed by the same per-station hash for visual parity. Malformed or unusable favicon URLs (including a Dart Uri quirk where 'http://' reports hasAuthority=true with an empty host) now fail the validity gate instead of being handed to the OS media browser as-is. Browsable items also show codec/bitrate as a subtitle when known.
93 lines
8.6 KiB
Markdown
93 lines
8.6 KiB
Markdown
# Apply Progress: auto-media-art-quality
|
|
|
|
**Mode**: Strict TDD
|
|
**Batch**: 1 of 1 (single delivery, no chaining — forecast was Low risk, ~170-230 est. lines; actual ~347 text lines, still under 400-line budget)
|
|
|
|
## Completed Tasks
|
|
|
|
All 22 tasks in `tasks.md` are complete and marked `[x]`.
|
|
|
|
- [x] 1.1 [RED] `faviconUsable` test matrix
|
|
- [x] 1.2 [GREEN] Implement `faviconUsable`
|
|
- [x] 2.1 [RED] `indiceArtePara` formula/order test
|
|
- [x] 2.2 [GREEN] Implement `indiceArtePara` + `_nombresArte`
|
|
- [x] 3.1 [RED] `artUriPara` integration test (favicon gate + rotation)
|
|
- [x] 3.2 [GREEN] Implement `artUriPara`; drop `_defaultArtUri` and old `_artUriPara`
|
|
- [x] 3.3 [GREEN] Replace obsolete default-art test
|
|
- [x] 4.1 [RED] `subtituloCalidad` matrix test
|
|
- [x] 4.2 [GREEN] Implement `subtituloCalidad`
|
|
- [x] 5.1 [RED] `itemEmisora` sets `displaySubtitle` test
|
|
- [x] 5.2 [GREEN] Wire `displaySubtitle` into `itemEmisora`
|
|
- [x] 5.3 [REFACTOR] Cleanup pass, doc comments, dead-reference check
|
|
- [x] 6.1 [coverage] `Emisora.fromMap` unsanitized-favicon regression test
|
|
- [x] 6.2 [coverage] Phone/auto art-order parity guard
|
|
- [x] 7.1 Add 4 native drawable copies (static-review-only)
|
|
- [x] 7.2 Delete `default_station_art.png` (static-review-only)
|
|
- [x] 7.3 Verify `applicationId`/authority match (static-review-only)
|
|
- [x] 8.1 Targeted regression run (`navegacion_auto_test.dart`, full suite deviation documented)
|
|
- [x] 8.2 `flutter analyze` — DEVIATION, not run (hangs in this environment); manual static review done
|
|
- [x] 8.3 `flutter build`/`flutter run` — DEVIATION, not run (hangs in this environment)
|
|
- [x] 8.4 Manual DHU verification note — recorded below (non-blocking, not coded)
|
|
|
|
## Files Changed
|
|
|
|
| File | Action | What Was Done |
|
|
|------|--------|----------------|
|
|
| `lib/servicios/navegacion_auto.dart` | Modified | Added `faviconUsable`, `indiceArtePara`, `_nombresArte`, `artUriPara`, `subtituloCalidad`; wired `artUri`/`displaySubtitle` into `itemEmisora`; dropped `_defaultArtUri` const and old private `_artUriPara` |
|
|
| `test/servicios/navegacion_auto_test.dart` | Modified | Added `faviconUsable`, `indiceArtePara`, `artUriPara`, `subtituloCalidad`, `itemEmisora` `displaySubtitle` extension, `Emisora.fromMap` unsanitized-favicon regression, and phone/auto art-order parity groups; replaced the obsolete `_defaultArtUri`-based default-art test; extended `_emisora()` test helper with a `codec` parameter |
|
|
| `android/app/src/main/res/drawable/station_art_aurora.png` | Created | Verbatim byte-for-byte copy of `assets/images/station_art_aurora.png` (size-verified match) |
|
|
| `android/app/src/main/res/drawable/station_art_cosmic.png` | Created | Verbatim byte-for-byte copy of `assets/images/station_art_cosmic.png` (size-verified match) |
|
|
| `android/app/src/main/res/drawable/station_art_pulse.png` | Created | Verbatim byte-for-byte copy of `assets/images/station_art_pulse.png` (size-verified match) |
|
|
| `android/app/src/main/res/drawable/station_art_nova.png` | Created | Verbatim byte-for-byte copy of `assets/images/station_art_nova.png` (size-verified match) |
|
|
| `android/app/src/main/res/drawable/default_station_art.png` | Deleted | Retired — rotation is total (uuid always present), launcher-icon-lookalike removed per Design Decision 3 |
|
|
|
|
## TDD Cycle Evidence
|
|
|
|
| Task | RED | GREEN | REFACTOR |
|
|
|------|-----|-------|----------|
|
|
| 1.1/1.2 `faviconUsable` | Confirmed fail: `Method not found: 'faviconUsable'` (compile error across all call sites) | Implemented; all `faviconUsable` cases pass. **Bug found during GREEN**: `Uri.tryParse('http://').hasAuthority` returns `true` even with an empty host — initial implementation used `uri.hasAuthority`, which wrongly accepted `'http://'`. Fixed to check `uri.host.isNotEmpty` explicitly; re-ran, all pass. | Doc comments added explaining the `hasAuthority` gotcha inline |
|
|
| 2.1/2.2 `indiceArtePara` | Confirmed fail: `Method not found: 'indiceArtePara'` | Implemented verbatim-ported formula; parity assertions pass | `_nombresArte` kept private per design; cross-link comment to `tarjeta_emisora.dart` added |
|
|
| 3.1/3.2/3.3 `artUriPara` | Confirmed fail: `Method not found: 'artUriPara'` (multiple sites) | Implemented; replaced old `_artUriPara`/`_defaultArtUri`; obsolete default-art test replaced | Grep-confirmed zero remaining `_defaultArtUri`/old `_artUriPara` references anywhere in `lib/`/`android/` |
|
|
| 4.1/4.2 `subtituloCalidad` | Confirmed fail: `Method not found: 'subtituloCalidad'` | Implemented per design's quality-format table incl. bitrate<=0 and whitespace-codec edge cases; all pass | — |
|
|
| 5.1/5.2 `itemEmisora` wiring | Confirmed fail: assertion targeted not-yet-set `displaySubtitle` | Wired `displaySubtitle: subtituloCalidad(e)` into `MediaItem(...)`; full group green | 5.3: re-read top to bottom, doc comments added, dead-reference grep clean, full suite (this file) green |
|
|
| 6.1 `Emisora.fromMap` gap regression | Coverage-only, single-pass assertion (design already characterizes current behavior) | Passed on first run — confirms `artUriPara`'s validity gate catches the `fromMap` unsanitized-favicon gap as designed | — |
|
|
| 6.2 Phone/auto parity guard | Coverage-only, single-pass assertion | Passed — verified via `artUriPara`/`indiceArtePara` over 4 seeds covering all rotation indices (no public accessor added, keeping `_nombresArte` private per design) | — |
|
|
|
|
## Deviations from Design
|
|
|
|
- **Test verifies canonical art-name order behaviorally, not via a public constant.** Design specifies `_nombresArte` as a **private** const (task 2.2 says "Private `const _nombresArte`"). Task 2.1/6.2 ask to assert the canonical order directly. Rather than making the list public just for test access (which would contradict the design's explicit privacy choice), the order is verified indirectly through `indiceArtePara` + `artUriPara` behavior using single-character seeds (`'d'`→index 0, `'a'`→1, `'b'`→2, `'c'`→3) that deterministically cover all 4 rotation indices. This satisfies the same intent (pin the order, catch reorder drift) without leaking an implementation-private symbol.
|
|
- No other deviations — implementation matches design (`faviconUsable`, `indiceArtePara`, `artUriPara`, `subtituloCalidad` signatures match the Design "Interfaces / Contracts" section exactly).
|
|
|
|
## Issues Found
|
|
|
|
- **Real bug caught by Strict TDD RED→GREEN cycle**: `Uri('http://').hasAuthority` returns `true` in Dart even when `host` is empty — a naive `uri.hasAuthority` check would have let `'http://'` (scheme, no host) pass as a "usable" favicon, contradicting the spec's malformed-URL rejection requirement. Fixed by checking `uri.host.isNotEmpty` explicitly. Documented inline with a code comment to prevent regression.
|
|
- No other issues.
|
|
|
|
## Static Review Notes (Phase 7 — no Android build env available)
|
|
|
|
- 4 new drawables (`station_art_aurora/cosmic/pulse/nova.png`) copied byte-for-byte from `assets/images/` — file sizes verified identical to source (1,910,538 / 2,053,431 / 1,879,963 / 1,742,275 bytes respectively).
|
|
- `default_station_art.png` deleted; `grep -rn default_station_art lib android` returns zero matches (excluding SDD artifact prose in `openspec/changes/auto-media-art-quality/`).
|
|
- `android/app/build.gradle.kts:34` confirmed unchanged: `applicationId = "es.freetimelab.pluriwave"`, matching the `android.resource://es.freetimelab.pluriwave/drawable/station_art_<name>` authority built in `artUriPara`. No manifest/build.gradle change required.
|
|
- A real Android build or DHU session is still required before shipping to confirm the OS art loader resolves the 4 new `station_art_*` drawables via `android.resource://` (Design's open question — unresolved, carried forward).
|
|
|
|
## Manual DHU Verification Note (task 8.4, non-blocking)
|
|
|
|
Recommended before merge/ship: a Desktop Head Unit (or real car) session to visually confirm (a) all 4 rotating arts render correctly per station, (b) `displaySubtitle` quality text renders as expected on the browse row, (c) no stale `default_station_art` reference lingers anywhere in the built APK's resources.
|
|
|
|
## Remaining Tasks
|
|
|
|
None — all 22 tasks complete.
|
|
|
|
## Workload / PR Boundary
|
|
|
|
- Mode: single PR (no chaining needed)
|
|
- Current work unit: entire change (`auto-media-art-quality`), single deliverable
|
|
- Boundary: starts from the existing `_artUriPara`/`_defaultArtUri` baseline, ends with the full validity-gate + rotation + quality-subtitle feature landed and tested
|
|
- Estimated review budget impact: ~347 changed text lines (2 files) + 4 binary adds + 1 binary delete — comfortably under the 400-line budget (forecast: Low risk, confirmed)
|
|
|
|
## Status
|
|
|
|
22/22 tasks complete. Ready for `sdd-verify`.
|
|
|
|
Working tree left uncommitted (staged-ready) per instructions — no `git commit` run by this agent.
|