Skip to content

Commit f31bfaf

Browse files
committed
Fixed race condition with internet check and added more debug logging around the startup
1 parent f16f95c commit f31bfaf

File tree

12 files changed

+166
-40
lines changed

12 files changed

+166
-40
lines changed

android/local.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ sdk.dir=/opt/homebrew/share/android-commandlinetools
22
flutter.sdk=/opt/homebrew/share/flutter
33
flutter.buildMode=release
44
flutter.versionName=1.0.0
5-
flutter.versionCode=1769917500
5+
flutter.versionCode=1769992189

ios/Flutter/Generated.xcconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ COCOAPODS_PARALLEL_CODE_SIGN=true
55
FLUTTER_TARGET=lib/main.dart
66
FLUTTER_BUILD_DIR=build
77
FLUTTER_BUILD_NAME=1.0.0
8-
FLUTTER_BUILD_NUMBER=1769917500
8+
FLUTTER_BUILD_NUMBER=1769992189
99
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
1010
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
11-
DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3Njk5MTc1MDA=,RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44
11+
DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3Njk5OTIxODk=,RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44
1212
DART_OBFUSCATION=false
1313
TRACK_WIDGET_CREATION=false
1414
TREE_SHAKE_ICONS=true

ios/Flutter/flutter_export_environment.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export "COCOAPODS_PARALLEL_CODE_SIGN=true"
66
export "FLUTTER_TARGET=lib/main.dart"
77
export "FLUTTER_BUILD_DIR=build"
88
export "FLUTTER_BUILD_NAME=1.0.0"
9-
export "FLUTTER_BUILD_NUMBER=1769917500"
10-
export "DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3Njk5MTc1MDA=,RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44"
9+
export "FLUTTER_BUILD_NUMBER=1769992189"
10+
export "DART_DEFINES=QVBQX1ZFUlNJT049QVBQLTE3Njk5OTIxODk=,RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44"
1111
export "DART_OBFUSCATION=false"
1212
export "TRACK_WIDGET_CREATION=false"
1313
export "TREE_SHAKE_ICONS=true"

lib/models/api_queue_item.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,18 @@ class ApiQueueItem extends HiveObject {
4848
@HiveField(13)
4949
final int canUploadAfter;
5050

51+
/// Whether an external antenna is being used
52+
@HiveField(14)
53+
final bool externalAntenna;
54+
5155
ApiQueueItem({
5256
required this.type,
5357
required this.latitude,
5458
required this.longitude,
5559
required this.timestamp,
5660
required this.heardRepeats,
5761
required this.canUploadAfter,
62+
required this.externalAntenna,
5863
this.retryCount = 0,
5964
this.lastRetryAt,
6065
this.noiseFloor,
@@ -67,6 +72,7 @@ class ApiQueueItem extends HiveObject {
6772
required double longitude,
6873
required String heardRepeats,
6974
required int timestamp,
75+
required bool externalAntenna,
7076
int? noiseFloor,
7177
}) {
7278
return ApiQueueItem(
@@ -76,6 +82,7 @@ class ApiQueueItem extends HiveObject {
7682
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
7783
heardRepeats: heardRepeats,
7884
canUploadAfter: DateTime.now().millisecondsSinceEpoch + 5000, // 5 seconds from now
85+
externalAntenna: externalAntenna,
7986
noiseFloor: noiseFloor,
8087
);
8188
}
@@ -87,6 +94,7 @@ class ApiQueueItem extends HiveObject {
8794
required double longitude,
8895
required String heardRepeats,
8996
required int timestamp,
97+
required bool externalAntenna,
9098
int? noiseFloor,
9199
}) {
92100
return ApiQueueItem(
@@ -96,6 +104,7 @@ class ApiQueueItem extends HiveObject {
96104
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
97105
heardRepeats: heardRepeats,
98106
canUploadAfter: DateTime.now().millisecondsSinceEpoch, // Immediate
107+
externalAntenna: externalAntenna,
99108
noiseFloor: noiseFloor,
100109
);
101110
}
@@ -112,6 +121,7 @@ class ApiQueueItem extends HiveObject {
112121
required double remoteSnr,
113122
required String pubkeyFull,
114123
required int timestamp,
124+
required bool externalAntenna,
115125
int? noiseFloor,
116126
}) {
117127
// Format: "repeaterId:nodeType:localSnr:localRssi:remoteSnr:pubkeyFull"
@@ -123,6 +133,7 @@ class ApiQueueItem extends HiveObject {
123133
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
124134
heardRepeats: heardRepeats,
125135
canUploadAfter: DateTime.now().millisecondsSinceEpoch, // Immediate
136+
externalAntenna: externalAntenna,
126137
noiseFloor: noiseFloor,
127138
);
128139
}
@@ -145,6 +156,7 @@ class ApiQueueItem extends HiveObject {
145156
'remote_snr': parts.length > 4 ? double.tryParse(parts[4]) ?? 0.0 : 0.0,
146157
'public_key': parts.length > 5 ? parts[5] : '',
147158
'timestamp': timestamp.millisecondsSinceEpoch ~/ 1000, // Unix timestamp in seconds
159+
'external_antenna': externalAntenna,
148160
};
149161
}
150162

@@ -155,6 +167,7 @@ class ApiQueueItem extends HiveObject {
155167
'noisefloor': noiseFloor,
156168
'heard_repeats': heardRepeats,
157169
'timestamp': timestamp.millisecondsSinceEpoch ~/ 1000, // Unix timestamp in seconds
170+
'external_antenna': externalAntenna,
158171
};
159172
}
160173

lib/models/api_queue_item.g.dart

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/providers/app_state_provider.dart

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,22 @@ class AppStateProvider extends ChangeNotifier {
490490
// Listen to GPS changes
491491
debugLog('[INIT] Setting up GPS status listener...');
492492
_gpsService.statusStream.listen((status) {
493-
debugLog('[GPS] Status changed: $_gpsStatus → $status');
493+
final previousStatus = _gpsStatus;
494494
_gpsStatus = status;
495+
debugLog('[GPS] Status changed: $previousStatus → $status');
496+
debugLog('[GPS] Post-change state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
497+
'hasInternet=$_hasInternet, hasPosition=${_currentPosition != null}');
498+
499+
// Log when we transition to locked state (permission granted + GPS available)
500+
if (status == GpsStatus.locked && previousStatus != GpsStatus.locked) {
501+
debugLog('[GPS] GPS lock acquired - zone check should trigger on first position');
502+
}
503+
// Log when permission is denied or GPS disabled
504+
if (status == GpsStatus.permissionDenied) {
505+
debugLog('[GPS] Location permission denied - zone checks will be blocked');
506+
} else if (status == GpsStatus.disabled) {
507+
debugLog('[GPS] Location services disabled - zone checks will be blocked');
508+
}
495509
notifyListeners();
496510
});
497511
_gpsStatus = _gpsService.status; // Sync initial status
@@ -502,16 +516,23 @@ class AppStateProvider extends ChangeNotifier {
502516
_currentPosition = position;
503517
notifyListeners();
504518

505-
// Diagnostic: log every position received and zone check conditions
506-
debugLog('[GPS] Position received in AppState: ${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)}');
519+
// Comprehensive diagnostic logging for debugging user issues
520+
debugLog('[GPS] Position received: ${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)} '
521+
'(accuracy: ${position.accuracy.toStringAsFixed(1)}m)');
522+
debugLog('[GPS] Current state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
523+
'hasInternet=$_hasInternet, offlineMode=${_preferences.offlineMode}, gpsStatus=$_gpsStatus');
507524

508525
// Check zone on first GPS lock (when _inZone is null)
509526
// Skip zone checks when offline mode is enabled
510527
if (_inZone == null && !_preferences.offlineMode) {
511-
debugLog('[GEOFENCE] First GPS lock, checking zone status');
512-
await checkZoneStatus();
513-
} else if (_inZone == null) {
514-
debugLog('[GEOFENCE] Zone check skipped: offlineMode=${_preferences.offlineMode}');
528+
if (!_hasInternet) {
529+
debugLog('[GEOFENCE] First GPS lock but no internet - zone check will wait for connectivity');
530+
} else {
531+
debugLog('[GEOFENCE] First GPS lock with internet, triggering zone check');
532+
await checkZoneStatus();
533+
}
534+
} else if (_inZone == null && _preferences.offlineMode) {
535+
debugLog('[GEOFENCE] First GPS lock skipped: offline mode enabled');
515536
}
516537

517538
// Check zone every 100m movement (while disconnected)
@@ -543,40 +564,64 @@ class AppStateProvider extends ChangeNotifier {
543564
_connectivityService = ConnectivityService();
544565
await _connectivityService.initialize();
545566
_connectivityService.internetStream.listen((hasInternet) async {
567+
final previousState = _hasInternet;
546568
_hasInternet = hasInternet;
569+
debugLog('[CONNECTIVITY] Internet status changed: $previousState → $hasInternet');
570+
debugLog('[CONNECTIVITY] Current state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
571+
'gpsStatus=$_gpsStatus, hasPosition=${_currentPosition != null}, offlineMode=${_preferences.offlineMode}');
547572
notifyListeners();
548573

549574
// Re-check zone when internet becomes available
550575
if (hasInternet && _inZone == null && !_preferences.offlineMode) {
551-
debugLog('[CONNECTIVITY] Internet restored, rechecking zone');
576+
debugLog('[CONNECTIVITY] Internet restored and zone unknown, will attempt zone check');
552577

553578
// If we have GPS position, check zone immediately
554579
if (_currentPosition != null) {
580+
debugLog('[CONNECTIVITY] GPS position available, triggering zone check');
555581
checkZoneStatus();
556582
} else {
557583
// GPS not providing positions - try to restart it
558584
debugLog('[CONNECTIVITY] No GPS position available, restarting GPS service');
559585
await _gpsService.startWatching();
586+
debugLog('[CONNECTIVITY] GPS service restarted, status: ${_gpsService.status}');
560587
// Zone check will be triggered by GPS position listener when position arrives
561588
}
589+
} else if (!hasInternet) {
590+
debugLog('[CONNECTIVITY] Internet lost - zone checks will be blocked until restored');
562591
}
563592
});
564593
_hasInternet = _connectivityService.hasInternet;
594+
debugLog('[INIT] Initial internet status: $_hasInternet');
565595

566596
// Initialize audio service for sound notifications
567597
await _audioService.initialize();
568598

569599
debugLog('[INIT] AppStateProvider initialization complete');
600+
debugLog('[INIT] Final init state: gpsStatus=$_gpsStatus, hasInternet=$_hasInternet, '
601+
'inZone=$_inZone, isCheckingZone=$_isCheckingZone, hasPosition=${_currentPosition != null}, '
602+
'offlineMode=${_preferences.offlineMode}');
570603
notifyListeners();
571604
}
572605

573606
/// Restart GPS service after permission disclosure is accepted
574607
/// Called from MainScaffold after user grants location permission
575608
Future<void> restartGpsAfterPermission() async {
576-
debugLog('[APP] Restarting GPS after permission granted');
609+
debugLog('[GPS] restartGpsAfterPermission() called');
610+
debugLog('[GPS] Pre-restart state: gpsStatus=$_gpsStatus, inZone=$_inZone, '
611+
'isCheckingZone=$_isCheckingZone, hasInternet=$_hasInternet, hasPosition=${_currentPosition != null}');
612+
577613
await _gpsService.startWatching();
578614
_gpsStatus = _gpsService.status; // Sync after restart
579-
debugLog('[APP] GPS restarted, status: $_gpsStatus');
615+
616+
debugLog('[GPS] GPS restarted, new status: $_gpsStatus');
617+
debugLog('[GPS] Post-restart state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
618+
'hasInternet=$_hasInternet, hasPosition=${_currentPosition != null}');
619+
620+
// If we now have a position and zone hasn't been checked, trigger check
621+
if (_currentPosition != null && _inZone == null && _hasInternet && !_preferences.offlineMode) {
622+
debugLog('[GPS] Permission granted with existing position - triggering zone check');
623+
await checkZoneStatus();
624+
}
580625
notifyListeners();
581626
}
582627

@@ -881,6 +926,9 @@ class AppStateProvider extends ChangeNotifier {
881926
return _preferences.autoPowerSet || _preferences.powerLevelSet || _deviceModel != null;
882927
};
883928

929+
// Get external antenna value for API payloads
930+
_pingService!.getExternalAntenna = () => _preferences.externalAntenna;
931+
884932
// Check if TX is allowed by API (zone capacity)
885933
_pingService!.checkTxAllowed = () => txAllowed;
886934

@@ -1261,6 +1309,7 @@ class AppStateProvider extends ChangeNotifier {
12611309
heardRepeats: heardRepeats,
12621310
timestamp: entry.timestamp.millisecondsSinceEpoch ~/ 1000,
12631311
repeaterId: entry.repeaterId,
1312+
externalAntenna: _preferences.externalAntenna,
12641313
noiseFloor: _meshCoreConnection?.lastNoiseFloor,
12651314
);
12661315

@@ -2167,27 +2216,33 @@ class AppStateProvider extends ChangeNotifier {
21672216
/// Check zone status via API
21682217
/// Should be called on app launch and every 100m of GPS movement while disconnected
21692218
Future<void> checkZoneStatus() async {
2219+
debugLog('[GEOFENCE] checkZoneStatus() called');
2220+
debugLog('[GEOFENCE] Pre-check state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
2221+
'hasInternet=$_hasInternet, hasPosition=${_currentPosition != null}, gpsStatus=$_gpsStatus');
2222+
21702223
// Skip if no internet (avoids stuck "Checking..." state)
21712224
if (!_hasInternet) {
2172-
debugLog('[GEOFENCE] Skipping zone check: no internet');
2225+
debugLog('[GEOFENCE] Skipping zone check: no internet (hasInternet=$_hasInternet)');
21732226
return;
21742227
}
21752228

21762229
if (_currentPosition == null) {
2177-
debugLog('[GEOFENCE] Cannot check zone status: no GPS position');
2230+
debugLog('[GEOFENCE] Cannot check zone status: no GPS position (gpsStatus=$_gpsStatus)');
21782231
return;
21792232
}
21802233

21812234
if (_isCheckingZone) {
2182-
debugLog('[GEOFENCE] Zone check already in progress');
2235+
debugLog('[GEOFENCE] Zone check already in progress, skipping duplicate call');
21832236
return;
21842237
}
21852238

2239+
debugLog('[GEOFENCE] Starting zone check - setting isCheckingZone=true (previous inZone=$_inZone)');
21862240
_isCheckingZone = true;
21872241
notifyListeners();
21882242

21892243
try {
2190-
debugLog('[GEOFENCE] Checking zone status at ${_currentPosition!.latitude.toStringAsFixed(5)}, ${_currentPosition!.longitude.toStringAsFixed(5)}');
2244+
debugLog('[GEOFENCE] Making API call to check zone at ${_currentPosition!.latitude.toStringAsFixed(5)}, '
2245+
'${_currentPosition!.longitude.toStringAsFixed(5)} (accuracy: ${_currentPosition!.accuracy.toStringAsFixed(1)}m)');
21912246

21922247
final result = await _apiService.checkZoneStatus(
21932248
lat: _currentPosition!.latitude,
@@ -2196,8 +2251,10 @@ class AppStateProvider extends ChangeNotifier {
21962251
appVersion: _appVersion,
21972252
);
21982253

2254+
debugLog('[GEOFENCE] API response received: ${result != null ? 'valid' : 'null'}');
2255+
21992256
if (result == null) {
2200-
debugError('[GEOFENCE] Zone status check failed: no response');
2257+
debugError('[GEOFENCE] Zone status check failed: no response from API');
22012258
return;
22022259
}
22032260

@@ -2255,6 +2312,8 @@ class AppStateProvider extends ChangeNotifier {
22552312
debugError('[GEOFENCE] Zone status check error: $e');
22562313
} finally {
22572314
_isCheckingZone = false;
2315+
debugLog('[GEOFENCE] Zone check complete - final state: inZone=$_inZone, isCheckingZone=$_isCheckingZone, '
2316+
'zoneName=${_currentZone?['name']}, zoneCode=${_currentZone?['code']}');
22582317
notifyListeners();
22592318
}
22602319
}

lib/screens/connection_screen.dart

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,13 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
101101
? 'GPS Disabled'
102102
: appState.gpsStatus == GpsStatus.permissionDenied
103103
? 'GPS Required'
104-
: appState.inZone == null
104+
: appState.isCheckingZone
105105
? 'Checking Zone...'
106106
: appState.inZone == true
107107
? 'Scan'
108-
: 'Outside Zone'),
108+
: appState.inZone == false
109+
? 'Outside Zone'
110+
: 'Checking Zone...'),
109111
backgroundColor: canScan ? null : Colors.grey,
110112
);
111113
}
@@ -692,11 +694,11 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
692694
locationIcon = Icons.engineering;
693695
locationText = 'Maintenance';
694696
locationColor = Colors.orange;
695-
// Only show "Checking..." on initial load when we don't have zone info yet
696-
// After that, keep showing current state while checking happens in background
697-
} else if (appState.isCheckingZone && appState.inZone == null) {
697+
// Show "Checking Zone..." whenever a zone check is in progress
698+
// This provides consistent UI feedback during both initial and re-checks
699+
} else if (appState.isCheckingZone) {
698700
locationIcon = Icons.location_searching;
699-
locationText = 'Checking...';
701+
locationText = 'Checking Zone...';
700702
locationColor = Colors.blue;
701703
} else if (appState.inZone == true) {
702704
locationIcon = Icons.location_on;
@@ -1173,7 +1175,9 @@ class _ConnectionScreenState extends State<ConnectionScreen> with WidgetsBinding
11731175
? 'No Internet'
11741176
: appState.maintenanceMode
11751177
? 'Maintenance'
1176-
: 'Outside Zone'),
1178+
: appState.isCheckingZone
1179+
? 'Checking Zone...'
1180+
: 'Outside Zone'),
11771181
style: ElevatedButton.styleFrom(
11781182
backgroundColor: canConnect
11791183
? Theme.of(context).colorScheme.primary

0 commit comments

Comments
 (0)