feat(auto): page local-music folders instead of truncating at 50 [size:exception]
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.
This commit is contained in:
@@ -21,6 +21,53 @@ bool esArchivoAudio(String? mime, String? nombre) {
|
||||
return mimeRecortado.toLowerCase().startsWith('audio/');
|
||||
}
|
||||
|
||||
/// Pure-Dart, SAF-URI-parsing derivation of a human-readable folder name
|
||||
/// (Design ADR-4) — NO native round-trip. SAF tree URIs are
|
||||
/// `content://<authority>/tree/<encoded-documentId>`; `Uri.pathSegments`
|
||||
/// already percent-decodes each segment, so the segment right after `tree`
|
||||
/// is the decoded documentId (e.g. `primary:Music/MyFolder`,
|
||||
/// `1A2B-3C4D:Music`). The trailing readable part of that documentId is
|
||||
/// extracted: everything after the last `/` when present, else everything
|
||||
/// after the last `:`, trimmed. An unparseable [treeUri], a missing/empty
|
||||
/// `tree` segment, or an empty-after-trim result all fall back to
|
||||
/// [nombreGenerico] — this function NEVER returns the raw `content://` URI
|
||||
/// and NEVER returns an empty string.
|
||||
///
|
||||
/// [nombreGenerico] is the caller-supplied fallback text (Design ADR-4/ADR-5
|
||||
/// — genuine phone UI, localized via `AppLocalizations.localMusicFolderGenericName`
|
||||
/// at the call site in `pantalla_ajustes.dart`). Taking it as a plain
|
||||
/// `String` parameter — rather than a `BuildContext`/`AppLocalizations`
|
||||
/// dependency — keeps this function pure and unit-testable without a
|
||||
/// widget tree, mirroring `pantalla_reproductor.dart`'s
|
||||
/// `_formatearDuracion(AppLocalizations l10n, ...)` precedent, minus the
|
||||
/// Flutter-generated-class coupling.
|
||||
String nombreCarpetaDesdeUri(String treeUri, {required String nombreGenerico}) {
|
||||
final uri = Uri.tryParse(treeUri);
|
||||
if (uri == null) return nombreGenerico;
|
||||
|
||||
final segmentos = uri.pathSegments;
|
||||
final indiceTree = segmentos.indexOf('tree');
|
||||
if (indiceTree == -1 || indiceTree + 1 >= segmentos.length) {
|
||||
return nombreGenerico;
|
||||
}
|
||||
final documentId = segmentos[indiceTree + 1];
|
||||
if (documentId.isEmpty) return nombreGenerico;
|
||||
|
||||
final String segmento;
|
||||
final ultimaBarra = documentId.lastIndexOf('/');
|
||||
if (ultimaBarra >= 0) {
|
||||
segmento = documentId.substring(ultimaBarra + 1);
|
||||
} else {
|
||||
final ultimosDosPuntos = documentId.lastIndexOf(':');
|
||||
segmento = ultimosDosPuntos >= 0
|
||||
? documentId.substring(ultimosDosPuntos + 1)
|
||||
: documentId;
|
||||
}
|
||||
|
||||
final recortado = segmento.trim();
|
||||
return recortado.isEmpty ? nombreGenerico : recortado;
|
||||
}
|
||||
|
||||
/// Browse-source abstraction for the local-music branch of the Android Auto
|
||||
/// tree (Design "Interfaces / Contracts"), mirroring [FuenteEmisorasAuto]'s
|
||||
/// (`navegacion_auto.dart`) cold-start-safe, never-throws contract. Kept as
|
||||
|
||||
Reference in New Issue
Block a user