Merge pull request #34 from dougborg/pr/02-lint-cleanup

Add flutter_lints and fix all analyzer warnings
This commit is contained in:
stopflock
2026-02-09 11:43:48 -06:00
committed by GitHub
77 changed files with 225 additions and 334 deletions

View File

@@ -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';
@@ -366,6 +364,7 @@ class AppState extends ChangeNotifier {
/// Show re-authentication dialog if needed
Future<void> checkAndPromptReauthForMessages(BuildContext context) async {
if (await needsReauthForMessages()) {
if (!context.mounted) return;
_showReauthDialog(context);
}
}

View File

@@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -147,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

View File

@@ -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) =>

View File

@@ -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

View File

@@ -1,4 +1,3 @@
import 'package:uuid/uuid.dart';
import 'osm_node.dart';
/// A bundle of OSM tags that describe a particular surveillance operator.

View File

@@ -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: <LatLng>[]);
}
@@ -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: <LatLng>[]);
}
}

View File

@@ -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 {

View File

@@ -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.
@@ -118,9 +117,8 @@ class SheetCoordinator {
controller.closed.then((_) {
_addSheetHeight = 0.0;
onStateChanged();
// Handle dismissal by canceling session if still active
final appState = context.read<AppState>();
if (appState.session != null) {
debugPrint('[SheetCoordinator] AddNodeSheet dismissed - canceling session');
appState.cancelSession();
@@ -187,9 +185,8 @@ class SheetCoordinator {
_editSheetHeight = 0.0;
_transitioningToEdit = false;
onStateChanged();
// Handle dismissal by canceling session if still active
final appState = context.read<AppState>();
if (appState.editSession != null) {
debugPrint('[SheetCoordinator] EditNodeSheet dismissed - canceling edit session');
appState.cancelEditSession();
@@ -266,7 +263,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
}

View File

@@ -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';
@@ -190,6 +185,7 @@ class _HomeScreenState extends State<HomeScreen> 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);
}
@@ -214,6 +210,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
case PopupType.changelog:
final changelogContent = await ChangelogService().getChangelogContentForDisplay();
if (!mounted) return;
if (changelogContent != null) {
await showDialog(
context: context,
@@ -252,35 +249,6 @@ class _HomeScreenState extends State<HomeScreen> 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,
@@ -402,9 +370,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
);
// Reset height and clear selection when sheet is dismissed
final appState = context.read<AppState>();
controller.closed.then((_) {
if (!mounted) return;
_sheetCoordinator.resetTagSheetHeight(() => setState(() {}));
context.read<AppState>().clearSuspectedLocationSelection();
appState.clearSuspectedLocationSelection();
});
}
@@ -578,7 +548,7 @@ class _HomeScreenState extends State<HomeScreen> 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),
)

View File

@@ -132,7 +132,7 @@ class _NavigationSettingsScreenState extends State<NavigationSettingsScreen> {
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),

View File

@@ -105,7 +105,6 @@ class _OperatorProfileEditorState extends State<OperatorProfileEditor> {
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),

View File

@@ -275,7 +275,7 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
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<OSMAccountScreen> {
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<OSMAccountScreen> {
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(

View File

@@ -153,7 +153,6 @@ class _ProfileEditorState extends State<ProfileEditor> {
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),

View File

@@ -99,7 +99,7 @@ class _ReleaseNotesScreenState extends State<ReleaseNotesScreen> {
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<ReleaseNotesScreen> {
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),
),

View File

@@ -23,14 +23,14 @@ class _LanguageSectionState extends State<LanguageSection> {
_loadLanguageNames();
}
_loadSelectedLanguage() async {
Future<void> _loadSelectedLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language_code');
});
}
_loadLanguageNames() async {
Future<void> _loadLanguageNames() async {
final locService = LocalizationService.instance;
final Map<String, String> names = {};
@@ -43,7 +43,7 @@ class _LanguageSectionState extends State<LanguageSection> {
});
}
_setLanguage(String? languageCode) async {
Future<void> _setLanguage(String? languageCode) async {
await LocalizationService.instance.setLanguage(languageCode);
setState(() {
_selectedLanguage = languageCode;

View File

@@ -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

View File

@@ -87,9 +87,9 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
: "${(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<OfflineAreasSection> {
: null,
),
);
}).toList(),
}),
],
);
},

View File

@@ -140,9 +140,9 @@ class _ProximityAlertsSectionState extends State<ProximityAlertsSection> {
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<ProximityAlertsSection> {
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),
),
),
],

View File

@@ -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,

View File

@@ -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

View File

@@ -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:
@@ -95,7 +95,6 @@ class UploadModeSection extends StatelessWidget {
),
);
case UploadMode.simulate:
default:
return Text(
locService.t('uploadMode.simulateDescription'),
style: TextStyle(

View File

@@ -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,
),

View File

@@ -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';

View File

@@ -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,

View File

@@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:math' as math;
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;
@@ -30,7 +30,6 @@ class AuthService {
case UploadMode.sandbox:
return 'osm_token_sandbox';
case UploadMode.simulate:
default:
return 'osm_token_simulate';
}
}
@@ -97,10 +96,10 @@ 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');
debugPrint('AuthService: OAuth login failed: $e');
log('OAuth login failed: $e');
rethrow;
}
@@ -128,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();
@@ -194,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;
}

View File

@@ -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;

View File

@@ -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=<base64>
/// Handle profile add deep link: `deflockapp://profiles/add?p=<base64>`
void _handleAddProfileLink(Uri uri) {
final base64Data = uri.queryParameters['p'];

View File

@@ -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<DeflockTileImageProvider> {
// Re-throw the exception and let FlutterMap handle missing tiles gracefully
// This is better than trying to provide fallback images
throw e;
rethrow;
}
}

View File

@@ -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';

View File

@@ -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<List<OsmNode>> _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
}
}

View File

@@ -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';

View File

@@ -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

View File

@@ -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');
}
}

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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
};

View File

@@ -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';

View File

@@ -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) {

View File

@@ -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;

View File

@@ -1,4 +1,5 @@
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;

View File

@@ -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<http.Response> _post(String path, String body) => http.post(
Uri.https(_host, path),
headers: _headers,
body: body,
).timeout(kUploadHttpTimeout);
Future<http.Response> _put(String path, String body) => http.put(
Uri.https(_host, path),
headers: _headers,

View File

@@ -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();
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import '../models/search_result.dart';
import '../services/search_service.dart';

View File

@@ -29,23 +29,23 @@ class UploadQueueState extends ChangeNotifier {
// Initialize by loading queue from storage and repopulate cache with pending nodes
Future<void> 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 = <OsmNode>[];
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
}

View File

@@ -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<AddNodeSheet> {
}
}
/// 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(() {
@@ -107,7 +89,8 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
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<bool>(
@@ -115,13 +98,14 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
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);
}
@@ -402,11 +386,11 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
final locService = LocalizationService.instance;
final appState = context.watch<AppState>();
void _commit() {
void commit() {
_checkProximityAndCommit(context, appState, locService);
}
void _cancel() {
void cancel() {
appState.cancelSession();
Navigator.pop(context);
}
@@ -442,11 +426,11 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
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<RefineTagsResult?>(
context,
MaterialPageRoute(
@@ -578,7 +562,7 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
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 +575,14 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
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')),
),
),

View File

@@ -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);
}

View File

@@ -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),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import '../services/changelog_service.dart';
import '../services/version_service.dart';
class ChangelogDialog extends StatelessWidget {

View File

@@ -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';
@@ -97,8 +96,8 @@ class _CompassIndicatorState extends State<CompassIndicator> {
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
@@ -108,7 +107,7 @@ class _CompassIndicatorState extends State<CompassIndicator> {
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
color: Colors.black.withValues(alpha: 0.25),
blurRadius: 6,
offset: const Offset(0, 3),
),

View File

@@ -57,8 +57,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
}
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<DownloadAreaDialog> {
final locService = LocalizationService.instance;
final appState = context.watch<AppState>();
final bounds = widget.controller.camera.visibleBounds;
final maxZoom = _zoom.toInt();
final isOfflineMode = appState.offlineMode;
// Use the calculated max possible zoom instead of fixed span
@@ -191,8 +189,8 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
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(
@@ -200,7 +198,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
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,
@@ -213,7 +211,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
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,
@@ -232,9 +230,9 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
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: [
@@ -266,8 +264,9 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
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<AppState>();
final selectedProvider = appState.selectedTileProvider;
@@ -294,6 +293,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
builder: (context) => const DownloadStartedDialog(),
);
} catch (e) {
if (!context.mounted) return;
Navigator.pop(context);
showDialog(
context: context,

View File

@@ -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';
@@ -95,7 +92,8 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
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<bool>(
@@ -103,13 +101,14 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
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);
}
@@ -435,25 +434,23 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
final locService = LocalizationService.instance;
final appState = context.watch<AppState>();
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 +478,11 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
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<RefineTagsResult?>(
context,
MaterialPageRoute(
@@ -694,7 +691,7 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
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 +704,14 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
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')),
),
),

View File

@@ -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,
@@ -231,7 +203,7 @@ class DirectionConesBuilder {
return Polygon(
points: points,
color: kDirectionConeColor.withOpacity(opacity),
color: kDirectionConeColor.withValues(alpha: opacity),
borderColor: kDirectionConeColor,
borderStrokeWidth: getDirectionConeBorderWidth(context),
);
@@ -279,7 +251,7 @@ class DirectionConesBuilder {
return Polygon(
points: points,
color: kDirectionConeColor.withOpacity(opacity),
color: kDirectionConeColor.withValues(alpha: opacity),
borderColor: kDirectionConeColor,
borderStrokeWidth: getDirectionConeBorderWidth(context),
);

View File

@@ -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(

View File

@@ -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';

View File

@@ -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';
@@ -64,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)),
@@ -98,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(
@@ -131,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),

View File

@@ -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';

View File

@@ -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<NodeMapMarker> createState() => _NodeMapMarkerState();

View File

@@ -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;

View File

@@ -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,
),

View File

@@ -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<SuspectedLocationMapMarker> createState() => _SuspectedLocationMapMarkerState();

View File

@@ -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!,
);
}

View File

@@ -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: [

View File

@@ -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';

View File

@@ -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<bool>(
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(

View File

@@ -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: [
@@ -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,

View File

@@ -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,
),

View File

@@ -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,
),
),

View File

@@ -82,7 +82,7 @@ class _ProximityAlertBannerState extends State<ProximityAlertBanner>
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),
),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import '../models/osm_node.dart';
import '../services/localization_service.dart';

View File

@@ -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(

View File

@@ -107,7 +107,7 @@ class _LocationSearchBarState extends State<LocationSearchBar> {
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<LocationSearchBar> {
: null,
dense: true,
onTap: () => _onResultTap(result),
)).toList(),
)),
],
),
);
@@ -184,7 +184,7 @@ class _LocationSearchBarState extends State<LocationSearchBar> {
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),
),

View File

@@ -81,9 +81,9 @@ class _SubmissionGuideDialogState extends State<SubmissionGuideDialog> {
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<SubmissionGuideDialog> {
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(

View File

@@ -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<AppState>();
final locService = LocalizationService.instance;
// Get all fields except location and ticket_no
@@ -97,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,
),
@@ -131,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,
),

View File

@@ -88,9 +88,9 @@ class _WelcomeDialogState extends State<WelcomeDialog> {
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<WelcomeDialog> {
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(

View File

@@ -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:

View File

@@ -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:

View File

@@ -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);