179 lines
No EOL
5.8 KiB
Dart
179 lines
No EOL
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:traccar_client/bridge/location_bridge.dart';
|
|
import 'package:traccar_client/preferences.dart';
|
|
|
|
class SettingsScreen extends StatefulWidget {
|
|
const SettingsScreen({super.key});
|
|
|
|
@override
|
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
}
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
|
late TextEditingController _serverUrlController;
|
|
late TextEditingController _deviceIdController;
|
|
late TextEditingController _passwordController;
|
|
late int _accuracy;
|
|
late int _distanceFilter;
|
|
late int _interval;
|
|
late int _heartbeat;
|
|
late bool _offlineBuffer;
|
|
late bool _stopDetection;
|
|
|
|
@override
|
|
void initState() {
|
|
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;
|
|
_heartbeat = Preferences.heartbeat;
|
|
_offlineBuffer = Preferences.offlineBuffer;
|
|
_stopDetection = Preferences.stopDetection;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_serverUrlController.dispose();
|
|
_deviceIdController.dispose();
|
|
_passwordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _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);
|
|
await Preferences.setHeartbeat(_heartbeat);
|
|
await Preferences.setOfflineBuffer(_offlineBuffer);
|
|
await Preferences.setStopDetection(_stopDetection);
|
|
|
|
await LocationBridge.updateConfig({
|
|
'serverUrl': _serverUrlController.text,
|
|
'deviceId': _deviceIdController.text,
|
|
'password': _passwordController.text,
|
|
'accuracy': _accuracy,
|
|
'distanceFilter': _distanceFilter,
|
|
'interval': _interval,
|
|
'heartbeat': _heartbeat,
|
|
'offlineBuffer': _offlineBuffer,
|
|
'stopDetection': _stopDetection,
|
|
});
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Settings saved')),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Settings'),
|
|
),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
_buildSectionHeader('Server'),
|
|
_buildTextField('Server URL', _serverUrlController),
|
|
_buildTextField('Device ID', _deviceIdController),
|
|
_buildTextField('Password', _passwordController, obscure: true),
|
|
const SizedBox(height: 16),
|
|
_buildSectionHeader('Location'),
|
|
_buildAccuracyDropdown(),
|
|
_buildNumberField('Distance Filter (m)', _distanceFilter, 0, 1000),
|
|
_buildNumberField('Update Interval (s)', _interval, 30, 3600),
|
|
_buildNumberField('Heartbeat (s)', _heartbeat, 60, 3600),
|
|
const SizedBox(height: 16),
|
|
_buildSectionHeader('Advanced'),
|
|
SwitchListTile(
|
|
title: const Text('Offline Buffering'),
|
|
subtitle: const Text('Queue locations when offline'),
|
|
value: _offlineBuffer,
|
|
onChanged: (v) => setState(() => _offlineBuffer = v),
|
|
),
|
|
SwitchListTile(
|
|
title: const Text('Stop Detection'),
|
|
subtitle: const Text('Auto-stop when stationary'),
|
|
value: _stopDetection,
|
|
onChanged: (v) => setState(() => _stopDetection = v),
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: _saveSettings,
|
|
child: const Text('Save Settings'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionHeader(String title) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 16, bottom: 8),
|
|
child: Text(
|
|
title,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTextField(String label, TextEditingController controller,
|
|
{bool obscure = false}) {
|
|
return TextField(
|
|
controller: controller,
|
|
decoration: InputDecoration(labelText: label),
|
|
obscureText: obscure,
|
|
);
|
|
}
|
|
|
|
Widget _buildAccuracyDropdown() {
|
|
return DropdownButtonFormField<int>(
|
|
value: _accuracy,
|
|
decoration: const InputDecoration(labelText: 'Accuracy'),
|
|
items: const [
|
|
DropdownMenuItem(value: 0, child: Text('High')),
|
|
DropdownMenuItem(value: 1, child: Text('High Accuracy')),
|
|
DropdownMenuItem(value: 2, child: Text('Balanced')),
|
|
DropdownMenuItem(value: 3, child: Text('Low')),
|
|
],
|
|
onChanged: (v) => setState(() => _accuracy = v!),
|
|
);
|
|
}
|
|
|
|
Widget _buildNumberField(String label, int value, int min, int max) {
|
|
return TextFormField(
|
|
initialValue: value.toString(),
|
|
decoration: InputDecoration(labelText: label),
|
|
keyboardType: TextInputType.number,
|
|
validator: (v) {
|
|
final parsed = int.tryParse(v ?? '');
|
|
if (parsed == null || parsed < min || parsed > max) {
|
|
return 'Must be between $min and $max';
|
|
}
|
|
return null;
|
|
},
|
|
onSaved: (v) {
|
|
final parsed = int.tryParse(v ?? '');
|
|
if (parsed != null) {
|
|
if (label.contains('Distance')) {
|
|
_distanceFilter = parsed;
|
|
} else if (label.contains('Interval')) {
|
|
_interval = parsed;
|
|
} else if (label.contains('Heartbeat')) {
|
|
_heartbeat = parsed;
|
|
}
|
|
}
|
|
},
|
|
);
|
|
}
|
|
} |