Merge FB-08: reflow HUD row widgets on hide/show
# Conflicts: # docs/feedback/FB-08-hud-reflow-on-hide.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# FB-08 — HUD widgets reflow to fill the gap when one is hidden
|
# FB-08 — HUD widgets reflow to fill the gap when one is hidden
|
||||||
|
|
||||||
**Depends on** — · **Size** M · **Status** Not started
|
**Depends on** — · **Size** M · **Status** Done
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
Hiding a HUD metric must resize the remaining widgets in its row to fill the empty
|
Hiding a HUD metric must resize the remaining widgets in its row to fill the empty
|
||||||
@@ -136,3 +136,22 @@ metric starts at, only how a row's members redistribute space among themselves w
|
|||||||
one of them is hidden or shown. Sequence FB-09 after this ticket merges, since both
|
one of them is hidden or shown. Sequence FB-09 after this ticket merges, since both
|
||||||
touch `HudLayoutController`/`HudWidgetLayout` and a large simultaneous diff in both is
|
touch `HudLayoutController`/`HudWidgetLayout` and a large simultaneous diff in both is
|
||||||
harder to review and merge than one after the other.
|
harder to review and merge than one after the other.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Implemented `_reflowRow` in `lib/src/hud/hud_layout_controller.dart` exactly as
|
||||||
|
specified in the Design section, and wired it into `setVisible` using the toggled
|
||||||
|
metric's `current.row` (its row before the toggle when hiding, its row after
|
||||||
|
`defaultFor`/`nextFreeSlot` placement when showing) — no deviation from the design.
|
||||||
|
|
||||||
|
Added four tests to `test/hud_layout_controller_test.dart` covering the four
|
||||||
|
acceptance-criteria scenarios: hiding one of four same-row metrics reflows the
|
||||||
|
remaining three to fill the row with no gap; hiding a metric alone in its row leaves
|
||||||
|
every other metric's `col`/`row`/`colSpan` untouched; showing a previously-hidden
|
||||||
|
metric into a row with an existing member reflows that row so their `colSpan`s sum to
|
||||||
|
`hudGridColumns`; and toggling a metric in one row leaves a different row's members'
|
||||||
|
`col`/`colSpan` unchanged.
|
||||||
|
|
||||||
|
`flutter analyze` stayed clean (only pre-existing, unrelated info-level lint notices in
|
||||||
|
`lib/src/crash/crash_reporter.dart` and `lib/src/tiles/map_connectivity.dart`, neither
|
||||||
|
touched by this ticket). `flutter test` is green with a final count of **416 tests**
|
||||||
|
(412 baseline + 4 new), no regressions.
|
||||||
|
|||||||
@@ -67,7 +67,30 @@ class HudLayoutController extends StateNotifier<Map<HudMetric, HudWidgetLayout>>
|
|||||||
rowSpan: current.rowSpan,
|
rowSpan: current.rowSpan,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
state = {...state, metric: current.copyWith(visible: visible)};
|
final updated = {...state, metric: current.copyWith(visible: visible)};
|
||||||
|
state = _reflowRow(updated, current.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spreads every visible widget in [row] evenly across the full grid width,
|
||||||
|
/// left to right in their existing column order. A widget whose row has no other
|
||||||
|
/// visible member is left alone -- there is nothing to redistribute.
|
||||||
|
Map<HudMetric, HudWidgetLayout> _reflowRow(
|
||||||
|
Map<HudMetric, HudWidgetLayout> layouts,
|
||||||
|
int row,
|
||||||
|
) {
|
||||||
|
final members = layouts.values.where((l) => l.visible && l.row == row).toList()
|
||||||
|
..sort((a, b) => a.col.compareTo(b.col));
|
||||||
|
if (members.length < 2) return layouts;
|
||||||
|
final baseSpan = hudGridColumns ~/ members.length;
|
||||||
|
final extra = hudGridColumns % members.length;
|
||||||
|
final result = Map<HudMetric, HudWidgetLayout>.from(layouts);
|
||||||
|
var col = 0;
|
||||||
|
for (var i = 0; i < members.length; i++) {
|
||||||
|
final span = baseSpan + (i < extra ? 1 : 0);
|
||||||
|
result[members[i].metric] = members[i].copyWith(col: col, colSpan: span);
|
||||||
|
col += span;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> persist() async => _config?.setHudLayout(state);
|
Future<void> persist() async => _config?.setHudLayout(state);
|
||||||
|
|||||||
@@ -110,6 +110,80 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('hiding one of four same-row metrics reflows the remaining three to fill '
|
||||||
|
'the row with no gap', () async {
|
||||||
|
final controller = HudLayoutController(await freshConfig());
|
||||||
|
|
||||||
|
controller.setVisible(HudMetric.avgSpeed, false);
|
||||||
|
|
||||||
|
final remaining = [HudMetric.speed, HudMetric.distance, HudMetric.elapsedTime]
|
||||||
|
.map((m) => controller.state[m]!)
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => a.col.compareTo(b.col));
|
||||||
|
final totalSpan = remaining.fold<int>(0, (sum, l) => sum + l.colSpan);
|
||||||
|
expect(totalSpan, hudGridColumns);
|
||||||
|
// baseSpan = 4 ~/ 3 = 1, extra = 1 -- the first (leftmost) member absorbs it.
|
||||||
|
expect(remaining[0].colSpan, 2);
|
||||||
|
expect(remaining[1].colSpan, 1);
|
||||||
|
expect(remaining[2].colSpan, 1);
|
||||||
|
expect(controller.state[HudMetric.avgSpeed]!.visible, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hiding a metric whose row has no other visible member leaves every '
|
||||||
|
'other metric untouched', () async {
|
||||||
|
final controller = HudLayoutController(await freshConfig());
|
||||||
|
// Row 4 is unused by any default layout -- move speed there alone.
|
||||||
|
controller.updatePosition(HudMetric.speed, 0, 4);
|
||||||
|
|
||||||
|
final others = {
|
||||||
|
for (final m in HudMetric.values.where((m) => m != HudMetric.speed))
|
||||||
|
m: controller.state[m]!,
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.setVisible(HudMetric.speed, false);
|
||||||
|
|
||||||
|
for (final entry in others.entries) {
|
||||||
|
final after = controller.state[entry.key]!;
|
||||||
|
expect(after.col, entry.value.col, reason: '${entry.key} col changed');
|
||||||
|
expect(after.row, entry.value.row, reason: '${entry.key} row changed');
|
||||||
|
expect(after.colSpan, entry.value.colSpan,
|
||||||
|
reason: '${entry.key} colSpan changed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showing a previously-hidden metric back into a row with existing '
|
||||||
|
'members reflows that row to fit it', () async {
|
||||||
|
final controller = HudLayoutController(await freshConfig());
|
||||||
|
|
||||||
|
// maxSpeed and movingTime both default to row 2, hidden, at adjacent columns.
|
||||||
|
controller.setVisible(HudMetric.maxSpeed, true);
|
||||||
|
controller.setVisible(HudMetric.movingTime, true);
|
||||||
|
|
||||||
|
final maxSpeed = controller.state[HudMetric.maxSpeed]!;
|
||||||
|
final movingTime = controller.state[HudMetric.movingTime]!;
|
||||||
|
expect(maxSpeed.row, movingTime.row,
|
||||||
|
reason: 'test setup assumes both land in the same row');
|
||||||
|
expect(maxSpeed.colSpan + movingTime.colSpan, hudGridColumns);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggling a metric in one row leaves a different row\'s members '
|
||||||
|
'untouched', () async {
|
||||||
|
final controller = HudLayoutController(await freshConfig());
|
||||||
|
final row2Before = {
|
||||||
|
for (final m in [HudMetric.maxSpeed, HudMetric.movingTime, HudMetric.elevationGain])
|
||||||
|
m: controller.state[m]!,
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.setVisible(HudMetric.avgSpeed, false);
|
||||||
|
|
||||||
|
for (final entry in row2Before.entries) {
|
||||||
|
final after = controller.state[entry.key]!;
|
||||||
|
expect(after.col, entry.value.col, reason: '${entry.key} col changed');
|
||||||
|
expect(after.colSpan, entry.value.colSpan,
|
||||||
|
reason: '${entry.key} colSpan changed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('persist writes the current state to Config', () async {
|
test('persist writes the current state to Config', () async {
|
||||||
final config = await freshConfig();
|
final config = await freshConfig();
|
||||||
final controller = HudLayoutController(config);
|
final controller = HudLayoutController(config);
|
||||||
|
|||||||
Reference in New Issue
Block a user