mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-09 04:26:12 +02:00
Network status indicator should only respect the latest / current request. Others finish in background. Replace stupid bools with an enum to track state. Be smarter about split requests.
This commit is contained in:
@@ -418,7 +418,9 @@
|
||||
"timedOut": "Anfrage Zeitüberschreitung",
|
||||
"noData": "Keine Offline-Daten",
|
||||
"success": "Überwachungsdaten geladen",
|
||||
"nodeDataSlow": "Überwachungsdaten langsam"
|
||||
"nodeDataSlow": "Überwachungsdaten langsam",
|
||||
"rateLimited": "Server-Limitierung",
|
||||
"networkError": "Netzwerkfehler"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Zeige {rendered} von {total} Geräten",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "Request timed out",
|
||||
"noData": "No offline data",
|
||||
"success": "Surveillance data loaded",
|
||||
"nodeDataSlow": "Surveillance data slow"
|
||||
"nodeDataSlow": "Surveillance data slow",
|
||||
"rateLimited": "Rate limited by server",
|
||||
"networkError": "Network error"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Showing {rendered} of {total} devices",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "Solicitud agotada",
|
||||
"noData": "Sin datos sin conexión",
|
||||
"success": "Datos de vigilancia cargados",
|
||||
"nodeDataSlow": "Datos de vigilancia lentos"
|
||||
"nodeDataSlow": "Datos de vigilancia lentos",
|
||||
"rateLimited": "Limitado por el servidor",
|
||||
"networkError": "Error de red"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Mostrando {rendered} de {total} dispositivos",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "Demande expirée",
|
||||
"noData": "Aucune donnée hors ligne",
|
||||
"success": "Données de surveillance chargées",
|
||||
"nodeDataSlow": "Données de surveillance lentes"
|
||||
"nodeDataSlow": "Données de surveillance lentes",
|
||||
"rateLimited": "Limité par le serveur",
|
||||
"networkError": "Erreur réseau"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Affichage de {rendered} sur {total} appareils",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "Richiesta scaduta",
|
||||
"noData": "Nessun dato offline",
|
||||
"success": "Dati di sorveglianza caricati",
|
||||
"nodeDataSlow": "Dati di sorveglianza lenti"
|
||||
"nodeDataSlow": "Dati di sorveglianza lenti",
|
||||
"rateLimited": "Limitato dal server",
|
||||
"networkError": "Errore di rete"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Mostra {rendered} di {total} dispositivi",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "Solicitação expirada",
|
||||
"noData": "Nenhum dado offline",
|
||||
"success": "Dados de vigilância carregados",
|
||||
"nodeDataSlow": "Dados de vigilância lentos"
|
||||
"nodeDataSlow": "Dados de vigilância lentos",
|
||||
"rateLimited": "Limitado pelo servidor",
|
||||
"networkError": "Erro de rede"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "Mostrando {rendered} de {total} dispositivos",
|
||||
|
||||
@@ -455,7 +455,9 @@
|
||||
"timedOut": "请求超时",
|
||||
"noData": "无离线数据",
|
||||
"success": "监控数据已加载",
|
||||
"nodeDataSlow": "监控数据缓慢"
|
||||
"nodeDataSlow": "监控数据缓慢",
|
||||
"rateLimited": "服务器限流",
|
||||
"networkError": "网络错误"
|
||||
},
|
||||
"nodeLimitIndicator": {
|
||||
"message": "显示 {rendered} / {total} 设备",
|
||||
|
||||
@@ -20,9 +20,6 @@ Future<List<OsmNode>> fetchOsmApiNodes({
|
||||
}) async {
|
||||
if (profiles.isEmpty) return [];
|
||||
|
||||
// Check if this is a user-initiated fetch (indicated by loading state)
|
||||
final wasUserInitiated = NetworkStatus.instance.currentStatus == NetworkStatusType.waiting;
|
||||
|
||||
try {
|
||||
final nodes = await _fetchFromOsmApi(
|
||||
bounds: bounds,
|
||||
@@ -31,22 +28,8 @@ Future<List<OsmNode>> fetchOsmApiNodes({
|
||||
maxResults: maxResults,
|
||||
);
|
||||
|
||||
// Only report success at the top level if this was user-initiated
|
||||
if (wasUserInitiated) {
|
||||
NetworkStatus.instance.setSuccess();
|
||||
}
|
||||
|
||||
return nodes;
|
||||
} catch (e) {
|
||||
// Only report errors at the top level if this was user-initiated
|
||||
if (wasUserInitiated) {
|
||||
if (e.toString().contains('timeout') || e.toString().contains('timed out')) {
|
||||
NetworkStatus.instance.setTimeoutError();
|
||||
} else {
|
||||
NetworkStatus.instance.setNetworkError();
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('[fetchOsmApiNodes] OSM API operation failed: $e');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,225 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../app_state.dart';
|
||||
|
||||
enum NetworkIssueType { overpassApi }
|
||||
enum NetworkStatusType { waiting, issues, timedOut, noData, ready, success }
|
||||
|
||||
|
||||
/// Simple enum-based network status for surveillance data requests.
|
||||
/// Only tracks the latest user-initiated request - background requests are ignored.
|
||||
enum NetworkRequestStatus {
|
||||
idle, // No active requests
|
||||
loading, // Request in progress
|
||||
splitting, // Request being split due to limits/timeouts
|
||||
success, // Data loaded successfully
|
||||
timeout, // Request timed out
|
||||
rateLimited, // API rate limited
|
||||
noData, // No offline data available
|
||||
error, // Other network errors
|
||||
}
|
||||
|
||||
class NetworkStatus extends ChangeNotifier {
|
||||
static final NetworkStatus instance = NetworkStatus._();
|
||||
NetworkStatus._();
|
||||
|
||||
bool _overpassHasIssues = false;
|
||||
bool _isWaitingForData = false;
|
||||
bool _isTimedOut = false;
|
||||
bool _hasNoData = false;
|
||||
bool _hasSuccess = false;
|
||||
int _recentOfflineMisses = 0;
|
||||
Timer? _overpassRecoveryTimer;
|
||||
Timer? _noDataResetTimer;
|
||||
Timer? _successResetTimer;
|
||||
// Getters
|
||||
bool get hasAnyIssues => _overpassHasIssues;
|
||||
bool get overpassHasIssues => _overpassHasIssues;
|
||||
bool get isWaitingForData => _isWaitingForData;
|
||||
bool get isTimedOut => _isTimedOut;
|
||||
bool get hasNoData => _hasNoData;
|
||||
bool get hasSuccess => _hasSuccess;
|
||||
NetworkRequestStatus _status = NetworkRequestStatus.idle;
|
||||
Timer? _autoResetTimer;
|
||||
|
||||
NetworkStatusType get currentStatus {
|
||||
// Simple single-path status logic
|
||||
if (hasAnyIssues) return NetworkStatusType.issues;
|
||||
if (_isWaitingForData) return NetworkStatusType.waiting;
|
||||
if (_isTimedOut) return NetworkStatusType.timedOut;
|
||||
if (_hasNoData) return NetworkStatusType.noData;
|
||||
if (_hasSuccess) return NetworkStatusType.success;
|
||||
return NetworkStatusType.ready;
|
||||
}
|
||||
|
||||
NetworkIssueType? get currentIssueType {
|
||||
if (_overpassHasIssues) return NetworkIssueType.overpassApi;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Report Overpass API issues
|
||||
void reportOverpassIssue() {
|
||||
if (!_overpassHasIssues) {
|
||||
_overpassHasIssues = true;
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] Overpass API issues detected');
|
||||
/// Current network status
|
||||
NetworkRequestStatus get status => _status;
|
||||
|
||||
/// Set status and handle auto-reset timers
|
||||
void _setStatus(NetworkRequestStatus newStatus) {
|
||||
if (_status == newStatus) return;
|
||||
|
||||
_status = newStatus;
|
||||
_autoResetTimer?.cancel();
|
||||
|
||||
// Auto-reset certain statuses after a delay
|
||||
switch (newStatus) {
|
||||
case NetworkRequestStatus.success:
|
||||
_autoResetTimer = Timer(const Duration(seconds: 2), () {
|
||||
_setStatus(NetworkRequestStatus.idle);
|
||||
});
|
||||
break;
|
||||
case NetworkRequestStatus.timeout:
|
||||
case NetworkRequestStatus.error:
|
||||
_autoResetTimer = Timer(const Duration(seconds: 5), () {
|
||||
_setStatus(NetworkRequestStatus.idle);
|
||||
});
|
||||
break;
|
||||
case NetworkRequestStatus.noData:
|
||||
_autoResetTimer = Timer(const Duration(seconds: 3), () {
|
||||
_setStatus(NetworkRequestStatus.idle);
|
||||
});
|
||||
break;
|
||||
case NetworkRequestStatus.rateLimited:
|
||||
_autoResetTimer = Timer(const Duration(minutes: 2), () {
|
||||
_setStatus(NetworkRequestStatus.idle);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// No auto-reset for idle, loading, splitting
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset recovery timer
|
||||
_overpassRecoveryTimer?.cancel();
|
||||
_overpassRecoveryTimer = Timer(const Duration(minutes: 2), () {
|
||||
_overpassHasIssues = false;
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] Overpass API issues cleared');
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Report successful operations to potentially clear issues faster
|
||||
void reportOverpassSuccess() {
|
||||
if (_overpassHasIssues) {
|
||||
// Quietly clear - don't log routine success
|
||||
_overpassHasIssues = false;
|
||||
_overpassRecoveryTimer?.cancel();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Report that requests are taking longer than usual (splitting, backoffs, etc.)
|
||||
void reportSlowProgress() {
|
||||
if (!_overpassHasIssues) {
|
||||
_overpassHasIssues = true;
|
||||
_isWaitingForData = false; // Transition from waiting to slow progress
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] Surveillance data requests taking longer than usual');
|
||||
}
|
||||
|
||||
// Reset recovery timer - we'll clear this when the operation actually completes
|
||||
_overpassRecoveryTimer?.cancel();
|
||||
_overpassRecoveryTimer = Timer(const Duration(minutes: 2), () {
|
||||
_overpassHasIssues = false;
|
||||
notifyListeners();
|
||||
debugPrint('[NetworkStatus] Slow progress status cleared');
|
||||
});
|
||||
}
|
||||
|
||||
/// Set waiting status (show when loading surveillance data)
|
||||
void setWaiting() {
|
||||
// Clear any previous timeout/no-data state when starting new wait
|
||||
_isTimedOut = false;
|
||||
_hasNoData = false;
|
||||
_recentOfflineMisses = 0;
|
||||
_noDataResetTimer?.cancel();
|
||||
|
||||
if (!_isWaitingForData) {
|
||||
_isWaitingForData = true;
|
||||
notifyListeners();
|
||||
}
|
||||
/// Start loading surveillance data
|
||||
void setLoading() {
|
||||
debugPrint('[NetworkStatus] Loading surveillance data');
|
||||
_setStatus(NetworkRequestStatus.loading);
|
||||
}
|
||||
|
||||
/// Show success status briefly when data loads
|
||||
/// Request is being split due to complexity/limits
|
||||
void setSplitting() {
|
||||
debugPrint('[NetworkStatus] Splitting request due to complexity');
|
||||
_setStatus(NetworkRequestStatus.splitting);
|
||||
}
|
||||
|
||||
/// Data loaded successfully
|
||||
void setSuccess() {
|
||||
_isWaitingForData = false;
|
||||
_isTimedOut = false;
|
||||
_hasNoData = false;
|
||||
_hasSuccess = true;
|
||||
_recentOfflineMisses = 0;
|
||||
_noDataResetTimer?.cancel();
|
||||
notifyListeners();
|
||||
|
||||
// Auto-clear success status after 2 seconds
|
||||
_successResetTimer?.cancel();
|
||||
_successResetTimer = Timer(const Duration(seconds: 2), () {
|
||||
if (_hasSuccess) {
|
||||
_hasSuccess = false;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Show no-data status briefly when tiles aren't available
|
||||
void setNoData() {
|
||||
_isWaitingForData = false;
|
||||
_isTimedOut = false;
|
||||
_hasSuccess = false;
|
||||
_hasNoData = true;
|
||||
_successResetTimer?.cancel();
|
||||
notifyListeners();
|
||||
|
||||
// Auto-clear no-data status after 2 seconds
|
||||
_noDataResetTimer?.cancel();
|
||||
_noDataResetTimer = Timer(const Duration(seconds: 2), () {
|
||||
if (_hasNoData) {
|
||||
_hasNoData = false;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Clear waiting/timeout/no-data status (legacy method for compatibility)
|
||||
void clearWaiting() {
|
||||
if (_isWaitingForData || _isTimedOut || _hasNoData || _hasSuccess) {
|
||||
_isWaitingForData = false;
|
||||
_isTimedOut = false;
|
||||
_hasNoData = false;
|
||||
_hasSuccess = false;
|
||||
_recentOfflineMisses = 0;
|
||||
_noDataResetTimer?.cancel();
|
||||
_successResetTimer?.cancel();
|
||||
notifyListeners();
|
||||
}
|
||||
debugPrint('[NetworkStatus] Surveillance data loaded successfully');
|
||||
_setStatus(NetworkRequestStatus.success);
|
||||
}
|
||||
|
||||
/// Set timeout error state
|
||||
void setTimeoutError() {
|
||||
_isWaitingForData = false;
|
||||
_isTimedOut = true;
|
||||
_hasNoData = false;
|
||||
_hasSuccess = false;
|
||||
_noDataResetTimer?.cancel();
|
||||
_successResetTimer?.cancel();
|
||||
notifyListeners();
|
||||
/// Request timed out
|
||||
void setTimeout() {
|
||||
debugPrint('[NetworkStatus] Request timed out');
|
||||
|
||||
// Auto-clear timeout after 5 seconds
|
||||
Timer(const Duration(seconds: 5), () {
|
||||
if (_isTimedOut) {
|
||||
_isTimedOut = false;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
_setStatus(NetworkRequestStatus.timeout);
|
||||
}
|
||||
|
||||
/// Set network error state (rate limits, connection issues, etc.)
|
||||
void setNetworkError() {
|
||||
_isWaitingForData = false;
|
||||
_isTimedOut = false;
|
||||
_hasNoData = false;
|
||||
_hasSuccess = false;
|
||||
_noDataResetTimer?.cancel();
|
||||
_successResetTimer?.cancel();
|
||||
|
||||
// Use existing issue reporting system
|
||||
reportOverpassIssue();
|
||||
/// Rate limited by API
|
||||
void setRateLimited() {
|
||||
debugPrint('[NetworkStatus] Rate limited by API');
|
||||
_setStatus(NetworkRequestStatus.rateLimited);
|
||||
}
|
||||
|
||||
/// No offline data available
|
||||
void setNoData() {
|
||||
debugPrint('[NetworkStatus] No offline data available');
|
||||
_setStatus(NetworkRequestStatus.noData);
|
||||
}
|
||||
|
||||
/// Network or other error
|
||||
void setError() {
|
||||
debugPrint('[NetworkStatus] Network error occurred');
|
||||
_setStatus(NetworkRequestStatus.error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// 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;
|
||||
notifyListeners();
|
||||
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;
|
||||
});
|
||||
/// Clear status (force to idle)
|
||||
void clear() {
|
||||
_setStatus(NetworkRequestStatus.idle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_overpassRecoveryTimer?.cancel();
|
||||
_noDataResetTimer?.cancel();
|
||||
_successResetTimer?.cancel();
|
||||
_autoResetTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,8 @@ class NodeDataManager extends ChangeNotifier {
|
||||
final OverpassService _overpassService = OverpassService();
|
||||
final NodeSpatialCache _cache = NodeSpatialCache();
|
||||
|
||||
// Track ongoing requests and which one is "primary" for status reporting
|
||||
final Set<String> _activeRequests = <String>{};
|
||||
String? _primaryRequestKey; // The latest request that should drive NetworkStatus
|
||||
// Track ongoing user-initiated requests for status reporting
|
||||
final Set<String> _userInitiatedRequests = <String>{};
|
||||
|
||||
/// Get nodes for the given bounds and profiles.
|
||||
/// Returns cached data immediately if available, otherwise fetches from appropriate source.
|
||||
@@ -44,7 +43,7 @@ class NodeDataManager extends ChangeNotifier {
|
||||
if (AppState.instance.offlineMode) {
|
||||
// Clear any existing loading states since offline data is instant
|
||||
if (isUserInitiated) {
|
||||
NetworkStatus.instance.clearWaiting();
|
||||
NetworkStatus.instance.clear();
|
||||
}
|
||||
|
||||
if (uploadMode == UploadMode.sandbox) {
|
||||
@@ -63,11 +62,16 @@ class NodeDataManager extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Show brief success for user-initiated offline loads
|
||||
// Show brief success for user-initiated offline loads with data
|
||||
if (isUserInitiated && offlineNodes.isNotEmpty) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
NetworkStatus.instance.setSuccess();
|
||||
});
|
||||
} else if (isUserInitiated && offlineNodes.isEmpty) {
|
||||
// Show no data briefly for offline areas with no surveillance devices
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
NetworkStatus.instance.setNoData();
|
||||
});
|
||||
}
|
||||
|
||||
return offlineNodes;
|
||||
@@ -92,40 +96,35 @@ class NodeDataManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Not cached - need to fetch
|
||||
// Create a unique key for this request to prevent duplicates
|
||||
final requestKey = '${bounds.hashCode}_${profiles.map((p) => p.id).join('_')}_$uploadMode';
|
||||
final bool isDuplicate = _activeRequests.contains(requestKey);
|
||||
final bool isPrimaryRequest = isUserInitiated;
|
||||
|
||||
if (isDuplicate && !isPrimaryRequest) {
|
||||
debugPrint('[NodeDataManager] Background request already in progress for this area, returning cached data');
|
||||
// Only allow one user-initiated request per area at a time
|
||||
if (isUserInitiated && _userInitiatedRequests.contains(requestKey)) {
|
||||
debugPrint('[NodeDataManager] User request already in progress for this area');
|
||||
return _cache.getNodesFor(bounds);
|
||||
}
|
||||
|
||||
// Set this as primary request if user-initiated (most recent)
|
||||
if (isPrimaryRequest) {
|
||||
_primaryRequestKey = requestKey;
|
||||
NetworkStatus.instance.setWaiting();
|
||||
debugPrint('[NodeDataManager] Starting PRIMARY request (${_activeRequests.length + 1} total active)');
|
||||
// Start status tracking for user-initiated requests only
|
||||
if (isUserInitiated) {
|
||||
_userInitiatedRequests.add(requestKey);
|
||||
NetworkStatus.instance.setLoading();
|
||||
debugPrint('[NodeDataManager] Starting user-initiated request');
|
||||
} else {
|
||||
debugPrint('[NodeDataManager] Starting background request (${_activeRequests.length + 1} total active)');
|
||||
debugPrint('[NodeDataManager] Starting background request (no status reporting)');
|
||||
}
|
||||
|
||||
_activeRequests.add(requestKey);
|
||||
try {
|
||||
final nodes = await fetchWithSplitting(bounds, profiles);
|
||||
final nodes = await fetchWithSplitting(bounds, profiles, isUserInitiated: isUserInitiated);
|
||||
|
||||
// Don't set success immediately - wait for UI to render the nodes
|
||||
// Update cache and notify listeners
|
||||
notifyListeners();
|
||||
|
||||
// Set success after the next frame renders, but only for primary requests
|
||||
if (isPrimaryRequest && _primaryRequestKey == requestKey) {
|
||||
// Set success after the next frame renders, but only for user-initiated requests
|
||||
if (isUserInitiated) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
NetworkStatus.instance.setSuccess();
|
||||
});
|
||||
debugPrint('[NodeDataManager] PRIMARY request completed successfully');
|
||||
} else {
|
||||
debugPrint('[NodeDataManager] Background request completed successfully');
|
||||
debugPrint('[NodeDataManager] User-initiated request completed successfully');
|
||||
}
|
||||
|
||||
return nodes;
|
||||
@@ -133,25 +132,23 @@ class NodeDataManager extends ChangeNotifier {
|
||||
} catch (e) {
|
||||
debugPrint('[NodeDataManager] Fetch failed: $e');
|
||||
|
||||
// Only report errors for primary requests to avoid status confusion
|
||||
if (isPrimaryRequest && _primaryRequestKey == requestKey) {
|
||||
// Only report errors for user-initiated requests
|
||||
if (isUserInitiated) {
|
||||
if (e is RateLimitError) {
|
||||
NetworkStatus.instance.reportOverpassIssue();
|
||||
NetworkStatus.instance.setRateLimited();
|
||||
} else if (e.toString().contains('timeout')) {
|
||||
NetworkStatus.instance.setTimeout();
|
||||
} else {
|
||||
NetworkStatus.instance.setNetworkError();
|
||||
NetworkStatus.instance.setError();
|
||||
}
|
||||
debugPrint('[NodeDataManager] PRIMARY request failed: $e');
|
||||
} else {
|
||||
debugPrint('[NodeDataManager] Background request failed: $e');
|
||||
debugPrint('[NodeDataManager] User-initiated request failed: $e');
|
||||
}
|
||||
|
||||
// Return whatever we have in cache for this area
|
||||
return _cache.getNodesFor(bounds);
|
||||
} finally {
|
||||
_activeRequests.remove(requestKey);
|
||||
// Clear primary key if this was the primary request
|
||||
if (_primaryRequestKey == requestKey) {
|
||||
_primaryRequestKey = null;
|
||||
if (isUserInitiated) {
|
||||
_userInitiatedRequests.remove(requestKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +158,7 @@ class NodeDataManager extends ChangeNotifier {
|
||||
LatLngBounds bounds,
|
||||
List<NodeProfile> profiles, {
|
||||
int splitDepth = 0,
|
||||
bool isUserInitiated = false,
|
||||
}) async {
|
||||
const maxSplitDepth = 3; // 4^3 = 64 max sub-areas
|
||||
|
||||
@@ -185,9 +183,13 @@ class NodeDataManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
debugPrint('[NodeDataManager] Splitting area (depth: $splitDepth)');
|
||||
NetworkStatus.instance.reportSlowProgress();
|
||||
|
||||
return _fetchSplitAreas(bounds, profiles, splitDepth + 1);
|
||||
// Only report splitting status for user-initiated requests
|
||||
if (isUserInitiated && splitDepth == 0) {
|
||||
NetworkStatus.instance.setSplitting();
|
||||
}
|
||||
|
||||
return _fetchSplitAreas(bounds, profiles, splitDepth + 1, isUserInitiated: isUserInitiated);
|
||||
|
||||
} on RateLimitError {
|
||||
// Rate limited - wait and return empty
|
||||
@@ -201,14 +203,20 @@ class NodeDataManager extends ChangeNotifier {
|
||||
Future<List<OsmNode>> _fetchSplitAreas(
|
||||
LatLngBounds bounds,
|
||||
List<NodeProfile> profiles,
|
||||
int splitDepth,
|
||||
) async {
|
||||
int splitDepth, {
|
||||
bool isUserInitiated = false,
|
||||
}) async {
|
||||
final quadrants = _splitBounds(bounds);
|
||||
final allNodes = <OsmNode>[];
|
||||
|
||||
for (final quadrant in quadrants) {
|
||||
try {
|
||||
final nodes = await fetchWithSplitting(quadrant, profiles, splitDepth: splitDepth);
|
||||
final nodes = await fetchWithSplitting(
|
||||
quadrant,
|
||||
profiles,
|
||||
splitDepth: splitDepth,
|
||||
isUserInitiated: isUserInitiated,
|
||||
);
|
||||
allNodes.addAll(nodes);
|
||||
} catch (e) {
|
||||
debugPrint('[NodeDataManager] Quadrant fetch failed: $e');
|
||||
@@ -276,9 +284,9 @@ class NodeDataManager extends ChangeNotifier {
|
||||
UploadMode uploadMode = UploadMode.production,
|
||||
}) async {
|
||||
// Clear any cached data for this area
|
||||
_cache.clear(); // Simple: clear everything for now
|
||||
_cache.clear();
|
||||
|
||||
// Re-fetch
|
||||
// Re-fetch as user-initiated request
|
||||
await getNodesFor(
|
||||
bounds: bounds,
|
||||
profiles: profiles,
|
||||
|
||||
@@ -26,49 +26,55 @@ class NetworkStatusIndicator extends StatelessWidget {
|
||||
IconData icon;
|
||||
Color color;
|
||||
|
||||
switch (networkStatus.currentStatus) {
|
||||
case NetworkStatusType.waiting:
|
||||
switch (networkStatus.status) {
|
||||
case NetworkRequestStatus.loading:
|
||||
message = locService.t('networkStatus.loading');
|
||||
icon = Icons.hourglass_empty;
|
||||
color = Colors.blue;
|
||||
break;
|
||||
|
||||
case NetworkStatusType.timedOut:
|
||||
message = locService.t('networkStatus.timedOut');
|
||||
icon = Icons.hourglass_disabled;
|
||||
case NetworkRequestStatus.splitting:
|
||||
message = locService.t('networkStatus.nodeDataSlow');
|
||||
icon = Icons.camera_alt_outlined;
|
||||
color = Colors.orange;
|
||||
break;
|
||||
|
||||
case NetworkStatusType.noData:
|
||||
message = locService.t('networkStatus.noData');
|
||||
icon = Icons.cloud_off;
|
||||
color = Colors.grey;
|
||||
break;
|
||||
|
||||
case NetworkStatusType.success:
|
||||
case NetworkRequestStatus.success:
|
||||
message = locService.t('networkStatus.success');
|
||||
icon = Icons.check_circle;
|
||||
color = Colors.green;
|
||||
break;
|
||||
|
||||
case NetworkStatusType.issues:
|
||||
switch (networkStatus.currentIssueType) {
|
||||
case NetworkIssueType.overpassApi:
|
||||
message = locService.t('networkStatus.nodeDataSlow');
|
||||
icon = Icons.camera_alt_outlined;
|
||||
color = Colors.orange;
|
||||
break;
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
case NetworkRequestStatus.timeout:
|
||||
message = locService.t('networkStatus.timedOut');
|
||||
icon = Icons.hourglass_disabled;
|
||||
color = Colors.orange;
|
||||
break;
|
||||
|
||||
case NetworkStatusType.ready:
|
||||
case NetworkRequestStatus.rateLimited:
|
||||
message = locService.t('networkStatus.rateLimited');
|
||||
icon = Icons.speed;
|
||||
color = Colors.red;
|
||||
break;
|
||||
|
||||
case NetworkRequestStatus.noData:
|
||||
message = locService.t('networkStatus.noData');
|
||||
icon = Icons.cloud_off;
|
||||
color = Colors.grey;
|
||||
break;
|
||||
|
||||
case NetworkRequestStatus.error:
|
||||
message = locService.t('networkStatus.networkError');
|
||||
icon = Icons.error_outline;
|
||||
color = Colors.red;
|
||||
break;
|
||||
|
||||
case NetworkRequestStatus.idle:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
top: top, // Position dynamically based on other indicators
|
||||
top: top,
|
||||
left: left,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
|
||||
Reference in New Issue
Block a user