feat: revamp UI with dark terminal aesthetic
This commit is contained in:
parent
d673f01a2d
commit
02040081ce
3 changed files with 825 additions and 201 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:traccar_client/bridge/location_bridge.dart';
|
||||
import 'package:traccar_client/preferences.dart';
|
||||
|
||||
|
|
@ -22,6 +23,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
statusBarColor: Color(0xFF0d0d0d),
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
),
|
||||
);
|
||||
_serverUrlController = TextEditingController(text: Preferences.serverUrl);
|
||||
_deviceIdController = TextEditingController(text: Preferences.deviceId);
|
||||
_distanceFilterController = TextEditingController(
|
||||
|
|
@ -74,111 +81,323 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
});
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Settings saved')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: const [
|
||||
Icon(Icons.check_circle, color: Color(0xFF00e676), size: 18),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'Settings saved',
|
||||
style: TextStyle(fontFamily: 'monospace', letterSpacing: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: const Color(0xFF1a1a1a),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
backgroundColor: const Color(0xFF0d0d0d),
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color(0xFF161616),
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Color(0xFF9e9e9e),
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Text(
|
||||
'SETTINGS',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 2,
|
||||
color: Color(0xFFe0e0e0),
|
||||
),
|
||||
),
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(height: 1, color: const Color(0xFF2a2a2a)),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
children: [
|
||||
_buildSectionHeader('Server'),
|
||||
_buildSectionHeader('SERVER'),
|
||||
const SizedBox(height: 12),
|
||||
_buildTextField('Server URL', _serverUrlController),
|
||||
const SizedBox(height: 12),
|
||||
_buildTextField('Device ID', _deviceIdController),
|
||||
const SizedBox(height: 16),
|
||||
_buildSectionHeader('Location'),
|
||||
_buildAccuracyDropdown(),
|
||||
_buildNumberField(
|
||||
'Distance Filter (m)',
|
||||
_distanceFilterController,
|
||||
0,
|
||||
1000,
|
||||
),
|
||||
_buildNumberField(
|
||||
'Update Interval (s)',
|
||||
_intervalController,
|
||||
30,
|
||||
3600,
|
||||
),
|
||||
_buildNumberField('Heartbeat (s)', _heartbeatController, 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'),
|
||||
_buildSectionHeader('LOCATION'),
|
||||
const SizedBox(height: 12),
|
||||
_buildAccuracyDropdown(),
|
||||
const SizedBox(height: 12),
|
||||
_buildNumberField('Distance Filter (m)', _distanceFilterController),
|
||||
const SizedBox(height: 12),
|
||||
_buildNumberField('Update Interval (s)', _intervalController),
|
||||
const SizedBox(height: 12),
|
||||
_buildNumberField('Heartbeat (s)', _heartbeatController),
|
||||
const SizedBox(height: 24),
|
||||
_buildSectionHeader('ADVANCED'),
|
||||
const SizedBox(height: 8),
|
||||
_buildSwitch(
|
||||
'Offline Buffering',
|
||||
'Queue locations when network unavailable',
|
||||
_offlineBuffer,
|
||||
(v) => setState(() => _offlineBuffer = v),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
_buildSwitch(
|
||||
'Stop Detection',
|
||||
'Auto-stop tracking when stationary',
|
||||
_stopDetection,
|
||||
(v) => setState(() => _stopDetection = v),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSaveButton(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 3,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00bcd4),
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 2,
|
||||
color: Color(0xFF00bcd4),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField(String label, TextEditingController controller) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF161616),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFF2a2a2a), width: 1),
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
color: Color(0xFFe0e0e0),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
letterSpacing: 1,
|
||||
color: Color(0xFF616161),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField(
|
||||
String label,
|
||||
TextEditingController controller, {
|
||||
bool obscure = false,
|
||||
}) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(labelText: label),
|
||||
obscureText: obscure,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAccuracyDropdown() {
|
||||
return DropdownButtonFormField<int>(
|
||||
// ignore: deprecated_member_use
|
||||
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!),
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF161616),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFF2a2a2a), width: 1),
|
||||
),
|
||||
child: DropdownButtonFormField<int>(
|
||||
value: _accuracy,
|
||||
dropdownColor: const Color(0xFF1a1a1a),
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
color: Color(0xFFe0e0e0),
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Accuracy',
|
||||
labelStyle: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
letterSpacing: 1,
|
||||
color: Color(0xFF616161),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 0,
|
||||
child: Text('High', style: TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 1,
|
||||
child: Text(
|
||||
'High Accuracy',
|
||||
style: TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 2,
|
||||
child: Text('Balanced', style: TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 3,
|
||||
child: Text('Low', style: TextStyle(fontFamily: 'monospace')),
|
||||
),
|
||||
],
|
||||
onChanged: (v) => setState(() => _accuracy = v!),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumberField(
|
||||
String label,
|
||||
TextEditingController controller,
|
||||
int min,
|
||||
int max,
|
||||
Widget _buildNumberField(String label, TextEditingController controller) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF161616),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFF2a2a2a), width: 1),
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
color: Color(0xFFe0e0e0),
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelStyle: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 11,
|
||||
letterSpacing: 1,
|
||||
color: Color(0xFF616161),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSwitch(
|
||||
String title,
|
||||
String subtitle,
|
||||
bool value,
|
||||
ValueChanged<bool> onChanged,
|
||||
) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(labelText: label),
|
||||
keyboardType: TextInputType.number,
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF161616),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFF2a2a2a), width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFFe0e0e0),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 10,
|
||||
color: Color(0xFF616161),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: const Color(0xFF00e676),
|
||||
activeTrackColor: const Color(0xFF00e676).withOpacity(0.3),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSaveButton() {
|
||||
return Material(
|
||||
color: const Color(0xFF00bcd4).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: InkWell(
|
||||
onTap: _saveSettings,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF00bcd4).withOpacity(0.4),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'SAVE SETTINGS',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 2,
|
||||
color: Color(0xFF00bcd4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue