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,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import 'package:miler/data/service_profile.dart';
|
||||
import 'package:miler/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:miler/views/helpers/constants/design_constants.dart';
|
||||
import 'package:miler/views/helpers/constants/Font_constant.dart';
|
||||
@@ -17,7 +19,7 @@ import 'package:miler/widget/Bottom_page.dart';
|
||||
/// three stops and scrolled back to the top had nothing on screen telling him
|
||||
/// so, and no way through.
|
||||
///
|
||||
/// This says both things in one control, in one fixed place: **how many
|
||||
/// This says both things in one control, in one fixed p~lace: **how many
|
||||
/// bookings he is holding**, and one tap to go work them. It is the counterpart
|
||||
/// to [SelectionBar] — that one is about the stops he is choosing, this one
|
||||
/// about the stops he has already taken — so they share the floating slot at
|
||||
@@ -29,6 +31,10 @@ import 'package:miler/widget/Bottom_page.dart';
|
||||
/// at some point today".
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
class AcceptedPill extends StatelessWidget {
|
||||
/// The tallest this pill gets, excluding the device inset. See
|
||||
/// [SelectionBar.maxHeight] for why the surface declares its own.
|
||||
static double get maxHeight => ButtonSizes.compact + 24.h;
|
||||
|
||||
/// Accepted bookings still waiting to be worked. Zero renders nothing.
|
||||
final int count;
|
||||
|
||||
@@ -41,6 +47,8 @@ class AcceptedPill extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
if (count <= 0) return const SizedBox.shrink();
|
||||
|
||||
final profile = ServiceProfile.active;
|
||||
|
||||
final radius = BorderRadius.circular(DesignConstants.radiusFull);
|
||||
|
||||
// Rides the floating nav bar: it is `Positioned` outside the Scaffold's
|
||||
@@ -64,11 +72,29 @@ class AcceptedPill extends StatelessWidget {
|
||||
alignment: Alignment.centerRight,
|
||||
child: Semantics(
|
||||
button: true,
|
||||
// The noun follows the rider's line of work — "booking" on a
|
||||
// parcel route, "delivery" on a meal run. See [ServiceProfile].
|
||||
label: count == 1
|
||||
? '1 accepted booking. Go to Bookings.'
|
||||
: '$count accepted bookings. Go to Bookings.',
|
||||
? '1 ${profile.jobNoun} ${_verb(profile)}. '
|
||||
'Go to ${profile.workTabLabel}.'
|
||||
: '$count ${profile.jobNounPlural} ${_verb(profile)}. '
|
||||
'Go to ${profile.workTabLabel}.',
|
||||
// ── Red is for the action, not for the notice ──
|
||||
//
|
||||
// This was a solid brand-red slab with a white disc in it, floating
|
||||
// over the foot of the route. On a page whose one accent moment is
|
||||
// supposed to be the journey button, it was the largest and
|
||||
// reddest object on the screen — and it is not an action the screen
|
||||
// exists for at all. It is a *notice with a way through*: "you are
|
||||
// holding nine of these, they are over there."
|
||||
//
|
||||
// So the surface goes quiet — the same lifted white the selection
|
||||
// bar uses, which is the app's one "floating over the list"
|
||||
// material — and the brand is spent on the two things that carry
|
||||
// the meaning: the count and the arrow out. Same height, same
|
||||
// wording, a third of the ink.
|
||||
child: Material(
|
||||
color: ColorConstants.primary,
|
||||
color: ColorConstants.pureSurface,
|
||||
borderRadius: radius,
|
||||
elevation: 0,
|
||||
child: InkWell(
|
||||
@@ -78,10 +104,11 @@ class AcceptedPill extends StatelessWidget {
|
||||
// 48 clears the Material touch-target floor without the pill
|
||||
// reading as a primary CTA.
|
||||
height: 48.h,
|
||||
padding: EdgeInsets.fromLTRB(8.w, 0, 14.w, 0),
|
||||
padding: EdgeInsets.fromLTRB(7.w, 0, 10.w, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.pureSurface,
|
||||
borderRadius: radius,
|
||||
boxShadow: DesignConstants.shadowLg,
|
||||
boxShadow: DesignConstants.shadowFloat,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -90,41 +117,52 @@ class AcceptedPill extends StatelessWidget {
|
||||
// is the thing the rider is checking from across the
|
||||
// handlebars, and it changes while the words do not.
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.w,
|
||||
width: 30.w,
|
||||
height: 30.w,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.pureSurface,
|
||||
color: ColorConstants.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1,
|
||||
letterSpacing: -0.3,
|
||||
color: ColorConstants.primary,
|
||||
color: ColorConstants.onAccent,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 9.w),
|
||||
Text(
|
||||
count == 1 ? 'booking accepted' : 'bookings accepted',
|
||||
// ── The word has to match the boundary ──
|
||||
//
|
||||
// It said "accepted" on both lines. On a round the
|
||||
// count is `WorkBoundary.deliveryQueue` — work already
|
||||
// **collected** and waiting to be delivered — so a
|
||||
// rider holding twenty-one bags read "21 deliveries
|
||||
// accepted", which is the wrong verb for the wrong
|
||||
// rung and understates what he is carrying.
|
||||
//
|
||||
// Logistics hands over at acceptance, so there the word
|
||||
// is right. Same knob the boundary itself reads.
|
||||
_waiting(profile, count),
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.1,
|
||||
color: Colors.white,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 2.w),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
LucideIcons.chevronRight,
|
||||
size: 20.sp,
|
||||
color: Colors.white,
|
||||
color: ColorConstants.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -136,4 +174,19 @@ class AcceptedPill extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The verb for the rung this count actually sits on.
|
||||
///
|
||||
/// The pill counts `WorkBoundary.deliveryQueue`, and where that boundary sits
|
||||
/// is a property of the line — [ServiceProfile.handoffAt]. On a round it is
|
||||
/// **pickup-complete**, so the work is in the rider's hands; on logistics it
|
||||
/// is acceptance, so "accepted" is exactly right. One knob, read here rather
|
||||
/// than a line-name check.
|
||||
static String _verb(ServiceProfile profile) =>
|
||||
profile.handsOffAtCollection ? 'in hand' : 'accepted';
|
||||
|
||||
/// `deliveries in hand` · `bookings accepted`.
|
||||
static String _waiting(ServiceProfile profile, int count) =>
|
||||
'${count == 1 ? profile.jobNoun : profile.jobNounPlural} '
|
||||
'${_verb(profile)}';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user