Folders over the 50-item cap now show a "Mas..." item that reveals the next page on tap, instead of silently dropping the rest. Paging slices the cheap raw list before building any MediaItem, so items beyond the requested page are never resolved (art, title) -- proven by a call-count test. Also swaps the raw SAF content:// URI shown in settings for a parsed, human-readable folder name with a localized fallback across all 13 locales. servicio_audio.dart is untouched; this stays entirely within the local-music tree/dispatch layer.
106 lines
5.1 KiB
Markdown
106 lines
5.1 KiB
Markdown
# Proposal: Android Auto Local Music — On-Demand Paging + Friendly Folder Name
|
|
|
|
Fast-follow polish to the archived `android-auto-local-music` Phase 1
|
|
(`archive/2026-07-19-android-auto-local-music/`). NOT Phase 2/3.
|
|
|
|
## Intent
|
|
|
|
Phase 1 capped each local-music folder at `_maxItemsCarpetaLocal = 50` and
|
|
silently truncated the rest alphabetically — items past 50 are unreachable.
|
|
The user rejects silent data loss and wants on-demand incremental loading,
|
|
with an explicit memory goal: do NOT eagerly build full `MediaItem`s (art,
|
|
title) for items beyond the page actually shown. Also fold in an archived
|
|
verify SUGGESTION: settings shows the raw SAF content URI instead of a
|
|
human-friendly folder name.
|
|
|
|
## Scope
|
|
|
|
### In Scope
|
|
- Replace silent truncation in `itemsLocales` with 50-per-page on-demand
|
|
paging for local-music folders, via a synthetic browsable non-playable
|
|
"Más…" trailing item (established `MediaBrowserService` workaround — the
|
|
car UI has no native "load more" signal).
|
|
- Memory efficiency: hold only the cheap full `NodoLocal` list per browsed
|
|
level; construct `MediaItem`s (art/title) ONLY for the requested page.
|
|
Verified by unit test.
|
|
- Friendly folder-name display in settings `_SeccionMusicaLocal` instead of
|
|
the raw `content://…/tree/…` URI.
|
|
- Update ALL 13 `app_*.arb` locale files for any new string (the "Más…"
|
|
label) — Phase 1's verify caught an en/es-only omission; do not repeat.
|
|
|
|
### Out of Scope
|
|
- Phase 2 (metadata/sort/filter/embedded art) and Phase 3 (subfolder
|
|
scoping/shuffle) — remain deferred.
|
|
- Radio-station folders (`_maxItemsPorCarpeta = 50`) keep cap-and-truncate;
|
|
paging is NOT generalized to radio (user raised local music only; no
|
|
architectural need forces sharing now).
|
|
- Native Kotlin paging: `listAudioChildren` already returns the full level;
|
|
no native offset/limit added.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
None.
|
|
|
|
### Modified Capabilities
|
|
- `android-auto-media`: local-music folder browse replaces silent 50-item
|
|
truncation with on-demand "Más…" paging. (sdd-spec: confirm exact
|
|
requirement location; the settings friendly-name fix is phone-UI and may
|
|
need only a light or no spec delta.)
|
|
|
|
## Approach
|
|
|
|
Pure-Dart, additive on existing seams. `itemsLocales(nodos, pagina)` sorts
|
|
the full (cheap) `NodoLocal` list deterministically, then
|
|
`.skip(pagina*50).take(50).map(_itemLocal)` — so only the page's items are
|
|
built — and appends a "Más…" item when more remain. The "Más…" media-id
|
|
encodes `(documentId, siguientePagina)` (new prefix, e.g.
|
|
`carpeta_local_pag:`); tapping it re-invokes `getChildren`, which
|
|
re-enumerates that one folder (native already returns the whole level) and
|
|
returns the next slice. Nothing is cached between taps → minimal memory;
|
|
deterministic sort keeps page boundaries stable across re-queries. Friendly
|
|
name via pure-Dart `nombreCarpetaDesdeUri(treeUri)` (decode the tree
|
|
documentId's trailing path segment; fall back to the raw URI when empty) —
|
|
zero native surface, fully unit-testable.
|
|
|
|
## Affected Areas
|
|
|
|
| Area | Impact | Description |
|
|
|------|--------|-------------|
|
|
| `lib/servicios/navegacion_auto.dart` | Modified | `itemsLocales` gains page param + "Más…" item; paged media-id predicate/parse; `hijosMusicaLocal` routes paged ids |
|
|
| `lib/servicios/servicio_audio.dart` | Modified | `getChildren` branch for the paged "Más…" media-id |
|
|
| `lib/servicios/musica_local_auto.dart` | Modified | Add `nombreCarpetaDesdeUri` helper (pure Dart) |
|
|
| `lib/pantallas/pantalla_ajustes.dart` | Modified | `_SeccionMusicaLocal` renders friendly name (line ~361) |
|
|
| `lib/l10n/app_{ar,bn,de,en,es,fr,hi,id,it,ja,pt,ru,zh}.arb` | Modified | New "Más…" label across all 13 locales |
|
|
| `MainActivity.kt` / `pubspec.yaml` | Unchanged | No native/dep change |
|
|
|
|
## Risks
|
|
|
|
| Risk | Likelihood | Mitigation |
|
|
|------|------------|------------|
|
|
| Re-enumerating the folder on each "Más…" tap repeats one SAF query | Med | Acceptable per-tap cost; deterministic sort keeps pages stable; no cache = minimal memory |
|
|
| Pure-Dart friendly-name derivation brittle for exotic SAF providers | Med | Fallback to raw URI; native `DocumentFile.getName` left as a design fallback |
|
|
| Very deep folders still hold full `NodoLocal` list per level | Low | DTOs are tiny; only paged `MediaItem` build is bounded |
|
|
| l10n omission regresses (missing locales) | Med | Scope all 13 arb files into tasks from the start |
|
|
|
|
## Rollback Plan
|
|
|
|
Behavioral/additive. Revert = restore `.take(_maxItemsCarpetaLocal)`
|
|
truncation, drop the paged media-id + "Más…" item, revert the settings
|
|
subtitle to the raw URI, remove the new arb key. Stations untouched.
|
|
|
|
## Dependencies
|
|
|
|
None (no new packages, no native changes).
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] A folder with >50 items exposes every item via "Más…" paging; none
|
|
silently dropped.
|
|
- [ ] Only the requested page's `MediaItem`s are constructed — proven by a
|
|
unit test (page N+1 items not built until page N+1 is requested).
|
|
- [ ] Settings shows a human-friendly folder name, not the raw content URI.
|
|
- [ ] All 13 `app_*.arb` files carry the new label.
|
|
- [ ] No station browse/play regression; pure-Dart fully unit-tested; native
|
|
unchanged.
|