Add wakelock plus package

This commit is contained in:
Janez T
2026-03-16 20:37:04 +01:00
parent 0e3acfa4ca
commit efbddc8831
7 changed files with 128 additions and 13 deletions

View File

@@ -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>(),

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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);