tracpulse/lib/main_screen.dart
2026-04-30 15:39:19 +07:00

255 lines
7.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:traccar_client/bridge/location_bridge.dart';
import 'package:traccar_client/preferences.dart';
import 'package:traccar_client/settings_screen.dart';
import 'package:traccar_client/status_screen.dart';
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
StreamSubscription<Map<String, dynamic>>? _locationSubscription;
bool _isTracking = false;
String _lastLat = '--';
String _lastLon = '--';
String _lastSpeed = '--';
String _lastTime = '--';
@override
void initState() {
super.initState();
_initLocationStream();
}
@override
void dispose() {
_locationSubscription?.cancel();
super.dispose();
}
Future<void> _initLocationStream() async {
final status = await LocationBridge.getStatus();
if (mounted) {
setState(() {
_isTracking = status?['isTracking'] == true;
final lat = status?['lastLatitude'];
final lon = status?['lastLongitude'];
if (lat != null && lat != 0.0) {
_lastLat = lat.toStringAsFixed(4);
}
if (lon != null && lon != 0.0) {
_lastLon = lon.toStringAsFixed(4);
}
final speed = status?['lastSpeed'];
if (speed != null) {
_lastSpeed = '${(speed * 3.6).toStringAsFixed(0)} km/h';
}
final timestamp = status?['lastTimestamp'];
if (timestamp != null) {
_lastTime = _formatTime(
DateTime.fromMillisecondsSinceEpoch(timestamp),
);
}
});
}
_locationSubscription = LocationBridge.locationUpdates.listen((location) {
if (mounted) {
setState(() {
_lastLat = location['latitude']?.toStringAsFixed(4) ?? '--';
_lastLon = location['longitude']?.toStringAsFixed(4) ?? '--';
_lastSpeed = location['speed'] != null
? '${(location['speed'] * 3.6).toStringAsFixed(0)} km/h'
: '--';
_lastTime = location['timestamp'] != null
? _formatTime(
DateTime.fromMillisecondsSinceEpoch(location['timestamp']),
)
: '--';
});
}
});
}
String _formatTime(DateTime dt) {
return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}';
}
Future<void> _toggleTracking() async {
final previousState = _isTracking;
final newState = !_isTracking;
setState(() {
_isTracking = newState;
});
bool success;
if (newState) {
success = await LocationBridge.startTracking(
config: {
'serverUrl': Preferences.serverUrl,
'deviceId': Preferences.deviceId,
'password': Preferences.password,
'accuracy': Preferences.accuracy,
'distanceFilter': Preferences.distanceFilter,
'interval': Preferences.interval,
'heartbeat': Preferences.heartbeat,
'offlineBuffer': Preferences.offlineBuffer,
'stopDetection': Preferences.stopDetection,
},
);
} else {
success = await LocationBridge.stopTracking();
}
if (!success && mounted) {
setState(() {
_isTracking = previousState;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
previousState
? 'Failed to stop tracking'
: 'Failed to start tracking',
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Traccar Client'), centerTitle: true),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildStatusCard(),
const SizedBox(height: 24),
_buildTrackingToggle(),
const Spacer(),
_buildActionButtons(),
],
),
),
);
}
Widget _buildStatusCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Last Location',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
_buildInfoRow('Lat', _lastLat),
_buildInfoRow('Lon', _lastLon),
_buildInfoRow('Speed', _lastSpeed),
_buildInfoRow('Time', _lastTime),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
Widget _buildTrackingToggle() {
return SwitchListTile(
title: Text(_isTracking ? 'Tracking: ON' : 'Tracking: OFF'),
subtitle: Text(
_isTracking ? 'Location updates active' : 'Tap to start tracking',
),
value: _isTracking,
onChanged: (_) => _toggleTracking(),
activeTrackColor: Colors.green,
);
}
Widget _buildActionButtons() {
return Column(
children: [
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 sent to server'
: 'Failed - check logs',
),
duration: Duration(seconds: 2),
),
);
}
},
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'),
),
),
],
),
],
);
}
}