- Add node id tie-breaker to sort comparator so equal-distance nodes
have deterministic ordering across renders (prevents flicker)
- Log validNodesCount instead of allNodes.length so the message
reflects the actual post-filter count
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sort nodes by squared distance from viewport center before applying the
render limit, so visible nodes always make the cut instead of arbitrary
selection causing gaps that shift as you pan.
Also: inject node provider for testability, deduplicate validity filter,
and reduce debug log spam to state transitions only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user edits a tile type's URL template, max zoom, or API key
without changing IDs, the cached DeflockTileProvider would keep the old
frozen config. Now _getOrCreateProvider() computes a config fingerprint
and replaces the provider when drift is detected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow offline area downloads for OSM tile server. Move the "downloads
not permitted" check from inside the download dialog to the download
button itself — the button is now disabled (greyed out) when the
current tile type doesn't support offline downloads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ServicePolicy framework with OSM-specific rate limiting and TTL
- Add per-provider disk tile cache (ProviderTileCacheStore) with O(1)
lookup, oldest-modified eviction, and ETag/304 revalidation
- Rewrite DeflockTileProvider with two paths: common (NetworkTileProvider)
and offline-first (disk cache -> local tiles -> network with caching)
- Add zoom-aware offline routing so tiles outside offline area zoom ranges
use the efficient common path instead of the overhead-heavy offline path
- Fix HTTP client lifecycle: dispose() is now a no-op for flutter_map
widget recycling; shutdown() handles permanent teardown
- Add TileLayerManager with exponential backoff retry (2s->60s cap),
provider switch detection, and backoff reset
- Guard null provider/tileType in download dialog with localized error
- Fix Nominatim cache key to use normalized viewbox values
- Comprehensive test coverage (1800+ lines across 6 test files)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Preview/PR builds don't have access to GitHub Secrets, so the OAuth
client IDs are empty. Previously this caused a runtime crash from
keys.dart throwing on empty values. Now we detect missing secrets
and force simulate mode, which already fully supports fake auth
and uploads.
Also fixes a latent bug where forceLogin() would crash with
LateInitializationError in simulate mode since _helper is never
initialized when OAuth setup is skipped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace our custom tile pipeline (fetchRemoteTile / _SimpleSemaphore /
exponential backoff) with flutter_map's built-in NetworkTileProvider,
gaining persistent disk cache, ETag revalidation, RetryClient, and
obsolete request aborting for free.
DeflockTileProvider now extends NetworkTileProvider and overrides
getTileUrl() to route through TileType.getTileUrl() (quadkey,
subdomains, API keys). getImageWithCancelLoadingSupport() routes
between two paths at runtime: the common network path (super) when
no offline areas exist, and a DeflockOfflineTileImageProvider for
offline-first when they do.
- Delete tiles_from_remote.dart (semaphore, retry loop, spatial helpers)
- Simplify MapDataProvider._fetchRemoteTileFromCurrentProvider to plain
http.get (only used by offline area downloader now)
- Remove dead clearTileQueue/clearTileQueueSelective from MapDataProvider
- Remove 7 tile fetch constants from dev_config.dart
- TileLayerManager now disposes provider on cache clear and uses actual
urlTemplate for cache key generation
- 9 new tests covering URL delegation, routing, and equality
Closes#87 Phase 2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Create UserAgentClient (http.BaseClient wrapper) that injects a
User-Agent header into every request, reading app name and version
from VersionService and contact/homepage from dev_config.dart.
Format follows OSM tile usage policy:
DeFlock/<version> (+https://deflock.org; contact: admin@stopflock.com)
Replaces 4 inconsistent hardcoded UA strings and adds UA to the 9
call sites that previously sent none.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch the second Overpass pass (ways/relations) from out meta to out skel,
dropping unused tags/version/changeset fields from the response. The app only
reads structural references (node lists, relation members) from these elements.
Also inject http.Client into OverpassService for testability (matching
RoutingService pattern) and add close() for client lifecycle management.
14 tests covering query building, constraint detection, and error handling.
Fixes#108
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cancel in-progress follow-me animations on pointer-down and suppress
new ones while any pointer is on the map. Without this, GPS position
updates trigger 600ms animateTo() calls that fight with the user's
stationary finger, causing visible wiggle — especially at low zoom
where small geographic shifts cover more pixels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user clears a tag value and presses Done, RawAutocomplete's
onFieldSubmitted auto-selects the first option from the suggestions
list. Since optionsBuilder returns all suggestions for empty text,
this causes the cleared value to reappear. Guarding the call on
non-empty text prevents the auto-selection while preserving
autocomplete behavior when the user has typed a partial match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Gate full error response body logging behind kDebugMode; truncate to
500 chars in release builds to avoid log noise and data exposure
- Add RoutingService.close() and call from NavigationState.dispose()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Built-in profiles (Flock, Motorola, etc.) include placeholder empty
values like camera:mount: '' for user refinement. When these get
serialized into the routing request body, the alprwatch API rejects
them with HTTP 400.
Fix: strip empty-valued tags from enabled_profiles before sending
the routing request. Also refactor RoutingService to accept an
injectable http.Client for testability, and log error response
bodies for easier debugging of future API issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move hardcoded suggestion limit to kNSIMaxSuggestions in dev_config, and remove
the redundant .take(10) from optionsBuilder since the fetch stage already caps
results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoids repeated iteration of the lazy .where().take() iterable on each
call to .length and .elementAt() in ListView.builder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NSITagValueField used raw OverlayEntry + CompositedTransformFollower
with no tap-outside dismiss mechanism, causing suggestion dropdowns to
stay visible when tapping elsewhere. Replace with Flutter's
RawAutocomplete which handles dismiss, keyboard navigation, and
accessibility out of the box.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change mapBounds from LatLngBounds? to final LatLngBounds so the
compiler can prove it's non-null after the inner try-catch. Addresses
review comment on PR #45.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused imports, fields, variables, and dead code introduced
during the RadioGroup widget migration and prior changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes avoid_print lint warnings by using debugPrint which respects
release mode and avoids console overflow on mobile platforms.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrate all withOpacity() calls to withValues(alpha:) and
surfaceVariant to surfaceContainerHighest across the codebase.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use AssetManifest.loadFromAssetBundle instead of manually parsing the
deprecated AssetManifest.json. Fix a broken localization key reference
(queue.cameraWithIndex → queue.itemWithIndex).
Replace the standalone scripts/validate_localizations.dart with proper
flutter tests (11 tests across two groups): file integrity checks
(directory exists, en.json present, valid JSON structure, language code
file names, deep key-completeness across all locales) and t() lookup
tests (nested resolution, missing-key fallback, parameter substitution,
partial-path fallback).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>