production
This commit is contained in:
@@ -4,12 +4,14 @@ import 'dart:math' as math;
|
||||
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';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import 'miler_app_bar.dart' show milerGlassSurface;
|
||||
import 'package:miler/Models/stop_status.dart';
|
||||
import 'package:miler/views/helpers/constants/miler_type.dart';
|
||||
import 'package:miler/views/helpers/widgets/miler_app_bar.dart'
|
||||
show milerBrandSurface;
|
||||
|
||||
import '../constants/Colorconstants.dart';
|
||||
import '../constants/design_constants.dart';
|
||||
@@ -24,6 +26,96 @@ import '../constants/Font_constant.dart';
|
||||
/// every screen stays visually consistent.
|
||||
/// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// A BAND — a section that runs the full width of the phone
|
||||
///
|
||||
/// The alternative, and what most of this app still does, is a card: a
|
||||
/// surface inset from both edges by a page margin, closed with a radius and
|
||||
/// usually an outline. That shape is right when a screen shows a *list of
|
||||
/// objects* — a stop, a job, a booking — because the margin is what says
|
||||
/// "these are separate things".
|
||||
///
|
||||
/// It is wrong for a screen that is one long thing: a form, a review, a
|
||||
/// receipt. There the margin buys nothing and costs twice. It costs width —
|
||||
/// 32 to 40 points off a 390pt phone, spent on two strips of empty ground
|
||||
/// beside text that is mostly addresses — and it costs a second boundary,
|
||||
/// because the gap between two cards already separates them and the outline
|
||||
/// says it again.
|
||||
///
|
||||
/// A band takes the other trade: it runs edge to edge, the page's ground
|
||||
/// showing between one band and the next is the only separator, and the
|
||||
/// margin moves *inside* as padding, where it is air around words rather than
|
||||
/// air around a box. Same breathing room at the left of a line, ~40pt more
|
||||
/// line to breathe in.
|
||||
///
|
||||
/// ── The rail ──
|
||||
///
|
||||
/// A band cannot carry state in an outline it does not have, so state that has
|
||||
/// to read at arm's length goes in the fill, and a finished band adds [rail] —
|
||||
/// a short accent stripe down its leading edge. It is the one mark that stays
|
||||
/// legible when a phone is at arm's length in sun, and it is cheap: three
|
||||
/// points of colour rather than a whole tinted border.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
class MilerBand extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
/// The band's ground. White unless the band's state says otherwise, and an
|
||||
/// **opaque** colour either way — see [ColorConstants.tint] for why a
|
||||
/// translucent wash is the wrong tool for a surface.
|
||||
final Color? color;
|
||||
|
||||
/// An accent stripe down the leading edge, for a band whose state has to
|
||||
/// read without being examined. Null draws no stripe and no inset.
|
||||
final Color? rail;
|
||||
|
||||
/// Overrides the standard inset. Pass only where the child owns its own
|
||||
/// horizontal padding — a row that has to bleed to the band's edge.
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const MilerBand({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.rail,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
/// The inset a band keeps around its content.
|
||||
///
|
||||
/// One number for the whole app. With the bands running edge to edge, their
|
||||
/// padding is the only thing left aligning the page's left margin — two
|
||||
/// bands a couple of points apart is exactly how a column of text stops
|
||||
/// looking like a column.
|
||||
static double get pad => 20.w;
|
||||
|
||||
/// The gap of page ground between one band and the next. This is the only
|
||||
/// separator a band has, so it is a real gap rather than a hairline.
|
||||
static double get gap => 10.h;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Animated because [color] and [rail] carry state: a section going from
|
||||
// outstanding to finished should be seen to change, and a hard cut on a
|
||||
// full-width fill reads as the screen having been replaced. Bands whose
|
||||
// state never moves pay nothing for this.
|
||||
return AnimatedContainer(
|
||||
duration: DesignConstants.motionState,
|
||||
curve: Curves.easeOut,
|
||||
width: double.infinity,
|
||||
padding: padding ?? EdgeInsets.symmetric(horizontal: pad, vertical: 16.h),
|
||||
decoration: BoxDecoration(
|
||||
color: color ?? ColorConstants.pureSurface,
|
||||
border: rail == null
|
||||
? null
|
||||
: Border(
|
||||
left: BorderSide(color: rail!, width: 3.w),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// White rounded card with the design-system shadow.
|
||||
class AppCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
@@ -792,6 +884,12 @@ PreferredSizeWidget milerPageBar(
|
||||
/// True for a screen that genuinely *dismisses* rather than pops — a sheet
|
||||
/// presented modally. Only this gets the cross.
|
||||
bool isModal = false,
|
||||
|
||||
/// The colour of the surface directly under the bar, so the curve at its
|
||||
/// bottom corners opens onto the page rather than onto more brand.
|
||||
/// Transparent reveals the Scaffold's own background, which is right for
|
||||
/// every pushed page here. See [milerBrandSurface].
|
||||
Color pageColor = Colors.transparent,
|
||||
}) {
|
||||
final IconData backIcon = isModal ? LucideIcons.x : LucideIcons.arrowLeft;
|
||||
final bool hasControlRow = onBack != null || (actions?.isNotEmpty ?? false);
|
||||
@@ -805,10 +903,15 @@ PreferredSizeWidget milerPageBar(
|
||||
// the phone AND the colour of the status-bar glyphs, which reads as leaving
|
||||
// the app rather than opening a page inside it.
|
||||
//
|
||||
// Solid [ColorConstants.primary], and everything on it inverts. The wash
|
||||
// survives as `milerGlassSurface()` for the sheets, which sit over content
|
||||
// and still want it.
|
||||
backgroundColor: ColorConstants.primary,
|
||||
// One ground, and everything on it inverts. The wash and the blur it was
|
||||
// painted behind are both gone; what the bar wears now is
|
||||
// [kBrandGradient], with its bottom corners taken off.
|
||||
//
|
||||
// **Transparent**, and the surface does all the painting: an opaque fill
|
||||
// behind a rounded surface fills in the two corners the curve exists to
|
||||
// open. See [milerBrandSurface].
|
||||
backgroundColor: Colors.transparent,
|
||||
flexibleSpace: milerBrandSurface(pageColor: pageColor),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
@@ -937,17 +1040,28 @@ Duration staggerDelay(int index, {int stepMs = 45, int maxMs = 320}) =>
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// GLASS CARD — the one surface every list card in the app is drawn on.
|
||||
///
|
||||
/// A translucent pane, a soft wide shadow, and **no border**.
|
||||
/// An opaque surface, a hairline, and a soft wide shadow.
|
||||
///
|
||||
/// ── Why no border ──
|
||||
/// ── The name is older than the material ──
|
||||
///
|
||||
/// A border, a fill and a shadow are three devices doing one job, and the
|
||||
/// border is the one that competes with the content: six bordered rectangles
|
||||
/// down a phone screen read as a form to be filled in, not a route to be
|
||||
/// ridden. Every large logistics app the rider already uses — Uber, Swiggy,
|
||||
/// Amazon Shopper — separates list cards with elevation alone, and it survives
|
||||
/// what a hairline does not: a scratched screen in direct sun, where a 1px
|
||||
/// #E2E8F0 line is simply not there.
|
||||
/// It was a translucent pane with no border at all, and both halves of that
|
||||
/// have been undone. The pane went opaque because a translucent card takes its
|
||||
/// contrast from whatever happens to be behind it — the same address is crisp
|
||||
/// over the canvas and grey over a map — and the hairline came back because on
|
||||
/// the budget LCD this app ships to, a shadow crushes to nothing and a
|
||||
/// borderless card reads as printed onto the page.
|
||||
///
|
||||
/// The name is kept because seven call sites use it and a rename buys nothing.
|
||||
///
|
||||
/// ── What the border is and is not ──
|
||||
///
|
||||
/// The risk a border carries is that it competes with the content: six heavily
|
||||
/// bordered rectangles down a phone screen read as a form to be filled in, not
|
||||
/// a route to be ridden. So it is a *hairline* and it is nearly the card's own
|
||||
/// colour — it is not asked to separate the card from the page, which is the
|
||||
/// shadow's job and the canvas step's. It is asked to finish an edge that the
|
||||
/// shadow has already softened, and to be the thing still standing when the
|
||||
/// shadow is crushed away by sunlight.
|
||||
///
|
||||
/// ── Why one widget rather than a decoration constant ──
|
||||
///
|
||||
@@ -969,8 +1083,8 @@ class GlassCard extends StatelessWidget {
|
||||
final String? semanticLabel;
|
||||
final String? semanticHint;
|
||||
|
||||
/// A wash over the pane, for the one card on a screen that is *live*.
|
||||
/// Deliberately a whisper — see [ColorConstants.glassCardLive].
|
||||
/// The card's fill, for the one card on a screen that is *live*. Deliberately
|
||||
/// a whisper — see [ColorConstants.glassCardLive].
|
||||
final Color? tint;
|
||||
|
||||
/// Dims the whole card without changing a single colour inside it, for a
|
||||
@@ -998,31 +1112,26 @@ class GlassCard extends StatelessWidget {
|
||||
margin: margin,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConstants.glassCard,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
// ── The rim is what a budget LCD can actually render ──
|
||||
// ── The tint replaces the fill, it does not sit over it ──
|
||||
//
|
||||
// On a calibrated panel the slate shadow separates the pane from the
|
||||
// It used to be a translucent wash laid *inside* the card, on the
|
||||
// reasoning that a live card should be the same glass as everything
|
||||
// else, warmed. With opaque surfaces that overlay would cover the
|
||||
// content rather than warm it, so the live card is simply a card with a
|
||||
// different fill — which is what it always looked like anyway.
|
||||
color: tint ?? ColorConstants.glassCard,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
// ── The hairline is what a budget LCD can actually render ──
|
||||
//
|
||||
// On a calibrated panel the shadow alone separates the card from the
|
||||
// ground; on the LCD this app ships to, shadows crush to nothing and
|
||||
// the cards read as printed on the page — reported twice from the
|
||||
// device. The one-pixel rim is the same trick the frosted sheets use
|
||||
// ([milerGlassSheet]'s glassRim): a line of light on the curve costs
|
||||
// nothing at any brightness and says "edge" where blur cannot.
|
||||
// device. A one-pixel edge costs nothing at any brightness and says
|
||||
// "edge" where a blur cannot.
|
||||
border: Border.all(color: ColorConstants.borderSubtle),
|
||||
boxShadow: DesignConstants.shadowGlass,
|
||||
),
|
||||
// The tint rides *inside* the pane rather than replacing it, so a live
|
||||
// card is the same glass everything else is, warmed — not a different
|
||||
// material.
|
||||
child: tint == null
|
||||
? child
|
||||
: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: tint,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
final tappable = onTap == null
|
||||
|
||||
Reference in New Issue
Block a user