feat: add send location button to force immediate report
This commit is contained in:
parent
4eade880b1
commit
cd34c8bbf3
5 changed files with 92 additions and 31 deletions
|
|
@ -62,6 +62,10 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
"getStatus" -> {
|
"getStatus" -> {
|
||||||
result.success(getTrackingStatus())
|
result.success(getTrackingStatus())
|
||||||
}
|
}
|
||||||
|
"reportLocation" -> {
|
||||||
|
reportLocationNow()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,4 +111,11 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||||
"lastTimestamp" to prefs.getLong("last_timestamp", 0L)
|
"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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ class LocationTrackingService : Service() {
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var isTracking = false
|
private var isTracking = false
|
||||||
private var isNetworkAvailable = true
|
private var isNetworkAvailable = true
|
||||||
|
private var bypassFilterOnce = false
|
||||||
|
|
||||||
private var currentConfig: TrackingConfig? = null
|
private var currentConfig: TrackingConfig? = null
|
||||||
|
|
||||||
|
|
@ -77,6 +78,7 @@ class LocationTrackingService : Service() {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
ACTION_START -> startTracking()
|
ACTION_START -> startTracking()
|
||||||
ACTION_STOP -> stopTracking()
|
ACTION_STOP -> stopTracking()
|
||||||
|
ACTION_REPORT -> reportLocationNow()
|
||||||
}
|
}
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +146,19 @@ class LocationTrackingService : Service() {
|
||||||
logEvent("INFO", "Tracking stopped")
|
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) {
|
fun updateConfig(config: TrackingConfig) {
|
||||||
currentConfig = config
|
currentConfig = config
|
||||||
if (isTracking) {
|
if (isTracking) {
|
||||||
|
|
@ -161,10 +176,11 @@ class LocationTrackingService : Service() {
|
||||||
angleFilter = config.angleFilter
|
angleFilter = config.angleFilter
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!distanceFilterProcessor.shouldAccept(location, filterConfig)) {
|
if (!bypassFilterOnce && !distanceFilterProcessor.shouldAccept(location, filterConfig)) {
|
||||||
logEvent("FILTERED", "Location filtered by distance/interval/angle")
|
logEvent("FILTERED", "Location filtered by distance/interval/angle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
bypassFilterOnce = false
|
||||||
|
|
||||||
val traccarLocation = TraccarLocation(
|
val traccarLocation = TraccarLocation(
|
||||||
timestamp = location.time,
|
timestamp = location.time,
|
||||||
|
|
@ -367,6 +383,7 @@ class LocationTrackingService : Service() {
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_START = "com.traccar.traccar_client.ACTION_START"
|
const val ACTION_START = "com.traccar.traccar_client.ACTION_START"
|
||||||
const val ACTION_STOP = "com.traccar.traccar_client.ACTION_STOP"
|
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 CHANNEL_ID = "traccar_tracking_channel"
|
||||||
private const val NOTIFICATION_ID = 1
|
private const val NOTIFICATION_ID = 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,16 @@ class LocationBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> reportLocation() async {
|
||||||
|
try {
|
||||||
|
final result = await _methodChannel.invokeMethod<bool>('reportLocation');
|
||||||
|
return result ?? false;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('Failed to report location: ${e.message}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Stream<Map<String, dynamic>> get locationUpdates {
|
static Stream<Map<String, dynamic>> get locationUpdates {
|
||||||
_locationStream ??= _eventChannel.receiveBroadcastStream().map(
|
_locationStream ??= _eventChannel.receiveBroadcastStream().map(
|
||||||
(event) => Map<String, dynamic>.from(event as Map),
|
(event) => Map<String, dynamic>.from(event as Map),
|
||||||
|
|
|
||||||
|
|
@ -154,32 +154,61 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActionButtons() {
|
Widget _buildActionButtons() {
|
||||||
return Row(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
if (_isTracking)
|
||||||
child: ElevatedButton.icon(
|
Padding(
|
||||||
onPressed: () {
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
Navigator.push(
|
child: SizedBox(
|
||||||
context,
|
width: double.infinity,
|
||||||
MaterialPageRoute(builder: (_) => const SettingsScreen()),
|
child: ElevatedButton.icon(
|
||||||
);
|
onPressed: () async {
|
||||||
},
|
final success = await LocationBridge.reportLocation();
|
||||||
icon: const Icon(Icons.settings),
|
if (mounted) {
|
||||||
label: const Text('Settings'),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
),
|
SnackBar(
|
||||||
),
|
content: Text(
|
||||||
const SizedBox(width: 16),
|
success
|
||||||
Expanded(
|
? 'Location reported'
|
||||||
child: ElevatedButton.icon(
|
: 'Failed to report location',
|
||||||
onPressed: () {
|
),
|
||||||
Navigator.push(
|
),
|
||||||
context,
|
);
|
||||||
MaterialPageRoute(builder: (_) => const StatusScreen()),
|
}
|
||||||
);
|
},
|
||||||
},
|
icon: const Icon(Icons.send),
|
||||||
icon: const Icon(Icons.history),
|
label: const Text('Send Location'),
|
||||||
label: const Text('Status/Logs'),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ class SettingsScreen extends StatefulWidget {
|
||||||
class _SettingsScreenState extends State<SettingsScreen> {
|
class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
late TextEditingController _serverUrlController;
|
late TextEditingController _serverUrlController;
|
||||||
late TextEditingController _deviceIdController;
|
late TextEditingController _deviceIdController;
|
||||||
late TextEditingController _passwordController;
|
|
||||||
late int _accuracy;
|
late int _accuracy;
|
||||||
late int _distanceFilter;
|
late int _distanceFilter;
|
||||||
late int _interval;
|
late int _interval;
|
||||||
|
|
@ -25,7 +24,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
super.initState();
|
super.initState();
|
||||||
_serverUrlController = TextEditingController(text: Preferences.serverUrl);
|
_serverUrlController = TextEditingController(text: Preferences.serverUrl);
|
||||||
_deviceIdController = TextEditingController(text: Preferences.deviceId);
|
_deviceIdController = TextEditingController(text: Preferences.deviceId);
|
||||||
_passwordController = TextEditingController(text: Preferences.password);
|
|
||||||
_accuracy = Preferences.accuracy;
|
_accuracy = Preferences.accuracy;
|
||||||
_distanceFilter = Preferences.distanceFilter;
|
_distanceFilter = Preferences.distanceFilter;
|
||||||
_interval = Preferences.interval;
|
_interval = Preferences.interval;
|
||||||
|
|
@ -38,14 +36,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_serverUrlController.dispose();
|
_serverUrlController.dispose();
|
||||||
_deviceIdController.dispose();
|
_deviceIdController.dispose();
|
||||||
_passwordController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveSettings() async {
|
Future<void> _saveSettings() async {
|
||||||
await Preferences.setServerUrl(_serverUrlController.text);
|
await Preferences.setServerUrl(_serverUrlController.text);
|
||||||
await Preferences.setDeviceId(_deviceIdController.text);
|
await Preferences.setDeviceId(_deviceIdController.text);
|
||||||
await Preferences.setPassword(_passwordController.text);
|
|
||||||
await Preferences.setAccuracy(_accuracy);
|
await Preferences.setAccuracy(_accuracy);
|
||||||
await Preferences.setDistanceFilter(_distanceFilter);
|
await Preferences.setDistanceFilter(_distanceFilter);
|
||||||
await Preferences.setInterval(_interval);
|
await Preferences.setInterval(_interval);
|
||||||
|
|
@ -56,7 +52,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
await LocationBridge.updateConfig({
|
await LocationBridge.updateConfig({
|
||||||
'serverUrl': _serverUrlController.text,
|
'serverUrl': _serverUrlController.text,
|
||||||
'deviceId': _deviceIdController.text,
|
'deviceId': _deviceIdController.text,
|
||||||
'password': _passwordController.text,
|
|
||||||
'accuracy': _accuracy,
|
'accuracy': _accuracy,
|
||||||
'distanceFilter': _distanceFilter,
|
'distanceFilter': _distanceFilter,
|
||||||
'interval': _interval,
|
'interval': _interval,
|
||||||
|
|
@ -82,7 +77,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
_buildSectionHeader('Server'),
|
_buildSectionHeader('Server'),
|
||||||
_buildTextField('Server URL', _serverUrlController),
|
_buildTextField('Server URL', _serverUrlController),
|
||||||
_buildTextField('Device ID', _deviceIdController),
|
_buildTextField('Device ID', _deviceIdController),
|
||||||
_buildTextField('Password', _passwordController, obscure: true),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildSectionHeader('Location'),
|
_buildSectionHeader('Location'),
|
||||||
_buildAccuracyDropdown(),
|
_buildAccuracyDropdown(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue