From cd34c8bbf306b9f0db5d6951aff20e5f8b8e9831 Mon Sep 17 00:00:00 2001 From: fiatcode Date: Thu, 30 Apr 2026 13:23:07 +0700 Subject: [PATCH] feat: add send location button to force immediate report --- .../traccar/traccar_client/BridgeModule.kt | 11 +++ .../service/LocationTrackingService.kt | 19 ++++- lib/bridge/location_bridge.dart | 10 +++ lib/main_screen.dart | 77 +++++++++++++------ lib/settings_screen.dart | 6 -- 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/android/app/src/main/kotlin/com/traccar/traccar_client/BridgeModule.kt b/android/app/src/main/kotlin/com/traccar/traccar_client/BridgeModule.kt index d15d1da..1a67d54 100644 --- a/android/app/src/main/kotlin/com/traccar/traccar_client/BridgeModule.kt +++ b/android/app/src/main/kotlin/com/traccar/traccar_client/BridgeModule.kt @@ -62,6 +62,10 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { "getStatus" -> { result.success(getTrackingStatus()) } + "reportLocation" -> { + reportLocationNow() + result.success(true) + } else -> result.notImplemented() } } @@ -107,4 +111,11 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { "lastTimestamp" to prefs.getLong("last_timestamp", 0L) ) } + + private fun reportLocationNow() { + val intent = android.content.Intent(context, LocationTrackingService::class.java).apply { + action = LocationTrackingService.ACTION_REPORT + } + context?.startService(intent) + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/traccar/traccar_client/service/LocationTrackingService.kt b/android/app/src/main/kotlin/com/traccar/traccar_client/service/LocationTrackingService.kt index 482170d..e9cb0b1 100644 --- a/android/app/src/main/kotlin/com/traccar/traccar_client/service/LocationTrackingService.kt +++ b/android/app/src/main/kotlin/com/traccar/traccar_client/service/LocationTrackingService.kt @@ -49,6 +49,7 @@ class LocationTrackingService : Service() { private var wakeLock: PowerManager.WakeLock? = null private var isTracking = false private var isNetworkAvailable = true + private var bypassFilterOnce = false private var currentConfig: TrackingConfig? = null @@ -77,6 +78,7 @@ class LocationTrackingService : Service() { when (intent?.action) { ACTION_START -> startTracking() ACTION_STOP -> stopTracking() + ACTION_REPORT -> reportLocationNow() } return START_STICKY } @@ -144,6 +146,19 @@ class LocationTrackingService : Service() { logEvent("INFO", "Tracking stopped") } + fun reportLocationNow() { + if (!isTracking) { + logEvent("ERROR", "Cannot report: tracking not active") + return + } + bypassFilterOnce = true + fusedLocationProvider.getLastLocation { location -> + location?.let { + onLocationReceived(it) + } ?: logEvent("ERROR", "No location available for report") + } + } + fun updateConfig(config: TrackingConfig) { currentConfig = config if (isTracking) { @@ -161,10 +176,11 @@ class LocationTrackingService : Service() { angleFilter = config.angleFilter ) - if (!distanceFilterProcessor.shouldAccept(location, filterConfig)) { + if (!bypassFilterOnce && !distanceFilterProcessor.shouldAccept(location, filterConfig)) { logEvent("FILTERED", "Location filtered by distance/interval/angle") return } + bypassFilterOnce = false val traccarLocation = TraccarLocation( timestamp = location.time, @@ -367,6 +383,7 @@ class LocationTrackingService : Service() { companion object { const val ACTION_START = "com.traccar.traccar_client.ACTION_START" const val ACTION_STOP = "com.traccar.traccar_client.ACTION_STOP" + const val ACTION_REPORT = "com.traccar.traccar_client.ACTION_REPORT" private const val CHANNEL_ID = "traccar_tracking_channel" private const val NOTIFICATION_ID = 1 } diff --git a/lib/bridge/location_bridge.dart b/lib/bridge/location_bridge.dart index bd34fd5..d8376e7 100644 --- a/lib/bridge/location_bridge.dart +++ b/lib/bridge/location_bridge.dart @@ -55,6 +55,16 @@ class LocationBridge { } } + static Future reportLocation() async { + try { + final result = await _methodChannel.invokeMethod('reportLocation'); + return result ?? false; + } on PlatformException catch (e) { + debugPrint('Failed to report location: ${e.message}'); + return false; + } + } + static Stream> get locationUpdates { _locationStream ??= _eventChannel.receiveBroadcastStream().map( (event) => Map.from(event as Map), diff --git a/lib/main_screen.dart b/lib/main_screen.dart index 98228fb..74af1d1 100644 --- a/lib/main_screen.dart +++ b/lib/main_screen.dart @@ -154,32 +154,61 @@ class _MainScreenState extends State { } Widget _buildActionButtons() { - return Row( + return Column( children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsScreen()), - ); - }, - icon: const Icon(Icons.settings), - label: const Text('Settings'), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const StatusScreen()), - ); - }, - icon: const Icon(Icons.history), - label: const Text('Status/Logs'), + if (_isTracking) + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () async { + final success = await LocationBridge.reportLocation(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + success + ? 'Location reported' + : 'Failed to report location', + ), + ), + ); + } + }, + icon: const Icon(Icons.send), + label: const Text('Send Location'), + ), + ), ), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsScreen()), + ); + }, + icon: const Icon(Icons.settings), + label: const Text('Settings'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const StatusScreen()), + ); + }, + icon: const Icon(Icons.history), + label: const Text('Status/Logs'), + ), + ), + ], ), ], ); diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index bddc061..fbfee47 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -12,7 +12,6 @@ class SettingsScreen extends StatefulWidget { class _SettingsScreenState extends State { late TextEditingController _serverUrlController; late TextEditingController _deviceIdController; - late TextEditingController _passwordController; late int _accuracy; late int _distanceFilter; late int _interval; @@ -25,7 +24,6 @@ class _SettingsScreenState extends State { super.initState(); _serverUrlController = TextEditingController(text: Preferences.serverUrl); _deviceIdController = TextEditingController(text: Preferences.deviceId); - _passwordController = TextEditingController(text: Preferences.password); _accuracy = Preferences.accuracy; _distanceFilter = Preferences.distanceFilter; _interval = Preferences.interval; @@ -38,14 +36,12 @@ class _SettingsScreenState extends State { void dispose() { _serverUrlController.dispose(); _deviceIdController.dispose(); - _passwordController.dispose(); super.dispose(); } Future _saveSettings() async { await Preferences.setServerUrl(_serverUrlController.text); await Preferences.setDeviceId(_deviceIdController.text); - await Preferences.setPassword(_passwordController.text); await Preferences.setAccuracy(_accuracy); await Preferences.setDistanceFilter(_distanceFilter); await Preferences.setInterval(_interval); @@ -56,7 +52,6 @@ class _SettingsScreenState extends State { await LocationBridge.updateConfig({ 'serverUrl': _serverUrlController.text, 'deviceId': _deviceIdController.text, - 'password': _passwordController.text, 'accuracy': _accuracy, 'distanceFilter': _distanceFilter, 'interval': _interval, @@ -82,7 +77,6 @@ class _SettingsScreenState extends State { _buildSectionHeader('Server'), _buildTextField('Server URL', _serverUrlController), _buildTextField('Device ID', _deviceIdController), - _buildTextField('Password', _passwordController, obscure: true), const SizedBox(height: 16), _buildSectionHeader('Location'), _buildAccuracyDropdown(),