mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add wakelock plus package
This commit is contained in:
@@ -365,6 +365,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
|||||||
connectionProvider: connectionProvider,
|
connectionProvider: connectionProvider,
|
||||||
contactsProvider: contactsProvider,
|
contactsProvider: contactsProvider,
|
||||||
messagesProvider: messagesProvider,
|
messagesProvider: messagesProvider,
|
||||||
|
sensorsProvider: context.read<SensorsProvider>(),
|
||||||
mapProvider: mapProvider,
|
mapProvider: mapProvider,
|
||||||
drawingProvider: context.read<DrawingProvider>(),
|
drawingProvider: context.read<DrawingProvider>(),
|
||||||
channelsProvider: context.read<ChannelsProvider>(),
|
channelsProvider: context.read<ChannelsProvider>(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
|
import '../services/profiles_feature_service.dart';
|
||||||
import '../widgets/sensors/sensor_telemetry_card.dart';
|
import '../widgets/sensors/sensor_telemetry_card.dart';
|
||||||
import 'connection_provider.dart';
|
import 'connection_provider.dart';
|
||||||
import 'contacts_provider.dart';
|
import 'contacts_provider.dart';
|
||||||
@@ -68,6 +69,8 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
unawaited(_loadWatchedSensors());
|
unawaited(_loadWatchedSensors());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _key(String baseKey) => ProfileStorageScope.scopedKey(baseKey);
|
||||||
|
|
||||||
List<String> get watchedSensorKeys => List.unmodifiable(_watchedSensorKeys);
|
List<String> get watchedSensorKeys => List.unmodifiable(_watchedSensorKeys);
|
||||||
bool get isLoaded => _isLoaded;
|
bool get isLoaded => _isLoaded;
|
||||||
bool get isRefreshingAll => _isRefreshingAll;
|
bool get isRefreshingAll => _isRefreshingAll;
|
||||||
@@ -78,20 +81,26 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
Future<void> _loadWatchedSensors() async {
|
Future<void> _loadWatchedSensors() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final stored = prefs.getStringList(_watchedSensorsKey) ?? <String>[];
|
final stored =
|
||||||
final storedMetricsJson = prefs.getString(_visibleSensorMetricsKey);
|
prefs.getStringList(_key(_watchedSensorsKey)) ?? <String>[];
|
||||||
final storedSpansJson = prefs.getString(_fieldSpanKey);
|
final storedMetricsJson = prefs.getString(_key(_visibleSensorMetricsKey));
|
||||||
final storedLabelsJson = prefs.getString(_metricLabelKey);
|
final storedSpansJson = prefs.getString(_key(_fieldSpanKey));
|
||||||
final storedOrderJson = prefs.getString(_metricOrderKey);
|
final storedLabelsJson = prefs.getString(_key(_metricLabelKey));
|
||||||
final storedAutoRefreshJson = prefs.getString(_autoRefreshMinutesKey);
|
final storedOrderJson = prefs.getString(_key(_metricOrderKey));
|
||||||
|
final storedAutoRefreshJson = prefs.getString(
|
||||||
|
_key(_autoRefreshMinutesKey),
|
||||||
|
);
|
||||||
_watchedSensorKeys
|
_watchedSensorKeys
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(stored);
|
..addAll(stored);
|
||||||
|
_refreshStates.clear();
|
||||||
|
_refreshStateUpdatedAt.clear();
|
||||||
_visibleFieldsBySensor.clear();
|
_visibleFieldsBySensor.clear();
|
||||||
_fieldSpansBySensor.clear();
|
_fieldSpansBySensor.clear();
|
||||||
_metricLabelsBySensor.clear();
|
_metricLabelsBySensor.clear();
|
||||||
_metricOrderBySensor.clear();
|
_metricOrderBySensor.clear();
|
||||||
_autoRefreshMinutesBySensor.clear();
|
_autoRefreshMinutesBySensor.clear();
|
||||||
|
_lastRefreshAttemptAt.clear();
|
||||||
if (storedMetricsJson != null && storedMetricsJson.isNotEmpty) {
|
if (storedMetricsJson != null && storedMetricsJson.isNotEmpty) {
|
||||||
final decoded = jsonDecode(storedMetricsJson) as Map<String, dynamic>;
|
final decoded = jsonDecode(storedMetricsJson) as Map<String, dynamic>;
|
||||||
for (final entry in decoded.entries) {
|
for (final entry in decoded.entries) {
|
||||||
@@ -159,10 +168,15 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> reloadProfileScopedState() async {
|
||||||
|
_isLoaded = false;
|
||||||
|
await _loadWatchedSensors();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _persistWatchedSensors() async {
|
Future<void> _persistWatchedSensors() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setStringList(_watchedSensorsKey, _watchedSensorKeys);
|
await prefs.setStringList(_key(_watchedSensorsKey), _watchedSensorKeys);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving watched sensors: $e');
|
debugPrint('Error saving watched sensors: $e');
|
||||||
}
|
}
|
||||||
@@ -175,7 +189,10 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
for (final entry in _visibleFieldsBySensor.entries) {
|
for (final entry in _visibleFieldsBySensor.entries) {
|
||||||
encoded[entry.key] = entry.value.toList();
|
encoded[entry.key] = entry.value.toList();
|
||||||
}
|
}
|
||||||
await prefs.setString(_visibleSensorMetricsKey, jsonEncode(encoded));
|
await prefs.setString(
|
||||||
|
_key(_visibleSensorMetricsKey),
|
||||||
|
jsonEncode(encoded),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving visible sensor metrics: $e');
|
debugPrint('Error saving visible sensor metrics: $e');
|
||||||
}
|
}
|
||||||
@@ -184,7 +201,10 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
Future<void> _persistFieldSpans() async {
|
Future<void> _persistFieldSpans() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString(_fieldSpanKey, jsonEncode(_fieldSpansBySensor));
|
await prefs.setString(
|
||||||
|
_key(_fieldSpanKey),
|
||||||
|
jsonEncode(_fieldSpansBySensor),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving sensor field spans: $e');
|
debugPrint('Error saving sensor field spans: $e');
|
||||||
}
|
}
|
||||||
@@ -193,7 +213,10 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
Future<void> _persistMetricLabels() async {
|
Future<void> _persistMetricLabels() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString(_metricLabelKey, jsonEncode(_metricLabelsBySensor));
|
await prefs.setString(
|
||||||
|
_key(_metricLabelKey),
|
||||||
|
jsonEncode(_metricLabelsBySensor),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving sensor metric labels: $e');
|
debugPrint('Error saving sensor metric labels: $e');
|
||||||
}
|
}
|
||||||
@@ -202,7 +225,10 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
Future<void> _persistMetricOrder() async {
|
Future<void> _persistMetricOrder() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString(_metricOrderKey, jsonEncode(_metricOrderBySensor));
|
await prefs.setString(
|
||||||
|
_key(_metricOrderKey),
|
||||||
|
jsonEncode(_metricOrderBySensor),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving sensor metric order: $e');
|
debugPrint('Error saving sensor metric order: $e');
|
||||||
}
|
}
|
||||||
@@ -212,7 +238,7 @@ class SensorsProvider with ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString(
|
await prefs.setString(
|
||||||
_autoRefreshMinutesKey,
|
_key(_autoRefreshMinutesKey),
|
||||||
jsonEncode(_autoRefreshMinutesBySensor),
|
jsonEncode(_autoRefreshMinutesBySensor),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -597,6 +597,9 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.watch<AppProvider>();
|
context.watch<AppProvider>();
|
||||||
|
final activeProfileId = context.select<ProfileManager, String>(
|
||||||
|
(manager) => manager.activeProfileId,
|
||||||
|
);
|
||||||
|
|
||||||
final enabledTabs = _enabledTabs;
|
final enabledTabs = _enabledTabs;
|
||||||
final isMapTabActive = _currentTab == _HomeTab.map;
|
final isMapTabActive = _currentTab == _HomeTab.map;
|
||||||
@@ -844,6 +847,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
);
|
);
|
||||||
case _HomeTab.map:
|
case _HomeTab.map:
|
||||||
return MapTab(
|
return MapTab(
|
||||||
|
key: ValueKey<String>('map:$activeProfileId'),
|
||||||
onFullscreenChanged: (isFullscreen) {
|
onFullscreenChanged: (isFullscreen) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isMapFullscreen = isFullscreen;
|
_isMapFullscreen = isFullscreen;
|
||||||
|
|||||||
@@ -1965,7 +1965,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
? _customMapMaxZoom
|
? _customMapMaxZoom
|
||||||
: _currentLayer.maxZoom,
|
: _currentLayer.maxZoom,
|
||||||
cameraConstraint: isCustomMapMode
|
cameraConstraint: isCustomMapMode
|
||||||
? CameraConstraint.contain(
|
? CameraConstraint.containCenter(
|
||||||
bounds: customMapConfig.displayBounds,
|
bounds: customMapConfig.displayBounds,
|
||||||
)
|
)
|
||||||
: const CameraConstraint.unconstrained(),
|
: const CameraConstraint.unconstrained(),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import '../providers/contacts_provider.dart';
|
|||||||
import '../providers/drawing_provider.dart';
|
import '../providers/drawing_provider.dart';
|
||||||
import '../providers/map_provider.dart';
|
import '../providers/map_provider.dart';
|
||||||
import '../providers/messages_provider.dart';
|
import '../providers/messages_provider.dart';
|
||||||
|
import '../providers/sensors_provider.dart';
|
||||||
import 'app_config_snapshot_service.dart';
|
import 'app_config_snapshot_service.dart';
|
||||||
import 'contact_storage_service.dart';
|
import 'contact_storage_service.dart';
|
||||||
import 'device_config_applicator.dart';
|
import 'device_config_applicator.dart';
|
||||||
@@ -27,6 +28,7 @@ class ProfileWorkspaceCoordinator {
|
|||||||
required this.connectionProvider,
|
required this.connectionProvider,
|
||||||
required this.contactsProvider,
|
required this.contactsProvider,
|
||||||
required this.messagesProvider,
|
required this.messagesProvider,
|
||||||
|
required this.sensorsProvider,
|
||||||
required this.mapProvider,
|
required this.mapProvider,
|
||||||
required this.drawingProvider,
|
required this.drawingProvider,
|
||||||
required this.channelsProvider,
|
required this.channelsProvider,
|
||||||
@@ -51,6 +53,7 @@ class ProfileWorkspaceCoordinator {
|
|||||||
final ConnectionProvider connectionProvider;
|
final ConnectionProvider connectionProvider;
|
||||||
final ContactsProvider contactsProvider;
|
final ContactsProvider contactsProvider;
|
||||||
final MessagesProvider messagesProvider;
|
final MessagesProvider messagesProvider;
|
||||||
|
final SensorsProvider sensorsProvider;
|
||||||
final MapProvider mapProvider;
|
final MapProvider mapProvider;
|
||||||
final DrawingProvider drawingProvider;
|
final DrawingProvider drawingProvider;
|
||||||
final ChannelsProvider channelsProvider;
|
final ChannelsProvider channelsProvider;
|
||||||
@@ -280,6 +283,7 @@ class ProfileWorkspaceCoordinator {
|
|||||||
namespace: ProfileStorageScope.effectiveNamespace,
|
namespace: ProfileStorageScope.effectiveNamespace,
|
||||||
devicePublicKey: connectionProvider.deviceInfo.publicKey,
|
devicePublicKey: connectionProvider.deviceInfo.publicKey,
|
||||||
);
|
);
|
||||||
|
await sensorsProvider.reloadProfileScopedState();
|
||||||
await drawingProvider.reloadProfileScopedState();
|
await drawingProvider.reloadProfileScopedState();
|
||||||
await mapProvider.reloadProfileScopedState();
|
await mapProvider.reloadProfileScopedState();
|
||||||
await appProvider.reloadProfileScopedSettings();
|
await appProvider.reloadProfileScopedSettings();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:meshcore_sar_app/models/device_info.dart';
|
|||||||
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/sensors_provider.dart';
|
import 'package:meshcore_sar_app/providers/sensors_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/services/profiles_feature_service.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class _FakeContactsProvider extends ContactsProvider {
|
class _FakeContactsProvider extends ContactsProvider {
|
||||||
@@ -50,6 +51,13 @@ class _FakeConnectionProvider extends ConnectionProvider {
|
|||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
ProfileStorageScope.setScope(
|
||||||
|
profilesEnabled: false,
|
||||||
|
activeProfileId: 'default',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Future<void> waitUntilLoaded(SensorsProvider provider) async {
|
Future<void> waitUntilLoaded(SensorsProvider provider) async {
|
||||||
for (var i = 0; i < 20 && !provider.isLoaded; i++) {
|
for (var i = 0; i < 20 && !provider.isLoaded; i++) {
|
||||||
await Future<void>.delayed(Duration.zero);
|
await Future<void>.delayed(Duration.zero);
|
||||||
@@ -194,6 +202,68 @@ void main() {
|
|||||||
expect(reloadedProvider.autoRefreshMinutesFor(contact.publicKeyHex), 1440);
|
expect(reloadedProvider.autoRefreshMinutesFor(contact.publicKeyHex), 1440);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('watched sensors and preferences are isolated per profile', () async {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
final contact = buildSensorContact();
|
||||||
|
|
||||||
|
ProfileStorageScope.setScope(
|
||||||
|
profilesEnabled: false,
|
||||||
|
activeProfileId: 'default',
|
||||||
|
);
|
||||||
|
final defaultProvider = SensorsProvider();
|
||||||
|
await waitUntilLoaded(defaultProvider);
|
||||||
|
await defaultProvider.addSensor(contact);
|
||||||
|
await defaultProvider.setMetricLabel(
|
||||||
|
contact.publicKeyHex,
|
||||||
|
'voltage',
|
||||||
|
'Default Voltage',
|
||||||
|
);
|
||||||
|
|
||||||
|
ProfileStorageScope.setScope(
|
||||||
|
profilesEnabled: true,
|
||||||
|
activeProfileId: 'alpha',
|
||||||
|
);
|
||||||
|
final customProvider = SensorsProvider();
|
||||||
|
await waitUntilLoaded(customProvider);
|
||||||
|
|
||||||
|
expect(customProvider.watchedSensorKeys, isEmpty);
|
||||||
|
expect(
|
||||||
|
customProvider.labelOverrideFor(contact.publicKeyHex, 'voltage'),
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
|
||||||
|
await customProvider.addSensor(contact);
|
||||||
|
await customProvider.setMetricLabel(
|
||||||
|
contact.publicKeyHex,
|
||||||
|
'voltage',
|
||||||
|
'Alpha Voltage',
|
||||||
|
);
|
||||||
|
|
||||||
|
ProfileStorageScope.setScope(
|
||||||
|
profilesEnabled: false,
|
||||||
|
activeProfileId: 'default',
|
||||||
|
);
|
||||||
|
final reloadedDefaultProvider = SensorsProvider();
|
||||||
|
await waitUntilLoaded(reloadedDefaultProvider);
|
||||||
|
expect(reloadedDefaultProvider.watchedSensorKeys, [contact.publicKeyHex]);
|
||||||
|
expect(
|
||||||
|
reloadedDefaultProvider.labelOverrideFor(contact.publicKeyHex, 'voltage'),
|
||||||
|
'Default Voltage',
|
||||||
|
);
|
||||||
|
|
||||||
|
ProfileStorageScope.setScope(
|
||||||
|
profilesEnabled: true,
|
||||||
|
activeProfileId: 'alpha',
|
||||||
|
);
|
||||||
|
final reloadedCustomProvider = SensorsProvider();
|
||||||
|
await waitUntilLoaded(reloadedCustomProvider);
|
||||||
|
expect(reloadedCustomProvider.watchedSensorKeys, [contact.publicKeyHex]);
|
||||||
|
expect(
|
||||||
|
reloadedCustomProvider.labelOverrideFor(contact.publicKeyHex, 'voltage'),
|
||||||
|
'Alpha Voltage',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'unsupported auto refresh minutes normalize to nearest option',
|
'unsupported auto refresh minutes normalize to nearest option',
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
|||||||
import 'package:meshcore_sar_app/providers/drawing_provider.dart';
|
import 'package:meshcore_sar_app/providers/drawing_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/map_provider.dart';
|
import 'package:meshcore_sar_app/providers/map_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/messages_provider.dart';
|
import 'package:meshcore_sar_app/providers/messages_provider.dart';
|
||||||
|
import 'package:meshcore_sar_app/providers/sensors_provider.dart';
|
||||||
import 'package:meshcore_sar_app/services/app_config_snapshot_service.dart';
|
import 'package:meshcore_sar_app/services/app_config_snapshot_service.dart';
|
||||||
import 'package:meshcore_sar_app/services/contact_storage_service.dart';
|
import 'package:meshcore_sar_app/services/contact_storage_service.dart';
|
||||||
import 'package:meshcore_sar_app/services/device_config_applicator.dart';
|
import 'package:meshcore_sar_app/services/device_config_applicator.dart';
|
||||||
@@ -196,6 +197,7 @@ ProfileWorkspaceCoordinator _buildCoordinator({
|
|||||||
connectionProvider: connectionProvider ?? _FakeConnectionProvider(),
|
connectionProvider: connectionProvider ?? _FakeConnectionProvider(),
|
||||||
contactsProvider: _FakeContactsProvider(),
|
contactsProvider: _FakeContactsProvider(),
|
||||||
messagesProvider: _FakeMessagesProvider(),
|
messagesProvider: _FakeMessagesProvider(),
|
||||||
|
sensorsProvider: _FakeSensorsProvider(),
|
||||||
mapProvider: _FakeMapProvider(),
|
mapProvider: _FakeMapProvider(),
|
||||||
drawingProvider: _FakeDrawingProvider(),
|
drawingProvider: _FakeDrawingProvider(),
|
||||||
channelsProvider: _FakeChannelsProvider(),
|
channelsProvider: _FakeChannelsProvider(),
|
||||||
@@ -319,6 +321,14 @@ class _FakeDrawingProvider implements DrawingProvider {
|
|||||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FakeSensorsProvider implements SensorsProvider {
|
||||||
|
@override
|
||||||
|
Future<void> reloadProfileScopedState() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
class _FakeChannelsProvider implements ChannelsProvider {
|
class _FakeChannelsProvider implements ChannelsProvider {
|
||||||
@override
|
@override
|
||||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
|||||||
Reference in New Issue
Block a user