diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart new file mode 100644 index 0000000..7fc6fd2 --- /dev/null +++ b/lib/settings_screen.dart @@ -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 createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + 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 _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( + 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; + } + } + }, + ); + } +} \ No newline at end of file