fix(favoritos): use the cross-version onReorder API and correct drag index math
The CI Flutter SDK predates v3.41 and only exposes ReorderableListView's onReorder; the newer onReorderItem broke the build with three analyzer errors. onReorder exists in both SDKs, so use it and compensate for its pre-removal newIndex internally. Fixing the call site surfaced a real ordering bug: _onReorder located the target neighbour in the untrimmed global list, while ServicioFavoritos .reordenar inserts into the list after the station is removed. Dragging a station downwards past its neighbours therefore landed it one slot too far. Adds a mid-list downward-drag test, the only case that separates the two coordinate spaces.
This commit is contained in:
@@ -58,23 +58,35 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
|
|||||||
/// global position [EstadoRadio.reordenarFavorito] expects, so a drag
|
/// global position [EstadoRadio.reordenarFavorito] expects, so a drag
|
||||||
/// while a group chip is active still produces a coherent global order
|
/// while a group chip is active still produces a coherent global order
|
||||||
/// (other groups' relative order is left untouched).
|
/// (other groups' relative order is left untouched).
|
||||||
|
///
|
||||||
|
/// [newIndex] arrives in `ReorderableListView.onReorder`'s pre-removal
|
||||||
|
/// coordinate space: dragging downwards reports the slot the row would
|
||||||
|
/// occupy while it is still in the list. The logic below indexes into the
|
||||||
|
/// list AFTER the row is removed, so shift by one in that direction first.
|
||||||
void _onReorder(
|
void _onReorder(
|
||||||
List<Emisora> filtrados,
|
List<Emisora> filtrados,
|
||||||
List<Emisora> favoritos,
|
List<Emisora> favoritos,
|
||||||
int oldIndex,
|
int oldIndex,
|
||||||
int newIndex,
|
int newIndex,
|
||||||
) {
|
) {
|
||||||
|
if (newIndex > oldIndex) newIndex -= 1;
|
||||||
final movido = filtrados[oldIndex];
|
final movido = filtrados[oldIndex];
|
||||||
final restantes = List<Emisora>.from(filtrados)..removeAt(oldIndex);
|
final restantes = List<Emisora>.from(filtrados)..removeAt(oldIndex);
|
||||||
|
// `ServicioFavoritos.reordenar` removes the station first and THEN
|
||||||
|
// inserts at the index it is given, so the target index must be
|
||||||
|
// expressed in the global list WITHOUT the moved station. Locating the
|
||||||
|
// neighbour in the untrimmed list instead drifts by one whenever the
|
||||||
|
// moved station sits before it.
|
||||||
|
final globalSinMovido =
|
||||||
|
favoritos.where((e) => e.uuid != movido.uuid).toList();
|
||||||
final int nuevoIndiceGlobal;
|
final int nuevoIndiceGlobal;
|
||||||
if (restantes.isEmpty) {
|
if (restantes.isEmpty) {
|
||||||
nuevoIndiceGlobal = favoritos.length - 1;
|
nuevoIndiceGlobal = globalSinMovido.length;
|
||||||
} else if (newIndex >= restantes.length) {
|
} else if (newIndex >= restantes.length) {
|
||||||
nuevoIndiceGlobal = favoritos.indexWhere(
|
nuevoIndiceGlobal =
|
||||||
(e) => e.uuid == restantes.last.uuid,
|
globalSinMovido.indexWhere((e) => e.uuid == restantes.last.uuid) + 1;
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
nuevoIndiceGlobal = favoritos.indexWhere(
|
nuevoIndiceGlobal = globalSinMovido.indexWhere(
|
||||||
(e) => e.uuid == restantes[newIndex].uuid,
|
(e) => e.uuid == restantes[newIndex].uuid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -217,7 +229,7 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
|
|||||||
onTap: _abrirFormularioEmisoraPersonalizada,
|
onTap: _abrirFormularioEmisoraPersonalizada,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onReorderItem:
|
onReorder:
|
||||||
(oldIndex, newIndex) =>
|
(oldIndex, newIndex) =>
|
||||||
_onReorder(filtrados, favoritos, oldIndex, newIndex),
|
_onReorder(filtrados, favoritos, oldIndex, newIndex),
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ void main() {
|
|||||||
for (final locale in _auditedLocales) {
|
for (final locale in _auditedLocales) {
|
||||||
final arb = readArb(locale);
|
final arb = readArb(locale);
|
||||||
for (final key in realKeys(arb)) {
|
for (final key in realKeys(arb)) {
|
||||||
if (!es.containsKey(key))
|
// Missing keys are arb_parity_test's job, not this one's.
|
||||||
continue; // arb_parity_test's job, not this one's
|
if (!es.containsKey(key)) continue;
|
||||||
if (arb[key] == es[key] &&
|
if (arb[key] == es[key] &&
|
||||||
!identicalValueAllowlist.contains((locale, key))) {
|
!identicalValueAllowlist.contains((locale, key))) {
|
||||||
unlisted.add('$locale/$key = "${arb[key]}"');
|
unlisted.add('$locale/$key = "${arb[key]}"');
|
||||||
|
|||||||
@@ -226,10 +226,13 @@ void main() {
|
|||||||
find.byType(ReorderableListView),
|
find.byType(ReorderableListView),
|
||||||
);
|
);
|
||||||
// Drag the 3rd row (index 2, "Station C") to the 1st position (index
|
// Drag the 3rd row (index 2, "Station C") to the 1st position (index
|
||||||
// 0) — exercised via the real onReorderItem callback the widget wires
|
// 0), exercised via the real onReorder callback the widget wires up.
|
||||||
// up. onReorderItem (not the deprecated onReorder) already adjusts
|
// `onReorder` is the API present across Flutter versions (the newer
|
||||||
// newIndex for the removed item, so no manual index math here.
|
// `onReorderItem` does not exist on the CI SDK), so it reports
|
||||||
lista.onReorderItem!(2, 0);
|
// newIndex in the PRE-removal coordinate space; `_onReorder`
|
||||||
|
// compensates internally. Moving upwards needs no shift, which is why
|
||||||
|
// (2, 0) maps straight through.
|
||||||
|
lista.onReorder!(2, 0);
|
||||||
await pumpStable(tester);
|
await pumpStable(tester);
|
||||||
|
|
||||||
expect(estado.listaFavoritosManual.map((e) => e.uuid).toList(), [
|
expect(estado.listaFavoritosManual.map((e) => e.uuid).toList(), [
|
||||||
@@ -247,6 +250,40 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('dragging the 1st item downwards lands it in the right slot', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
// Guards the pre-removal index compensation in `_onReorder`. Downward
|
||||||
|
// drags are the ONLY direction `ReorderableListView.onReorder` reports
|
||||||
|
// in the pre-removal coordinate space, so an off-by-one here would slip
|
||||||
|
// past the upward-drag test above entirely.
|
||||||
|
setLargeSurface(tester);
|
||||||
|
_suppressListTileInkAssertion();
|
||||||
|
final estado = await crearEstadoConFavoritos();
|
||||||
|
addTearDown(estado.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildScreen(estado));
|
||||||
|
await pumpStable(tester);
|
||||||
|
|
||||||
|
final lista = tester.widget<ReorderableListView>(
|
||||||
|
find.byType(ReorderableListView),
|
||||||
|
);
|
||||||
|
// Move "Station A" (index 0) into the MIDDLE slot. onReorder reports
|
||||||
|
// newIndex == 2 in the pre-removal space; `_onReorder` shifts it to 1.
|
||||||
|
// This specific case is what makes the test meaningful: dropping at the
|
||||||
|
// very end (0, 3) yields the same answer with or without the shift,
|
||||||
|
// because both land in the `newIndex >= restantes.length` branch. Only a
|
||||||
|
// mid-list drop separates the two.
|
||||||
|
lista.onReorder!(0, 2);
|
||||||
|
await pumpStable(tester);
|
||||||
|
|
||||||
|
expect(estado.listaFavoritosManual.map((e) => e.uuid).toList(), [
|
||||||
|
'b',
|
||||||
|
'a',
|
||||||
|
'c',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'swap_vert sort action applies OrdenEmisoras.nombre and re-renders '
|
'swap_vert sort action applies OrdenEmisoras.nombre and re-renders '
|
||||||
'alphabetically',
|
'alphabetically',
|
||||||
|
|||||||
Reference in New Issue
Block a user