|
|
|
|
@@ -10,32 +10,72 @@ enum DoormileEnvironment { dev, staging, prod }
|
|
|
|
|
/// Build-time configuration: which backend this build talks to, and the
|
|
|
|
|
/// identity it sends on every request.
|
|
|
|
|
///
|
|
|
|
|
/// ── The one bypass, and why it is not the Miler app's bypass ──
|
|
|
|
|
/// ── The offline build, and the two rules that keep it honest ──
|
|
|
|
|
///
|
|
|
|
|
/// The Miler app shipped a `USE_NEW_API=false` flag that silently swapped
|
|
|
|
|
/// authentication for a bypass — any four digits logged you in as a fake rider,
|
|
|
|
|
/// with invented earnings, in a build that could ship.
|
|
|
|
|
/// with invented earnings, in a build that could ship. This app grew its own
|
|
|
|
|
/// version of that and it cost two days of hunting for bookings in the admin
|
|
|
|
|
/// console that had never left the phone.
|
|
|
|
|
///
|
|
|
|
|
/// This app has one API implementation that talks to a server, and exactly one
|
|
|
|
|
/// exception to it: [useDevData], now the **default in a debug build** because
|
|
|
|
|
/// staging has no SMS gateway and the login screen cannot otherwise be walked.
|
|
|
|
|
/// Three things keep it from becoming the flag that was deleted — it cannot
|
|
|
|
|
/// exist in a release build (`!kReleaseMode`, not a define), it is named on the
|
|
|
|
|
/// Account screen every time it is on, and nothing it does reaches a server.
|
|
|
|
|
/// Neither failure was caused by a fake existing. Both were caused by a fake
|
|
|
|
|
/// that did not announce itself and that nothing stopped from shipping. So the
|
|
|
|
|
/// offline build is back — it is genuinely useful with no SMS gateway on the
|
|
|
|
|
/// backend — under two rules that close exactly those holes:
|
|
|
|
|
///
|
|
|
|
|
/// * **It cannot ship.** [useDevData] is `false` in a release whatever the
|
|
|
|
|
/// defines say, so no customer can be shown invented data.
|
|
|
|
|
/// * **It says so.** [describe] leads with DEV DATA (offline) the entire
|
|
|
|
|
/// time it is on, and the Account screen prints it.
|
|
|
|
|
///
|
|
|
|
|
/// It is also opt-in — `flutter run` still talks to the real API — which is the
|
|
|
|
|
/// property whose absence caused the original mess.
|
|
|
|
|
///
|
|
|
|
|
/// Separately, [autoLoginIdentifier] skips the login SCREEN without faking the
|
|
|
|
|
/// login: it runs the real OTP exchange against the real server and ends up
|
|
|
|
|
/// holding a server-issued token. Use that one when the bookings need to be
|
|
|
|
|
/// real; use [useDevData] when there is no server worth reaching.
|
|
|
|
|
class AppConfig {
|
|
|
|
|
AppConfig._();
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------- environment
|
|
|
|
|
|
|
|
|
|
static const String _envName = String.fromEnvironment(
|
|
|
|
|
'DM_ENV',
|
|
|
|
|
defaultValue: 'staging',
|
|
|
|
|
);
|
|
|
|
|
/// Empty when nobody said. The default is decided below rather than here,
|
|
|
|
|
/// because it is not the same answer for a release build as for a debug one.
|
|
|
|
|
static const String _envName = String.fromEnvironment('DM_ENV');
|
|
|
|
|
|
|
|
|
|
/// ── A shipped build is production unless it says otherwise ──
|
|
|
|
|
///
|
|
|
|
|
/// This defaulted to `staging` for every build, release included, and
|
|
|
|
|
/// `_stagingBase` is `_prodBase` — so a release APK built without
|
|
|
|
|
/// `--dart-define=DM_ENV=prod` talked to production while believing it was
|
|
|
|
|
/// staging. The URL was right and [isProd] was wrong, which is the worst of
|
|
|
|
|
/// the two to get wrong, because [isProd] is what the non-network guards read:
|
|
|
|
|
///
|
|
|
|
|
/// * the Account screen shows its Build panel — base URL and every build
|
|
|
|
|
/// flag — whenever [isProd] is false, so a shipped app showed diagnostics
|
|
|
|
|
/// to real customers;
|
|
|
|
|
/// * [allowStageOverride] and `setStage` both test it, so the guard against
|
|
|
|
|
/// driving the backend's QA stage endpoint was one dart-define away from
|
|
|
|
|
/// being off in the shop;
|
|
|
|
|
/// * [describe] labelled a production session "staging", which is what a
|
|
|
|
|
/// bug report would have carried.
|
|
|
|
|
///
|
|
|
|
|
/// Release now defaults to prod and debug/profile to staging, so forgetting
|
|
|
|
|
/// the flag fails safe in both directions: a shipped build cannot quietly
|
|
|
|
|
/// behave like a test one, and a developer build cannot quietly claim to be
|
|
|
|
|
/// production. An explicit `DM_ENV` still wins everywhere — including
|
|
|
|
|
/// `DM_ENV=staging` on a release build, which is a deliberate act.
|
|
|
|
|
///
|
|
|
|
|
/// This changes no network behaviour today: staging and prod resolve to the
|
|
|
|
|
/// same host until the backend names a staging one.
|
|
|
|
|
static DoormileEnvironment get environment => switch (_envName) {
|
|
|
|
|
'prod' || 'production' => DoormileEnvironment.prod,
|
|
|
|
|
'dev' || 'development' => DoormileEnvironment.dev,
|
|
|
|
|
_ => DoormileEnvironment.staging,
|
|
|
|
|
'staging' => DoormileEnvironment.staging,
|
|
|
|
|
_ => kReleaseMode
|
|
|
|
|
? DoormileEnvironment.prod
|
|
|
|
|
: DoormileEnvironment.staging,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool get isProd => environment == DoormileEnvironment.prod;
|
|
|
|
|
@@ -75,38 +115,13 @@ class AppConfig {
|
|
|
|
|
static String url(String path) =>
|
|
|
|
|
'$baseUrl/customer${path.startsWith('/') ? path : '/$path'}';
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------- development data
|
|
|
|
|
//
|
|
|
|
|
// ── The one exception to "no invented data", and it is now the debug default ──
|
|
|
|
|
//
|
|
|
|
|
// The shipping app has one API implementation and talks to a server; a build
|
|
|
|
|
// that cannot reach one shows its error state rather than inventing a
|
|
|
|
|
// booking, a slot or a price. [useDevData] is the single, loud exception.
|
|
|
|
|
//
|
|
|
|
|
// **Off by default again.** It was briefly the debug default so the app could
|
|
|
|
|
// be opened at all, and the cost showed up immediately: a booking made in
|
|
|
|
|
// that mode reaches no server and never appears in the admin console, which
|
|
|
|
|
// looks exactly like a broken integration. Skipping the login screen is worth
|
|
|
|
|
// a flag; faking every booking behind it is not.
|
|
|
|
|
//
|
|
|
|
|
// For a session without typing a code, use [autoLoginIdentifier] instead —
|
|
|
|
|
// it signs in against the **real** API, so everything after it is real.
|
|
|
|
|
//
|
|
|
|
|
// --dart-define=DM_MOCK=true offline fake, for UI work with no server
|
|
|
|
|
// --dart-define=DM_DEV_LOGIN=false keep the fake, but walk the entrance
|
|
|
|
|
//
|
|
|
|
|
// The guard has not moved: `!kReleaseMode`, so no define puts this in a
|
|
|
|
|
// release build. When it is on the app talks to [DevDoormileApi] instead of
|
|
|
|
|
// the network, `9876543210` + any 4-digit code signs in, and the Account
|
|
|
|
|
// screen says `DEV DATA (offline)`. **Nothing it does reaches a server or the
|
|
|
|
|
// admin console** — a booking made in this mode is not a booking.
|
|
|
|
|
static const bool _devDataRequested = bool.fromEnvironment(
|
|
|
|
|
'DM_MOCK',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
);
|
|
|
|
|
// ----------------------------------------------------------------- testing
|
|
|
|
|
|
|
|
|
|
/// True under `flutter test`, where the widget tests drive the whole journey
|
|
|
|
|
/// against [DevDoormileApi] and there is no server to reach.
|
|
|
|
|
/// True under `flutter test`, where the widget tests drive the journey
|
|
|
|
|
/// against the fake in [DevDoormileApi] and there is no server to reach.
|
|
|
|
|
///
|
|
|
|
|
/// The app itself never branches on this — it exists so a build-time
|
|
|
|
|
/// convenience cannot leak into a test run and quietly delete its coverage.
|
|
|
|
|
static bool get isTest {
|
|
|
|
|
if (kIsWeb) return false;
|
|
|
|
|
try {
|
|
|
|
|
@@ -116,30 +131,41 @@ class AppConfig {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// True only when dev data was asked for — or we are under test — **and**
|
|
|
|
|
/// this is not a release build. There is no define that can turn it on in a
|
|
|
|
|
/// release.
|
|
|
|
|
static bool get useDevData => (_devDataRequested || isTest) && !kReleaseMode;
|
|
|
|
|
// ------------------------------------------------------------- offline data
|
|
|
|
|
//
|
|
|
|
|
// ── The mock is back, and this time it cannot ship ──
|
|
|
|
|
//
|
|
|
|
|
// An offline build was deleted on 15 Sep 2026 after it cost two rounds of
|
|
|
|
|
// hunting for bookings in the admin console that had never left the phone.
|
|
|
|
|
// That failure was not caused by the fake existing — it was caused by the
|
|
|
|
|
// fake being reachable without saying so, and by nothing stopping it
|
|
|
|
|
// reaching a release. Both are fixed here rather than by doing without it:
|
|
|
|
|
//
|
|
|
|
|
// * `!kReleaseMode` is unconditional, so no combination of defines puts
|
|
|
|
|
// invented data in front of a customer;
|
|
|
|
|
// * the Account screen prints DEV DATA (offline) whenever it is on, so a
|
|
|
|
|
// confusing session is one glance from being explained;
|
|
|
|
|
// * every booking it makes is stamped MOCK- and lives on the handset.
|
|
|
|
|
//
|
|
|
|
|
// It is opt-in, never a default: `flutter run` still talks to the real API,
|
|
|
|
|
// which is the property whose absence caused the original mess.
|
|
|
|
|
//
|
|
|
|
|
// flutter run --dart-define=DM_MOCK=true
|
|
|
|
|
static const bool _mockRequested = bool.fromEnvironment('DM_MOCK');
|
|
|
|
|
|
|
|
|
|
/// Opens the app already signed in, skipping the login screen.
|
|
|
|
|
/// Whether this build answers from [DevDoormileApi] instead of the network.
|
|
|
|
|
///
|
|
|
|
|
/// On by default wherever [useDevData] is, which is the point of the pair:
|
|
|
|
|
/// one `flutter run` and you are looking at Home. Only meaningful alongside
|
|
|
|
|
/// dev data — there is no one to sign in as without it, so a build talking to
|
|
|
|
|
/// the real API still signs in properly.
|
|
|
|
|
/// True under `FLUTTER_TEST` as well, so widget tests get the fake without
|
|
|
|
|
/// every test having to install it — and false in a release whatever is passed.
|
|
|
|
|
static bool get useDevData => (_mockRequested || isTest) && !kReleaseMode;
|
|
|
|
|
|
|
|
|
|
/// Whether an offline build opens straight on Home.
|
|
|
|
|
///
|
|
|
|
|
/// To walk the entrance itself — the sign-in, sign-up and verify screens —
|
|
|
|
|
/// turn this off and keep the dev data: `--dart-define=DM_DEV_LOGIN=false`,
|
|
|
|
|
/// where `9876543210` and any 4-digit code gets you in.
|
|
|
|
|
///
|
|
|
|
|
/// Never under `flutter test`: the widget tests drive the real entrance —
|
|
|
|
|
/// phone, code, and the guard that refuses to open the code screen when no
|
|
|
|
|
/// code was sent — and a session waiting for them at launch would delete
|
|
|
|
|
/// that coverage silently.
|
|
|
|
|
static bool get devAutoLogin =>
|
|
|
|
|
useDevData &&
|
|
|
|
|
!isTest &&
|
|
|
|
|
const bool.fromEnvironment('DM_DEV_LOGIN', defaultValue: true);
|
|
|
|
|
/// Deliberately NOT true under `FLUTTER_TEST`. The widget tests drive the
|
|
|
|
|
/// real entrance — phone, code, and the guard that refuses to open the code
|
|
|
|
|
/// screen when nothing was sent — and a session waiting for them at launch
|
|
|
|
|
/// would delete that coverage without a single test failing to say so.
|
|
|
|
|
static bool get devAutoLogin => _mockRequested && !isTest && !kReleaseMode;
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------- auto sign-in
|
|
|
|
|
//
|
|
|
|
|
@@ -174,8 +200,12 @@ class AppConfig {
|
|
|
|
|
static String get autoLoginCode =>
|
|
|
|
|
kReleaseMode ? '' : const String.fromEnvironment('DM_LOGIN_CODE');
|
|
|
|
|
|
|
|
|
|
/// Never under `flutter test`: the widget tests drive the real entrance —
|
|
|
|
|
/// phone, code, and the guard that refuses to open the code screen when no
|
|
|
|
|
/// code was sent — and a session waiting for them at launch would delete that
|
|
|
|
|
/// coverage silently.
|
|
|
|
|
static bool get hasAutoLogin =>
|
|
|
|
|
autoLoginIdentifier.isNotEmpty && autoLoginCode.isNotEmpty;
|
|
|
|
|
!isTest && autoLoginIdentifier.isNotEmpty && autoLoginCode.isNotEmpty;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------- ops QA only
|
|
|
|
|
|
|
|
|
|
@@ -194,10 +224,10 @@ class AppConfig {
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// Whether the tracking screen offers its stage stepper. On a dev build it
|
|
|
|
|
/// walks [DevDoormileApi]'s in-memory booking; on staging it drives the real
|
|
|
|
|
/// QA endpoint.
|
|
|
|
|
static bool get showStageStepper => useDevData || allowStageOverride;
|
|
|
|
|
/// Whether the tracking screen offers its stage stepper. It drives the
|
|
|
|
|
/// backend's own QA endpoint, so it is only ever offered where that endpoint
|
|
|
|
|
/// exists.
|
|
|
|
|
static bool get showStageStepper => allowStageOverride;
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------- development access
|
|
|
|
|
//
|
|
|
|
|
@@ -233,7 +263,7 @@ class AppConfig {
|
|
|
|
|
|
|
|
|
|
/// True when this build got its session from somewhere other than a real
|
|
|
|
|
/// sign-in — a pasted dev token, or dev data where any code is accepted.
|
|
|
|
|
static bool get authBypassed => hasDevToken || useDevData || hasAutoLogin;
|
|
|
|
|
static bool get authBypassed => hasDevToken || hasAutoLogin || devAutoLogin;
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------- client identity
|
|
|
|
|
|
|
|
|
|
@@ -271,7 +301,12 @@ class AppConfig {
|
|
|
|
|
/// Names the dev token whenever one is in use: a build whose session did not
|
|
|
|
|
/// come from a sign-in must say so on screen.
|
|
|
|
|
static String get describe =>
|
|
|
|
|
'${environment.name} · ${useDevData ? 'DEV DATA (offline)' : baseUrl}'
|
|
|
|
|
// Offline first and loudest. A session answered by the fake is the one
|
|
|
|
|
// state where nothing on screen came from a server, and the whole reason
|
|
|
|
|
// the previous mock was deleted was that a build in it looked exactly
|
|
|
|
|
// like a build that was not.
|
|
|
|
|
'${useDevData ? 'DEV DATA (offline) · ' : ''}'
|
|
|
|
|
'${environment.name} · ${useDevData ? 'no network' : baseUrl}'
|
|
|
|
|
'${allowStageOverride ? ' · STAGE OVERRIDE' : ''}'
|
|
|
|
|
'${hasDevToken ? ' · DEV TOKEN' : ''}'
|
|
|
|
|
'${hasAutoLogin ? ' · AUTO LOGIN ($autoLoginIdentifier)' : ''}';
|
|
|
|
|
|