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