From e290e11c5b32da9a7e867b022dfb608f37c894b5 Mon Sep 17 00:00:00 2001 From: stopflock Date: Sat, 19 Jul 2025 19:58:26 -0500 Subject: [PATCH] test mode to disable uploads, queue view/edit/clear --- lib/app_state.dart | 50 ++++++++++++-- lib/screens/settings_screen.dart | 108 ++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 7 deletions(-) diff --git a/lib/app_state.dart b/lib/app_state.dart index b359ec0..2bbb5d6 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -28,6 +28,15 @@ class AppState extends ChangeNotifier { late final List _profiles = [CameraProfile.alpr()]; final Set _enabled = {}; + + // Test mode - prevents actual uploads to OSM + bool _testMode = false; + bool get testMode => _testMode; + void setTestMode(bool enabled) { + _testMode = enabled; + print('AppState: Test mode ${enabled ? 'enabled' : 'disabled'}'); + notifyListeners(); + } AddCameraSession? _session; AddCameraSession? get session => _session; @@ -216,13 +225,30 @@ class AppState extends ChangeNotifier { if (access == null) return; // not logged in final item = _queue.first; - final up = Uploader(access, () { + + bool ok; + if (_testMode) { + // Test mode - simulate successful upload without actually calling OSM API + print('AppState: Test mode - simulating upload for ${item.coord}'); + await Future.delayed(const Duration(seconds: 1)); // Simulate network delay + ok = true; + print('AppState: Test mode - simulated upload successful'); + } else { + // Real upload + final up = Uploader(access, () { + _queue.remove(item); + _saveQueue(); + notifyListeners(); + }); + ok = await up.upload(item); + } + + if (ok && _testMode) { + // In test mode, manually remove from queue since Uploader callback won't be called _queue.remove(item); _saveQueue(); notifyListeners(); - }); - - final ok = await up.upload(item); + } if (!ok) { item.attempts++; if (item.attempts >= 3) { @@ -237,5 +263,21 @@ class AppState extends ChangeNotifier { // ---------- Exposed getters ---------- int get pendingCount => _queue.length; + List get pendingUploads => List.unmodifiable(_queue); + + // ---------- Queue management ---------- + void clearQueue() { + print('AppState: Clearing upload queue (${_queue.length} items)'); + _queue.clear(); + _saveQueue(); + notifyListeners(); + } + + void removeFromQueue(PendingUpload upload) { + print('AppState: Removing upload from queue: ${upload.coord}'); + _queue.remove(upload); + _saveQueue(); + notifyListeners(); + } } diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5c7139e..139ca6c 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -88,10 +88,112 @@ class SettingsScreen extends StatelessWidget { ), ), const Divider(), - ListTile( - leading: const Icon(Icons.sync), - title: Text('Pending uploads: ${appState.pendingCount}'), + // Test mode toggle + SwitchListTile( + secondary: const Icon(Icons.bug_report), + title: const Text('Test Mode'), + subtitle: const Text('Simulate uploads without sending to OSM'), + value: appState.testMode, + onChanged: (value) => appState.setTestMode(value), ), + const Divider(), + // Queue management + ListTile( + leading: const Icon(Icons.queue), + title: Text('Pending uploads: ${appState.pendingCount}'), + subtitle: appState.testMode + ? const Text('Test mode enabled - uploads simulated') + : const Text('Tap to view queue'), + onTap: appState.pendingCount > 0 ? () { + _showQueueDialog(context, appState); + } : null, + ), + if (appState.pendingCount > 0) + ListTile( + leading: const Icon(Icons.clear_all), + title: const Text('Clear Upload Queue'), + subtitle: Text('Remove all ${appState.pendingCount} pending uploads'), + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Clear Queue'), + content: Text('Remove all ${appState.pendingCount} pending uploads?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + appState.clearQueue(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Queue cleared')), + ); + }, + child: const Text('Clear'), + ), + ], + ), + ); + }, + ), + ], + ), + ); + } + + void _showQueueDialog(BuildContext context, AppState appState) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Upload Queue (${appState.pendingCount} items)'), + content: SizedBox( + width: double.maxFinite, + height: 300, + child: ListView.builder( + itemCount: appState.pendingUploads.length, + itemBuilder: (context, index) { + final upload = appState.pendingUploads[index]; + return ListTile( + leading: const Icon(Icons.camera_alt), + title: Text('Camera ${index + 1}'), + subtitle: Text( + 'Lat: ${upload.coord.latitude.toStringAsFixed(6)}\n' + 'Lon: ${upload.coord.longitude.toStringAsFixed(6)}\n' + 'Direction: ${upload.direction.round()}°\n' + 'Attempts: ${upload.attempts}' + ), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + appState.removeFromQueue(upload); + if (appState.pendingCount == 0) { + Navigator.pop(context); + } + }, + ), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + if (appState.pendingCount > 1) + TextButton( + onPressed: () { + appState.clearQueue(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Queue cleared')), + ); + }, + child: const Text('Clear All'), + ), ], ), );