Merges its delta requirements into the android-auto-media base spec. This closes out Phase-1 polish for local music; Phase 2 (metadata, sort/filter, real art) and Phase 3 (subfolder scoping, shuffle) remain the only planned future work for this feature.
8.6 KiB
8.6 KiB
Tasks: Android Auto Local Music — On-Demand Paging + Friendly Folder Name
Review Workload Forecast
| Field | Value |
|---|---|
| Estimated changed lines | ~480-600 (prod ~170: navegacion_auto.dart ~120, musica_local_auto.dart ~45, pantalla_ajustes.dart ~8; l10n ~26 across 13 arb; tests ~300-350: paging/id/Más/call-count/hijosMusicaLocal + friendly-name + 8 call-site updates) |
| 400-line budget risk | Medium |
| Chained PRs recommended | Yes |
| Suggested split | PR 1 → PR 2 → PR 3 (each independently <400 lines) |
| Delivery strategy | single PR with size:exception (user explicitly overrode the chained-PR recommendation) |
| Chain strategy | n/a — single PR |
Decision needed before apply: Resolved — single PR, size:exception.
Chained PRs recommended: Yes (not taken; user chose single-PR exception)
400-line budget risk: Medium (accepted)
Suggested Work Units
| Unit | Goal | Likely PR | Notes |
|---|---|---|---|
| 1 | paginaDe<T>/hayPaginaSiguiente + carpeta_local_pag: id encode/decode, fully unit-tested |
PR 1 | Foundation; no behavior change yet; ~200 lines |
| 2 | itemsLocales paged rewrite + "Más…" item + hijosMusicaLocal paged branch, call-count/boundary tests |
PR 2 | Depends on PR 1; user-visible paging behavior; ~250 lines |
| 3 | nombreCarpetaDesdeUri + 13-locale localMusicFolderGenericName + settings UI wiring |
PR 3 | Independent of PR 1/2; ~150 lines |
Implemented as a single PR per delivery strategy — all 3 units applied in one pass, work-unit commit boundaries preserved conceptually in the diff structure (paging primitives → paged id scheme → itemsLocales/hijosMusicaLocal → friendly name → l10n → settings UI).
Phase 1: Paging Primitives (lib/servicios/navegacion_auto.dart)
- 1.1 RED: test
paginaDe<T>— mid-list slice, empty list, page beyond range, partial last page (spec: no permanent unreachability) - 1.2 GREEN: implement top-level
paginaDe<T>(skip/take, generic per ADR-6) - 1.3 RED: test
hayPaginaSiguienteexact boundary (total == (pagina+1)*tamano→ false;>→ true) - 1.4 GREEN: implement top-level
hayPaginaSiguiente
Phase 2: Paged Media-ID Scheme
- 2.1 RED: test
esCarpetaLocalPaginadaMediaId/paginaCarpetaLocalDesde— collision-free vsemisora:/grupo:/eq_preset:/carpeta_local:/pista:+ bare root ids; docId containing://; empty-tail root page (carpeta_local_pag:1:) - 2.2 GREEN: implement
_prefijoCarpetaLocalPaginada,esCarpetaLocalPaginadaMediaId,paginaCarpetaLocalDesde(strip-by-length, split-on-first-:, per ADR-1)
Phase 3: Paged itemsLocales + "Más…" Item
- 3.1 RED: test call-count invariant via
@visibleForTesting construirItemspy — 200NodoLocals, page 0/1/3 each call spy exactlymin(50, remaining)times (ADR-3 regression guard) - 3.2 RED: test "Más…" boundary — exactly 50 (no "Más…"), 51-100 (one "Más…", then last page none), 100+ chains 3+ pages, all items reached
- 3.3 RED: test "Más…" item shape (id
carpeta_local_pag:<n+1>:<docId>,title='Más…',playable:false, noartUri, round-trips viapaginaCarpetaLocalDesde); root paging (documentIdPadre:''); stale page beyond range →[], no "Más…", no throw - 3.4 GREEN: extend
itemsLocales(nodos, {required documentIdPadre, pagina=0, tamano=_maxItemsCarpetaLocal, construirItem})— sort full list,paginaDethe cheap list, map only the slice, append_itemMasLocalviahayPaginaSiguiente; add_tituloMasLocal='Más…',_itemMasLocal(hardcoded Spanish per ADR-5 — no arb key) - 3.5 Update existing
itemsLocales(nodos)call sites intest/servicios/navegacion_auto_test.dart(~lines 384-484) to passdocumentIdPadre— mechanical signature adaptation. One pre-existing 60-item test asserted the OLD cap-truncate behavior (exactly 50 items, no more) — this is superseded by the spec's paging requirement (Modified Requirement "Local Music Folder Item Cap and Paging"), so that specific assertion was deliberately rewritten (not just signature-patched) into two new boundary tests (exactamente 50 items,51 a 100 items) that assert the NEW paging behavior; every other call site kept identical assertions with onlydocumentIdPadreadded.
Phase 4: Wire Paged ID into hijosMusicaLocal
- 4.1 RED: test
hijosMusicaLocalroutescarpeta_local_pag:<n>:<docId>to(docId, n)and returns that page; non-local id stillnull;nullfuente →[]; thrown error →[] - 4.2 GREEN: add paged branch inside
hijosMusicaLocal(ADR-7 —servicio_audio.dartunchanged;getChildren/playFromMediaIdneed no edit)
Phase 5: Friendly Folder Name (lib/servicios/musica_local_auto.dart)
- 5.1 RED: test
nombreCarpetaDesdeUri— internal storage (primary:Music/MyFolder→MyFolder), SD card (1A2B-3C4D:Music→Music), nested/-segment, storage-root empty tail (primary:)→generic fallback, unparseable→generic fallback, never raw URI/empty (ADR-4) - 5.2 GREEN: implement pure
nombreCarpetaDesdeUri(String, {required String nombreGenerico}) -> String. Signature refinement vs. the terse task description (mirrors the same abbreviation gap task 3.4 had foritemsLocales): the generic-fallback text must be the LOCALIZEDlocalMusicFolderGenericNamestring per ADR-4/ADR-5, but this function has noBuildContext/AppLocalizationsaccess by design (pure, unit-testable without a widget tree). So the fallback text is threaded in as a plainString nombreGenericoparameter — the call site (pantalla_ajustes.dart, which already holdsl10n) suppliesl10n.localMusicFolderGenericName. Precedent:pantalla_reproductor.dart's_formatearDuracion(AppLocalizations l10n, ...)pattern, minus the Flutter-generated-class coupling.
Phase 6: Localization
- 6.1 Added
localMusicFolderGenericNamekey (phone UI only, per ADR-5) to all 13lib/l10n/app_{ar,bn,de,en,es,fr,hi,id,it,ja,pt,ru,zh}.arb— no "Más…" arb key anywhere. Values matched each locale's existinglocalMusicFolder*/localMusicChoosePathregister precedent (e.g. es "Carpeta seleccionada", en "Selected folder", de "Ausgewählter Ordner", fr "Dossier sélectionné", ar "مجلد محدد", ja "選択したフォルダー", zh "已选文件夹", etc).
Phase 7: Settings UI Wiring
- 7.1
_SeccionMusicaLocal(lib/pantallas/pantalla_ajustes.dart~line 361): rendersnombreCarpetaDesdeUri(carpeta, nombreGenerico: l10n.localMusicFolderGenericName)instead of rawcarpetawhen configured; unconfigured case (null/empty) keepsl10n.localMusicFolderNotConfigured. Widget test added intest/pantallas/pantalla_ajustes_test.dart(group "_SeccionMusicaLocal— friendly folder name (Phase 7)") but COULD NOT be executed in this sandbox — see Known Deviations below; this is a pre-existing environment gap, not introduced by this change.
Phase 8: Regression / Manual Follow-Up
- 8.1 Confirmed
esArchivoAudiotests (musica_local_auto_test.dart, 10/10 pass) andraiz()/esCarpetaLocalMediaId/idCarpetaLocalDesde/plaincarpeta_local:hijosMusicaLocaltests (navegacion_auto_test.dart, 85/85 pass) pass with no behavior change — this change only extendsitemsLocalesinternals and adds a new branch, root-folder-count/dispatch logic untouched. - 8.2 DEVIATED (manual follow-up, not executable here):
flutter analyze,flutter test(full suite),flutter gen-l10n— run manually, same convention as prior changes.
Known Deviations (apply-time)
pantalla_ajustes_test.dartPhase 7 widget tests could not be run in this sandbox. The trackedlib/l10n/gen/app_localizations.dartis stale — it predates even the PARENT local-music change (missinglocalMusicFolderNotConfigured,localMusicSectionTitle, etc., which the file already referenced before this apply pass touched anything). Regenerating it requiresflutter gen-l10n, which is documented to hang in this environment and was explicitly out of scope for this apply run. This is a pre-existing gap, confirmed by compiler errors pointing exclusively at PRE-EXISTING lines (320-383) before any of my new code was even reached. The new tests are written and structurally correct (seedSharedPreferenceswith amusica_local_uri, assert the friendly name renders and the rawcontent://URI never does) and will pass onceflutter gen-l10nis run as part of the normal manual follow-up (task 8.2).itemsLocales' 60-item cap test was intentionally rewritten, not just signature-patched (see 3.5) — this is a deliberate behavior change mandated by the spec delta (Modified Requirement "Local Music Folder Item Cap and Paging": cap-and-truncate → cap-and-page), not a regression.