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:
100
lib/data/load_state.dart
Normal file
100
lib/data/load_state.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// WHAT A SCREEN IS SHOWING, AS ONE VALUE
|
||||
///
|
||||
/// Screens carried three or four independent booleans — `_firstLoadDone`,
|
||||
/// `_fetching`, `_failed`, plus a list that might be empty — and every screen
|
||||
/// combined them slightly differently. That is how a page ends up showing an
|
||||
/// empty state during a refresh, or a spinner over stale data, or nothing at
|
||||
/// all when a request fails.
|
||||
///
|
||||
/// One value, and the combinations that cannot happen are unrepresentable.
|
||||
///
|
||||
/// ── Empty is not a failure, and unavailable is neither ──
|
||||
///
|
||||
/// Three answers a rider acts on differently, which were all `[]` before:
|
||||
///
|
||||
/// * **[LoadEmpty]** — the hub has given him nothing today. Wait.
|
||||
/// * **[LoadFailure]** — the request did not complete. Retry.
|
||||
/// * **[LoadUnavailable]** — his line of work has no endpoint behind it. Neither
|
||||
/// waiting nor retrying will help, and telling him to do either is a lie.
|
||||
/// See `ServiceProfile.hasBookingsEndpoint`.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
library;
|
||||
|
||||
/// Why a load did not produce data. Kept separate from the HTTP status because
|
||||
/// the rider's next action is what differs, not the number.
|
||||
enum LoadFailureKind {
|
||||
/// No usable connection, a timeout, or a socket that died mid-flight.
|
||||
offline,
|
||||
|
||||
/// The session is over. The shell signs the rider out; the screen should not
|
||||
/// offer a retry that cannot succeed.
|
||||
unauthorized,
|
||||
|
||||
/// Too many requests. Retrying immediately makes it worse.
|
||||
rateLimited,
|
||||
|
||||
/// The server answered and refused, or answered with something unreadable.
|
||||
server;
|
||||
|
||||
/// Whether offering "Try again" is honest.
|
||||
bool get isRetryable => this != LoadFailureKind.unauthorized;
|
||||
}
|
||||
|
||||
/// The state of one screen's data.
|
||||
sealed class LoadState<T> {
|
||||
const LoadState();
|
||||
|
||||
/// The data, when there is any. Null in every other state — so a screen that
|
||||
/// forgets to handle a case renders nothing rather than stale content.
|
||||
T? get valueOrNull => switch (this) {
|
||||
LoadData<T>(:final value) => value,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
bool get isLoading => this is LoadLoading<T>;
|
||||
}
|
||||
|
||||
/// First load, or a refresh with nothing to show yet.
|
||||
class LoadLoading<T> extends LoadState<T> {
|
||||
const LoadLoading();
|
||||
}
|
||||
|
||||
/// Data arrived and there is something in it.
|
||||
class LoadData<T> extends LoadState<T> {
|
||||
final T value;
|
||||
|
||||
/// True while a refresh is running behind data that is already on screen.
|
||||
///
|
||||
/// The distinction the old booleans lost: a pull-to-refresh must not blank
|
||||
/// the list it is refreshing.
|
||||
final bool refreshing;
|
||||
|
||||
const LoadData(this.value, {this.refreshing = false});
|
||||
}
|
||||
|
||||
/// The request succeeded and the answer was "nothing today".
|
||||
class LoadEmpty<T> extends LoadState<T> {
|
||||
const LoadEmpty();
|
||||
}
|
||||
|
||||
/// The request did not complete.
|
||||
class LoadFailure<T> extends LoadState<T> {
|
||||
final LoadFailureKind kind;
|
||||
|
||||
/// The server's own sentence where it gave one — it is more useful than
|
||||
/// anything this app can invent about a failure it did not cause.
|
||||
final String message;
|
||||
|
||||
const LoadFailure(this.kind, {this.message = ''});
|
||||
}
|
||||
|
||||
/// There is no backend for this rider's line of work.
|
||||
///
|
||||
/// Not an error and not an empty day: a capability the deployment does not have
|
||||
/// yet. Retrying cannot fix it and neither can waiting.
|
||||
class LoadUnavailable<T> extends LoadState<T> {
|
||||
final String reason;
|
||||
|
||||
const LoadUnavailable(this.reason);
|
||||
}
|
||||
Reference in New Issue
Block a user