Miler rider app: surface system, visible design language, backend lifecycle
Design system - MilerSurface ladder (canvas → working → raised → floating) with MilerPanel as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1. - Visible vocabulary applied across Home, Deliveries, Activity, Account and the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x), canvas wells for anything that opens, small filled tags for shelf labels, demoted placeholders. Recorded in DESIGN_SYSTEM.md §6. - One icon family: 222 Material glyphs migrated to Lucide; none left outside lib/xpress. - Colour semantics corrected: amber only for what is genuinely owed, brand red reserved for the live stop, disabled primaries go neutral rather than pale. Data and lifecycle - lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart makes admin sequence the single ordering authority; service_day.dart, and stop_area.dart rewritten against live Coimbatore addresses (digit-token stripping, city stoplist, street suffixes, stammer collapse). - countLabel states the load once, in bags. Testing - 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity, sheets and verify, with test/failures/ now gitignored (diff debris). - New pins: home_gutter_test, stop_area_test, plus updated structural bounds. Note: this commit also carries pre-existing working-tree deletions that were present before this work (API_SPEC.md, README.md, demo test fixtures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import 'package:miler/views/Dashboard/home/trip.dart';
|
||||
import 'package:miler/views/helpers/constants/miler_surface.dart';
|
||||
import 'package:miler/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:miler/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:miler/views/helpers/constants/design_constants.dart';
|
||||
@@ -159,7 +161,12 @@ class _TripTabsState extends State<TripTabs>
|
||||
|
||||
/// The colour a segment settles on once the pill is over it.
|
||||
Color _litColourOf(int tripIndex) {
|
||||
if (tripIndex >= widget.trips.length) return ColorConstants.secondaryText;
|
||||
if (widget.trips.tripAt(tripIndex) == null) {
|
||||
// Selected-but-empty keeps a readable voice — the rider deliberately
|
||||
// looked at this slot, so it answers — but never the brand: there is
|
||||
// nothing here to be mission about.
|
||||
return ColorConstants.secondaryText;
|
||||
}
|
||||
return _percentOf(tripIndex) >= 100
|
||||
? ColorConstants.acceptGreen
|
||||
: ColorConstants.primary;
|
||||
@@ -171,16 +178,37 @@ class _TripTabsState extends State<TripTabs>
|
||||
/// Done" is worth knowing from Trip 2 — so for those the two colours are the
|
||||
/// same and nothing changes as the pill goes by.
|
||||
Color _restColourOf(int tripIndex) {
|
||||
if (tripIndex >= widget.trips.length) return ColorConstants.secondaryText;
|
||||
if (widget.trips.tripAt(tripIndex) == null) {
|
||||
// ── An empty slot is furniture, not a peer ──
|
||||
//
|
||||
// At `secondaryText` an unassigned tab was indistinguishable from a
|
||||
// real unselected trip, so a morning screen read as three equal trips
|
||||
// when two of them were placeholders. The disabled token is what the
|
||||
// app already means by "nothing here to act on", and it is exactly one
|
||||
// voice quieter — the slot is still legible, it has simply stopped
|
||||
// competing.
|
||||
return ColorConstants.disabledFill;
|
||||
}
|
||||
return _percentOf(tripIndex) >= 100
|
||||
? ColorConstants.acceptGreen
|
||||
: ColorConstants.secondaryText;
|
||||
}
|
||||
|
||||
int _percentOf(int tripIndex) => widget.trips[tripIndex].completionPercent(
|
||||
acceptedIds: widget.acceptedIds,
|
||||
rejectedIds: widget.rejectedIds,
|
||||
);
|
||||
/// ── Slot, not position ──
|
||||
///
|
||||
/// Every lookup here goes through [TripSlots.tripAt]. Tab 1 is the morning
|
||||
/// whether or not the rider has morning work, so `trips[i]` — which meant
|
||||
/// "the i-th trip he happens to have" — would put an afternoon run under the
|
||||
/// Trip 1 tab on a day with no morning stops, and the rider's Trip 2 would
|
||||
/// not be the hub's.
|
||||
int _percentOf(int tripIndex) =>
|
||||
widget.trips
|
||||
.tripAt(tripIndex)
|
||||
?.completionPercent(
|
||||
acceptedIds: widget.acceptedIds,
|
||||
rejectedIds: widget.rejectedIds,
|
||||
) ??
|
||||
0;
|
||||
|
||||
/// True when this trip has a stop in progress — the rider is on it now.
|
||||
///
|
||||
@@ -231,7 +259,10 @@ class _TripTabsState extends State<TripTabs>
|
||||
margin: EdgeInsets.fromLTRB(16.w, 2.h, 16.w, 4.h),
|
||||
padding: EdgeInsets.all(3.w),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.neutralLight,
|
||||
// Layer 1 on the canvas, same as every working surface on Home.
|
||||
// `neutralLight` separated from the old cream backing by 1.02:1 —
|
||||
// a track you had to be told about.
|
||||
color: MilerSurface.working,
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusXl),
|
||||
),
|
||||
// LayoutBuilder OUTSIDE IntrinsicHeight, not inside it. LayoutBuilder
|
||||
@@ -286,7 +317,9 @@ class _TripTabsState extends State<TripTabs>
|
||||
// tint, so the two controls a rider uses most looked
|
||||
// like they came from different apps.
|
||||
color: ColorConstants.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusLg),
|
||||
borderRadius: BorderRadius.circular(
|
||||
DesignConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
@@ -304,7 +337,9 @@ class _TripTabsState extends State<TripTabs>
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: markColour,
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusFull),
|
||||
borderRadius: BorderRadius.circular(
|
||||
DesignConstants.radiusFull,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -334,14 +369,13 @@ class _TripTabsState extends State<TripTabs>
|
||||
return Expanded(
|
||||
child: _Tab(
|
||||
label: Trip.tabLabel(i),
|
||||
percent: i < widget.trips.length
|
||||
? _percentOf(i)
|
||||
: 0,
|
||||
assigned: i < widget.trips.length,
|
||||
percent: _percentOf(i),
|
||||
assigned: widget.trips.tripAt(i) != null,
|
||||
selected: i == widget.selectedIndex,
|
||||
active:
|
||||
i < widget.trips.length &&
|
||||
_isRunning(widget.trips[i]),
|
||||
active: switch (widget.trips.tripAt(i)) {
|
||||
final t? => _isRunning(t),
|
||||
_ => false,
|
||||
},
|
||||
lit: lit,
|
||||
restColour: _restColourOf(i),
|
||||
litColour: _litColourOf(i),
|
||||
@@ -479,8 +513,14 @@ class _PinnedSurface extends StatelessWidget {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 160),
|
||||
decoration: BoxDecoration(
|
||||
// Opaque, or the stop cards show through the bar as they pass under it.
|
||||
color: ColorConstants.surface,
|
||||
// Opaque, or the stop cards show through the bar as they pass under
|
||||
// it. And it is the CANVAS, not `ColorConstants.surface` — that token
|
||||
// is the warm near-white the surface audit flagged as "warm, unlike
|
||||
// everything else", and here it painted a cream band across the top
|
||||
// of a cool grey page. The bar is the page holding its position, so
|
||||
// it wears the page's own ground; the white segmented track stands on
|
||||
// it at the full canvas step.
|
||||
color: MilerSurface.canvas,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: floating ? ColorConstants.borderStrong : Colors.transparent,
|
||||
@@ -552,7 +592,10 @@ class _Tab extends StatelessWidget {
|
||||
final Color accent = Color.lerp(restColour, litColour, lit)!;
|
||||
|
||||
final String status = !assigned
|
||||
? 'Not set'
|
||||
// "Not set" read as a chore waiting for the rider. The hub fills
|
||||
// these slots; all this tab can honestly say is that nothing has
|
||||
// arrived for it yet.
|
||||
? 'None yet'
|
||||
: complete
|
||||
? 'Done'
|
||||
: active
|
||||
@@ -581,7 +624,9 @@ class _Tab extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
// A placeholder slot also gives up the headline weight —
|
||||
// colour alone was not enough separation on a sunlit LCD.
|
||||
fontWeight: assigned ? FontWeight.w700 : FontWeight.w600,
|
||||
letterSpacing: -0.3,
|
||||
color: accent,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -598,7 +643,7 @@ class _Tab extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 10.5.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accent,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -658,17 +703,17 @@ class _OverflowMenu extends StatelessWidget {
|
||||
Trip.tabLabel(i),
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.w),
|
||||
Text(
|
||||
i < trips.length ? trips[i].slotLabel : 'Not assigned',
|
||||
trips.tripAt(i)?.slotLabel ?? 'Not assigned',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -684,7 +729,7 @@ class _OverflowMenu extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.expand_more_rounded,
|
||||
LucideIcons.chevronDown,
|
||||
size: 22.sp,
|
||||
color: ColorConstants.slateText,
|
||||
),
|
||||
@@ -692,7 +737,7 @@ class _OverflowMenu extends StatelessWidget {
|
||||
'+${hidden.length}',
|
||||
style: TextStyle(
|
||||
fontSize: 11.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user