locus 2.1.3 copy "locus: ^2.1.3" to clipboard
locus: ^2.1.3 copied to clipboard

Core background geolocation SDK of the WeOrbis ecosystem for Flutter. Native tracking, geofencing, activity recognition, and sync.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart' hide Config;
import 'package:locus/locus.dart';

void main() => runApp(const LocusExampleApp());

// =============================================================================
// App Entry Point
// =============================================================================

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

  @override
  State<LocusExampleApp> createState() => _LocusExampleAppState();
}

class _LocusExampleAppState extends State<LocusExampleApp> {
  static const int _maxEventEntries = 250;

  final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
      GlobalKey<ScaffoldMessengerState>();

  // Stream subscriptions
  final List<StreamSubscription<dynamic>> _subscriptions = [];

  // State
  final List<String> _events = [];
  final Map<String, int> _eventCounts = {};
  Location? _latestLocation;
  Activity? _lastActivity;

  ConnectivityChangeEvent? _lastConnectivity;

  GeolocationState? _lastState;

  List<LogEntry>? _lastLog;

  PowerState? _powerState;
  BatteryStats? _batteryStats;
  BatteryRunway? _batteryRunway;
  AdaptiveSettings? _adaptiveSettings;
  AdaptiveTrackingConfig? _adaptiveTrackingConfig;
  PowerStateChangeEvent? _lastPowerEvent;
  List<Location> _storedLocations = [];
  LocationSummary? _locationSummary;
  List<QueueItem> _syncQueue = [];
  DiagnosticsSnapshot? _diagnostics;
  List<PolygonGeofence> _polygonGeofences = [];
  List<PrivacyZone> _privacyZones = [];
  GeofenceWorkflowEvent? _lastWorkflowEvent;
  LocationQuality? _lastQuality;
  LocationAnomaly? _lastAnomaly;
  StreamSubscription<LocationQuality>? _qualitySubscription;
  StreamSubscription<LocationAnomaly>? _anomalySubscription;

  // Toggles
  bool _isRunning = false;
  bool _isReady = false;

  bool _spoofDetectionEnabled = false;
  bool _significantChangesEnabled = false;
  bool _isSyncPaused = false;
  bool _automationEnabled = false;
  bool _qualityMonitoringEnabled = false;
  bool _anomalyMonitoringEnabled = false;
  bool _workflowRegistered = false;
  String? _benchmarkStatus;
  TrackingProfile? _currentProfile;
  SyncPolicy _syncPolicy = SyncPolicy.balanced;
  SyncDecision? _syncDecision;
  String? _lastQueueId;
  String _activeScenario = 'None';
  Map<String, dynamic> _syncContext = const {
    'shift_id': 'shift-001',
    'driver_id': 'driver-42',
    'route_id': 'route-7',
  };

  @override
  void initState() {
    super.initState();
    unawaited(_configure());
  }

  @override
  void dispose() {
    unawaited(_qualitySubscription?.cancel());
    unawaited(_anomalySubscription?.cancel());
    for (final sub in _subscriptions) {
      unawaited(sub.cancel());
    }
    super.dispose();
  }

  // ===========================================================================
  // Configuration
  // ===========================================================================

  Future<void> _configure() async {
    final isGranted = await Locus.requestPermission();
    if (!isGranted) {
      _showSnackbar('Location permission required', isSuccess: false);
      return;
    }

    final config = Config(
      stationaryRadius: 25,
      motionTriggerDelay: 15000,
      activityRecognitionInterval: 10000,
      startOnBoot: true,
      stopOnTerminate: false,
      enableHeadless: true,
      autoSync: true,
      batchSync: true,
      maxBatchSize: 5,
      autoSyncThreshold: 1,
      queueMaxDays: 7,
      queueMaxRecords: 500,
      persistMode: PersistMode.location,
      maxDaysToPersist: 7,
      maxRecordsToPersist: 200,
      maxMonitoredGeofences: 20,
      url: 'https://example.com/locations',
      extras: _syncContext,
      adaptiveTracking: AdaptiveTrackingConfig.balanced,
      logLevel: LogLevel.info,
      notification: const NotificationConfig(
        title: 'Locus Example',
        text: 'Tracking location in background',
      ),
    );

    await Locus.ready(config);
    await _configureProfiles();
    _setupListeners();
    await _refreshState();
    await Locus.dataSync.setPolicy(_syncPolicy);
    await Locus.battery.setAdaptiveTracking(AdaptiveTrackingConfig.balanced);

    // Demo: Custom sync body builder
    await Locus.setSyncBodyBuilder((locations, extras) async {
      return {
        'app': 'locus_example',
        'timestamp': DateTime.now().toIso8601String(),
        'locations': locations.map((l) => l.toMap()).toList(),
        ...extras,
      };
    });
    Locus.dataSync.setHeadersCallback(() async {
      return {
        'X-Client': 'locus_example',
        'X-Shift-Id': _syncContext['shift_id']?.toString() ?? 'unknown',
      };
    });
    Locus.dataSync.setPreSyncValidator((locations, extras) async {
      final hasShift = extras['shift_id'] != null;
      if (!hasShift) {
        _recordEvent('sync', 'Sync skipped (missing shift_id)');
      }
      return hasShift;
    });

    setState(() {
      _isReady = true;
      _adaptiveTrackingConfig = AdaptiveTrackingConfig.balanced;
    });
  }

  Future<void> _configureProfiles({bool enableAutomation = false}) async {
    final rules = enableAutomation
        ? [
            const TrackingProfileRule(
              profile: TrackingProfile.enRoute,
              type: TrackingProfileRuleType.activity,
              activity: ActivityType.inVehicle,
              cooldownSeconds: 60,
            ),
            const TrackingProfileRule(
              profile: TrackingProfile.standby,
              type: TrackingProfileRuleType.activity,
              activity: ActivityType.still,
              cooldownSeconds: 60,
            ),
            const TrackingProfileRule(
              profile: TrackingProfile.arrived,
              type: TrackingProfileRuleType.geofence,
              geofenceAction: GeofenceAction.enter,
              geofenceIdentifier: 'delivery_dropoff',
              cooldownSeconds: 120,
            ),
          ]
        : const <TrackingProfileRule>[];
    await Locus.setTrackingProfiles({
      TrackingProfile.offDuty: ConfigPresets.lowPower,
      TrackingProfile.standby: ConfigPresets.balanced,
      TrackingProfile.enRoute: ConfigPresets.tracking,
      TrackingProfile.arrived: ConfigPresets.trail,
    },
        initialProfile: TrackingProfile.standby,
        rules: rules,
        enableAutomation: enableAutomation);
    setState(() {
      _currentProfile = Locus.currentTrackingProfile;
      _automationEnabled = enableAutomation;
    });
  }

  void _setupListeners() {
    _subscriptions.addAll([
      Locus.location.stream.listen((loc) {
        _recordEvent('location', _formatLocation(loc));
        setState(() => _latestLocation = loc);
      }),
      Locus.location.motionChanges.listen((loc) {
        _recordEvent(
          'motion',
          'Motion: ${loc.isMoving == true ? "moving" : "stationary"}',
        );
        setState(() => _latestLocation = loc);
      }),
      Locus.location.heartbeats.listen((loc) {
        _recordEvent('heartbeat', 'Heartbeat: ${_formatLocation(loc)}');
      }),
      Locus.instance.activityStream.listen((activity) {
        _recordEvent(
          'activity',
          'Activity: ${activity.type.name} (${activity.confidence}%)',
        );
        setState(() => _lastActivity = activity);
      }),
      Locus.trips.events.listen((event) {
        _recordEvent('trip', 'Trip: ${event.type.name}');
      }),
      Locus.instance.providerStream.listen((event) {
        _recordEvent('provider', 'Provider: ${event.authorizationStatus.name}');
      }),
      Locus.geofencing.events.listen((event) {
        _recordEvent(
          'geofence',
          'Geofence: ${event.geofence.identifier} ${event.action.name}',
        );
      }),
      Locus.geofencing.polygonEvents.listen((event) {
        _recordEvent(
          'polygon',
          'Polygon: ${event.geofence.identifier} ${event.type.name}',
        );
      }),
      Locus.geofencing.workflowEvents.listen((event) {
        _recordEvent(
          'workflow',
          'Workflow: ${event.workflowId} ${event.status.name}',
        );
        setState(() => _lastWorkflowEvent = event);
      }),
      Locus.privacy.events.listen((event) {
        _recordEvent(
          'privacy',
          'Privacy: ${event.zone.identifier} ${event.type.name}',
        );
      }),
      Locus.dataSync.connectivityEvents.listen((event) {
        _recordEvent(
          'connectivity',
          'Network: ${event.connected ? "online" : "offline"}',
        );
        setState(() => _lastConnectivity = event);
      }),
      Locus.battery.powerStateEvents.listen((event) {
        _recordEvent(
          'power',
          'Power: ${event.changeType.name} ${event.current.batteryLevel}%',
        );
        setState(() => _lastPowerEvent = event);
      }),
      Locus.battery.powerSaveChanges.listen((enabled) {
        _recordEvent('power', 'Power save: ${enabled ? "ON" : "OFF"}');
      }),
      Locus.instance.enabledStream.listen((enabled) {
        _recordEvent('state', 'Tracking: ${enabled ? "started" : "stopped"}');
        setState(() => _isRunning = enabled);
      }),
      Locus.dataSync.events.listen((event) {
        _recordEvent(
          'http',
          'HTTP: ${event.status} ${event.ok ? "OK" : "FAILED"}',
        );
      }),
      Locus.instance.onNotificationAction((action) {
        _recordEvent('notification', 'Action: $action');
      }),
    ]);
  }

  // ===========================================================================
  // Actions
  // ===========================================================================

  Future<void> _refreshState() async {
    final state = await Locus.getState();
    _isSyncPaused = Locus.dataSync.isPaused;
    setState(() {
      _lastState = state;
      _isRunning = state.enabled;
    });
  }

  Future<void> _toggleTracking() async {
    if (!_isReady) {
      _showSnackbar('SDK not ready', isSuccess: false);
      return;
    }
    if (_isRunning) {
      await Locus.stop();
      _showSnackbar('Tracking stopped');
    } else {
      await Locus.start();
      _showSnackbar('Tracking started');
    }
    await _refreshState();
  }

  Future<void> _getPosition() async {
    try {
      final loc = await Locus.location.getCurrentPosition();
      setState(() => _latestLocation = loc);
      _showSnackbar(
        'Position: ${loc.coords.latitude.toStringAsFixed(4)}, ${loc.coords.longitude.toStringAsFixed(4)}',
      );
      _recordEvent('position', _formatLocation(loc));
    } catch (e) {
      _showSnackbar('Failed to get position', isSuccess: false);
    }
  }

  Future<void> _setProfile(TrackingProfile profile) async {
    await Locus.setTrackingProfile(profile);
    setState(() => _currentProfile = profile);
    _showSnackbar('Profile: ${profile.name}');
    _recordEvent('profile', 'Switched to ${profile.name}');
  }

  Future<void> _addGeofence() async {
    await Locus.geofencing.add(
      const Geofence(
        identifier: 'demo_geofence',
        radius: 100,
        latitude: 37.4219983,
        longitude: -122.084,
        notifyOnEntry: true,
        notifyOnExit: true,
      ),
    );
    final count = (await Locus.geofencing.getAll()).length;
    _showSnackbar('Geofence added ($count total)');
    _recordEvent('geofence', 'Added demo_geofence');
  }

  Future<void> _clearGeofences() async {
    final count = (await Locus.geofencing.getAll()).length;
    await Locus.geofencing.removeAll();
    _showSnackbar('Cleared $count geofence(s)');
    _recordEvent('geofence', 'Cleared all');
  }

  Future<void> _addPrivacyZone() async {
    await Locus.privacy.add(
      PrivacyZone.create(
        identifier: 'demo_zone',
        latitude: 37.4219983,
        longitude: -122.084,
        radius: 200,
        action: PrivacyZoneAction.obfuscate,
      ),
    );
    final zones = await Locus.privacy.getAll();
    setState(() => _privacyZones = zones);
    final count = zones.length;
    _showSnackbar('Privacy zone added ($count total)');
    _recordEvent('privacy', 'Added demo_zone');
  }

  Future<void> _startTrip() async {
    await Locus.trips.start(const TripConfig(startOnMoving: true));
    _showSnackbar('Trip started');
    _recordEvent('trip', 'Trip started');
  }

  Future<void> _stopTrip() async {
    final summary = await Locus.trips.stop();
    // setState(() => _lastTripSummary = summary);
    if (summary != null) {
      _showSnackbar('Trip: ${summary.distanceMeters.toStringAsFixed(0)}m');
    } else {
      _showSnackbar('Trip stopped');
    }
    _recordEvent('trip', 'Trip stopped');
  }

  Future<void> _syncNow() async {
    if (_isSyncPaused) {
      _showSnackbar('Sync is paused', isSuccess: false);
      return;
    }
    final result = await Locus.dataSync.now();
    _showSnackbar('Sync: $result');
    _recordEvent('sync', 'Manual sync: $result');
  }

  Future<void> _toggleSyncPause() async {
    if (_isSyncPaused) {
      await Locus.dataSync.resume();
      setState(() => _isSyncPaused = false);
      _showSnackbar('Sync resumed');
      _recordEvent('sync', 'Sync resumed');
    } else {
      await Locus.dataSync.pause();
      setState(() => _isSyncPaused = true);
      _showSnackbar('Sync paused');
      _recordEvent('sync', 'Sync paused');
    }
  }

  Future<void> _loadLocations() async {
    final locs = await Locus.location.getLocations(limit: 50);
    setState(() => _storedLocations = locs);
    _showSnackbar('Loaded ${locs.length} location(s)');
  }

  Future<void> _clearLocations() async {
    await Locus.location.destroyLocations();
    setState(() => _storedLocations = []);
    _showSnackbar('Locations cleared');
  }

  Future<void> _loadLogs() async {
    final logs = await Locus.getLog();
    setState(() => _lastLog = logs);
    _showSnackbar('Loaded ${logs.length} log entries');
  }

  Future<void> _refreshBattery() async {
    final state = await Locus.battery.getPowerState();
    final stats = await Locus.battery.getStats();
    setState(() {
      _powerState = state;
      _batteryStats = stats;
    });
    _showSnackbar('Battery: ${state.batteryLevel}%');
    _recordEvent(
      'battery',
      '${state.batteryLevel}%, GPS: ${(stats.gpsOnTimePercent * 100).toStringAsFixed(0)}%',
    );
  }

  Future<void> _toggleBenchmark() async {
    if (_benchmarkStatus == null) {
      await Locus.startBatteryBenchmark();
      setState(() => _benchmarkStatus = 'Running');
      _showSnackbar('Benchmark started');
    } else {
      await Locus.stopBatteryBenchmark();
      setState(() => _benchmarkStatus = null);
      _showSnackbar('Benchmark stopped');
    }
  }

  Future<void> _toggleSpoof() async {
    final enabled = !_spoofDetectionEnabled;
    await Locus.setSpoofDetection(
      enabled ? SpoofDetectionConfig.high : SpoofDetectionConfig.disabled,
    );
    setState(() => _spoofDetectionEnabled = enabled);
    _showSnackbar('Spoof detection: ${enabled ? "ON" : "OFF"}');
  }

  Future<void> _toggleSignificant() async {
    final enabled = !_significantChangesEnabled;
    if (enabled) {
      await Locus.startSignificantChangeMonitoring(
        SignificantChangeConfig.defaults,
      );
    } else {
      await Locus.stopSignificantChangeMonitoring();
    }
    setState(() => _significantChangesEnabled = enabled);
    _showSnackbar('Significant changes: ${enabled ? "ON" : "OFF"}');
  }

  Future<void> _toggleAutomation() async {
    final enabled = !_automationEnabled;
    await _configureProfiles(enableAutomation: enabled);
    _showSnackbar('Automation: ${enabled ? "ON" : "OFF"}');
    _recordEvent('profile', 'Automation ${enabled ? "enabled" : "disabled"}');
  }

  Future<void> _applySyncContext(Map<String, dynamic> context) async {
    await Locus.setConfig(Config(extras: context));
    await Locus.dataSync.refreshHeaders();
    setState(() => _syncContext = Map<String, dynamic>.from(context));
    _showSnackbar('Context: ${context['shift_id'] ?? 'updated'}');
    _recordEvent('config', 'Context updated');
  }

  Future<void> _applySyncPolicy(SyncPolicy policy, String label) async {
    await Locus.dataSync.setPolicy(policy);
    setState(() => _syncPolicy = policy);
    _showSnackbar('Sync policy: $label');
    _recordEvent('sync', 'Policy set: $label');
  }

  Future<void> _evaluateSyncPolicy() async {
    final decision = await Locus.dataSync.evaluatePolicy(policy: _syncPolicy);
    setState(() => _syncDecision = decision);
    _showSnackbar(decision.reason);
    _recordEvent('sync', 'Policy decision: ${decision.reason}');
  }

  Future<void> _enqueueCheckIn() async {
    final payload = {
      'type': 'check_in',
      'timestamp': DateTime.now().toIso8601String(),
      'context': _syncContext,
      if (_latestLocation != null) 'coords': _latestLocation!.coords.toMap(),
    };
    final id = await Locus.dataSync.enqueue(
      payload,
      type: 'check_in',
      idempotencyKey: 'check_in_${DateTime.now().millisecondsSinceEpoch}',
    );
    setState(() => _lastQueueId = id);
    _showSnackbar('Queued check-in');
    _recordEvent('queue', 'Enqueued check-in: $id');
  }

  Future<void> _loadSyncQueue() async {
    final items = await Locus.dataSync.getQueue(limit: 20);
    setState(() => _syncQueue = items);
    _showSnackbar('Queue: ${items.length} item(s)');
  }

  Future<void> _syncQueueNow() async {
    final count = await Locus.dataSync.syncQueue(limit: 20);
    _showSnackbar('Synced $count queued item(s)');
    _recordEvent('sync', 'Queue sync: $count');
    await _loadSyncQueue();
  }

  Future<void> _clearSyncQueue() async {
    await Locus.dataSync.clearQueue();
    setState(() => _syncQueue = []);
    _showSnackbar('Queue cleared');
    _recordEvent('queue', 'Queue cleared');
  }

  Future<void> _addPolygonGeofence() async {
    final polygon = PolygonGeofence(
      identifier: 'campus_zone',
      vertices: [
        const GeoPoint(latitude: 37.4232, longitude: -122.0852),
        const GeoPoint(latitude: 37.4232, longitude: -122.0824),
        const GeoPoint(latitude: 37.4210, longitude: -122.0824),
        const GeoPoint(latitude: 37.4210, longitude: -122.0852),
      ],
      notifyOnEntry: true,
      notifyOnExit: true,
      notifyOnDwell: true,
      loiteringDelay: 10000,
      extras: {'name': 'Demo Campus'},
    );
    final added = await Locus.geofencing.addPolygon(polygon);
    final polygons = await Locus.geofencing.getAllPolygons();
    setState(() => _polygonGeofences = polygons);
    _showSnackbar(added ? 'Polygon added' : 'Polygon exists');
    _recordEvent('polygon', 'Polygons: ${polygons.length}');
  }

  Future<void> _clearPolygonGeofences() async {
    await Locus.geofencing.removeAllPolygons();
    setState(() => _polygonGeofences = []);
    _showSnackbar('Polygons cleared');
    _recordEvent('polygon', 'Cleared polygons');
  }

  Future<void> _registerDeliveryWorkflow() async {
    await Locus.geofencing.addAll(const [
      Geofence(
        identifier: 'delivery_pickup',
        radius: 120,
        latitude: 37.4228,
        longitude: -122.085,
        notifyOnEntry: true,
        notifyOnExit: false,
      ),
      Geofence(
        identifier: 'delivery_dropoff',
        radius: 120,
        latitude: 37.4187,
        longitude: -122.0816,
        notifyOnEntry: true,
        notifyOnExit: true,
      ),
    ]);
    const workflow = GeofenceWorkflow(
      id: 'delivery_flow',
      requireSequence: true,
      steps: [
        GeofenceWorkflowStep(
          id: 'pickup',
          geofenceIdentifier: 'delivery_pickup',
          action: GeofenceAction.enter,
          cooldownSeconds: 30,
        ),
        GeofenceWorkflowStep(
          id: 'dropoff',
          geofenceIdentifier: 'delivery_dropoff',
          action: GeofenceAction.enter,
          cooldownSeconds: 30,
        ),
      ],
    );
    Locus.geofencing.registerWorkflows([workflow]);
    setState(() => _workflowRegistered = true);
    _showSnackbar('Workflow registered');
    _recordEvent('workflow', 'Registered delivery workflow');
  }

  void _stopWorkflows() {
    Locus.geofencing.stopWorkflows();
    Locus.geofencing.clearWorkflows();
    setState(() => _workflowRegistered = false);
    _showSnackbar('Workflows cleared');
    _recordEvent('workflow', 'Workflows cleared');
  }

  Future<void> _loadPrivacyZones() async {
    final zones = await Locus.privacy.getAll();
    setState(() => _privacyZones = zones);
    _showSnackbar('Privacy zones: ${zones.length}');
  }

  Future<void> _clearPrivacyZones() async {
    await Locus.privacy.removeAll();
    setState(() => _privacyZones = []);
    _showSnackbar('Privacy zones cleared');
    _recordEvent('privacy', 'Cleared all zones');
  }

  Future<void> _loadLocationSummary() async {
    final summary = await Locus.location.getSummary(
      query: LocationQuery.lastHours(12, limit: 250),
    );
    setState(() => _locationSummary = summary);
    _showSnackbar('Summary: ${summary.locationCount} points');
  }

  Future<void> _loadDiagnostics() async {
    final snapshot = await Locus.getDiagnostics();
    setState(() => _diagnostics = snapshot);
    _showSnackbar('Diagnostics captured');
    _recordEvent('diagnostics', 'Diagnostics snapshot captured');
  }

  Future<void> _setAdaptiveTracking(
    AdaptiveTrackingConfig config,
    String label,
  ) async {
    await Locus.battery.setAdaptiveTracking(config);
    setState(() => _adaptiveTrackingConfig = config);
    _showSnackbar('Adaptive: $label');
    _recordEvent('battery', 'Adaptive tracking: $label');
  }

  Future<void> _calculateAdaptiveSettings() async {
    final settings = await Locus.battery.calculateAdaptiveSettings();
    setState(() => _adaptiveSettings = settings);
    _showSnackbar('Adaptive settings updated');
  }

  Future<void> _estimateRunway() async {
    final runway = await Locus.battery.estimateRunway();
    setState(() => _batteryRunway = runway);
    _showSnackbar(runway.recommendation);
  }

  Future<void> _toggleQualityMonitoring() async {
    if (_qualityMonitoringEnabled) {
      await _qualitySubscription?.cancel();
      setState(() {
        _qualityMonitoringEnabled = false;
        _lastQuality = null;
      });
      _showSnackbar('Quality monitoring stopped');
      return;
    }
    _qualitySubscription = Locus.locationQuality(
      config: const LocationQualityConfig(maxAccuracyMeters: 80, windowSize: 5),
    ).listen((quality) {
      setState(() => _lastQuality = quality);
      _recordEvent(
        'quality',
        'Quality: ${(quality.overallScore * 100).toStringAsFixed(0)}%',
      );
    });
    setState(() => _qualityMonitoringEnabled = true);
    _showSnackbar('Quality monitoring started');
  }

  Future<void> _toggleAnomalyMonitoring() async {
    if (_anomalyMonitoringEnabled) {
      await _anomalySubscription?.cancel();
      setState(() {
        _anomalyMonitoringEnabled = false;
        _lastAnomaly = null;
      });
      _showSnackbar('Anomaly monitoring stopped');
      return;
    }
    _anomalySubscription = Locus.locationAnomalies(
      config: const LocationAnomalyConfig(maxSpeedKph: 180),
    ).listen((anomaly) {
      setState(() => _lastAnomaly = anomaly);
      _recordEvent(
        'anomaly',
        'Anomaly: ${anomaly.speedKph.toStringAsFixed(0)} kph',
      );
    });
    setState(() => _anomalyMonitoringEnabled = true);
    _showSnackbar('Anomaly monitoring started');
  }

  Future<void> _setPace(bool isMoving) async {
    await Locus.location.changePace(isMoving);
    _showSnackbar('Pace: ${isMoving ? "moving" : "stationary"}');
    _recordEvent('motion', 'Pace set: ${isMoving ? "moving" : "stationary"}');
  }

  Future<void> _resetOdometer() async {
    await Locus.location.setOdometer(0);
    await _refreshState();
    _showSnackbar('Odometer reset');
    _recordEvent('state', 'Odometer reset');
  }

  Future<void> _activateDeliveryOps() async {
    if (!_ensureReady()) return;
    await _applySyncContext({
      'shift_id': 'shift-204',
      'driver_id': 'driver-17',
      'route_id': 'route-5',
      'tenant': 'west-coast',
    });
    await _applySyncPolicy(SyncPolicy.balanced, 'Balanced');
    await _setAdaptiveTracking(AdaptiveTrackingConfig.balanced, 'Balanced');
    await _registerDeliveryWorkflow();
    await _configureProfiles(enableAutomation: true);
    if (Locus.trips.getState() == null) {
      await Locus.trips.start(
        const TripConfig(
          tripId: 'delivery-204',
          startOnMoving: true,
          destination: RoutePoint(
            latitude: 37.4187,
            longitude: -122.0816,
          ),
          waypoints: [
            RoutePoint(latitude: 37.4215, longitude: -122.0834),
          ],
        ),
      );
    }
    if (!_isRunning) {
      await Locus.start();
    }
    setState(() => _activeScenario = 'Delivery Ops');
    _showSnackbar('Delivery ops ready');
    _recordEvent('scenario', 'Delivery ops activated');
  }

  Future<void> _activatePrivacyMode() async {
    if (!_ensureReady()) return;
    await _addPrivacyZone();
    if (!_spoofDetectionEnabled) {
      await _toggleSpoof();
    }
    setState(() => _activeScenario = 'Privacy Mode');
    _showSnackbar('Privacy mode enabled');
    _recordEvent('scenario', 'Privacy mode enabled');
  }

  // ===========================================================================
  // Helpers
  // ===========================================================================

  bool _ensureReady() {
    if (!_isReady) {
      _showSnackbar('SDK not ready', isSuccess: false);
      return false;
    }
    return true;
  }

  void _recordEvent(String type, String message) {
    final time = DateTime.now();
    final ts =
        '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}:${time.second.toString().padLeft(2, '0')}';
    setState(() {
      _events.insert(0, '[$ts] $message');
      _eventCounts[type] = (_eventCounts[type] ?? 0) + 1;
      if (_events.length > _maxEventEntries) _events.removeLast();
    });
  }

  void _showSnackbar(String message, {bool isSuccess = true}) {
    _scaffoldMessengerKey.currentState?.showSnackBar(
      SnackBar(
        content: Row(
          children: [
            Icon(
              isSuccess ? Icons.check_circle_rounded : Icons.error_rounded,
              color: Colors.white,
              size: 20,
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Text(
                message,
                style: const TextStyle(fontWeight: FontWeight.w500),
              ),
            ),
          ],
        ),
        backgroundColor:
            isSuccess ? const Color(0xFF2E7D5F) : const Color(0xFFB33A3A),
        behavior: SnackBarBehavior.floating,
        margin: const EdgeInsets.all(16),
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  String _formatLocation(Location loc) {
    return '${loc.coords.latitude.toStringAsFixed(5)}, ${loc.coords.longitude.toStringAsFixed(5)} (±${loc.coords.accuracy.toStringAsFixed(0)}m)';
  }

  String _formatDuration(Duration duration) {
    if (duration.inHours >= 24) {
      return '${duration.inDays}d ${duration.inHours % 24}h';
    }
    if (duration.inHours >= 1) {
      return '${duration.inHours}h ${duration.inMinutes % 60}m';
    }
    if (duration.inMinutes >= 1) {
      return '${duration.inMinutes}m';
    }
    return '${duration.inSeconds}s';
  }

  String _formatDistance(double meters) {
    if (meters >= 1000) {
      return '${(meters / 1000).toStringAsFixed(2)} km';
    }
    return '${meters.toStringAsFixed(0)} m';
  }

  String _adaptiveTrackingLabel(AdaptiveTrackingConfig? config) {
    if (config == null) return 'Unknown';
    if (!config.enabled) return 'Disabled';
    if (identical(config, AdaptiveTrackingConfig.aggressive)) {
      return 'Aggressive';
    }
    if (identical(config, AdaptiveTrackingConfig.balanced)) {
      return 'Balanced';
    }
    return 'Custom';
  }

  String _syncPolicyLabel(SyncPolicy policy) {
    if (identical(policy, SyncPolicy.aggressive)) return 'Aggressive';
    if (identical(policy, SyncPolicy.conservative)) return 'Conservative';
    if (identical(policy, SyncPolicy.minimal)) return 'Minimal';
    return 'Balanced';
  }

  // ===========================================================================
  // Build
  // ===========================================================================

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Locus Example',
      scaffoldMessengerKey: _scaffoldMessengerKey,
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF2E5D4B),
          brightness: Brightness.light,
        ),
        textTheme: GoogleFonts.interTextTheme(),
        cardTheme: CardThemeData(
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          color: Colors.white,
        ),
        appBarTheme: const AppBarTheme(
          centerTitle: false,
          elevation: 0,
          scrolledUnderElevation: 1,
        ),
      ),
      home: DefaultTabController(
        length: 4,
        child: Scaffold(
          backgroundColor: const Color(0xFFF5F5F5),
          appBar: AppBar(
            title: Row(
              children: [
                SvgPicture.asset(
                  'assets/locus_logo.svg',
                  width: 32,
                  height: 32,
                ),
                const SizedBox(width: 10),
                const Text(
                  'Locus',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
              ],
            ),
            actions: [
              IconButton(
                onPressed: _refreshState,
                icon: const Icon(Icons.refresh_rounded),
                tooltip: 'Refresh',
              ),
            ],
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.dashboard_rounded), text: 'Dashboard'),
                Tab(icon: Icon(Icons.list_alt_rounded), text: 'Events'),
                Tab(icon: Icon(Icons.storage_rounded), text: 'Storage'),
                Tab(icon: Icon(Icons.settings_rounded), text: 'Settings'),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              _buildDashboard(),
              _buildEvents(),
              _buildStorage(),
              _buildSettings(),
            ],
          ),
        ),
      ),
    );
  }

  // ===========================================================================
  // Dashboard Tab
  // ===========================================================================

  Widget _buildDashboard() {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildStatusCard(),
        const SizedBox(height: 16),
        _buildTrackingControls(),
        const SizedBox(height: 16),
        _buildProfileSelector(),
        const SizedBox(height: 16),
        _buildQuickActions(),
        const SizedBox(height: 16),
        _buildGeofencingTools(),
        const SizedBox(height: 16),
        _buildUseCases(),
        const SizedBox(height: 16),
        _buildEventStats(),
      ],
    );
  }

  Widget _buildStatusCard() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                _StatusIndicator(active: _isReady, label: 'Ready'),
                const SizedBox(width: 12),
                _StatusIndicator(active: _isRunning, label: 'Tracking'),
                const SizedBox(width: 12),
                _StatusIndicator(
                  active: _lastState?.isMoving ?? false,
                  label: _lastState?.isMoving == true ? 'Moving' : 'Stationary',
                ),
              ],
            ),
            if (_latestLocation != null) ...[
              const Divider(height: 32),
              _InfoRow(
                icon: Icons.location_on_outlined,
                label: 'Location',
                value: _formatLocation(_latestLocation!),
              ),
            ],
            if (_lastActivity != null)
              _InfoRow(
                icon: Icons.directions_walk_rounded,
                label: 'Activity',
                value:
                    '${_lastActivity!.type.name} (${_lastActivity!.confidence}%)',
              ),
            if (_lastConnectivity != null)
              _InfoRow(
                icon: Icons.wifi_rounded,
                label: 'Network',
                value: _lastConnectivity!.connected ? 'Online' : 'Offline',
              ),
            if (_lastState?.odometer != null)
              _InfoRow(
                icon: Icons.straighten_rounded,
                label: 'Odometer',
                value: '${_lastState!.odometer!.toStringAsFixed(0)} m',
              ),
          ],
        ),
      ),
    );
  }

  Widget _buildTrackingControls() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.play_circle_outline,
              title: 'Tracking',
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _toggleTracking,
                    icon: _isRunning
                        ? Icons.stop_rounded
                        : Icons.play_arrow_rounded,
                    label: _isRunning ? 'Stop' : 'Start',
                    color: _isRunning ? Colors.red : Colors.green,
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _getPosition,
                    icon: Icons.my_location_rounded,
                    label: 'Get Position',
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProfileSelector() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.tune_rounded,
              title: 'Profile',
              trailing: _currentProfile != null
                  ? Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 10,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: Theme.of(context).colorScheme.primaryContainer,
                        borderRadius: BorderRadius.circular(20),
                      ),
                      child: Text(
                        _currentProfile!.name,
                        style: TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.w600,
                          color: Theme.of(context).colorScheme.primary,
                        ),
                      ),
                    )
                  : null,
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                _ProfileChip(
                  label: 'Off Duty',
                  icon: Icons.bedtime_outlined,
                  selected: _currentProfile == TrackingProfile.offDuty,
                  onTap: () => _setProfile(TrackingProfile.offDuty),
                ),
                _ProfileChip(
                  label: 'Standby',
                  icon: Icons.pause_circle_outline,
                  selected: _currentProfile == TrackingProfile.standby,
                  onTap: () => _setProfile(TrackingProfile.standby),
                ),
                _ProfileChip(
                  label: 'En Route',
                  icon: Icons.navigation_outlined,
                  selected: _currentProfile == TrackingProfile.enRoute,
                  onTap: () => _setProfile(TrackingProfile.enRoute),
                ),
                _ProfileChip(
                  label: 'Arrived',
                  icon: Icons.flag_outlined,
                  selected: _currentProfile == TrackingProfile.arrived,
                  onTap: () => _setProfile(TrackingProfile.arrived),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildQuickActions() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.bolt_rounded,
              title: 'Quick Actions',
            ),
            const SizedBox(height: 16),
            GridView.count(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              crossAxisCount: 3,
              mainAxisSpacing: 10,
              crossAxisSpacing: 10,
              childAspectRatio: 1.1,
              children: [
                _QuickActionTile(
                  icon: Icons.add_location_alt_rounded,
                  label: 'Geofence',
                  onTap: _addGeofence,
                ),
                _QuickActionTile(
                  icon: Icons.delete_outline_rounded,
                  label: 'Clear Geo',
                  onTap: _clearGeofences,
                ),
                _QuickActionTile(
                  icon: Icons.privacy_tip_outlined,
                  label: 'Privacy',
                  onTap: _addPrivacyZone,
                ),
                _QuickActionTile(
                  icon: Icons.trip_origin_rounded,
                  label: 'Start Trip',
                  onTap: _startTrip,
                ),
                _QuickActionTile(
                  icon: Icons.stop_circle_outlined,
                  label: 'Stop Trip',
                  onTap: _stopTrip,
                ),
                _QuickActionTile(
                  icon: _isSyncPaused
                      ? Icons.play_arrow_rounded
                      : Icons.pause_rounded,
                  label: _isSyncPaused ? 'Resume Sync' : 'Pause Sync',
                  onTap: _toggleSyncPause,
                ),
                _QuickActionTile(
                  icon: Icons.sync_rounded,
                  label: 'Sync Now',
                  onTap: _syncNow,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildGeofencingTools() {
    final workflowStatus = _lastWorkflowEvent?.status.name ?? 'idle';

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.map_outlined,
              title: 'Geofencing',
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                _StatusIndicator(
                  active: _polygonGeofences.isNotEmpty,
                  label: 'Polygons ${_polygonGeofences.length}',
                ),
                const SizedBox(width: 12),
                _StatusIndicator(
                  active: _workflowRegistered,
                  label:
                      _workflowRegistered ? 'Workflow $workflowStatus' : 'No',
                ),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _addPolygonGeofence,
                    icon: Icons.crop_square_rounded,
                    label: 'Add Polygon',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _clearPolygonGeofences,
                    icon: Icons.layers_clear_rounded,
                    label: 'Clear Polygons',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _registerDeliveryWorkflow,
                    icon: Icons.route,
                    label: 'Register Flow',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _stopWorkflows,
                    icon: Icons.stop_circle_outlined,
                    label: 'Clear Flow',
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildUseCases() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.workspaces_filled,
              title: 'Use Cases',
            ),
            const SizedBox(height: 12),
            Text(
              'Active: $_activeScenario',
              style: const TextStyle(fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _activateDeliveryOps,
                    icon: Icons.local_shipping_outlined,
                    label: 'Delivery Ops',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _activatePrivacyMode,
                    icon: Icons.shield_outlined,
                    label: 'Privacy Mode',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            _InfoRow(
              icon: Icons.badge_outlined,
              label: 'Shift',
              value: _syncContext['shift_id']?.toString() ?? '-',
            ),
            _InfoRow(
              icon: Icons.person_outline,
              label: 'Driver',
              value: _syncContext['driver_id']?.toString() ?? '-',
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildEventStats() {
    final sorted = _eventCounts.entries.toList()
      ..sort((a, b) => b.value.compareTo(a.value));

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.insights_rounded,
              title: 'Event Stats',
            ),
            const SizedBox(height: 16),
            if (sorted.isEmpty)
              const Text('No events yet', style: TextStyle(color: Colors.grey))
            else
              Wrap(
                spacing: 8,
                runSpacing: 8,
                children: sorted.take(8).map((e) {
                  return Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12,
                      vertical: 6,
                    ),
                    decoration: BoxDecoration(
                      color: const Color(0xFFF0F0F0),
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Text(
                      '${e.key}: ${e.value}',
                      style: const TextStyle(
                        fontSize: 13,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  );
                }).toList(),
              ),
          ],
        ),
      ),
    );
  }

  // ===========================================================================
  // Events Tab
  // ===========================================================================

  Widget _buildEvents() {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.white,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                '${_events.length} events',
                style: const TextStyle(
                  fontWeight: FontWeight.w600,
                  fontSize: 16,
                ),
              ),
              TextButton.icon(
                onPressed: () {
                  setState(() {
                    _events.clear();
                    _eventCounts.clear();
                  });
                  _showSnackbar('Events cleared');
                },
                icon: const Icon(Icons.delete_outline, size: 20),
                label: const Text('Clear'),
              ),
            ],
          ),
        ),
        Expanded(
          child: _events.isEmpty
              ? const Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.inbox_rounded, size: 48, color: Colors.grey),
                      SizedBox(height: 12),
                      Text(
                        'No events yet',
                        style: TextStyle(color: Colors.grey),
                      ),
                    ],
                  ),
                )
              : ListView.separated(
                  padding: const EdgeInsets.symmetric(vertical: 8),
                  itemCount: _events.length,
                  separatorBuilder: (_, __) =>
                      const Divider(height: 1, indent: 56),
                  itemBuilder: (_, i) => ListTile(
                    leading: const CircleAvatar(
                      radius: 16,
                      backgroundColor: Color(0xFFF0F0F0),
                      child: Icon(Icons.circle, size: 8, color: Colors.grey),
                    ),
                    title: Text(
                      _events[i],
                      style: const TextStyle(
                        fontSize: 13,
                        fontFamily: 'monospace',
                      ),
                    ),
                  ),
                ),
        ),
      ],
    );
  }

  // ===========================================================================
  // Storage Tab
  // ===========================================================================

  Widget _buildStorage() {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Card(
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _SectionHeader(
                  icon: Icons.storage_rounded,
                  title: 'Stored Locations',
                  trailing: Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 10,
                      vertical: 4,
                    ),
                    decoration: BoxDecoration(
                      color: const Color(0xFFF0F0F0),
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Text(
                      '${_storedLocations.length}',
                      style: const TextStyle(
                        fontSize: 12,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 16),
                Row(
                  children: [
                    Expanded(
                      child: _ActionButton(
                        onPressed: _loadLocations,
                        icon: Icons.download_rounded,
                        label: 'Load',
                        filled: true,
                      ),
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: _ActionButton(
                        onPressed: _clearLocations,
                        icon: Icons.delete_outline_rounded,
                        label: 'Clear',
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
        if (_storedLocations.isNotEmpty) ...[
          const SizedBox(height: 16),
          Card(
            child: ListView.separated(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: _storedLocations.length.clamp(0, 20),
              separatorBuilder: (_, __) => const Divider(height: 1),
              itemBuilder: (_, i) {
                final loc = _storedLocations[i];
                return ListTile(
                  leading: CircleAvatar(
                    radius: 16,
                    backgroundColor: Theme.of(
                      context,
                    ).colorScheme.primaryContainer,
                    child: Text(
                      '${i + 1}',
                      style: TextStyle(
                        fontSize: 11,
                        fontWeight: FontWeight.bold,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                    ),
                  ),
                  title: Text(
                    _formatLocation(loc),
                    style: const TextStyle(
                      fontSize: 13,
                      fontFamily: 'monospace',
                    ),
                  ),
                  subtitle: Text(
                    loc.timestamp.toLocal().toString().substring(0, 19),
                    style: const TextStyle(fontSize: 11),
                  ),
                );
              },
            ),
          ),
        ],
        const SizedBox(height: 16),
        _buildSyncQueueCard(),
        const SizedBox(height: 16),
        _buildHistorySummaryCard(),
        const SizedBox(height: 16),
        _buildPrivacyZonesCard(),
        const SizedBox(height: 16),
        Card(
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _SectionHeader(
                  icon: Icons.article_outlined,
                  title: 'Logs',
                  trailing: _lastLog != null
                      ? Text(
                          '${_lastLog!.length} entries',
                          style: const TextStyle(
                            fontSize: 12,
                            color: Colors.grey,
                          ),
                        )
                      : null,
                ),
                const SizedBox(height: 16),
                _ActionButton(
                  onPressed: _loadLogs,
                  icon: Icons.refresh_rounded,
                  label: 'Load Logs',
                  filled: true,
                ),
                if (_lastLog != null && _lastLog!.isNotEmpty) ...[
                  const SizedBox(height: 16),
                  Container(
                    padding: const EdgeInsets.all(12),
                    decoration: BoxDecoration(
                      color: const Color(0xFFF8F8F8),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(
                      _lastLog!.take(10).map((e) {
                        final ts =
                            '${e.timestamp.hour.toString().padLeft(2, '0')}:${e.timestamp.minute.toString().padLeft(2, '0')}';
                        return '[$ts] ${e.level}: ${e.message}';
                      }).join('\n'),
                      style: const TextStyle(
                        fontSize: 11,
                        fontFamily: 'monospace',
                      ),
                    ),
                  ),
                ],
              ],
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildSyncQueueCard() {
    final itemCount = _syncQueue.length > 5 ? 5 : _syncQueue.length;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.sync_rounded,
              title: 'Sync Queue',
              trailing: Text(
                '${_syncQueue.length} items',
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _enqueueCheckIn,
                    icon: Icons.add_rounded,
                    label: 'Enqueue',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _loadSyncQueue,
                    icon: Icons.refresh_rounded,
                    label: 'Load',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _syncQueueNow,
                    icon: Icons.cloud_upload_outlined,
                    label: 'Sync Queue',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _clearSyncQueue,
                    icon: Icons.delete_outline_rounded,
                    label: 'Clear',
                  ),
                ),
              ],
            ),
            if (_lastQueueId != null) ...[
              const SizedBox(height: 12),
              Text(
                'Last queued: $_lastQueueId',
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ],
            if (_syncQueue.isNotEmpty) ...[
              const SizedBox(height: 16),
              ListView.separated(
                shrinkWrap: true,
                physics: const NeverScrollableScrollPhysics(),
                itemCount: itemCount,
                separatorBuilder: (_, __) => const Divider(height: 1),
                itemBuilder: (_, i) {
                  final item = _syncQueue[i];
                  final shortIdLength = item.id.length > 6 ? 6 : item.id.length;
                  return ListTile(
                    dense: true,
                    title: Text(
                      item.type ?? 'payload',
                      style: const TextStyle(fontSize: 13),
                    ),
                    subtitle: Text(
                      '${item.createdAt.toLocal().toString().substring(0, 19)} · retries ${item.retryCount}',
                      style: const TextStyle(fontSize: 11),
                    ),
                    trailing: Text(
                      item.id.substring(0, shortIdLength),
                      style: const TextStyle(
                        fontSize: 11,
                        color: Colors.grey,
                      ),
                    ),
                  );
                },
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildHistorySummaryCard() {
    final summary = _locationSummary;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.insights_rounded,
              title: 'History Summary',
              trailing: summary != null
                  ? Text(
                      '${summary.locationCount} pts',
                      style: const TextStyle(fontSize: 12, color: Colors.grey),
                    )
                  : null,
            ),
            const SizedBox(height: 16),
            _ActionButton(
              onPressed: _loadLocationSummary,
              icon: Icons.insights_outlined,
              label: 'Load last 12h',
              filled: true,
            ),
            if (summary == null) ...[
              const SizedBox(height: 12),
              const Text(
                'No summary loaded',
                style: TextStyle(color: Colors.grey),
              ),
            ] else ...[
              const SizedBox(height: 16),
              _InfoRow(
                icon: Icons.straighten_rounded,
                label: 'Distance',
                value: _formatDistance(summary.totalDistanceMeters),
              ),
              _InfoRow(
                icon: Icons.directions_walk_rounded,
                label: 'Moving',
                value:
                    '${_formatDuration(summary.movingDuration)} (${summary.movingPercent.toStringAsFixed(0)}%)',
              ),
              _InfoRow(
                icon: Icons.pause_circle_outline,
                label: 'Stationary',
                value: _formatDuration(summary.stationaryDuration),
              ),
              if (summary.averageAccuracyMeters != null)
                _InfoRow(
                  icon: Icons.gps_fixed,
                  label: 'Avg Accuracy',
                  value:
                      '${summary.averageAccuracyMeters!.toStringAsFixed(0)} m',
                ),
              _InfoRow(
                icon: Icons.place_outlined,
                label: 'Frequent Spots',
                value: '${summary.frequentLocations.length}',
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildPrivacyZonesCard() {
    final zones = _privacyZones;
    final itemCount = zones.length > 4 ? 4 : zones.length;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.privacy_tip_outlined,
              title: 'Privacy Zones',
              trailing: Text(
                '${zones.length}',
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: _addPrivacyZone,
                    icon: Icons.add_rounded,
                    label: 'Add Demo',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _loadPrivacyZones,
                    icon: Icons.refresh_rounded,
                    label: 'Load',
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _clearPrivacyZones,
                    icon: Icons.delete_outline_rounded,
                    label: 'Clear',
                  ),
                ),
              ],
            ),
            if (zones.isEmpty) ...[
              const SizedBox(height: 12),
              const Text(
                'No privacy zones saved',
                style: TextStyle(color: Colors.grey),
              ),
            ] else ...[
              const SizedBox(height: 12),
              ListView.separated(
                shrinkWrap: true,
                physics: const NeverScrollableScrollPhysics(),
                itemCount: itemCount,
                separatorBuilder: (_, __) => const Divider(height: 1),
                itemBuilder: (_, i) {
                  final zone = zones[i];
                  return ListTile(
                    dense: true,
                    title: Text(
                      zone.identifier,
                      style: const TextStyle(fontSize: 13),
                    ),
                    subtitle: Text(
                      '${zone.action.name} · ${zone.enabled ? "enabled" : "disabled"}',
                      style: const TextStyle(fontSize: 11),
                    ),
                  );
                },
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildAdaptiveTrackingCard() {
    final label = _adaptiveTrackingLabel(_adaptiveTrackingConfig);

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.battery_charging_full_rounded,
              title: 'Adaptive Tracking',
              trailing: Text(
                label,
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _setAdaptiveTracking(
                        AdaptiveTrackingConfig.balanced, 'Balanced'),
                    icon: Icons.tune,
                    label: 'Balanced',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _setAdaptiveTracking(
                        AdaptiveTrackingConfig.aggressive, 'Aggressive'),
                    icon: Icons.flash_on,
                    label: 'Aggressive',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _setAdaptiveTracking(
                        AdaptiveTrackingConfig.disabled, 'Disabled'),
                    icon: Icons.power_settings_new,
                    label: 'Disable',
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _calculateAdaptiveSettings,
                    icon: Icons.tune,
                    label: 'Calc Settings',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            _ActionButton(
              onPressed: _estimateRunway,
              icon: Icons.timelapse,
              label: 'Estimate Runway',
              filled: true,
            ),
            if (_adaptiveSettings != null) ...[
              const SizedBox(height: 16),
              _InfoRow(
                icon: Icons.my_location_outlined,
                label: 'Accuracy',
                value: _adaptiveSettings!.desiredAccuracy.name,
              ),
              _InfoRow(
                icon: Icons.linear_scale,
                label: 'Distance Filter',
                value:
                    '${_adaptiveSettings!.distanceFilter.toStringAsFixed(0)} m',
              ),
              _InfoRow(
                icon: Icons.favorite_border,
                label: 'Heartbeat',
                value: '${_adaptiveSettings!.heartbeatInterval}s',
              ),
            ],
            if (_batteryRunway != null) ...[
              const SizedBox(height: 12),
              _InfoRow(
                icon: Icons.battery_full_rounded,
                label: 'Runway',
                value: _batteryRunway!.formattedDuration,
              ),
              _InfoRow(
                icon: Icons.battery_2_bar_rounded,
                label: 'Low Power',
                value: _batteryRunway!.formattedLowPowerDuration,
              ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildSyncPolicyCard() {
    final label = _syncPolicyLabel(_syncPolicy);

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.sync_rounded,
              title: 'Sync Policy',
              trailing: Text(
                label,
                style: const TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () =>
                        _applySyncPolicy(SyncPolicy.balanced, 'Balanced'),
                    icon: Icons.tune,
                    label: 'Balanced',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: () =>
                        _applySyncPolicy(SyncPolicy.aggressive, 'Aggressive'),
                    icon: Icons.flash_on,
                    label: 'Aggressive',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _applySyncPolicy(
                        SyncPolicy.conservative, 'Conservative'),
                    icon: Icons.slow_motion_video,
                    label: 'Conservative',
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: () =>
                        _applySyncPolicy(SyncPolicy.minimal, 'Minimal'),
                    icon: Icons.savings_outlined,
                    label: 'Minimal',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            _ActionButton(
              onPressed: _evaluateSyncPolicy,
              icon: Icons.insights_rounded,
              label: 'Evaluate Policy',
              filled: true,
            ),
            if (_syncDecision != null) ...[
              const SizedBox(height: 12),
              Text(
                _syncDecision!.reason,
                style: const TextStyle(color: Colors.grey),
              ),
              if (_syncDecision!.batchLimit != null)
                _InfoRow(
                  icon: Icons.layers_outlined,
                  label: 'Batch Size',
                  value: '${_syncDecision!.batchLimit}',
                ),
              if (_syncDecision!.delay != null)
                _InfoRow(
                  icon: Icons.timer_outlined,
                  label: 'Delay',
                  value: _formatDuration(_syncDecision!.delay!),
                ),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildSyncContextCard() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const _SectionHeader(
              icon: Icons.badge_outlined,
              title: 'Sync Context',
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _applySyncContext(const {
                      'shift_id': 'shift-001',
                      'driver_id': 'driver-42',
                      'route_id': 'route-7',
                    }),
                    icon: Icons.route,
                    label: 'Shift A',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _applySyncContext(const {
                      'shift_id': 'shift-002',
                      'driver_id': 'driver-55',
                      'route_id': 'route-12',
                      'priority': 'rush',
                    }),
                    icon: Icons.route,
                    label: 'Shift B',
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            _InfoRow(
              icon: Icons.work_outline,
              label: 'Shift',
              value: _syncContext['shift_id']?.toString() ?? '-',
            ),
            _InfoRow(
              icon: Icons.person_outline,
              label: 'Driver',
              value: _syncContext['driver_id']?.toString() ?? '-',
            ),
            _InfoRow(
              icon: Icons.route,
              label: 'Route',
              value: _syncContext['route_id']?.toString() ?? '-',
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMonitoringCard() {
    return Card(
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
            child: Row(
              children: [
                Icon(
                  Icons.tune,
                  size: 20,
                  color: Theme.of(context).colorScheme.primary,
                ),
                const SizedBox(width: 10),
                const Text(
                  'Monitoring & Automation',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
                ),
              ],
            ),
          ),
          SwitchListTile(
            secondary: const Icon(Icons.smart_toy_outlined),
            title: const Text('Tracking Automation'),
            subtitle: const Text('Auto-switch profiles based on rules'),
            value: _automationEnabled,
            onChanged: (_) => _toggleAutomation(),
          ),
          const Divider(height: 1),
          SwitchListTile(
            secondary: const Icon(Icons.high_quality),
            title: const Text('Quality Monitoring'),
            subtitle: const Text('Assess signal quality'),
            value: _qualityMonitoringEnabled,
            onChanged: (_) => _toggleQualityMonitoring(),
          ),
          const Divider(height: 1),
          SwitchListTile(
            secondary: const Icon(Icons.report_problem_outlined),
            title: const Text('Anomaly Detection'),
            subtitle: const Text('Detect implausible jumps'),
            value: _anomalyMonitoringEnabled,
            onChanged: (_) => _toggleAnomalyMonitoring(),
          ),
          const Divider(height: 1),
          SwitchListTile(
            secondary: const Icon(Icons.security_rounded),
            title: const Text('Spoof Detection'),
            subtitle: const Text('Detect mock locations'),
            value: _spoofDetectionEnabled,
            onChanged: (_) => _toggleSpoof(),
          ),
          const Divider(height: 1),
          SwitchListTile(
            secondary: const Icon(Icons.compare_arrows_rounded),
            title: const Text('Significant Changes'),
            subtitle: const Text('Ultra-low power monitoring'),
            value: _significantChangesEnabled,
            onChanged: (_) => _toggleSignificant(),
          ),
          if (_lastQuality != null) ...[
            const Divider(height: 1),
            Padding(
              padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
              child: Column(
                children: [
                  _InfoRow(
                    icon: Icons.insights_rounded,
                    label: 'Quality Score',
                    value:
                        '${(_lastQuality!.overallScore * 100).toStringAsFixed(0)}%',
                  ),
                  _InfoRow(
                    icon: Icons.speed_rounded,
                    label: 'Jitter',
                    value:
                        '${(_lastQuality!.jitterScore * 100).toStringAsFixed(0)}%',
                  ),
                  _InfoRow(
                    icon: Icons.shield_outlined,
                    label: 'Spoof Suspect',
                    value: _lastQuality!.isSpoofSuspected ? 'Yes' : 'No',
                  ),
                ],
              ),
            ),
          ],
          if (_lastAnomaly != null) ...[
            const Divider(height: 1),
            Padding(
              padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
              child: _InfoRow(
                icon: Icons.warning_rounded,
                label: 'Last Anomaly',
                value: '${_lastAnomaly!.speedKph.toStringAsFixed(0)} kph',
              ),
            ),
          ],
          const Divider(height: 1),
          Padding(
            padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
            child: Row(
              children: [
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _setPace(true),
                    icon: Icons.directions_walk_rounded,
                    label: 'Set Moving',
                    filled: true,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: () => _setPace(false),
                    icon: Icons.do_not_disturb_on_outlined,
                    label: 'Set Stationary',
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _ActionButton(
                    onPressed: _resetOdometer,
                    icon: Icons.restore_rounded,
                    label: 'Reset Odo',
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDiagnosticsCard() {
    final snapshot = _diagnostics;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _SectionHeader(
              icon: Icons.health_and_safety_outlined,
              title: 'Diagnostics',
              trailing: snapshot != null
                  ? Text(
                      snapshot.capturedAt.toLocal().toString().substring(0, 16),
                      style: const TextStyle(fontSize: 12, color: Colors.grey),
                    )
                  : null,
            ),
            const SizedBox(height: 16),
            _ActionButton(
              onPressed: _loadDiagnostics,
              icon: Icons.health_and_safety_outlined,
              label: 'Capture Snapshot',
              filled: true,
            ),
            if (snapshot != null) ...[
              const SizedBox(height: 16),
              _InfoRow(
                icon: Icons.queue,
                label: 'Queue Size',
                value: '${snapshot.queue.length}',
              ),
              if (snapshot.state != null)
                _InfoRow(
                  icon: Icons.play_circle_outline,
                  label: 'Tracking',
                  value: snapshot.state!.enabled ? 'On' : 'Off',
                ),
              if (snapshot.state?.isMoving != null)
                _InfoRow(
                  icon: Icons.directions_walk_rounded,
                  label: 'Moving',
                  value: snapshot.state!.isMoving ? 'Yes' : 'No',
                ),
            ],
          ],
        ),
      ),
    );
  }

  // ===========================================================================
  // Settings Tab
  // ===========================================================================

  Widget _buildSettings() {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Card(
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _SectionHeader(
                  icon: Icons.battery_charging_full_rounded,
                  title: 'Battery',
                  trailing: _powerState != null
                      ? Text(
                          '${_powerState!.batteryLevel}%',
                          style: const TextStyle(fontWeight: FontWeight.w600),
                        )
                      : null,
                ),
                const SizedBox(height: 16),
                if (_batteryStats != null) ...[
                  _InfoRow(
                    icon: Icons.gps_fixed,
                    label: 'GPS On Time',
                    value:
                        '${(_batteryStats!.gpsOnTimePercent * 100).toStringAsFixed(1)}%',
                  ),
                  const SizedBox(height: 8),
                ],
                if (_powerState != null)
                  _InfoRow(
                    icon: Icons.power,
                    label: 'Charging',
                    value: _powerState!.isCharging ? 'Yes' : 'No',
                  ),
                if (_lastPowerEvent != null)
                  _InfoRow(
                    icon: Icons.power_settings_new,
                    label: 'Last Power Change',
                    value: _lastPowerEvent!.changeType.name,
                  ),
                Row(
                  children: [
                    Expanded(
                      child: _ActionButton(
                        onPressed: _refreshBattery,
                        icon: Icons.refresh_rounded,
                        label: 'Refresh',
                        filled: true,
                      ),
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: _ActionButton(
                        onPressed: _toggleBenchmark,
                        icon: _benchmarkStatus != null
                            ? Icons.stop_rounded
                            : Icons.speed_rounded,
                        label: _benchmarkStatus ?? 'Benchmark',
                        color: _benchmarkStatus != null ? Colors.red : null,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 16),
        _buildAdaptiveTrackingCard(),
        const SizedBox(height: 16),
        _buildSyncPolicyCard(),
        const SizedBox(height: 16),
        _buildSyncContextCard(),
        const SizedBox(height: 16),
        _buildMonitoringCard(),
        const SizedBox(height: 16),
        _buildDiagnosticsCard(),
        const SizedBox(height: 16),
        const Card(
          child: Padding(
            padding: EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _SectionHeader(
                  icon: Icons.info_outline_rounded,
                  title: 'About',
                ),
                SizedBox(height: 16),
                Text(
                  'Locus Example App showcases tracking profiles, sync queues, adaptive tracking, polygon geofences, workflows, diagnostics, and quality monitoring.',
                  style: TextStyle(color: Colors.grey, height: 1.5),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

// =============================================================================
// Reusable Components
// =============================================================================

class _SectionHeader extends StatelessWidget {
  const _SectionHeader({
    required this.icon,
    required this.title,
    this.trailing,
  });

  final IconData icon;
  final String title;
  final Widget? trailing;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
        const SizedBox(width: 10),
        Text(
          title,
          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
        ),
        const Spacer(),
        if (trailing != null) trailing!,
      ],
    );
  }
}

class _StatusIndicator extends StatelessWidget {
  const _StatusIndicator({required this.active, required this.label});

  final bool active;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      decoration: BoxDecoration(
        color: active ? const Color(0xFFE8F5E9) : const Color(0xFFF5F5F5),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(
          color: active ? const Color(0xFF4CAF50) : const Color(0xFFE0E0E0),
        ),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 8,
            height: 8,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: active ? const Color(0xFF4CAF50) : const Color(0xFFBDBDBD),
            ),
          ),
          const SizedBox(width: 6),
          Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w500,
              color: active ? const Color(0xFF2E7D32) : const Color(0xFF757575),
            ),
          ),
        ],
      ),
    );
  }
}

class _InfoRow extends StatelessWidget {
  const _InfoRow({
    required this.icon,
    required this.label,
    required this.value,
  });

  final IconData icon;
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          Icon(icon, size: 16, color: Colors.grey),
          const SizedBox(width: 8),
          Text(label, style: const TextStyle(color: Colors.grey, fontSize: 13)),
          const Spacer(),
          Text(
            value,
            style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13),
          ),
        ],
      ),
    );
  }
}

class _ActionButton extends StatelessWidget {
  const _ActionButton({
    required this.onPressed,
    required this.icon,
    required this.label,
    this.color,
    this.filled = false,
  });

  final VoidCallback onPressed;
  final IconData icon;
  final String label;
  final Color? color;
  final bool filled;

  @override
  Widget build(BuildContext context) {
    final effectiveColor = color ?? Theme.of(context).colorScheme.primary;

    if (filled) {
      return FilledButton.icon(
        onPressed: onPressed,
        icon: Icon(icon, size: 18),
        label: Text(label),
        style: FilledButton.styleFrom(
          backgroundColor: effectiveColor,
          padding: const EdgeInsets.symmetric(vertical: 12),
        ),
      );
    }

    return OutlinedButton.icon(
      onPressed: onPressed,
      icon: Icon(icon, size: 18, color: effectiveColor),
      label: Text(label, style: TextStyle(color: effectiveColor)),
      style: OutlinedButton.styleFrom(
        side: BorderSide(color: effectiveColor.withAlpha(100)),
        padding: const EdgeInsets.symmetric(vertical: 12),
      ),
    );
  }
}

class _ProfileChip extends StatelessWidget {
  const _ProfileChip({
    required this.label,
    required this.icon,
    required this.selected,
    required this.onTap,
  });

  final String label;
  final IconData icon;
  final bool selected;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: GestureDetector(
        onTap: onTap,
        child: Container(
          padding: const EdgeInsets.symmetric(vertical: 12),
          margin: const EdgeInsets.symmetric(horizontal: 4),
          decoration: BoxDecoration(
            color: selected
                ? Theme.of(context).colorScheme.primaryContainer
                : const Color(0xFFF5F5F5),
            borderRadius: BorderRadius.circular(12),
            border: selected
                ? Border.all(
                    color: Theme.of(context).colorScheme.primary.withAlpha(100),
                  )
                : null,
          ),
          child: Column(
            children: [
              Icon(
                icon,
                size: 20,
                color: selected
                    ? Theme.of(context).colorScheme.primary
                    : Colors.grey,
              ),
              const SizedBox(height: 4),
              Text(
                label,
                style: TextStyle(
                  fontSize: 10,
                  fontWeight: selected ? FontWeight.w600 : FontWeight.normal,
                  color: selected
                      ? Theme.of(context).colorScheme.primary
                      : Colors.grey,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _QuickActionTile extends StatelessWidget {
  const _QuickActionTile({
    required this.icon,
    required this.label,
    required this.onTap,
  });

  final IconData icon;
  final String label;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: const Color(0xFFF8F8F8),
      borderRadius: BorderRadius.circular(12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(8),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                icon,
                size: 22,
                color: Theme.of(context).colorScheme.primary,
              ),
              const SizedBox(height: 6),
              FittedBox(
                fit: BoxFit.scaleDown,
                child: Text(
                  label,
                  textAlign: TextAlign.center,
                  maxLines: 1,
                  style: const TextStyle(
                    fontSize: 11,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
20
likes
150
points
507
downloads

Documentation

API reference

Publisher

verified publisherweorbis.com

Weekly Downloads

Core background geolocation SDK of the WeOrbis ecosystem for Flutter. Native tracking, geofencing, activity recognition, and sync.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

args, device_info_plus, flutter, http, logging, permission_handler, uuid

More

Packages that depend on locus

Packages that implement locus