diff --git a/lib/foreground_task/task_handler.dart b/lib/foreground_task/task_handler.dart index 0113d9498cb02fa996b29d8ea0de2176c94625fe..ac566817e2571cae021b5805a20af226f499e426 100644 --- a/lib/foreground_task/task_handler.dart +++ b/lib/foreground_task/task_handler.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:app/foreground_task/bluetooth.dart'; import 'package:app/foreground_task/circular_buffer.dart'; +import 'package:app/manager/settings_manager.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:isar/isar.dart'; @@ -29,7 +30,7 @@ void startBackgroundTask() { class ForegroundTaskHandler extends TaskHandler { late Isar _isar; late Bluetooth _bluetooth; - late SharedPreferencesAsync _prefs; + final SettingsManager _settingsManager = SettingsManager(); late Uuid _uuid; final LocationSettings locationSettings = LocationSettings( accuracy: LocationAccuracy.best, @@ -108,9 +109,6 @@ class ForegroundTaskHandler extends TaskHandler { // add callback for the disconnect event _bluetooth.subscribeToDisconnectEvent(_onBleDeviceDisconnected); - //Initialize access to the sharedPreferenzes - _prefs = SharedPreferencesAsync(); - // Initialize the location _positionStream = Geolocator.getPositionStream(locationSettings: locationSettings) @@ -128,11 +126,8 @@ class ForegroundTaskHandler extends TaskHandler { ); // Initialize Sentry to report errors from isolate - final sentryEnabled = await _prefs.getBool('sentryEnabled') ?? false; - //final sentryEnabled = true; - - final sentryDSN = await _prefs.getString('sentryDSN') ?? - 'https://d36af7603bf2434889b0bc7ffcaeeda7@glitchtip.weiler.rocks/1'; + final sentryEnabled = await _settingsManager.readSentryEnabled(); + final sentryDSN = await _settingsManager.readSentryDSN(); if (sentryEnabled && sentryDSN.isNotEmpty) { await SentryFlutter.init((options) { @@ -375,22 +370,29 @@ class ForegroundTaskHandler extends TaskHandler { } // Substract the handlebar length from the sensor readings - final handlebarDistance = await _prefs.getInt("handlebarDistance") ?? 0; - final invertSensors = await _prefs.getBool("invertSensors") ?? false; + final handlebarDistance = await _settingsManager.readHandlebarOffset(); + final invertSensors = await _settingsManager.readInvertSensors(); + final minMeassurement = await _settingsManager.readMinMeasurement(); + final maxMeassurement = await _settingsManager.readMaxMeasurement(); // find the index in the list of the confirmed event final confirmedIdx = sampleData.indexWhere((element) => element.confirmed); // Extract the sensor data, invert the sensors if set in settings // if the value is smaller then the handlebar distance set it to 65000 + // also check if the value is inside of the min and max meassurement range // 65535 is the max value a obs reports and will be ignored by the portal final s1Data = sampleData .map((e) => invertSensors ? e.sensor2 : e.sensor1) .map((val) => (val - handlebarDistance) > 0.0 ? val : 65535.0) + .map((val) => + val < minMeassurement || val > maxMeassurement ? 65535.0 : val) .toList(); final s2Data = sampleData .map((e) => invertSensors ? e.sensor1 : e.sensor2) .map((val) => (val - handlebarDistance) > 0.0 ? val : 65535.0) + .map((val) => + val < minMeassurement || val > maxMeassurement ? 65535.0 : val) .toList(); final TripSegment event = TripSegment() diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6fe9e0d1626321b3e79794e260c94fb60b6a776d..86ce87a0a70cb2edf47121297ec45fc2d0da3d9b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -28,6 +28,9 @@ "settingsPageTitle": "OBS APP Einstellungen", "settingsPageHandlebarDistanceDescription": "Miss den horizontalen Abstand deines linken und rechten Lenker-Endes bis zur Radmitte. Hat dein Lenker zum Beispiel eine gesamtbreite von 60 cm, dann gibts du hier 30cm ein.", "settingsPageHandlebarDistanceLabel": "Lenkerabstand in cm", + "settingsPageDistanceFilterDescription": "Einige OpenBikeSensors melden ungültige Entfernungsdaten. Oft liegen diese Werte in der Nähe der Min/Max-Werte. Hier kannst du den Bereich angeben, in dem du die Werte aufzeichnen möchten. Entfernungen, die nicht zwischen diesen beiden Werten liegen, werden nicht aufgezeichnet", + "settingsPageMinMeasurementLabel": "Min Messung in cm", + "settingsPageMaxMeasurementLabel": "Max Messung in cm", "settingsPageInvertSensorsDescription": ",Wenn die Abstandswerte links und rechts vertauscht angezeigt werden, kannst du hier die Sensoren vertauschen. Dies ist z.B. notwendig, wenn der OBS kopfüber montiert wird.", "settingsPageInvertSensorsLabel": "Vertausche den linken und rechten sensor", "settingsPageHandlebarDistanceError": "Bitte gib eine Zahl zwischen 0 und 200 ein", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f9b0f85915ca1fdd44bf2cd014f83005275ac45b..f891b5bb850316400f1c12420b6ba235cbd18dc1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -28,6 +28,9 @@ "settingsPageTitle": "OBS APP Settings", "settingsPageHandlebarDistanceDescription": "Measure the horizontal distance from your left and right handlebar ends to the centre of the wheel. For example, if your handlebars are at all 60 cm wide, enter 30 cm here.", "settingsPageHandlebarDistanceLabel": "Handlebar distance in cm", + "settingsPageDistanceFilterDescription": "Some OpenBikeSensors report invalid distance data. Often these values are close to the min/max values. You can specify the range in which you want to record the values. Distances not between these two values will not be recorded.", + "settingsPageMinMeasurementLabel": "Min Measurement in cm", + "settingsPageMaxMeasurementLabel": "Max Measurement in cm", "settingsPageInvertSensorsDescription": "If the distance values are displayed reversed on the left and right, you can swap the sensors here. This is necessary, for example, if the OBS is mounted upside down.", "settingsPageInvertSensorsLabel": "Swap left and right sensor", "settingsPageHandlebarDistanceError": "Please enter a number between 0 and 200", diff --git a/lib/main.dart b/lib/main.dart index 8fe0061cb84b567f1055d129479b10820867fb4c..38496eb30861b9bdc5bd0d884fd24f159abe8c30 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:app/generated/i18n/app_localizations.dart'; import 'package:app/manager/background_manger.dart'; +import 'package:app/manager/settings_manager.dart'; import 'package:app/providers/battery_provider.dart'; import 'package:app/providers/bluetooth_provider.dart'; import 'package:app/providers/button_provider.dart'; @@ -36,16 +37,11 @@ Future<void> main() async { ], child: const MyApp(), ); - - // Initialize the shared preferences - SharedPreferencesAsync prefs = SharedPreferencesAsync(); + final settingsManager = SettingsManager(); // Initialize Sentry to report errors from isolate - final sentryEnabled = await prefs.getBool('sentryEnabled') ?? false; - //final sentryEnabled = true; - final sentryDSN = await prefs.getString('sentryDSN') ?? - 'https://d36af7603bf2434889b0bc7ffcaeeda7@glitchtip.weiler.rocks/1'; - + final sentryEnabled = await settingsManager.readSentryEnabled(); + final sentryDSN = await settingsManager.readSentryDSN(); if (sentryEnabled && sentryDSN.isNotEmpty) { await SentryFlutter.init((options) { options.dsn = sentryDSN; diff --git a/lib/manager/settings_manager.dart b/lib/manager/settings_manager.dart new file mode 100644 index 0000000000000000000000000000000000000000..341fb700b8c834cb78527f425ad1aad6e561f40d --- /dev/null +++ b/lib/manager/settings_manager.dart @@ -0,0 +1,74 @@ +import 'dart:core'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingsManager { + SharedPreferencesAsync prefs = SharedPreferencesAsync(); + + Future<void> _saveStringValue(String key, String value) async { + await prefs.setString(key, value); + } + + Future<void> _saveIntValue(String key, int value) async { + await prefs.setInt(key, value); + } + + Future<void> _saveBoolValue(String key, bool value) async { + await prefs.setBool(key, value); + } + + Future<int> readHandlebarOffset() async => + await prefs.getInt('handlebarOffset') ?? 0; + + Future<void> writeHandlebarOffset(int value) async { + await _saveIntValue('handlebarOffset', value); + } + + Future<String> readPortalServer() async => + await prefs.getString('portalServer') ?? ""; + + Future<void> writePortalServer(String value) async { + await _saveStringValue('portalServer', value); + } + + Future<String> readPortalKey() async => + await prefs.getString('portalKey') ?? ""; + + Future<void> writePortalKey(String value) async { + await _saveStringValue('portalKey', value); + } + + Future<String> readSentryDSN() async => + await prefs.getString('sentryDSN') ?? ""; + + Future<void> writeSentryDSN(String value) async { + await _saveStringValue('sentryDSN', value); + } + + Future<bool> readSentryEnabled() async => + await prefs.getBool('sentryEnabled') ?? false; + + Future<void> writeSentryEnabled(bool value) async { + await _saveBoolValue('sentryEnabled', value); + } + + Future<bool> readInvertSensors() async => + await prefs.getBool('invertSensors') ?? false; + + Future<void> writeInvertSensors(bool value) async { + await _saveBoolValue('invertSensors', value); + } + + Future<int> readMinMeasurement() async => + await prefs.getInt('minMeasurement') ?? 0; + + Future<void> writeMinMeasurement(int value) async { + await _saveIntValue('minMeasurement', value); + } + + Future<int> readMaxMeasurement() async => + await prefs.getInt('maxMeasurement') ?? 1000; + + Future<void> writeMaxMeasurement(int value) async { + await _saveIntValue('maxMeasurement', value); + } +} diff --git a/lib/manager/upload_manger.dart b/lib/manager/upload_manger.dart index eff1c18b9e0ca8259e67f8e0b022a180ae2a6880..9fa1f93dd950406b86fd198aa1ff0199eec76de9 100644 --- a/lib/manager/upload_manger.dart +++ b/lib/manager/upload_manger.dart @@ -1,5 +1,6 @@ import 'package:app/database/trip.dart'; import 'package:app/manager/database_manger.dart'; +import 'package:app/manager/settings_manager.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:isar/isar.dart'; @@ -7,6 +8,8 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; class UploadManager { + final MICRO_SEC_TO_CM_DIVIDER = 58.0; + Future<(int, int)> upload() async { final database = await IsarService().isar; @@ -78,8 +81,8 @@ class UploadManager { return (minLeft, minRight); } - String _genMetadataHeader( - Trip trip, int maxMeasurements, int handlebarDistance) { + String _genMetadataHeader(Trip trip, int maxMeasurements, + int handlebarDistance, int maxFlightTimeinMicroSec) { // https://github.com/openbikesensor/OpenBikeSensorFirmware/blob/main/docs/software/firmware/csv_format.md#metadata final fields = [ "OBSFirmwareVersion=${trip.obsFirmwareRevision}", @@ -90,7 +93,7 @@ class UploadManager { "OffsetRight=${handlebarDistance / 2}", "NumberOfDefinedPrivacyAreas=0", // can be 0 as we do not record in privacy areas "PrivacyLevelApplied=AbsolutePrivacy", - "MaximumValidFlightTimeMicroseconds=18560", + "MaximumValidFlightTimeMicroseconds=$maxFlightTimeinMicroSec", "DistanceSensorsUsed=HC-SR04/JSN-SR04T", // TODO read from db "DeviceId=obs-app" ]; @@ -141,8 +144,6 @@ class UploadManager { final double minLeftDistance = minDistances.$1 - event.offsetLeft; final double minRightDistance = minDistances.$2 - event.offsetRight; - final MICRO_SEC_TO_CM_DIVIDER = 58.0; - final List<String> fields = [ "${event.timestamp.day}.${event.timestamp.month}.${event.timestamp.year}", "${event.timestamp.hour}:${event.timestamp.minute}:${event.timestamp.second}", @@ -192,10 +193,11 @@ class UploadManager { return fields.join(";"); } - String _genCSVFile(Trip trip, int handlebarDistance) { + String _genCSVFile( + Trip trip, int handlebarDistance, int maxFlightTimeinMicroSec) { final maxMeasurements = _maxDistanceForTrip(trip); - final metadata = - _genMetadataHeader(trip, maxMeasurements, handlebarDistance); + final metadata = _genMetadataHeader( + trip, maxMeasurements, handlebarDistance, maxFlightTimeinMicroSec); final header = _genCSVHeader(trip, maxMeasurements); final List<String> rows = []; @@ -207,23 +209,25 @@ class UploadManager { } Future<bool> uploadTrip(Trip trip) async { - final SharedPreferencesAsync prefs = SharedPreferencesAsync(); + final SettingsManager settingsManager = SettingsManager(); - int handlebarDistance = await prefs.getInt('handlebarDistance') ?? 0; + int handlebarDistance = await settingsManager.readHandlebarOffset(); + int maxMeasurements = await settingsManager.readMaxMeasurement(); - String portalServer = await prefs.getString('portalServer') ?? ""; + String portalServer = await settingsManager.readPortalServer(); if (portalServer.isEmpty) { throw Exception("No portal server set"); } if (!portalServer.startsWith("https://")) { portalServer = "https://$portalServer"; } - final portalKey = await prefs.getString('portalKey') ?? ""; + final portalKey = await settingsManager.readPortalKey(); if (portalKey.isEmpty) { throw Exception("No portal key set"); } - String csvContent = _genCSVFile(trip, handlebarDistance); + String csvContent = _genCSVFile(trip, handlebarDistance, + maxMeasurements * MICRO_SEC_TO_CM_DIVIDER.toInt()); String filename = "${trip.trackID}_${trip.createdAt.toString()}.csv"; diff --git a/lib/obs_list_upload_page.dart b/lib/obs_list_upload_page.dart index 9c6e4d818f016e848178042facaf96795ac0a131..a914c498be1e7ae08c56f79e7b12015d9c7def97 100644 --- a/lib/obs_list_upload_page.dart +++ b/lib/obs_list_upload_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:app/manager/settings_manager.dart'; import 'package:app/manager/upload_manger.dart'; import 'package:app/providers/trip_provider.dart'; import 'package:app/providers/upload_provider.dart'; @@ -44,13 +45,11 @@ class _ObsListUploadPageState extends State<ObsListUploadPage> { onPressed: uploadStateProvider.uploading ? null : () async { - final SharedPreferencesAsync prefs = - SharedPreferencesAsync(); + final settingsManager = SettingsManager(); - if (((await prefs.getString('portalServer'))?.isEmpty ?? - true) || - ((await prefs.getString('portalKey'))?.isEmpty ?? - true)) { + if (((await settingsManager.readPortalServer()) + .isEmpty) || + (await settingsManager.readPortalKey()).isEmpty) { toastification.show( context: context, type: ToastificationType.error, diff --git a/lib/obs_settings_page.dart b/lib/obs_settings_page.dart index acc737ec6c4908cbb7f826b3a8c4abdcb33a8cfd..05e6de248af21cd41f18e0bcf5568911115302bf 100644 --- a/lib/obs_settings_page.dart +++ b/lib/obs_settings_page.dart @@ -1,7 +1,9 @@ import 'package:app/database/privacy_zone.dart'; +import 'package:app/manager/settings_manager.dart'; import 'package:app/widgets/map_page.dart'; import 'package:app/providers/privacy_zone_provider.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -25,9 +27,14 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { final TextEditingController _portalServerController = TextEditingController(); final TextEditingController _portalKeyController = TextEditingController(); final TextEditingController _sentryDSNController = TextEditingController(); + final TextEditingController _minMeasurementController = + TextEditingController(); + final TextEditingController _maxMeasurementController = + TextEditingController(); bool _sentryEnabled = false; bool _invertSensors = false; String? _lenkerabstandError; // Variable to hold error message + final SettingsManager _settingsManager = SettingsManager(); @override void initState() { @@ -45,8 +52,7 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { } else { setState(() { _lenkerabstandError = null; // Clear the error if valid - _saveIntValue( - 'handlebarDistance', numberValue); // Save only if valid + _settingsManager.writeHandlebarOffset(numberValue); }); } } else { @@ -58,17 +64,18 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { } Future<void> _loadSavedValues() async { - SharedPreferencesAsync prefs = SharedPreferencesAsync(); - - final handlebarDistance = await prefs.getInt('handlebarDistance') ?? 0; - final portalServer = await prefs.getString('portalServer') ?? ""; - final portalKey = await prefs.getString('portalKey') ?? ""; - final sentryDSN = await prefs.getString('sentryDSN') ?? ""; - final sentryEnabled = await prefs.getBool('sentryEnabled') ?? false; - final invertSensors = await prefs.getBool('invertSensors') ?? false; - + final handlebarDistance = await _settingsManager.readHandlebarOffset(); + final portalServer = await _settingsManager.readPortalServer(); + final portalKey = await _settingsManager.readPortalKey(); + final sentryDSN = await _settingsManager.readSentryDSN(); + final sentryEnabled = await _settingsManager.readSentryEnabled(); + final invertSensors = await _settingsManager.readInvertSensors(); + final minMeasurement = await _settingsManager.readMinMeasurement(); + final maxMeasurement = await _settingsManager.readMaxMeasurement(); setState(() { _lenkerabstandController.text = handlebarDistance.toString(); + _minMeasurementController.text = minMeasurement.toString(); + _maxMeasurementController.text = maxMeasurement.toString(); _portalServerController.text = portalServer; _portalKeyController.text = portalKey; _sentryDSNController.text = sentryDSN; @@ -82,21 +89,6 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { return packageInfo.version; } - Future<void> _saveStringValue(String key, String value) async { - SharedPreferencesAsync prefs = SharedPreferencesAsync(); - await prefs.setString(key, value); - } - - Future<void> _saveIntValue(String key, int value) async { - SharedPreferencesAsync prefs = SharedPreferencesAsync(); - await prefs.setInt(key, value); - } - - Future<void> _saveBoolValue(String key, bool value) async { - SharedPreferencesAsync prefs = SharedPreferencesAsync(); - await prefs.setBool(key, value); - } - @override Widget build(BuildContext context) { final privacyZonesProvider = context.watch<PrivacyZoneProvider>(); @@ -123,16 +115,49 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { keyboardType: TextInputType.number, ), const SizedBox(height: 20), + SelectableText(localizations.settingsPageDistanceFilterDescription), + Row(children: [ + Expanded( + child: TextField( + controller: _minMeasurementController, + decoration: InputDecoration( + labelText: localizations.settingsPageMinMeasurementLabel, + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (value) { + _settingsManager.writeMinMeasurement(int.parse(value)); + }, + ), + ), + const SizedBox(width: 5), + Expanded( + child: TextField( + controller: _maxMeasurementController, + decoration: InputDecoration( + labelText: localizations.settingsPageMaxMeasurementLabel, + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (value) { + _settingsManager.writeMaxMeasurement(int.parse(value)); + }, + ), + ), + ]), + const SizedBox(height: 20), SelectableText(localizations.settingsPageInvertSensorsDescription), SwitchListTile( title: Text(localizations.settingsPageInvertSensorsLabel), value: _invertSensors, onChanged: (value) { + _settingsManager.writeInvertSensors(value); setState(() { - _saveBoolValue('invertSensors', value); - setState(() { - _invertSensors = value; - }); + _invertSensors = value; }); }, ), @@ -142,7 +167,7 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { controller: _portalServerController, decoration: InputDecoration( labelText: localizations.settingsPagePortalServerLabel), - onChanged: (value) => _saveStringValue('portalServer', value), + onChanged: (value) => _settingsManager.writePortalServer(value), ), const SizedBox(height: 20), SelectableText(localizations.settingsPagePortalKeyDescription), @@ -150,7 +175,7 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { controller: _portalKeyController, decoration: InputDecoration( labelText: localizations.settingsPagePortalKeyLabel), - onChanged: (value) => _saveStringValue('portalKey', value), + onChanged: (value) => _settingsManager.writePortalKey(value), ), const SizedBox(height: 40), Text(localizations.settingsPageErrorReportingTitle, @@ -160,11 +185,9 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { title: Text(localizations.settingsPageErrorReportingSwitchLabel), value: _sentryEnabled, onChanged: (value) { + _settingsManager.writeSentryEnabled(value); setState(() { - _saveBoolValue('sentryEnabled', value); - setState(() { - _sentryEnabled = value; - }); + _sentryEnabled = value; }); }, ), @@ -173,7 +196,7 @@ class _OBSSettingsPageState extends State<OBSSettingsPage> { controller: _sentryDSNController, decoration: InputDecoration( labelText: localizations.settingsPageSentryDSNLabel), - onChanged: (value) => _saveStringValue('sentryDSN', value), + onChanged: (value) => _settingsManager.writeSentryDSN(value), ), Divider(), const SizedBox(height: 20), diff --git a/lib/widgets/measurements.dart b/lib/widgets/measurements.dart index 88275f1904cb2417ccc5ddb1aa1e8e9b551039b7..20067740d9d4b1f2e03839e57d7c66b420c7627e 100644 --- a/lib/widgets/measurements.dart +++ b/lib/widgets/measurements.dart @@ -1,11 +1,11 @@ import 'package:app/events.dart'; import 'package:app/generated/i18n/app_localizations.dart'; import 'package:app/manager/background_manger.dart'; +import 'package:app/manager/settings_manager.dart'; import 'package:app/providers/distance_provider.dart'; import 'package:flutter/material.dart'; import 'package:app/providers/bluetooth_provider.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; class Measurements extends StatefulWidget { const Measurements({super.key}); @@ -16,19 +16,18 @@ class Measurements extends StatefulWidget { class _MeasurementsState extends State<Measurements> { late BackgroundManager backgroundManger; - late SharedPreferencesAsync _prefs; + final _settingsManager = SettingsManager(); bool invertSensors = false; @override void initState() { super.initState(); backgroundManger = BackgroundManager.instance; - _prefs = SharedPreferencesAsync(); getInvertSesnors(); } Future<void> getInvertSesnors() async { - final val = await _prefs.getBool('invertSensors') ?? false; + final val = await _settingsManager.readInvertSensors(); setState(() { invertSensors = val; });