From 999a918062ecd0c67ebedeadccddea5c3c7c41b1 Mon Sep 17 00:00:00 2001 From: stopflock Date: Fri, 21 Nov 2025 12:49:35 -0600 Subject: [PATCH] doesnt really work, probably abandon, saving because sunk cost --- lib/dev_config.dart | 162 +++++++++++++----- lib/main.dart | 2 + lib/screens/developer_settings_screen.dart | 127 ++++++++++++++ lib/screens/home_screen.dart | 4 +- lib/screens/osm_account_screen.dart | 2 +- lib/screens/settings_screen.dart | 47 ++++- lib/screens/tile_provider_editor_screen.dart | 8 +- .../nodes_from_overpass.dart | 2 +- .../tiles_from_remote.dart | 10 +- lib/services/prefetch_area_service.dart | 8 +- lib/services/proximity_alert_service.dart | 2 +- lib/services/suspected_location_service.dart | 4 +- lib/services/tile_preview_service.dart | 2 +- lib/services/uploader.dart | 2 +- lib/state/settings_state.dart | 14 +- lib/widgets/add_node_sheet.dart | 6 +- lib/widgets/camera_icon.dart | 18 +- lib/widgets/download_area_dialog.dart | 20 +-- lib/widgets/edit_node_sheet.dart | 14 +- lib/widgets/map/camera_markers.dart | 8 +- .../map/camera_refresh_controller.dart | 4 +- lib/widgets/map/direction_cones.dart | 14 +- lib/widgets/map/gps_controller.dart | 10 +- lib/widgets/map/map_overlays.dart | 6 +- .../map/suspected_location_markers.dart | 4 +- lib/widgets/map_view.dart | 30 ++-- scripts/update_dev_config.py | 150 ++++++++++++++++ scripts/update_dev_config_references.sh | 12 ++ 28 files changed, 543 insertions(+), 149 deletions(-) create mode 100644 lib/screens/developer_settings_screen.dart create mode 100644 scripts/update_dev_config.py create mode 100755 scripts/update_dev_config_references.sh diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 76a6e05..08ffdea 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -2,36 +2,125 @@ import 'package:flutter/material.dart'; /// Developer/build-time configuration for global/non-user-tunable constants. +/// Single source of truth with typed maps for settings auto-generation. -// Fallback tile storage estimate (KB per tile), used when no preview tile data is available -const double kFallbackTileEstimateKb = 25.0; +// Typed configuration maps - single definition of each constant +const Map _boolConfig = { + 'kEnableDevelopmentModes': true, + 'kEnableNodeEdits': true, + 'kEnableNodeExtraction': false, +}; -// Preview tile coordinates for tile provider previews and size estimates -const int kPreviewTileZoom = 18; -const int kPreviewTileY = 101300; -const int kPreviewTileX = 41904; +const Map _intConfig = { + 'kPreviewTileZoom': 18, + 'kPreviewTileY': 101300, + 'kPreviewTileX': 41904, + 'kNodeMinZoomLevel': 10, + 'kOsmApiMinZoomLevel': 13, + 'kPreFetchZoomLevel': 10, + 'kMaxPreFetchSplitDepth': 3, + 'kDataRefreshIntervalSeconds': 60, + 'kProximityAlertDefaultDistance': 400, + 'kProximityAlertMinDistance': 50, + 'kProximityAlertMaxDistance': 1600, + 'kTileFetchMaxAttempts': 16, + 'kTileFetchInitialDelayMs': 500, + 'kTileFetchMaxDelayMs': 10000, + 'kTileFetchRandomJitterMs': 250, + 'kMaxUserDownloadZoomSpan': 7, + 'kMaxReasonableTileCount': 20000, + 'kAbsoluteMaxTileCount': 50000, + 'kAbsoluteMaxZoom': 23, +}; -// Direction cone for map view -const double kDirectionConeHalfAngle = 35.0; // degrees -const double kDirectionConeBaseLength = 5; // multiplier -const Color kDirectionConeColor = Color(0xD0767474); // FOV cone color -const double kDirectionConeOpacity = 0.5; // Fill opacity for FOV cones -// Base values for thickness - use helper functions below for pixel-ratio scaling -const double _kDirectionConeBorderWidthBase = 1.6; +const Map _doubleConfig = { + 'kFallbackTileEstimateKb': 25.0, + 'kDirectionConeHalfAngle': 35.0, + 'kDirectionConeBaseLength': 5.0, + 'kDirectionConeOpacity': 0.5, + '_kDirectionConeBorderWidthBase': 1.6, + 'kBottomButtonBarOffset': 4.0, + 'kButtonBarHeight': 60.0, + 'kAttributionSpacingAboveButtonBar': 10.0, + 'kZoomIndicatorSpacingAboveButtonBar': 40.0, + 'kScaleBarSpacingAboveButtonBar': 70.0, + 'kZoomControlsSpacingAboveButtonBar': 20.0, + 'kPreFetchAreaExpansionMultiplier': 3.0, + 'kMinSpeedForRotationMps': 1.0, + 'kMaxTagListHeightRatioPortrait': 0.3, + 'kMaxTagListHeightRatioLandscape': 0.2, + 'kNodeDoubleTapZoomDelta': 1.0, + 'kScrollWheelVelocity': 0.01, + 'kPinchZoomThreshold': 0.2, + 'kPinchMoveThreshold': 30.0, + 'kRotationThreshold': 6.0, + 'kNodeIconDiameter': 18.0, + '_kNodeRingThicknessBase': 2.5, + 'kNodeDotOpacity': 0.3, + 'kDirectionButtonMinWidth': 22.0, + 'kDirectionButtonMinHeight': 32.0, + 'kTileFetchBackoffMultiplier': 1.5, +}; -// Bottom button bar positioning -const double kBottomButtonBarOffset = 4.0; // Distance from screen bottom (above safe area) -const double kButtonBarHeight = 60.0; // Button height (48) + padding (12) +const Map _stringConfig = { + 'kClientName': 'DeFlock', // Read-only in settings + 'kSuspectedLocationsCsvUrl': 'https://stopflock.com/app/flock_utilities_mini_latest.csv', +}; -// Map overlay spacing relative to button bar top -const double kAttributionSpacingAboveButtonBar = 10.0; // Attribution above button bar top -const double kZoomIndicatorSpacingAboveButtonBar = 40.0; // Zoom indicator above button bar top -const double kScaleBarSpacingAboveButtonBar = 70.0; // Scale bar above button bar top -const double kZoomControlsSpacingAboveButtonBar = 20.0; // Zoom controls above button bar top +const Map _colorConfig = { + 'kDirectionConeColor': Color(0xD0767474), + 'kNodeRingColorReal': Color(0xFF3036F0), + 'kNodeRingColorMock': Color(0xD0FFFFFF), + 'kNodeRingColorPending': Color(0xD09C27B0), + 'kNodeRingColorEditing': Color(0xD0FF9800), + 'kNodeRingColorPendingEdit': Color(0xD0757575), + 'kNodeRingColorPendingDeletion': Color(0xC0F44336), +}; + +const Map _durationConfig = { + 'kMarkerTapTimeout': Duration(milliseconds: 250), + 'kDebounceCameraRefresh': Duration(milliseconds: 500), + 'kFollowMeAnimationDuration': Duration(milliseconds: 600), + 'kProximityAlertCooldown': Duration(minutes: 10), +}; + +// Dynamic accessor class +class _DevConfig { + @override + dynamic noSuchMethod(Invocation invocation) { + final name = invocation.memberName.toString().replaceAll('Symbol("', '').replaceAll('")', ''); + + // Check each typed map + if (_boolConfig.containsKey(name)) return _boolConfig[name]; + if (_intConfig.containsKey(name)) return _intConfig[name]; + if (_doubleConfig.containsKey(name)) return _doubleConfig[name]; + if (_stringConfig.containsKey(name)) return _stringConfig[name]; + if (_colorConfig.containsKey(name)) return _colorConfig[name]; + if (_durationConfig.containsKey(name)) return _durationConfig[name]; + + throw NoSuchMethodError.withInvocation(this, invocation); + } +} + +// Global accessor +final dynamic dev = _DevConfig(); + +// For settings page - combine all maps +Map get devConfigForSettings => { + ..._boolConfig, + ..._intConfig, + ..._doubleConfig, + ..._stringConfig, + ..._colorConfig, + ..._durationConfig, +}; + +// Computed constants +bool get kEnableNavigationFeatures => dev.kEnableDevelopmentModes; // Helper to calculate bottom position relative to button bar double bottomPositionFromButtonBar(double spacingAboveButtonBar, double safeAreaBottom) { - return safeAreaBottom + kBottomButtonBarOffset + kButtonBarHeight + spacingAboveButtonBar; + return safeAreaBottom + dev.kBottomButtonBarOffset + dev.kButtonBarHeight + spacingAboveButtonBar; } // Helper to get left positioning that accounts for safe area (for landscape mode) @@ -49,28 +138,9 @@ double topPositionWithSafeArea(double baseTop, EdgeInsets safeArea) { return baseTop + safeArea.top; } -// Client name for OSM uploads ("created_by" tag) -const String kClientName = 'DeFlock'; -// Note: Version is now dynamically retrieved from VersionService - -// Suspected locations CSV URL -const String kSuspectedLocationsCsvUrl = 'https://stopflock.com/app/flock_utilities_mini_latest.csv'; - -// Development/testing features - set to false for production builds -const bool kEnableDevelopmentModes = true; // Set to false to hide sandbox/simulate modes and force production mode - -// Navigation features - set to false to hide navigation UI elements while in development -const bool kEnableNavigationFeatures = kEnableDevelopmentModes; // Hide navigation until fully implemented - -// Node editing features - set to false to temporarily disable editing -const bool kEnableNodeEdits = true; // Set to false to temporarily disable node editing - -// Node extraction features - set to false to hide extract functionality for constrained nodes -const bool kEnableNodeExtraction = false; // Set to true to enable extract from way/relation feature (WIP) - /// Navigation availability: only dev builds, and only when online bool enableNavigationFeatures({required bool offlineMode}) { - if (!kEnableDevelopmentModes) { + if (!dev.kEnableDevelopmentModes) { return false; // Release builds: never allow navigation } else { return !offlineMode; // Dev builds: only when online @@ -151,11 +221,11 @@ const double kDirectionButtonMinHeight = 32.0; // Helper functions for pixel-ratio scaling double getDirectionConeBorderWidth(BuildContext context) { -// return _kDirectionConeBorderWidthBase * MediaQuery.of(context).devicePixelRatio; - return _kDirectionConeBorderWidthBase; +// return dev._kDirectionConeBorderWidthBase * MediaQuery.of(context).devicePixelRatio; + return dev._kDirectionConeBorderWidthBase; } double getNodeRingThickness(BuildContext context) { -// return _kNodeRingThicknessBase * MediaQuery.of(context).devicePixelRatio; - return _kNodeRingThicknessBase; +// return dev._kNodeRingThicknessBase * MediaQuery.of(context).devicePixelRatio; + return dev._kNodeRingThicknessBase; } diff --git a/lib/main.dart b/lib/main.dart index 0adf3a0..77a8272 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'screens/profiles_settings_screen.dart'; import 'screens/navigation_settings_screen.dart'; import 'screens/offline_settings_screen.dart'; import 'screens/advanced_settings_screen.dart'; +import 'screens/developer_settings_screen.dart'; import 'screens/language_settings_screen.dart'; import 'screens/about_screen.dart'; import 'screens/release_notes_screen.dart'; @@ -77,6 +78,7 @@ class DeFlockApp extends StatelessWidget { '/settings/navigation': (context) => const NavigationSettingsScreen(), '/settings/offline': (context) => const OfflineSettingsScreen(), '/settings/advanced': (context) => const AdvancedSettingsScreen(), + '/settings/developer': (context) => const DeveloperSettingsScreen(), '/settings/language': (context) => const LanguageSettingsScreen(), '/settings/about': (context) => const AboutScreen(), '/settings/release-notes': (context) => const ReleaseNotesScreen(), diff --git a/lib/screens/developer_settings_screen.dart b/lib/screens/developer_settings_screen.dart new file mode 100644 index 0000000..711b3d7 --- /dev/null +++ b/lib/screens/developer_settings_screen.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../dev_config.dart'; + +class DeveloperSettingsScreen extends StatefulWidget { + const DeveloperSettingsScreen({super.key}); + + @override + State createState() => _DeveloperSettingsScreenState(); +} + +class _DeveloperSettingsScreenState extends State { + final Map _controllers = {}; + final Map _overrides = {}; + + @override + void initState() { + super.initState(); + _initializeControllers(); + } + + @override + void dispose() { + for (final controller in _controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + void _initializeControllers() { + for (final entry in devConfigForSettings.entries) { + if (entry.value is String) { + _controllers[entry.key] = TextEditingController(text: entry.value); + } else if (entry.value is int) { + _controllers[entry.key] = TextEditingController(text: entry.value.toString()); + } else if (entry.value is double) { + _controllers[entry.key] = TextEditingController(text: entry.value.toString()); + } else if (entry.value is Color) { + final color = entry.value as Color; + final hex = color.value.toRadixString(16).padLeft(8, '0').toUpperCase(); + _controllers[entry.key] = TextEditingController(text: hex); + } else if (entry.value is Duration) { + final duration = entry.value as Duration; + _controllers[entry.key] = TextEditingController(text: duration.inMilliseconds.toString()); + } + } + } + + void _saveAndRestart() { + // For now, just show a dialog - actual restart would require platform channels + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Restart Required'), + content: const Text('Changes saved. Please restart the app to apply new settings.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } + + Widget _buildSettingWidget(String key, dynamic defaultValue) { + if (key == 'kClientName') { + // Special read-only case + return ListTile( + title: Text(key), + subtitle: Text(defaultValue.toString()), + trailing: const Text('READ ONLY'), + ); + } + + if (defaultValue is bool) { + return SwitchListTile( + title: Text(key), + value: _overrides[key] ?? defaultValue, + onChanged: (value) { + setState(() { + _overrides[key] = value; + }); + }, + ); + } else if (defaultValue is int || defaultValue is double || defaultValue is String || + defaultValue is Color || defaultValue is Duration) { + return ListTile( + title: Text(key), + subtitle: TextField( + controller: _controllers[key], + keyboardType: defaultValue is int || defaultValue is double + ? const TextInputType.numberWithOptions(signed: true, decimal: true) + : TextInputType.text, + textInputAction: TextInputAction.done, + onChanged: (value) { + // Store the string value for now - actual parsing would happen on save + _overrides[key] = value; + }, + ), + ); + } + + return const SizedBox.shrink(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Developer Settings'), + actions: [ + TextButton( + onPressed: _saveAndRestart, + child: const Text('SAVE', style: TextStyle(color: Colors.white)), + ), + ], + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: devConfigForSettings.entries + .map((entry) => _buildSettingWidget(entry.key, entry.value)) + .toList(), + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index b81fde6..b906764 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -713,7 +713,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { final safeArea = MediaQuery.of(context).padding; return Padding( padding: EdgeInsets.only( - bottom: safeArea.bottom + kBottomButtonBarOffset, + bottom: safeArea.bottom + dev.kBottomButtonBarOffset, left: leftPositionWithSafeArea(8, safeArea), right: rightPositionWithSafeArea(8, safeArea), ), @@ -731,7 +731,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { ) ], ), - margin: EdgeInsets.only(bottom: kBottomButtonBarOffset), + margin: EdgeInsets.only(bottom: dev.kBottomButtonBarOffset), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Row( children: [ diff --git a/lib/screens/osm_account_screen.dart b/lib/screens/osm_account_screen.dart index 8a9c3d7..f54aca3 100644 --- a/lib/screens/osm_account_screen.dart +++ b/lib/screens/osm_account_screen.dart @@ -117,7 +117,7 @@ class OSMAccountScreen extends StatelessWidget { const SizedBox(height: 16), // Upload Mode Section (only show in development builds) - if (kEnableDevelopmentModes) ...[ + if (dev.kEnableDevelopmentModes) ...[ Card( child: const Padding( padding: EdgeInsets.all(16.0), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 1a17105..9c4e7b2 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,11 +1,41 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import '../services/localization_service.dart'; import '../services/version_service.dart'; import '../dev_config.dart'; -class SettingsScreen extends StatelessWidget { +class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + int _versionTapCount = 0; + Timer? _tapTimer; + + @override + void dispose() { + _tapTimer?.cancel(); + super.dispose(); + } + + void _onVersionTap() { + _tapTimer?.cancel(); + _versionTapCount++; + + if (_versionTapCount >= 10) { + Navigator.pushNamed(context, '/settings/developer'); + _versionTapCount = 0; + return; + } + + _tapTimer = Timer(const Duration(milliseconds: 400), () { + _versionTapCount = 0; + }); + } + @override Widget build(BuildContext context) { final locService = LocalizationService.instance; @@ -100,15 +130,18 @@ class SettingsScreen extends StatelessWidget { ), const Divider(), - // Version display + // Version display with secret tap counter Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text( - 'Version: ${VersionService().version}', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + child: GestureDetector( + onTap: _onVersionTap, + child: Text( + 'Version: ${VersionService().version}', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).textTheme.bodySmall?.color?.withOpacity(0.6), + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, ), ), ], diff --git a/lib/screens/tile_provider_editor_screen.dart b/lib/screens/tile_provider_editor_screen.dart index 6ca2013..d0e8589 100644 --- a/lib/screens/tile_provider_editor_screen.dart +++ b/lib/screens/tile_provider_editor_screen.dart @@ -345,7 +345,7 @@ class _TileTypeDialogState extends State<_TileTypeDialog> { if (value?.trim().isEmpty == true) return locService.t('tileTypeEditor.maxZoomRequired'); final zoom = int.tryParse(value!); if (zoom == null) return locService.t('tileTypeEditor.maxZoomInvalid'); - if (zoom < 1 || zoom > kAbsoluteMaxZoom) return locService.t('tileTypeEditor.maxZoomRange', params: ['1', kAbsoluteMaxZoom.toString()]); + if (zoom < 1 || zoom > dev.kAbsoluteMaxZoom) return locService.t('tileTypeEditor.maxZoomRange', params: ['1', kAbsoluteMaxZoom.toString()]); return null; }, ), @@ -405,9 +405,9 @@ class _TileTypeDialogState extends State<_TileTypeDialog> { try { // Use a sample tile from configured preview location final url = _urlController.text - .replaceAll('{z}', kPreviewTileZoom.toString()) - .replaceAll('{x}', kPreviewTileX.toString()) - .replaceAll('{y}', kPreviewTileY.toString()); + .replaceAll('{z}', dev.kPreviewTileZoom.toString()) + .replaceAll('{x}', dev.kPreviewTileX.toString()) + .replaceAll('{y}', dev.kPreviewTileY.toString()); final response = await http.get(Uri.parse(url)); diff --git a/lib/services/map_data_submodules/nodes_from_overpass.dart b/lib/services/map_data_submodules/nodes_from_overpass.dart index f7b75c4..88b517f 100644 --- a/lib/services/map_data_submodules/nodes_from_overpass.dart +++ b/lib/services/map_data_submodules/nodes_from_overpass.dart @@ -44,7 +44,7 @@ Future> _fetchOverpassNodesWithSplitting({ }) async { if (profiles.isEmpty) return []; - const int maxSplitDepth = kMaxPreFetchSplitDepth; // Maximum times we'll split (4^3 = 64 max sub-areas) + final int maxSplitDepth = dev.dev.kMaxPreFetchSplitDepth; // Maximum times we'll split (4^3 = 64 max sub-areas) try { return await _fetchSingleOverpassQuery( diff --git a/lib/services/map_data_submodules/tiles_from_remote.dart b/lib/services/map_data_submodules/tiles_from_remote.dart index a435749..0d5354b 100644 --- a/lib/services/map_data_submodules/tiles_from_remote.dart +++ b/lib/services/map_data_submodules/tiles_from_remote.dart @@ -36,14 +36,14 @@ void clearRemoteTileQueueSelective(LatLngBounds currentBounds) { /// Uses: initialDelay * (multiplier ^ (attempt - 1)) + randomJitter, capped at maxDelay int _calculateRetryDelay(int attempt, Random random) { // Calculate exponential backoff: initialDelay * (multiplier ^ (attempt - 1)) - final baseDelay = (kTileFetchInitialDelayMs * - pow(kTileFetchBackoffMultiplier, attempt - 1)).round(); + final baseDelay = (dev.kTileFetchInitialDelayMs * + pow(dev.kTileFetchBackoffMultiplier, attempt - 1)).round(); // Add random jitter to avoid thundering herd - final jitter = random.nextInt(kTileFetchRandomJitterMs + 1); + final jitter = random.nextInt(dev.kTileFetchRandomJitterMs + 1); // Apply max delay cap - return (baseDelay + jitter).clamp(0, kTileFetchMaxDelayMs); + return (baseDelay + jitter).clamp(0, dev.kTileFetchMaxDelayMs); } /// Convert tile coordinates to lat/lng bounds for spatial filtering @@ -101,7 +101,7 @@ Future> fetchRemoteTile({ required int y, required String url, }) async { - const int maxAttempts = kTileFetchMaxAttempts; + final int maxAttempts = dev.dev.kTileFetchMaxAttempts; int attempt = 0; final random = Random(); final hostInfo = Uri.parse(url).host; // For logging diff --git a/lib/services/prefetch_area_service.dart b/lib/services/prefetch_area_service.dart index 440cfbb..dc1a1aa 100644 --- a/lib/services/prefetch_area_service.dart +++ b/lib/services/prefetch_area_service.dart @@ -30,8 +30,8 @@ class PrefetchAreaService { Timer? _debounceTimer; // Configuration from dev_config - static const double _areaExpansionMultiplier = kPreFetchAreaExpansionMultiplier; - static const int _preFetchZoomLevel = kPreFetchZoomLevel; + static final double _areaExpansionMultiplier = dev.dev.kPreFetchAreaExpansionMultiplier; + static final int _preFetchZoomLevel = dev.dev.kPreFetchZoomLevel; /// Check if the given bounds are fully within the current pre-fetched area. bool isWithinPreFetchedArea(LatLngBounds bounds, List profiles, UploadMode uploadMode) { @@ -58,7 +58,7 @@ class PrefetchAreaService { /// Check if cached data is stale (older than configured refresh interval). bool isDataStale() { if (_lastFetchTime == null) return true; - return DateTime.now().difference(_lastFetchTime!).inSeconds > kDataRefreshIntervalSeconds; + return DateTime.now().difference(_lastFetchTime!).inSeconds > dev.kDataRefreshIntervalSeconds; } /// Request pre-fetch for the given view bounds if not already covered or if data is stale. @@ -84,7 +84,7 @@ class PrefetchAreaService { } if (isStale) { - debugPrint('[PrefetchAreaService] Data is stale (>${kDataRefreshIntervalSeconds}s), refreshing'); + debugPrint('[PrefetchAreaService] Data is stale (>${dev.kDataRefreshIntervalSeconds}s), refreshing'); } else { debugPrint('[PrefetchAreaService] Current view outside pre-fetched area, fetching larger area'); } diff --git a/lib/services/proximity_alert_service.dart b/lib/services/proximity_alert_service.dart index ea1de30..c5bf16c 100644 --- a/lib/services/proximity_alert_service.dart +++ b/lib/services/proximity_alert_service.dart @@ -28,7 +28,7 @@ class ProximityAlertService { // Simple in-memory tracking of recent alerts to prevent spam final List _recentAlerts = []; - static const Duration _alertCooldown = kProximityAlertCooldown; + static final Duration _alertCooldown = dev.dev.kProximityAlertCooldown; // Callback for showing in-app visual alerts VoidCallback? _onVisualAlert; diff --git a/lib/services/suspected_location_service.dart b/lib/services/suspected_location_service.dart index e7fe269..a629135 100644 --- a/lib/services/suspected_location_service.dart +++ b/lib/services/suspected_location_service.dart @@ -102,10 +102,10 @@ class SuspectedLocationService { /// Fetch data from the CSV URL Future _fetchData() async { try { - debugPrint('[SuspectedLocationService] Fetching CSV data from $kSuspectedLocationsCsvUrl'); + debugPrint('[SuspectedLocationService] Fetching CSV data from $dev.kSuspectedLocationsCsvUrl'); final response = await http.get( - Uri.parse(kSuspectedLocationsCsvUrl), + Uri.parse(dev.kSuspectedLocationsCsvUrl), headers: { 'User-Agent': 'DeFlock/1.0 (OSM surveillance mapping app)', }, diff --git a/lib/services/tile_preview_service.dart b/lib/services/tile_preview_service.dart index 91d3b68..2431109 100644 --- a/lib/services/tile_preview_service.dart +++ b/lib/services/tile_preview_service.dart @@ -61,7 +61,7 @@ class TilePreviewService { static Future _fetchPreviewForTileType(TileType tileType, String? apiKey) async { try { - final url = tileType.getTileUrl(kPreviewTileZoom, kPreviewTileX, kPreviewTileY, apiKey: apiKey); + final url = tileType.getTileUrl(dev.kPreviewTileZoom, dev.kPreviewTileX, dev.kPreviewTileY, apiKey: apiKey); final response = await http.get(Uri.parse(url)).timeout(_timeout); diff --git a/lib/services/uploader.dart b/lib/services/uploader.dart index 9d6a92f..1ee62cb 100644 --- a/lib/services/uploader.dart +++ b/lib/services/uploader.dart @@ -44,7 +44,7 @@ class Uploader { final csXml = ''' - + '''; diff --git a/lib/state/settings_state.dart b/lib/state/settings_state.dart index 9d0a170..cbcf721 100644 --- a/lib/state/settings_state.dart +++ b/lib/state/settings_state.dart @@ -33,10 +33,10 @@ class SettingsState extends ChangeNotifier { bool _offlineMode = false; bool _pauseQueueProcessing = false; int _maxCameras = 250; - UploadMode _uploadMode = kEnableDevelopmentModes ? UploadMode.simulate : UploadMode.production; + UploadMode _uploadMode = dev.kEnableDevelopmentModes ? UploadMode.simulate : UploadMode.production; FollowMeMode _followMeMode = FollowMeMode.follow; bool _proximityAlertsEnabled = false; - int _proximityAlertDistance = kProximityAlertDefaultDistance; + int _proximityAlertDistance = dev.kProximityAlertDefaultDistance; bool _networkStatusIndicatorEnabled = true; int _suspectedLocationMinDistance = 100; // meters List _tileProviders = []; @@ -105,7 +105,7 @@ class SettingsState extends ChangeNotifier { // Load proximity alerts settings _proximityAlertsEnabled = prefs.getBool(_proximityAlertsEnabledPrefsKey) ?? false; - _proximityAlertDistance = prefs.getInt(_proximityAlertDistancePrefsKey) ?? kProximityAlertDefaultDistance; + _proximityAlertDistance = prefs.getInt(_proximityAlertDistancePrefsKey) ?? dev.kProximityAlertDefaultDistance; // Load network status indicator setting _networkStatusIndicatorEnabled = prefs.getBool(_networkStatusIndicatorEnabledPrefsKey) ?? true; @@ -128,7 +128,7 @@ class SettingsState extends ChangeNotifier { } // In production builds, force production mode if development modes are disabled - if (!kEnableDevelopmentModes && _uploadMode != UploadMode.production) { + if (!dev.kEnableDevelopmentModes && _uploadMode != UploadMode.production) { debugPrint('SettingsState: Development modes disabled, forcing production mode'); _uploadMode = UploadMode.production; await prefs.setInt(_uploadModePrefsKey, _uploadMode.index); @@ -236,7 +236,7 @@ class SettingsState extends ChangeNotifier { Future setUploadMode(UploadMode mode) async { // In production builds, only allow production mode - if (!kEnableDevelopmentModes && mode != UploadMode.production) { + if (!dev.kEnableDevelopmentModes && mode != UploadMode.production) { debugPrint('SettingsState: Development modes disabled, forcing production mode'); mode = UploadMode.production; } @@ -323,8 +323,8 @@ class SettingsState extends ChangeNotifier { /// Set proximity alert distance in meters Future setProximityAlertDistance(int distance) async { - if (distance < kProximityAlertMinDistance) distance = kProximityAlertMinDistance; - if (distance > kProximityAlertMaxDistance) distance = kProximityAlertMaxDistance; + if (distance < dev.kProximityAlertMinDistance) distance = dev.kProximityAlertMinDistance; + if (distance > dev.kProximityAlertMaxDistance) distance = dev.kProximityAlertMaxDistance; if (_proximityAlertDistance != distance) { _proximityAlertDistance = distance; final prefs = await SharedPreferences.getInstance(); diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 58ba75c..04144e0 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -81,7 +81,7 @@ class AddNodeSheet extends StatelessWidget { : null, tooltip: requiresDirection ? 'Remove current direction' : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), // Add button IconButton( @@ -95,7 +95,7 @@ class AddNodeSheet extends StatelessWidget { ? (session.directions.length >= 8 ? 'Maximum 8 directions allowed' : 'Add new direction') : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), // Cycle button IconButton( @@ -109,7 +109,7 @@ class AddNodeSheet extends StatelessWidget { : null, tooltip: requiresDirection ? 'Cycle through directions' : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), ], ), diff --git a/lib/widgets/camera_icon.dart b/lib/widgets/camera_icon.dart index 4fa015a..b78f46c 100644 --- a/lib/widgets/camera_icon.dart +++ b/lib/widgets/camera_icon.dart @@ -19,28 +19,28 @@ class CameraIcon extends StatelessWidget { Color get _ringColor { switch (type) { case CameraIconType.real: - return kNodeRingColorReal; + return dev.kNodeRingColorReal; case CameraIconType.mock: - return kNodeRingColorMock; + return dev.kNodeRingColorMock; case CameraIconType.pending: - return kNodeRingColorPending; + return dev.kNodeRingColorPending; case CameraIconType.editing: - return kNodeRingColorEditing; + return dev.kNodeRingColorEditing; case CameraIconType.pendingEdit: - return kNodeRingColorPendingEdit; + return dev.kNodeRingColorPendingEdit; case CameraIconType.pendingDeletion: - return kNodeRingColorPendingDeletion; + return dev.kNodeRingColorPendingDeletion; } } @override Widget build(BuildContext context) { return Container( - width: kNodeIconDiameter, - height: kNodeIconDiameter, + width: dev.kNodeIconDiameter, + height: dev.kNodeIconDiameter, decoration: BoxDecoration( shape: BoxShape.circle, - color: _ringColor.withOpacity(kNodeDotOpacity), + color: _ringColor.withOpacity(dev.kNodeDotOpacity), border: Border.all( color: _ringColor, width: getNodeRingThickness(context), diff --git a/lib/widgets/download_area_dialog.dart b/lib/widgets/download_area_dialog.dart index 1242483..83a5511 100644 --- a/lib/widgets/download_area_dialog.dart +++ b/lib/widgets/download_area_dialog.dart @@ -76,14 +76,14 @@ class _DownloadAreaDialogState extends State { /// Calculate the maximum zoom level that keeps tile count under the absolute limit int _calculateMaxZoomForTileLimit(LatLngBounds bounds, int minZoom) { - for (int zoom = minZoom; zoom <= kAbsoluteMaxZoom; zoom++) { + for (int zoom = minZoom; zoom <= dev.kAbsoluteMaxZoom; zoom++) { final tileCount = computeTileList(bounds, minZoom, zoom).length; - if (tileCount > kAbsoluteMaxTileCount) { + if (tileCount > dev.kAbsoluteMaxTileCount) { // Return the previous zoom level that was still under the absolute limit return math.max(minZoom, zoom - 1); } } - return kAbsoluteMaxZoom; + return dev.kAbsoluteMaxZoom; } /// Get tile size estimate in KB, using preview tile data if available, otherwise fallback to constant @@ -98,7 +98,7 @@ class _DownloadAreaDialogState extends State { return previewSizeKb; } else { // Fall back to configured estimate - return kFallbackTileEstimateKb; + return dev.kFallbackTileEstimateKb; } } @@ -176,7 +176,7 @@ class _DownloadAreaDialogState extends State { child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: _tileCount! > kMaxReasonableTileCount + color: _tileCount! > dev.kMaxReasonableTileCount ? Colors.orange.withOpacity(0.1) : Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(4), @@ -185,12 +185,12 @@ class _DownloadAreaDialogState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - _tileCount! > kMaxReasonableTileCount + _tileCount! > dev.kMaxReasonableTileCount ? 'Above recommended limit (Z${_maxPossibleZoom})' : locService.t('download.maxRecommendedZoom', params: [_maxPossibleZoom.toString()]), style: TextStyle( fontSize: 12, - color: _tileCount! > kMaxReasonableTileCount + color: _tileCount! > dev.kMaxReasonableTileCount ? Colors.orange[700] : Colors.green[700], fontWeight: FontWeight.w500, @@ -198,12 +198,12 @@ class _DownloadAreaDialogState extends State { ), const SizedBox(height: 2), Text( - _tileCount! > kMaxReasonableTileCount - ? 'Current selection exceeds ${kMaxReasonableTileCount} recommended tile limit but is within ${kAbsoluteMaxTileCount} absolute limit' + _tileCount! > dev.kMaxReasonableTileCount + ? 'Current selection exceeds ${dev.kMaxReasonableTileCount} recommended tile limit but is within ${dev.kAbsoluteMaxTileCount} absolute limit' : locService.t('download.withinTileLimit', params: [kMaxReasonableTileCount.toString()]), style: TextStyle( fontSize: 11, - color: _tileCount! > kMaxReasonableTileCount + color: _tileCount! > dev.kMaxReasonableTileCount ? Colors.orange[600] : Colors.green[600], ), diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index ad7277a..4fbdafc 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -83,7 +83,7 @@ class EditNodeSheet extends StatelessWidget { : null, tooltip: requiresDirection ? 'Remove current direction' : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), // Add button IconButton( @@ -97,7 +97,7 @@ class EditNodeSheet extends StatelessWidget { ? (session.directions.length >= 8 ? 'Maximum 8 directions allowed' : 'Add new direction') : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), // Cycle button IconButton( @@ -111,7 +111,7 @@ class EditNodeSheet extends StatelessWidget { : null, tooltip: requiresDirection ? 'Cycle through directions' : 'Direction not required for this profile', padding: EdgeInsets.zero, - constraints: const BoxConstraints(minWidth: kDirectionButtonMinWidth, minHeight: kDirectionButtonMinHeight), + constraints: BoxConstraints(minWidth: dev.kDirectionButtonMinWidth, minHeight: dev.kDirectionButtonMinHeight), ), ], ), @@ -160,7 +160,7 @@ class EditNodeSheet extends StatelessWidget { final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); final isSandboxMode = appState.uploadMode == UploadMode.sandbox; - final allowSubmit = kEnableNodeEdits && + final allowSubmit = dev.kEnableNodeEdits && appState.isLoggedIn && submittableProfiles.isNotEmpty && session.profile != null && @@ -220,7 +220,7 @@ class EditNodeSheet extends StatelessWidget { child: Column( children: [ // Extract from way checkbox (only show if enabled in dev config) - if (kEnableNodeExtraction) ...[ + if (dev.kEnableNodeExtraction) ...[ CheckboxListTile( title: Text(locService.t('editNode.extractFromWay')), subtitle: Text(locService.t('editNode.extractFromWaySubtitle')), @@ -234,7 +234,7 @@ class EditNodeSheet extends StatelessWidget { const SizedBox(height: 8), ], // Constraint info message (only show if extract is not checked or not enabled) - if (!kEnableNodeExtraction || !session.extractFromWay) ...[ + if (!dev.kEnableNodeExtraction || !session.extractFromWay) ...[ Row( children: [ const Icon(Icons.info_outline, size: 20), @@ -266,7 +266,7 @@ class EditNodeSheet extends StatelessWidget { ), ), - if (!kEnableNodeEdits) + if (!dev.kEnableNodeEdits) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Row( diff --git a/lib/widgets/map/camera_markers.dart b/lib/widgets/map/camera_markers.dart index 031f42d..01eabae 100644 --- a/lib/widgets/map/camera_markers.dart +++ b/lib/widgets/map/camera_markers.dart @@ -28,7 +28,7 @@ class CameraMapMarker extends StatefulWidget { class _CameraMapMarkerState extends State { Timer? _tapTimer; // From dev_config.dart for build-time parameters - static const Duration tapTimeout = kMarkerTapTimeout; + static final Duration tapTimeout = dev.dev.kMarkerTapTimeout; void _onTap() { _tapTimer = Timer(tapTimeout, () { @@ -49,7 +49,7 @@ class _CameraMapMarkerState extends State { void _onDoubleTap() { _tapTimer?.cancel(); - widget.mapController.move(widget.node.coord, widget.mapController.camera.zoom + kNodeDoubleTapZoomDelta); + widget.mapController.move(widget.node.coord, widget.mapController.camera.zoom + dev.kNodeDoubleTapZoomDelta); } @override @@ -108,8 +108,8 @@ class CameraMarkersBuilder { return Marker( point: n.coord, - width: kNodeIconDiameter, - height: kNodeIconDiameter, + width: dev.kNodeIconDiameter, + height: dev.kNodeIconDiameter, child: Opacity( opacity: shouldDimNode ? 0.5 : 1.0, child: CameraMapMarker( diff --git a/lib/widgets/map/camera_refresh_controller.dart b/lib/widgets/map/camera_refresh_controller.dart index 381eefa..a104381 100644 --- a/lib/widgets/map/camera_refresh_controller.dart +++ b/lib/widgets/map/camera_refresh_controller.dart @@ -72,12 +72,12 @@ class CameraRefreshController { } final zoom = controller.mapController.camera.zoom; - if (zoom < kNodeMinZoomLevel) { + if (zoom < dev.kNodeMinZoomLevel) { // Show a snackbar-style bubble warning if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Nodes not drawn below zoom level $kNodeMinZoomLevel'), + content: Text('Nodes not drawn below zoom level $dev.kNodeMinZoomLevel'), duration: const Duration(seconds: 2), ), ); diff --git a/lib/widgets/map/direction_cones.dart b/lib/widgets/map/direction_cones.dart index 7324b24..6516458 100644 --- a/lib/widgets/map/direction_cones.dart +++ b/lib/widgets/map/direction_cones.dart @@ -112,11 +112,11 @@ class DirectionConesBuilder { bool isSession = false, bool isActiveDirection = true, }) { - final halfAngle = kDirectionConeHalfAngle; + final halfAngle = dev.kDirectionConeHalfAngle; // Calculate pixel-based radii - final outerRadiusPx = kNodeIconDiameter + (kNodeIconDiameter * kDirectionConeBaseLength); - final innerRadiusPx = kNodeIconDiameter + (2 * getNodeRingThickness(context)); + final outerRadiusPx = dev.kNodeIconDiameter + (dev.kNodeIconDiameter * dev.kDirectionConeBaseLength); + final innerRadiusPx = dev.kNodeIconDiameter + (2 * getNodeRingThickness(context)); // Convert pixels to coordinate distances with zoom scaling final pixelToCoordinate = 0.00001 * math.pow(2, 15 - zoom); @@ -150,15 +150,15 @@ class DirectionConesBuilder { } // Adjust opacity based on direction state - double opacity = kDirectionConeOpacity; + double opacity = dev.kDirectionConeOpacity; if (isSession && !isActiveDirection) { - opacity = kDirectionConeOpacity * 0.4; // Reduced opacity for inactive session directions + opacity = dev.kDirectionConeOpacity * 0.4; // Reduced opacity for inactive session directions } return Polygon( points: points, - color: kDirectionConeColor.withOpacity(opacity), - borderColor: kDirectionConeColor, + color: dev.kDirectionConeColor.withOpacity(opacity), + borderColor: dev.kDirectionConeColor, borderStrokeWidth: getDirectionConeBorderWidth(context), ); } diff --git a/lib/widgets/map/gps_controller.dart b/lib/widgets/map/gps_controller.dart index caf8edb..4cf5496 100644 --- a/lib/widgets/map/gps_controller.dart +++ b/lib/widgets/map/gps_controller.dart @@ -60,7 +60,7 @@ class GpsController { controller.animateTo( dest: _currentLatLng!, zoom: controller.mapController.camera.zoom, - duration: kFollowMeAnimationDuration, + duration: dev.kFollowMeAnimationDuration, curve: Curves.easeOut, ); onMapMovedProgrammatically?.call(); @@ -70,7 +70,7 @@ class GpsController { dest: _currentLatLng!, zoom: controller.mapController.camera.zoom, rotation: 0.0, - duration: kFollowMeAnimationDuration, + duration: dev.kFollowMeAnimationDuration, curve: Curves.easeOut, ); onMapMovedProgrammatically?.call(); @@ -123,7 +123,7 @@ class GpsController { dest: latLng, zoom: controller.mapController.camera.zoom, rotation: controller.mapController.camera.rotation, - duration: kFollowMeAnimationDuration, + duration: dev.kFollowMeAnimationDuration, curve: Curves.easeOut, ); @@ -135,14 +135,14 @@ class GpsController { final speed = position.speed; // Speed in m/s // Only apply rotation if moving fast enough to avoid wild spinning when stationary - final shouldRotate = !speed.isNaN && speed >= kMinSpeedForRotationMps && !heading.isNaN; + final shouldRotate = !speed.isNaN && speed >= dev.kMinSpeedForRotationMps && !heading.isNaN; final rotation = shouldRotate ? -heading : controller.mapController.camera.rotation; controller.animateTo( dest: latLng, zoom: controller.mapController.camera.zoom, rotation: rotation, - duration: kFollowMeAnimationDuration, + duration: dev.kFollowMeAnimationDuration, curve: Curves.easeOut, ); diff --git a/lib/widgets/map/map_overlays.dart b/lib/widgets/map/map_overlays.dart index 9ca409e..56f083b 100644 --- a/lib/widgets/map/map_overlays.dart +++ b/lib/widgets/map/map_overlays.dart @@ -94,7 +94,7 @@ class MapOverlays extends StatelessWidget { // Zoom indicator, positioned relative to button bar with left safe area Positioned( left: leftPositionWithSafeArea(10, safeArea), - bottom: bottomPositionFromButtonBar(kZoomIndicatorSpacingAboveButtonBar, safeArea.bottom), + bottom: bottomPositionFromButtonBar(dev.kZoomIndicatorSpacingAboveButtonBar, safeArea.bottom), child: Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( @@ -125,7 +125,7 @@ class MapOverlays extends StatelessWidget { // Attribution overlay, positioned relative to button bar with left safe area if (attribution != null) Positioned( - bottom: bottomPositionFromButtonBar(kAttributionSpacingAboveButtonBar, safeArea.bottom), + bottom: bottomPositionFromButtonBar(dev.kAttributionSpacingAboveButtonBar, safeArea.bottom), left: leftPositionWithSafeArea(10, safeArea), child: GestureDetector( onTap: () => _showAttributionDialog(context, attribution!), @@ -151,7 +151,7 @@ class MapOverlays extends StatelessWidget { // Zoom and layer controls (bottom-right), positioned relative to button bar with right safe area Positioned( - bottom: bottomPositionFromButtonBar(kZoomControlsSpacingAboveButtonBar, safeArea.bottom), + bottom: bottomPositionFromButtonBar(dev.kZoomControlsSpacingAboveButtonBar, safeArea.bottom), right: rightPositionWithSafeArea(16, safeArea), child: Consumer( builder: (context, appState, child) { diff --git a/lib/widgets/map/suspected_location_markers.dart b/lib/widgets/map/suspected_location_markers.dart index e95d2f4..1e43d9d 100644 --- a/lib/widgets/map/suspected_location_markers.dart +++ b/lib/widgets/map/suspected_location_markers.dart @@ -28,7 +28,7 @@ class SuspectedLocationMapMarker extends StatefulWidget { class _SuspectedLocationMapMarkerState extends State { Timer? _tapTimer; // From dev_config.dart for build-time parameters - static const Duration tapTimeout = kMarkerTapTimeout; + static final Duration tapTimeout = dev.dev.kMarkerTapTimeout; void _onTap() { _tapTimer = Timer(tapTimeout, () { @@ -47,7 +47,7 @@ class _SuspectedLocationMapMarkerState extends State void _onDoubleTap() { _tapTimer?.cancel(); - widget.mapController.move(widget.location.centroid, widget.mapController.camera.zoom + kNodeDoubleTapZoomDelta); + widget.mapController.move(widget.location.centroid, widget.mapController.camera.zoom + dev.kNodeDoubleTapZoomDelta); } @override diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index b4f1919..0ff6c17 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -60,7 +60,7 @@ class MapView extends StatefulWidget { class MapViewState extends State { late final AnimatedMapController _controller; - final Debouncer _cameraDebounce = Debouncer(kDebounceCameraRefresh); + final Debouncer _cameraDebounce = Debouncer(dev.kDebounceCameraRefresh); final Debouncer _tileDebounce = Debouncer(const Duration(milliseconds: 150)); final Debouncer _mapPositionDebounce = Debouncer(const Duration(milliseconds: 1000)); final Debouncer _constrainedNodeSnapBack = Debouncer(const Duration(milliseconds: 100)); @@ -240,9 +240,9 @@ class MapViewState extends State { // OSM API (sandbox mode) needs higher zoom level due to bbox size limits if (uploadMode == UploadMode.sandbox) { - return kOsmApiMinZoomLevel; + return dev.kOsmApiMinZoomLevel; } else { - return kNodeMinZoomLevel; + return dev.kNodeMinZoomLevel; } } @@ -268,17 +268,17 @@ class MapViewState extends State { // Check if we're editing a constrained node that's not being extracted if (editSession?.originalNode.isConstrained == true && editSession?.extractFromWay != true) { // Constrained node (not extracting): only allow pinch zoom and rotation, disable ALL panning - return const InteractionOptions( + return InteractionOptions( enableMultiFingerGestureRace: true, flags: InteractiveFlag.pinchZoom | InteractiveFlag.rotate, - scrollWheelVelocity: kScrollWheelVelocity, - pinchZoomThreshold: kPinchZoomThreshold, - pinchMoveThreshold: kPinchMoveThreshold, + scrollWheelVelocity: dev.kScrollWheelVelocity, + pinchZoomThreshold: dev.kPinchZoomThreshold, + pinchMoveThreshold: dev.kPinchMoveThreshold, ); } // Normal case: all interactions allowed with gesture race to prevent accidental rotation during zoom - return const InteractionOptions( + return InteractionOptions( enableMultiFingerGestureRace: true, flags: InteractiveFlag.doubleTapDragZoom | InteractiveFlag.doubleTapZoom | @@ -287,9 +287,9 @@ class MapViewState extends State { InteractiveFlag.pinchZoom | InteractiveFlag.rotate | InteractiveFlag.scrollWheelZoom, - scrollWheelVelocity: kScrollWheelVelocity, - pinchZoomThreshold: kPinchZoomThreshold, - pinchMoveThreshold: kPinchMoveThreshold, + scrollWheelVelocity: dev.kScrollWheelVelocity, + pinchZoomThreshold: dev.kPinchZoomThreshold, + pinchMoveThreshold: dev.kPinchMoveThreshold, ); } @@ -506,8 +506,8 @@ class MapViewState extends State { centerMarkers.add( Marker( point: center, - width: kNodeIconDiameter, - height: kNodeIconDiameter, + width: dev.kNodeIconDiameter, + height: dev.kNodeIconDiameter, child: CameraIcon( type: editSession != null ? CameraIconType.editing : CameraIconType.mock, ), @@ -691,7 +691,7 @@ class MapViewState extends State { alignment: Alignment.bottomLeft, padding: EdgeInsets.only( left: leftPositionWithSafeArea(8, safeArea), - bottom: bottomPositionFromButtonBar(kScaleBarSpacingAboveButtonBar, safeArea.bottom) + bottom: bottomPositionFromButtonBar(dev.kScaleBarSpacingAboveButtonBar, safeArea.bottom) ), textStyle: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), lineColor: Colors.black, @@ -753,7 +753,7 @@ class MapViewState extends State { if (originalCoord != null) { lines.add(Polyline( points: [originalCoord, node.coord], - color: kNodeRingColorPending, + color: dev.kNodeRingColorPending, strokeWidth: 3.0, )); } diff --git a/scripts/update_dev_config.py b/scripts/update_dev_config.py new file mode 100644 index 0000000..9115522 --- /dev/null +++ b/scripts/update_dev_config.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 + +import os +import re + +# All constants to replace +CONSTANTS = [ + "kFallbackTileEstimateKb", "kPreviewTileZoom", "kPreviewTileY", "kPreviewTileX", + "kDirectionConeHalfAngle", "kDirectionConeBaseLength", "kDirectionConeColor", "kDirectionConeOpacity", + "kBottomButtonBarOffset", "kButtonBarHeight", "kAttributionSpacingAboveButtonBar", + "kZoomIndicatorSpacingAboveButtonBar", "kScaleBarSpacingAboveButtonBar", "kZoomControlsSpacingAboveButtonBar", + "kClientName", "kSuspectedLocationsCsvUrl", "kEnableDevelopmentModes", "kEnableNodeEdits", "kEnableNodeExtraction", + "kNodeMinZoomLevel", "kOsmApiMinZoomLevel", "kMarkerTapTimeout", "kDebounceCameraRefresh", + "kPreFetchAreaExpansionMultiplier", "kPreFetchZoomLevel", "kMaxPreFetchSplitDepth", "kDataRefreshIntervalSeconds", + "kFollowMeAnimationDuration", "kMinSpeedForRotationMps", "kProximityAlertDefaultDistance", + "kProximityAlertMinDistance", "kProximityAlertMaxDistance", "kProximityAlertCooldown", + "kNodeDoubleTapZoomDelta", "kScrollWheelVelocity", "kPinchZoomThreshold", "kPinchMoveThreshold", "kRotationThreshold", + "kTileFetchMaxAttempts", "kTileFetchInitialDelayMs", "kTileFetchBackoffMultiplier", "kTileFetchMaxDelayMs", + "kTileFetchRandomJitterMs", "kMaxUserDownloadZoomSpan", "kMaxReasonableTileCount", "kAbsoluteMaxTileCount", + "kAbsoluteMaxZoom", "kNodeIconDiameter", "kNodeDotOpacity", "kNodeRingColorReal", "kNodeRingColorMock", + "kNodeRingColorPending", "kNodeRingColorEditing", "kNodeRingColorPendingEdit", "kNodeRingColorPendingDeletion", + "kDirectionButtonMinWidth", "kDirectionButtonMinHeight" +] + +def find_dart_files(): + """Find all .dart files except dev_config.dart""" + dart_files = [] + for root, dirs, files in os.walk('.'): + for file in files: + if file.endswith('.dart'): + path = os.path.join(root, file) + if 'dev_config.dart' not in path: + dart_files.append(path) + return dart_files + +def process_file(filepath): + """Process a single dart file""" + print(f" šŸ“ Processing {filepath}") + + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + except Exception as e: + print(f" āŒ Error reading file: {e}") + return False + + original_content = content + changes_made = [] + + # Process each constant + for constant in CONSTANTS: + content, changed = process_constant_in_content(content, constant) + if changed: + changes_made.append(constant) + + # Only write if something actually changed + if content != original_content: + try: + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + print(f" āœ… Updated: {', '.join(changes_made)}") + return True + except Exception as e: + print(f" āŒ Error writing file: {e}") + return False + else: + print(f" ā­ļø No changes needed") + return False + +def process_constant_in_content(content, constant): + """Process a single constant in file content, handling const issues""" + original_content = content + + # Skip if already using dev.constant (idempotent) + if f"dev.{constant}" in content: + return content, False + + # Skip if constant not found at all + if constant not in content: + return content, False + + print(f" šŸ”„ Replacing {constant}") + + # Pattern 1: const Type variable = kConstant; + # Change to: final Type variable = dev.kConstant; + pattern1 = rf'\bconst\s+(\w+)\s+(\w+)\s*=\s*{re.escape(constant)}\s*;' + replacement1 = rf'final \1 \2 = dev.{constant};' + content = re.sub(pattern1, replacement1, content) + + # Pattern 2: static const Type variable = kConstant; + # Change to: static final Type variable = dev.kConstant; + pattern2 = rf'\bstatic\s+const\s+(\w+)\s+(\w+)\s*=\s*{re.escape(constant)}\s*;' + replacement2 = rf'static final \1 \2 = dev.{constant};' + content = re.sub(pattern2, replacement2, content) + + # Pattern 3: const ConstructorName(...kConstant...) + # We need to be careful here - find const constructors that contain our constant + # and remove the const keyword + # This is tricky to do perfectly with regex, so let's do a simple approach: + # If we find "const SomeConstructor(" followed by our constant somewhere before the matching ")" + # we'll remove the const keyword from the constructor + + # Find all const constructor calls that contain our constant + const_constructor_pattern = r'\bconst\s+(\w+)\s*\([^)]*' + re.escape(constant) + r'[^)]*\)' + matches = list(re.finditer(const_constructor_pattern, content)) + + # Replace const with just the constructor name for each match + for match in reversed(matches): # Reverse to maintain positions + full_match = match.group(0) + constructor_name = match.group(1) + # Remove 'const ' from the beginning + replacement = full_match.replace(f'const {constructor_name}', constructor_name, 1) + content = content[:match.start()] + replacement + content[match.end():] + + # Pattern 4: Simple replacements - any remaining instances of kConstant + # Use word boundaries to avoid partial matches, but avoid already replaced dev.kConstant + pattern4 = rf'\b{re.escape(constant)}\b(?![\w.])' # Negative lookahead to avoid partial matches + replacement4 = f'dev.{constant}' + content = re.sub(pattern4, replacement4, content) + + return content, content != original_content + +def main(): + print("šŸš€ Starting dev_config reference update...") + print("šŸ” Finding Dart files...") + + dart_files = find_dart_files() + print(f"šŸ“ Found {len(dart_files)} Dart files to process") + + if not dart_files: + print("āŒ No Dart files found!") + return + + updated_files = 0 + + for filepath in dart_files: + if process_file(filepath): + updated_files += 1 + + print(f"\n✨ Finished! Updated {updated_files} out of {len(dart_files)} files") + print("šŸ’” Next steps:") + print(" 1. flutter analyze (check for syntax errors)") + print(" 2. flutter pub get (refresh dependencies)") + print(" 3. flutter run (test the app)") + + if updated_files > 0: + print("āš ļø If you see compilation errors, the script can be run again safely") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/scripts/update_dev_config_references.sh b/scripts/update_dev_config_references.sh new file mode 100755 index 0000000..e54a2f1 --- /dev/null +++ b/scripts/update_dev_config_references.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Super simple test - just replace one constant first +echo "šŸ”„ Testing with kClientName..." + +find . -name "*.dart" -not -path "./lib/dev_config.dart" -exec grep -l "kClientName" {} \; + +echo "Found files with kClientName. Now replacing..." + +find . -name "*.dart" -not -path "./lib/dev_config.dart" -exec sed -i '' 's/kClientName/dev.kClientName/g' {} \; + +echo "āœ… Done with test. Check if lib/services/uploader.dart changed" \ No newline at end of file