Add profile storage and sync

This commit is contained in:
Janez T
2026-03-13 10:34:52 +01:00
parent 90877a1891
commit 6e17de3866
2 changed files with 25 additions and 158 deletions

View File

@@ -342,6 +342,21 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
}); });
} }
void _syncRadioPresetSelection() {
final freqResult = ValidationService().parseFrequency(_freqController.text);
final matchedPreset = freqResult.isSuccess
? _matchRadioPreset(
frequencyKhz: (freqResult.value! * 1000).round(),
bandwidth: _bandwidthToValue(_selectedBandwidth),
spreadingFactor: _selectedSpreadingFactor,
codingRate: _selectedCodingRate,
)
: null;
_selectedRadioPreset = matchedPreset;
_showCustomRadioSettings = matchedPreset == null;
}
void _markPublicInfoDirty() { void _markPublicInfoDirty() {
if (_publicInfoSaved || _publicInfoError != null) { if (_publicInfoSaved || _publicInfoError != null) {
setState(() { setState(() {
@@ -902,6 +917,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
DropdownButtonFormField<_RadioPreset?>( DropdownButtonFormField<_RadioPreset?>(
key: ValueKey(_selectedRadioPreset?.id ?? 'custom'),
initialValue: _selectedRadioPreset, initialValue: _selectedRadioPreset,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Radio preset', labelText: 'Radio preset',
@@ -993,16 +1009,13 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
decimal: true, decimal: true,
), ),
onChanged: (_) { onChanged: (_) {
if (_selectedRadioPreset != null) { setState(_syncRadioPresetSelection);
setState(() {
_selectedRadioPreset = null;
});
}
_markRadioSettingsDirty(); _markRadioSettingsDirty();
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
key: ValueKey('bandwidth-$_selectedBandwidth'),
initialValue: _selectedBandwidth, initialValue: _selectedBandwidth,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of( labelText: AppLocalizations.of(
@@ -1024,7 +1037,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (newValue != null) { if (newValue != null) {
setState(() { setState(() {
_selectedBandwidth = newValue; _selectedBandwidth = newValue;
_selectedRadioPreset = null; _syncRadioPresetSelection();
}); });
_markRadioSettingsDirty(); _markRadioSettingsDirty();
} }
@@ -1032,6 +1045,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<int>( DropdownButtonFormField<int>(
key: ValueKey(
'spreading-factor-$_selectedSpreadingFactor',
),
initialValue: _selectedSpreadingFactor, initialValue: _selectedSpreadingFactor,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of( labelText: AppLocalizations.of(
@@ -1055,7 +1071,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (newValue != null) { if (newValue != null) {
setState(() { setState(() {
_selectedSpreadingFactor = newValue; _selectedSpreadingFactor = newValue;
_selectedRadioPreset = null; _syncRadioPresetSelection();
}); });
_markRadioSettingsDirty(); _markRadioSettingsDirty();
} }
@@ -1063,6 +1079,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<int>( DropdownButtonFormField<int>(
key: ValueKey('coding-rate-$_selectedCodingRate'),
initialValue: _selectedCodingRate, initialValue: _selectedCodingRate,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of( labelText: AppLocalizations.of(
@@ -1086,7 +1103,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (newValue != null) { if (newValue != null) {
setState(() { setState(() {
_selectedCodingRate = newValue; _selectedCodingRate = newValue;
_selectedRadioPreset = null; _syncRadioPresetSelection();
}); });
_markRadioSettingsDirty(); _markRadioSettingsDirty();
} }

View File

@@ -1260,156 +1260,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
), ),
]), ]),
_buildSettingsCard([
ListTile(
leading: const Icon(Icons.layers_outlined),
title: const Text('Rendering'),
subtitle: const Text(
'Control map drawings and overlay layers used by the renderer',
),
),
Consumer<DrawingProvider>(
builder: (context, drawingProvider, child) => SwitchListTile(
secondary: const Icon(Icons.draw_outlined),
title: Text(AppLocalizations.of(context)!.showReceivedDrawings),
subtitle: const Text(
'Render drawings received from other devices on the map',
),
value: drawingProvider.showReceivedDrawings,
onChanged: (value) async {
await drawingProvider.toggleReceivedDrawings();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.grid_on, color: Colors.blue),
title: Text(AppLocalizations.of(context)!.cadastralParcels),
subtitle: const Text('WMS overlay'),
value: mapProvider.showCadastralOverlay,
onChanged: (value) async {
await mapProvider.toggleCadastralOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.route, color: Colors.green),
title: Text(AppLocalizations.of(context)!.forestRoads),
subtitle: const Text('WMS overlay'),
value: mapProvider.showForestRoadsOverlay,
onChanged: (value) async {
await mapProvider.toggleForestRoadsOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.hiking, color: Colors.brown),
title: Text(AppLocalizations.of(context)!.hikingTrails),
subtitle: const Text('WMS overlay'),
value: mapProvider.showHikingTrailsOverlay,
onChanged: (value) async {
await mapProvider.toggleHikingTrailsOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.alt_route, color: Colors.grey),
title: Text(AppLocalizations.of(context)!.mainRoads),
subtitle: const Text('WMS overlay'),
value: mapProvider.showMainRoadsOverlay,
onChanged: (value) async {
await mapProvider.toggleMainRoadsOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.numbers, color: Colors.purple),
title: Text(AppLocalizations.of(context)!.houseNumbers),
subtitle: const Text('WMS overlay'),
value: mapProvider.showHouseNumbersOverlay,
onChanged: (value) async {
await mapProvider.toggleHouseNumbersOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(
Icons.warning_amber,
color: Colors.orange,
),
title: Text(AppLocalizations.of(context)!.fireHazardZones),
subtitle: const Text('WMS overlay'),
value: mapProvider.showFireHazardZonesOverlay,
onChanged: (value) async {
await mapProvider.toggleFireHazardZonesOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(
Icons.local_fire_department,
color: Colors.red,
),
title: Text(AppLocalizations.of(context)!.historicalFires),
subtitle: const Text('WMS overlay'),
value: mapProvider.showHistoricalFiresOverlay,
onChanged: (value) async {
await mapProvider.toggleHistoricalFiresOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.forest, color: Colors.teal),
title: Text(AppLocalizations.of(context)!.firebreaks),
subtitle: const Text('WMS overlay'),
value: mapProvider.showFirebreaksOverlay,
onChanged: (value) async {
await mapProvider.toggleFirebreaksOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.warning, color: Colors.deepOrange),
title: Text(AppLocalizations.of(context)!.krasFireZones),
subtitle: const Text('WMS overlay'),
value: mapProvider.showKrasFireZonesOverlay,
onChanged: (value) async {
await mapProvider.toggleKrasFireZonesOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.place, color: Colors.indigo),
title: Text(AppLocalizations.of(context)!.placeNames),
subtitle: const Text('WMS overlay'),
value: mapProvider.showPlaceNamesOverlay,
onChanged: (value) async {
await mapProvider.togglePlaceNamesOverlay();
},
),
),
Consumer<MapProvider>(
builder: (context, mapProvider, child) => SwitchListTile(
secondary: const Icon(Icons.border_outer, color: Colors.cyan),
title: Text(AppLocalizations.of(context)!.municipalityBorders),
subtitle: const Text('WMS overlay'),
value: mapProvider.showMunicipalityBordersOverlay,
onChanged: (value) async {
await mapProvider.toggleMunicipalityBordersOverlay();
},
),
),
]),
_buildSectionHeader('Voice'), _buildSectionHeader('Voice'),
Consumer2<AppProvider, ConnectionProvider>( Consumer2<AppProvider, ConnectionProvider>(
builder: (context, appProvider, connectionProvider, child) => builder: (context, appProvider, connectionProvider, child) =>