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,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
@@ -50,7 +51,7 @@ import 'package:miler/views/helpers/widgets/app_widgets.dart';
|
||||
abstract final class AuthType {
|
||||
static TextStyle get title => TextStyle(
|
||||
fontSize: 27.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.7,
|
||||
height: 1.15,
|
||||
color: ColorConstants.slateText,
|
||||
@@ -67,7 +68,7 @@ abstract final class AuthType {
|
||||
|
||||
static TextStyle get fieldLabel => TextStyle(
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.5,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -75,7 +76,7 @@ abstract final class AuthType {
|
||||
|
||||
static TextStyle get meta => TextStyle(
|
||||
fontSize: 12.5.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.45,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -168,7 +169,7 @@ class AuthSteps extends StatelessWidget {
|
||||
'STEP $step OF $total',
|
||||
style: TextStyle(
|
||||
fontSize: 10.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.0,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -188,7 +189,9 @@ class AuthSteps extends StatelessWidget {
|
||||
color: i <= step
|
||||
? ColorConstants.primary
|
||||
: ColorConstants.borderStrong,
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusFull),
|
||||
borderRadius: BorderRadius.circular(
|
||||
DesignConstants.radiusFull,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -278,7 +281,7 @@ class AuthScaffold extends StatelessWidget {
|
||||
onTap: onBack!,
|
||||
semanticLabel: 'Back',
|
||||
child: Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
LucideIcons.arrowLeft,
|
||||
size: 22.sp,
|
||||
color: ColorConstants.slateText,
|
||||
),
|
||||
@@ -385,7 +388,7 @@ class AuthScaffold extends StatelessWidget {
|
||||
onTap: onBack!,
|
||||
semanticLabel: 'Back',
|
||||
child: Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
LucideIcons.arrowLeft,
|
||||
size: 22.sp,
|
||||
color: ColorConstants.onAccent,
|
||||
),
|
||||
@@ -513,7 +516,8 @@ class AuthBrandBanner extends StatelessWidget {
|
||||
duration: DesignConstants.motionState,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: double.infinity,
|
||||
height: (typing ? _typingHeight : _restHeight).h +
|
||||
height:
|
||||
(typing ? _typingHeight : _restHeight).h +
|
||||
MediaQuery.paddingOf(context).top,
|
||||
color: ColorConstants.primary,
|
||||
child: ClipRect(
|
||||
@@ -526,8 +530,16 @@ class AuthBrandBanner extends StatelessWidget {
|
||||
// ornament. Bled off both edges and held at 7% white: at that
|
||||
// weight they read as texture on the brand colour and never as
|
||||
// objects the rider might try to tap.
|
||||
Positioned(left: -78.w, top: -34.h, child: const _BrandRing(size: 196)),
|
||||
Positioned(right: -92.w, bottom: -78.h, child: const _BrandRing(size: 244)),
|
||||
Positioned(
|
||||
left: -78.w,
|
||||
top: -34.h,
|
||||
child: const _BrandRing(size: 196),
|
||||
),
|
||||
Positioned(
|
||||
right: -92.w,
|
||||
bottom: -78.h,
|
||||
child: const _BrandRing(size: 244),
|
||||
),
|
||||
|
||||
Center(
|
||||
child: Padding(
|
||||
@@ -566,7 +578,7 @@ class AuthBrandBanner extends StatelessWidget {
|
||||
'MILER · RIDER APP',
|
||||
style: TextStyle(
|
||||
fontSize: 10.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.6,
|
||||
color: ColorConstants.onAccent.withValues(alpha: 0.72),
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -667,7 +679,7 @@ class AuthPrimaryButton extends StatelessWidget {
|
||||
const AuthPrimaryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.icon = Icons.arrow_forward_rounded,
|
||||
this.icon = LucideIcons.arrowRight,
|
||||
this.loading = false,
|
||||
required this.onPressed,
|
||||
});
|
||||
@@ -719,7 +731,7 @@ class AuthPrimaryButton extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.2,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -774,7 +786,7 @@ class AuthTextAction extends StatelessWidget {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -812,7 +824,7 @@ class AuthInlineError extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline_rounded,
|
||||
LucideIcons.circleAlert,
|
||||
size: 15.sp,
|
||||
color: ColorConstants.errorRed,
|
||||
),
|
||||
@@ -823,7 +835,7 @@ class AuthInlineError extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: ColorConstants.errorRed,
|
||||
fontSize: 12.5.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.35,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -865,31 +877,65 @@ class AuthCodeRow extends StatelessWidget {
|
||||
this.hasError = false,
|
||||
});
|
||||
|
||||
/// ── The boxes stopped stretching ──
|
||||
///
|
||||
/// Each box was `Expanded` inside an `AspectRatio`, so four of them divided
|
||||
/// the *whole* content width between themselves: on a 390pt phone that is a
|
||||
/// **79×83pt** box, four of them, filling the screen edge to edge. A PIN
|
||||
/// field that size reads as a keypad rather than as an input — the digits
|
||||
/// float in the middle of a large empty square, the group dominates a screen
|
||||
/// whose only job is four taps, and it gets worse on a tablet, where the
|
||||
/// boxes keep growing with the window.
|
||||
///
|
||||
/// Every app that does this well — the ones a rider already uses to bank and
|
||||
/// to sign in — sizes the box to the *digit*, not to the screen, and centres
|
||||
/// the group. 56pt is a comfortable target well above the 44pt floor, and
|
||||
/// four of them plus their gaps come to 260 of the 390 available, so the
|
||||
/// group reads as one control with air around it instead of a wall.
|
||||
///
|
||||
/// The size is a *preference*, not a constant: [LayoutBuilder] caps it at
|
||||
/// whatever the width actually allows, so a 6-digit code on a 320pt phone
|
||||
/// shrinks to fit rather than overflowing. It can get smaller than preferred;
|
||||
/// it can never get bigger.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final count = controllers.length;
|
||||
// Taller boxes when there are fewer of them: a 4-digit PIN gets room to
|
||||
// feel deliberate, a 6-digit code has to stay inside the width.
|
||||
final ratio = count > 4 ? 1.20 : 1.05;
|
||||
final double gap = MilerCodeBox.gap(context).w * (count > 4 ? 1 : 1.5);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
for (var i = 0; i < count; i++) ...[
|
||||
if (i > 0) SizedBox(width: MilerCodeBox.gap(context).w),
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1 / ratio,
|
||||
child: _Box(
|
||||
controller: controllers[i],
|
||||
node: nodes[i],
|
||||
obscure: obscure,
|
||||
hasError: hasError,
|
||||
onChanged: (v) => onChanged(v, i),
|
||||
// A 4-digit PIN gets the full target; a 6-digit code starts smaller so it
|
||||
// is not relying on the cap to be legible on a narrow phone.
|
||||
final double preferred = (count > 4 ? 46 : 56).w;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final double available = constraints.maxWidth - gap * (count - 1);
|
||||
final double box = preferred < available / count
|
||||
? preferred
|
||||
: available / count;
|
||||
// Barely taller than it is wide. A tall box puts the digit in the
|
||||
// middle of empty space; this reads as a field.
|
||||
final double height = box * 1.12;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for (var i = 0; i < count; i++) ...[
|
||||
if (i > 0) SizedBox(width: gap),
|
||||
SizedBox(
|
||||
width: box,
|
||||
height: height,
|
||||
child: _Box(
|
||||
controller: controllers[i],
|
||||
node: nodes[i],
|
||||
obscure: obscure,
|
||||
hasError: hasError,
|
||||
onChanged: (v) => onChanged(v, i),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user