mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
Merge pull request #34 from dougborg/pr/02-lint-cleanup
Add flutter_lints and fix all analyzer warnings
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'osm_node.dart';
|
||||
|
||||
/// A bundle of OSM tags that describe a particular surveillance operator.
|
||||
|
||||
@@ -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>[]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../models/search_result.dart';
|
||||
import '../services/search_service.dart';
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/changelog_service.dart';
|
||||
import '../services/version_service.dart';
|
||||
|
||||
class ChangelogDialog extends StatelessWidget {
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../models/osm_node.dart';
|
||||
import '../services/localization_service.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(
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
16
pubspec.lock
16
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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user