Skip to content

Commit 6c53fcd

Browse files
Merge pull request #144 from OpenEarable/save_config
Save config
2 parents 083f3a3 + 2a0b6db commit 6c53fcd

File tree

6 files changed

+358
-59
lines changed

6 files changed

+358
-59
lines changed

open_wearable/lib/view_models/sensor_configuration_provider.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,36 @@ class SensorConfigurationProvider with ChangeNotifier {
189189
}
190190
}
191191

192+
Map<String, String> toJson() {
193+
return _sensorConfigurations.map((config, value) =>
194+
MapEntry(config.name, value.key),);
195+
}
196+
197+
Future<bool> restoreFromJson(Map<String, String> jsonMap) async {
198+
Map<SensorConfiguration, SensorConfigurationValue>
199+
restoredConfigurations = {};
200+
for (final config in _sensorConfigurations.keys) {
201+
final selectedKey = jsonMap[config.name];
202+
if (selectedKey == null) continue;
203+
204+
try {
205+
final SensorConfigurationValue matchingValue = config.values.firstWhere(
206+
(v) => v.key == selectedKey,
207+
);
208+
restoredConfigurations[config] = matchingValue;
209+
} on StateError {
210+
logger.e("Failed to restore configuration for ${config.name}");
211+
return false;
212+
}
213+
}
214+
for (final config in restoredConfigurations.keys) {
215+
_sensorConfigurations[config] = restoredConfigurations[config]!;
216+
_updateSelectedOptions(config);
217+
}
218+
notifyListeners();
219+
return true;
220+
}
221+
192222
@override
193223
void dispose() {
194224
_sensorConfigurationSubscription?.cancel();
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'package:path_provider/path_provider.dart';
4+
5+
class SensorConfigurationStorage {
6+
/// Returns the directory where sensor configurations are stored.
7+
/// Creates the directory if it does not exist.
8+
static Future<Directory> _getConfigDirectory() async {
9+
final dir = await getApplicationDocumentsDirectory();
10+
final configDir = Directory('${dir.path}/sensor_configurations');
11+
if (!await configDir.exists()) {
12+
await configDir.create(recursive: true);
13+
}
14+
return configDir;
15+
}
16+
17+
/// Returns a list of all configuration files in the sensor configurations directory.
18+
/// Each file is expected to be a JSON file with a specific configuration.
19+
static Future<List<File>> _getAllConfigFiles() async {
20+
final configDir = await _getConfigDirectory();
21+
return configDir.list().where((file) =>
22+
file is File && file.path.endsWith('.json'),
23+
).cast<File>().toList();
24+
}
25+
26+
/// Returns the file for a specific configuration key.
27+
/// Creates the file if it does not exist.
28+
static Future<File> _getConfigFile(String key) async {
29+
final configDir = await _getConfigDirectory();
30+
return File('${configDir.path}/${sanitizeKey(key)}.json');
31+
}
32+
33+
/// Saves a configuration for a specific key.
34+
/// If the file already exists, it will be overwritten.
35+
/// The configuration is expected to be a map of string key-value pairs.
36+
static Future<void> saveConfiguration(String key, Map<String, String> config) async {
37+
final File file = await _getConfigFile(key);
38+
await file.writeAsString(jsonEncode(config));
39+
}
40+
41+
static Future<List<String>> listConfigurationKeys() async {
42+
final files = await _getAllConfigFiles();
43+
return files.map(_getKeyFromFile).toList();
44+
}
45+
46+
static String _getKeyFromFile(File file) =>
47+
file.uri.pathSegments.last.replaceAll('.json', '');
48+
49+
/// Loads all configurations from the sensor configurations directory.
50+
/// Returns a map where the keys are configuration names and the values are maps of string key-value pairs.
51+
/// Each configuration is expected to be stored in a JSON file.
52+
static Future<Map<String, Map<String, String>>> loadConfigurations() async {
53+
final allConfigs = <String, Map<String, String>>{};
54+
final configFiles = await _getAllConfigFiles();
55+
for (final file in configFiles) {
56+
final contents = await file.readAsString();
57+
allConfigs[_getKeyFromFile(file)] = Map<String, String>.from(jsonDecode(contents));
58+
}
59+
return allConfigs;
60+
}
61+
62+
static Future<Map<String, String>> loadConfiguration(String key) async {
63+
final file = await _getConfigFile(key);
64+
if (await file.exists()) {
65+
final contents = await file.readAsString();
66+
return Map<String, String>.from(jsonDecode(contents));
67+
}
68+
return {};
69+
}
70+
71+
/// Deletes a specific configuration by its key.
72+
/// If the file does not exist, it will do nothing.
73+
static Future<void> deleteConfiguration(String key) async {
74+
final file = await _getConfigFile(key);
75+
if (await file.exists()) {
76+
await file.delete();
77+
}
78+
}
79+
80+
static String sanitizeKey(String key) => key.replaceAll(RegExp(r'[^\w\-]'), '_');
81+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
3+
import 'package:logger/logger.dart';
4+
import 'package:provider/provider.dart';
5+
6+
import '../../../view_models/sensor_configuration_provider.dart';
7+
import '../../../view_models/sensor_configuration_storage.dart';
8+
9+
Logger _logger = Logger();
10+
11+
class SaveConfigRow extends StatefulWidget {
12+
const SaveConfigRow({super.key});
13+
14+
@override
15+
State<SaveConfigRow> createState() => _SaveConfigRowState();
16+
}
17+
18+
class _SaveConfigRowState extends State<SaveConfigRow> {
19+
String _configName = '';
20+
21+
@override
22+
Widget build(BuildContext context) {
23+
return PlatformListTile(
24+
title: PlatformTextField(
25+
onChanged: (value) {
26+
setState(() {
27+
_configName = value;
28+
});
29+
},
30+
onSubmitted: (value) async {
31+
setState(() {
32+
_configName = value.trim();
33+
});
34+
},
35+
onTapOutside: (event) => FocusScope.of(context).unfocus(),
36+
hintText: "Save as...",
37+
),
38+
trailing: PlatformElevatedButton(
39+
onPressed: () async {
40+
SensorConfigurationProvider provider =
41+
Provider.of<SensorConfigurationProvider>(context, listen: false);
42+
Map<String, String> config = provider.toJson();
43+
44+
_logger.d("Saving configuration: $_configName with data: $config");
45+
46+
if (_configName.isNotEmpty) {
47+
await SensorConfigurationStorage.saveConfiguration(_configName.trim(), config);
48+
} else {
49+
showPlatformDialog(
50+
context: context,
51+
builder: (context) {
52+
return PlatformAlertDialog(
53+
title: Text("Configuration Name Required"),
54+
content: Text("Please enter a name for the configuration."),
55+
actions: [
56+
PlatformDialogAction(
57+
child: PlatformText("OK"),
58+
onPressed: () => Navigator.of(context).pop(),
59+
),
60+
],
61+
);
62+
},
63+
);
64+
}
65+
},
66+
child: Text("Save"),
67+
),
68+
);
69+
}
70+
}

0 commit comments

Comments
 (0)