From c712aba72463935f7af110630c7738f302619a3b Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Mon, 2 Feb 2026 00:55:03 -0700 Subject: [PATCH 1/4] Add flutter_lints and fix analyzer errors, dead code, and unused imports --- lib/app_state.dart | 2 -- lib/migrations.dart | 1 - lib/models/direction_fov.dart | 2 +- lib/models/node_profile.dart | 1 - lib/models/operator_profile.dart | 1 - lib/screens/advanced_settings_screen.dart | 1 - .../coordinators/sheet_coordinator.dart | 3 +- lib/screens/home_screen.dart | 34 ------------------- lib/screens/operator_profile_editor.dart | 1 - lib/screens/profile_editor.dart | 1 - .../settings/sections/language_section.dart | 6 ++-- .../sections/offline_areas_section.dart | 8 ++--- .../settings/sections/queue_section.dart | 17 +++++----- .../sections/upload_mode_section.dart | 1 - lib/screens/tile_provider_editor_screen.dart | 1 - lib/screens/upload_queue_screen.dart | 27 +++++++-------- lib/services/auth_service.dart | 4 +-- lib/services/changelog_service.dart | 5 ++- lib/services/deep_link_service.dart | 3 +- lib/services/deflock_tile_provider.dart | 10 +++--- lib/services/map_data_provider.dart | 1 - .../nodes_from_osm_api.dart | 3 +- .../map_data_submodules/tiles_from_local.dart | 1 - lib/services/network_status.dart | 2 +- lib/services/node_data_manager.dart | 2 -- lib/services/offline_area_service.dart | 3 -- lib/services/routing_service.dart | 10 +++--- lib/services/suspected_location_cache.dart | 1 - lib/services/suspected_location_database.dart | 2 -- lib/services/suspected_location_service.dart | 4 +-- lib/services/tile_preview_service.dart | 1 + lib/services/uploader.dart | 10 ++---- lib/state/search_state.dart | 1 - lib/widgets/add_node_sheet.dart | 34 +++++-------------- lib/widgets/changelog_dialog.dart | 1 - lib/widgets/compass_indicator.dart | 1 - lib/widgets/download_area_dialog.dart | 4 +-- lib/widgets/edit_node_sheet.dart | 23 +++++-------- lib/widgets/map/direction_cones.dart | 28 --------------- lib/widgets/map/map_data_manager.dart | 1 - lib/widgets/map/map_overlays.dart | 2 -- lib/widgets/map/marker_layer_builder.dart | 1 - lib/widgets/map/node_markers.dart | 4 +-- lib/widgets/map/node_refresh_controller.dart | 1 - .../map/suspected_location_markers.dart | 4 +-- lib/widgets/map/tile_layer_manager.dart | 3 +- lib/widgets/node_provider_with_cache.dart | 3 -- lib/widgets/nuclear_reset_dialog.dart | 8 ++--- lib/widgets/proximity_warning_dialog.dart | 1 - lib/widgets/search_bar.dart | 6 ++-- lib/widgets/suspected_location_sheet.dart | 3 -- pubspec.lock | 16 +++++++++ pubspec.yaml | 3 ++ test/models/tile_provider_test.dart | 16 ++++----- 54 files changed, 107 insertions(+), 226 deletions(-) diff --git a/lib/app_state.dart b/lib/app_state.dart index 78ef65d..88b92bf 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; @@ -23,7 +22,6 @@ import 'services/operator_profile_service.dart'; import 'services/deep_link_service.dart'; import 'widgets/node_provider_with_cache.dart'; import 'services/profile_service.dart'; -import 'widgets/proximity_warning_dialog.dart'; import 'widgets/reauth_messages_dialog.dart'; import 'dev_config.dart'; import 'state/auth_state.dart'; diff --git a/lib/migrations.dart b/lib/migrations.dart index a661501..54f2dca 100644 --- a/lib/migrations.dart +++ b/lib/migrations.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/lib/models/direction_fov.dart b/lib/models/direction_fov.dart index 9e65fd5..948a60e 100644 --- a/lib/models/direction_fov.dart +++ b/lib/models/direction_fov.dart @@ -9,7 +9,7 @@ class DirectionFov { DirectionFov(this.centerDegrees, this.fovDegrees); @override - String toString() => 'DirectionFov(center: ${centerDegrees}°, fov: ${fovDegrees}°)'; + String toString() => 'DirectionFov(center: $centerDegrees°, fov: $fovDegrees°)'; @override bool operator ==(Object other) => diff --git a/lib/models/node_profile.dart b/lib/models/node_profile.dart index e532cde..61aaad6 100644 --- a/lib/models/node_profile.dart +++ b/lib/models/node_profile.dart @@ -1,4 +1,3 @@ -import 'package:uuid/uuid.dart'; import 'osm_node.dart'; /// Sentinel value for copyWith methods to distinguish between null and not provided diff --git a/lib/models/operator_profile.dart b/lib/models/operator_profile.dart index 30db363..487086a 100644 --- a/lib/models/operator_profile.dart +++ b/lib/models/operator_profile.dart @@ -1,4 +1,3 @@ -import 'package:uuid/uuid.dart'; import 'osm_node.dart'; /// A bundle of OSM tags that describe a particular surveillance operator. diff --git a/lib/screens/advanced_settings_screen.dart b/lib/screens/advanced_settings_screen.dart index 17bf570..b5630a4 100644 --- a/lib/screens/advanced_settings_screen.dart +++ b/lib/screens/advanced_settings_screen.dart @@ -3,7 +3,6 @@ import 'settings/sections/max_nodes_section.dart'; import 'settings/sections/proximity_alerts_section.dart'; import 'settings/sections/suspected_locations_section.dart'; import 'settings/sections/tile_provider_section.dart'; -import 'settings/sections/network_status_section.dart'; import '../services/localization_service.dart'; class AdvancedSettingsScreen extends StatelessWidget { diff --git a/lib/screens/coordinators/sheet_coordinator.dart b/lib/screens/coordinators/sheet_coordinator.dart index 14f15f4..c984641 100644 --- a/lib/screens/coordinators/sheet_coordinator.dart +++ b/lib/screens/coordinators/sheet_coordinator.dart @@ -9,7 +9,6 @@ import '../../widgets/add_node_sheet.dart'; import '../../widgets/edit_node_sheet.dart'; import '../../widgets/navigation_sheet.dart'; import '../../widgets/measured_sheet.dart'; -import '../../state/settings_state.dart' show FollowMeMode; /// Coordinates all bottom sheet operations including opening, closing, height tracking, /// and sheet-related validation logic. @@ -266,7 +265,7 @@ class SheetCoordinator { /// Restore the follow-me mode that was active before opening a node sheet void _restoreFollowMeMode(AppState appState) { if (_followMeModeBeforeSheet != null) { - debugPrint('[SheetCoordinator] Restoring follow-me mode: ${_followMeModeBeforeSheet}'); + debugPrint('[SheetCoordinator] Restoring follow-me mode: $_followMeModeBeforeSheet'); appState.setFollowMeMode(_followMeModeBeforeSheet!); _followMeModeBeforeSheet = null; // Clear stored state } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 32f01d9..3242edc 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import '../app_state.dart'; @@ -10,12 +8,9 @@ import '../dev_config.dart'; import '../widgets/map_view.dart'; import '../services/localization_service.dart'; -import '../widgets/add_node_sheet.dart'; -import '../widgets/edit_node_sheet.dart'; import '../widgets/node_tag_sheet.dart'; import '../widgets/download_area_dialog.dart'; import '../widgets/measured_sheet.dart'; -import '../widgets/navigation_sheet.dart'; import '../widgets/search_bar.dart'; import '../widgets/suspected_location_sheet.dart'; import '../widgets/welcome_dialog.dart'; @@ -252,35 +247,6 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ); } - void _zoomAndCenterForRoute(bool followMeEnabled, LatLng? userLocation, LatLng? routeStart) { - try { - LatLng centerLocation; - - if (followMeEnabled && userLocation != null) { - // Center on user if follow-me is enabled - centerLocation = userLocation; - debugPrint('[HomeScreen] Centering on user location for route start'); - } else if (routeStart != null) { - // Center on start pin if user is far away or no GPS - centerLocation = routeStart; - debugPrint('[HomeScreen] Centering on route start pin'); - } else { - debugPrint('[HomeScreen] No valid location to center on'); - return; - } - - // Animate to zoom 14 and center location - _mapController.animateTo( - dest: centerLocation, - zoom: 14.0, - duration: const Duration(milliseconds: 800), - curve: Curves.easeInOut, - ); - } catch (e) { - debugPrint('[HomeScreen] Could not zoom/center for route: $e'); - } - } - void _onResumeRoute() { _navigationCoordinator.resumeRoute( context: context, diff --git a/lib/screens/operator_profile_editor.dart b/lib/screens/operator_profile_editor.dart index 486a58a..b967d62 100644 --- a/lib/screens/operator_profile_editor.dart +++ b/lib/screens/operator_profile_editor.dart @@ -105,7 +105,6 @@ class _OperatorProfileEditorState extends State { return List.generate(_tags.length, (i) { final keyController = TextEditingController(text: _tags[i].key); - final valueController = TextEditingController(text: _tags[i].value); return Padding( padding: const EdgeInsets.only(bottom: 8.0), diff --git a/lib/screens/profile_editor.dart b/lib/screens/profile_editor.dart index e607479..08a6bf5 100644 --- a/lib/screens/profile_editor.dart +++ b/lib/screens/profile_editor.dart @@ -153,7 +153,6 @@ class _ProfileEditorState extends State { return List.generate(_tags.length, (i) { final keyController = TextEditingController(text: _tags[i].key); - final valueController = TextEditingController(text: _tags[i].value); return Padding( padding: const EdgeInsets.only(bottom: 8.0), diff --git a/lib/screens/settings/sections/language_section.dart b/lib/screens/settings/sections/language_section.dart index 6dbb42d..ccf58ca 100644 --- a/lib/screens/settings/sections/language_section.dart +++ b/lib/screens/settings/sections/language_section.dart @@ -23,14 +23,14 @@ class _LanguageSectionState extends State { _loadLanguageNames(); } - _loadSelectedLanguage() async { + Future _loadSelectedLanguage() async { final prefs = await SharedPreferences.getInstance(); setState(() { _selectedLanguage = prefs.getString('language_code'); }); } - _loadLanguageNames() async { + Future _loadLanguageNames() async { final locService = LocalizationService.instance; final Map names = {}; @@ -43,7 +43,7 @@ class _LanguageSectionState extends State { }); } - _setLanguage(String? languageCode) async { + Future _setLanguage(String? languageCode) async { await LocalizationService.instance.setLanguage(languageCode); setState(() { _selectedLanguage = languageCode; diff --git a/lib/screens/settings/sections/offline_areas_section.dart b/lib/screens/settings/sections/offline_areas_section.dart index 8c623c0..51261e8 100644 --- a/lib/screens/settings/sections/offline_areas_section.dart +++ b/lib/screens/settings/sections/offline_areas_section.dart @@ -87,9 +87,9 @@ class _OfflineAreasSectionState extends State { : "${(area.sizeBytes / 1024).toStringAsFixed(1)} ${locService.t('offlineAreas.kilobytes')}" : '--'; - String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' + - '${locService.t('offlineAreas.maxZoom')}: Z${area.maxZoom}' + '\n' + - '${locService.t('offlineAreas.latitude')}: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' + + String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' + '${locService.t('offlineAreas.maxZoom')}: Z${area.maxZoom}\n' + '${locService.t('offlineAreas.latitude')}: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' '${locService.t('offlineAreas.latitude')}: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}'; if (area.status == OfflineAreaStatus.downloading) { @@ -207,7 +207,7 @@ class _OfflineAreasSectionState extends State { : null, ), ); - }).toList(), + }), ], ); }, diff --git a/lib/screens/settings/sections/queue_section.dart b/lib/screens/settings/sections/queue_section.dart index 4c4d946..45baa1c 100644 --- a/lib/screens/settings/sections/queue_section.dart +++ b/lib/screens/settings/sections/queue_section.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../../app_state.dart'; import '../../../services/localization_service.dart'; -import '../../../state/settings_state.dart'; class QueueSection extends StatelessWidget { const QueueSection({super.key}); @@ -116,16 +115,16 @@ class QueueSection extends StatelessWidget { (upload.error ? locService.t('queue.error') : "") + (upload.completing ? locService.t('queue.completing') : "")), subtitle: Text( - locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)]) + '\n' + - locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)]) + '\n' + - locService.t('queue.longitude', params: [upload.coord.longitude.toStringAsFixed(6)]) + '\n' + - locService.t('queue.direction', params: [ - upload.direction is String + '${locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)])}\n' + '${locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)])}\n' + '${locService.t('queue.longitude', params: [upload.coord.longitude.toStringAsFixed(6)])}\n' + '${locService.t('queue.direction', params: [ + upload.direction is String ? upload.direction.toString() : upload.direction.round().toString() - ]) + '\n' + - locService.t('queue.attempts', params: [upload.attempts.toString()]) + - (upload.error ? "\n${locService.t('queue.uploadFailedRetry')}" : "") + ])}\n' + '${locService.t('queue.attempts', params: [upload.attempts.toString()])}' + '${upload.error ? "\n${locService.t('queue.uploadFailedRetry')}" : ""}' ), trailing: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/screens/settings/sections/upload_mode_section.dart b/lib/screens/settings/sections/upload_mode_section.dart index 6875596..65d280e 100644 --- a/lib/screens/settings/sections/upload_mode_section.dart +++ b/lib/screens/settings/sections/upload_mode_section.dart @@ -95,7 +95,6 @@ class UploadModeSection extends StatelessWidget { ), ); case UploadMode.simulate: - default: return Text( locService.t('uploadMode.simulateDescription'), style: TextStyle( diff --git a/lib/screens/tile_provider_editor_screen.dart b/lib/screens/tile_provider_editor_screen.dart index 01a51d2..213090a 100644 --- a/lib/screens/tile_provider_editor_screen.dart +++ b/lib/screens/tile_provider_editor_screen.dart @@ -2,7 +2,6 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; -import 'package:collection/collection.dart'; import '../app_state.dart'; import '../models/tile_provider.dart'; diff --git a/lib/screens/upload_queue_screen.dart b/lib/screens/upload_queue_screen.dart index 3221030..16ca969 100644 --- a/lib/screens/upload_queue_screen.dart +++ b/lib/screens/upload_queue_screen.dart @@ -3,7 +3,6 @@ import 'package:provider/provider.dart'; import '../app_state.dart'; import '../models/pending_upload.dart'; import '../services/localization_service.dart'; -import '../state/settings_state.dart'; class UploadQueueScreen extends StatelessWidget { const UploadQueueScreen({super.key}); @@ -114,8 +113,8 @@ class UploadQueueScreen extends StatelessWidget { margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + color: Colors.orange.withValues(alpha: 0.1), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), borderRadius: BorderRadius.circular(8), ), child: Row( @@ -205,7 +204,7 @@ class UploadQueueScreen extends StatelessWidget { icon: const Icon(Icons.clear_all), label: Text(locService.t('queue.clearUploadQueue')), style: ElevatedButton.styleFrom( - backgroundColor: appState.pendingCount > 0 ? null : Theme.of(context).disabledColor.withOpacity(0.1), + backgroundColor: appState.pendingCount > 0 ? null : Theme.of(context).disabledColor.withValues(alpha: 0.1), ), ), ), @@ -224,13 +223,13 @@ class UploadQueueScreen extends StatelessWidget { Icon( Icons.check_circle_outline, size: 64, - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.4), + color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.4), ), const SizedBox(height: 16), Text( locService.t('queue.nothingInQueue'), style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), @@ -272,16 +271,16 @@ class UploadQueueScreen extends StatelessWidget { _getUploadStateText(upload, locService) ), subtitle: Text( - locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)]) + '\n' + - locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)]) + '\n' + - locService.t('queue.longitude', params: [upload.coord.longitude.toStringAsFixed(6)]) + '\n' + - locService.t('queue.direction', params: [ - upload.direction is String + '${locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)])}\n' + '${locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)])}\n' + '${locService.t('queue.longitude', params: [upload.coord.longitude.toStringAsFixed(6)])}\n' + '${locService.t('queue.direction', params: [ + upload.direction is String ? upload.direction.toString() : upload.direction.round().toString() - ]) + '\n' + - locService.t('queue.attempts', params: [upload.attempts.toString()]) + - (upload.uploadState == UploadState.error ? "\n${locService.t('queue.uploadFailedRetry')}" : "") + ])}\n' + '${locService.t('queue.attempts', params: [upload.attempts.toString()])}' + '${upload.uploadState == UploadState.error ? "\n${locService.t('queue.uploadFailedRetry')}" : ""}' ), trailing: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index bdc1b9c..843d642 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:developer'; -import 'dart:math' as math; import 'package:oauth2_client/oauth2_client.dart'; import 'package:oauth2_client/oauth2_helper.dart'; @@ -30,7 +29,6 @@ class AuthService { case UploadMode.sandbox: return 'osm_token_sandbox'; case UploadMode.simulate: - default: return 'osm_token_simulate'; } } @@ -97,7 +95,7 @@ class AuthService { final tokenJson = jsonEncode(tokenMap); final prefs = await SharedPreferences.getInstance(); await prefs.setString(_tokenKey, tokenJson); // Save token for current mode - _displayName = await _fetchUsername(token!.accessToken!); + _displayName = await _fetchUsername(token.accessToken!); return _displayName; } catch (e) { print('AuthService: OAuth login failed: $e'); diff --git a/lib/services/changelog_service.dart b/lib/services/changelog_service.dart index 244fc19..9244d70 100644 --- a/lib/services/changelog_service.dart +++ b/lib/services/changelog_service.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -304,8 +303,8 @@ class ChangelogService { final v2Parts = v2.split('.').map(int.parse).toList(); // Ensure we have at least 3 parts (major.minor.patch) - while (v1Parts.length < 3) v1Parts.add(0); - while (v2Parts.length < 3) v2Parts.add(0); + while (v1Parts.length < 3) { v1Parts.add(0); } + while (v2Parts.length < 3) { v2Parts.add(0); } // Compare major version first if (v1Parts[0] < v2Parts[0]) return -1; diff --git a/lib/services/deep_link_service.dart b/lib/services/deep_link_service.dart index ca57599..cbf4f2e 100644 --- a/lib/services/deep_link_service.dart +++ b/lib/services/deep_link_service.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:app_links/app_links.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart'; import '../models/node_profile.dart'; import 'profile_import_service.dart'; @@ -87,7 +86,7 @@ class DeepLinkService { } } - /// Handle profile add deep link: deflockapp://profiles/add?p= + /// Handle profile add deep link: `deflockapp://profiles/add?p=` void _handleAddProfileLink(Uri uri) { final base64Data = uri.queryParameters['p']; diff --git a/lib/services/deflock_tile_provider.dart b/lib/services/deflock_tile_provider.dart index 0c95d53..f5b9533 100644 --- a/lib/services/deflock_tile_provider.dart +++ b/lib/services/deflock_tile_provider.dart @@ -1,12 +1,12 @@ -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + import '../app_state.dart'; -import '../models/tile_provider.dart' as models; import 'map_data_provider.dart'; import 'offline_area_service.dart'; @@ -108,7 +108,7 @@ class DeflockTileImageProvider extends ImageProvider { // Re-throw the exception and let FlutterMap handle missing tiles gracefully // This is better than trying to provide fallback images - throw e; + rethrow; } } diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index d989589..34c24c8 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -1,6 +1,5 @@ import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter/foundation.dart'; import '../models/node_profile.dart'; import '../models/osm_node.dart'; diff --git a/lib/services/map_data_submodules/nodes_from_osm_api.dart b/lib/services/map_data_submodules/nodes_from_osm_api.dart index 142e6fd..8d7e676 100644 --- a/lib/services/map_data_submodules/nodes_from_osm_api.dart +++ b/lib/services/map_data_submodules/nodes_from_osm_api.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; @@ -81,7 +80,7 @@ Future> _fetchFromOsmApi({ debugPrint('[fetchOsmApiNodes] Exception: $e'); // Don't report status here - let the top level handle it - throw e; // Re-throw to let caller handle + rethrow; // Re-throw to let caller handle } } diff --git a/lib/services/map_data_submodules/tiles_from_local.dart b/lib/services/map_data_submodules/tiles_from_local.dart index 69b732c..373f20d 100644 --- a/lib/services/map_data_submodules/tiles_from_local.dart +++ b/lib/services/map_data_submodules/tiles_from_local.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'package:latlong2/latlong.dart'; import '../offline_area_service.dart'; import '../offline_areas/offline_area_models.dart'; import '../offline_areas/offline_tile_utils.dart'; diff --git a/lib/services/network_status.dart b/lib/services/network_status.dart index f482b69..4dd3bb3 100644 --- a/lib/services/network_status.dart +++ b/lib/services/network_status.dart @@ -5,7 +5,7 @@ import 'dart:async'; /// Only tracks the latest user-initiated request - background requests are ignored. enum NetworkRequestStatus { idle, // No active requests - loading, // Request in progress + loading, // Request in progress splitting, // Request being split due to limits/timeouts success, // Data loaded successfully timeout, // Request timed out diff --git a/lib/services/node_data_manager.dart b/lib/services/node_data_manager.dart index 94da51a..2dbaeeb 100644 --- a/lib/services/node_data_manager.dart +++ b/lib/services/node_data_manager.dart @@ -1,6 +1,4 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; diff --git a/lib/services/offline_area_service.dart b/lib/services/offline_area_service.dart index ccdcf8c..9300825 100644 --- a/lib/services/offline_area_service.dart +++ b/lib/services/offline_area_service.dart @@ -1,17 +1,14 @@ import 'dart:io'; import 'dart:convert'; import 'package:flutter/foundation.dart'; -import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import 'package:path_provider/path_provider.dart'; import 'offline_areas/offline_area_models.dart'; import 'offline_areas/offline_tile_utils.dart'; import 'offline_areas/offline_area_downloader.dart'; -import '../models/osm_node.dart'; import '../app_state.dart'; import 'map_data_provider.dart'; -import 'package:deflockapp/dev_config.dart'; /// Service for managing download, storage, and retrieval of offline map areas and cameras. class OfflineAreaService { diff --git a/lib/services/routing_service.dart b/lib/services/routing_service.dart index 94b43bc..b2dc90f 100644 --- a/lib/services/routing_service.dart +++ b/lib/services/routing_service.dart @@ -36,9 +36,9 @@ class RoutingService { debugPrint('[RoutingService] Calculating route from $start to $end'); final prefs = await SharedPreferences.getInstance(); - final avoidance_distance = await prefs.getInt('navigation_avoidance_distance'); + final avoidanceDistance = prefs.getInt('navigation_avoidance_distance') ?? 250; - final enabled_profiles = AppState.instance.enabledProfiles.map((p) { + final enabledProfiles = AppState.instance.enabledProfiles.map((p) { final full = p.toJson(); return { 'id': full['id'], @@ -47,7 +47,7 @@ class RoutingService { }; }).toList(); - final uri = Uri.parse('$_baseUrl'); + final uri = Uri.parse(_baseUrl); final params = { 'start': { 'longitude': start.longitude, @@ -57,8 +57,8 @@ class RoutingService { 'longitude': end.longitude, 'latitude': end.latitude }, - 'avoidance_distance': avoidance_distance, - 'enabled_profiles': enabled_profiles, + 'avoidance_distance': avoidanceDistance, + 'enabled_profiles': enabledProfiles, 'show_exclusion_zone': false, // for debugging: if true, returns a GeoJSON Feature MultiPolygon showing what areas are avoided in calculating the route }; diff --git a/lib/services/suspected_location_cache.dart b/lib/services/suspected_location_cache.dart index bb58480..9c4c72b 100644 --- a/lib/services/suspected_location_cache.dart +++ b/lib/services/suspected_location_cache.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; import '../models/suspected_location.dart'; import 'suspected_location_service.dart'; diff --git a/lib/services/suspected_location_database.dart b/lib/services/suspected_location_database.dart index b893b9f..c6c61e3 100644 --- a/lib/services/suspected_location_database.dart +++ b/lib/services/suspected_location_database.dart @@ -143,7 +143,6 @@ class SuspectedLocationDatabase { // Process entries in batches to avoid memory issues const batchSize = 1000; - int totalInserted = 0; int validCount = 0; int errorCount = 0; @@ -188,7 +187,6 @@ class SuspectedLocationDatabase { // Commit this batch await batch.commit(noResult: true); - totalInserted += currentBatch.length; // Log progress every few batches if ((i ~/ batchSize) % 5 == 0) { diff --git a/lib/services/suspected_location_service.dart b/lib/services/suspected_location_service.dart index 4f82c96..8f57f33 100644 --- a/lib/services/suspected_location_service.dart +++ b/lib/services/suspected_location_service.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:http/http.dart' as http; @@ -208,7 +206,7 @@ class SuspectedLocationService { validRows++; } - } catch (e, stackTrace) { + } catch (e) { // Skip rows that can't be parsed debugPrint('[SuspectedLocationService] Error parsing row $rowIndex: $e'); continue; diff --git a/lib/services/tile_preview_service.dart b/lib/services/tile_preview_service.dart index 91d3b68..d04abb3 100644 --- a/lib/services/tile_preview_service.dart +++ b/lib/services/tile_preview_service.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; diff --git a/lib/services/uploader.dart b/lib/services/uploader.dart index 88926fc..8c675ae 100644 --- a/lib/services/uploader.dart +++ b/lib/services/uploader.dart @@ -147,7 +147,7 @@ class Uploader { final currentNodeXml = currentNodeResp.body; final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml); if (versionMatch == null) { - final errorMsg = 'Could not parse version from node XML: ${currentNodeXml.length > 200 ? currentNodeXml.substring(0, 200) + "..." : currentNodeXml}'; + final errorMsg = 'Could not parse version from node XML: ${currentNodeXml.length > 200 ? '${currentNodeXml.substring(0, 200)}...' : currentNodeXml}'; debugPrint('[Uploader] $errorMsg'); return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId); } @@ -184,7 +184,7 @@ class Uploader { final currentNodeXml = currentNodeResp.body; final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml); if (versionMatch == null) { - final errorMsg = 'Could not parse version from node XML for deletion: ${currentNodeXml.length > 200 ? currentNodeXml.substring(0, 200) + "..." : currentNodeXml}'; + final errorMsg = 'Could not parse version from node XML for deletion: ${currentNodeXml.length > 200 ? '${currentNodeXml.substring(0, 200)}...' : currentNodeXml}'; debugPrint('[Uploader] $errorMsg'); return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId); } @@ -350,12 +350,6 @@ class Uploader { headers: _headers, ).timeout(kUploadHttpTimeout); - Future _post(String path, String body) => http.post( - Uri.https(_host, path), - headers: _headers, - body: body, - ).timeout(kUploadHttpTimeout); - Future _put(String path, String body) => http.put( Uri.https(_host, path), headers: _headers, diff --git a/lib/state/search_state.dart b/lib/state/search_state.dart index 3adccea..7bc915b 100644 --- a/lib/state/search_state.dart +++ b/lib/state/search_state.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; import '../models/search_result.dart'; import '../services/search_service.dart'; diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 0a7ec2b..49bc90e 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -7,7 +7,6 @@ import 'package:flutter_map/flutter_map.dart'; import '../app_state.dart'; import '../dev_config.dart'; import '../models/node_profile.dart'; -import '../models/operator_profile.dart'; import '../services/localization_service.dart'; import '../services/map_data_provider.dart'; import '../services/node_data_manager.dart'; @@ -59,23 +58,6 @@ class _AddNodeSheetState extends State { } } - /// Listen for tutorial completion from AppState - void _onTutorialCompleted() { - _hideTutorial(); - } - - /// Also check periodically if tutorial was completed by another sheet - void _recheckTutorialStatus() async { - if (_showTutorial) { - final hasCompleted = await ChangelogService().hasCompletedPositioningTutorial(); - if (hasCompleted && mounted) { - setState(() { - _showTutorial = false; - }); - } - } - } - void _hideTutorial() { if (mounted && _showTutorial) { setState(() { @@ -402,11 +384,11 @@ class _AddNodeSheetState extends State { final locService = LocalizationService.instance; final appState = context.watch(); - void _commit() { + void commit() { _checkProximityAndCommit(context, appState, locService); } - void _cancel() { + void cancel() { appState.cancelSession(); Navigator.pop(context); } @@ -442,11 +424,11 @@ class _AddNodeSheetState extends State { session.profile!.isSubmittable && hasGoodCoverage; - void _navigateToLogin() { + void navigateToLogin() { Navigator.pushNamed(context, '/settings/osm-account'); } - - void _openRefineTags() async { + + void openRefineTags() async { final result = await Navigator.push( context, MaterialPageRoute( @@ -578,7 +560,7 @@ class _AddNodeSheetState extends State { child: SizedBox( width: double.infinity, child: OutlinedButton.icon( - onPressed: session.profile != null ? _openRefineTags : null, // Disabled when no profile selected + onPressed: session.profile != null ? openRefineTags : null, // Disabled when no profile selected icon: const Icon(Icons.tune), label: Text(locService.t('addNode.refineTags')), ), @@ -591,14 +573,14 @@ class _AddNodeSheetState extends State { children: [ Expanded( child: OutlinedButton( - onPressed: _cancel, + onPressed: cancel, child: Text(locService.cancel), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( - onPressed: !appState.isLoggedIn ? _navigateToLogin : (allowSubmit ? _commit : null), + onPressed: !appState.isLoggedIn ? navigateToLogin : (allowSubmit ? commit : null), child: Text(!appState.isLoggedIn ? locService.t('actions.logIn') : locService.t('actions.submit')), ), ), diff --git a/lib/widgets/changelog_dialog.dart b/lib/widgets/changelog_dialog.dart index 862508b..99216ca 100644 --- a/lib/widgets/changelog_dialog.dart +++ b/lib/widgets/changelog_dialog.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import '../services/changelog_service.dart'; import '../services/version_service.dart'; class ChangelogDialog extends StatelessWidget { diff --git a/lib/widgets/compass_indicator.dart b/lib/widgets/compass_indicator.dart index e804836..4770379 100644 --- a/lib/widgets/compass_indicator.dart +++ b/lib/widgets/compass_indicator.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/download_area_dialog.dart b/lib/widgets/download_area_dialog.dart index 2888490..0a4ef32 100644 --- a/lib/widgets/download_area_dialog.dart +++ b/lib/widgets/download_area_dialog.dart @@ -57,8 +57,7 @@ class _DownloadAreaDialogState extends State { } final minZoom = 1; // Always start from zoom 1 to show area overview when zoomed out - final maxZoom = _zoom.toInt(); - + // Calculate maximum possible zoom based on tile count limit and tile provider max zoom final maxPossibleZoom = _calculateMaxZoomForTileLimit(bounds, minZoom); @@ -124,7 +123,6 @@ class _DownloadAreaDialogState extends State { final locService = LocalizationService.instance; final appState = context.watch(); final bounds = widget.controller.camera.visibleBounds; - final maxZoom = _zoom.toInt(); final isOfflineMode = appState.offlineMode; // Use the calculated max possible zoom instead of fixed span diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 99421e0..c9c197e 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -7,14 +7,11 @@ import 'package:flutter_map/flutter_map.dart'; import '../app_state.dart'; import '../dev_config.dart'; import '../models/node_profile.dart'; -import '../models/operator_profile.dart'; import '../models/pending_upload.dart'; import '../services/localization_service.dart'; import '../services/map_data_provider.dart'; import '../services/node_data_manager.dart'; import '../services/changelog_service.dart'; -import '../state/settings_state.dart'; -import '../state/session_state.dart'; import 'refine_tags_sheet.dart'; import 'advanced_edit_options_sheet.dart'; import 'proximity_warning_dialog.dart'; @@ -435,25 +432,23 @@ class _EditNodeSheetState extends State { final locService = LocalizationService.instance; final appState = context.watch(); - void _commit() { + void commit() { // Check if there are any actual changes to submit if (!_hasActualChanges(widget.session)) { _showNoChangesDialog(context, locService); return; } - + _checkProximityAndCommit(context, appState, locService); } - void _cancel() { + void cancel() { appState.cancelEditSession(); Navigator.pop(context); } final session = widget.session; final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); - final isSandboxMode = appState.uploadMode == UploadMode.sandbox; - // Check if we have good cache coverage around the node position bool hasGoodCoverage = true; final nodeCoord = session.originalNode.coord; @@ -481,11 +476,11 @@ class _EditNodeSheetState extends State { session.profile!.isSubmittable && hasGoodCoverage; - void _navigateToLogin() { + void navigateToLogin() { Navigator.pushNamed(context, '/settings/osm-account'); } - - void _openRefineTags() async { + + void openRefineTags() async { final result = await Navigator.push( context, MaterialPageRoute( @@ -694,7 +689,7 @@ class _EditNodeSheetState extends State { child: SizedBox( width: double.infinity, child: OutlinedButton.icon( - onPressed: session.profile != null ? _openRefineTags : null, // Disabled when no profile selected + onPressed: session.profile != null ? openRefineTags : null, // Disabled when no profile selected icon: const Icon(Icons.tune), label: Text(locService.t('editNode.refineTags')), ), @@ -707,14 +702,14 @@ class _EditNodeSheetState extends State { children: [ Expanded( child: OutlinedButton( - onPressed: _cancel, + onPressed: cancel, child: Text(locService.cancel), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton( - onPressed: !appState.isLoggedIn ? _navigateToLogin : (allowSubmit ? _commit : null), + onPressed: !appState.isLoggedIn ? navigateToLogin : (allowSubmit ? commit : null), child: Text(!appState.isLoggedIn ? locService.t('actions.logIn') : locService.t('actions.saveEdit')), ), ), diff --git a/lib/widgets/map/direction_cones.dart b/lib/widgets/map/direction_cones.dart index df984a3..fce8265 100644 --- a/lib/widgets/map/direction_cones.dart +++ b/lib/widgets/map/direction_cones.dart @@ -6,7 +6,6 @@ import 'package:latlong2/latlong.dart'; import '../../app_state.dart'; import '../../dev_config.dart'; import '../../models/osm_node.dart'; -import '../../models/direction_fov.dart'; /// Helper class to build direction cone polygons for cameras class DirectionConesBuilder { @@ -113,11 +112,6 @@ class DirectionConesBuilder { node.coord.longitude.abs() <= 180; } - static bool _isPendingUpload(OsmNode node) { - return node.tags.containsKey('_pending_upload') && - node.tags['_pending_upload'] == 'true'; - } - /// Build cone with variable FOV width - new method for range notation support static Polygon _buildConeWithFov( LatLng origin, @@ -141,28 +135,6 @@ class DirectionConesBuilder { ); } - /// Legacy method for backward compatibility - uses dev_config FOV - static Polygon _buildCone( - LatLng origin, - double bearingDeg, - double zoom, { - required BuildContext context, - bool isPending = false, - bool isSession = false, - bool isActiveDirection = true, - }) { - return _buildConeInternal( - origin: origin, - bearingDeg: bearingDeg, - halfAngleDeg: kDirectionConeHalfAngle, - zoom: zoom, - context: context, - isPending: isPending, - isSession: isSession, - isActiveDirection: isActiveDirection, - ); - } - /// Internal cone building method that handles the actual rendering static Polygon _buildConeInternal({ required LatLng origin, diff --git a/lib/widgets/map/map_data_manager.dart b/lib/widgets/map/map_data_manager.dart index d9f3676..a74c6da 100644 --- a/lib/widgets/map/map_data_manager.dart +++ b/lib/widgets/map/map_data_manager.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:latlong2/latlong.dart'; import '../../models/osm_node.dart'; diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index e02cf99..b284319 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:provider/provider.dart'; import '../../app_state.dart'; import '../../dev_config.dart'; import '../../services/localization_service.dart'; -import '../camera_icon.dart'; import '../compass_indicator.dart'; import 'layer_selector_button.dart'; diff --git a/lib/widgets/map/marker_layer_builder.dart b/lib/widgets/map/marker_layer_builder.dart index f70a9dd..26528b4 100644 --- a/lib/widgets/map/marker_layer_builder.dart +++ b/lib/widgets/map/marker_layer_builder.dart @@ -6,7 +6,6 @@ import 'package:latlong2/latlong.dart'; import '../../models/osm_node.dart'; import '../../models/suspected_location.dart'; import '../../app_state.dart'; -import '../../state/session_state.dart'; import '../../dev_config.dart'; import '../camera_icon.dart'; import '../provisional_pin.dart'; diff --git a/lib/widgets/map/node_markers.dart b/lib/widgets/map/node_markers.dart index 4d1292c..7c386be 100644 --- a/lib/widgets/map/node_markers.dart +++ b/lib/widgets/map/node_markers.dart @@ -20,8 +20,8 @@ class NodeMapMarker extends StatefulWidget { required this.mapController, this.onNodeTap, this.enabled = true, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _NodeMapMarkerState(); diff --git a/lib/widgets/map/node_refresh_controller.dart b/lib/widgets/map/node_refresh_controller.dart index 0105d72..5fce1b3 100644 --- a/lib/widgets/map/node_refresh_controller.dart +++ b/lib/widgets/map/node_refresh_controller.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; import '../../models/node_profile.dart'; import '../../app_state.dart' show UploadMode; diff --git a/lib/widgets/map/suspected_location_markers.dart b/lib/widgets/map/suspected_location_markers.dart index 0666300..f48c824 100644 --- a/lib/widgets/map/suspected_location_markers.dart +++ b/lib/widgets/map/suspected_location_markers.dart @@ -20,8 +20,8 @@ class SuspectedLocationMapMarker extends StatefulWidget { required this.mapController, this.onLocationTap, this.enabled = true, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _SuspectedLocationMapMarkerState(); diff --git a/lib/widgets/map/tile_layer_manager.dart b/lib/widgets/map/tile_layer_manager.dart index 97f5303..d436ade 100644 --- a/lib/widgets/map/tile_layer_manager.dart +++ b/lib/widgets/map/tile_layer_manager.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; import '../../models/tile_provider.dart' as models; import '../../services/deflock_tile_provider.dart'; @@ -93,7 +92,7 @@ class TileLayerManager { return TileLayer( urlTemplate: urlTemplate, // Critical for cache key generation userAgentPackageName: 'me.deflock.deflockapp', - maxZoom: selectedTileType?.maxZoom?.toDouble() ?? 18.0, + maxZoom: selectedTileType?.maxZoom.toDouble() ?? 18.0, tileProvider: _tileProvider!, ); } diff --git a/lib/widgets/node_provider_with_cache.dart b/lib/widgets/node_provider_with_cache.dart index e54eade..09ff7ce 100644 --- a/lib/widgets/node_provider_with_cache.dart +++ b/lib/widgets/node_provider_with_cache.dart @@ -1,12 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; import 'package:flutter_map/flutter_map.dart' show LatLngBounds; -import '../services/map_data_provider.dart'; import '../services/node_data_manager.dart'; import '../services/node_spatial_cache.dart'; -import '../services/network_status.dart'; import '../models/node_profile.dart'; import '../models/osm_node.dart'; import '../app_state.dart'; diff --git a/lib/widgets/nuclear_reset_dialog.dart b/lib/widgets/nuclear_reset_dialog.dart index ad10d34..e899f5c 100644 --- a/lib/widgets/nuclear_reset_dialog.dart +++ b/lib/widgets/nuclear_reset_dialog.dart @@ -8,15 +8,15 @@ class NuclearResetDialog extends StatelessWidget { final String errorReport; const NuclearResetDialog({ - Key? key, + super.key, required this.errorReport, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - return WillPopScope( + return PopScope( // Prevent back button from closing dialog - onWillPop: () async => false, + canPop: false, child: AlertDialog( title: const Row( children: [ diff --git a/lib/widgets/proximity_warning_dialog.dart b/lib/widgets/proximity_warning_dialog.dart index 81402f8..ba9298f 100644 --- a/lib/widgets/proximity_warning_dialog.dart +++ b/lib/widgets/proximity_warning_dialog.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; import '../models/osm_node.dart'; import '../services/localization_service.dart'; diff --git a/lib/widgets/search_bar.dart b/lib/widgets/search_bar.dart index 0e09dbb..61a2f4a 100644 --- a/lib/widgets/search_bar.dart +++ b/lib/widgets/search_bar.dart @@ -107,7 +107,7 @@ class _LocationSearchBarState extends State { borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)), boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.2), + color: Theme.of(context).shadowColor.withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, 2), ), @@ -152,7 +152,7 @@ class _LocationSearchBarState extends State { : null, dense: true, onTap: () => _onResultTap(result), - )).toList(), + )), ], ), ); @@ -184,7 +184,7 @@ class _LocationSearchBarState extends State { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.2), + color: Theme.of(context).shadowColor.withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, 2), ), diff --git a/lib/widgets/suspected_location_sheet.dart b/lib/widgets/suspected_location_sheet.dart index 8e9d4d0..470e7b9 100644 --- a/lib/widgets/suspected_location_sheet.dart +++ b/lib/widgets/suspected_location_sheet.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../models/suspected_location.dart'; -import '../app_state.dart'; import '../services/localization_service.dart'; import '../dev_config.dart'; @@ -17,7 +15,6 @@ class SuspectedLocationSheet extends StatelessWidget { return AnimatedBuilder( animation: LocalizationService.instance, builder: (context, child) { - final appState = context.watch(); final locService = LocalizationService.instance; // Get all fields except location and ticket_no diff --git a/pubspec.lock b/pubspec.lock index c266e82..27e167b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -222,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" flutter_local_notifications: dependency: "direct main" description: @@ -496,6 +504,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" lists: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1dda115..1a50411 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,15 +33,18 @@ dependencies: shared_preferences: ^2.2.2 sqflite: ^2.4.1 path: ^1.8.3 + path_provider: ^2.1.0 uuid: ^4.0.0 package_info_plus: ^8.0.0 csv: ^6.0.0 + collection: ^1.18.0 dev_dependencies: flutter_test: sdk: flutter mocktail: ^1.0.4 flutter_launcher_icons: ^0.14.4 + flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.6 flutter: diff --git a/test/models/tile_provider_test.dart b/test/models/tile_provider_test.dart index 3d825b0..c4c9f21 100644 --- a/test/models/tile_provider_test.dart +++ b/test/models/tile_provider_test.dart @@ -31,16 +31,16 @@ void main() { ); // Test 0-3 range - final url_0_3_a = tileType0_3.getTileUrl(1, 0, 0); - final url_0_3_b = tileType0_3.getTileUrl(1, 3, 0); - expect(url_0_3_a, contains('s0.example.com')); - expect(url_0_3_b, contains('s3.example.com')); + final url03A = tileType0_3.getTileUrl(1, 0, 0); + final url03B = tileType0_3.getTileUrl(1, 3, 0); + expect(url03A, contains('s0.example.com')); + expect(url03B, contains('s3.example.com')); // Test 1-4 range - final url_1_4_a = tileType1_4.getTileUrl(1, 0, 0); - final url_1_4_b = tileType1_4.getTileUrl(1, 3, 0); - expect(url_1_4_a, contains('s1.example.com')); - expect(url_1_4_b, contains('s4.example.com')); + final url14A = tileType1_4.getTileUrl(1, 0, 0); + final url14B = tileType1_4.getTileUrl(1, 3, 0); + expect(url14A, contains('s1.example.com')); + expect(url14B, contains('s4.example.com')); // Test consistency final url1 = tileType0_3.getTileUrl(1, 2, 3); From 3dada20ec21e32cb359880de77aa78f94c8aa8de Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Sun, 1 Feb 2026 21:26:55 -0700 Subject: [PATCH 2/4] Replace deprecated withOpacity and surfaceVariant APIs Migrate all withOpacity() calls to withValues(alpha:) and surfaceVariant to surfaceContainerHighest across the codebase. Co-Authored-By: Claude Opus 4.5 --- lib/screens/home_screen.dart | 2 +- lib/screens/navigation_settings_screen.dart | 2 +- lib/screens/osm_account_screen.dart | 8 ++++---- lib/screens/release_notes_screen.dart | 6 +++--- .../sections/proximity_alerts_section.dart | 6 +++--- .../sections/tile_provider_section.dart | 4 ++-- .../settings/sections/upload_mode_section.dart | 2 +- lib/screens/settings_screen.dart | 2 +- lib/widgets/camera_icon.dart | 2 +- lib/widgets/compass_indicator.dart | 6 +++--- lib/widgets/download_area_dialog.dart | 12 ++++++------ lib/widgets/map/direction_cones.dart | 4 ++-- lib/widgets/map/layer_selector_button.dart | 2 +- lib/widgets/map/map_overlays.dart | 8 ++++---- lib/widgets/map/overlay_layer_builder.dart | 3 +-- lib/widgets/navigation_sheet.dart | 9 ++++----- lib/widgets/node_tag_sheet.dart | 18 +++++++++--------- lib/widgets/positioning_tutorial_overlay.dart | 4 ++-- lib/widgets/provisional_pin.dart | 4 ++-- lib/widgets/proximity_alert_banner.dart | 2 +- lib/widgets/reauth_messages_dialog.dart | 2 +- lib/widgets/submission_guide_dialog.dart | 8 ++++---- lib/widgets/suspected_location_sheet.dart | 4 ++-- lib/widgets/welcome_dialog.dart | 8 ++++---- 24 files changed, 63 insertions(+), 65 deletions(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 3242edc..cc0514d 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -544,7 +544,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Theme.of(context).shadowColor.withOpacity(0.3), + color: Theme.of(context).shadowColor.withValues(alpha: 0.3), blurRadius: 10, offset: Offset(0, -2), ) diff --git a/lib/screens/navigation_settings_screen.dart b/lib/screens/navigation_settings_screen.dart index 659fe76..9a4829b 100644 --- a/lib/screens/navigation_settings_screen.dart +++ b/lib/screens/navigation_settings_screen.dart @@ -132,7 +132,7 @@ class _NavigationSettingsScreenState extends State { Text( value, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6), ), ), const SizedBox(width: 8), diff --git a/lib/screens/osm_account_screen.dart b/lib/screens/osm_account_screen.dart index 11135ca..1e25488 100644 --- a/lib/screens/osm_account_screen.dart +++ b/lib/screens/osm_account_screen.dart @@ -275,7 +275,7 @@ class _OSMAccountScreenState extends State { Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), + color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(8.0), ), child: Row( @@ -308,7 +308,7 @@ class _OSMAccountScreenState extends State { label: Text(locService.t('auth.deleteAccount')), style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.error, - side: BorderSide(color: Theme.of(context).colorScheme.error.withOpacity(0.5)), + side: BorderSide(color: Theme.of(context).colorScheme.error.withValues(alpha: 0.5)), ), ), ), @@ -354,10 +354,10 @@ class _OSMAccountScreenState extends State { Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.errorContainer.withOpacity(0.1), + color: Theme.of(context).colorScheme.errorContainer.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8.0), border: Border.all( - color: Theme.of(context).colorScheme.error.withOpacity(0.3), + color: Theme.of(context).colorScheme.error.withValues(alpha: 0.3), ), ), child: Text( diff --git a/lib/screens/release_notes_screen.dart b/lib/screens/release_notes_screen.dart index 28bfcc6..f713d22 100644 --- a/lib/screens/release_notes_screen.dart +++ b/lib/screens/release_notes_screen.dart @@ -99,7 +99,7 @@ class _ReleaseNotesScreenState extends State { color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context).colorScheme.primary.withOpacity(0.3), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), ), ), child: Row( @@ -142,8 +142,8 @@ class _ReleaseNotesScreenState extends State { decoration: BoxDecoration( border: Border.all( color: isCurrentVersion - ? Theme.of(context).colorScheme.primary.withOpacity(0.3) - : Theme.of(context).dividerColor.withOpacity(0.3), + ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.3) + : Theme.of(context).dividerColor.withValues(alpha: 0.3), ), borderRadius: BorderRadius.circular(8), ), diff --git a/lib/screens/settings/sections/proximity_alerts_section.dart b/lib/screens/settings/sections/proximity_alerts_section.dart index 44acf5e..77bdea5 100644 --- a/lib/screens/settings/sections/proximity_alerts_section.dart +++ b/lib/screens/settings/sections/proximity_alerts_section.dart @@ -140,9 +140,9 @@ class _ProximityAlertsSectionState extends State { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), + color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -233,7 +233,7 @@ class _ProximityAlertsSectionState extends State { DistanceService.convertFromMeters(kProximityAlertDefaultDistance.toDouble(), appState.distanceUnit).round().toString(), ]), style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6), ), ), ], diff --git a/lib/screens/settings/sections/tile_provider_section.dart b/lib/screens/settings/sections/tile_provider_section.dart index 93cee9d..6ed55bd 100644 --- a/lib/screens/settings/sections/tile_provider_section.dart +++ b/lib/screens/settings/sections/tile_provider_section.dart @@ -44,7 +44,7 @@ class TileProviderSection extends StatelessWidget { ), ) else - ...providers.map((provider) => _buildProviderTile(context, provider, appState)).toList(), + ...providers.map((provider) => _buildProviderTile(context, provider, appState)), ], ); }, @@ -89,7 +89,7 @@ class TileProviderSection extends StatelessWidget { leading: CircleAvatar( backgroundColor: isSelected ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.surfaceVariant, + : Theme.of(context).colorScheme.surfaceContainerHighest, child: Icon( Icons.map, color: isSelected diff --git a/lib/screens/settings/sections/upload_mode_section.dart b/lib/screens/settings/sections/upload_mode_section.dart index 65d280e..61da379 100644 --- a/lib/screens/settings/sections/upload_mode_section.dart +++ b/lib/screens/settings/sections/upload_mode_section.dart @@ -81,7 +81,7 @@ class UploadModeSection extends StatelessWidget { fontSize: 12, color: appState.pendingCount > 0 ? Theme.of(context).disabledColor - : Theme.of(context).colorScheme.onSurface.withOpacity(0.7) + : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7) ) ); case UploadMode.sandbox: diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index ea88105..620acaf 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -102,7 +102,7 @@ class SettingsScreen extends StatelessWidget { child: Text( 'Version: ${VersionService().version}', style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), diff --git a/lib/widgets/camera_icon.dart b/lib/widgets/camera_icon.dart index 4fa015a..0637936 100644 --- a/lib/widgets/camera_icon.dart +++ b/lib/widgets/camera_icon.dart @@ -40,7 +40,7 @@ class CameraIcon extends StatelessWidget { height: kNodeIconDiameter, decoration: BoxDecoration( shape: BoxShape.circle, - color: _ringColor.withOpacity(kNodeDotOpacity), + color: _ringColor.withValues(alpha: kNodeDotOpacity), border: Border.all( color: _ringColor, width: getNodeRingThickness(context), diff --git a/lib/widgets/compass_indicator.dart b/lib/widgets/compass_indicator.dart index 4770379..c1630be 100644 --- a/lib/widgets/compass_indicator.dart +++ b/lib/widgets/compass_indicator.dart @@ -96,8 +96,8 @@ class _CompassIndicatorState extends State { height: 52, decoration: BoxDecoration( color: isDisabled - ? Colors.grey.withOpacity(0.8) - : Colors.white.withOpacity(0.95), + ? Colors.grey.withValues(alpha: 0.8) + : Colors.white.withValues(alpha: 0.95), shape: BoxShape.circle, border: Border.all( color: isDisabled @@ -107,7 +107,7 @@ class _CompassIndicatorState extends State { ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.25), + color: Colors.black.withValues(alpha: 0.25), blurRadius: 6, offset: const Offset(0, 3), ), diff --git a/lib/widgets/download_area_dialog.dart b/lib/widgets/download_area_dialog.dart index 0a4ef32..8ea1682 100644 --- a/lib/widgets/download_area_dialog.dart +++ b/lib/widgets/download_area_dialog.dart @@ -189,8 +189,8 @@ class _DownloadAreaDialogState extends State { padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: _tileCount! > kMaxReasonableTileCount - ? Colors.orange.withOpacity(0.1) - : Colors.green.withOpacity(0.1), + ? Colors.orange.withValues(alpha: 0.1) + : Colors.green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Column( @@ -198,7 +198,7 @@ class _DownloadAreaDialogState extends State { children: [ Text( _tileCount! > kMaxReasonableTileCount - ? 'Above recommended limit (Z${_maxPossibleZoom})' + ? 'Above recommended limit (Z$_maxPossibleZoom)' : locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]), style: TextStyle( fontSize: 12, @@ -211,7 +211,7 @@ class _DownloadAreaDialogState extends State { const SizedBox(height: 2), Text( _tileCount! > kMaxReasonableTileCount - ? 'Current selection exceeds ${kMaxReasonableTileCount} recommended tile limit but is within ${kAbsoluteMaxTileCount} absolute limit' + ? 'Current selection exceeds $kMaxReasonableTileCount recommended tile limit but is within $kAbsoluteMaxTileCount absolute limit' : locService.t('download.withinTileLimit', params: [kMaxReasonableTileCount.toString()]), style: TextStyle( fontSize: 11, @@ -230,9 +230,9 @@ class _DownloadAreaDialogState extends State { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), + color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), ), child: Row( children: [ diff --git a/lib/widgets/map/direction_cones.dart b/lib/widgets/map/direction_cones.dart index fce8265..5c38aed 100644 --- a/lib/widgets/map/direction_cones.dart +++ b/lib/widgets/map/direction_cones.dart @@ -203,7 +203,7 @@ class DirectionConesBuilder { return Polygon( points: points, - color: kDirectionConeColor.withOpacity(opacity), + color: kDirectionConeColor.withValues(alpha: opacity), borderColor: kDirectionConeColor, borderStrokeWidth: getDirectionConeBorderWidth(context), ); @@ -251,7 +251,7 @@ class DirectionConesBuilder { return Polygon( points: points, - color: kDirectionConeColor.withOpacity(opacity), + color: kDirectionConeColor.withValues(alpha: opacity), borderColor: kDirectionConeColor, borderStrokeWidth: getDirectionConeBorderWidth(context), ); diff --git a/lib/widgets/map/layer_selector_button.dart b/lib/widgets/map/layer_selector_button.dart index f820289..1490105 100644 --- a/lib/widgets/map/layer_selector_button.dart +++ b/lib/widgets/map/layer_selector_button.dart @@ -81,7 +81,7 @@ class _LayerSelectorDialogState extends State<_LayerSelectorDialog> { Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), ), child: Row( diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index b284319..0d9772f 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -62,8 +62,8 @@ class MapOverlays extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: uploadMode == UploadMode.sandbox - ? Colors.orange.withOpacity(0.90) - : Colors.deepPurple.withOpacity(0.80), + ? Colors.orange.withValues(alpha: 0.90) + : Colors.deepPurple.withValues(alpha: 0.80), borderRadius: BorderRadius.circular(8), boxShadow: const [ BoxShadow(color: Colors.black26, blurRadius: 5, offset: Offset(0,2)), @@ -96,7 +96,7 @@ class MapOverlays extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( - color: Colors.black.withOpacity(0.52), + color: Colors.black.withValues(alpha: 0.52), borderRadius: BorderRadius.circular(7), ), child: Builder( @@ -129,7 +129,7 @@ class MapOverlays extends StatelessWidget { onTap: () => _showAttributionDialog(context, attribution!), child: Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface.withOpacity(0.9), + color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(4), ), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), diff --git a/lib/widgets/map/overlay_layer_builder.dart b/lib/widgets/map/overlay_layer_builder.dart index 2658357..4461f1f 100644 --- a/lib/widgets/map/overlay_layer_builder.dart +++ b/lib/widgets/map/overlay_layer_builder.dart @@ -4,7 +4,6 @@ import 'package:latlong2/latlong.dart'; import '../../models/osm_node.dart'; import '../../app_state.dart'; -import '../../state/session_state.dart'; import '../../dev_config.dart'; import 'direction_cones.dart'; @@ -38,7 +37,7 @@ class OverlayLayerBuilder { overlays.add( Polygon( points: selectedLocation.bounds, - color: Colors.orange.withOpacity(0.3), + color: Colors.orange.withValues(alpha: 0.3), borderColor: Colors.orange, borderStrokeWidth: 2.0, ), diff --git a/lib/widgets/navigation_sheet.dart b/lib/widgets/navigation_sheet.dart index 312a952..4c6d390 100644 --- a/lib/widgets/navigation_sheet.dart +++ b/lib/widgets/navigation_sheet.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:latlong2/latlong.dart'; @@ -182,9 +181,9 @@ class NavigationSheet extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.amber.withOpacity(0.1), + color: Colors.amber.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.amber.withOpacity(0.3)), + border: Border.all(color: Colors.amber.withValues(alpha: 0.3)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -212,9 +211,9 @@ class NavigationSheet extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), + color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), ), child: Row( children: [ diff --git a/lib/widgets/node_tag_sheet.dart b/lib/widgets/node_tag_sheet.dart index 80b0994..5c54b35 100644 --- a/lib/widgets/node_tag_sheet.dart +++ b/lib/widgets/node_tag_sheet.dart @@ -40,7 +40,7 @@ class NodeTagSheet extends StatelessWidget { final isRealOSMNode = !node.tags.containsKey('_pending_upload') && node.id > 0; // Real OSM nodes have positive IDs - void _openEditSheet() { + void openEditSheet() { // Check if node limit is active and warn user if (isNodeLimitActive) { ScaffoldMessenger.of(context).showSnackBar( @@ -64,7 +64,7 @@ class NodeTagSheet extends StatelessWidget { } } - void _deleteNode() async { + void deleteNode() async { final shouldDelete = await showDialog( context: context, builder: (BuildContext context) { @@ -95,7 +95,7 @@ class NodeTagSheet extends StatelessWidget { } } - void _viewOnOSM() async { + void viewOnOSM() async { final url = 'https://www.openstreetmap.org/node/${node.id}'; try { final uri = Uri.parse(url); @@ -117,7 +117,7 @@ class NodeTagSheet extends StatelessWidget { } } - void _openAdvancedEdit() { + void openAdvancedEdit() { showModalBottomSheet( context: context, isScrollControlled: true, @@ -177,7 +177,7 @@ class NodeTagSheet extends StatelessWidget { }, text: e.value, style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), linkStyle: TextStyle( color: Theme.of(context).colorScheme.primary, @@ -201,7 +201,7 @@ class NodeTagSheet extends StatelessWidget { children: [ if (isRealOSMNode) ...[ TextButton.icon( - onPressed: () => _viewOnOSM(), + onPressed: () => viewOnOSM(), icon: const Icon(Icons.open_in_new, size: 16), label: Text(locService.t('actions.viewOnOSM')), ), @@ -209,7 +209,7 @@ class NodeTagSheet extends StatelessWidget { ], if (isEditable) ...[ OutlinedButton.icon( - onPressed: _openAdvancedEdit, + onPressed: openAdvancedEdit, icon: const Icon(Icons.open_in_new, size: 18), label: Text(locService.t('actions.advanced')), style: OutlinedButton.styleFrom( @@ -226,7 +226,7 @@ class NodeTagSheet extends StatelessWidget { children: [ if (isEditable) ...[ ElevatedButton.icon( - onPressed: _openEditSheet, + onPressed: openEditSheet, icon: const Icon(Icons.edit, size: 18), label: Text(locService.edit), style: ElevatedButton.styleFrom( @@ -235,7 +235,7 @@ class NodeTagSheet extends StatelessWidget { ), const SizedBox(width: 8), ElevatedButton.icon( - onPressed: node.isConstrained ? null : _deleteNode, + onPressed: node.isConstrained ? null : deleteNode, icon: const Icon(Icons.delete, size: 18), label: Text(locService.t('actions.delete')), style: ElevatedButton.styleFrom( diff --git a/lib/widgets/positioning_tutorial_overlay.dart b/lib/widgets/positioning_tutorial_overlay.dart index 20475cb..4b7be77 100644 --- a/lib/widgets/positioning_tutorial_overlay.dart +++ b/lib/widgets/positioning_tutorial_overlay.dart @@ -30,7 +30,7 @@ class PositioningTutorialOverlay extends StatelessWidget { ), child: Container( decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), // Semi-transparent overlay + color: Colors.black.withValues(alpha: 0.3), // Semi-transparent overlay ), child: Center( child: Padding( @@ -73,7 +73,7 @@ class PositioningTutorialOverlay extends StatelessWidget { Text( locService.t('positioningTutorial.hint'), style: TextStyle( - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues(alpha: 0.8), fontSize: 14, fontStyle: FontStyle.italic, ), diff --git a/lib/widgets/provisional_pin.dart b/lib/widgets/provisional_pin.dart index 2757dc1..1e49d91 100644 --- a/lib/widgets/provisional_pin.dart +++ b/lib/widgets/provisional_pin.dart @@ -43,7 +43,7 @@ class LocationPin extends StatelessWidget { width: size * 0.4, height: size * 0.2, decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(size * 0.1), ), ), @@ -64,7 +64,7 @@ class LocationPin extends StatelessWidget { color: Colors.white, shape: BoxShape.circle, border: Border.all( - color: _pinColor.withOpacity(0.8), + color: _pinColor.withValues(alpha: 0.8), width: 1.5, ), ), diff --git a/lib/widgets/proximity_alert_banner.dart b/lib/widgets/proximity_alert_banner.dart index 154f3c7..d461fb7 100644 --- a/lib/widgets/proximity_alert_banner.dart +++ b/lib/widgets/proximity_alert_banner.dart @@ -82,7 +82,7 @@ class _ProximityAlertBannerState extends State color: Colors.red.shade600, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 2), ), diff --git a/lib/widgets/reauth_messages_dialog.dart b/lib/widgets/reauth_messages_dialog.dart index 891f7c4..5b57a77 100644 --- a/lib/widgets/reauth_messages_dialog.dart +++ b/lib/widgets/reauth_messages_dialog.dart @@ -38,7 +38,7 @@ class ReauthMessagesDialog extends StatelessWidget { Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), + color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(8.0), ), child: Row( diff --git a/lib/widgets/submission_guide_dialog.dart b/lib/widgets/submission_guide_dialog.dart index 37262ba..6473edb 100644 --- a/lib/widgets/submission_guide_dialog.dart +++ b/lib/widgets/submission_guide_dialog.dart @@ -81,9 +81,9 @@ class _SubmissionGuideDialogState extends State { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), + color: Colors.blue.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.withOpacity(0.3)), + border: Border.all(color: Colors.blue.withValues(alpha: 0.3)), ), child: Text( locService.t('submissionGuide.bestPractices'), @@ -171,10 +171,10 @@ class _SubmissionGuideDialogState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all( - color: Theme.of(context).colorScheme.primary.withOpacity(0.3), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), ), ), child: Text( diff --git a/lib/widgets/suspected_location_sheet.dart b/lib/widgets/suspected_location_sheet.dart index 470e7b9..60f90d3 100644 --- a/lib/widgets/suspected_location_sheet.dart +++ b/lib/widgets/suspected_location_sheet.dart @@ -94,7 +94,7 @@ class SuspectedLocationSheet extends StatelessWidget { : Text( e.value, style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), softWrap: true, ), @@ -128,7 +128,7 @@ class SuspectedLocationSheet extends StatelessWidget { child: Text( '${location.centroid.latitude.toStringAsFixed(6)}, ${location.centroid.longitude.toStringAsFixed(6)}', style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), softWrap: true, ), diff --git a/lib/widgets/welcome_dialog.dart b/lib/widgets/welcome_dialog.dart index 0e9911c..627c3d9 100644 --- a/lib/widgets/welcome_dialog.dart +++ b/lib/widgets/welcome_dialog.dart @@ -88,9 +88,9 @@ class _WelcomeDialogState extends State { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.1), + color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.orange.withOpacity(0.3)), + border: Border.all(color: Colors.orange.withValues(alpha: 0.3)), ), child: Text( locService.t('welcome.firsthandKnowledge'), @@ -171,10 +171,10 @@ class _WelcomeDialogState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), border: Border.all( - color: Theme.of(context).colorScheme.primary.withOpacity(0.3), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), ), ), child: Text( From 4fddd8e807955b9c43e7d8ab7fca28966d46fa5f Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Sun, 1 Feb 2026 21:27:02 -0700 Subject: [PATCH 3/4] Replace print() with debugPrint() across codebase Fixes avoid_print lint warnings by using debugPrint which respects release mode and avoids console overflow on mobile platforms. Co-Authored-By: Claude Opus 4.5 --- lib/models/suspected_location.dart | 11 ++++++----- lib/services/auth_service.dart | 7 ++++--- lib/services/node_cache.dart | 9 +++++---- lib/state/auth_state.dart | 12 ++++++------ lib/state/upload_queue_state.dart | 18 ++++++++---------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/models/suspected_location.dart b/lib/models/suspected_location.dart index fd7c05a..63346c3 100644 --- a/lib/models/suspected_location.dart +++ b/lib/models/suspected_location.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; /// A suspected surveillance location from the CSV data @@ -35,8 +36,8 @@ class SuspectedLocation { bounds = coordinates.bounds; } catch (e) { // If GeoJSON parsing fails, use default coordinates - print('[SuspectedLocation] Failed to parse GeoJSON for ticket $ticketNo: $e'); - print('[SuspectedLocation] Location string: $locationString'); + debugPrint('[SuspectedLocation] Failed to parse GeoJSON for ticket $ticketNo: $e'); + debugPrint('[SuspectedLocation] Location string: $locationString'); } } @@ -60,7 +61,7 @@ class SuspectedLocation { // The geoJson IS the geometry object (not wrapped in a 'geometry' property) final coordinates = geoJson['coordinates'] as List?; if (coordinates == null || coordinates.isEmpty) { - print('[SuspectedLocation] No coordinates found in GeoJSON'); + debugPrint('[SuspectedLocation] No coordinates found in GeoJSON'); return (centroid: const LatLng(0, 0), bounds: []); } @@ -109,7 +110,7 @@ class SuspectedLocation { } break; default: - print('Unsupported geometry type: $type'); + debugPrint('Unsupported geometry type: $type'); } if (points.isEmpty) { @@ -127,7 +128,7 @@ class SuspectedLocation { return (centroid: centroid, bounds: points); } catch (e) { - print('Error extracting coordinates from GeoJSON: $e'); + debugPrint('Error extracting coordinates from GeoJSON: $e'); return (centroid: const LatLng(0, 0), bounds: []); } } diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 843d642..8aa94bb 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:flutter/foundation.dart'; import 'package:oauth2_client/oauth2_client.dart'; import 'package:oauth2_client/oauth2_helper.dart'; import 'package:http/http.dart' as http; @@ -98,7 +99,7 @@ class AuthService { _displayName = await _fetchUsername(token.accessToken!); return _displayName; } catch (e) { - print('AuthService: OAuth login failed: $e'); + debugPrint('AuthService: OAuth login failed: $e'); log('OAuth login failed: $e'); rethrow; } @@ -126,7 +127,7 @@ class AuthService { _displayName = await _fetchUsername(accessToken); return _displayName; } catch (e) { - print('AuthService: Error restoring login with stored token: $e'); + debugPrint('AuthService: Error restoring login with stored token: $e'); log('Error restoring login with stored token: $e'); // Token might be expired or invalid, clear it await logout(); @@ -192,7 +193,7 @@ class AuthService { final displayName = userData['user']?['display_name']; return displayName; } catch (e) { - print('AuthService: Error fetching username: $e'); + debugPrint('AuthService: Error fetching username: $e'); log('Error fetching username: $e'); return null; } diff --git a/lib/services/node_cache.dart b/lib/services/node_cache.dart index 2f72cfe..39a0207 100644 --- a/lib/services/node_cache.dart +++ b/lib/services/node_cache.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:latlong2/latlong.dart'; import '../models/osm_node.dart'; import 'package:flutter_map/flutter_map.dart' show LatLngBounds; @@ -85,7 +86,7 @@ class NodeCache { /// Remove a node by ID from the cache (used for successful deletions) void removeNodeById(int nodeId) { if (_nodes.remove(nodeId) != null) { - print('[NodeCache] Removed node $nodeId from cache (successful deletion)'); + debugPrint('[NodeCache] Removed node $nodeId from cache (successful deletion)'); } } @@ -111,19 +112,19 @@ class NodeCache { } if (nodesToRemove.isNotEmpty) { - print('[NodeCache] Removed ${nodesToRemove.length} temp nodes at coordinate ${coord.latitude}, ${coord.longitude}'); + debugPrint('[NodeCache] Removed ${nodesToRemove.length} temp nodes at coordinate ${coord.latitude}, ${coord.longitude}'); } } /// Remove a specific temporary node by its ID (for queue item-specific cleanup) void removeTempNodeById(int tempNodeId) { if (tempNodeId >= 0) { - print('[NodeCache] Warning: Attempted to remove non-temp node ID $tempNodeId'); + debugPrint('[NodeCache] Warning: Attempted to remove non-temp node ID $tempNodeId'); return; } if (_nodes.remove(tempNodeId) != null) { - print('[NodeCache] Removed specific temp node $tempNodeId from cache'); + debugPrint('[NodeCache] Removed specific temp node $tempNodeId from cache'); } } diff --git a/lib/state/auth_state.dart b/lib/state/auth_state.dart index 0b26be8..dcf163b 100644 --- a/lib/state/auth_state.dart +++ b/lib/state/auth_state.dart @@ -21,7 +21,7 @@ class AuthState extends ChangeNotifier { _username = await _auth.restoreLogin(); } } catch (e) { - print("AuthState: Error during auth initialization: $e"); + debugPrint("AuthState: Error during auth initialization: $e"); } } @@ -29,7 +29,7 @@ class AuthState extends ChangeNotifier { try { _username = await _auth.login(); } catch (e) { - print("AuthState: Login error: $e"); + debugPrint("AuthState: Login error: $e"); _username = null; } notifyListeners(); @@ -49,7 +49,7 @@ class AuthState extends ChangeNotifier { _username = null; } } catch (e) { - print("AuthState: Auth refresh error: $e"); + debugPrint("AuthState: Auth refresh error: $e"); _username = null; } notifyListeners(); @@ -59,7 +59,7 @@ class AuthState extends ChangeNotifier { try { _username = await _auth.forceLogin(); } catch (e) { - print("AuthState: Forced login error: $e"); + debugPrint("AuthState: Forced login error: $e"); _username = null; } notifyListeners(); @@ -69,7 +69,7 @@ class AuthState extends ChangeNotifier { try { return await _auth.isLoggedIn(); } catch (e) { - print("AuthState: Token validation error: $e"); + debugPrint("AuthState: Token validation error: $e"); return false; } } @@ -92,7 +92,7 @@ class AuthState extends ChangeNotifier { } } catch (e) { _username = null; - print("AuthState: Mode change user restoration error: $e"); + debugPrint("AuthState: Mode change user restoration error: $e"); } notifyListeners(); } diff --git a/lib/state/upload_queue_state.dart b/lib/state/upload_queue_state.dart index 1cafb1d..cabb8d4 100644 --- a/lib/state/upload_queue_state.dart +++ b/lib/state/upload_queue_state.dart @@ -29,23 +29,23 @@ class UploadQueueState extends ChangeNotifier { // Initialize by loading queue from storage and repopulate cache with pending nodes Future init() async { await _loadQueue(); - print('[UploadQueue] Loaded ${_queue.length} items from storage'); + debugPrint('[UploadQueue] Loaded ${_queue.length} items from storage'); _repopulateCacheFromQueue(); } // Repopulate the cache with pending nodes from the queue on startup void _repopulateCacheFromQueue() { - print('[UploadQueue] Repopulating cache from ${_queue.length} queue items'); + debugPrint('[UploadQueue] Repopulating cache from ${_queue.length} queue items'); final nodesToAdd = []; for (final upload in _queue) { // Skip completed uploads - they should already be in OSM and will be fetched normally if (upload.isComplete) { - print('[UploadQueue] Skipping completed upload at ${upload.coord}'); + debugPrint('[UploadQueue] Skipping completed upload at ${upload.coord}'); continue; } - print('[UploadQueue] Processing ${upload.operation} upload at ${upload.coord}'); + debugPrint('[UploadQueue] Processing ${upload.operation} upload at ${upload.coord}'); if (upload.isDeletion) { // For deletions: mark the original node as pending deletion if it exists in cache @@ -73,9 +73,7 @@ class UploadQueueState extends ChangeNotifier { tags['_temp_id'] = tempId.toString(); // Store temp ID for future cleanup if not already set - if (upload.tempNodeId == null) { - upload.tempNodeId = tempId; - } + upload.tempNodeId ??= tempId; if (upload.isEdit) { // For edits: also mark original with _pending_edit if it exists @@ -112,7 +110,7 @@ class UploadQueueState extends ChangeNotifier { if (nodesToAdd.isNotEmpty) { _nodeCache.addOrUpdate(nodesToAdd); - print('[UploadQueue] Repopulated cache with ${nodesToAdd.length} pending nodes from queue'); + debugPrint('[UploadQueue] Repopulated cache with ${nodesToAdd.length} pending nodes from queue'); // Save queue if we updated any temp IDs for backward compatibility _saveQueue(); @@ -587,7 +585,7 @@ class UploadQueueState extends ChangeNotifier { // Still have time, will retry after backoff delay final nextDelay = item.nextNodeSubmissionRetryDelay; final timeLeft = item.timeUntilAutoClose; - debugPrint('[UploadQueue] Will retry node submission in ${nextDelay}, ${timeLeft?.inMinutes}m remaining'); + debugPrint('[UploadQueue] Will retry node submission in $nextDelay, ${timeLeft?.inMinutes}m remaining'); // No state change needed - attempt count was already updated above } } @@ -646,7 +644,7 @@ class UploadQueueState extends ChangeNotifier { // Note: This will NEVER error out - will keep trying until 59-minute window expires final nextDelay = item.nextChangesetCloseRetryDelay; final timeLeft = item.timeUntilAutoClose; - debugPrint('[UploadQueue] Changeset close failed (attempt ${item.changesetCloseAttempts}), will retry in ${nextDelay}, ${timeLeft?.inMinutes}m remaining'); + debugPrint('[UploadQueue] Changeset close failed (attempt ${item.changesetCloseAttempts}), will retry in $nextDelay, ${timeLeft?.inMinutes}m remaining'); debugPrint('[UploadQueue] Error: ${result.errorMessage}'); // No additional state change needed - attempt count was already updated above } From 35701048003bc2cb51a45088003e363f0e065f64 Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Mon, 2 Feb 2026 00:55:25 -0700 Subject: [PATCH 4/4] Add mounted guards for BuildContext use across async gaps --- lib/app_state.dart | 1 + lib/migrations.dart | 2 +- lib/screens/coordinators/sheet_coordinator.dart | 6 ++---- lib/screens/home_screen.dart | 6 +++++- lib/screens/settings/sections/node_profiles_section.dart | 2 +- lib/widgets/add_node_sheet.dart | 8 +++++--- lib/widgets/advanced_edit_options_sheet.dart | 1 + lib/widgets/download_area_dialog.dart | 4 +++- lib/widgets/edit_node_sheet.dart | 8 +++++--- lib/widgets/nuclear_reset_dialog.dart | 3 ++- 10 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/app_state.dart b/lib/app_state.dart index 88b92bf..d90c69a 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -364,6 +364,7 @@ class AppState extends ChangeNotifier { /// Show re-authentication dialog if needed Future checkAndPromptReauthForMessages(BuildContext context) async { if (await needsReauthForMessages()) { + if (!context.mounted) return; _showReauthDialog(context); } } diff --git a/lib/migrations.dart b/lib/migrations.dart index 54f2dca..ec8e2fa 100644 --- a/lib/migrations.dart +++ b/lib/migrations.dart @@ -146,7 +146,7 @@ class OneTimeMigrations { debugPrint('[Migration] Stack trace: $stackTrace'); // Nuclear option: clear everything and show non-dismissible error dialog - if (context != null) { + if (context != null && context.mounted) { NuclearResetDialog.show(context, error, stackTrace); } else { // If no context available, just log and hope for the best diff --git a/lib/screens/coordinators/sheet_coordinator.dart b/lib/screens/coordinators/sheet_coordinator.dart index c984641..e8954c3 100644 --- a/lib/screens/coordinators/sheet_coordinator.dart +++ b/lib/screens/coordinators/sheet_coordinator.dart @@ -117,9 +117,8 @@ class SheetCoordinator { controller.closed.then((_) { _addSheetHeight = 0.0; onStateChanged(); - + // Handle dismissal by canceling session if still active - final appState = context.read(); if (appState.session != null) { debugPrint('[SheetCoordinator] AddNodeSheet dismissed - canceling session'); appState.cancelSession(); @@ -186,9 +185,8 @@ class SheetCoordinator { _editSheetHeight = 0.0; _transitioningToEdit = false; onStateChanged(); - + // Handle dismissal by canceling session if still active - final appState = context.read(); if (appState.editSession != null) { debugPrint('[SheetCoordinator] EditNodeSheet dismissed - canceling edit session'); appState.cancelEditSession(); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index cc0514d..ff9702e 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -185,6 +185,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { // Run any needed migrations first final versionsNeedingMigration = await ChangelogService().getVersionsNeedingMigration(); + if (!mounted) return; for (final version in versionsNeedingMigration) { await ChangelogService().runMigration(version, appState, context); } @@ -209,6 +210,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { case PopupType.changelog: final changelogContent = await ChangelogService().getChangelogContentForDisplay(); + if (!mounted) return; if (changelogContent != null) { await showDialog( context: context, @@ -368,9 +370,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ); // Reset height and clear selection when sheet is dismissed + final appState = context.read(); controller.closed.then((_) { + if (!mounted) return; _sheetCoordinator.resetTagSheetHeight(() => setState(() {})); - context.read().clearSuspectedLocationSelection(); + appState.clearSuspectedLocationSelection(); }); } diff --git a/lib/screens/settings/sections/node_profiles_section.dart b/lib/screens/settings/sections/node_profiles_section.dart index 26cb84f..ed997f0 100644 --- a/lib/screens/settings/sections/node_profiles_section.dart +++ b/lib/screens/settings/sections/node_profiles_section.dart @@ -118,7 +118,7 @@ class NodeProfilesSection extends StatelessWidget { ); // If user chose to create custom profile, open the profile editor - if (result == 'create') { + if (result == 'create' && context.mounted) { _createNewProfile(context); } // If user chose import from website, ProfileAddChoiceDialog handles opening the URL diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 49bc90e..c836866 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -89,7 +89,8 @@ class _AddNodeSheetState extends State { void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async { // Check if user has seen the submission guide final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide(); - + if (!context.mounted) return; + if (!hasSeenGuide) { // Show submission guide dialog first final shouldProceed = await showDialog( @@ -97,13 +98,14 @@ class _AddNodeSheetState extends State { barrierDismissible: false, builder: (context) => const SubmissionGuideDialog(), ); - + if (!context.mounted) return; + // If user canceled the submission guide, don't proceed with submission if (shouldProceed != true) { return; } } - + // Now proceed with proximity check _checkProximityOnly(context, appState, locService); } diff --git a/lib/widgets/advanced_edit_options_sheet.dart b/lib/widgets/advanced_edit_options_sheet.dart index 7f82e27..ca4cfbc 100644 --- a/lib/widgets/advanced_edit_options_sheet.dart +++ b/lib/widgets/advanced_edit_options_sheet.dart @@ -185,6 +185,7 @@ class AdvancedEditOptionsSheet extends StatelessWidget { } // No custom scheme or app launch failed - redirect to app store + if (!context.mounted) return; await _redirectToAppStore(context, editor); } diff --git a/lib/widgets/download_area_dialog.dart b/lib/widgets/download_area_dialog.dart index 8ea1682..0adbdb2 100644 --- a/lib/widgets/download_area_dialog.dart +++ b/lib/widgets/download_area_dialog.dart @@ -264,8 +264,9 @@ class _DownloadAreaDialogState extends State { try { final id = DateTime.now().toIso8601String().replaceAll(':', '-'); final appDocDir = await OfflineAreaService().getOfflineAreaDir(); + if (!context.mounted) return; final dir = "${appDocDir.path}/$id"; - + // Get current tile provider info final appState = context.read(); final selectedProvider = appState.selectedTileProvider; @@ -292,6 +293,7 @@ class _DownloadAreaDialogState extends State { builder: (context) => const DownloadStartedDialog(), ); } catch (e) { + if (!context.mounted) return; Navigator.pop(context); showDialog( context: context, diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index c9c197e..0299dbc 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -92,7 +92,8 @@ class _EditNodeSheetState extends State { void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async { // Check if user has seen the submission guide final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide(); - + if (!context.mounted) return; + if (!hasSeenGuide) { // Show submission guide dialog first final shouldProceed = await showDialog( @@ -100,13 +101,14 @@ class _EditNodeSheetState extends State { barrierDismissible: false, builder: (context) => const SubmissionGuideDialog(), ); - + if (!context.mounted) return; + // If user canceled the submission guide, don't proceed with submission if (shouldProceed != true) { return; } } - + // Now proceed with proximity check _checkProximityOnly(context, appState, locService); } diff --git a/lib/widgets/nuclear_reset_dialog.dart b/lib/widgets/nuclear_reset_dialog.dart index e899f5c..b89d6fa 100644 --- a/lib/widgets/nuclear_reset_dialog.dart +++ b/lib/widgets/nuclear_reset_dialog.dart @@ -96,7 +96,8 @@ class NuclearResetDialog extends StatelessWidget { // Clear all app data await NuclearResetService.clearEverything(); - + + if (!context.mounted) return; // Show non-dismissible dialog await showDialog( context: context,