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:
2026-07-19 22:14:05 +02:00
parent 977cbcd8cc
commit 725169cd31
25 changed files with 1601 additions and 40 deletions
@@ -38,4 +38,88 @@ void main() {
},
);
});
group('nombreCarpetaDesdeUri', () {
const generico = 'Carpeta seleccionada';
test('almacenamiento interno: toma el segmento tras el último ":"', () {
expect(
nombreCarpetaDesdeUri(
'content://com.android.externalstorage.documents/tree/'
'primary%3AMusic%2FMyFolder',
nombreGenerico: generico,
),
'MyFolder',
);
});
test('tarjeta SD: toma el segmento tras el último ":"', () {
expect(
nombreCarpetaDesdeUri(
'content://com.android.externalstorage.documents/tree/'
'1A2B-3C4D%3AMusic',
nombreGenerico: generico,
),
'Music',
);
});
test(
'carpeta anidada con "/": toma el segmento tras la última "/"',
() {
expect(
nombreCarpetaDesdeUri(
'content://com.android.externalstorage.documents/tree/'
'primary%3AMusic%2FAlbum%2FDeep',
nombreGenerico: generico,
),
'Deep',
);
},
);
test(
'raíz de almacenamiento con tail vacío tras ":" cae al genérico',
() {
final resultado = nombreCarpetaDesdeUri(
'content://com.android.externalstorage.documents/tree/primary%3A',
nombreGenerico: generico,
);
expect(resultado, generico);
},
);
test('URI no parseable o de forma inesperada cae al genérico', () {
for (final uri in [
'',
'not a uri \$\$\$ ///',
'content://com.android.externalstorage.documents/tree/',
]) {
final resultado = nombreCarpetaDesdeUri(uri, nombreGenerico: generico);
expect(resultado, generico);
}
});
test(
'nunca devuelve la URI cruda ni una cadena vacía, para ningún caso '
'de esta suite',
() {
final casos = [
'content://com.android.externalstorage.documents/tree/'
'primary%3AMusic%2FMyFolder',
'content://com.android.externalstorage.documents/tree/'
'1A2B-3C4D%3AMusic',
'content://com.android.externalstorage.documents/tree/primary%3A',
'',
'garbage',
];
for (final uri in casos) {
final resultado = nombreCarpetaDesdeUri(uri, nombreGenerico: generico);
expect(resultado, isNotEmpty);
expect(resultado, isNot(equals(uri)));
expect(resultado, isNot(contains('content://')));
}
},
);
});
}