diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 873f80c..d0fe214 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -511,7 +511,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -530,7 +530,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -547,7 +547,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -679,7 +679,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -702,7 +702,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 136;
+ CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index b775490..17c0aa5 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 136
+ 137
LSRequiresIPhoneOS
ITSAppUsesNonExemptEncryption
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index bd8342f..fc05a60 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,22 @@
-
+
-
+
-
+
-
+
diff --git a/lib/providers/sensors_provider.dart b/lib/providers/sensors_provider.dart
index 46b0024..83d611f 100644
--- a/lib/providers/sensors_provider.dart
+++ b/lib/providers/sensors_provider.dart
@@ -12,6 +12,30 @@ import 'contacts_provider.dart';
enum SensorRefreshState { idle, refreshing, success, timeout, unavailable }
+class SensorHistorySample {
+ final DateTime timestamp;
+ final Map values;
+
+ const SensorHistorySample({required this.timestamp, required this.values});
+
+ factory SensorHistorySample.fromJson(Map json) {
+ final rawValues = json['values'] as Map? ?? const {};
+ return SensorHistorySample(
+ timestamp: DateTime.fromMillisecondsSinceEpoch(
+ (json['timestampMillis'] as num?)?.toInt() ?? 0,
+ ),
+ values: rawValues.map(
+ (key, value) => MapEntry(key, (value as num).toDouble()),
+ ),
+ );
+ }
+
+ Map toJson() => {
+ 'timestampMillis': timestamp.millisecondsSinceEpoch,
+ 'values': values,
+ };
+}
+
class SensorsProvider with ChangeNotifier {
static const Duration _successStateRetention = Duration(minutes: 1);
static const Duration selfAutoRefreshInterval = Duration(seconds: 30);
@@ -21,6 +45,10 @@ class SensorsProvider with ChangeNotifier {
static const String _metricLabelKey = 'sensor_metric_labels';
static const String _metricOrderKey = 'sensor_metric_order';
static const String _autoRefreshMinutesKey = 'sensor_auto_refresh_minutes';
+ static const String _historyKey = 'sensor_history_v1';
+ static const String _telemetrySourceChannelPrefix = '__source_channel:';
+ static const String _rawTelemetryHexKey = '__raw_lpp_hex';
+ static const int _maxHistorySamplesPerSensor = 288;
static const List supportedAutoRefreshIntervals = [
0,
5,
@@ -62,6 +90,8 @@ class SensorsProvider with ChangeNotifier {
>{};
final Map _autoRefreshMinutesBySensor = {};
final Map _lastRefreshAttemptAt = {};
+ final Map> _historyBySensor =
+ >{};
bool _isLoaded = false;
bool _isRefreshingAll = false;
bool _isRunningAutoRefreshTick = false;
@@ -91,6 +121,7 @@ class SensorsProvider with ChangeNotifier {
final storedAutoRefreshJson = prefs.getString(
_key(_autoRefreshMinutesKey),
);
+ final storedHistoryJson = prefs.getString(_key(_historyKey));
_watchedSensorKeys
..clear()
..addAll(stored);
@@ -102,6 +133,7 @@ class SensorsProvider with ChangeNotifier {
_metricOrderBySensor.clear();
_autoRefreshMinutesBySensor.clear();
_lastRefreshAttemptAt.clear();
+ _historyBySensor.clear();
if (storedMetricsJson != null && storedMetricsJson.isNotEmpty) {
final decoded = jsonDecode(storedMetricsJson) as Map;
for (final entry in decoded.entries) {
@@ -146,6 +178,21 @@ class SensorsProvider with ChangeNotifier {
}
}
}
+ if (storedHistoryJson != null && storedHistoryJson.isNotEmpty) {
+ final decoded = jsonDecode(storedHistoryJson) as Map;
+ for (final entry in decoded.entries) {
+ final rawSamples = entry.value as List? ?? const [];
+ final samples = rawSamples
+ .whereType