Add offline-first tile system with per-provider caching and error retry

- 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>
This commit is contained in:
Doug BorgandClaude Opus 4.6 committed 2026-03-07 12:34:01 -07:00
1 parent be446fbcbc
commit 2d92214bed
36 files changed
+3940 -351

No files matched your search

+13 -7
View File
@@ -181,7 +181,10 @@
"offlineModeWarning": "离线模式下禁用下载。禁用离线模式以下载新区域。",
"areaTooBigMessage": "请放大至至少第{}级来下载离线区域。下载大区域可能导致应用程序无响应。",
"downloadStarted": "下载已开始!正在获取瓦片和节点...",
"downloadFailed": "启动下载失败:{}"
"downloadFailed": "启动下载失败:{}",
"offlineNotPermitted": "{}服务器不允许离线下载。请切换到允许离线使用的瓦片提供商(例如 Bing Maps、Mapbox 或自托管的瓦片服务器)。",
"currentTileProvider": "当前瓦片",
"noTileProviderSelected": "未选择瓦片提供商。请在下载离线区域之前选择地图样式。"
},
"downloadStarted": {
"title": "下载已开始",
@@ -329,13 +332,16 @@
"addProfileChoiceMessage": "您希望如何添加配置文件?",
"createCustomProfile": "创建自定义配置文件",
"createCustomProfileDescription": "从头开始构建带有您自己标签的配置文件",
"importFromWebsite": "从网站导入",
"importFromWebsite": "从网站导入",
"importFromWebsiteDescription": "浏览并从 deflock.me/identify 导入配置文件"
},
"mapTiles": {
"title": "地图瓦片",
"manageProviders": "管理提供商",
"attribution": "地图归属"
"attribution": "地图归属",
"mapAttribution": "地图来源:{}",
"couldNotOpenLink": "无法打开链接",
"openLicense": "打开许可证:{}"
},
"profileEditor": {
"viewProfile": "查看配置文件",
@@ -362,7 +368,7 @@
},
"operatorProfileEditor": {
"newOperatorProfile": "新建运营商配置文件",
"editOperatorProfile": "编辑运营商配置文件",
"editOperatorProfile": "编辑运营商配置文件",
"operatorName": "运营商名称",
"operatorNameHint": "例如,奥斯汀警察局",
"operatorNameRequired": "运营商名称为必填项",
@@ -520,7 +526,7 @@
"updateFailed": "疑似位置更新失败",
"neverFetched": "从未获取",
"daysAgo": "{}天前",
"hoursAgo": "{}小时前",
"hoursAgo": "{}小时前",
"minutesAgo": "{}分钟前",
"justNow": "刚刚"
},
@@ -528,7 +534,7 @@
"title": "疑似位置 #{}",
"ticketNo": "工单号",
"address": "地址",
"street": "街道",
"street": "街道",
"city": "城市",
"state": "州/省",
"intersectingStreet": "交叉街道",
@@ -552,4 +558,4 @@
"metricDescription": "公制 (公里, 米)",
"imperialDescription": "英制 (英里, 英尺)"
}
}
}