Refresh the Flutter snapshot: UI-01 through UI-09, plus a fresh installable APK

Full UI redesign pass complete: persistent tab shell with an always-visible
background map, Modern Professional Dark theme, monochrome dark map tiles,
offline skeleton map, a shared GlassPanel/FloatingPill component kit,
customizable HUD telemetry widgets, and the Map HUD / Plan & Route Planning /
Rides History screen rebuilds. 374 tests passing, up from 316.

The APK is a fresh release build (debug-signed, no release signing config
exists yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
This commit is contained in:
2026-08-24 14:41:35 -05:00
parent 4c634354bd
commit 587f75eab4
42 changed files with 4401 additions and 89 deletions

View File

@@ -7,6 +7,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:rippr/src/app/providers.dart';
import 'package:rippr/src/data/database.dart';
import 'package:rippr/src/data/route_plan_repository.dart';
import 'package:rippr/src/ui/app_shell.dart';
import 'package:rippr/src/ui/components/floating_pill.dart';
import 'package:rippr/src/ui/format.dart';
import 'package:rippr/src/ui/routes/route_planner_screen.dart';
import 'package:rippr/src/ui/routes/routes_list_screen.dart';
import 'package:rippr/src/ui/theme.dart';
@@ -107,8 +110,10 @@ void main() {
final id = await repo.createRoutePlan(1000);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
expect(find.text('0 m'), findsOneWidget);
expect(find.textContaining('0 pins'), findsOneWidget);
// UI-06: no pins yet -- the "tap to drop a pin" tooltip shows, not a stat pill
// (there is nothing to summarise yet).
expect(find.textContaining('TAP TO DROP A PIN'), findsOneWidget);
expect(find.byType(FloatingPill), findsNothing);
await tester.tapAt(tester.getCenter(find.byType(FlutterMap)));
await tester.pump(const Duration(milliseconds: 50));
@@ -119,35 +124,54 @@ void main() {
final waypoints = await repo.waypointsFor(id);
expect(waypoints, hasLength(2));
expect(find.textContaining('2 pins'), findsOneWidget);
final label = tester.widget<Text>(find.byKey(const Key('route-distance')));
expect(label.data, isNot('0 m'));
// The tooltip is gone and the stat pill has taken its place, showing the real
// pin count and a non-zero distance. Read the pill's own `stats` directly
// rather than `find.text('2')` -- the second waypoint pin's own marker label
// is also "2", so that text is no longer unique on screen.
expect(find.textContaining('TAP TO DROP A PIN'), findsNothing);
final pill = tester.widget<FloatingPill>(find.byType(FloatingPill));
final pinsStat = pill.stats.firstWhere((s) => s.label == 'Pins');
final distanceStat = pill.stats.firstWhere((s) => s.label == 'Distance');
expect(pinsStat.value, '2');
expect(distanceStat.value, isNot(formatDistance(0)),
reason: 'the distance stat must reflect the two real pins, not stay zeroed');
});
screenTest('tapping a pin deletes it', (tester) async {
// UI-06: a single waypoint (rather than two) -- with only one pin, the camera
// centres exactly on it, keeping it clear of the floating "Start Route" button
// now anchored at the bottom of the screen; two close pins previously landed
// one of them directly underneath it in this test's fixed viewport.
final id = await repo.createRoutePlan(1000);
await repo.addWaypoint(id, 51.0, -114.0);
await repo.addWaypoint(id, 51.01, -114.0);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
// flutter_map's `Marker` is a plain data class, not a Widget -- it never appears
// in the tree itself. `CircleAvatar` is what `_WaypointPin` actually renders.
expect(find.byType(CircleAvatar), findsNWidgets(2));
expect(find.byType(CircleAvatar), findsOneWidget);
await tester.tap(find.text('1')); // the first pin's label
// Not `find.text('1')`: the FloatingPill's "Pins" stat also reads "1" with a
// single waypoint, so the pin's own label text is no longer a unique match.
await tester.tap(find.byType(CircleAvatar));
await tester.pump();
expect(await repo.waypointsFor(id), hasLength(1));
expect(await repo.waypointsFor(id), isEmpty);
});
screenTest('renaming updates the app bar title', (tester) async {
screenTest('renaming writes through to the repository (UI-06: no app bar title '
'to display it on any more)', (tester) async {
final id = await repo.createRoutePlan(1000, name: 'Old name');
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
await tester.tap(find.byKey(const Key('rename-route')));
// Rename now lives behind the floating overflow menu -- open it first.
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
await tester.tap(find.byKey(const Key('rename-route')));
await tester.pump(const Duration(milliseconds: 300));
await tester.enterText(
find.byKey(const Key('route-name-field')),
'Sunday coast run',
@@ -155,7 +179,8 @@ void main() {
await tester.tap(find.text('Save'));
await tester.pump();
expect(find.text('Sunday coast run'), findsOneWidget);
final renamed = await repo.routePlanById(id);
expect(renamed?.name, 'Sunday coast run');
});
screenTest('a missing route says so instead of a blank map', (tester) async {
@@ -165,16 +190,21 @@ void main() {
expect(find.textContaining('no longer exists'), findsOneWidget);
});
screenTest('the offline-tiles download button is disabled with no pins '
'(V3-11)', (tester) async {
screenTest('the offline-tiles download menu item is disabled with no pins '
'(V3-11, UI-06: now a PopupMenuItem behind the overflow menu)',
(tester) async {
final id = await repo.createRoutePlan(1000);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
final button = tester.widget<IconButton>(
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
final item = tester.widget<PopupMenuItem<String>>(
find.byKey(const Key('download-tiles')),
);
expect(button.onPressed, isNull);
expect(button.tooltip, contains('Add pins'));
expect(item.enabled, isFalse);
});
screenTest('downloading shows a tile count and size estimate before any '
@@ -184,10 +214,15 @@ void main() {
await repo.addWaypoint(id, 51.01, -114.0);
await pumpMap(tester, host(RoutePlannerScreen(routeId: id)));
final button = tester.widget<IconButton>(
await tester.tap(find.byKey(const Key('route-overflow-menu')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
await tester.pump();
final item = tester.widget<PopupMenuItem<String>>(
find.byKey(const Key('download-tiles')),
);
expect(button.onPressed, isNotNull);
expect(item.enabled, isTrue);
await tester.tap(find.byKey(const Key('download-tiles')));
// Bounded, not pumpAndSettle: the confirmation dialog is safe to settle (no
@@ -201,5 +236,25 @@ void main() {
expect(find.textContaining('tiles?'), findsOneWidget);
expect(find.textContaining('MB'), findsOneWidget);
});
screenTest('the shell nav bar correctly shows Plan active on this screen '
'(UI-06)', (tester) async {
final id = await repo.createRoutePlan(1000);
await pumpMap(
tester,
host(
ShellScaffold(
currentIndex: 2, // Map, Rides, Plan, Settings -- Plan is index 2.
onDestinationSelected: (_) {},
child: RoutePlannerScreen(routeId: id),
),
),
);
final navBar = tester.widget<NavigationBar>(find.byKey(const Key('shell-nav-bar')));
expect(navBar.selectedIndex, 2,
reason: 'not the mockup\'s own generation error (it showed Rides active '
'while viewing Plan) -- the shipped nav bar must reflect the real tab');
});
});
}