miler map

This commit is contained in:
2026-09-09 12:55:23 +05:30
parent 074cc0eccf
commit 127fa062ed
143 changed files with 4315 additions and 2291 deletions

View File

@@ -149,7 +149,7 @@ class _MilerAttribution extends StatelessWidget {
'© OpenStreetMap',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
color: ColorConstants.secondaryText,
fontFamily: FontConstants.fontFamily,
),
@@ -345,7 +345,7 @@ class MilerNumberPin extends StatelessWidget {
style: const TextStyle(
fontSize: 14,
height: 1,
fontWeight: FontWeight.w700,
fontWeight: FontWeight.w800,
color: Colors.white,
fontFamily: FontConstants.fontFamily,
),
@@ -512,14 +512,102 @@ class _MilerLegMapState extends State<MilerLegMap> {
}
}
/// How many times to ask for the road before settling for the bearing.
///
/// ── Why one attempt was never enough ──
///
/// `route()` answers an empty list for every failure — a 429, a cold DNS
/// lookup that outran the timeout, a tunnel — and this asked exactly once,
/// from `initState`. The only other trigger is `didUpdateWidget`, and it
/// fires on a change of *endpoint*: a rider standing at a door changes
/// neither. So a single unlucky moment at the instant the sheet opened left
/// `_road` empty for as long as the sheet stayed open, and the leg drew as a
/// dashed straight line across a route the router would have returned a
/// second later. That is the "it shows a straight line, not the real route"
/// report, and it is a retry bug rather than a routing one — the public OSRM
/// server answers this leg in under a second when asked again.
///
/// Three tries with a widening gap. Cheap, bounded, and it costs nothing at
/// all in the normal case where the first answer is the road.
static const int _routeAttempts = 3;
static const Duration _retryBackoff = Duration(milliseconds: 700);
/// Distinguishes a stale in-flight request from the current one, so a leg
/// that changed while the network was slow cannot have the previous leg's
/// road painted onto it when the old request finally lands.
int _fetchGeneration = 0;
/// ── The backoff has to be cancellable, not just ignored ──
///
/// A bare `Future.delayed` checks `mounted` on the far side of the wait, which
/// is too late for the one observer that counts: the timer is still pending
/// when the tree goes, and Flutter's test binding asserts on exactly that —
/// *"A Timer is still pending even after the widget tree was disposed"*. It
/// failed 15 tests that open this sheet and dispose it inside the retry gap.
///
/// Off a test bench it is the same leak in slower motion: a rider flicking
/// through six stops leaves six timers running against six dead widgets.
///
/// So the wait is a [Timer] this state owns and [dispose] cancels — and
/// disposing also completes the pending waiter, so the retry loop wakes,
/// sees `!mounted`, and unwinds instead of parking forever on a future
/// nothing will ever finish.
Timer? _retryTimer;
Completer<void>? _retryWait;
@override
void dispose() {
_retryTimer?.cancel();
_retryTimer = null;
final waiter = _retryWait;
_retryWait = null;
if (waiter != null && !waiter.isCompleted) waiter.complete();
super.dispose();
}
/// A cancellable [Future.delayed]. See [_retryTimer].
Future<void> _wait(Duration d) {
final completer = Completer<void>();
_retryWait = completer;
_retryTimer = Timer(d, () {
_retryTimer = null;
if (identical(_retryWait, completer)) _retryWait = null;
if (!completer.isCompleted) completer.complete();
});
return completer.future;
}
Future<void> _fetchRoad() async {
final from = widget.from;
if (from == null) return;
final road = await MilerRouter.route(from, widget.to);
if (!mounted || road.isEmpty) return;
setState(() => _road = road);
// The road can bulge outside the straight-line bounds; refit around it.
_fit();
final to = widget.to;
final generation = ++_fetchGeneration;
for (var attempt = 1; attempt <= _routeAttempts; attempt++) {
final road = await MilerRouter.route(from, to);
// Superseded, or gone from the tree, while the request was in flight.
if (!mounted || generation != _fetchGeneration) return;
if (road.isNotEmpty) {
setState(() => _road = road);
// The road can bulge outside the straight-line bounds; refit around it.
_fit();
return;
}
if (attempt < _routeAttempts) {
await _wait(_retryBackoff * attempt);
if (!mounted || generation != _fetchGeneration) return;
}
}
// Says which leg gave up, so a rider reporting a dashed line can be matched
// against the `[MAP] OSRM …` line that explains why.
debugPrint(
'[MAP] no road after $_routeAttempts attempts for '
'${from.latitude},${from.longitude} -> ${to.latitude},${to.longitude} — '
'drawing the dashed bearing instead.',
);
}
void _fit() {