feat: add SettingsScreen for configuration
This commit is contained in:
parent
db08fc8d5c
commit
4e2afa3428
1 changed files with 179 additions and 0 deletions
179
lib/settings_screen.dart
Normal file
179
lib/settings_screen.dart
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue