From 7bd6f68a99ff162de5b457cab4e23d69b4e1670d Mon Sep 17 00:00:00 2001 From: stopflock Date: Sat, 23 Aug 2025 20:06:46 -0500 Subject: [PATCH] add no offline data status indicator --- lib/services/network_status.dart | 60 ++++++++++++++++++++--- lib/services/simple_tile_service.dart | 2 + lib/widgets/network_status_indicator.dart | 6 +++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/lib/services/network_status.dart b/lib/services/network_status.dart index eedc29e..e5fa97a 100644 --- a/lib/services/network_status.dart +++ b/lib/services/network_status.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'dart:async'; +import '../app_state.dart'; + enum NetworkIssueType { osmTiles, overpassApi, both } -enum NetworkStatusType { waiting, issues, timedOut, ready } +enum NetworkStatusType { waiting, issues, timedOut, noData, ready } class NetworkStatus extends ChangeNotifier { static final NetworkStatus instance = NetworkStatus._(); @@ -12,9 +14,12 @@ class NetworkStatus extends ChangeNotifier { bool _overpassHasIssues = false; bool _isWaitingForData = false; bool _isTimedOut = false; + bool _hasNoData = false; + int _recentOfflineMisses = 0; Timer? _osmRecoveryTimer; Timer? _overpassRecoveryTimer; Timer? _waitingTimer; + Timer? _noDataResetTimer; // Getters bool get hasAnyIssues => _osmTilesHaveIssues || _overpassHasIssues; @@ -22,11 +27,13 @@ class NetworkStatus extends ChangeNotifier { bool get overpassHasIssues => _overpassHasIssues; bool get isWaitingForData => _isWaitingForData; bool get isTimedOut => _isTimedOut; + bool get hasNoData => _hasNoData; NetworkStatusType get currentStatus { if (hasAnyIssues) return NetworkStatusType.issues; if (_isWaitingForData) return NetworkStatusType.waiting; if (_isTimedOut) return NetworkStatusType.timedOut; + if (_hasNoData) return NetworkStatusType.noData; return NetworkStatusType.ready; } @@ -93,8 +100,11 @@ class NetworkStatus extends ChangeNotifier { /// Set waiting status (show when loading tiles/cameras) void setWaiting() { - // Clear any previous timeout state when starting new wait + // Clear any previous timeout/no-data state when starting new wait _isTimedOut = false; + _hasNoData = false; + _recentOfflineMisses = 0; + _noDataResetTimer?.cancel(); if (!_isWaitingForData) { _isWaitingForData = true; @@ -102,25 +112,58 @@ class NetworkStatus extends ChangeNotifier { debugPrint('[NetworkStatus] Waiting for data...'); } - // Set timeout to show "timed out" status after reasonable time + // Set timeout to show appropriate status after reasonable time _waitingTimer?.cancel(); _waitingTimer = Timer(const Duration(seconds: 10), () { _isWaitingForData = false; - _isTimedOut = true; + + // If in offline mode, this is "no data" not "timed out" + if (AppState.instance.offlineMode) { + _hasNoData = true; + debugPrint('[NetworkStatus] No offline data available (timeout in offline mode)'); + } else { + _isTimedOut = true; + debugPrint('[NetworkStatus] Data request timed out (online mode)'); + } + notifyListeners(); - debugPrint('[NetworkStatus] Data request timed out'); }); } - /// Clear waiting/timeout status when data arrives + /// Clear waiting/timeout/no-data status when data arrives void clearWaiting() { - if (_isWaitingForData || _isTimedOut) { + if (_isWaitingForData || _isTimedOut || _hasNoData) { _isWaitingForData = false; _isTimedOut = false; + _hasNoData = false; + _recentOfflineMisses = 0; + _waitingTimer?.cancel(); + _noDataResetTimer?.cancel(); + notifyListeners(); + debugPrint('[NetworkStatus] Waiting/timeout/no-data status cleared - data arrived'); + } + } + + /// Report that a tile was not available offline + void reportOfflineMiss() { + _recentOfflineMisses++; + debugPrint('[NetworkStatus] Offline miss #$_recentOfflineMisses'); + + // If we get several misses in a short time, show "no data" status + if (_recentOfflineMisses >= 3 && !_hasNoData) { + _isWaitingForData = false; + _isTimedOut = false; + _hasNoData = true; _waitingTimer?.cancel(); notifyListeners(); - debugPrint('[NetworkStatus] Waiting/timeout status cleared - data arrived'); + debugPrint('[NetworkStatus] No offline data available for this area'); } + + // Reset the miss counter after some time + _noDataResetTimer?.cancel(); + _noDataResetTimer = Timer(const Duration(seconds: 5), () { + _recentOfflineMisses = 0; + }); } @override @@ -128,6 +171,7 @@ class NetworkStatus extends ChangeNotifier { _osmRecoveryTimer?.cancel(); _overpassRecoveryTimer?.cancel(); _waitingTimer?.cancel(); + _noDataResetTimer?.cancel(); super.dispose(); } } \ No newline at end of file diff --git a/lib/services/simple_tile_service.dart b/lib/services/simple_tile_service.dart index 14bfaaa..1c97436 100644 --- a/lib/services/simple_tile_service.dart +++ b/lib/services/simple_tile_service.dart @@ -70,6 +70,8 @@ class SimpleTileHttpClient extends http.BaseClient { // Check if we're in offline mode if (AppState.instance.offlineMode) { debugPrint('[SimpleTileService] Offline mode - not attempting OSM fetch for $z/$x/$y'); + // Report that we couldn't serve this tile offline + NetworkStatus.instance.reportOfflineMiss(); return http.StreamedResponse( Stream.value([]), 404, diff --git a/lib/widgets/network_status_indicator.dart b/lib/widgets/network_status_indicator.dart index 829c308..5c89cf7 100644 --- a/lib/widgets/network_status_indicator.dart +++ b/lib/widgets/network_status_indicator.dart @@ -28,6 +28,12 @@ class NetworkStatusIndicator extends StatelessWidget { color = Colors.orange; break; + case NetworkStatusType.noData: + message = 'No offline data'; + icon = Icons.cloud_off; + color = Colors.grey; + break; + case NetworkStatusType.issues: switch (networkStatus.currentIssueType) { case NetworkIssueType.osmTiles: