mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-28 21:50:40 +02: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
-2
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
@@ -23,7 +22,6 @@ import 'services/operator_profile_service.dart';
|
|||||||
import 'services/deep_link_service.dart';
|
import 'services/deep_link_service.dart';
|
||||||
import 'widgets/node_provider_with_cache.dart';
|
import 'widgets/node_provider_with_cache.dart';
|
||||||
import 'services/profile_service.dart';
|
import 'services/profile_service.dart';
|
||||||
import 'widgets/proximity_warning_dialog.dart';
|
|
||||||
import 'widgets/reauth_messages_dialog.dart';
|
import 'widgets/reauth_messages_dialog.dart';
|
||||||
import 'dev_config.dart';
|
import 'dev_config.dart';
|
||||||
import 'state/auth_state.dart';
|
import 'state/auth_state.dart';
|
||||||
@@ -366,6 +364,7 @@ class AppState extends ChangeNotifier {
|
|||||||
/// Show re-authentication dialog if needed
|
/// Show re-authentication dialog if needed
|
||||||
Future<void> checkAndPromptReauthForMessages(BuildContext context) async {
|
Future<void> checkAndPromptReauthForMessages(BuildContext context) async {
|
||||||
if (await needsReauthForMessages()) {
|
if (await needsReauthForMessages()) {
|
||||||
|
if (!context.mounted) return;
|
||||||
_showReauthDialog(context);
|
_showReauthDialog(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
@@ -147,7 +146,7 @@ class OneTimeMigrations {
|
|||||||
debugPrint('[Migration] Stack trace: $stackTrace');
|
debugPrint('[Migration] Stack trace: $stackTrace');
|
||||||
|
|
||||||
// Nuclear option: clear everything and show non-dismissible error dialog
|
// Nuclear option: clear everything and show non-dismissible error dialog
|
||||||
if (context != null) {
|
if (context != null && context.mounted) {
|
||||||
NuclearResetDialog.show(context, error, stackTrace);
|
NuclearResetDialog.show(context, error, stackTrace);
|
||||||
} else {
|
} else {
|
||||||
// If no context available, just log and hope for the best
|
// If no context available, just log and hope for the best
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class DirectionFov {
|
|||||||
DirectionFov(this.centerDegrees, this.fovDegrees);
|
DirectionFov(this.centerDegrees, this.fovDegrees);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'DirectionFov(center: ${centerDegrees}°, fov: ${fovDegrees}°)';
|
String toString() => 'DirectionFov(center: $centerDegrees°, fov: $fovDegrees°)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:uuid/uuid.dart';
|
|
||||||
import 'osm_node.dart';
|
import 'osm_node.dart';
|
||||||
|
|
||||||
/// Sentinel value for copyWith methods to distinguish between null and not provided
|
/// 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';
|
import 'osm_node.dart';
|
||||||
|
|
||||||
/// A bundle of OSM tags that describe a particular surveillance operator.
|
/// A bundle of OSM tags that describe a particular surveillance operator.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
/// A suspected surveillance location from the CSV data
|
/// A suspected surveillance location from the CSV data
|
||||||
@@ -35,8 +36,8 @@ class SuspectedLocation {
|
|||||||
bounds = coordinates.bounds;
|
bounds = coordinates.bounds;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If GeoJSON parsing fails, use default coordinates
|
// If GeoJSON parsing fails, use default coordinates
|
||||||
print('[SuspectedLocation] Failed to parse GeoJSON for ticket $ticketNo: $e');
|
debugPrint('[SuspectedLocation] Failed to parse GeoJSON for ticket $ticketNo: $e');
|
||||||
print('[SuspectedLocation] Location string: $locationString');
|
debugPrint('[SuspectedLocation] Location string: $locationString');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ class SuspectedLocation {
|
|||||||
// The geoJson IS the geometry object (not wrapped in a 'geometry' property)
|
// The geoJson IS the geometry object (not wrapped in a 'geometry' property)
|
||||||
final coordinates = geoJson['coordinates'] as List?;
|
final coordinates = geoJson['coordinates'] as List?;
|
||||||
if (coordinates == null || coordinates.isEmpty) {
|
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>[]);
|
return (centroid: const LatLng(0, 0), bounds: <LatLng>[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ class SuspectedLocation {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unsupported geometry type: $type');
|
debugPrint('Unsupported geometry type: $type');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (points.isEmpty) {
|
if (points.isEmpty) {
|
||||||
@@ -127,7 +128,7 @@ class SuspectedLocation {
|
|||||||
|
|
||||||
return (centroid: centroid, bounds: points);
|
return (centroid: centroid, bounds: points);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error extracting coordinates from GeoJSON: $e');
|
debugPrint('Error extracting coordinates from GeoJSON: $e');
|
||||||
return (centroid: const LatLng(0, 0), bounds: <LatLng>[]);
|
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/proximity_alerts_section.dart';
|
||||||
import 'settings/sections/suspected_locations_section.dart';
|
import 'settings/sections/suspected_locations_section.dart';
|
||||||
import 'settings/sections/tile_provider_section.dart';
|
import 'settings/sections/tile_provider_section.dart';
|
||||||
import 'settings/sections/network_status_section.dart';
|
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
|
|
||||||
class AdvancedSettingsScreen extends StatelessWidget {
|
class AdvancedSettingsScreen extends StatelessWidget {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import '../../widgets/add_node_sheet.dart';
|
|||||||
import '../../widgets/edit_node_sheet.dart';
|
import '../../widgets/edit_node_sheet.dart';
|
||||||
import '../../widgets/navigation_sheet.dart';
|
import '../../widgets/navigation_sheet.dart';
|
||||||
import '../../widgets/measured_sheet.dart';
|
import '../../widgets/measured_sheet.dart';
|
||||||
import '../../state/settings_state.dart' show FollowMeMode;
|
|
||||||
|
|
||||||
/// Coordinates all bottom sheet operations including opening, closing, height tracking,
|
/// Coordinates all bottom sheet operations including opening, closing, height tracking,
|
||||||
/// and sheet-related validation logic.
|
/// and sheet-related validation logic.
|
||||||
@@ -118,9 +117,8 @@ class SheetCoordinator {
|
|||||||
controller.closed.then((_) {
|
controller.closed.then((_) {
|
||||||
_addSheetHeight = 0.0;
|
_addSheetHeight = 0.0;
|
||||||
onStateChanged();
|
onStateChanged();
|
||||||
|
|
||||||
// Handle dismissal by canceling session if still active
|
// Handle dismissal by canceling session if still active
|
||||||
final appState = context.read<AppState>();
|
|
||||||
if (appState.session != null) {
|
if (appState.session != null) {
|
||||||
debugPrint('[SheetCoordinator] AddNodeSheet dismissed - canceling session');
|
debugPrint('[SheetCoordinator] AddNodeSheet dismissed - canceling session');
|
||||||
appState.cancelSession();
|
appState.cancelSession();
|
||||||
@@ -187,9 +185,8 @@ class SheetCoordinator {
|
|||||||
_editSheetHeight = 0.0;
|
_editSheetHeight = 0.0;
|
||||||
_transitioningToEdit = false;
|
_transitioningToEdit = false;
|
||||||
onStateChanged();
|
onStateChanged();
|
||||||
|
|
||||||
// Handle dismissal by canceling session if still active
|
// Handle dismissal by canceling session if still active
|
||||||
final appState = context.read<AppState>();
|
|
||||||
if (appState.editSession != null) {
|
if (appState.editSession != null) {
|
||||||
debugPrint('[SheetCoordinator] EditNodeSheet dismissed - canceling edit session');
|
debugPrint('[SheetCoordinator] EditNodeSheet dismissed - canceling edit session');
|
||||||
appState.cancelEditSession();
|
appState.cancelEditSession();
|
||||||
@@ -266,7 +263,7 @@ class SheetCoordinator {
|
|||||||
/// Restore the follow-me mode that was active before opening a node sheet
|
/// Restore the follow-me mode that was active before opening a node sheet
|
||||||
void _restoreFollowMeMode(AppState appState) {
|
void _restoreFollowMeMode(AppState appState) {
|
||||||
if (_followMeModeBeforeSheet != null) {
|
if (_followMeModeBeforeSheet != null) {
|
||||||
debugPrint('[SheetCoordinator] Restoring follow-me mode: ${_followMeModeBeforeSheet}');
|
debugPrint('[SheetCoordinator] Restoring follow-me mode: $_followMeModeBeforeSheet');
|
||||||
appState.setFollowMeMode(_followMeModeBeforeSheet!);
|
appState.setFollowMeMode(_followMeModeBeforeSheet!);
|
||||||
_followMeModeBeforeSheet = null; // Clear stored state
|
_followMeModeBeforeSheet = null; // Clear stored state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
@@ -10,12 +8,9 @@ import '../dev_config.dart';
|
|||||||
import '../widgets/map_view.dart';
|
import '../widgets/map_view.dart';
|
||||||
import '../services/localization_service.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/node_tag_sheet.dart';
|
||||||
import '../widgets/download_area_dialog.dart';
|
import '../widgets/download_area_dialog.dart';
|
||||||
import '../widgets/measured_sheet.dart';
|
import '../widgets/measured_sheet.dart';
|
||||||
import '../widgets/navigation_sheet.dart';
|
|
||||||
import '../widgets/search_bar.dart';
|
import '../widgets/search_bar.dart';
|
||||||
import '../widgets/suspected_location_sheet.dart';
|
import '../widgets/suspected_location_sheet.dart';
|
||||||
import '../widgets/welcome_dialog.dart';
|
import '../widgets/welcome_dialog.dart';
|
||||||
@@ -190,6 +185,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
// Run any needed migrations first
|
// Run any needed migrations first
|
||||||
final versionsNeedingMigration = await ChangelogService().getVersionsNeedingMigration();
|
final versionsNeedingMigration = await ChangelogService().getVersionsNeedingMigration();
|
||||||
|
if (!mounted) return;
|
||||||
for (final version in versionsNeedingMigration) {
|
for (final version in versionsNeedingMigration) {
|
||||||
await ChangelogService().runMigration(version, appState, context);
|
await ChangelogService().runMigration(version, appState, context);
|
||||||
}
|
}
|
||||||
@@ -214,6 +210,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
case PopupType.changelog:
|
case PopupType.changelog:
|
||||||
final changelogContent = await ChangelogService().getChangelogContentForDisplay();
|
final changelogContent = await ChangelogService().getChangelogContentForDisplay();
|
||||||
|
if (!mounted) return;
|
||||||
if (changelogContent != null) {
|
if (changelogContent != null) {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
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() {
|
void _onResumeRoute() {
|
||||||
_navigationCoordinator.resumeRoute(
|
_navigationCoordinator.resumeRoute(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -402,9 +370,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Reset height and clear selection when sheet is dismissed
|
// Reset height and clear selection when sheet is dismissed
|
||||||
|
final appState = context.read<AppState>();
|
||||||
controller.closed.then((_) {
|
controller.closed.then((_) {
|
||||||
|
if (!mounted) return;
|
||||||
_sheetCoordinator.resetTagSheetHeight(() => setState(() {}));
|
_sheetCoordinator.resetTagSheetHeight(() => setState(() {}));
|
||||||
context.read<AppState>().clearSuspectedLocationSelection();
|
appState.clearSuspectedLocationSelection();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +548,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Theme.of(context).shadowColor.withOpacity(0.3),
|
color: Theme.of(context).shadowColor.withValues(alpha: 0.3),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: Offset(0, -2),
|
offset: Offset(0, -2),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class _NavigationSettingsScreenState extends State<NavigationSettingsScreen> {
|
|||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
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),
|
const SizedBox(width: 8),
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ class _OperatorProfileEditorState extends State<OperatorProfileEditor> {
|
|||||||
|
|
||||||
return List.generate(_tags.length, (i) {
|
return List.generate(_tags.length, (i) {
|
||||||
final keyController = TextEditingController(text: _tags[i].key);
|
final keyController = TextEditingController(text: _tags[i].key);
|
||||||
final valueController = TextEditingController(text: _tags[i].value);
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -308,7 +308,7 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
|
|||||||
label: Text(locService.t('auth.deleteAccount')),
|
label: Text(locService.t('auth.deleteAccount')),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: Theme.of(context).colorScheme.error,
|
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(
|
Container(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.error.withOpacity(0.3),
|
color: Theme.of(context).colorScheme.error.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ class _ProfileEditorState extends State<ProfileEditor> {
|
|||||||
|
|
||||||
return List.generate(_tags.length, (i) {
|
return List.generate(_tags.length, (i) {
|
||||||
final keyController = TextEditingController(text: _tags[i].key);
|
final keyController = TextEditingController(text: _tags[i].key);
|
||||||
final valueController = TextEditingController(text: _tags[i].value);
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class _ReleaseNotesScreenState extends State<ReleaseNotesScreen> {
|
|||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -142,8 +142,8 @@ class _ReleaseNotesScreenState extends State<ReleaseNotesScreen> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isCurrentVersion
|
color: isCurrentVersion
|
||||||
? Theme.of(context).colorScheme.primary.withOpacity(0.3)
|
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.3)
|
||||||
: Theme.of(context).dividerColor.withOpacity(0.3),
|
: Theme.of(context).dividerColor.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ class _LanguageSectionState extends State<LanguageSection> {
|
|||||||
_loadLanguageNames();
|
_loadLanguageNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadSelectedLanguage() async {
|
Future<void> _loadSelectedLanguage() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedLanguage = prefs.getString('language_code');
|
_selectedLanguage = prefs.getString('language_code');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadLanguageNames() async {
|
Future<void> _loadLanguageNames() async {
|
||||||
final locService = LocalizationService.instance;
|
final locService = LocalizationService.instance;
|
||||||
final Map<String, String> names = {};
|
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);
|
await LocalizationService.instance.setLanguage(languageCode);
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedLanguage = languageCode;
|
_selectedLanguage = languageCode;
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class NodeProfilesSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If user chose to create custom profile, open the profile editor
|
// If user chose to create custom profile, open the profile editor
|
||||||
if (result == 'create') {
|
if (result == 'create' && context.mounted) {
|
||||||
_createNewProfile(context);
|
_createNewProfile(context);
|
||||||
}
|
}
|
||||||
// If user chose import from website, ProfileAddChoiceDialog handles opening the URL
|
// 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')}"
|
: "${(area.sizeBytes / 1024).toStringAsFixed(1)} ${locService.t('offlineAreas.kilobytes')}"
|
||||||
: '--';
|
: '--';
|
||||||
|
|
||||||
String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' +
|
String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n'
|
||||||
'${locService.t('offlineAreas.maxZoom')}: Z${area.maxZoom}' + '\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.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)}';
|
'${locService.t('offlineAreas.latitude')}: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}';
|
||||||
|
|
||||||
if (area.status == OfflineAreaStatus.downloading) {
|
if (area.status == OfflineAreaStatus.downloading) {
|
||||||
@@ -207,7 +207,7 @@ class _OfflineAreasSectionState extends State<OfflineAreasSection> {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -140,9 +140,9 @@ class _ProximityAlertsSectionState extends State<ProximityAlertsSection> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -233,7 +233,7 @@ class _ProximityAlertsSectionState extends State<ProximityAlertsSection> {
|
|||||||
DistanceService.convertFromMeters(kProximityAlertDefaultDistance.toDouble(), appState.distanceUnit).round().toString(),
|
DistanceService.convertFromMeters(kProximityAlertDefaultDistance.toDouble(), appState.distanceUnit).round().toString(),
|
||||||
]),
|
]),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
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 'package:provider/provider.dart';
|
||||||
import '../../../app_state.dart';
|
import '../../../app_state.dart';
|
||||||
import '../../../services/localization_service.dart';
|
import '../../../services/localization_service.dart';
|
||||||
import '../../../state/settings_state.dart';
|
|
||||||
|
|
||||||
class QueueSection extends StatelessWidget {
|
class QueueSection extends StatelessWidget {
|
||||||
const QueueSection({super.key});
|
const QueueSection({super.key});
|
||||||
@@ -116,16 +115,16 @@ class QueueSection extends StatelessWidget {
|
|||||||
(upload.error ? locService.t('queue.error') : "") +
|
(upload.error ? locService.t('queue.error') : "") +
|
||||||
(upload.completing ? locService.t('queue.completing') : "")),
|
(upload.completing ? locService.t('queue.completing') : "")),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)]) + '\n' +
|
'${locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)])}\n'
|
||||||
locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)]) + '\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.longitude', params: [upload.coord.longitude.toStringAsFixed(6)])}\n'
|
||||||
locService.t('queue.direction', params: [
|
'${locService.t('queue.direction', params: [
|
||||||
upload.direction is String
|
upload.direction is String
|
||||||
? upload.direction.toString()
|
? upload.direction.toString()
|
||||||
: upload.direction.round().toString()
|
: upload.direction.round().toString()
|
||||||
]) + '\n' +
|
])}\n'
|
||||||
locService.t('queue.attempts', params: [upload.attempts.toString()]) +
|
'${locService.t('queue.attempts', params: [upload.attempts.toString()])}'
|
||||||
(upload.error ? "\n${locService.t('queue.uploadFailedRetry')}" : "")
|
'${upload.error ? "\n${locService.t('queue.uploadFailedRetry')}" : ""}'
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TileProviderSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
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(
|
leading: CircleAvatar(
|
||||||
backgroundColor: isSelected
|
backgroundColor: isSelected
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.surfaceVariant,
|
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.map,
|
Icons.map,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class UploadModeSection extends StatelessWidget {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: appState.pendingCount > 0
|
color: appState.pendingCount > 0
|
||||||
? Theme.of(context).disabledColor
|
? Theme.of(context).disabledColor
|
||||||
: Theme.of(context).colorScheme.onSurface.withOpacity(0.7)
|
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
case UploadMode.sandbox:
|
case UploadMode.sandbox:
|
||||||
@@ -95,7 +95,6 @@ class UploadModeSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
case UploadMode.simulate:
|
case UploadMode.simulate:
|
||||||
default:
|
|
||||||
return Text(
|
return Text(
|
||||||
locService.t('uploadMode.simulateDescription'),
|
locService.t('uploadMode.simulateDescription'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Version: ${VersionService().version}',
|
'Version: ${VersionService().version}',
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
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,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
import '../models/tile_provider.dart';
|
import '../models/tile_provider.dart';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:provider/provider.dart';
|
|||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
import '../models/pending_upload.dart';
|
import '../models/pending_upload.dart';
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
import '../state/settings_state.dart';
|
|
||||||
|
|
||||||
class UploadQueueScreen extends StatelessWidget {
|
class UploadQueueScreen extends StatelessWidget {
|
||||||
const UploadQueueScreen({super.key});
|
const UploadQueueScreen({super.key});
|
||||||
@@ -114,8 +113,8 @@ class UploadQueueScreen extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
border: Border.all(color: Colors.orange.withOpacity(0.3)),
|
border: Border.all(color: Colors.orange.withValues(alpha: 0.3)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -205,7 +204,7 @@ class UploadQueueScreen extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.clear_all),
|
icon: const Icon(Icons.clear_all),
|
||||||
label: Text(locService.t('queue.clearUploadQueue')),
|
label: Text(locService.t('queue.clearUploadQueue')),
|
||||||
style: ElevatedButton.styleFrom(
|
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(
|
Icon(
|
||||||
Icons.check_circle_outline,
|
Icons.check_circle_outline,
|
||||||
size: 64,
|
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),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
locService.t('queue.nothingInQueue'),
|
locService.t('queue.nothingInQueue'),
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
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,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -272,16 +271,16 @@ class UploadQueueScreen extends StatelessWidget {
|
|||||||
_getUploadStateText(upload, locService)
|
_getUploadStateText(upload, locService)
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)]) + '\n' +
|
'${locService.t('queue.destination', params: [_getUploadModeDisplayName(upload.uploadMode)])}\n'
|
||||||
locService.t('queue.latitude', params: [upload.coord.latitude.toStringAsFixed(6)]) + '\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.longitude', params: [upload.coord.longitude.toStringAsFixed(6)])}\n'
|
||||||
locService.t('queue.direction', params: [
|
'${locService.t('queue.direction', params: [
|
||||||
upload.direction is String
|
upload.direction is String
|
||||||
? upload.direction.toString()
|
? upload.direction.toString()
|
||||||
: upload.direction.round().toString()
|
: upload.direction.round().toString()
|
||||||
]) + '\n' +
|
])}\n'
|
||||||
locService.t('queue.attempts', params: [upload.attempts.toString()]) +
|
'${locService.t('queue.attempts', params: [upload.attempts.toString()])}'
|
||||||
(upload.uploadState == UploadState.error ? "\n${locService.t('queue.uploadFailedRetry')}" : "")
|
'${upload.uploadState == UploadState.error ? "\n${locService.t('queue.uploadFailedRetry')}" : ""}'
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
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_client.dart';
|
||||||
import 'package:oauth2_client/oauth2_helper.dart';
|
import 'package:oauth2_client/oauth2_helper.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@@ -30,7 +30,6 @@ class AuthService {
|
|||||||
case UploadMode.sandbox:
|
case UploadMode.sandbox:
|
||||||
return 'osm_token_sandbox';
|
return 'osm_token_sandbox';
|
||||||
case UploadMode.simulate:
|
case UploadMode.simulate:
|
||||||
default:
|
|
||||||
return 'osm_token_simulate';
|
return 'osm_token_simulate';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,10 +96,10 @@ class AuthService {
|
|||||||
final tokenJson = jsonEncode(tokenMap);
|
final tokenJson = jsonEncode(tokenMap);
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString(_tokenKey, tokenJson); // Save token for current mode
|
await prefs.setString(_tokenKey, tokenJson); // Save token for current mode
|
||||||
_displayName = await _fetchUsername(token!.accessToken!);
|
_displayName = await _fetchUsername(token.accessToken!);
|
||||||
return _displayName;
|
return _displayName;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('AuthService: OAuth login failed: $e');
|
debugPrint('AuthService: OAuth login failed: $e');
|
||||||
log('OAuth login failed: $e');
|
log('OAuth login failed: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -128,7 +127,7 @@ class AuthService {
|
|||||||
_displayName = await _fetchUsername(accessToken);
|
_displayName = await _fetchUsername(accessToken);
|
||||||
return _displayName;
|
return _displayName;
|
||||||
} catch (e) {
|
} 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');
|
log('Error restoring login with stored token: $e');
|
||||||
// Token might be expired or invalid, clear it
|
// Token might be expired or invalid, clear it
|
||||||
await logout();
|
await logout();
|
||||||
@@ -194,7 +193,7 @@ class AuthService {
|
|||||||
final displayName = userData['user']?['display_name'];
|
final displayName = userData['user']?['display_name'];
|
||||||
return displayName;
|
return displayName;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('AuthService: Error fetching username: $e');
|
debugPrint('AuthService: Error fetching username: $e');
|
||||||
log('Error fetching username: $e');
|
log('Error fetching username: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@@ -304,8 +303,8 @@ class ChangelogService {
|
|||||||
final v2Parts = v2.split('.').map(int.parse).toList();
|
final v2Parts = v2.split('.').map(int.parse).toList();
|
||||||
|
|
||||||
// Ensure we have at least 3 parts (major.minor.patch)
|
// Ensure we have at least 3 parts (major.minor.patch)
|
||||||
while (v1Parts.length < 3) v1Parts.add(0);
|
while (v1Parts.length < 3) { v1Parts.add(0); }
|
||||||
while (v2Parts.length < 3) v2Parts.add(0);
|
while (v2Parts.length < 3) { v2Parts.add(0); }
|
||||||
|
|
||||||
// Compare major version first
|
// Compare major version first
|
||||||
if (v1Parts[0] < v2Parts[0]) return -1;
|
if (v1Parts[0] < v2Parts[0]) return -1;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import '../models/node_profile.dart';
|
import '../models/node_profile.dart';
|
||||||
import 'profile_import_service.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) {
|
void _handleAddProfileLink(Uri uri) {
|
||||||
final base64Data = uri.queryParameters['p'];
|
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:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui';
|
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 '../app_state.dart';
|
||||||
import '../models/tile_provider.dart' as models;
|
|
||||||
import 'map_data_provider.dart';
|
import 'map_data_provider.dart';
|
||||||
import 'offline_area_service.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
|
// Re-throw the exception and let FlutterMap handle missing tiles gracefully
|
||||||
// This is better than trying to provide fallback images
|
// This is better than trying to provide fallback images
|
||||||
throw e;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import '../models/node_profile.dart';
|
import '../models/node_profile.dart';
|
||||||
import '../models/osm_node.dart';
|
import '../models/osm_node.dart';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
@@ -81,7 +80,7 @@ Future<List<OsmNode>> _fetchFromOsmApi({
|
|||||||
debugPrint('[fetchOsmApiNodes] Exception: $e');
|
debugPrint('[fetchOsmApiNodes] Exception: $e');
|
||||||
|
|
||||||
// Don't report status here - let the top level handle it
|
// 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 'dart:io';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import '../offline_area_service.dart';
|
import '../offline_area_service.dart';
|
||||||
import '../offline_areas/offline_area_models.dart';
|
import '../offline_areas/offline_area_models.dart';
|
||||||
import '../offline_areas/offline_tile_utils.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.
|
/// Only tracks the latest user-initiated request - background requests are ignored.
|
||||||
enum NetworkRequestStatus {
|
enum NetworkRequestStatus {
|
||||||
idle, // No active requests
|
idle, // No active requests
|
||||||
loading, // Request in progress
|
loading, // Request in progress
|
||||||
splitting, // Request being split due to limits/timeouts
|
splitting, // Request being split due to limits/timeouts
|
||||||
success, // Data loaded successfully
|
success, // Data loaded successfully
|
||||||
timeout, // Request timed out
|
timeout, // Request timed out
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import '../models/osm_node.dart';
|
import '../models/osm_node.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
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)
|
/// Remove a node by ID from the cache (used for successful deletions)
|
||||||
void removeNodeById(int nodeId) {
|
void removeNodeById(int nodeId) {
|
||||||
if (_nodes.remove(nodeId) != null) {
|
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) {
|
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)
|
/// Remove a specific temporary node by its ID (for queue item-specific cleanup)
|
||||||
void removeTempNodeById(int tempNodeId) {
|
void removeTempNodeById(int tempNodeId) {
|
||||||
if (tempNodeId >= 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_nodes.remove(tempNodeId) != null) {
|
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 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'offline_areas/offline_area_models.dart';
|
import 'offline_areas/offline_area_models.dart';
|
||||||
import 'offline_areas/offline_tile_utils.dart';
|
import 'offline_areas/offline_tile_utils.dart';
|
||||||
import 'offline_areas/offline_area_downloader.dart';
|
import 'offline_areas/offline_area_downloader.dart';
|
||||||
|
|
||||||
import '../models/osm_node.dart';
|
|
||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
import 'map_data_provider.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.
|
/// Service for managing download, storage, and retrieval of offline map areas and cameras.
|
||||||
class OfflineAreaService {
|
class OfflineAreaService {
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ class RoutingService {
|
|||||||
debugPrint('[RoutingService] Calculating route from $start to $end');
|
debugPrint('[RoutingService] Calculating route from $start to $end');
|
||||||
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
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();
|
final full = p.toJson();
|
||||||
return {
|
return {
|
||||||
'id': full['id'],
|
'id': full['id'],
|
||||||
@@ -47,7 +47,7 @@ class RoutingService {
|
|||||||
};
|
};
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
final uri = Uri.parse('$_baseUrl');
|
final uri = Uri.parse(_baseUrl);
|
||||||
final params = {
|
final params = {
|
||||||
'start': {
|
'start': {
|
||||||
'longitude': start.longitude,
|
'longitude': start.longitude,
|
||||||
@@ -57,8 +57,8 @@ class RoutingService {
|
|||||||
'longitude': end.longitude,
|
'longitude': end.longitude,
|
||||||
'latitude': end.latitude
|
'latitude': end.latitude
|
||||||
},
|
},
|
||||||
'avoidance_distance': avoidance_distance,
|
'avoidance_distance': avoidanceDistance,
|
||||||
'enabled_profiles': enabled_profiles,
|
'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
|
'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/foundation.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
import '../models/suspected_location.dart';
|
import '../models/suspected_location.dart';
|
||||||
import 'suspected_location_service.dart';
|
import 'suspected_location_service.dart';
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ class SuspectedLocationDatabase {
|
|||||||
|
|
||||||
// Process entries in batches to avoid memory issues
|
// Process entries in batches to avoid memory issues
|
||||||
const batchSize = 1000;
|
const batchSize = 1000;
|
||||||
int totalInserted = 0;
|
|
||||||
int validCount = 0;
|
int validCount = 0;
|
||||||
int errorCount = 0;
|
int errorCount = 0;
|
||||||
|
|
||||||
@@ -188,7 +187,6 @@ class SuspectedLocationDatabase {
|
|||||||
|
|
||||||
// Commit this batch
|
// Commit this batch
|
||||||
await batch.commit(noResult: true);
|
await batch.commit(noResult: true);
|
||||||
totalInserted += currentBatch.length;
|
|
||||||
|
|
||||||
// Log progress every few batches
|
// Log progress every few batches
|
||||||
if ((i ~/ batchSize) % 5 == 0) {
|
if ((i ~/ batchSize) % 5 == 0) {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@@ -208,7 +206,7 @@ class SuspectedLocationService {
|
|||||||
validRows++;
|
validRows++;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
// Skip rows that can't be parsed
|
// Skip rows that can't be parsed
|
||||||
debugPrint('[SuspectedLocationService] Error parsing row $rowIndex: $e');
|
debugPrint('[SuspectedLocationService] Error parsing row $rowIndex: $e');
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class Uploader {
|
|||||||
final currentNodeXml = currentNodeResp.body;
|
final currentNodeXml = currentNodeResp.body;
|
||||||
final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml);
|
final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml);
|
||||||
if (versionMatch == null) {
|
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');
|
debugPrint('[Uploader] $errorMsg');
|
||||||
return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId);
|
return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId);
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ class Uploader {
|
|||||||
final currentNodeXml = currentNodeResp.body;
|
final currentNodeXml = currentNodeResp.body;
|
||||||
final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml);
|
final versionMatch = RegExp(r'version="(\d+)"').firstMatch(currentNodeXml);
|
||||||
if (versionMatch == null) {
|
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');
|
debugPrint('[Uploader] $errorMsg');
|
||||||
return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId);
|
return UploadResult.failure(errorMessage: errorMsg, changesetId: changesetId);
|
||||||
}
|
}
|
||||||
@@ -350,12 +350,6 @@ class Uploader {
|
|||||||
headers: _headers,
|
headers: _headers,
|
||||||
).timeout(kUploadHttpTimeout);
|
).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(
|
Future<http.Response> _put(String path, String body) => http.put(
|
||||||
Uri.https(_host, path),
|
Uri.https(_host, path),
|
||||||
headers: _headers,
|
headers: _headers,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class AuthState extends ChangeNotifier {
|
|||||||
_username = await _auth.restoreLogin();
|
_username = await _auth.restoreLogin();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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 {
|
try {
|
||||||
_username = await _auth.login();
|
_username = await _auth.login();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("AuthState: Login error: $e");
|
debugPrint("AuthState: Login error: $e");
|
||||||
_username = null;
|
_username = null;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -49,7 +49,7 @@ class AuthState extends ChangeNotifier {
|
|||||||
_username = null;
|
_username = null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("AuthState: Auth refresh error: $e");
|
debugPrint("AuthState: Auth refresh error: $e");
|
||||||
_username = null;
|
_username = null;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -59,7 +59,7 @@ class AuthState extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
_username = await _auth.forceLogin();
|
_username = await _auth.forceLogin();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("AuthState: Forced login error: $e");
|
debugPrint("AuthState: Forced login error: $e");
|
||||||
_username = null;
|
_username = null;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -69,7 +69,7 @@ class AuthState extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
return await _auth.isLoggedIn();
|
return await _auth.isLoggedIn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("AuthState: Token validation error: $e");
|
debugPrint("AuthState: Token validation error: $e");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ class AuthState extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_username = null;
|
_username = null;
|
||||||
print("AuthState: Mode change user restoration error: $e");
|
debugPrint("AuthState: Mode change user restoration error: $e");
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
import '../models/search_result.dart';
|
import '../models/search_result.dart';
|
||||||
import '../services/search_service.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
|
// Initialize by loading queue from storage and repopulate cache with pending nodes
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await _loadQueue();
|
await _loadQueue();
|
||||||
print('[UploadQueue] Loaded ${_queue.length} items from storage');
|
debugPrint('[UploadQueue] Loaded ${_queue.length} items from storage');
|
||||||
_repopulateCacheFromQueue();
|
_repopulateCacheFromQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repopulate the cache with pending nodes from the queue on startup
|
// Repopulate the cache with pending nodes from the queue on startup
|
||||||
void _repopulateCacheFromQueue() {
|
void _repopulateCacheFromQueue() {
|
||||||
print('[UploadQueue] Repopulating cache from ${_queue.length} queue items');
|
debugPrint('[UploadQueue] Repopulating cache from ${_queue.length} queue items');
|
||||||
final nodesToAdd = <OsmNode>[];
|
final nodesToAdd = <OsmNode>[];
|
||||||
|
|
||||||
for (final upload in _queue) {
|
for (final upload in _queue) {
|
||||||
// Skip completed uploads - they should already be in OSM and will be fetched normally
|
// Skip completed uploads - they should already be in OSM and will be fetched normally
|
||||||
if (upload.isComplete) {
|
if (upload.isComplete) {
|
||||||
print('[UploadQueue] Skipping completed upload at ${upload.coord}');
|
debugPrint('[UploadQueue] Skipping completed upload at ${upload.coord}');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
print('[UploadQueue] Processing ${upload.operation} upload at ${upload.coord}');
|
debugPrint('[UploadQueue] Processing ${upload.operation} upload at ${upload.coord}');
|
||||||
|
|
||||||
if (upload.isDeletion) {
|
if (upload.isDeletion) {
|
||||||
// For deletions: mark the original node as pending deletion if it exists in cache
|
// 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();
|
tags['_temp_id'] = tempId.toString();
|
||||||
|
|
||||||
// Store temp ID for future cleanup if not already set
|
// Store temp ID for future cleanup if not already set
|
||||||
if (upload.tempNodeId == null) {
|
upload.tempNodeId ??= tempId;
|
||||||
upload.tempNodeId = tempId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (upload.isEdit) {
|
if (upload.isEdit) {
|
||||||
// For edits: also mark original with _pending_edit if it exists
|
// For edits: also mark original with _pending_edit if it exists
|
||||||
@@ -112,7 +110,7 @@ class UploadQueueState extends ChangeNotifier {
|
|||||||
|
|
||||||
if (nodesToAdd.isNotEmpty) {
|
if (nodesToAdd.isNotEmpty) {
|
||||||
_nodeCache.addOrUpdate(nodesToAdd);
|
_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
|
// Save queue if we updated any temp IDs for backward compatibility
|
||||||
_saveQueue();
|
_saveQueue();
|
||||||
@@ -587,7 +585,7 @@ class UploadQueueState extends ChangeNotifier {
|
|||||||
// Still have time, will retry after backoff delay
|
// Still have time, will retry after backoff delay
|
||||||
final nextDelay = item.nextNodeSubmissionRetryDelay;
|
final nextDelay = item.nextNodeSubmissionRetryDelay;
|
||||||
final timeLeft = item.timeUntilAutoClose;
|
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
|
// 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
|
// Note: This will NEVER error out - will keep trying until 59-minute window expires
|
||||||
final nextDelay = item.nextChangesetCloseRetryDelay;
|
final nextDelay = item.nextChangesetCloseRetryDelay;
|
||||||
final timeLeft = item.timeUntilAutoClose;
|
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}');
|
debugPrint('[UploadQueue] Error: ${result.errorMessage}');
|
||||||
// No additional state change needed - attempt count was already updated above
|
// 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 '../app_state.dart';
|
||||||
import '../dev_config.dart';
|
import '../dev_config.dart';
|
||||||
import '../models/node_profile.dart';
|
import '../models/node_profile.dart';
|
||||||
import '../models/operator_profile.dart';
|
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
import '../services/map_data_provider.dart';
|
import '../services/map_data_provider.dart';
|
||||||
import '../services/node_data_manager.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() {
|
void _hideTutorial() {
|
||||||
if (mounted && _showTutorial) {
|
if (mounted && _showTutorial) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -107,7 +89,8 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async {
|
void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async {
|
||||||
// Check if user has seen the submission guide
|
// Check if user has seen the submission guide
|
||||||
final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide();
|
final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (!hasSeenGuide) {
|
if (!hasSeenGuide) {
|
||||||
// Show submission guide dialog first
|
// Show submission guide dialog first
|
||||||
final shouldProceed = await showDialog<bool>(
|
final shouldProceed = await showDialog<bool>(
|
||||||
@@ -115,13 +98,14 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => const SubmissionGuideDialog(),
|
builder: (context) => const SubmissionGuideDialog(),
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
// If user canceled the submission guide, don't proceed with submission
|
// If user canceled the submission guide, don't proceed with submission
|
||||||
if (shouldProceed != true) {
|
if (shouldProceed != true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now proceed with proximity check
|
// Now proceed with proximity check
|
||||||
_checkProximityOnly(context, appState, locService);
|
_checkProximityOnly(context, appState, locService);
|
||||||
}
|
}
|
||||||
@@ -402,11 +386,11 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
final locService = LocalizationService.instance;
|
final locService = LocalizationService.instance;
|
||||||
final appState = context.watch<AppState>();
|
final appState = context.watch<AppState>();
|
||||||
|
|
||||||
void _commit() {
|
void commit() {
|
||||||
_checkProximityAndCommit(context, appState, locService);
|
_checkProximityAndCommit(context, appState, locService);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cancel() {
|
void cancel() {
|
||||||
appState.cancelSession();
|
appState.cancelSession();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
@@ -442,11 +426,11 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
session.profile!.isSubmittable &&
|
session.profile!.isSubmittable &&
|
||||||
hasGoodCoverage;
|
hasGoodCoverage;
|
||||||
|
|
||||||
void _navigateToLogin() {
|
void navigateToLogin() {
|
||||||
Navigator.pushNamed(context, '/settings/osm-account');
|
Navigator.pushNamed(context, '/settings/osm-account');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openRefineTags() async {
|
void openRefineTags() async {
|
||||||
final result = await Navigator.push<RefineTagsResult?>(
|
final result = await Navigator.push<RefineTagsResult?>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -578,7 +562,7 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: OutlinedButton.icon(
|
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),
|
icon: const Icon(Icons.tune),
|
||||||
label: Text(locService.t('addNode.refineTags')),
|
label: Text(locService.t('addNode.refineTags')),
|
||||||
),
|
),
|
||||||
@@ -591,14 +575,14 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: _cancel,
|
onPressed: cancel,
|
||||||
child: Text(locService.cancel),
|
child: Text(locService.cancel),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
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')),
|
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
|
// No custom scheme or app launch failed - redirect to app store
|
||||||
|
if (!context.mounted) return;
|
||||||
await _redirectToAppStore(context, editor);
|
await _redirectToAppStore(context, editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class CameraIcon extends StatelessWidget {
|
|||||||
height: kNodeIconDiameter,
|
height: kNodeIconDiameter,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: _ringColor.withOpacity(kNodeDotOpacity),
|
color: _ringColor.withValues(alpha: kNodeDotOpacity),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _ringColor,
|
color: _ringColor,
|
||||||
width: getNodeRingThickness(context),
|
width: getNodeRingThickness(context),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/changelog_service.dart';
|
|
||||||
import '../services/version_service.dart';
|
import '../services/version_service.dart';
|
||||||
|
|
||||||
class ChangelogDialog extends StatelessWidget {
|
class ChangelogDialog extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -97,8 +96,8 @@ class _CompassIndicatorState extends State<CompassIndicator> {
|
|||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDisabled
|
color: isDisabled
|
||||||
? Colors.grey.withOpacity(0.8)
|
? Colors.grey.withValues(alpha: 0.8)
|
||||||
: Colors.white.withOpacity(0.95),
|
: Colors.white.withValues(alpha: 0.95),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isDisabled
|
color: isDisabled
|
||||||
@@ -108,7 +107,7 @@ class _CompassIndicatorState extends State<CompassIndicator> {
|
|||||||
),
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.25),
|
color: Colors.black.withValues(alpha: 0.25),
|
||||||
blurRadius: 6,
|
blurRadius: 6,
|
||||||
offset: const Offset(0, 3),
|
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 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
|
// Calculate maximum possible zoom based on tile count limit and tile provider max zoom
|
||||||
final maxPossibleZoom = _calculateMaxZoomForTileLimit(bounds, minZoom);
|
final maxPossibleZoom = _calculateMaxZoomForTileLimit(bounds, minZoom);
|
||||||
|
|
||||||
@@ -124,7 +123,6 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
final locService = LocalizationService.instance;
|
final locService = LocalizationService.instance;
|
||||||
final appState = context.watch<AppState>();
|
final appState = context.watch<AppState>();
|
||||||
final bounds = widget.controller.camera.visibleBounds;
|
final bounds = widget.controller.camera.visibleBounds;
|
||||||
final maxZoom = _zoom.toInt();
|
|
||||||
final isOfflineMode = appState.offlineMode;
|
final isOfflineMode = appState.offlineMode;
|
||||||
|
|
||||||
// Use the calculated max possible zoom instead of fixed span
|
// Use the calculated max possible zoom instead of fixed span
|
||||||
@@ -191,8 +189,8 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _tileCount! > kMaxReasonableTileCount
|
color: _tileCount! > kMaxReasonableTileCount
|
||||||
? Colors.orange.withOpacity(0.1)
|
? Colors.orange.withValues(alpha: 0.1)
|
||||||
: Colors.green.withOpacity(0.1),
|
: Colors.green.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -200,7 +198,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_tileCount! > kMaxReasonableTileCount
|
_tileCount! > kMaxReasonableTileCount
|
||||||
? 'Above recommended limit (Z${_maxPossibleZoom})'
|
? 'Above recommended limit (Z$_maxPossibleZoom)'
|
||||||
: locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]),
|
: locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -213,7 +211,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
_tileCount! > kMaxReasonableTileCount
|
_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()]),
|
: locService.t('download.withinTileLimit', params: [kMaxReasonableTileCount.toString()]),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
@@ -232,9 +230,9 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -266,8 +264,9 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
try {
|
try {
|
||||||
final id = DateTime.now().toIso8601String().replaceAll(':', '-');
|
final id = DateTime.now().toIso8601String().replaceAll(':', '-');
|
||||||
final appDocDir = await OfflineAreaService().getOfflineAreaDir();
|
final appDocDir = await OfflineAreaService().getOfflineAreaDir();
|
||||||
|
if (!context.mounted) return;
|
||||||
final dir = "${appDocDir.path}/$id";
|
final dir = "${appDocDir.path}/$id";
|
||||||
|
|
||||||
// Get current tile provider info
|
// Get current tile provider info
|
||||||
final appState = context.read<AppState>();
|
final appState = context.read<AppState>();
|
||||||
final selectedProvider = appState.selectedTileProvider;
|
final selectedProvider = appState.selectedTileProvider;
|
||||||
@@ -294,6 +293,7 @@ class _DownloadAreaDialogState extends State<DownloadAreaDialog> {
|
|||||||
builder: (context) => const DownloadStartedDialog(),
|
builder: (context) => const DownloadStartedDialog(),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -7,14 +7,11 @@ import 'package:flutter_map/flutter_map.dart';
|
|||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
import '../dev_config.dart';
|
import '../dev_config.dart';
|
||||||
import '../models/node_profile.dart';
|
import '../models/node_profile.dart';
|
||||||
import '../models/operator_profile.dart';
|
|
||||||
import '../models/pending_upload.dart';
|
import '../models/pending_upload.dart';
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
import '../services/map_data_provider.dart';
|
import '../services/map_data_provider.dart';
|
||||||
import '../services/node_data_manager.dart';
|
import '../services/node_data_manager.dart';
|
||||||
import '../services/changelog_service.dart';
|
import '../services/changelog_service.dart';
|
||||||
import '../state/settings_state.dart';
|
|
||||||
import '../state/session_state.dart';
|
|
||||||
import 'refine_tags_sheet.dart';
|
import 'refine_tags_sheet.dart';
|
||||||
import 'advanced_edit_options_sheet.dart';
|
import 'advanced_edit_options_sheet.dart';
|
||||||
import 'proximity_warning_dialog.dart';
|
import 'proximity_warning_dialog.dart';
|
||||||
@@ -95,7 +92,8 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async {
|
void _checkSubmissionGuideAndProceed(BuildContext context, AppState appState, LocalizationService locService) async {
|
||||||
// Check if user has seen the submission guide
|
// Check if user has seen the submission guide
|
||||||
final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide();
|
final hasSeenGuide = await ChangelogService().hasSeenSubmissionGuide();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (!hasSeenGuide) {
|
if (!hasSeenGuide) {
|
||||||
// Show submission guide dialog first
|
// Show submission guide dialog first
|
||||||
final shouldProceed = await showDialog<bool>(
|
final shouldProceed = await showDialog<bool>(
|
||||||
@@ -103,13 +101,14 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => const SubmissionGuideDialog(),
|
builder: (context) => const SubmissionGuideDialog(),
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
// If user canceled the submission guide, don't proceed with submission
|
// If user canceled the submission guide, don't proceed with submission
|
||||||
if (shouldProceed != true) {
|
if (shouldProceed != true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now proceed with proximity check
|
// Now proceed with proximity check
|
||||||
_checkProximityOnly(context, appState, locService);
|
_checkProximityOnly(context, appState, locService);
|
||||||
}
|
}
|
||||||
@@ -435,25 +434,23 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
final locService = LocalizationService.instance;
|
final locService = LocalizationService.instance;
|
||||||
final appState = context.watch<AppState>();
|
final appState = context.watch<AppState>();
|
||||||
|
|
||||||
void _commit() {
|
void commit() {
|
||||||
// Check if there are any actual changes to submit
|
// Check if there are any actual changes to submit
|
||||||
if (!_hasActualChanges(widget.session)) {
|
if (!_hasActualChanges(widget.session)) {
|
||||||
_showNoChangesDialog(context, locService);
|
_showNoChangesDialog(context, locService);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkProximityAndCommit(context, appState, locService);
|
_checkProximityAndCommit(context, appState, locService);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cancel() {
|
void cancel() {
|
||||||
appState.cancelEditSession();
|
appState.cancelEditSession();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
final session = widget.session;
|
final session = widget.session;
|
||||||
final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList();
|
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
|
// Check if we have good cache coverage around the node position
|
||||||
bool hasGoodCoverage = true;
|
bool hasGoodCoverage = true;
|
||||||
final nodeCoord = session.originalNode.coord;
|
final nodeCoord = session.originalNode.coord;
|
||||||
@@ -481,11 +478,11 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
session.profile!.isSubmittable &&
|
session.profile!.isSubmittable &&
|
||||||
hasGoodCoverage;
|
hasGoodCoverage;
|
||||||
|
|
||||||
void _navigateToLogin() {
|
void navigateToLogin() {
|
||||||
Navigator.pushNamed(context, '/settings/osm-account');
|
Navigator.pushNamed(context, '/settings/osm-account');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openRefineTags() async {
|
void openRefineTags() async {
|
||||||
final result = await Navigator.push<RefineTagsResult?>(
|
final result = await Navigator.push<RefineTagsResult?>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -694,7 +691,7 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: OutlinedButton.icon(
|
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),
|
icon: const Icon(Icons.tune),
|
||||||
label: Text(locService.t('editNode.refineTags')),
|
label: Text(locService.t('editNode.refineTags')),
|
||||||
),
|
),
|
||||||
@@ -707,14 +704,14 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: _cancel,
|
onPressed: cancel,
|
||||||
child: Text(locService.cancel),
|
child: Text(locService.cancel),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
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')),
|
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 '../../app_state.dart';
|
||||||
import '../../dev_config.dart';
|
import '../../dev_config.dart';
|
||||||
import '../../models/osm_node.dart';
|
import '../../models/osm_node.dart';
|
||||||
import '../../models/direction_fov.dart';
|
|
||||||
|
|
||||||
/// Helper class to build direction cone polygons for cameras
|
/// Helper class to build direction cone polygons for cameras
|
||||||
class DirectionConesBuilder {
|
class DirectionConesBuilder {
|
||||||
@@ -113,11 +112,6 @@ class DirectionConesBuilder {
|
|||||||
node.coord.longitude.abs() <= 180;
|
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
|
/// Build cone with variable FOV width - new method for range notation support
|
||||||
static Polygon _buildConeWithFov(
|
static Polygon _buildConeWithFov(
|
||||||
LatLng origin,
|
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
|
/// Internal cone building method that handles the actual rendering
|
||||||
static Polygon _buildConeInternal({
|
static Polygon _buildConeInternal({
|
||||||
required LatLng origin,
|
required LatLng origin,
|
||||||
@@ -231,7 +203,7 @@ class DirectionConesBuilder {
|
|||||||
|
|
||||||
return Polygon(
|
return Polygon(
|
||||||
points: points,
|
points: points,
|
||||||
color: kDirectionConeColor.withOpacity(opacity),
|
color: kDirectionConeColor.withValues(alpha: opacity),
|
||||||
borderColor: kDirectionConeColor,
|
borderColor: kDirectionConeColor,
|
||||||
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
||||||
);
|
);
|
||||||
@@ -279,7 +251,7 @@ class DirectionConesBuilder {
|
|||||||
|
|
||||||
return Polygon(
|
return Polygon(
|
||||||
points: points,
|
points: points,
|
||||||
color: kDirectionConeColor.withOpacity(opacity),
|
color: kDirectionConeColor.withValues(alpha: opacity),
|
||||||
borderColor: kDirectionConeColor,
|
borderColor: kDirectionConeColor,
|
||||||
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
borderStrokeWidth: getDirectionConeBorderWidth(context),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class _LayerSelectorDialogState extends State<_LayerSelectorDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
import '../../models/osm_node.dart';
|
import '../../models/osm_node.dart';
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../app_state.dart';
|
import '../../app_state.dart';
|
||||||
import '../../dev_config.dart';
|
import '../../dev_config.dart';
|
||||||
import '../../services/localization_service.dart';
|
import '../../services/localization_service.dart';
|
||||||
import '../camera_icon.dart';
|
|
||||||
import '../compass_indicator.dart';
|
import '../compass_indicator.dart';
|
||||||
import 'layer_selector_button.dart';
|
import 'layer_selector_button.dart';
|
||||||
|
|
||||||
@@ -64,8 +62,8 @@ class MapOverlays extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: uploadMode == UploadMode.sandbox
|
color: uploadMode == UploadMode.sandbox
|
||||||
? Colors.orange.withOpacity(0.90)
|
? Colors.orange.withValues(alpha: 0.90)
|
||||||
: Colors.deepPurple.withOpacity(0.80),
|
: Colors.deepPurple.withValues(alpha: 0.80),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
BoxShadow(color: Colors.black26, blurRadius: 5, offset: Offset(0,2)),
|
BoxShadow(color: Colors.black26, blurRadius: 5, offset: Offset(0,2)),
|
||||||
@@ -98,7 +96,7 @@ class MapOverlays extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.52),
|
color: Colors.black.withValues(alpha: 0.52),
|
||||||
borderRadius: BorderRadius.circular(7),
|
borderRadius: BorderRadius.circular(7),
|
||||||
),
|
),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
@@ -131,7 +129,7 @@ class MapOverlays extends StatelessWidget {
|
|||||||
onTap: () => _showAttributionDialog(context, attribution!),
|
onTap: () => _showAttributionDialog(context, attribution!),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 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/osm_node.dart';
|
||||||
import '../../models/suspected_location.dart';
|
import '../../models/suspected_location.dart';
|
||||||
import '../../app_state.dart';
|
import '../../app_state.dart';
|
||||||
import '../../state/session_state.dart';
|
|
||||||
import '../../dev_config.dart';
|
import '../../dev_config.dart';
|
||||||
import '../camera_icon.dart';
|
import '../camera_icon.dart';
|
||||||
import '../provisional_pin.dart';
|
import '../provisional_pin.dart';
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class NodeMapMarker extends StatefulWidget {
|
|||||||
required this.mapController,
|
required this.mapController,
|
||||||
this.onNodeTap,
|
this.onNodeTap,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NodeMapMarker> createState() => _NodeMapMarkerState();
|
State<NodeMapMarker> createState() => _NodeMapMarkerState();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
import 'package:flutter_map_animations/flutter_map_animations.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
import '../../models/node_profile.dart';
|
import '../../models/node_profile.dart';
|
||||||
import '../../app_state.dart' show UploadMode;
|
import '../../app_state.dart' show UploadMode;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:latlong2/latlong.dart';
|
|||||||
|
|
||||||
import '../../models/osm_node.dart';
|
import '../../models/osm_node.dart';
|
||||||
import '../../app_state.dart';
|
import '../../app_state.dart';
|
||||||
import '../../state/session_state.dart';
|
|
||||||
import '../../dev_config.dart';
|
import '../../dev_config.dart';
|
||||||
import 'direction_cones.dart';
|
import 'direction_cones.dart';
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ class OverlayLayerBuilder {
|
|||||||
overlays.add(
|
overlays.add(
|
||||||
Polygon(
|
Polygon(
|
||||||
points: selectedLocation.bounds,
|
points: selectedLocation.bounds,
|
||||||
color: Colors.orange.withOpacity(0.3),
|
color: Colors.orange.withValues(alpha: 0.3),
|
||||||
borderColor: Colors.orange,
|
borderColor: Colors.orange,
|
||||||
borderStrokeWidth: 2.0,
|
borderStrokeWidth: 2.0,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class SuspectedLocationMapMarker extends StatefulWidget {
|
|||||||
required this.mapController,
|
required this.mapController,
|
||||||
this.onLocationTap,
|
this.onLocationTap,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SuspectedLocationMapMarker> createState() => _SuspectedLocationMapMarkerState();
|
State<SuspectedLocationMapMarker> createState() => _SuspectedLocationMapMarkerState();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
import '../../models/tile_provider.dart' as models;
|
import '../../models/tile_provider.dart' as models;
|
||||||
import '../../services/deflock_tile_provider.dart';
|
import '../../services/deflock_tile_provider.dart';
|
||||||
@@ -93,7 +92,7 @@ class TileLayerManager {
|
|||||||
return TileLayer(
|
return TileLayer(
|
||||||
urlTemplate: urlTemplate, // Critical for cache key generation
|
urlTemplate: urlTemplate, // Critical for cache key generation
|
||||||
userAgentPackageName: 'me.deflock.deflockapp',
|
userAgentPackageName: 'me.deflock.deflockapp',
|
||||||
maxZoom: selectedTileType?.maxZoom?.toDouble() ?? 18.0,
|
maxZoom: selectedTileType?.maxZoom.toDouble() ?? 18.0,
|
||||||
tileProvider: _tileProvider!,
|
tileProvider: _tileProvider!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
@@ -182,9 +181,9 @@ class NavigationSheet extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.amber.withOpacity(0.1),
|
color: Colors.amber.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -212,9 +211,9 @@ class NavigationSheet extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
|
||||||
|
|
||||||
import '../services/map_data_provider.dart';
|
|
||||||
import '../services/node_data_manager.dart';
|
import '../services/node_data_manager.dart';
|
||||||
import '../services/node_spatial_cache.dart';
|
import '../services/node_spatial_cache.dart';
|
||||||
import '../services/network_status.dart';
|
|
||||||
import '../models/node_profile.dart';
|
import '../models/node_profile.dart';
|
||||||
import '../models/osm_node.dart';
|
import '../models/osm_node.dart';
|
||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
final isRealOSMNode = !node.tags.containsKey('_pending_upload') &&
|
final isRealOSMNode = !node.tags.containsKey('_pending_upload') &&
|
||||||
node.id > 0; // Real OSM nodes have positive IDs
|
node.id > 0; // Real OSM nodes have positive IDs
|
||||||
|
|
||||||
void _openEditSheet() {
|
void openEditSheet() {
|
||||||
// Check if node limit is active and warn user
|
// Check if node limit is active and warn user
|
||||||
if (isNodeLimitActive) {
|
if (isNodeLimitActive) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -64,7 +64,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteNode() async {
|
void deleteNode() async {
|
||||||
final shouldDelete = await showDialog<bool>(
|
final shouldDelete = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext 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}';
|
final url = 'https://www.openstreetmap.org/node/${node.id}';
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
@@ -117,7 +117,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openAdvancedEdit() {
|
void openAdvancedEdit() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
@@ -177,7 +177,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
text: e.value,
|
text: e.value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
@@ -201,7 +201,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (isRealOSMNode) ...[
|
if (isRealOSMNode) ...[
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => _viewOnOSM(),
|
onPressed: () => viewOnOSM(),
|
||||||
icon: const Icon(Icons.open_in_new, size: 16),
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
label: Text(locService.t('actions.viewOnOSM')),
|
label: Text(locService.t('actions.viewOnOSM')),
|
||||||
),
|
),
|
||||||
@@ -209,7 +209,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
if (isEditable) ...[
|
if (isEditable) ...[
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: _openAdvancedEdit,
|
onPressed: openAdvancedEdit,
|
||||||
icon: const Icon(Icons.open_in_new, size: 18),
|
icon: const Icon(Icons.open_in_new, size: 18),
|
||||||
label: Text(locService.t('actions.advanced')),
|
label: Text(locService.t('actions.advanced')),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
@@ -226,7 +226,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (isEditable) ...[
|
if (isEditable) ...[
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: _openEditSheet,
|
onPressed: openEditSheet,
|
||||||
icon: const Icon(Icons.edit, size: 18),
|
icon: const Icon(Icons.edit, size: 18),
|
||||||
label: Text(locService.edit),
|
label: Text(locService.edit),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@@ -235,7 +235,7 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: node.isConstrained ? null : _deleteNode,
|
onPressed: node.isConstrained ? null : deleteNode,
|
||||||
icon: const Icon(Icons.delete, size: 18),
|
icon: const Icon(Icons.delete, size: 18),
|
||||||
label: Text(locService.t('actions.delete')),
|
label: Text(locService.t('actions.delete')),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ class NuclearResetDialog extends StatelessWidget {
|
|||||||
final String errorReport;
|
final String errorReport;
|
||||||
|
|
||||||
const NuclearResetDialog({
|
const NuclearResetDialog({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.errorReport,
|
required this.errorReport,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return PopScope(
|
||||||
// Prevent back button from closing dialog
|
// Prevent back button from closing dialog
|
||||||
onWillPop: () async => false,
|
canPop: false,
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
title: const Row(
|
title: const Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -96,7 +96,8 @@ class NuclearResetDialog extends StatelessWidget {
|
|||||||
|
|
||||||
// Clear all app data
|
// Clear all app data
|
||||||
await NuclearResetService.clearEverything();
|
await NuclearResetService.clearEverything();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
// Show non-dismissible dialog
|
// Show non-dismissible dialog
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class PositioningTutorialOverlay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.3), // Semi-transparent overlay
|
color: Colors.black.withValues(alpha: 0.3), // Semi-transparent overlay
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -73,7 +73,7 @@ class PositioningTutorialOverlay extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
locService.t('positioningTutorial.hint'),
|
locService.t('positioningTutorial.hint'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withOpacity(0.8),
|
color: Colors.white.withValues(alpha: 0.8),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class LocationPin extends StatelessWidget {
|
|||||||
width: size * 0.4,
|
width: size * 0.4,
|
||||||
height: size * 0.2,
|
height: size * 0.2,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.black.withOpacity(0.3),
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
borderRadius: BorderRadius.circular(size * 0.1),
|
borderRadius: BorderRadius.circular(size * 0.1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -64,7 +64,7 @@ class LocationPin extends StatelessWidget {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _pinColor.withOpacity(0.8),
|
color: _pinColor.withValues(alpha: 0.8),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class _ProximityAlertBannerState extends State<ProximityAlertBanner>
|
|||||||
color: Colors.red.shade600,
|
color: Colors.red.shade600,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.3),
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
import '../models/osm_node.dart';
|
import '../models/osm_node.dart';
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class ReauthMessagesDialog extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class _LocationSearchBarState extends State<LocationSearchBar> {
|
|||||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
|
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Theme.of(context).shadowColor.withOpacity(0.2),
|
color: Theme.of(context).shadowColor.withValues(alpha: 0.2),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -152,7 +152,7 @@ class _LocationSearchBarState extends State<LocationSearchBar> {
|
|||||||
: null,
|
: null,
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () => _onResultTap(result),
|
onTap: () => _onResultTap(result),
|
||||||
)).toList(),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -184,7 +184,7 @@ class _LocationSearchBarState extends State<LocationSearchBar> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Theme.of(context).shadowColor.withOpacity(0.2),
|
color: Theme.of(context).shadowColor.withValues(alpha: 0.2),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ class _SubmissionGuideDialogState extends State<SubmissionGuideDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue.withOpacity(0.1),
|
color: Colors.blue.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Text(
|
||||||
locService.t('submissionGuide.bestPractices'),
|
locService.t('submissionGuide.bestPractices'),
|
||||||
@@ -171,10 +171,10 @@ class _SubmissionGuideDialogState extends State<SubmissionGuideDialog> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../models/suspected_location.dart';
|
import '../models/suspected_location.dart';
|
||||||
import '../app_state.dart';
|
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
import '../dev_config.dart';
|
import '../dev_config.dart';
|
||||||
|
|
||||||
@@ -17,7 +15,6 @@ class SuspectedLocationSheet extends StatelessWidget {
|
|||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: LocalizationService.instance,
|
animation: LocalizationService.instance,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
final appState = context.watch<AppState>();
|
|
||||||
final locService = LocalizationService.instance;
|
final locService = LocalizationService.instance;
|
||||||
|
|
||||||
// Get all fields except location and ticket_no
|
// Get all fields except location and ticket_no
|
||||||
@@ -97,7 +94,7 @@ class SuspectedLocationSheet extends StatelessWidget {
|
|||||||
: Text(
|
: Text(
|
||||||
e.value,
|
e.value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
@@ -131,7 +128,7 @@ class SuspectedLocationSheet extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'${location.centroid.latitude.toStringAsFixed(6)}, ${location.centroid.longitude.toStringAsFixed(6)}',
|
'${location.centroid.latitude.toStringAsFixed(6)}, ${location.centroid.longitude.toStringAsFixed(6)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ class _WelcomeDialogState extends State<WelcomeDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
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(
|
child: Text(
|
||||||
locService.t('welcome.firsthandKnowledge'),
|
locService.t('welcome.firsthandKnowledge'),
|
||||||
@@ -171,10 +171,10 @@ class _WelcomeDialogState extends State<WelcomeDialog> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -222,6 +222,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
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:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -496,6 +504,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
lists:
|
lists:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -33,15 +33,18 @@ dependencies:
|
|||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.2
|
||||||
sqflite: ^2.4.1
|
sqflite: ^2.4.1
|
||||||
path: ^1.8.3
|
path: ^1.8.3
|
||||||
|
path_provider: ^2.1.0
|
||||||
uuid: ^4.0.0
|
uuid: ^4.0.0
|
||||||
package_info_plus: ^8.0.0
|
package_info_plus: ^8.0.0
|
||||||
csv: ^6.0.0
|
csv: ^6.0.0
|
||||||
|
collection: ^1.18.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
mocktail: ^1.0.4
|
mocktail: ^1.0.4
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: ^0.14.4
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
flutter_native_splash: ^2.4.6
|
flutter_native_splash: ^2.4.6
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Test 0-3 range
|
// Test 0-3 range
|
||||||
final url_0_3_a = tileType0_3.getTileUrl(1, 0, 0);
|
final url03A = tileType0_3.getTileUrl(1, 0, 0);
|
||||||
final url_0_3_b = tileType0_3.getTileUrl(1, 3, 0);
|
final url03B = tileType0_3.getTileUrl(1, 3, 0);
|
||||||
expect(url_0_3_a, contains('s0.example.com'));
|
expect(url03A, contains('s0.example.com'));
|
||||||
expect(url_0_3_b, contains('s3.example.com'));
|
expect(url03B, contains('s3.example.com'));
|
||||||
|
|
||||||
// Test 1-4 range
|
// Test 1-4 range
|
||||||
final url_1_4_a = tileType1_4.getTileUrl(1, 0, 0);
|
final url14A = tileType1_4.getTileUrl(1, 0, 0);
|
||||||
final url_1_4_b = tileType1_4.getTileUrl(1, 3, 0);
|
final url14B = tileType1_4.getTileUrl(1, 3, 0);
|
||||||
expect(url_1_4_a, contains('s1.example.com'));
|
expect(url14A, contains('s1.example.com'));
|
||||||
expect(url_1_4_b, contains('s4.example.com'));
|
expect(url14B, contains('s4.example.com'));
|
||||||
|
|
||||||
// Test consistency
|
// Test consistency
|
||||||
final url1 = tileType0_3.getTileUrl(1, 2, 3);
|
final url1 = tileType0_3.getTileUrl(1, 2, 3);
|
||||||
|
|||||||
Reference in New Issue
Block a user