FB-02: hide the idle Speed panel until recording starts

Removes the idle GlassPanel/BigStat Speed-Status-Last ride block from
RecordScreen; the live map plus START control is now the entire idle
screen. HudEditOverlay is gated on !ui.isIdle. Migrates the five
affected widget tests to seed a recording trip and assert against the
HUD's own ValueKey(HudMetric.speed) widget instead of the old idle
Key('speed').

Also fixes a pre-existing gap this migration exposed: _HudMetricValue
had no mounted-mode awareness at all (no text scaling, and its
per-metric accent colours fall just under AA contrast against the
mounted theme's GlassPanel surface). Adds scale and mountedMode
params, reusing BigStat's own mechanisms (scale multiplier, explicit
onSurface ink) to fix both.
This commit is contained in:
2026-08-24 15:53:14 -05:00
parent 04127d7237
commit 094e42a12f
3 changed files with 324 additions and 56 deletions

View File

@@ -9,6 +9,7 @@ import 'package:rippr/src/app/providers.dart';
import 'package:rippr/src/data/database.dart';
import 'package:rippr/src/data/trip_repository.dart';
import 'package:rippr/src/domain/models.dart';
import 'package:rippr/src/hud/hud_metric.dart';
import 'package:rippr/src/ui/activity_display.dart';
import 'package:rippr/src/recording/location_source.dart';
import 'package:rippr/src/recording/wakelock_controller.dart';
@@ -123,11 +124,10 @@ void main() {
}
group('record screen', () {
screenTest('idle shows Ready and only START', (tester) async {
screenTest('idle shows only START', (tester) async {
await tester.pumpWidget(host(const RecordScreen()));
await tester.pumpAndSettle();
expect(find.text('Ready'), findsOneWidget);
expect(find.byKey(const Key('start')), findsOneWidget);
expect(find.byKey(const Key('pause')), findsNothing);
expect(find.byKey(const Key('stop')), findsNothing);
@@ -139,8 +139,8 @@ void main() {
// v2.0 shipped max speed as the headline and it read as a frozen, broken screen on
// a real ride, because a max figure only moves when you beat it. This guards the
// 2.0.1 fix.
await tester.pumpWidget(host(const RecordScreen()));
await tester.pumpAndSettle();
await repo.startTrip(1000);
await pumpLive(tester, host(const RecordScreen(), map: false));
expect(find.text('SPEED'), findsOneWidget);
expect(find.text('MAX SPEED'), findsNothing);
@@ -236,12 +236,12 @@ void main() {
// The regression this exists for: removing Compose's Surface left LocalContentColor
// black, and a 64sp figure rendered invisibly on a near-black background. No logic
// test could catch it. This asserts the rendered colour differs from the ground.
await tester.pumpWidget(host(const RecordScreen()));
await tester.pumpAndSettle();
await repo.startTrip(1000);
await pumpLive(tester, host(const RecordScreen(), map: false));
final speed = tester.widget<Text>(
find.descendant(
of: find.byKey(const Key('speed')),
of: find.byKey(const ValueKey(HudMetric.speed)),
matching: find.text('0'),
),
);
@@ -379,21 +379,24 @@ void main() {
screenTest('the mounted theme is high-contrast and text scales up (V3-05)',
(tester) async {
await tester.pumpWidget(host(const RecordScreen(), mountedMode: true));
await tester.pumpAndSettle();
await repo.startTrip(1000);
await pumpLive(
tester,
host(const RecordScreen(), map: false, mountedMode: true),
);
final speed = tester.widget<Text>(
find.descendant(
of: find.byKey(const Key('speed')),
of: find.byKey(const ValueKey(HudMetric.speed)),
matching: find.text('0'),
),
);
expect(speed.style?.fontSize, 64 * mountedTextScale);
expect(speed.style?.fontSize, 18 * mountedTextScale);
expect(speed.style?.color, isNot(ripprBackground),
reason: 'still legible, just against a different (lighter) ground');
final start = tester.getSize(find.byKey(const Key('start')));
expect(start.height, 96,
final pause = tester.getSize(find.byKey(const Key('pause')));
expect(pause.height, 96,
reason: 'V3-05: 72dp is not enough at speed, with gloves');
});
@@ -405,13 +408,24 @@ void main() {
// content behaves differently than the dark-on-dark case V3-05 originally
// guarded against. This asserts the real contrast ratio, not just "differs from
// the wrong ground" the way the test above does.
await tester.pumpWidget(host(const RecordScreen(), mountedMode: true));
await tester.pumpAndSettle();
await repo.startTrip(1000);
await pumpLive(
tester,
host(const RecordScreen(), map: false, mountedMode: true),
);
final speed = tester.widget<Text>(
find.descendant(of: find.byKey(const Key('speed')), matching: find.text('0')),
find.descendant(
of: find.byKey(const ValueKey(HudMetric.speed)),
matching: find.text('0'),
),
);
final panel = tester.widget<GlassPanel>(
find.descendant(
of: find.byKey(const ValueKey(HudMetric.speed)),
matching: find.byType(GlassPanel),
),
);
final panel = tester.widget<GlassPanel>(find.byType(GlassPanel).first);
final mountedColors = ripprMountedTheme().colorScheme;
// GlassPanel fills with `colors.surface` at `GlassPanel.fillOpacity` -- since it
// is a solid, near-opaque fill (not a transparency composited over unknown