UI-01: persistent 4-tab shell with a shared background map
Replaces the push/pop stack rooted at Record with a StatefulShellRoute (Map/Rides/Plan/Settings), each tab keeping its own navigator so Trip Detail and the route planner push within their own branch. AppShell/ShellScaffold host one shared RideMap instance behind every tab -- full opacity and interactive on Map, dimmed and non-interactive elsewhere -- so the camera position and live path survive a tab switch instead of being refetched. Every tab's AppBar/header is removed per the redesign's no-chrome mandate. Fixes a bug this surfaced: RideMap rendered a structurally different tree for empty vs. non-empty points, which crashed once the map became a long-lived shell background instead of a fresh per-screen widget. Unified the background-map tree shape so it no longer remounts mid-recording. Verified on Medium_Phone_API_35: all four tabs, live recording surviving tab switches with the camera preserved, and the real HOME-key lifecycle tile-drop from V3-04 still firing correctly in the shell context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
This commit is contained in:
@@ -21,10 +21,9 @@ final _selectionProvider = StateProvider.autoDispose<Set<int>>(
|
||||
);
|
||||
|
||||
class TripsScreen extends ConsumerWidget {
|
||||
const TripsScreen({super.key, this.onOpenTrip, this.onBack});
|
||||
const TripsScreen({super.key, this.onOpenTrip});
|
||||
|
||||
final void Function(int tripId)? onOpenTrip;
|
||||
final VoidCallback? onBack;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -44,18 +43,22 @@ class TripsScreen extends ConsumerWidget {
|
||||
void clearSelection() =>
|
||||
ref.read(_selectionProvider.notifier).state = <int>{};
|
||||
|
||||
// UI-01: no back/title header -- Rides is a tab now. The selection toolbar (Cancel/
|
||||
// count/Merge/Delete) is functional, not chrome, so it stays -- it only appears
|
||||
// while `selecting` is true.
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (selecting) ...[
|
||||
if (selecting)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: clearSelection,
|
||||
child: const Text('Cancel'),
|
||||
@@ -67,27 +70,7 @@ class TripsScreen extends ConsumerWidget {
|
||||
style: TextStyle(fontSize: 14, color: colors.outline),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
TextButton(
|
||||
key: const Key('back'),
|
||||
onPressed: onBack,
|
||||
child: const Text('‹ Record'),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
'RIDES',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 3,
|
||||
color: colors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
if (selecting) ...[
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
key: const Key('merge'),
|
||||
onPressed: canMerge
|
||||
@@ -104,9 +87,8 @@ class TripsScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: switch (tripsAsync) {
|
||||
AsyncValue(hasValue: true, value: final list)
|
||||
|
||||
Reference in New Issue
Block a user