Replace the customer app with Doormile CX
Book a pickup, track it to delivery — the rebuilt customer app. - Design language from doormile-screens.html: brand #8F0F06, Manrope + Geist Mono (variable fonts), bordered cards instead of shadows, crimson brand headers, sliding tab indicator, mono for anything read digit by digit. - lib/data (one live API implementation, plus a debug-only offline fake), lib/state, lib/ui (tokens, widgets, screens). - 84 tests, plus a design snapshot harness that renders every screen with the real fonts: flutter test test/design_snapshot_test.dart --run-skipped --update-goldens This replaces the previous app (pubspec 'doormile', app id com.doormile.customer). That tree remains in history at 6c7d656; note its android/app/google-services.json is not carried over, and the application id here is in.doormile.customer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
76
lib/ui/widgets/map_tiles.dart
Normal file
76
lib/ui/widgets/map_tiles.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
|
||||
import '../../data/map_config.dart';
|
||||
import '../tokens.dart';
|
||||
|
||||
/// Turns the configured tile provider ([DmMapConfig]) into a flutter_map layer.
|
||||
///
|
||||
/// The one bridge between map configuration and map rendering: no widget in
|
||||
/// this app knows a tile URL, a subdomain list or an API key, and changing
|
||||
/// provider never reaches a screen.
|
||||
class DmMapTiles {
|
||||
DmMapTiles._();
|
||||
|
||||
/// What shows through before a tile paints, or when one never arrives.
|
||||
static const ground = Color(0xFFF0EDE9);
|
||||
|
||||
static DmMapConfig get config => DmMapConfig.instance;
|
||||
|
||||
static TileLayer layer(
|
||||
BuildContext context, {
|
||||
DmMapStyle style = DmMapStyle.streets,
|
||||
}) {
|
||||
final map = config;
|
||||
return TileLayer(
|
||||
urlTemplate: map.urlFor(style),
|
||||
subdomains: map.subdomains,
|
||||
// Identifies the app to the provider so our traffic is attributable and
|
||||
// can be rate-limited or allow-listed properly.
|
||||
userAgentPackageName: DmMapConfig.packageName,
|
||||
tileProvider: NetworkTileProvider(
|
||||
headers: {'User-Agent': map.userAgent},
|
||||
// A failed tile is a cosmetic event — the ground colour covers it.
|
||||
silenceExceptions: true,
|
||||
),
|
||||
retinaMode: map.retina && RetinaMode.isHighDensity(context),
|
||||
maxNativeZoom: map.maxNativeZoom,
|
||||
// Defaults, stated so they are not "tuned" upward later: the app fetches
|
||||
// only the tiles it is about to draw plus the standard render margin.
|
||||
// Bulk prefetching or scraping a provider's tiles is out of bounds.
|
||||
keepBuffer: 2,
|
||||
panBuffer: 1,
|
||||
tileDisplay: const TileDisplay.fadeIn(
|
||||
duration: Duration(milliseconds: 160),
|
||||
),
|
||||
// Offline, or under `flutter test`, tiles simply never arrive.
|
||||
errorTileCallback: (_, _, _) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The credit line the tile licence requires. Small, quiet, and on every map —
|
||||
/// its text comes from the provider, so it stays correct after a swap.
|
||||
class DmMapAttribution extends StatelessWidget {
|
||||
const DmMapAttribution({super.key, this.dark = false});
|
||||
|
||||
/// Slightly stronger contrast for maps with busy cartography behind it.
|
||||
final bool dark;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: DmColors.surface.withValues(alpha: dark ? 0.92 : 0.82),
|
||||
borderRadius: DmRadius.all(DmRadius.pill),
|
||||
),
|
||||
child: Text(
|
||||
DmMapTiles.config.attribution,
|
||||
style: DmText.small.copyWith(fontSize: 9.5, color: DmColors.ink3),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user