mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-13 01:03:03 +00:00
offline mode fix for camera upload queue
This commit is contained in:
@@ -43,6 +43,7 @@ class AppState extends ChangeNotifier {
|
||||
if (wasOffline && !enabled) {
|
||||
// Transitioning from offline to online: clear tile cache!
|
||||
TileProviderWithCache.clearCache();
|
||||
_startUploader(); // Resume upload queue processing as we leave offline mode
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -387,17 +388,23 @@ class AppState extends ChangeNotifier {
|
||||
void _startUploader() {
|
||||
_uploadTimer?.cancel();
|
||||
|
||||
// No uploads without auth or queue.
|
||||
if (_queue.isEmpty) return;
|
||||
// No uploads without auth or queue, or if offline mode is enabled.
|
||||
if (_queue.isEmpty || _offlineMode) return;
|
||||
|
||||
_uploadTimer = Timer.periodic(const Duration(seconds: 10), (t) async {
|
||||
if (_queue.isEmpty) return;
|
||||
if (_queue.isEmpty || _offlineMode) {
|
||||
_uploadTimer?.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first queue item that is NOT in error state and act on that
|
||||
final item = _queue.where((pu) => !pu.error).cast<PendingUpload?>().firstOrNull;
|
||||
if (item == null) return;
|
||||
|
||||
// Retrieve access after every tick (accounts for re-login)
|
||||
final access = await _auth.getAccessToken();
|
||||
if (access == null) return; // not logged in
|
||||
|
||||
final item = _queue.first;
|
||||
bool ok;
|
||||
if (_uploadMode == UploadMode.simulate) {
|
||||
// Simulate successful upload without calling real API
|
||||
@@ -424,7 +431,10 @@ class AppState extends ChangeNotifier {
|
||||
if (!ok) {
|
||||
item.attempts++;
|
||||
if (item.attempts >= 3) {
|
||||
// give up until next launch
|
||||
// Mark as error and stop the uploader. User can manually retry.
|
||||
item.error = true;
|
||||
_saveQueue();
|
||||
notifyListeners();
|
||||
_uploadTimer?.cancel();
|
||||
} else {
|
||||
await Future.delayed(const Duration(seconds: 20));
|
||||
@@ -451,4 +461,13 @@ class AppState extends ChangeNotifier {
|
||||
_saveQueue();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Retry a failed upload (clear error and attempts, then try uploading again)
|
||||
void retryUpload(PendingUpload upload) {
|
||||
upload.error = false;
|
||||
upload.attempts = 0;
|
||||
_saveQueue();
|
||||
notifyListeners();
|
||||
_startUploader(); // resume uploader if not busy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ class PendingUpload {
|
||||
final double direction;
|
||||
final CameraProfile profile;
|
||||
int attempts;
|
||||
bool error;
|
||||
|
||||
PendingUpload({
|
||||
required this.coord,
|
||||
required this.direction,
|
||||
required this.profile,
|
||||
this.attempts = 0,
|
||||
this.error = false,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -20,6 +22,7 @@ class PendingUpload {
|
||||
'dir': direction,
|
||||
'profile': profile.toJson(),
|
||||
'attempts': attempts,
|
||||
'error': error,
|
||||
};
|
||||
|
||||
factory PendingUpload.fromJson(Map<String, dynamic> j) => PendingUpload(
|
||||
@@ -27,8 +30,9 @@ class PendingUpload {
|
||||
direction: j['dir'],
|
||||
profile: j['profile'] is Map<String, dynamic>
|
||||
? CameraProfile.fromJson(j['profile'])
|
||||
: CameraProfile.alpr(), // fallback for legacy, more logic can be added
|
||||
: CameraProfile.alpr(),
|
||||
attempts: j['attempts'] ?? 0,
|
||||
error: j['error'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,22 +71,40 @@ class QueueSection extends StatelessWidget {
|
||||
itemBuilder: (context, index) {
|
||||
final upload = appState.pendingUploads[index];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.camera_alt),
|
||||
title: Text('Camera ${index + 1}'),
|
||||
leading: Icon(
|
||||
upload.error ? Icons.error : Icons.camera_alt,
|
||||
color: upload.error ? Colors.red : null,
|
||||
),
|
||||
title: Text('Camera ${index + 1}${upload.error ? " (Error)" : ""}'),
|
||||
subtitle: Text(
|
||||
'Lat: ${upload.coord.latitude.toStringAsFixed(6)}\n'
|
||||
'Lon: ${upload.coord.longitude.toStringAsFixed(6)}\n'
|
||||
'Direction: ${upload.direction.round()}°\n'
|
||||
'Attempts: ${upload.attempts}'
|
||||
'Attempts: ${upload.attempts}' +
|
||||
(upload.error ? "\nUpload failed. Tap retry to try again." : "")
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
appState.removeFromQueue(upload);
|
||||
if (appState.pendingCount == 0) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (upload.error)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
color: Colors.orange,
|
||||
tooltip: 'Retry upload',
|
||||
onPressed: () {
|
||||
appState.retryUpload(upload);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () {
|
||||
appState.removeFromQueue(upload);
|
||||
if (appState.pendingCount == 0) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user