smart_location 3.7.1 copy "smart_location: ^3.7.1" to clipboard
smart_location: ^3.7.1 copied to clipboard

Smart Location is a powerful, battery-conscious Flutter plugin for continuous background geolocation tracking, advanced geofencing, and motion-aware updates.

example/lib/main.dart

import 'dart:async';
// ignore_for_file: use_build_context_synchronously, unused_field, unused_element
import 'dart:convert';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:smart_location/smart_location.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

import 'package:realm/realm.dart';
import 'src/offline_location_model.dart';
import 'src/offline_location_store.dart';
import 'splash_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Offline Store
  final config = Configuration.local([OfflineLocationData.schema]);
  final database = Realm(config);
  SmartLocation.offlineStore = OfflineLocationStore(database);

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Smart Location Sandbox',
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.dark,
        fontFamily: 'Avenir Next',
        colorScheme: const ColorScheme.dark(
          primary: Color(0xFF7C86C9),
          secondary: Color(0xFF34D399),
          surface: Color(0xFF1F2439),
        ),
        scaffoldBackgroundColor: const Color(0xFF1A1E2E),
        cardTheme: CardThemeData(
          color: const Color(0xFF1F2439),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          elevation: 0,
          shadowColor: Colors.transparent,
        ),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF1A1E2E),
          foregroundColor: Colors.white,
          elevation: 0,
          shadowColor: Colors.transparent,
        ),
        sliderTheme: SliderThemeData(
          activeTrackColor: const Color(0xFF7C86C9),
          inactiveTrackColor: const Color(0xFF252D45),
          thumbColor: const Color(0xFF7C86C9),
          overlayColor: const Color(0xFF7C86C9).withValues(alpha: 0.18),
          trackHeight: 3,
        ),
        dividerTheme: const DividerThemeData(
          color: Color(0xFF252D45),
          thickness: 1,
        ),
        elevatedButtonTheme: ElevatedButtonThemeData(
          style:
              ElevatedButton.styleFrom(
                backgroundColor: const Color(0xFF252D45),
                foregroundColor: Colors.white,
                elevation: 0,
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 14,
                ),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ).copyWith(
                overlayColor: WidgetStateProperty.all(
                  const Color(0xFF7C86C9).withValues(alpha: 0.12),
                ),
              ),
        ),
        chipTheme: ChipThemeData(
          backgroundColor: const Color(0xFF1D2236),
          selectedColor: const Color(0xFF7C86C9).withValues(alpha: 0.25),
          side: BorderSide.none,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20),
          ),
          labelStyle: const TextStyle(color: Colors.white70, fontSize: 12),
        ),
        inputDecorationTheme: InputDecorationTheme(
          filled: true,
          fillColor: const Color(0xFF1D2236),
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: BorderSide.none,
          ),
          enabledBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: BorderSide.none,
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: BorderRadius.circular(12),
            borderSide: const BorderSide(color: Color(0xFF7C86C9), width: 1.5),
          ),
          hintStyle: const TextStyle(color: Colors.white38),
          contentPadding: const EdgeInsets.symmetric(
            horizontal: 16,
            vertical: 14,
          ),
        ),
      ),
      home: const SplashScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class DashboardScreen extends StatefulWidget {
  const DashboardScreen({super.key});

  @override
  State<DashboardScreen> createState() => _DashboardScreenState();
}

class _DashboardScreenState extends State<DashboardScreen>
    with TickerProviderStateMixin {
  // Navigation / Tabs
  late TabController _tabController;

  // Radar Animation Controller
  late AnimationController _radarAnimationController;

  // Map Controller
  final MapController _mapController = MapController();

  // Stream & Simulation Controllers
  late final StreamController<LocationData> _simStreamController;
  StreamSubscription<LocationData>? _hardwareStreamSub;
  StreamSubscription<ActivityType>? _activitySub;

  // Sandbox Mode: true = Draggable Pin, false = Hardware GPS
  bool _isSimulatorMode = false;

  // Core Location State
  LocationData _currentLocation = LocationData(
    latitude: 37.7749,
    longitude: -122.4194,
    accuracy: 5.0,
    altitude: 0.0,
    speed: 0.0,
    speedAccuracy: 0.0,
    heading: 0.0,
    timestamp: DateTime.now(),
  );

  // Drag Physics variables
  DateTime? _lastDragTime;
  LocationData? _prevDragLocation;
  ActivityType _currentActivity = ActivityType.stationary;

  // Real-time calculated G-forces from drag physics
  double _longG = 0.0; // Longitudinal G (accelerate/brake)
  double _latG = 0.0; // Lateral G (centripetal turn)

  // 1. Geofencing states
  double _geofenceRadius = 60.0;
  final List<GeofenceEvent> _geofenceEvents = [];
  final List<Geofence> _activeGeofences = [];

  // 2. Route Recorder states
  bool _isRecording = false;
  bool _isPaused = false;
  Duration _recordElapsed = Duration.zero;
  Timer? _recordTimer;
  List<LocationData> _recordedPath = [];
  List<LocationData> _simplifiedPath = [];
  double _recordedDistance = 0.0;
  double _rdpTolerance = 10.0; // meters

  // 3. Driver Safety states
  double _speedLimitKmh = 60.0;
  double _gForceThreshold = 10.0; // km/h/s (roughly 0.28 Gs)
  final List<String> _driverAlertsLogs = [];
  StreamSubscription<SpeedAlert>? _speedAlertSub;
  StreamSubscription<GForceAlert>? _gForceAlertSub;

  // 4. Privacy Guard states
  double _fuzzRadius = 300.0; // meters
  double _gridSizeDegrees = 0.001;
  LocationData? _fuzzedLocation;
  LocationData? _snappedLocation;

  // 5. Dead Reckoning / Extrapolation states
  bool _isDREnabled = false;
  LocationData? _drPredictedLocation;
  final List<String> _drLogs = [];

  // Map Matching states
  bool _isMapMatchingEnabled = false;
  final List<String> _mapMatchingLogs = [];

  // 6. Indoor Beacons states
  bool _showIndoorBeacons = false;
  double _rssiLobby = -50.0;
  double _rssiConfRoom = -85.0;
  double _rssiKitchen = -95.0;
  LocationData? _indoorCentroid;
  final List<String> _indoorLogs = [];

  // 7. Spatial Hashing states
  bool _showSpatialGrid = false;
  final List<String> _spatialHashLogs = [];

  // 8. Offline Sync states
  List<Map<String, dynamic>> _offlineLocations = [];
  bool _isBackgroundTrackingActive = false;

  // 9. Delivery Journey states
  final List<LocationData> _deliveryWaypoints = [];
  List<LatLng> _deliveryRouteCoords = [];
  StreamSubscription<ArrivalEvent>? _journeySub;
  double _remainingDeliveryDistance = 0.0;
  int _etaMinutes = 0;
  DateTime? _segmentStartTime;
  LocationData? _lastDepartureNode;

  // 10. ML Route states
  Duration? _predictedEta;
  final List<String> _mlRouteLogs = [];

  void _triggerMLRouteLearningDemo() async {
    setState(() {
      _mlRouteLogs.clear();
      _mlRouteLogs.add(
        '[ML] Initializing Route Learning Engine (alpha=0.3)...',
      );
    });

    await Future.delayed(const Duration(milliseconds: 600));
    if (!mounted) return;
    setState(() {
      _mlRouteLogs.add(
        '[ML] Driver A completed trip: Warehouse -> Store (15m 30s)',
      );
      SmartLocation.routeLearning.recordTrip(
        'warehouse',
        'store',
        const Duration(minutes: 15, seconds: 30),
      );
    });

    await Future.delayed(const Duration(milliseconds: 600));
    if (!mounted) return;
    setState(() {
      _mlRouteLogs.add(
        '[ML] Driver B completed trip: Warehouse -> Store (22m 10s)',
      );
      SmartLocation.routeLearning.recordTrip(
        'warehouse',
        'store',
        const Duration(minutes: 22, seconds: 10),
      );
    });

    await Future.delayed(const Duration(milliseconds: 600));
    if (!mounted) return;
    setState(() {
      _mlRouteLogs.add(
        '[ML] Driver C completed trip: Warehouse -> Store (16m 00s)',
      );
      SmartLocation.routeLearning.recordTrip(
        'warehouse',
        'store',
        const Duration(minutes: 16),
      );
    });

    await Future.delayed(const Duration(milliseconds: 600));
    final predicted = SmartLocation.routeLearning.predictDuration(
      'warehouse',
      'store',
    );

    if (mounted && predicted != null) {
      setState(() {
        _predictedEta = predicted;
        _mlRouteLogs.add(
          '✅ [PREDICTION] Highly accurate learned ETA for next driver: ${predicted.inMinutes}m ${predicted.inSeconds % 60}s',
        );
      });
    }
  }

  // Geocoding and Search states
  String _selectedVehicle = 'driving'; // driving, bike, foot
  final TextEditingController _searchController = TextEditingController();
  List<Map<String, dynamic>> _searchResults = [];
  bool _isSearching = false;
  final Map<String, String> _locationNames = {};
  double _currentZoom = 12.0;

  // Canvas bounds configuration
  static const double mapCenterLat = 37.7749;
  static const double mapCenterLng = -122.4194;
  static const double latRange = 0.005;
  static const double lngRange = 0.005;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 9, vsync: this);
    _simStreamController = StreamController<LocationData>.broadcast();

    // Radar Sweep Animation
    _radarAnimationController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 3),
    )..repeat();

    // Setup base geofences for visualization
    _resetGeofences();

    // Listen to our unified coordinates stream
    _simStreamController.stream.listen((loc) {
      if (loc.latitude.isNaN || loc.longitude.isNaN) return;

      if (mounted) {
        setState(() {
          _currentLocation = loc;
          if (_deliveryWaypoints.isNotEmpty) {
            _calculateDeliveryMetrics();
          }
        });
        try {
          _mapController.move(
            LatLng(loc.latitude, loc.longitude),
            _mapController.camera.zoom,
          );
        } catch (_) {}
        _updatePrivacyStates(loc);
      }
    });

    // Start activity classifier
    SmartLocation.activity.startMonitoring(_simStreamController.stream);
    _activitySub = SmartLocation.activity.activityStream.listen((act) {
      if (mounted) {
        setState(() {
          _currentActivity = act;
        });
      }
    });

    // Configure and start Geofencing
    SmartLocation.geofence.startMonitoring(_simStreamController.stream);
    SmartLocation.geofence.events.listen((event) {
      if (mounted) {
        setState(() {
          _geofenceEvents.insert(0, event);
        });
      }
    });

    // Configure and start Speed/G-Force monitoring
    SmartLocation.speed.configure(
      speedLimitKmh: _speedLimitKmh,
      suddenDecelThresholdKmhPerSec: _gForceThreshold,
      suddenAccelThresholdKmhPerSec: _gForceThreshold,
    );
    SmartLocation.speed.startMonitoring(_simStreamController.stream);

    _speedAlertSub = SmartLocation.speed.speedAlerts.listen((alert) {
      if (mounted) {
        setState(() {
          _driverAlertsLogs.insert(0, '[⚠️ SPEEDING] ${alert.message}');
        });
      }
    });

    _gForceAlertSub = SmartLocation.speed.gForceAlerts.listen((alert) {
      if (mounted) {
        setState(() {
          _driverAlertsLogs.insert(0, '[🚨 G-FORCE] ${alert.message}');
        });
      }
    });

    // Initialize with center location
    _updatePrivacyStates(_currentLocation);

    // Ensure initial mode is set correctly
    _toggleTrackingMode(_isSimulatorMode);
  }

  void _resetGeofences() {
    SmartLocation.geofence.clearGeofences();
    _activeGeofences.clear();

    final gfCenter = Geofence(
      id: 'Office_HQ',
      latitude: mapCenterLat,
      longitude: mapCenterLng,
      radiusInMeters: _geofenceRadius,
      triggerTypes: {
        GeofenceTrigger.enter,
        GeofenceTrigger.exit,
        GeofenceTrigger.dwell,
      },
      dwellDuration: const Duration(seconds: 5),
    );

    final gfNorthEast = Geofence(
      id: 'Logistics_Gate',
      latitude: mapCenterLat + 0.0013,
      longitude: mapCenterLng + 0.0012,
      radiusInMeters: 45.0,
      triggerTypes: {GeofenceTrigger.enter, GeofenceTrigger.exit},
    );

    final gfSouthWest = Geofence(
      id: 'Retail_Hub',
      latitude: mapCenterLat - 0.0014,
      longitude: mapCenterLng - 0.0015,
      radiusInMeters: 55.0,
      triggerTypes: {GeofenceTrigger.enter, GeofenceTrigger.exit},
    );

    SmartLocation.geofence.addGeofence(gfCenter);
    SmartLocation.geofence.addGeofence(gfNorthEast);
    SmartLocation.geofence.addGeofence(gfSouthWest);

    _activeGeofences.addAll([gfCenter, gfNorthEast, gfSouthWest]);
  }

  void _updatePrivacyStates(LocationData loc) {
    setState(() {
      _fuzzedLocation = PrivacyGuard.fuzzLocation(
        loc,
        radiusInMeters: _fuzzRadius,
      );
      _snappedLocation = PrivacyGuard.snapLocation(
        loc,
        gridSizeDegrees: _gridSizeDegrees,
      );
    });
  }

  // Toggle between simulated dragging and hardware GPS
  void _toggleTrackingMode(bool isSimulator) async {
    if (!isSimulator) {
      // Switch to Hardware GPS
      setState(() {
        _isSimulatorMode = false;
      });
      try {
        await SmartLocation.ensureReady();
        _hardwareStreamSub = SmartLocation.stream.listen(
          (loc) {
            _simStreamController.add(loc);
          },
          onError: (err) {
            ScaffoldMessenger.of(
              context,
            ).showSnackBar(SnackBar(content: Text('GPS Stream error: $err')));
          },
        );
      } catch (e) {
        setState(() {
          _isSimulatorMode = true;
        });
        ScaffoldMessenger.of(
          context,
        ).showSnackBar(SnackBar(content: Text('Failed to initialize GPS: $e')));
      }
    } else {
      // Switch back to Simulator drag mode
      _hardwareStreamSub?.cancel();
      _hardwareStreamSub = null;
      setState(() {
        _isSimulatorMode = true;
      });
    }
  }

  // Handle Dragging / Movement on the Canvas
  void _onCanvasInteract(Offset localOffset, Size size) {
    if (!_isSimulatorMode) return; // Drag disabled in Real GPS mode

    final double minLat = mapCenterLat - latRange / 2;
    final double maxLat = mapCenterLat + latRange / 2;
    final double minLng = mapCenterLng - lngRange / 2;
    final double maxLng = mapCenterLng + lngRange / 2;

    // Convert pixel to coordinate
    final pctX = localOffset.dx / size.width;
    final pctY = (size.height - localOffset.dy) / size.height;

    final lat = minLat + pctY * latRange;
    final lng = minLng + pctX * lngRange;

    // Boundary check
    if (lat < minLat || lat > maxLat || lng < minLng || lng > maxLng) return;

    final now = DateTime.now();
    double speed = 0.0;
    double heading = _currentLocation.heading;
    double accelLong = 0.0;
    double accelLat = 0.0;

    if (_prevDragLocation != null && _lastDragTime != null) {
      final double dist = SmartLocation.distanceBetween(
        _prevDragLocation!.latitude,
        _prevDragLocation!.longitude,
        lat,
        lng,
      );
      final double dt = now.difference(_lastDragTime!).inMilliseconds / 1000.0;
      if (dt > 0.02) {
        speed = dist / dt; // meters/sec
        if (dist > 0.2) {
          heading = SmartLocation.bearingBetween(
            _prevDragLocation!.latitude,
            _prevDragLocation!.longitude,
            lat,
            lng,
          );
        }

        // 1. Calculate Longitudinal acceleration (change in speed over time)
        final double dv = speed - _prevDragLocation!.speed;
        accelLong = dv / dt; // m/s^2

        // 2. Calculate Lateral acceleration (turning velocity centripetal force)
        final double dh = heading - _prevDragLocation!.heading;
        double diffHeading = (dh + 180) % 360 - 180; // Normalise to -180..180
        final double omega = (diffHeading * math.pi / 180) / dt; // Rads/sec
        accelLat = speed * omega; // Centripetal accel v * w
      }
    }

    // Convert physics units to G-forces (1G = 9.8 m/s^2)
    final double gLong = (accelLong / 9.8).clamp(-2.0, 2.0);
    final double gLat = (accelLat / 9.8).clamp(-2.0, 2.0);

    final newLoc = LocationData(
      latitude: lat,
      longitude: lng,
      accuracy: 5.0,
      altitude: 0.0,
      speed: speed,
      speedAccuracy: 0.0,
      heading: heading,
      timestamp: now,
      isMocked: true,
    );

    _prevDragLocation = newLoc;
    _lastDragTime = now;

    // Clear Dead Reckoning prediction when manually moving
    if (_drPredictedLocation != null) {
      setState(() {
        _drPredictedLocation = null;
      });
    }

    setState(() {
      _longG = gLong;
      _latG = gLat;
    });

    _simStreamController.add(newLoc);
  }

  // 2. Route Recorder controllers
  void _toggleRecording() {
    if (!_isRecording) {
      SmartLocation.recorder.start(_simStreamController.stream);
      _recordTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
        setState(() {
          _recordElapsed = SmartLocation.recorder.elapsedTime;
          _recordedDistance = SmartLocation.recorder.totalDistanceInMeters;
        });
      });
      setState(() {
        _isRecording = true;
        _isPaused = false;
        _recordedPath.clear();
        _simplifiedPath.clear();
      });
    } else {
      _stopRecording();
    }
  }

  void _togglePauseRecording() {
    if (_isPaused) {
      SmartLocation.recorder.resume();
      setState(() {
        _isPaused = false;
      });
    } else {
      SmartLocation.recorder.pause();
      setState(() {
        _isPaused = true;
      });
    }
  }

  void _stopRecording() {
    _recordTimer?.cancel();
    final path = SmartLocation.recorder.stop();
    setState(() {
      _isRecording = false;
      _isPaused = false;
      _recordedPath = path;
      _recordElapsed = Duration.zero;
      _recordedDistance = 0.0;
    });
  }

  void _simplifyPath() {
    if (_recordedPath.isEmpty) return;
    final simplified = SmartLocation.simplifyPath(_recordedPath, _rdpTolerance);
    setState(() {
      _simplifiedPath = simplified;
    });
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          'Reduced coordinates from ${_recordedPath.length} to ${simplified.length} (${((1 - (simplified.length / _recordedPath.length)) * 100).toStringAsFixed(1)}% savings).',
        ),
      ),
    );
  }

  // 5. Dead Reckoning simulator trigger
  void _triggerDeadReckoningDemo() async {
    if (_isDREnabled) return;
    setState(() {
      _isDREnabled = true;
      _drLogs.clear();
      _drLogs.add('[DR] Starting Dead Reckoning Watchdog...');
    });

    // Make sure we have speed and heading to extrapolate from
    final baselineLocation = LocationData(
      latitude: _currentLocation.latitude,
      longitude: _currentLocation.longitude,
      accuracy: 5.0,
      altitude: 0,
      speed: 12.0, // 12 m/s (~43 km/h)
      speedAccuracy: 0,
      heading: 45.0, // North East
      timestamp: DateTime.now(),
    );

    _simStreamController.add(baselineLocation);
    _drLogs.add('[DR] Stable GPS signal set (Speed: 43km/h, Heading: 45°).');

    // Simulate GPS signal loss by not sending any updates for 2 seconds.
    // Start Dead Reckoning to extrapolate
    SmartLocation.deadReckoning.start(
      gpsStream: _simStreamController.stream,
      onUpdate: (extrapolatedLoc) {
        if (mounted) {
          setState(() {
            _drPredictedLocation = extrapolatedLoc;
            _drLogs.insert(
              0,
              '[DR EXTRAPOLATION] Lat: ${extrapolatedLoc.latitude.toStringAsFixed(6)}, Lng: ${extrapolatedLoc.longitude.toStringAsFixed(6)} (isMock: ${extrapolatedLoc.isMocked})',
            );
          });
        }
      },
      staleThreshold: const Duration(seconds: 2),
      extrapolationInterval: const Duration(milliseconds: 500),
    );

    _drLogs.add('[DR] GPS Signal dropping in 1 second...');
    await Future.delayed(const Duration(seconds: 1));
    _drLogs.add(
      '[DR] ⚠️ GPS Signal LOST! Watchdog running extrapolation model...',
    );

    // Wait 5 seconds of extrapolation
    await Future.delayed(const Duration(seconds: 5));

    // Restore GPS stream signal
    _drLogs.add('[DR] 🔄 GPS Signal Regained. Resuming raw hardware stream.');
    final restoredLoc = LocationData(
      latitude: _drPredictedLocation?.latitude ?? _currentLocation.latitude,
      longitude:
          (_drPredictedLocation?.longitude ?? _currentLocation.longitude) +
          0.0003,
      accuracy: 5.0,
      altitude: 0,
      speed: 8.0,
      speedAccuracy: 0,
      heading: 90.0, // Swerving East
      timestamp: DateTime.now(),
    );
    _simStreamController.add(restoredLoc);

    SmartLocation.deadReckoning.stop();
    setState(() {
      _isDREnabled = false;
      _drPredictedLocation = null;
    });
  }

  // Map Matching simulator trigger
  void _triggerMapMatchingDemo() async {
    if (_isMapMatchingEnabled) return;
    setState(() {
      _isMapMatchingEnabled = true;
      _mapMatchingLogs.clear();
      _mapMatchingLogs.add('[MapMatching] Generating noisy GPS path...');
    });

    await Future.delayed(const Duration(milliseconds: 800));

    // Dummy "noisy" GPS path
    List<LocationData> noisyPath = [
      LocationData(
        latitude: mapCenterLat,
        longitude: mapCenterLng,
        accuracy: 20,
        altitude: 0,
        speed: 0,
        speedAccuracy: 0,
        heading: 0,
      ),
      LocationData(
        latitude: mapCenterLat + 0.0001,
        longitude: mapCenterLng + 0.0005,
        accuracy: 25,
        altitude: 0,
        speed: 0,
        speedAccuracy: 0,
        heading: 0,
      ), // Drifted
      LocationData(
        latitude: mapCenterLat + 0.0002,
        longitude: mapCenterLng + 0.0008,
        accuracy: 30,
        altitude: 0,
        speed: 0,
        speedAccuracy: 0,
        heading: 0,
      ), // Drifted
    ];

    // Dummy OSM road segment to snap to
    List<LocationData> roadSegment = [
      LocationData(
        latitude: mapCenterLat,
        longitude: mapCenterLng,
        accuracy: 5,
        altitude: 0,
        speed: 0,
        speedAccuracy: 0,
        heading: 0,
      ),
      LocationData(
        latitude: mapCenterLat + 0.0002,
        longitude: mapCenterLng + 0.0009,
        accuracy: 5,
        altitude: 0,
        speed: 0,
        speedAccuracy: 0,
        heading: 0,
      ),
    ];

    setState(() {
      _mapMatchingLogs.add(
        '[MapMatching] Snapping points to nearest OSM road segment in background Isolate...',
      );
    });

    final mapMatcher = SmartLocation.mapMatching;
    final snappedPath = await mapMatcher.snapToNetworkAsync(
      noisyPath,
      roadSegment,
    );

    setState(() {
      _mapMatchingLogs.add(
        '✅ [MapMatching] Points successfully snapped to road network!',
      );
      _mapMatchingLogs.add(
        'Original offset point: ${noisyPath[1].latitude}, ${noisyPath[1].longitude}',
      );
      _mapMatchingLogs.add(
        'Snapped point: ${snappedPath[1].latitude}, ${snappedPath[1].longitude}',
      );
      _isMapMatchingEnabled = false;
    });
  }

  // 6. Indoor Centroid calculation
  void _calculateIndoorPosition() {
    final lobby = LocationData(
      latitude: mapCenterLat,
      longitude: mapCenterLng,
      accuracy: 2.0,
      altitude: 0,
      speed: 0,
      speedAccuracy: 0,
      heading: 0,
    );
    final confRoom = LocationData(
      latitude: mapCenterLat + 0.0009,
      longitude: mapCenterLng + 0.0012,
      accuracy: 3.0,
      altitude: 0,
      speed: 0,
      speedAccuracy: 0,
      heading: 0,
    );
    final kitchen = LocationData(
      latitude: mapCenterLat - 0.0010,
      longitude: mapCenterLng - 0.0008,
      accuracy: 4.0,
      altitude: 0,
      speed: 0,
      speedAccuracy: 0,
      heading: 0,
    );

    SmartLocation.indoor.registerBeacons({
      'Lobby_AP': lobby,
      'ConfRoom_AP': confRoom,
      'Kitchen_AP': kitchen,
    });

    final signals = {
      'Lobby_AP': _rssiLobby,
      'ConfRoom_AP': _rssiConfRoom,
      'Kitchen_AP': _rssiKitchen,
    };

    final estimated = SmartLocation.indoor.locateBySignals(signals);

    setState(() {
      _indoorCentroid = estimated;
      _indoorLogs.clear();
      _indoorLogs.add('WiFi Scanning metrics:');
      signals.forEach(
        (ssid, rssi) => _indoorLogs.add(' • $ssid RSSI: ${rssi.round()} dBm'),
      );
      if (estimated != null) {
        _indoorLogs.add('📍 Weighted Centroid estimated successfully!');
        _indoorLogs.add('Lat: ${estimated.latitude.toStringAsFixed(6)}');
        _indoorLogs.add('Lng: ${estimated.longitude.toStringAsFixed(6)}');
      } else {
        _indoorLogs.add('❌ Signals too weak to solve centroid.');
      }
    });
  }

  // Open modal bottom sheet displaying beautiful raw GeoJSON code
  void _exportGeoJson(Map<String, dynamic> geoJsonMap, String title) {
    final prettyString = const JsonEncoder.withIndent('  ').convert(geoJsonMap);
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: const Color(0xFF141828),
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (context) {
        return Container(
          height: MediaQuery.of(context).size.height * 0.75,
          padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Drag handle
              Center(
                child: Container(
                  width: 36,
                  height: 4,
                  decoration: BoxDecoration(
                    color: Colors.white24,
                    borderRadius: BorderRadius.circular(2),
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Row(
                children: [
                  Container(
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: const Color(0xFF7C86C9).withValues(alpha: 0.1),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: const Icon(
                      Icons.code,
                      color: Color(0xFF7C86C9),
                      size: 16,
                    ),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    child: Text(
                      title,
                      style: const TextStyle(
                        fontSize: 15,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                  ),
                  IconButton(
                    icon: const Icon(
                      Icons.copy_outlined,
                      color: Color(0xFF7C86C9),
                      size: 20,
                    ),
                    onPressed: () {
                      Clipboard.setData(ClipboardData(text: prettyString));
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text('Copied GeoJSON to clipboard!'),
                        ),
                      );
                    },
                  ),
                ],
              ),
              const SizedBox(height: 12),
              Expanded(
                child: Container(
                  decoration: const BoxDecoration(
                    color: Color(0xFF141828),
                    borderRadius: BorderRadius.all(Radius.circular(12)),
                    boxShadow: [
                      BoxShadow(
                        color: Color(0xFF0D1020),
                        offset: Offset(3, 3),
                        blurRadius: 8,
                      ),
                      BoxShadow(
                        color: Color(0xFF252D45),
                        offset: Offset(-1, -1),
                        blurRadius: 4,
                      ),
                    ],
                  ),
                  child: Column(
                    children: [
                      Container(
                        width: double.infinity,
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 7,
                        ),
                        decoration: const BoxDecoration(
                          color: Color(0xFF1D2236),
                          borderRadius: BorderRadius.vertical(
                            top: Radius.circular(11),
                          ),
                        ),
                        child: Row(
                          children: [
                            Container(
                              width: 8,
                              height: 8,
                              decoration: const BoxDecoration(
                                color: Colors.redAccent,
                                shape: BoxShape.circle,
                              ),
                            ),
                            const SizedBox(width: 5),
                            Container(
                              width: 8,
                              height: 8,
                              decoration: const BoxDecoration(
                                color: Colors.orangeAccent,
                                shape: BoxShape.circle,
                              ),
                            ),
                            const SizedBox(width: 5),
                            Container(
                              width: 8,
                              height: 8,
                              decoration: const BoxDecoration(
                                color: Color(0xFF34D399),
                                shape: BoxShape.circle,
                              ),
                            ),
                            const SizedBox(width: 10),
                            const Text(
                              'route.geojson',
                              style: TextStyle(
                                fontSize: 9,
                                color: Colors.grey,
                                fontFamily: 'monospace',
                              ),
                            ),
                          ],
                        ),
                      ),
                      Expanded(
                        child: SingleChildScrollView(
                          padding: const EdgeInsets.all(12),
                          child: Text(
                            prettyString,
                            style: const TextStyle(
                              fontFamily: 'monospace',
                              fontSize: 11,
                              color: Colors.greenAccent,
                              height: 1.5,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              FilledButton(
                onPressed: () => Navigator.pop(context),
                style: FilledButton.styleFrom(
                  backgroundColor: const Color(0xFF7C86C9),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                  padding: const EdgeInsets.symmetric(vertical: 14),
                ),
                child: const Text(
                  'Close Viewer',
                  style: TextStyle(
                    color: Colors.black,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _hardwareStreamSub?.cancel();
    _activitySub?.cancel();
    _recordTimer?.cancel();
    _speedAlertSub?.cancel();
    _gForceAlertSub?.cancel();
    _radarAnimationController.dispose();
    SmartLocation.geofence.dispose();
    SmartLocation.speed.dispose();
    SmartLocation.activity.dispose();
    SmartLocation.deadReckoning.stop();
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final double currentSpeedKmh = _currentLocation.speed * 3.6;

    return Scaffold(
      appBar: AppBar(
        title: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              padding: const EdgeInsets.all(7),
              decoration: const BoxDecoration(
                color: Color(0xFF252D45),
                borderRadius: BorderRadius.all(Radius.circular(10)),
                boxShadow: [
                  BoxShadow(
                    color: Color(0xFF0D1020),
                    offset: Offset(3, 3),
                    blurRadius: 6,
                  ),
                  BoxShadow(
                    color: Color(0xFF2D3550),
                    offset: Offset(-2, -2),
                    blurRadius: 5,
                  ),
                ],
              ),
              child: const Icon(
                Icons.my_location,
                color: Color(0xFF7C86C9),
                size: 16,
              ),
            ),
            const SizedBox(width: 10),
            const Text(
              'Smart Location',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
                letterSpacing: 0.5,
                color: Colors.white,
              ),
            ),
          ],
        ),
        backgroundColor: const Color(0xFF141828),
        actions: [
          Container(
            margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
            decoration: const BoxDecoration(
              color: Color(0xFF252D45),
              borderRadius: BorderRadius.all(Radius.circular(20)),
              boxShadow: [
                BoxShadow(
                  color: Color(0xFF0D1020),
                  offset: Offset(3, 3),
                  blurRadius: 7,
                ),
                BoxShadow(
                  color: Color(0xFF2D3550),
                  offset: Offset(-2, -2),
                  blurRadius: 5,
                ),
              ],
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  _isSimulatorMode ? 'SIM' : 'GPS',
                  style: TextStyle(
                    fontSize: 10,
                    fontWeight: FontWeight.bold,
                    color: _isSimulatorMode
                        ? Colors.orangeAccent.shade100
                        : const Color(0xFF34D399),
                    letterSpacing: 1.2,
                  ),
                ),
                Switch(
                  value: _isSimulatorMode,
                  activeThumbColor: Colors.orangeAccent,
                  inactiveThumbColor: const Color(0xFF34D399),
                  inactiveTrackColor: const Color(
                    0xFF34D399,
                  ).withValues(alpha: 0.25),
                  materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                  onChanged: _toggleTrackingMode,
                ),
              ],
            ),
          ),
          const SizedBox(width: 4),
        ],
      ),
      bottomNavigationBar: Container(
        decoration: const BoxDecoration(
          color: Color(0xFF1A1E2E),
          boxShadow: [
            BoxShadow(
              color: Color(0xFF0D1020),
              offset: Offset(0, -5),
              blurRadius: 14,
              spreadRadius: 1,
            ),
            BoxShadow(
              color: Color(0xFF252D45),
              offset: Offset(0, -1),
              blurRadius: 6,
            ),
          ],
        ),
        child: SafeArea(
          child: TabBar(
            controller: _tabController,
            isScrollable: true,
            tabAlignment: TabAlignment.start,
            dividerColor: Colors.transparent,
            indicator: const UnderlineTabIndicator(
              borderSide: BorderSide(color: Color(0xFF7C86C9), width: 3),
              borderRadius: BorderRadius.all(Radius.circular(3)),
            ),
            indicatorColor: const Color(0xFF7C86C9),
            labelColor: const Color(0xFF7C86C9),
            unselectedLabelColor: const Color(0xFF5C6480),
            labelStyle: const TextStyle(
              fontSize: 11,
              fontWeight: FontWeight.w600,
              letterSpacing: 0.3,
            ),
            unselectedLabelStyle: const TextStyle(
              fontSize: 11,
              fontWeight: FontWeight.w400,
            ),
            onTap: (index) {
              setState(() {
                _showIndoorBeacons =
                    (index == 5); // Show beacons in Advanced Tab
                _showSpatialGrid = (index == 5);
              });
            },
            tabs: const [
              Tab(icon: Icon(Icons.map, size: 22), text: 'Map'),
              Tab(icon: Icon(Icons.local_shipping, size: 22), text: 'Delivery'),
              Tab(icon: Icon(Icons.speed, size: 22), text: 'Meters'),
              Tab(icon: Icon(Icons.adjust, size: 22), text: 'Geofence'),
              Tab(icon: Icon(Icons.route, size: 22), text: 'Recorder'),
              Tab(icon: Icon(Icons.warning_amber, size: 22), text: 'Safety'),
              Tab(icon: Icon(Icons.privacy_tip, size: 22), text: 'Privacy'),
              Tab(
                icon: Icon(Icons.developer_board, size: 22),
                text: 'Advanced',
              ),
              Tab(icon: Icon(Icons.cloud_sync, size: 22), text: 'Sync'),
            ],
          ),
        ),
      ),
      body: SafeArea(
        top: false,
        bottom: false,
        child: Column(
          children: [
            // 1. Drag Sandbox Map Canvas
            if (_isSimulatorMode)
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 12,
                ),
                decoration: const BoxDecoration(
                  color: Color(0xFF1F2439),
                  boxShadow: [
                    BoxShadow(
                      color: Color(0xFF0D1020),
                      offset: Offset(0, 4),
                      blurRadius: 10,
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: [
                        Row(
                          children: [
                            Container(
                              width: 8,
                              height: 8,
                              decoration: BoxDecoration(
                                color: _isSimulatorMode
                                    ? Colors.orangeAccent
                                    : const Color(0xFF34D399),
                                shape: BoxShape.circle,
                              ),
                            ),
                            const SizedBox(width: 6),
                            Expanded(
                              child: Text(
                                _isSimulatorMode
                                    ? 'Simulator Active: Drag Pin'
                                    : 'Real Hardware GPS Active',
                                style: TextStyle(
                                  fontSize: 12,
                                  fontWeight: FontWeight.bold,
                                  color: _isSimulatorMode
                                      ? Colors.orangeAccent
                                      : const Color(0xFF34D399),
                                ),
                                overflow: TextOverflow.ellipsis,
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 4),
                        Text(
                          'Velocity: ${currentSpeedKmh.toStringAsFixed(1)} km/h  •  ${_getActivityIcon(_currentActivity)} ${_currentActivity.name.toUpperCase()}',
                          style: const TextStyle(
                            fontSize: 11,
                            color: Colors.grey,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 10),
                    LayoutBuilder(
                      builder: (context, constraints) {
                        final screenHeight = MediaQuery.of(context).size.height;
                        final double maxCanvasSize = screenHeight < 750
                            ? 220.0
                            : 300.0;
                        final size = math.min(
                          constraints.maxWidth,
                          maxCanvasSize,
                        );
                        return Center(
                          child: Container(
                            width: size,
                            height: size,
                            decoration: BoxDecoration(
                              borderRadius: BorderRadius.circular(16),
                              boxShadow: const [
                                BoxShadow(
                                  color: Color(0xFF0D1020),
                                  offset: Offset(8, 8),
                                  blurRadius: 20,
                                  spreadRadius: 2,
                                ),
                                BoxShadow(
                                  color: Color(0xFF252D45),
                                  offset: Offset(-4, -4),
                                  blurRadius: 12,
                                ),
                              ],
                            ),
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(14),
                              child: GestureDetector(
                                onPanStart: (details) => _onCanvasInteract(
                                  details.localPosition,
                                  Size(size, size),
                                ),
                                onPanUpdate: (details) => _onCanvasInteract(
                                  details.localPosition,
                                  Size(size, size),
                                ),
                                child: CustomPaint(
                                  size: Size(size, size),
                                  painter: LocationSandboxPainter(
                                    repaint: _radarAnimationController,
                                    currentLocation: _currentLocation,
                                    activeGeofences: _activeGeofences,
                                    geofenceRadius: _geofenceRadius,
                                    recordedPath: _recordedPath,
                                    simplifiedPath: _simplifiedPath,
                                    showSpatialGrid: _showSpatialGrid,
                                    showIndoorBeacons: _showIndoorBeacons,
                                    beaconSignals: {
                                      'Lobby_AP': _rssiLobby,
                                      'ConfRoom_AP': _rssiConfRoom,
                                      'Kitchen_AP': _rssiKitchen,
                                    },
                                    indoorCentroid: _indoorCentroid,
                                    fuzzedLocation: _fuzzedLocation,
                                    snappedLocation: _snappedLocation,
                                    fuzzRadius: _fuzzRadius,
                                    showPrivacyInfo:
                                        _tabController.index ==
                                        4, // Privacy Tab
                                    drPredictedLocation: _drPredictedLocation,
                                    radarValue: _radarAnimationController.value,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),

            // 3. Tab Body
            Expanded(
              child: TabBarView(
                controller: _tabController,
                physics:
                    const NeverScrollableScrollPhysics(), // Prevent swipe interfering with map pan
                children: [
                  _buildLiveMapTab(),
                  _buildDeliveryTab(),
                  _buildMetersTab(currentSpeedKmh),
                  _buildGeofenceTab(),
                  _buildRecorderTab(),
                  _buildSafetyTab(),
                  _buildPrivacyTab(),
                  _buildAdvancedTab(),
                  _buildSyncTab(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  String _getActivityIcon(ActivityType activity) {
    switch (activity) {
      case ActivityType.stationary:
        return '🧍';
      case ActivityType.walking:
        return '🚶';
      case ActivityType.running:
        return '🏃';
      case ActivityType.cycling:
        return '🚴';
      case ActivityType.driving:
        return '🚗';
      default:
        return '❓';
    }
  }

  // --- TAB 0: LIVE MAP ---
  Widget _buildLiveMapTab() {
    return Stack(
      children: [
        FlutterMap(
          mapController: _mapController,
          options: MapOptions(
            initialCenter: LatLng(
              _currentLocation.latitude,
              _currentLocation.longitude,
            ),
            initialZoom: 12.0,
            minZoom: 3.0,
            maxZoom: 19.0,
            onPositionChanged: (position, hasGesture) {
              if (mounted && position.zoom != _currentZoom) {
                setState(() {
                  _currentZoom = position.zoom;
                });
              }
            },
          ),
          children: [
            TileLayer(
              urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
              userAgentPackageName: 'com.antigravity.smart_location.example',
            ),
            PolylineLayer(
              polylines: [
                if (_recordedPath.isNotEmpty)
                  Polyline(
                    points: _recordedPath
                        .map((loc) => LatLng(loc.latitude, loc.longitude))
                        .toList(),
                    strokeWidth: 4.0,
                    color: const Color(0xFF7C86C9),
                  ),
                if (_deliveryRouteCoords.isNotEmpty)
                  Polyline(
                    points: _deliveryRouteCoords,
                    strokeWidth: 5.0,
                    color: const Color(
                      0xFF7C86C9,
                    ), // Royal Blue for delivery route
                  ),
              ],
            ),
            MarkerLayer(
              markers: [
                Marker(
                  width: 60.0,
                  height: 60.0,
                  point: LatLng(
                    _currentLocation.latitude,
                    _currentLocation.longitude,
                  ),
                  child: GestureDetector(
                    onTap: () => _showCurrentLocationDetails(),
                    child: Container(
                      decoration: BoxDecoration(
                        color: const Color(0xFF7C86C9).withValues(alpha: 0.2),
                        shape: BoxShape.circle,
                      ),
                      child: Center(
                        child: Container(
                          width: 36,
                          height: 36,
                          decoration: BoxDecoration(
                            color: const Color(
                              0xFF3B82F6,
                            ), // Distinct blue for current location
                            shape: BoxShape.circle,
                            border: Border.all(color: Colors.white, width: 3),
                            boxShadow: const [
                              BoxShadow(
                                color: Colors.black38,
                                blurRadius: 6,
                                offset: Offset(0, 3),
                              ),
                            ],
                          ),
                          child: const Center(
                            child: Icon(
                              Icons.my_location,
                              color: Colors.white,
                              size: 20,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
                ..._deliveryWaypoints.asMap().entries.map((entry) {
                  final index = entry.key;
                  final wp = entry.value;
                  return Marker(
                    width: 80.0,
                    height: 80.0,
                    point: LatLng(wp.latitude, wp.longitude),
                    child: GestureDetector(
                      onTap: () => _showWaypointDetails(index, wp),
                      child: Column(
                        children: [
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 10,
                              vertical: 6,
                            ),
                            decoration: BoxDecoration(
                              color: index == 0
                                  ? const Color(0xFF34D399)
                                  : Colors.grey.shade600,
                              borderRadius: BorderRadius.circular(16),
                              border: Border.all(color: Colors.white, width: 2),
                              boxShadow: const [
                                BoxShadow(
                                  color: Colors.black26,
                                  blurRadius: 4,
                                  offset: Offset(0, 2),
                                ),
                              ],
                            ),
                            child: Text(
                              'Stop ${index + 1}',
                              style: const TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 14,
                              ),
                            ),
                          ),
                          Icon(
                            Icons.location_on,
                            color: index == 0
                                ? const Color(0xFF34D399)
                                : Colors.grey.shade600,
                            size: 36,
                          ),
                        ],
                      ),
                    ),
                  );
                }),
              ],
            ),
          ],
        ),
        Positioned(
          top: 10,
          right: 10,
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            decoration: BoxDecoration(
              color: const Color(0xFF1F2439).withValues(alpha: 0.9),
              borderRadius: BorderRadius.circular(20),
              boxShadow: const [
                BoxShadow(
                  color: Color(0xFF0D1020),
                  blurRadius: 4,
                  offset: Offset(2, 2),
                ),
              ],
            ),
            child: Text(
              'Zoom: ${_currentZoom.toStringAsFixed(1)}',
              style: const TextStyle(
                color: Color(0xFF7C86C9),
                fontWeight: FontWeight.bold,
                fontSize: 12,
              ),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildMetricHeader(String title) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 10.0),
      child: Row(
        children: [
          Container(
            width: 3,
            height: 20,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(2),
              gradient: const LinearGradient(
                colors: [Color(0xFF7C86C9), Color(0xFF34D399)],
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
              ),
            ),
          ),
          const SizedBox(width: 10),
          Text(
            title,
            style: const TextStyle(
              fontSize: 15,
              fontWeight: FontWeight.bold,
              color: Colors.white,
              letterSpacing: 0.4,
            ),
          ),
        ],
      ),
    );
  }

  // --- TAB 1: DELIVERY & RIDESHARE ---
  Widget _buildDeliveryTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _buildMetricHeader('Vehicle Setup & Search'),
          const SizedBox(height: 16),
          SegmentedButton<String>(
            segments: const [
              ButtonSegment(
                value: 'driving',
                icon: Icon(Icons.directions_car),
                label: Text('Car'),
              ),
              ButtonSegment(
                value: 'bike',
                icon: Icon(Icons.pedal_bike),
                label: Text('Bike'),
              ),
              ButtonSegment(
                value: 'foot',
                icon: Icon(Icons.directions_walk),
                label: Text('Walk'),
              ),
            ],
            selected: {_selectedVehicle},
            onSelectionChanged: (val) {
              setState(() => _selectedVehicle = val.first);
              _fetchRoute();
              _calculateDeliveryMetrics();
            },
          ),
          const SizedBox(height: 16),
          TextField(
            controller: _searchController,
            keyboardType: TextInputType.streetAddress,
            enableSuggestions: true,
            autocorrect: true,
            textCapitalization: TextCapitalization.words,
            decoration: InputDecoration(
              hintText: 'Search for a stop...',
              prefixIcon: const Icon(Icons.search),
              suffixIcon: _isSearching
                  ? const Padding(
                      padding: EdgeInsets.all(12.0),
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : null,
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(8),
              ),
            ),
            onChanged: (val) {
              if (val.length == 6 && RegExp(r'^\d{6}$').hasMatch(val)) {
                _searchAndAddPincode(val);
              } else if (val.length > 2) {
                _searchLocation(val);
              } else if (val.isEmpty) {
                setState(() => _searchResults.clear());
              }
            },
          ),
          if (_searchResults.isNotEmpty)
            Container(
              margin: const EdgeInsets.only(top: 8),
              decoration: const BoxDecoration(
                color: Color(0xFF1F2439),
                borderRadius: BorderRadius.all(Radius.circular(12)),
                boxShadow: [
                  BoxShadow(
                    color: Color(0xFF0D1020),
                    offset: Offset(4, 4),
                    blurRadius: 10,
                  ),
                  BoxShadow(
                    color: Color(0xFF252D45),
                    offset: Offset(-2, -2),
                    blurRadius: 6,
                  ),
                ],
              ),
              child: ListView.builder(
                shrinkWrap: true,
                physics: const NeverScrollableScrollPhysics(),
                itemCount: _searchResults.length,
                itemBuilder: (ctx, idx) {
                  final res = _searchResults[idx];
                  return ListTile(
                    dense: true,
                    leading: const Icon(
                      Icons.location_on,
                      color: Color(0xFF7C86C9),
                      size: 18,
                    ),
                    title: Text(
                      res['display_name'] ?? 'Unknown',
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(color: Colors.white, fontSize: 13),
                    ),
                    onTap: () => _addWaypointFromSearch(res),
                  );
                },
              ),
            ),
          const SizedBox(height: 24),
          Row(
            children: [
              Expanded(
                child: ElevatedButton.icon(
                  onPressed:
                      _deliveryWaypoints.isNotEmpty && _journeySub == null
                      ? () {
                          SmartLocation.journey.startJourney(
                            stops: _deliveryWaypoints,
                            gpsStream: _simStreamController.stream,
                          );
                          _segmentStartTime = DateTime.now();
                          _lastDepartureNode = _currentLocation;
                          _journeySub?.cancel();
                          _journeySub = SmartLocation.journey.arrivals.listen((
                            event,
                          ) {
                            if (mounted) {
                              // ML Hook: Record actual trip duration
                              if (_segmentStartTime != null &&
                                  _lastDepartureNode != null) {
                                final actualDuration = DateTime.now()
                                    .difference(_segmentStartTime!);
                                final fromNode =
                                    '${_lastDepartureNode!.latitude},${_lastDepartureNode!.longitude}';
                                final toNode =
                                    '${event.waypoint.latitude},${event.waypoint.longitude}';
                                SmartLocation.routeLearning.recordTrip(
                                  fromNode,
                                  toNode,
                                  actualDuration,
                                );
                              }

                              setState(() {
                                if (_deliveryWaypoints.isNotEmpty) {
                                  _lastDepartureNode =
                                      _deliveryWaypoints[0]; // Next segment starts from this stop
                                  _segmentStartTime = DateTime.now();
                                  _deliveryWaypoints.removeAt(0);
                                  _calculateDeliveryMetrics();
                                }
                              });
                              _fetchRoute();
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(
                                  content: Text(
                                    'Arrived at Stop ${event.stopIndex + 1}! ML Engine Learned Time.',
                                  ),
                                  backgroundColor: const Color(0xFF34D399),
                                ),
                              );
                            }
                          });
                          setState(() {});
                        }
                      : null,
                  icon: const Icon(Icons.route),
                  label: const Text('Start Route'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: const Color(0xFF1E3A30),
                    foregroundColor: const Color(0xFF34D399),
                    padding: const EdgeInsets.symmetric(vertical: 16),
                    shadowColor: Colors.transparent,
                  ),
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _journeySub != null ? _cancelDelivery : null,
                  icon: const Icon(Icons.stop),
                  label: const Text('Cancel Route'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: const Color(0xFF3A1E22),
                    foregroundColor: Colors.redAccent,
                    padding: const EdgeInsets.symmetric(vertical: 16),
                    shadowColor: Colors.transparent,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 24),
          Row(
            children: [
              Expanded(
                child: _buildDigitalMetricCard(
                  'Remaining',
                  '${(_remainingDeliveryDistance / 1000).toStringAsFixed(2)} km',
                  subtitle: 'Distance',
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: _buildDigitalMetricCard(
                  'ETA',
                  '$_etaMinutes min',
                  subtitle: 'Time to finish',
                ),
              ),
            ],
          ),
          if (_deliveryWaypoints.isNotEmpty) ...[
            const SizedBox(height: 24),
            _buildMetricHeader('Upcoming Stops'),
            ListView.builder(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: _deliveryWaypoints.length,
              itemBuilder: (context, index) {
                final wp = _deliveryWaypoints[index];
                final address =
                    _locationNames['${wp.latitude},${wp.longitude}'] ??
                    'Lat: ${wp.latitude.toStringAsFixed(4)}, Lng: ${wp.longitude.toStringAsFixed(4)}';
                return Container(
                  margin: const EdgeInsets.only(top: 8),
                  decoration: BoxDecoration(
                    color: const Color(0xFF1F2439),
                    borderRadius: BorderRadius.circular(12),
                    boxShadow: index == 0
                        ? const [
                            BoxShadow(
                              color: Color(0xFF0D1020),
                              offset: Offset(4, 4),
                              blurRadius: 10,
                            ),
                            BoxShadow(
                              color: Color(0xFF1A3040),
                              offset: Offset(-2, -2),
                              blurRadius: 6,
                            ),
                          ]
                        : const [
                            BoxShadow(
                              color: Color(0xFF0D1020),
                              offset: Offset(4, 4),
                              blurRadius: 10,
                            ),
                            BoxShadow(
                              color: Color(0xFF252D45),
                              offset: Offset(-2, -2),
                              blurRadius: 6,
                            ),
                          ],
                  ),
                  child: ListTile(
                    contentPadding: const EdgeInsets.symmetric(
                      horizontal: 14,
                      vertical: 4,
                    ),
                    leading: CircleAvatar(
                      backgroundColor: index == 0
                          ? const Color(0xFF34D399)
                          : Colors.grey.shade700,
                      child: Text(
                        '${index + 1}',
                        style: const TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontSize: 13,
                        ),
                      ),
                    ),
                    title: Text(
                      index == 0 ? 'Next Destination' : 'Stop ${index + 1}',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        color: index == 0
                            ? const Color(0xFF34D399)
                            : Colors.white,
                        fontSize: 13,
                      ),
                    ),
                    subtitle: Text(
                      address,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(color: Colors.grey, fontSize: 11),
                    ),
                    trailing: IconButton(
                      icon: const Icon(
                        Icons.delete_outline,
                        color: Colors.redAccent,
                        size: 20,
                      ),
                      onPressed: () {
                        setState(() {
                          _deliveryWaypoints.removeAt(index);
                          _fetchRoute();
                          _calculateDeliveryMetrics();
                        });
                      },
                    ),
                    onTap: () {
                      _tabController.animateTo(0);
                      _mapController.move(
                        LatLng(wp.latitude, wp.longitude),
                        16,
                      );
                    },
                  ),
                );
              },
            ),
          ],
        ],
      ),
    );
  }

  void _cancelDelivery() {
    SmartLocation.journey.stopJourney();
    _journeySub?.cancel();
    setState(() {
      _deliveryWaypoints.clear();
      _deliveryRouteCoords.clear();
      _remainingDeliveryDistance = 0.0;
      _etaMinutes = 0;
    });
  }

  Future<void> _fetchRoute() async {
    if (_deliveryWaypoints.isEmpty) return;

    final points = [_currentLocation, ..._deliveryWaypoints];
    final coordsString = points
        .map((p) => '${p.longitude},${p.latitude}')
        .join(';');

    try {
      final uri = Uri.parse(
        'https://router.project-osrm.org/route/v1/$_selectedVehicle/$coordsString?overview=full&geometries=geojson',
      );
      final response = await http.get(uri);
      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        if (data['code'] == 'Ok' &&
            data['routes'] != null &&
            data['routes'].isNotEmpty) {
          final coordinates =
              data['routes'][0]['geometry']['coordinates'] as List;
          if (mounted) {
            setState(() {
              _deliveryRouteCoords = coordinates
                  .map((c) => LatLng(c[1] as double, c[0] as double))
                  .toList();
            });
          }
        }
      }
    } catch (e) {
      debugPrint('Routing failed: $e');
    }
  }

  Future<String> _reverseGeocode(double lat, double lon) async {
    final key = '$lat,$lon';
    if (_locationNames.containsKey(key)) return _locationNames[key]!;

    try {
      final uri = Uri.parse(
        'https://nominatim.openstreetmap.org/reverse?format=json&lat=$lat&lon=$lon',
      );
      final response = await http.get(
        uri,
        headers: {'User-Agent': 'SmartLocationDeliveryApp/1.0'},
      );
      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        final name = data['display_name'] as String?;
        if (name != null) {
          _locationNames[key] = name;
          return name;
        }
      }
    } catch (e) {
      debugPrint('Reverse geocode failed: $e');
    }
    return 'Unknown Location';
  }

  void _showCurrentLocationDetails() {
    showModalBottomSheet(
      context: context,
      backgroundColor: const Color(0xFF141828),
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (context) {
        String address =
            _locationNames['${_currentLocation.latitude},${_currentLocation.longitude}'] ??
            'Fetching address...';
        return StatefulBuilder(
          builder: (context, setModalState) {
            if (address == 'Fetching address...') {
              _reverseGeocode(
                _currentLocation.latitude,
                _currentLocation.longitude,
              ).then((value) {
                if (mounted) setModalState(() => address = value);
              });
            }
            return Padding(
              padding: const EdgeInsets.fromLTRB(24, 16, 24, 32),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Center(
                    child: Container(
                      width: 36,
                      height: 4,
                      decoration: BoxDecoration(
                        color: Colors.white24,
                        borderRadius: BorderRadius.circular(2),
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),
                  Row(
                    children: [
                      const CircleAvatar(
                        backgroundColor: Color(0xFF7C86C9),
                        radius: 22,
                        child: Icon(
                          Icons.person_pin_circle,
                          color: Colors.black,
                          size: 20,
                        ),
                      ),
                      const SizedBox(width: 14),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: const [
                            Text(
                              'Current Location',
                              style: TextStyle(
                                fontSize: 18,
                                fontWeight: FontWeight.bold,
                                color: Colors.white,
                              ),
                            ),
                            Text(
                              'Your live position',
                              style: TextStyle(
                                fontSize: 12,
                                color: Colors.grey,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 20),
                  const Divider(color: Colors.white10),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.location_city,
                        color: Color(0xFF7C86C9),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          address,
                          style: const TextStyle(
                            fontWeight: FontWeight.w500,
                            color: Colors.white,
                            fontSize: 13,
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.pin_drop,
                        color: Color(0xFF7C86C9),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      const Text(
                        'Coordinates  ',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.grey,
                          fontSize: 12,
                        ),
                      ),
                      Text(
                        '${_currentLocation.latitude.toStringAsFixed(5)}, ${_currentLocation.longitude.toStringAsFixed(5)}',
                        style: const TextStyle(
                          color: Colors.white,
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.speed,
                        color: Color(0xFF7C86C9),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      const Text(
                        'Speed  ',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.grey,
                          fontSize: 12,
                        ),
                      ),
                      Text(
                        '${(_currentLocation.speed * 3.6).toStringAsFixed(1)} km/h',
                        style: const TextStyle(
                          color: Colors.white,
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 24),
                  FilledButton(
                    onPressed: () => Navigator.pop(context),
                    style: FilledButton.styleFrom(
                      backgroundColor: const Color(0xFF7C86C9),
                      foregroundColor: Colors.black,
                      padding: const EdgeInsets.symmetric(vertical: 14),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: const Text(
                      'Close',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }

  void _showWaypointDetails(int index, LocationData wp) {
    // Calculate distance from current location
    final dist = SmartLocation.distanceBetween(
      _currentLocation.latitude,
      _currentLocation.longitude,
      wp.latitude,
      wp.longitude,
    );

    showModalBottomSheet(
      context: context,
      backgroundColor: const Color(0xFF141828),
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (context) {
        String address =
            _locationNames['${wp.latitude},${wp.longitude}'] ??
            'Fetching address...';
        return StatefulBuilder(
          builder: (context, setModalState) {
            if (address == 'Fetching address...') {
              _reverseGeocode(wp.latitude, wp.longitude).then((value) {
                if (mounted) setModalState(() => address = value);
              });
            }
            return Padding(
              padding: const EdgeInsets.fromLTRB(24, 16, 24, 32),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Center(
                    child: Container(
                      width: 36,
                      height: 4,
                      decoration: BoxDecoration(
                        color: Colors.white24,
                        borderRadius: BorderRadius.circular(2),
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),
                  Row(
                    children: [
                      CircleAvatar(
                        backgroundColor: index == 0
                            ? const Color(0xFF34D399)
                            : Colors.grey.shade700,
                        radius: 22,
                        child: const Icon(
                          Icons.local_shipping,
                          color: Colors.white,
                          size: 18,
                        ),
                      ),
                      const SizedBox(width: 14),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              'Stop ${index + 1}',
                              style: const TextStyle(
                                fontSize: 18,
                                fontWeight: FontWeight.bold,
                                color: Colors.white,
                              ),
                            ),
                            Text(
                              index == 0 ? 'Next Destination' : 'Upcoming Stop',
                              style: const TextStyle(
                                fontSize: 12,
                                color: Colors.grey,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 20),
                  const Divider(color: Colors.white10),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.location_city,
                        color: Color(0xFF34D399),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          address,
                          style: const TextStyle(
                            fontWeight: FontWeight.w500,
                            color: Colors.white,
                            fontSize: 13,
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.pin_drop,
                        color: Color(0xFF34D399),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      const Text(
                        'Coordinates  ',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.grey,
                          fontSize: 12,
                        ),
                      ),
                      Text(
                        '${wp.latitude.toStringAsFixed(5)}, ${wp.longitude.toStringAsFixed(5)}',
                        style: const TextStyle(
                          color: Colors.white,
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      const Icon(
                        Icons.straighten,
                        color: Color(0xFF34D399),
                        size: 18,
                      ),
                      const SizedBox(width: 12),
                      const Text(
                        'Distance  ',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.grey,
                          fontSize: 12,
                        ),
                      ),
                      Text(
                        '${(dist / 1000).toStringAsFixed(2)} km away',
                        style: const TextStyle(
                          color: Colors.white,
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 24),
                  FilledButton(
                    onPressed: () => Navigator.pop(context),
                    style: FilledButton.styleFrom(
                      backgroundColor: const Color(0xFF34D399),
                      foregroundColor: Colors.black,
                      padding: const EdgeInsets.symmetric(vertical: 14),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12),
                      ),
                    ),
                    child: const Text(
                      'Close',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }

  Future<void> _searchAndAddPincode(String pincode) async {
    setState(() => _isSearching = true);
    try {
      final uri = Uri.parse(
        'https://nominatim.openstreetmap.org/search?format=json&postalcode=$pincode&limit=1',
      );
      final response = await http.get(
        uri,
        headers: {'User-Agent': 'SmartLocationDeliveryApp/1.0'},
      );
      if (response.statusCode == 200) {
        final data = json.decode(response.body) as List;
        if (data.isNotEmpty && mounted) {
          _addWaypointFromSearch(data.first.cast<String, dynamic>());
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Pincode $pincode added!'),
              backgroundColor: const Color(0xFF34D399),
            ),
          );
        } else if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('Pincode not found'),
              backgroundColor: Colors.redAccent,
            ),
          );
        }
      }
    } catch (e) {
      debugPrint('Pincode search failed: $e');
    } finally {
      if (mounted) setState(() => _isSearching = false);
    }
  }

  int _searchRequestId = 0;

  Future<void> _searchLocation(String query) async {
    if (query.isEmpty) {
      setState(() => _searchResults = []);
      return;
    }
    final int currentRequestId = ++_searchRequestId;
    setState(() => _isSearching = true);

    // Tiny debounce
    await Future.delayed(const Duration(milliseconds: 300));
    if (currentRequestId != _searchRequestId) return;

    try {
      String viewboxParam = '';
      try {
        final bounds = _mapController.camera.visibleBounds;
        // Bias results to viewbox without strictly bounding them
        viewboxParam =
            '&viewbox=${bounds.west},${bounds.north},${bounds.east},${bounds.south}';
      } catch (_) {}

      // countrycodes=in biases it heavily to India, viewbox biases to visible map area
      final uri = Uri.parse(
        'https://nominatim.openstreetmap.org/search?format=json&q=${Uri.encodeComponent(query)}$viewboxParam&countrycodes=in&limit=5',
      );
      final response = await http.get(
        uri,
        headers: {'User-Agent': 'SmartLocationDeliveryApp/1.0'},
      );
      if (response.statusCode == 200) {
        if (currentRequestId == _searchRequestId) {
          final data = json.decode(response.body) as List;
          if (mounted) {
            setState(() {
              _searchResults = data.cast<Map<String, dynamic>>();
            });
          }
        }
      }
    } catch (e) {
      debugPrint('Search failed: $e');
    } finally {
      if (mounted && currentRequestId == _searchRequestId) {
        setState(() => _isSearching = false);
      }
    }
  }

  void _addWaypointFromSearch(Map<String, dynamic> result) {
    final lat = double.parse(result['lat'].toString());
    final lon = double.parse(result['lon'].toString());
    final name = result['display_name'].toString();
    final loc = LocationData(
      latitude: lat,
      longitude: lon,
      timestamp: DateTime.now(),
      accuracy: 10,
      speed: 0,
      heading: 0,
      altitude: 0,
      speedAccuracy: 0,
      isMocked: false,
    );

    setState(() {
      _deliveryWaypoints.add(loc);
      _locationNames['$lat,$lon'] = name;
      _searchRequestId++; // Invalidate any pending search results
      _isSearching = false; // Turn off the loading spinner
      _searchResults.clear();
      _searchController.clear();
      _fetchRoute();
      _calculateDeliveryMetrics();
    });
    FocusScope.of(context).unfocus();
  }

  void _calculateDeliveryMetrics() {
    if (_deliveryWaypoints.isEmpty) {
      setState(() {
        _remainingDeliveryDistance = 0;
        _etaMinutes = 0;
      });
      return;
    }

    double totalDist = 0;
    int totalEtaMinutes = 0;
    LocationData lastWp = _currentLocation;

    // Update ETA logic based on vehicle (fallback baseline)
    double speedMs = 8.33; // 30km/h default driving
    if (_selectedVehicle == 'bike') speedMs = 4.16; // 15km/h
    if (_selectedVehicle == 'foot') speedMs = 1.38; // 5km/h

    for (var wp in _deliveryWaypoints) {
      final dist = SmartLocation.distanceBetween(
        lastWp.latitude,
        lastWp.longitude,
        wp.latitude,
        wp.longitude,
      );
      totalDist += dist;

      // Calculate static fallback ETA for this segment
      final fallbackMinutes = (dist / speedMs / 60).ceil();

      // Use ML Engine to predict highly accurate ETA if learned
      final fromNode = '${lastWp.latitude},${lastWp.longitude}';
      final toNode = '${wp.latitude},${wp.longitude}';
      final predicted = SmartLocation.routeLearning.predictDuration(
        fromNode,
        toNode,
        fallback: Duration(minutes: fallbackMinutes),
      );

      totalEtaMinutes += predicted?.inMinutes ?? fallbackMinutes;
      lastWp = wp;
    }

    setState(() {
      _remainingDeliveryDistance = totalDist;
      _etaMinutes = totalEtaMinutes;
    });
  }

  // --- TAB 2: METERS AND ACCELEROMETER ---
  Widget _buildMetersTab(double speedKmh) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          // Radial Speedometer & G-Meter row
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              // Speedometer
              Column(
                children: [
                  const Text(
                    'SPEEDOMETER',
                    style: TextStyle(
                      fontSize: 10,
                      color: Colors.grey,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 8),
                  SizedBox(
                    width: 130,
                    height: 100,
                    child: CustomPaint(
                      painter: SpeedometerPainter(
                        speedKmh: speedKmh,
                        limitKmh: _speedLimitKmh,
                      ),
                    ),
                  ),
                ],
              ),
              // G-Meter
              Column(
                children: [
                  const Text(
                    'TELEMATICS G-METER',
                    style: TextStyle(
                      fontSize: 10,
                      color: Colors.grey,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 8),
                  SizedBox(
                    width: 130,
                    height: 100,
                    child: CustomPaint(
                      painter: GMeterPainter(longG: _longG, latG: _latG),
                    ),
                  ),
                ],
              ),
            ],
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: _buildDigitalMetricCard(
                  'HEADING VECTOR',
                  '${_currentLocation.heading.toStringAsFixed(0)}°',
                  subtitle: 'Bearing Direction',
                  color: const Color(0xFF34D399),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildDigitalMetricCard(
                  'ALTITUDE',
                  '${_currentLocation.altitude.toStringAsFixed(1)} M',
                  subtitle: 'Elevation level',
                  color: Colors.pinkAccent,
                ),
              ),
            ],
          ),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: _buildDigitalMetricCard(
                  'LATITUDE',
                  _currentLocation.latitude.toStringAsFixed(6),
                  subtitle: 'North Coordinate',
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildDigitalMetricCard(
                  'LONGITUDE',
                  _currentLocation.longitude.toStringAsFixed(6),
                  subtitle: 'West Coordinate',
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  const Text(
                    'GPS Accuracy and Telematics',
                    style: TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.bold,
                      letterSpacing: 0.5,
                    ),
                  ),
                  const SizedBox(height: 12),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      Expanded(
                        child: _buildMiniStat(
                          'Signal Accuracy',
                          '${_currentLocation.accuracy}m',
                        ),
                      ),
                      Expanded(
                        child: _buildMiniStat(
                          'Altitude',
                          '${_currentLocation.altitude}m',
                        ),
                      ),
                      Expanded(
                        child: _buildMiniStat(
                          'Type',
                          _currentLocation.isMocked ? 'Simulated' : 'Hardware',
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  // --- TAB 2: GEOFENCE MANAGER ---
  Widget _buildGeofenceTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Row(
            children: [
              const Expanded(
                child: Text(
                  'Office HQ Geofence Radius:',
                  style: TextStyle(fontSize: 12, color: Colors.grey),
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              const SizedBox(width: 8),
              Text(
                '${_geofenceRadius.round()} meters',
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF7C86C9),
                ),
              ),
            ],
          ),
          Slider(
            value: _geofenceRadius,
            min: 20,
            max: 120,
            activeColor: const Color(0xFF7C86C9),
            onChanged: (val) {
              setState(() {
                _geofenceRadius = val;
              });
              _resetGeofences();
            },
          ),
          const SizedBox(height: 8),
          const Text(
            'Geofence Events Log History:',
            style: TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 14,
              letterSpacing: 0.5,
            ),
          ),
          const SizedBox(height: 8),
          _geofenceEvents.isEmpty
              ? Container(
                  height: 120,
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1F2439),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Text(
                    'No geofence transitions detected.\nDrag the location pin inside or outside a zone.',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      color: Colors.grey,
                      fontSize: 13,
                      fontStyle: FontStyle.italic,
                    ),
                  ),
                )
              : ListView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  padding: EdgeInsets.zero,
                  itemCount: _geofenceEvents.length,
                  itemBuilder: (context, index) {
                    final event = _geofenceEvents[index];
                    Color triggerColor = Colors.blue;
                    IconData triggerIcon = Icons.info_outline;

                    if (event.triggerType == GeofenceTrigger.enter) {
                      triggerColor = const Color(0xFF34D399);
                      triggerIcon = Icons.login_outlined;
                    } else if (event.triggerType == GeofenceTrigger.exit) {
                      triggerColor = Colors.redAccent;
                      triggerIcon = Icons.logout_outlined;
                    } else if (event.triggerType == GeofenceTrigger.dwell) {
                      triggerColor = Colors.orangeAccent;
                      triggerIcon = Icons.timer_outlined;
                    }

                    return Card(
                      margin: const EdgeInsets.symmetric(vertical: 4),
                      color: const Color(0xFF1F2439),
                      child: ListTile(
                        leading: Icon(triggerIcon, color: triggerColor),
                        title: Text(
                          'Geofence ID: ${event.geofenceId}',
                          style: const TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 13,
                          ),
                        ),
                        subtitle: Text(
                          'Event Type: ${event.triggerType.name.toUpperCase()}',
                          style: TextStyle(
                            color: triggerColor.withValues(alpha: 0.8),
                            fontSize: 11,
                          ),
                        ),
                        trailing: Text(
                          '${event.timestamp.hour.toString().padLeft(2, '0')}:${event.timestamp.minute.toString().padLeft(2, '0')}:${event.timestamp.second.toString().padLeft(2, '0')}',
                          style: const TextStyle(
                            fontSize: 11,
                            color: Colors.grey,
                          ),
                        ),
                      ),
                    );
                  },
                ),
        ],
      ),
    );
  }

  // --- TAB 3: ROUTE RECORDER ---
  Widget _buildRecorderTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Card(
            color: const Color(0xFF1F2439),
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      Expanded(
                        child: _buildMiniStat(
                          'Duration',
                          _formatDuration(_recordElapsed),
                        ),
                      ),
                      Expanded(
                        child: _buildMiniStat(
                          'Distance',
                          '${_recordedDistance.toStringAsFixed(1)} m',
                        ),
                      ),
                      Expanded(
                        child: _buildMiniStat(
                          'Points Count',
                          '${_isRecording ? SmartLocation.recorder.points.length : _recordedPath.length}',
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 16),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      ElevatedButton.icon(
                        onPressed: _toggleRecording,
                        icon: Icon(
                          _isRecording ? Icons.stop : Icons.play_arrow,
                        ),
                        label: Text(
                          _isRecording ? 'Stop Recording' : 'Start Recording',
                          style: const TextStyle(fontWeight: FontWeight.bold),
                        ),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: _isRecording
                              ? const Color(0xFF3A1E22)
                              : const Color(0xFF252D45),
                          foregroundColor: _isRecording
                              ? Colors.redAccent
                              : const Color(0xFF7C86C9),
                          shadowColor: Colors.transparent,
                        ),
                      ),
                      if (_isRecording) ...[
                        const SizedBox(width: 12),
                        IconButton.filled(
                          onPressed: _togglePauseRecording,
                          icon: Icon(
                            _isPaused ? Icons.play_arrow : Icons.pause,
                          ),
                          style: IconButton.styleFrom(
                            backgroundColor: _isPaused
                                ? const Color(0xFF1A3028)
                                : const Color(0xFF2E2010),
                            foregroundColor: _isPaused
                                ? const Color(0xFF34D399)
                                : Colors.orangeAccent,
                          ),
                        ),
                      ],
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),
          if (_recordedPath.isNotEmpty) ...[
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Row(
                      children: [
                        const Text(
                          'Compression Tolerance:',
                          style: TextStyle(fontSize: 12, color: Colors.grey),
                        ),
                        const Spacer(),
                        Text(
                          '${_rdpTolerance.round()} meters',
                          style: const TextStyle(
                            fontWeight: FontWeight.bold,
                            color: Color(0xFF34D399),
                          ),
                        ),
                      ],
                    ),
                    Slider(
                      value: _rdpTolerance,
                      min: 2,
                      max: 40,
                      activeColor: const Color(0xFF34D399),
                      onChanged: (v) => setState(() => _rdpTolerance = v),
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: _simplifyPath,
                            icon: const Icon(Icons.compress),
                            label: const Text('RDP Compress'),
                            style: ElevatedButton.styleFrom(
                              backgroundColor: const Color(0xFF1E3A30),
                              foregroundColor: const Color(0xFF34D399),
                              shadowColor: Colors.transparent,
                            ),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: FilledButton.icon(
                            onPressed: () {
                              final path = _simplifiedPath.isNotEmpty
                                  ? _simplifiedPath
                                  : _recordedPath;
                              _exportGeoJson(
                                GeoJsonExporter.toLineStringMap(path),
                                'Export GeoJSON LineString',
                              );
                            },
                            icon: const Icon(Icons.code),
                            label: const Text(
                              'GeoJSON',
                              style: TextStyle(fontWeight: FontWeight.bold),
                            ),
                            style: FilledButton.styleFrom(
                              backgroundColor: const Color(0xFF252D45),
                              foregroundColor: const Color(0xFF7C86C9),
                            ),
                          ),
                        ),
                      ],
                    ),
                    if (_simplifiedPath.isNotEmpty) ...[
                      const Divider(color: Colors.white24, height: 24),
                      Text(
                        'Original Path: ${_recordedPath.length} coordinates',
                        style: const TextStyle(
                          fontSize: 12,
                          color: Colors.grey,
                        ),
                      ),
                      Text(
                        'Simplified Path: ${_simplifiedPath.length} coordinates',
                        style: const TextStyle(
                          fontSize: 12,
                          color: Colors.grey,
                        ),
                      ),
                      Text(
                        'Space Saved: ${((1 - (_simplifiedPath.length / _recordedPath.length)) * 100).toStringAsFixed(1)}%',
                        style: const TextStyle(
                          color: Color(0xFF34D399),
                          fontWeight: FontWeight.bold,
                          fontSize: 13,
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ),
          ] else
            Container(
              padding: const EdgeInsets.all(24),
              alignment: Alignment.center,
              child: const Text(
                'Start a recording, drag the pin around the map, and then click stop to enable RDP compression and GeoJSON maps.',
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: Colors.grey,
                  fontSize: 12,
                  fontStyle: FontStyle.italic,
                ),
              ),
            ),
        ],
      ),
    );
  }

  // --- TAB 4: DRIVER SAFETY MONITOR ---
  Widget _buildSafetyTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  Row(
                    children: [
                      const Expanded(
                        child: Text(
                          'Alert Speed Limit Threshold:',
                          style: TextStyle(fontSize: 12, color: Colors.grey),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        '${_speedLimitKmh.round()} km/h',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.redAccent,
                        ),
                      ),
                    ],
                  ),
                  Slider(
                    value: _speedLimitKmh,
                    min: 20,
                    max: 120,
                    divisions: 10,
                    activeColor: Colors.redAccent,
                    onChanged: (val) {
                      setState(() {
                        _speedLimitKmh = val;
                      });
                      SmartLocation.speed.configure(
                        speedLimitKmh: _speedLimitKmh,
                        suddenDecelThresholdKmhPerSec: _gForceThreshold,
                        suddenAccelThresholdKmhPerSec: _gForceThreshold,
                      );
                    },
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Expanded(
                        child: Text(
                          'G-Force Sensor decel alert:',
                          style: TextStyle(fontSize: 12, color: Colors.grey),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        '${_gForceThreshold.round()} km/h/s',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.orangeAccent,
                        ),
                      ),
                    ],
                  ),
                  Slider(
                    value: _gForceThreshold,
                    min: 4,
                    max: 20,
                    divisions: 8,
                    activeColor: Colors.orangeAccent,
                    onChanged: (val) {
                      setState(() {
                        _gForceThreshold = val;
                      });
                      SmartLocation.speed.configure(
                        speedLimitKmh: _speedLimitKmh,
                        suddenDecelThresholdKmhPerSec: _gForceThreshold,
                        suddenAccelThresholdKmhPerSec: _gForceThreshold,
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 10),
          const Text(
            'Driver Alerts Log History:',
            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
          ),
          const SizedBox(height: 6),
          _driverAlertsLogs.isEmpty
              ? Container(
                  height: 120,
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1F2439),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Text(
                    'No telematics violations logged.\nDrag the pin rapidly to simulate speeding\nor release it suddenly to simulate harsh braking.',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      color: Colors.grey,
                      fontSize: 12,
                      fontStyle: FontStyle.italic,
                    ),
                  ),
                )
              : ListView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  padding: EdgeInsets.zero,
                  itemCount: _driverAlertsLogs.length,
                  itemBuilder: (ctx, index) {
                    final isG = _driverAlertsLogs[index].contains('G-FORCE');
                    return Card(
                      margin: const EdgeInsets.symmetric(vertical: 4),
                      color: isG
                          ? Colors.orange.shade900.withValues(alpha: 0.3)
                          : Colors.red.shade900.withValues(alpha: 0.3),
                      child: Padding(
                        padding: const EdgeInsets.all(12.0),
                        child: Text(
                          _driverAlertsLogs[index],
                          style: const TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                            fontFamily: 'monospace',
                          ),
                        ),
                      ),
                    );
                  },
                ),
        ],
      ),
    );
  }

  // --- TAB 5: PRIVACY GUARD ---
  Widget _buildPrivacyTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  Row(
                    children: [
                      const Expanded(
                        child: Text(
                          'Obfuscation Displacement Radius:',
                          style: TextStyle(fontSize: 12, color: Colors.grey),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        '${_fuzzRadius.round()} meters',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.deepOrange,
                        ),
                      ),
                    ],
                  ),
                  Slider(
                    value: _fuzzRadius,
                    min: 100,
                    max: 800,
                    activeColor: Colors.deepOrange,
                    onChanged: (val) {
                      setState(() {
                        _fuzzRadius = val;
                      });
                      _updatePrivacyStates(_currentLocation);
                    },
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Expanded(
                        child: Text(
                          'Coarse Snap Degrees Grid:',
                          style: TextStyle(fontSize: 12, color: Colors.grey),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        _gridSizeDegrees.toStringAsFixed(4),
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.orangeAccent,
                        ),
                      ),
                    ],
                  ),
                  Slider(
                    value: _gridSizeDegrees,
                    min: 0.0005,
                    max: 0.003,
                    activeColor: Colors.orangeAccent,
                    onChanged: (val) {
                      setState(() {
                        _gridSizeDegrees = val;
                      });
                      _updatePrivacyStates(_currentLocation);
                    },
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),
          _buildPrivacyDetailCard(
            'Location Obfuscation Output Comparison',
            raw:
                '${_currentLocation.latitude.toStringAsFixed(6)}, ${_currentLocation.longitude.toStringAsFixed(6)}',
            fuzzed: _fuzzedLocation != null
                ? '${_fuzzedLocation!.latitude.toStringAsFixed(6)}, ${_fuzzedLocation!.longitude.toStringAsFixed(6)}'
                : 'N/A',
            snapped: _snappedLocation != null
                ? '${_snappedLocation!.latitude.toStringAsFixed(6)}, ${_snappedLocation!.longitude.toStringAsFixed(6)}'
                : 'N/A',
          ),
        ],
      ),
    );
  }

  // --- TAB 6: ADVANCED FEATURES PANEL ---
  Widget _buildAdvancedTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // 1. Dead Reckoning Card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.rocket, color: Colors.orangeAccent),
                      const SizedBox(width: 8),
                      const Text(
                        '1. GPS Loss & Dead Reckoning',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      const Spacer(),
                      if (_isDREnabled)
                        const SizedBox(
                          width: 14,
                          height: 14,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            color: Colors.orangeAccent,
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  const Text(
                    'Simulate a sudden GPS drop while moving. The Dead Reckoning system extrapolates coordinates automatically using heading velocity vector calculus.',
                    style: TextStyle(fontSize: 11, color: Colors.grey),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _isDREnabled ? null : _triggerDeadReckoningDemo,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF2E2010),
                      foregroundColor: Colors.orangeAccent,
                      shadowColor: Colors.transparent,
                    ),
                    child: Text(
                      _isDREnabled
                          ? 'Extrapolating Trail...'
                          : 'Simulate GPS Signal Dropout',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  if (_drLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      height: 110,
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                      ),
                      child: ListView.builder(
                        padding: const EdgeInsets.all(8),
                        itemCount: _drLogs.length,
                        itemBuilder: (context, i) => Text(
                          _drLogs[i],
                          style: const TextStyle(
                            fontFamily: 'monospace',
                            fontSize: 10,
                            color: Colors.greenAccent,
                          ),
                        ),
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Map Matching Card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.alt_route, color: Colors.blueAccent),
                      const SizedBox(width: 8),
                      const Text(
                        'Map-Matching (Snap to Road)',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      const Spacer(),
                      if (_isMapMatchingEnabled)
                        const SizedBox(
                          width: 14,
                          height: 14,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            color: Colors.blueAccent,
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  const Text(
                    'Simulate taking noisy, drifted GPS coordinates and using Flat-Earth projection math in a background isolate to snap them cleanly to an OSM road vector.',
                    style: TextStyle(fontSize: 11, color: Colors.grey),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _isMapMatchingEnabled
                        ? null
                        : _triggerMapMatchingDemo,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF10202E),
                      foregroundColor: Colors.blueAccent,
                      shadowColor: Colors.transparent,
                    ),
                    child: Text(
                      _isMapMatchingEnabled
                          ? 'Snapping in Background...'
                          : 'Snap Noisy Path to Road',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  if (_mapMatchingLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      height: 90,
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                      ),
                      child: ListView.builder(
                        padding: const EdgeInsets.all(8),
                        itemCount: _mapMatchingLogs.length,
                        itemBuilder: (context, i) => Text(
                          _mapMatchingLogs[i],
                          style: const TextStyle(
                            fontFamily: 'monospace',
                            fontSize: 10,
                            color: Colors.greenAccent,
                          ),
                        ),
                      ),
                    ),
                  ],
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _isDREnabled ? null : _triggerDeadReckoningDemo,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF2E2010),
                      foregroundColor: Colors.orangeAccent,
                      shadowColor: Colors.transparent,
                    ),
                    child: Text(
                      _isDREnabled
                          ? 'Extrapolating Trail...'
                          : 'Simulate GPS Signal Dropout',
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  if (_drLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      height: 110,
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                        boxShadow: [
                          BoxShadow(
                            color: Color(0xFF0D1020),
                            offset: Offset(2, 2),
                            blurRadius: 6,
                          ),
                          BoxShadow(
                            color: Color(0xFF1E2538),
                            offset: Offset(-1, -1),
                            blurRadius: 4,
                          ),
                        ],
                      ),
                      child: Column(
                        children: [
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 10,
                              vertical: 5,
                            ),
                            decoration: const BoxDecoration(
                              color: Color(0xFF1D2236),
                              borderRadius: BorderRadius.vertical(
                                top: Radius.circular(7),
                              ),
                            ),
                            child: Row(
                              children: [
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.redAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.orangeAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Color(0xFF34D399),
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 10),
                                const Text(
                                  'dead_reckoning.log',
                                  style: TextStyle(
                                    fontSize: 9,
                                    color: Colors.grey,
                                    fontFamily: 'monospace',
                                  ),
                                ),
                              ],
                            ),
                          ),
                          Expanded(
                            child: ListView.builder(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 10,
                                vertical: 6,
                              ),
                              itemCount: _drLogs.length,
                              itemBuilder: (ctx, idx) => Text(
                                _drLogs[idx],
                                style: const TextStyle(
                                  fontFamily: 'monospace',
                                  fontSize: 10,
                                  color: Colors.orangeAccent,
                                  height: 1.5,
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),

          // 3. ML Route Optimizer
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  const Row(
                    children: [
                      Icon(Icons.memory, color: Colors.purpleAccent),
                      SizedBox(width: 8),
                      Text(
                        '3. ML Route Optimizer',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  const Text(
                    'Simulate multiple drivers completing the same route. The On-Device ML Engine learns the traffic patterns to predict highly accurate future ETAs.',
                    style: TextStyle(fontSize: 11, color: Colors.grey),
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: _triggerMLRouteLearningDemo,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF221A38),
                      foregroundColor: Colors.purpleAccent,
                      shadowColor: Colors.transparent,
                    ),
                    child: const Text(
                      'Simulate 3 Deliveries & Predict ETA',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  if (_mlRouteLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      height: 130,
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                        boxShadow: [
                          BoxShadow(
                            color: Color(0xFF0D1020),
                            offset: Offset(2, 2),
                            blurRadius: 6,
                          ),
                          BoxShadow(
                            color: Color(0xFF1E2538),
                            offset: Offset(-1, -1),
                            blurRadius: 4,
                          ),
                        ],
                      ),
                      child: Column(
                        children: [
                          Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 10,
                              vertical: 5,
                            ),
                            decoration: const BoxDecoration(
                              color: Color(0xFF1D2236),
                              borderRadius: BorderRadius.vertical(
                                top: Radius.circular(7),
                              ),
                            ),
                            child: Row(
                              children: [
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.redAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.orangeAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Color(0xFF34D399),
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 10),
                                const Text(
                                  'ml_route_engine.log',
                                  style: TextStyle(
                                    fontSize: 9,
                                    color: Colors.grey,
                                    fontFamily: 'monospace',
                                  ),
                                ),
                              ],
                            ),
                          ),
                          Expanded(
                            child: ListView.builder(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 10,
                                vertical: 6,
                              ),
                              itemCount: _mlRouteLogs.length,
                              itemBuilder: (ctx, idx) => Text(
                                _mlRouteLogs[idx],
                                style: TextStyle(
                                  fontFamily: 'monospace',
                                  fontSize: 10,
                                  height: 1.5,
                                  color:
                                      _mlRouteLogs[idx].contains('PREDICTION')
                                      ? Colors.greenAccent
                                      : Colors.purpleAccent.withValues(
                                          alpha: 0.8,
                                        ),
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),

          // 2. Indoor AP Centroid
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  const Row(
                    children: [
                      Icon(Icons.wifi, color: Colors.pinkAccent),
                      SizedBox(width: 8),
                      Text(
                        '2. Indoor WiFi Signal Centroid',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  const Text(
                    'Simulate AP beacons locations. Sliders represents RSSI signal strength (dBm). Calculate coordinates centroid.',
                    style: TextStyle(fontSize: 11, color: Colors.grey),
                  ),
                  const SizedBox(height: 8),
                  _buildRssiSlider(
                    'Lobby AP (Center)',
                    _rssiLobby,
                    (v) => setState(() => _rssiLobby = v),
                  ),
                  _buildRssiSlider(
                    'ConfRoom AP (NE)',
                    _rssiConfRoom,
                    (v) => setState(() => _rssiConfRoom = v),
                  ),
                  _buildRssiSlider(
                    'Kitchen AP (SW)',
                    _rssiKitchen,
                    (v) => setState(() => _rssiKitchen = v),
                  ),
                  ElevatedButton(
                    onPressed: _calculateIndoorPosition,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: const Color(0xFF301828),
                      foregroundColor: Colors.pinkAccent,
                      shadowColor: Colors.transparent,
                    ),
                    child: const Text(
                      'Solve Indoor Coordinate Centroid',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  if (_indoorLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                        boxShadow: [
                          BoxShadow(
                            color: Color(0xFF0D1020),
                            offset: Offset(2, 2),
                            blurRadius: 6,
                          ),
                          BoxShadow(
                            color: Color(0xFF1E2538),
                            offset: Offset(-1, -1),
                            blurRadius: 4,
                          ),
                        ],
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            width: double.infinity,
                            padding: const EdgeInsets.symmetric(
                              horizontal: 10,
                              vertical: 5,
                            ),
                            decoration: const BoxDecoration(
                              color: Color(0xFF1D2236),
                              borderRadius: BorderRadius.vertical(
                                top: Radius.circular(7),
                              ),
                            ),
                            child: Row(
                              children: [
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.redAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Colors.orangeAccent,
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 5),
                                Container(
                                  width: 8,
                                  height: 8,
                                  decoration: const BoxDecoration(
                                    color: Color(0xFF34D399),
                                    shape: BoxShape.circle,
                                  ),
                                ),
                                const SizedBox(width: 10),
                                const Text(
                                  'indoor_centroid.log',
                                  style: TextStyle(
                                    fontSize: 9,
                                    color: Colors.grey,
                                    fontFamily: 'monospace',
                                  ),
                                ),
                              ],
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 10,
                              vertical: 8,
                            ),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: _indoorLogs
                                  .map(
                                    (log) => Text(
                                      log,
                                      style: TextStyle(
                                        fontFamily: 'monospace',
                                        fontSize: 10,
                                        height: 1.6,
                                        color: log.contains('📍')
                                            ? Colors.pinkAccent
                                            : Colors.grey.shade400,
                                      ),
                                    ),
                                  )
                                  .toList(),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),

          // 3. Spatial Hashing grid
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.grid_on, color: Color(0xFF7C86C9)),
                      const SizedBox(width: 8),
                      const Text(
                        '3. Spatial Hashing Grid O(1)',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      const Spacer(),
                      Switch(
                        value: _showSpatialGrid,
                        activeThumbColor: const Color(0xFF7C86C9),
                        onChanged: (v) => setState(() => _showSpatialGrid = v),
                      ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  const Text(
                    'Renders grid bucket divisions representing O(1) geofence cells query optimization.',
                    style: TextStyle(fontSize: 11, color: Colors.grey),
                  ),
                  const SizedBox(height: 8),
                  ElevatedButton(
                    onPressed: () {
                      final latKey = ((_currentLocation.latitude) / 0.001)
                          .floor();
                      final lngKey = ((_currentLocation.longitude) / 0.001)
                          .floor();
                      setState(() {
                        _spatialHashLogs.insert(
                          0,
                          'Active Cell Index: LatCell_$latKey, LngCell_$lngKey',
                        );
                      });
                    },
                    child: const Text('Resolve Hash Grid Cell Key'),
                  ),
                  if (_spatialHashLogs.isNotEmpty) ...[
                    const SizedBox(height: 8),
                    Container(
                      height: 70,
                      decoration: const BoxDecoration(
                        color: Color(0xFF141828),
                        borderRadius: BorderRadius.all(Radius.circular(8)),
                        boxShadow: [
                          BoxShadow(
                            color: Color(0xFF0D1020),
                            offset: Offset(2, 2),
                            blurRadius: 6,
                          ),
                          BoxShadow(
                            color: Color(0xFF1E2538),
                            offset: Offset(-1, -1),
                            blurRadius: 4,
                          ),
                        ],
                      ),
                      child: ListView.builder(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 10,
                          vertical: 8,
                        ),
                        itemCount: _spatialHashLogs.length,
                        itemBuilder: (ctx, i) => Text(
                          '> ${_spatialHashLogs[i]}',
                          style: const TextStyle(
                            fontFamily: 'monospace',
                            fontSize: 10,
                            color: Color(0xFF7C86C9),
                            height: 1.5,
                          ),
                        ),
                      ),
                    ),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 24),
        ],
      ),
    );
  }

  Widget _buildRssiSlider(
    String label,
    double val,
    ValueChanged<double> onChanged,
  ) {
    return Row(
      children: [
        Expanded(
          flex: 3,
          child: Text(label, style: const TextStyle(fontSize: 11)),
        ),
        Expanded(
          flex: 4,
          child: Slider(
            value: val,
            min: -100,
            max: -30,
            activeColor: Colors.pinkAccent,
            onChanged: onChanged,
          ),
        ),
        SizedBox(
          width: 36,
          child: Text(
            '${val.round()}dBm',
            style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
          ),
        ),
      ],
    );
  }

  // General Components Helpers
  Widget _buildDigitalMetricCard(
    String title,
    String val, {
    String? subtitle,
    Color? color,
  }) {
    final accentColor = color ?? const Color(0xFF7C86C9);
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
      decoration: BoxDecoration(
        color: const Color(0xFF1F2439),
        borderRadius: BorderRadius.circular(16),
        boxShadow: const [
          BoxShadow(
            color: Color(0xFF0D1020),
            offset: Offset(5, 5),
            blurRadius: 12,
            spreadRadius: 1,
          ),
          BoxShadow(
            color: Color(0xFF252D45),
            offset: Offset(-3, -3),
            blurRadius: 8,
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 9,
              color: Colors.grey,
              fontWeight: FontWeight.bold,
              letterSpacing: 1.1,
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 6),
          Text(
            val,
            style: TextStyle(
              color: accentColor,
              fontSize: 19,
              fontWeight: FontWeight.bold,
              fontFamily: 'monospace',
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          if (subtitle != null) ...[
            const SizedBox(height: 3),
            Text(
              subtitle,
              style: const TextStyle(fontSize: 10, color: Colors.grey),
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildDigitalMetricCardOld(
    String title,
    String val, {
    String? subtitle,
    Color? color,
  }) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: const BoxDecoration(
        color: Color(0xFF1F2439),
        borderRadius: BorderRadius.all(Radius.circular(10)),
        boxShadow: [
          BoxShadow(
            color: Color(0xFF0D1020),
            offset: Offset(4, 4),
            blurRadius: 10,
          ),
          BoxShadow(
            color: Color(0xFF252D45),
            offset: Offset(-2, -2),
            blurRadius: 6,
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 10,
              color: Colors.grey,
              fontWeight: FontWeight.bold,
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 4),
          Text(
            val,
            style: TextStyle(
              fontSize: 15,
              fontWeight: FontWeight.bold,
              fontFamily: 'monospace',
              color: color ?? Colors.white,
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          if (subtitle != null) ...[
            const SizedBox(height: 2),
            Text(
              subtitle,
              style: const TextStyle(fontSize: 9, color: Colors.grey),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildMiniStat(String label, String val) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          label,
          style: const TextStyle(
            fontSize: 10,
            color: Colors.grey,
            letterSpacing: 0.4,
          ),
          textAlign: TextAlign.center,
        ),
        const SizedBox(height: 5),
        Text(
          val,
          style: const TextStyle(
            fontSize: 13,
            fontWeight: FontWeight.bold,
            color: Colors.white,
            fontFamily: 'monospace',
          ),
          textAlign: TextAlign.center,
        ),
      ],
    );
  }

  Widget _buildPrivacyDetailCard(
    String title, {
    required String raw,
    required String fuzzed,
    required String snapped,
  }) {
    return Container(
      decoration: BoxDecoration(
        color: const Color(0xFF1F2439),
        borderRadius: BorderRadius.circular(16),
        boxShadow: const [
          BoxShadow(
            color: Color(0xFF0D1020),
            offset: Offset(5, 5),
            blurRadius: 12,
            spreadRadius: 1,
          ),
          BoxShadow(
            color: Color(0xFF252D45),
            offset: Offset(-3, -3),
            blurRadius: 8,
          ),
        ],
      ),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Row(
              children: [
                Container(
                  width: 3,
                  height: 16,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(2),
                    gradient: const LinearGradient(
                      colors: [Colors.deepOrange, Colors.orangeAccent],
                      begin: Alignment.topCenter,
                      end: Alignment.bottomCenter,
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                Text(
                  title,
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 12,
                    letterSpacing: 0.5,
                    color: Colors.white,
                  ),
                ),
              ],
            ),
            const Divider(color: Colors.white10, height: 20),
            _buildLogDataRow(
              'Hardware GPS Raw',
              raw,
              color: const Color(0xFF7C86C9),
            ),
            _buildLogDataRow(
              'Fuzzed Location',
              fuzzed,
              color: Colors.deepOrange,
            ),
            _buildLogDataRow(
              'Snapped Coarse Grid',
              snapped,
              color: Colors.orangeAccent,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildLogDataRow(String label, String val, {required Color color}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: const TextStyle(fontSize: 12, color: Colors.grey)),
          Text(
            val,
            style: TextStyle(
              fontFamily: 'monospace',
              fontSize: 12,
              color: color,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }

  String _formatDuration(Duration d) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final minutes = twoDigits(d.inMinutes.remainder(60));
    final seconds = twoDigits(d.inSeconds.remainder(60));
    return '$minutes:$seconds';
  }

  Widget _buildSyncTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Background engine status card
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: const Color(0xFF1F2439),
              borderRadius: BorderRadius.circular(14),
              boxShadow: _isBackgroundTrackingActive
                  ? const [
                      BoxShadow(
                        color: Color(0xFF0D1020),
                        offset: Offset(5, 5),
                        blurRadius: 12,
                        spreadRadius: 1,
                      ),
                      BoxShadow(
                        color: Color(0xFF1A3040),
                        offset: Offset(-3, -3),
                        blurRadius: 8,
                      ),
                    ]
                  : const [
                      BoxShadow(
                        color: Color(0xFF0D1020),
                        offset: Offset(5, 5),
                        blurRadius: 12,
                        spreadRadius: 1,
                      ),
                      BoxShadow(
                        color: Color(0xFF252D45),
                        offset: Offset(-3, -3),
                        blurRadius: 8,
                      ),
                    ],
            ),
            child: Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(10),
                  decoration: BoxDecoration(
                    color:
                        (_isBackgroundTrackingActive
                                ? const Color(0xFF34D399)
                                : Colors.grey)
                            .withValues(alpha: 0.15),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Icon(
                    _isBackgroundTrackingActive
                        ? Icons.sensors
                        : Icons.sensors_off,
                    color: _isBackgroundTrackingActive
                        ? const Color(0xFF34D399)
                        : Colors.grey,
                    size: 22,
                  ),
                ),
                const SizedBox(width: 14),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'Background Engine',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.white,
                          fontSize: 14,
                        ),
                      ),
                      const SizedBox(height: 2),
                      Text(
                        _isBackgroundTrackingActive
                            ? 'Active — recording to SQLite cache'
                            : 'Inactive — tap to start tracking',
                        style: TextStyle(
                          fontSize: 11,
                          color: _isBackgroundTrackingActive
                              ? const Color(0xFF34D399)
                              : Colors.grey,
                        ),
                      ),
                    ],
                  ),
                ),
                Switch(
                  value: _isBackgroundTrackingActive,
                  activeThumbColor: const Color(0xFF34D399),
                  onChanged: (val) async {
                    if (val) {
                      await SmartLocation.startBackgroundTracking();
                    } else {
                      await SmartLocation.stopBackgroundTracking();
                    }
                    setState(() {
                      _isBackgroundTrackingActive = val;
                    });
                  },
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),

          // SQLite fetch button
          FilledButton.icon(
            onPressed: () async {
              final locs = await SmartLocation.getOfflineLocations();
              setState(() {
                _offlineLocations = locs;
              });
            },
            icon: const Icon(Icons.storage, color: Colors.black, size: 18),
            style: FilledButton.styleFrom(
              backgroundColor: const Color(0xFF7C86C9),
              padding: const EdgeInsets.symmetric(vertical: 14),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
            label: const Text(
              'Fetch SQLite Offline Cache',
              style: TextStyle(
                color: Colors.black,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Cached points count badge
          Row(
            children: [
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 4,
                ),
                decoration: const BoxDecoration(
                  color: Color(0xFF1D2236),
                  borderRadius: BorderRadius.all(Radius.circular(20)),
                  boxShadow: [
                    BoxShadow(
                      color: Color(0xFF0D1020),
                      offset: Offset(3, 3),
                      blurRadius: 7,
                    ),
                    BoxShadow(
                      color: Color(0xFF252D45),
                      offset: Offset(-2, -2),
                      blurRadius: 5,
                    ),
                  ],
                ),
                child: Row(
                  children: [
                    const Icon(Icons.circle, color: Color(0xFF7C86C9), size: 8),
                    const SizedBox(width: 6),
                    Text(
                      '${_offlineLocations.length} points cached',
                      style: const TextStyle(
                        fontSize: 11,
                        color: Color(0xFF7C86C9),
                        fontWeight: FontWeight.bold,
                        fontFamily: 'monospace',
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 10),

          if (_offlineLocations.isNotEmpty)
            Container(
              decoration: const BoxDecoration(
                color: Color(0xFF141828),
                borderRadius: BorderRadius.all(Radius.circular(10)),
                boxShadow: [
                  BoxShadow(
                    color: Color(0xFF0D1020),
                    offset: Offset(3, 3),
                    blurRadius: 8,
                  ),
                  BoxShadow(
                    color: Color(0xFF1E2538),
                    offset: Offset(-1, -1),
                    blurRadius: 4,
                  ),
                ],
              ),
              child: Column(
                children: [
                  Container(
                    width: double.infinity,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12,
                      vertical: 7,
                    ),
                    decoration: const BoxDecoration(
                      color: Color(0xFF1D2236),
                      borderRadius: BorderRadius.vertical(
                        top: Radius.circular(9),
                      ),
                    ),
                    child: Row(
                      children: [
                        Container(
                          width: 8,
                          height: 8,
                          decoration: const BoxDecoration(
                            color: Colors.redAccent,
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 5),
                        Container(
                          width: 8,
                          height: 8,
                          decoration: const BoxDecoration(
                            color: Colors.orangeAccent,
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 5),
                        Container(
                          width: 8,
                          height: 8,
                          decoration: const BoxDecoration(
                            color: Color(0xFF34D399),
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 10),
                        const Text(
                          'offline_cache.sqlite',
                          style: TextStyle(
                            fontSize: 9,
                            color: Colors.grey,
                            fontFamily: 'monospace',
                          ),
                        ),
                      ],
                    ),
                  ),
                  ListView.builder(
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12,
                      vertical: 8,
                    ),
                    itemCount: _offlineLocations.length,
                    itemBuilder: (context, i) {
                      final loc = _offlineLocations[i];
                      return Text(
                        '[${i.toString().padLeft(3, '0')}] lat=${loc['lat']}, lng=${loc['lng']}',
                        style: const TextStyle(
                          color: Colors.greenAccent,
                          fontFamily: 'monospace',
                          fontSize: 10,
                          height: 1.6,
                        ),
                      );
                    },
                  ),
                ],
              ),
            ),
        ],
      ),
    );
  }
}

// --- INTERACTIVE CUSTOM CANVAS PAINTER ---
class LocationSandboxPainter extends CustomPainter {
  final LocationData currentLocation;
  final List<Geofence> activeGeofences;
  final double geofenceRadius;
  final List<LocationData> recordedPath;
  final List<LocationData> simplifiedPath;
  final bool showSpatialGrid;
  final bool showIndoorBeacons;
  final Map<String, double> beaconSignals;
  final LocationData? indoorCentroid;
  final LocationData? fuzzedLocation;
  final LocationData? snappedLocation;
  final double fuzzRadius;
  final bool showPrivacyInfo;
  final LocationData? drPredictedLocation;
  final double radarValue;

  // Viewport setup constants
  static const double mapCenterLat = 37.7749;
  static const double mapCenterLng = -122.4194;
  static const double latRange = 0.005;
  static const double lngRange = 0.005;

  LocationSandboxPainter({
    required Listenable repaint,
    required this.currentLocation,
    required this.activeGeofences,
    required this.geofenceRadius,
    required this.recordedPath,
    required this.simplifiedPath,
    required this.showSpatialGrid,
    required this.showIndoorBeacons,
    required this.beaconSignals,
    required this.indoorCentroid,
    required this.fuzzedLocation,
    required this.snappedLocation,
    required this.fuzzRadius,
    required this.showPrivacyInfo,
    required this.drPredictedLocation,
    required this.radarValue,
  }) : super(repaint: repaint);

  // Conversion math helpers
  Offset _toOffset(double lat, double lng, Size size) {
    final double minLat = mapCenterLat - latRange / 2;
    final double minLng = mapCenterLng - lngRange / 2;

    final double pctX = (lng - minLng) / lngRange;
    final double pctY = (lat - minLat) / latRange;

    final double x = pctX * size.width;
    final double y =
        size.height -
        (pctY * size.height); // Flip y because canvas goes top-to-bottom

    return Offset(x, y);
  }

  void _drawDashedLine(Canvas canvas, Offset p1, Offset p2, Paint paint) {
    const double dashWidth = 4.0;
    const double dashSpace = 4.0;
    final double distance = (p2 - p1).distance;
    final int count = (distance / (dashWidth + dashSpace)).floor();
    final Offset direction = (p2 - p1) / distance;
    for (int i = 0; i < count; i++) {
      final double start = i * (dashWidth + dashSpace);
      canvas.drawLine(
        p1 + direction * start,
        p1 + direction * (start + dashWidth),
        paint,
      );
    }
  }

  @override
  void paint(Canvas canvas, Size size) {
    final double width = size.width;
    final double height = size.height;

    // 1. Draw Grid lines
    final Paint gridPaint = Paint()
      ..color = const Color(0xFF1E293B)
      ..strokeWidth = 0.8;

    // Draw horizontal grid lines every 0.001 degrees
    final double minLat = mapCenterLat - latRange / 2;
    final double maxLat = mapCenterLat + latRange / 2;
    for (double lat = minLat; lat <= maxLat; lat += 0.001) {
      final y = _toOffset(lat, mapCenterLng, size).dy;
      canvas.drawLine(Offset(0, y), Offset(width, y), gridPaint);
    }

    // Draw vertical grid lines
    final double minLng = mapCenterLng - lngRange / 2;
    final double maxLng = mapCenterLng + lngRange / 2;
    for (double lng = minLng; lng <= maxLng; lng += 0.001) {
      final x = _toOffset(mapCenterLat, lng, size).dx;
      canvas.drawLine(Offset(x, 0), Offset(x, height), gridPaint);
    }

    // Scale conversion: 1 degree latitude ~ 111,000 meters
    // 0.005 degrees height = ~555 meters.
    final double pixelsPerMeter = height / 555.0;

    // 2. Continuous Glowing Sweep Radar Animation
    final double sweepAngle = radarValue * 2 * math.pi;
    final Offset centerOffset = Offset(width / 2, height / 2);
    final double radarRadius = math.min(width, height) / 2;

    final Paint radarPaint = Paint()
      ..shader = SweepGradient(
        center: Alignment.center,
        colors: [
          const Color(0xFF7C86C9).withValues(alpha: 0.0),
          const Color(0xFF7C86C9).withValues(alpha: 0.12),
        ],
        stops: const [0.0, 1.0],
        transform: GradientRotation(sweepAngle - (math.pi / 2)),
      ).createShader(Rect.fromLTWH(0, 0, width, height));

    canvas.drawCircle(centerOffset, radarRadius, radarPaint);

    // Draw scanning vector line
    final Offset vectorEnd =
        centerOffset +
        Offset(
          math.cos(sweepAngle - (math.pi / 2)) * radarRadius,
          math.sin(sweepAngle - (math.pi / 2)) * radarRadius,
        );
    final Paint vectorPaint = Paint()
      ..color = const Color(0xFF7C86C9).withValues(alpha: 0.3)
      ..strokeWidth = 1.5;
    canvas.drawLine(centerOffset, vectorEnd, vectorPaint);

    // 3. Draw Spatial Hashing Grid buckets if active
    if (showSpatialGrid) {
      final Paint hashGridPaint = Paint()
        ..color = const Color(0xFF7C86C9).withValues(alpha: 0.18)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 1.2;

      // Hashing buckets every 0.001 degrees
      for (double lat = minLat; lat <= maxLat; lat += 0.001) {
        for (double lng = minLng; lng <= maxLng; lng += 0.001) {
          final nw = _toOffset(lat + 0.001, lng, size);
          final se = _toOffset(lat, lng + 0.001, size);
          canvas.drawRect(Rect.fromPoints(nw, se), hashGridPaint);
        }
      }

      // Highlight current cell index
      final currentLatCell = (currentLocation.latitude / 0.001).floor() * 0.001;
      final currentLngCell =
          (currentLocation.longitude / 0.001).floor() * 0.001;
      final cellNW = _toOffset(currentLatCell + 0.001, currentLngCell, size);
      final cellSE = _toOffset(currentLatCell, currentLngCell + 0.001, size);

      final Paint activeCellPaint = Paint()
        ..color = const Color(0xFF7C86C9).withValues(alpha: 0.3)
        ..style = PaintingStyle.fill;
      canvas.drawRect(Rect.fromPoints(cellNW, cellSE), activeCellPaint);
    }

    // 4. Draw WiFi Beacons (Indoor Mode)
    if (showIndoorBeacons) {
      final Map<String, Offset> beacons = {
        'Lobby_AP': _toOffset(mapCenterLat, mapCenterLng, size),
        'ConfRoom_AP': _toOffset(
          mapCenterLat + 0.0009,
          mapCenterLng + 0.0012,
          size,
        ),
        'Kitchen_AP': _toOffset(
          mapCenterLat - 0.0010,
          mapCenterLng - 0.0008,
          size,
        ),
      };

      final Paint beaconPaint = Paint()
        ..color = Colors.pinkAccent
        ..style = PaintingStyle.fill;

      beacons.forEach((id, pos) {
        // Draw physical node AP dot
        canvas.drawCircle(pos, 6, beaconPaint);

        // Draw WiFi wave outline representing strength RSSI
        final rssi = beaconSignals[id] ?? -100.0;
        final signalWeight = 100.0 + rssi; // -100dBm -> 0, -30dBm -> 70
        final waveRadius = (signalWeight * 1.5).clamp(10.0, 100.0);

        final Paint wavePaint = Paint()
          ..color = Colors.pinkAccent.withValues(alpha: 0.12)
          ..style = PaintingStyle.stroke
          ..strokeWidth = 1.0;
        canvas.drawCircle(pos, waveRadius, wavePaint);
      });

      // Draw Estimated Indoor Centroid solved position
      if (indoorCentroid != null) {
        final pos = _toOffset(
          indoorCentroid!.latitude,
          indoorCentroid!.longitude,
          size,
        );
        final Paint centroidPaint = Paint()
          ..color = Colors.pinkAccent
          ..style = PaintingStyle.stroke
          ..strokeWidth = 2.0;

        // Hot pink targeting crosshair
        canvas.drawCircle(pos, 10, centroidPaint);
        canvas.drawLine(
          Offset(pos.dx - 15, pos.dy),
          Offset(pos.dx + 15, pos.dy),
          centroidPaint,
        );
        canvas.drawLine(
          Offset(pos.dx, pos.dy - 15),
          Offset(pos.dx, pos.dy + 15),
          centroidPaint,
        );

        final Paint centroidDot = Paint()
          ..color = Colors.white
          ..style = PaintingStyle.fill;
        canvas.drawCircle(pos, 4, centroidDot);
      }
    }

    // 5. Draw Geofences Layer
    for (final gf in activeGeofences) {
      final pos = _toOffset(gf.latitude, gf.longitude, size);
      final double radInPixels = gf.radiusInMeters * pixelsPerMeter;

      // Distance checking logic
      final distToGf = SmartLocation.distanceBetween(
        currentLocation.latitude,
        currentLocation.longitude,
        gf.latitude,
        gf.longitude,
      );

      final bool isInside = distToGf <= gf.radiusInMeters;

      final Paint gfPaint = Paint()
        ..color = isInside
            ? const Color(0xFF34D399).withValues(alpha: 0.15)
            : Colors.white.withValues(alpha: 0.04)
        ..style = PaintingStyle.fill;

      final Paint borderPaint = Paint()
        ..color = isInside ? const Color(0xFF34D399) : Colors.white24
        ..style = PaintingStyle.stroke
        ..strokeWidth = 1.5;

      canvas.drawCircle(pos, radInPixels, gfPaint);
      canvas.drawCircle(pos, radInPixels, borderPaint);

      // Label Geofence ID text
      final textPainter = TextPainter(
        text: TextSpan(
          text: gf.id,
          style: TextStyle(
            color: isInside ? const Color(0xFF34D399) : Colors.white54,
            fontSize: 9,
            fontWeight: FontWeight.bold,
          ),
        ),
        textDirection: TextDirection.ltr,
      )..layout();
      textPainter.paint(
        canvas,
        Offset(pos.dx - textPainter.width / 2, pos.dy - textPainter.height / 2),
      );
    }

    // 6. Draw Recorded Path Trail
    if (recordedPath.isNotEmpty) {
      final Paint pathPaint = Paint()
        ..color = const Color(0xFF7C86C9).withValues(alpha: 0.65)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2.5
        ..strokeCap = StrokeCap.round;

      for (int i = 0; i < recordedPath.length - 1; i++) {
        final p1 = _toOffset(
          recordedPath[i].latitude,
          recordedPath[i].longitude,
          size,
        );
        final p2 = _toOffset(
          recordedPath[i + 1].latitude,
          recordedPath[i + 1].longitude,
          size,
        );
        canvas.drawLine(p1, p2, pathPaint);
      }
    }

    // 7. Draw RDP Simplified Path Overlay
    if (simplifiedPath.isNotEmpty) {
      final Paint simplifiedPaint = Paint()
        ..color = const Color(0xFF34D399)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 4.0
        ..strokeCap = StrokeCap.round;

      final Paint nodePaint = Paint()
        ..color = const Color(0xFF34D399)
        ..style = PaintingStyle.fill;

      for (int i = 0; i < simplifiedPath.length - 1; i++) {
        final p1 = _toOffset(
          simplifiedPath[i].latitude,
          simplifiedPath[i].longitude,
          size,
        );
        final p2 = _toOffset(
          simplifiedPath[i + 1].latitude,
          simplifiedPath[i + 1].longitude,
          size,
        );
        canvas.drawLine(p1, p2, simplifiedPaint);
        canvas.drawCircle(p1, 5, nodePaint);
      }
      // Last node
      canvas.drawCircle(
        _toOffset(
          simplifiedPath.last.latitude,
          simplifiedPath.last.longitude,
          size,
        ),
        5,
        nodePaint,
      );
    }

    // 8. Draw Privacy Obfuscation dots
    if (showPrivacyInfo) {
      final pos = _toOffset(
        currentLocation.latitude,
        currentLocation.longitude,
        size,
      );

      // Dash fuzzed area radius
      final Paint rangePaint = Paint()
        ..color = Colors.deepOrange.withValues(alpha: 0.15)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 1.0;
      canvas.drawCircle(pos, fuzzRadius * pixelsPerMeter, rangePaint);

      // Fuzzed offset dot
      if (fuzzedLocation != null) {
        final fuzzPos = _toOffset(
          fuzzedLocation!.latitude,
          fuzzedLocation!.longitude,
          size,
        );
        final Paint fPaint = Paint()
          ..color = Colors.deepOrange
          ..style = PaintingStyle.fill;
        canvas.drawCircle(fuzzPos, 5, fPaint);
      }

      // Snapped grid coordinate dot
      if (snappedLocation != null) {
        final snapPos = _toOffset(
          snappedLocation!.latitude,
          snappedLocation!.longitude,
          size,
        );
        final Paint sPaint = Paint()
          ..color = Colors.orangeAccent
          ..style = PaintingStyle.fill;
        canvas.drawRect(
          Rect.fromCenter(center: snapPos, width: 8, height: 8),
          sPaint,
        );
      }
    }

    // 9. Draw Dead Reckoning Kinematic prediction Pin
    if (drPredictedLocation != null) {
      final drPos = _toOffset(
        drPredictedLocation!.latitude,
        drPredictedLocation!.longitude,
        size,
      );

      final Paint drPaint = Paint()
        ..color = Colors.orangeAccent
        ..style = PaintingStyle.fill;

      // Draws orange ghost dot representing extrapolation vector
      canvas.drawCircle(drPos, 8, drPaint);

      final Paint drRing = Paint()
        ..color = Colors.orangeAccent.withValues(alpha: 0.3)
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2.0;
      canvas.drawCircle(drPos, 14, drRing);
    }

    // 10. Draw Primary Device Pointer
    final primaryPos = _toOffset(
      currentLocation.latitude,
      currentLocation.longitude,
      size,
    );

    // Glowing halo pulse
    final Paint pulsePaint = Paint()
      ..color = const Color(0xFF7C86C9).withValues(alpha: 0.25)
      ..style = PaintingStyle.fill;
    canvas.drawCircle(primaryPos, 12, pulsePaint);

    final Paint borderPaint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    final Paint pinPaint = Paint()
      ..color = const Color(0xFF7C86C9)
      ..style = PaintingStyle.fill;

    canvas.drawCircle(primaryPos, 7, pinPaint);
    canvas.drawCircle(primaryPos, 7, borderPaint);

    // 11. Draw HUD Crosshairs centered on the primary pointer
    final Paint crossPaint = Paint()
      ..color = const Color(0xFF7C86C9).withValues(alpha: 0.25)
      ..strokeWidth = 0.8;

    // Dashed horizontal and vertical guides extending to boundaries
    _drawDashedLine(
      canvas,
      Offset(0, primaryPos.dy),
      Offset(width, primaryPos.dy),
      crossPaint,
    );
    _drawDashedLine(
      canvas,
      Offset(primaryPos.dx, 0),
      Offset(primaryPos.dx, height),
      crossPaint,
    );

    // Edge HUD Tick Coordinates Labels
    final String latText = '${currentLocation.latitude.toStringAsFixed(5)}N';
    final String lngText = '${currentLocation.longitude.toStringAsFixed(5)}W';

    final textStyle = TextStyle(
      color: const Color(0xFF7C86C9).withValues(alpha: 0.8),
      fontSize: 8,
      fontWeight: FontWeight.bold,
      fontFamily: 'monospace',
      backgroundColor: const Color(0xFF141828),
    );

    final latPainter = TextPainter(
      text: TextSpan(text: latText, style: textStyle),
      textDirection: TextDirection.ltr,
    )..layout();

    final lngPainter = TextPainter(
      text: TextSpan(text: lngText, style: textStyle),
      textDirection: TextDirection.ltr,
    )..layout();

    // Draw latitude label on the left margin
    latPainter.paint(canvas, Offset(4, primaryPos.dy - latPainter.height / 2));
    // Draw longitude label on the top margin
    lngPainter.paint(canvas, Offset(primaryPos.dx - lngPainter.width / 2, 4));

    // 12. Draw Visual Scale Bar (100m)
    final double scaleWidth = 100.0 * pixelsPerMeter;
    final Paint scalePaint = Paint()
      ..color = Colors.white70
      ..strokeWidth = 1.5;

    final double scaleX = width - scaleWidth - 10;
    final double scaleY = height - 10;

    // Draw horizontal scale bar
    canvas.drawLine(
      Offset(scaleX, scaleY),
      Offset(scaleX + scaleWidth, scaleY),
      scalePaint,
    );
    // Draw vertical end ticks
    canvas.drawLine(
      Offset(scaleX, scaleY - 4),
      Offset(scaleX, scaleY + 4),
      scalePaint,
    );
    canvas.drawLine(
      Offset(scaleX + scaleWidth, scaleY - 4),
      Offset(scaleX + scaleWidth, scaleY + 4),
      scalePaint,
    );

    final scaleTextPainter = TextPainter(
      text: const TextSpan(
        text: '100m',
        style: TextStyle(
          color: Colors.white70,
          fontSize: 8,
          fontFamily: 'monospace',
          fontWeight: FontWeight.bold,
        ),
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    scaleTextPainter.paint(
      canvas,
      Offset(scaleX + (scaleWidth - scaleTextPainter.width) / 2, scaleY - 12),
    );
  }

  @override
  bool shouldRepaint(covariant LocationSandboxPainter oldDelegate) {
    return true; // Continuously repaint for animation
  }
}

// --- CUSTOM RADIAL SPEEDOMETER DIAL PAINTER ---
class SpeedometerPainter extends CustomPainter {
  final double speedKmh;
  final double limitKmh;

  SpeedometerPainter({required this.speedKmh, required this.limitKmh});

  @override
  void paint(Canvas canvas, Size size) {
    final double width = size.width;
    final double height = size.height;
    final Offset center = Offset(width / 2, height * 0.75);
    final double radius = math.min(width / 2, height * 0.7) - 6;

    // 1. Draw dial background arc (180 degrees sweep: from pi to 2*pi)
    final Paint bgArcPaint = Paint()
      ..color = Colors.white10
      ..style = PaintingStyle.stroke
      ..strokeWidth = 6
      ..strokeCap = StrokeCap.round;

    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      math.pi,
      math.pi,
      false,
      bgArcPaint,
    );

    // 2. Draw active speed arc (gradient color)
    final double speedPercent = (speedKmh / 120.0).clamp(0.0, 1.0);
    final double sweepAngle = speedPercent * math.pi;

    final Paint activeArcPaint = Paint()
      ..shader = SweepGradient(
        center: Alignment.center,
        colors: [
          const Color(0xFF7C86C9), // Cyan
          const Color(0xFF34D399), // Green
          Colors.orangeAccent,
          Colors.redAccent,
        ],
        stops: const [0.0, 0.4, 0.7, 1.0],
      ).createShader(Rect.fromCircle(center: center, radius: radius))
      ..style = PaintingStyle.stroke
      ..strokeWidth = 8
      ..strokeCap = StrokeCap.round;

    canvas.save();
    // Rotate canvas around center to align sweep gradient with horizontal start
    canvas.translate(center.dx, center.dy);
    canvas.rotate(math.pi);
    canvas.translate(-center.dx, -center.dy);
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      0,
      sweepAngle,
      false,
      activeArcPaint,
    );
    canvas.restore();

    // 3. Draw Speed Limit Marker tick
    final double limitPercent = (limitKmh / 120.0).clamp(0.0, 1.0);
    final double limitAngle = math.pi + (limitPercent * math.pi);
    final Offset tickStart =
        center +
        Offset(
          math.cos(limitAngle) * (radius - 12),
          math.sin(limitAngle) * (radius - 12),
        );
    final Offset tickEnd =
        center +
        Offset(
          math.cos(limitAngle) * (radius + 4),
          math.sin(limitAngle) * (radius + 4),
        );

    final Paint limitTickPaint = Paint()
      ..color = Colors.redAccent
      ..strokeWidth = 3.0;
    canvas.drawLine(tickStart, tickEnd, limitTickPaint);

    // 4. Draw Dial Needle
    final double needleAngle = math.pi + sweepAngle;
    final Offset needleEnd =
        center +
        Offset(
          math.cos(needleAngle) * (radius - 10),
          math.sin(needleAngle) * (radius - 10),
        );

    final Paint needlePaint = Paint()
      ..color = speedKmh > limitKmh ? Colors.redAccent : const Color(0xFF7C86C9)
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.round;
    canvas.drawLine(center, needleEnd, needlePaint);

    // Needle pivot center cap
    final Paint pivotPaint = Paint()
      ..color = const Color(0xFF1F2439)
      ..style = PaintingStyle.fill;
    final Paint pivotBorder = Paint()
      ..color = Colors.white24
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.5;

    canvas.drawCircle(center, 7, pivotPaint);
    canvas.drawCircle(center, 7, pivotBorder);

    // 5. Draw Digital speed text
    final textStyle = TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
      fontFamily: 'monospace',
      color: speedKmh > limitKmh ? Colors.redAccent : Colors.white,
    );
    final textPainter = TextPainter(
      text: TextSpan(
        text: '${speedKmh.toStringAsFixed(0)} KM/H',
        style: textStyle,
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    textPainter.paint(
      canvas,
      Offset(center.dx - textPainter.width / 2, center.dy - radius / 3),
    );

    // Limit label
    final limitStyle = const TextStyle(
      fontSize: 8,
      color: Colors.grey,
      fontWeight: FontWeight.bold,
    );
    final limitPainter = TextPainter(
      text: TextSpan(text: 'LIMIT: ${limitKmh.round()}', style: limitStyle),
      textDirection: TextDirection.ltr,
    )..layout();
    limitPainter.paint(
      canvas,
      Offset(center.dx - limitPainter.width / 2, center.dy + 8),
    );
  }

  @override
  bool shouldRepaint(covariant SpeedometerPainter oldDelegate) {
    return oldDelegate.speedKmh != speedKmh || oldDelegate.limitKmh != limitKmh;
  }
}

// --- CUSTOM 2D G-FORCE G-METER PAINTER ---
class GMeterPainter extends CustomPainter {
  final double longG;
  final double latG;

  GMeterPainter({required this.longG, required this.latG});

  @override
  void paint(Canvas canvas, Size size) {
    final double width = size.width;
    final double height = size.height;
    final Offset center = Offset(width / 2, height / 2);
    final double radius = math.min(width, height) / 2 - 8;

    // 1. Draw background target concentric circles (0.5G, 1.0G, 1.5G)
    final Paint circlePaint = Paint()
      ..color = Colors.white10
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.0;

    canvas.drawCircle(center, radius * 0.33, circlePaint); // 0.5G
    canvas.drawCircle(center, radius * 0.66, circlePaint); // 1.0G
    canvas.drawCircle(center, radius, circlePaint); // 1.5G

    // 2. Draw cross axes (horizontal and vertical center lines)
    canvas.drawLine(
      Offset(center.dx - radius, center.dy),
      Offset(center.dx + radius, center.dy),
      circlePaint,
    );
    canvas.drawLine(
      Offset(center.dx, center.dy - radius),
      Offset(center.dx, center.dy + radius),
      circlePaint,
    );

    // Label grid markers
    final textStyle = const TextStyle(
      color: Colors.white24,
      fontSize: 8,
      fontWeight: FontWeight.bold,
    );
    final textPainter = TextPainter(
      text: TextSpan(text: '1.0G', style: textStyle),
      textDirection: TextDirection.ltr,
    )..layout();
    textPainter.paint(
      canvas,
      Offset(center.dx + radius * 0.66 - textPainter.width / 2, center.dy + 2),
    );

    // 3. Draw G-force bubble vector dot
    // latG moves bubble horizontally (X axis)
    // longG moves bubble vertically (Y axis) - decelerate/braking shifts bubble UP (forward), acceleration shifts it DOWN (backward)
    final double multiplier = radius / 1.5; // Scale max reading range to 1.5G
    final double bubbleX =
        center.dx + (latG * multiplier).clamp(-radius, radius);
    final double bubbleY =
        center.dy - (longG * multiplier).clamp(-radius, radius);
    final Offset bubbleOffset = Offset(bubbleX, bubbleY);

    // Draw glowing trace line connecting center to bubble position
    final double totalG = math.sqrt(longG * longG + latG * latG);
    final bool isHarsh =
        totalG > 0.3; // High G threshold warning (rough acceleration or turn)

    final Paint tracePaint = Paint()
      ..color = isHarsh
          ? Colors.orangeAccent.withValues(alpha: 0.4)
          : const Color(0xFF7C86C9).withValues(alpha: 0.3)
      ..strokeWidth = 1.5;
    canvas.drawLine(center, bubbleOffset, tracePaint);

    // Draw dynamic bubble dot
    final Paint bubblePaint = Paint()
      ..color = isHarsh ? Colors.orangeAccent : const Color(0xFF34D399)
      ..style = PaintingStyle.fill;
    final Paint bubbleHalo = Paint()
      ..color = (isHarsh ? Colors.orangeAccent : const Color(0xFF34D399))
          .withValues(alpha: 0.25)
      ..style = PaintingStyle.fill;

    canvas.drawCircle(bubbleOffset, 10, bubbleHalo);
    canvas.drawCircle(bubbleOffset, 5, bubblePaint);

    // Digital readout text on bottom corner
    final readoutPainter = TextPainter(
      text: TextSpan(
        text: 'G: ${totalG.toStringAsFixed(2)}',
        style: TextStyle(
          color: isHarsh ? Colors.orangeAccent : Colors.grey,
          fontSize: 9,
          fontWeight: FontWeight.bold,
          fontFamily: 'monospace',
        ),
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    readoutPainter.paint(
      canvas,
      Offset(
        width - readoutPainter.width - 4,
        height - readoutPainter.height - 4,
      ),
    );
  }

  @override
  bool shouldRepaint(covariant GMeterPainter oldDelegate) {
    return oldDelegate.longG != longG || oldDelegate.latG != latG;
  }
}
5
likes
0
points
549
downloads

Publisher

unverified uploader

Weekly Downloads

Smart Location is a powerful, battery-conscious Flutter plugin for continuous background geolocation tracking, advanced geofencing, and motion-aware updates.

Repository (GitHub)
View/report issues

Topics

#geofencing #background #geolocation #location #tracking

License

unknown (license)

Dependencies

cryptography, flutter, flutter_web_plugins, geolocator, http, plugin_platform_interface, protobuf, tflite_flutter, web

More

Packages that depend on smart_location

Packages that implement smart_location