Files
doormile_customer_app/lib/ui/widgets/map_tiles.dart
Thiru-tenext 0d66627c3c 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>
2026-09-15 16:07:33 +05:30

77 lines
2.7 KiB
Dart

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),
),
),
);
}
}