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:
2026-08-22 05:40:35 +05:30
parent c8350563d9
commit d7348e253f
387 changed files with 80693 additions and 12272 deletions

View File

@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
/// The delivery line's palette.
///
/// ── Why these three values changed and nothing else did ──
///
/// The delivery screens are a verbatim port of the Xpress-rider app: same
/// layout, same components, same 4-tab bottom bar. What was *not* kept is the
/// brand. Xpress-rider was purple (#662582) with a lilac tint (#E7D3EF), and a
/// rider signing in to Doormile should not be looking at another company's
/// colour — the whole point of the port is that both lines are Doormile.
///
/// So the primary and its two tints are now the Doormile red the parcel line
/// uses (`lib/views/helpers/constants/Colorconstants.dart`, `primary`), and
/// every other entry below is untouched. Those others are semantic — greens for
/// success, ambers for categories — and carry no brand.
class ColorConstants {
/// Doormile red. Was #662582, Xpress-rider's purple.
static const primaryColor = Color(0xFF960019);
/// The 8%-ish wash behind selected rows and icon badges. Was #E7D3EF, the
/// lilac derived from the purple; now the red's equivalent, so a tinted
/// surface still reads as "the brand, quietly" rather than as a second hue.
static Color? primaryColor1 = const Color(0xFFF6E0E2);
static Color? secondaryColor = Colors.white;
/// Same wash as [primaryColor1], reached through the hex-string helper the
/// ported screens use on a handful of call sites.
static Color? ternaryColor = "#F6E0E2".toColor();
static Color? darkGreyColor = "575756".toColor();
static Color? lightGrey = "b2b2b2".toColor();
static Color? lightGreyBg = Colors.grey.shade100;
static Color? greenColor = "00b894".toColor();
static Color? mintColor = "69c0ac".toColor();
static Color restaurantColor = Colors.amber[100]!;
static Color groceriesColor = Colors.purple[100]!;
static Color shoppingColor = Colors.orange[100]!;
static Color healthColor = Colors.cyan[100]!;
static Color handymanColor = Colors.red[100]!;
static const blueColor = 0xff007AC2;
static const redColor = 0xffEF3F42;
static const orangeColor = 0xffFAAB53;
static Color? lightColor = const Color.fromRGBO(244, 244, 244, 1);
}
extension ColorExtenstion on String {
// ignore: body_might_complete_normally_nullable
Color? toColor() {
var hexColor = replaceAll("#", "");
if (hexColor.length == 6) {
hexColor = "FF$hexColor";
}
if (hexColor.length == 8) {
return Color(int.parse("0x$hexColor"));
}
}
}

View File

@@ -0,0 +1,111 @@
// ignore: file_names
import 'package:flutter/material.dart';
class FontConstants {
static String fontFamily = 'Proxima Nova';
// Base screen width for scaling (iPhone standard: 375)
static const double _baseWidth = 375.0;
// Base font sizes (for baseWidth = 375)
static const double _baseExtraSmall = 10.0;
static const double _baseSmall = 12.0;
static const double _baseMedium = 14.0;
static const double _baseRegular = 16.0;
static const double _baseLarge = 20.0;
static const double _baseXLarge = 21.0;
static const double _baseXXLarge = 22.0;
static const double _baseXXXLarge = 24.0;
static const double _baseHuge = 30.0;
/// Get fixed font size (no width scaling) so small & large phones look same.
static double getResponsiveFontSize(BuildContext context, double baseSize) {
return baseSize;
}
/// Extra Small Text (10px base) - For labels, captions
static double extraSmall(BuildContext context) =>
getResponsiveFontSize(context, _baseExtraSmall);
/// Small Text (12px base) - For small labels, timestamps
static double small(BuildContext context) =>
getResponsiveFontSize(context, _baseSmall);
/// Medium Text (14px base) - For body text, descriptions
static double medium(BuildContext context) =>
getResponsiveFontSize(context, _baseMedium);
/// Regular Text (16px base) - Standard body text, most common
static double regular(BuildContext context) =>
getResponsiveFontSize(context, _baseRegular);
/// Large Text (18px base) - For subheadings, important text
static double large(BuildContext context) =>
getResponsiveFontSize(context, _baseLarge);
/// Extra Large Text (20px base) - For headings, titles
static double xLarge(BuildContext context) =>
getResponsiveFontSize(context, _baseXLarge);
/// 2X Large Text (22px base) - For main headings
static double xxLarge(BuildContext context) =>
getResponsiveFontSize(context, _baseXXLarge);
/// 3X Large Text (24px base) - For prominent headings
static double xxxLarge(BuildContext context) =>
getResponsiveFontSize(context, _baseXXXLarge);
/// Huge Text (26px base) - For hero text, very prominent headings
static double huge(BuildContext context) =>
getResponsiveFontSize(context, _baseHuge);
}
class ReusableTextWidget extends StatelessWidget {
final String text;
final double? fontSize;
final double? textHeight;
final String? fontFamily;
final FontWeight? fontWeight;
final FontStyle? fontStyle;
final Color? color;
final TextAlign? textAlign;
final int? maxLines;
final TextDecoration? isUnderText;
const ReusableTextWidget({
super.key,
required this.text,
this.fontSize,
this.textHeight,
this.fontFamily,
this.fontWeight,
this.fontStyle,
this.color,
this.textAlign,
this.maxLines,
this.isUnderText,
});
@override
Widget build(BuildContext context) {
return Text(
text,
softWrap: true,
style: TextStyle(
fontSize: fontSize ?? FontConstants.medium(context),
decoration: isUnderText,
fontFamily: fontFamily ?? FontConstants.fontFamily,
decorationColor: color,
fontWeight: fontWeight ?? FontWeight.normal,
fontStyle: fontStyle ?? FontStyle.normal,
color: color ?? Colors.grey.shade900,
overflow: TextOverflow.ellipsis,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: 1,
height: textHeight,
),
maxLines: maxLines,
textAlign: textAlign ?? TextAlign.start,
);
}
}

View File

@@ -0,0 +1,81 @@
import 'package:miler/xpress/data/delivery_api.dart';
/// The delivery line's endpoint names, kept as a thin façade over [DeliveryApi].
///
/// ── Why this class still exists ──
///
/// The ported screens reference `ApiConstants.currentDeliveryLive` and friends
/// on roughly thirty call sites, most of them inside 1000-line `part` files that
/// were brought over verbatim. Renaming those call sites would mean editing the
/// ported flow, which is the one thing this port was meant not to do.
///
/// So the names survive and the values now resolve to Doormile routes. Every
/// URL — including the ones that used to be inline in `homepage.dart`,
/// `cartpage.dart`, `deliveries.dart` and `summary.dart` — is defined once, in
/// [DeliveryApi], with a LIVE / MAPPED / ABSENT marker saying whether the route
/// exists on the backend today. Read that file before trusting any call here.
///
/// ── The dev/live pairs collapsed ──
///
/// The old backend had a `dev` and a `live` deployment selected by a string
/// (`mainRoute`), so every endpoint came in two. Doormile has one API and one
/// base URL, so both members of each pair now return the same value. They are
/// kept only so the ported call sites — which branch on
/// `ApiConstants.mainRoute == 'live'` — keep compiling and land on the same
/// place either way.
class ApiConstants {
/// Retained because ported call sites branch on it. Both branches now resolve
/// to the same Doormile route.
static String mainDev = "dev";
static String mainRoute = "live";
//Delivery Queue — MAPPED to /miler/assignments
static String get deliveryQueueDev => DeliveryApi.deliveryQueue;
static String get deliveryQueueLive => DeliveryApi.deliveryQueue;
//Current Delivery — MAPPED to /miler/bookings
static String get currentDeliveryDev => DeliveryApi.currentDeliveries;
static String get currentDeliveryLive => DeliveryApi.currentDeliveries;
//Current Delivery V3 (was date-bounded) — MAPPED to /miler/bookings
static String get currentDeliveryV3Dev => DeliveryApi.currentDeliveries;
static String get currentDeliveryV3Live => DeliveryApi.currentDeliveries;
//Update Delivery — ABSENT, see DeliveryApi.updateDelivery
static String get updateDeliveryDev => DeliveryApi.updateDelivery;
static String get updateDeliveryLive => DeliveryApi.updateDelivery;
//Get Rider Log — LIVE
static String get getRiderLogDev => DeliveryApi.getRiderLog;
static String get getRiderLogLive => DeliveryApi.getRiderLog;
//Create Rider Log — LIVE
static String get createRiderLogDev => DeliveryApi.createRiderLog;
static String get createRiderLogLive => DeliveryApi.createRiderLog;
//Update Rider Log — LIVE (same collection, PUT semantics at the call site)
static String get updateRiderLogDev => DeliveryApi.createRiderLog;
static String get updateRiderLogLive => DeliveryApi.createRiderLog;
//Get Rider Count — ABSENT on Doormile; nearest real read is duty status.
static String get getRiderCountDev => DeliveryApi.currentDuty;
static String get getRiderCountLive => DeliveryApi.currentDuty;
//Create Break Rider Log — LIVE
static String get createBreakRiderLogDev => DeliveryApi.startBreak;
static String get createBreakRiderLogLive => DeliveryApi.startBreak;
//Update Break Rider Log — LIVE
static String get updateBreakRiderLogDev => DeliveryApi.endBreak;
static String get updateBreakRiderLogLive => DeliveryApi.endBreak;
//Create Delivery Log — ABSENT, see DeliveryApi.createDeliveryLog
static String get createDeliveryLogDev => DeliveryApi.createDeliveryLog;
static String get createDeliveryLogLive => DeliveryApi.createDeliveryLog;
//Summary — LIVE (/miler/earnings)
static String get summaryApiLive => DeliveryApi.earnings;
//Summary Rider Weekly KMs — ABSENT, see DeliveryApi.weeklyKms
static String get summaryriderkmLive => DeliveryApi.weeklyKms;
}

View File

@@ -0,0 +1,19 @@
class MqttConstants {
static const String brokerHost = '66.116.225.226'; // Updated with VPS IP
static const int brokerPort = 1883;
static const String username = 'admin';
static const String passwordString = 'Package@321#'; // Provided by user
// Topic Structure
static const String topicRiderStatus = 'doormile/riders/{riderId}/status';
static const String topicRiderProfile = 'doormile/riders/{riderId}/profile';
static const String topicRiderLocation = 'doormile/riders/{riderId}/location';
static const String topicRiderTelemetry =
'doormile/riders/{riderId}/telemetry';
static const String topicRiderLogs = 'doormile/riders/{riderId}/logs';
// Status Values
static const String statusOnline = 'Online';
static const String statusOffline = 'Offline';
static const String statusIdle = 'Idle';
}