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