import 'package:flutter_ics_homescreen/export.dart';
import 'package:protos/val_api.dart';
+import 'package:protos/storage-api.dart' as storage_api;
+import 'initialize_settings.dart';
+
class UnitsNotifier extends Notifier<Units> {
@override
Units build() {
return const Units.initial();
}
+ // Load Units state of the selected user from the storage API.
+ Future <void> loadSettingsUnits() async {
+ final storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+
+ try {
+ await initializeSettingsUser(ref);
+ } catch (e) {
+ print('Error loading settings of user: $e');
+ }
+
+ try {
+ // Read unit values from the selected user namespace.
+ final distanceResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiDistanceUnit, namespace: userClient.selectedUser.id));
+ final temperatureResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiTemperatureUnit, namespace: userClient.selectedUser.id));
+ final pressureResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiPressureUnit, namespace: userClient.selectedUser.id));
+
+ // Prepare state declaration and fall back to default values if the key is not present in the storage API.
+ final distanceUnit = distanceResponse.result == 'MILES'
+ ? DistanceUnit.miles
+ : DistanceUnit.kilometers;
+
+ final temperatureUnit = temperatureResponse.result == 'F'
+ ? TemperatureUnit.fahrenheit
+ : TemperatureUnit.celsius;
+
+ final pressureUnit = pressureResponse.result == 'PSI'
+ ? PressureUnit.psi
+ : PressureUnit.kilopascals;
+
+ state = Units(distanceUnit, temperatureUnit, pressureUnit);
+ } catch (e) {
+ // Fallback to initial defaults if error occurs.
+ print('Error loading settings for units: $e');
+ state = const Units.initial();
+ }
+ }
+
bool handleSignalUpdate(DataEntry entry) {
bool handled = true;
switch (entry.path) {
return handled;
}
- void setDistanceUnit(DistanceUnit unit) {
+ Future <void> setDistanceUnit(DistanceUnit unit) async {
state = state.copyWith(distanceUnit: unit);
var valClient = ref.read(valClientProvider);
valClient.setString(
unit == DistanceUnit.kilometers ? "KILOMETERS" : "MILES",
true,
);
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiDistanceUnit,
+ value: unit == DistanceUnit.kilometers ? 'KILOMETERS' : 'MILES',
+ namespace: userClient.selectedUser.id
+ ));
+ } catch (e) {
+ print('Error saving distance unit: $e');
+ }
}
- void setTemperatureUnit(TemperatureUnit unit) {
+ Future <void> setTemperatureUnit(TemperatureUnit unit) async {
state = state.copyWith(temperatureUnit: unit);
var valClient = ref.read(valClientProvider);
valClient.setString(
unit == TemperatureUnit.celsius ? "C" : "F",
true,
);
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiTemperatureUnit,
+ value: unit == TemperatureUnit.celsius ? "C" : "F",
+ namespace: userClient.selectedUser.id
+ ));
+ } catch (e) {
+ print('Error saving distance unit: $e');
+ }
}
- void setPressureUnit(PressureUnit unit) {
+ Future <void> setPressureUnit(PressureUnit unit) async {
state = state.copyWith(pressureUnit: unit);
var valClient = ref.read(valClientProvider);
valClient.setString(
unit == PressureUnit.kilopascals ? "KPA" : "PSI",
true,
);
+ // Write to storage API (to selected user namespace).
+ var storageClient = ref.read(storageClientProvider);
+ final userClient = ref.read(usersProvider);
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: VSSPath.vehicleHmiPressureUnit,
+ value: unit == PressureUnit.kilopascals ? "KPA" : "PSI",
+ namespace: userClient.selectedUser.id
+ ));
+ } catch (e) {
+ print('Error saving distance unit: $e');
+ }
}
}
import '../models/user.dart';
-class UsersNotifier extends StateNotifier<Users> {
- UsersNotifier(super.state) {
+import 'package:protos/storage-api.dart' as storage_api;
+import 'initialize_settings.dart';
+
+class UsersNotifier extends Notifier<Users> {
+ @override
+ Users build() {
+ // Initialize default state.
+ state = Users.initial();
loadUsers();
+ return state;
+ }
+
+ Future <void> loadSettingsUsers() async {
+ final storageClient = ref.read(storageClientProvider);
+ try {
+ // Access users branch.
+ final searchResponseUsers = await storageClient.search(storage_api.Key(key: UsersPath.InfotainmentUsers));
+ // Add default users if no users are inside the storage API.
+ if (searchResponseUsers.result.isEmpty) {
+ loadUsers();
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[0].id}.id', value: _users[0].id));
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[0].id}.name', value: _users[0].name));
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[1].id}.id', value: _users[1].id));
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[1].id}.name', value: _users[1].name));
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[2].id}.id', value: _users[2].id));
+ await storageClient.write(storage_api.KeyValue(key: '${UsersPath.InfotainmentUsers}.${_users[2].id}.name', value: _users[2].name));
+ await selectUser(_users[0].id);
+ }
+ else {
+ List<User> users = [];
+ List<String> idList = [];
+ // Get list of all ids.
+ for (var key in searchResponseUsers.result) {
+ var readResponse = await storageClient.read(storage_api.Key(key: key));
+ if (key.contains('.id')) {
+ idList.insert(0, readResponse.result);
+ }
+ }
+ // Extract names corresponding to ids.
+ for (var id in idList) {
+ var readResponse = await storageClient.read(storage_api.Key(key:'${UsersPath.InfotainmentUsers}.$id.name'));
+ users.insert(0, User(id: id, name: readResponse.result));
+ }
+ // Extract id of selected user.
+ final readResponseSelectedUser = await storageClient.read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ User selectedUser;
+ final userCurrentId = readResponseSelectedUser.result;
+ // Extract name of selected user.
+ final readResponseCurrentUserName = await storageClient.read(storage_api.Key(key: '${UsersPath.InfotainmentUsers}.$userCurrentId.name'));
+ final userCurrentName = readResponseCurrentUserName.result;
+ selectedUser = User(id: userCurrentId, name: userCurrentName);
+ state = Users(users: users, selectedUser: selectedUser);
+ }
+ } catch (e) {
+ // Fallback to initial defaults if error.
+ print('Error loading settings for units: $e');
+ loadUsers();
+ state = state.copyWith(selectedUser: _users[0]);
+ }
}
void loadUsers() {
const User(id: '2', name: 'George'),
const User(id: '3', name: 'Riley'),
];
- void selectUser(String userId) {
+
+ Future <void> selectUser(String userId) async {
+ final storageClient = ref.read(storageClientProvider);
var seletedUser = state.users.firstWhere((user) => user.id == userId);
state = state.copyWith(selectedUser: seletedUser);
+ // Write to storage API.
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: UsersPath.InfotainmentCurrentUser,
+ value: userId,
+ ));
+ } catch (e) {
+ print('Error saving user: $e');
+ }
+
+ try {
+ await initializeSettingsUser(ref);
+ } catch (e) {
+ print('Error loading settings of user: $e');
+ }
+
}
- void removeUser(String userId) {
+ Future <void> removeUser(String userId) async {
+ final storageClient = ref.read(storageClientProvider);
+ var currentUserId = state.selectedUser.id;
state.users.removeWhere((user) => user.id == userId);
- if (state.users.isNotEmpty) {
+
+ if (state.users.isNotEmpty && currentUserId == userId) {
state = state.copyWith(selectedUser: state.users.first);
+ //Write to API to change selected user.
+ await storageClient.write(storage_api.KeyValue(key: UsersPath.InfotainmentCurrentUser, value: state.users.first.id));
}
if (state.users.isEmpty) {
- state = state.copyWith(selectedUser: const User(id: '', name: ''));
+ state = state.copyWith(selectedUser: const User(id: '0', name: ''));
+ //Write to API to change selected user.
+ await storageClient.write(storage_api.KeyValue(key: UsersPath.InfotainmentCurrentUser, value: '0'));
+ }
+ // Delete from storage API.
+ try {
+ final searchResponse = await storageClient.search(storage_api.Key(key: userId));
+ final keyList = searchResponse.result;
+ //Delete id, name entries of the user from the default namespace.
+ for (final key in keyList) {
+ await storageClient.delete(storage_api.Key(
+ key: key
+ ));
+ }
+ //Delete all VSS keys from the user namespace.
+ await storageClient.deleteNodes(storage_api.Key(key: "Vehicle", namespace: userId));
+ } catch (e) {
+ print('Error removing user with id $userId: $e');
}
}
- void addUser(String userName) {
+ Future <void> addUser(String userName) async {
+ final storageClient = ref.read(storageClientProvider);
final id = const Uuid().v1();
final user = User(id: id, name: userName);
-
state.users.insert(0, user);
- state = state.copyWith(selectedUser: state.users.first);
+ // New user is automaticaly selected.
+ await selectUser(user.id);
+ // Write to storage API.
+ try {
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.$id.name',
+ value: userName
+ ));
+ await storageClient.write(storage_api.KeyValue(
+ key: '${UsersPath.InfotainmentUsers}.$id.id',
+ value: id
+ ));
+ } catch (e) {
+ print('Error adding user with id $id: $e');
+ }
}
void editUser(User user) {
--- /dev/null
+import 'package:flutter_test/flutter_test.dart';
+import 'package:protos/storage-api.dart' as storage_api;
+import 'package:flutter_ics_homescreen/export.dart';
+
+import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart';
+import 'package:flutter_ics_homescreen/data/data_providers/initialize_settings.dart';
+
+// Mock implementation of Ref if necessary.
+class MockRef extends Ref {
+ @override
+ dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+void main() {
+ late StorageClient storageClient;
+ late ProviderContainer container;
+
+ setUp(() {
+ storageClient = StorageClient(
+ config: StorageConfig.defaultConfig(),
+ ref: MockRef(),
+ );
+ container = ProviderContainer();
+ // Dispose container after each test.
+ addTearDown(container.dispose);
+ });
+
+ test('add User', () async {
+ await storageClient.destroyDB();
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ // Access state.
+ var userState = container.read(usersProvider);
+ final userId = userState.users[0].id;
+ final searchResponse = await storageClient.read(storage_api.Key(key: '${UsersPath.InfotainmentUsers}.$userId.name'));
+ expect(searchResponse.success, isTrue);
+ expect(searchResponse.result, 'Mark');
+ await storageClient.destroyDB();
+ });
+
+ test('remove User', () async {
+ await storageClient.destroyDB();
+ // Add User.
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ // Access state.
+ var userState = container.read(usersProvider);
+ final markId = userState.users[0].id;
+
+ // Remove User.
+ await userClient.removeUser(markId);
+ final searchResponse = await storageClient.search(storage_api.Key(key: markId));
+ expect(searchResponse.success, isTrue);
+ expect(searchResponse.result, []);
+ await storageClient.destroyDB();
+ });
+
+ test('add users, select user', () async {
+ await storageClient.destroyDB();
+ // Add Users.
+ final userClient = container.read(usersProvider.notifier);
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+
+ final readResponseAfterSelectUser = await storageClient.read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ expect(readResponseAfterSelectUser.success, isTrue);
+ expect(readResponseAfterSelectUser.result, markId);
+ await storageClient.destroyDB();
+ });
+
+ test('selected user default', () async {
+ await storageClient.destroyDB();
+
+ // Access state.
+ var userState = container.read(usersProvider);
+ final selectedId = userState.selectedUser.id;
+
+ final readResponse = await storageClient.read(storage_api.Key(key: UsersPath.InfotainmentCurrentUser));
+ expect(readResponse.success, isFalse);
+ expect(selectedId, '0');
+ await storageClient.destroyDB();
+ });
+
+ test('save Unit preference for a User', () async {
+ await storageClient.destroyDB();
+ // Add Users.
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ final readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiDistanceUnit, namespace: markId));
+ expect(readResponse.success, isTrue);
+ expect(readResponse.result, 'MILES');
+ await storageClient.destroyDB();
+ });
+
+ test('load settings: add users, selcect user, setDistanceUnit, kill state, initialize', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final markId = userState.users[1].id;
+ await userClient.selectUser(markId);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Clara');
+ expect(userState.users[1].name, 'Mark');
+ expect(userState.users.length, 5);
+ expect(userState.selectedUser.id, markId);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.selectedUser.id, markId);
+ List<String> userNames = userState.users.map((user) => user.name).toList();
+ expect(userNames.contains('Mark'), isTrue);
+ expect(userNames.contains('Clara'), isTrue);
+ expect(userState.users.length, 2);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ await storageClient.destroyDB();
+ });
+
+ test('loadsettings: add users, setDistanceUnit, kill state, initialize', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final claraId = userState.users[0].id;
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Clara');
+ expect(userState.users[1].name, 'Mark');
+ expect(userState.selectedUser.id, claraId);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.selectedUser.id, claraId);
+ List<String> userNames = userState.users.map((user) => user.name).toList();
+ expect(userNames.contains('Mark'), isTrue);
+ expect(userNames.contains('Clara'), isTrue);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+
+ await storageClient.destroyDB();
+ });
+
+ test('loadsettings: initialize, add no user, setDistanceUnit, kill state, inizialize', () async {
+ await storageClient.destroyDB();
+ await initializeSettings(container);
+
+ var userState = container.read(usersProvider);
+ var readResponse = await storageClient.read(storage_api.Key(key: '${UsersPath.InfotainmentUsers}.1.name'));
+ expect(readResponse.result, 'Heather');
+
+ final unitsClient = container.read(unitStateProvider.notifier);
+
+ // Prepare state.
+ userState = container.read(usersProvider);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+
+ // Check success.
+ var unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Heather');
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(userState.selectedUser.id, '1');
+
+ // Killing state.
+ container.dispose();
+ container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ // Check that state is killed.
+ userState = container.read(usersProvider);
+ unitState = container.read(unitStateProvider);
+ expect(userState.selectedUser.id, '0');
+ expect(unitState.distanceUnit, DistanceUnit.kilometers);
+
+ // Load state.
+ await initializeSettings(container);
+
+ // Check success.
+ unitState = container.read(unitStateProvider);
+ userState = container.read(usersProvider);
+ expect(userState.users[0].name, 'Heather');
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(userState.selectedUser.name, 'Heather');
+
+ await storageClient.destroyDB();
+ });
+
+ test('select user: load settings', () async {
+ await storageClient.destroyDB();
+
+ final userClient = container.read(usersProvider.notifier);
+ final unitsClient = container.read(unitStateProvider.notifier);
+ // Prepare state.
+ await userClient.addUser('Mark');
+ await userClient.addUser('Clara');
+ var userState = container.read(usersProvider);
+ final claraId = userState.users[0].id;
+ final markId = userState.users[1].id;
+ // Note: current user is Clara.
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+ await unitsClient.setTemperatureUnit(TemperatureUnit.fahrenheit);
+ await unitsClient.setPressureUnit(PressureUnit.psi);
+
+ await userClient.selectUser(markId);
+ await unitsClient.setDistanceUnit(DistanceUnit.miles);
+ await unitsClient.setTemperatureUnit(TemperatureUnit.celsius);
+ await unitsClient.setPressureUnit(PressureUnit.kilopascals);
+
+ await userClient.selectUser(claraId);
+ var unitState = container.read(unitStateProvider);
+ expect(unitState.distanceUnit, DistanceUnit.miles);
+ expect(unitState.temperatureUnit, TemperatureUnit.fahrenheit);
+ expect(unitState.pressureUnit, PressureUnit.psi);
+
+ var readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiDistanceUnit, namespace: claraId));
+ expect(readResponse.result, 'MILES');
+ readResponse = await storageClient.read(storage_api.Key(key: VSSPath.vehicleHmiTemperatureUnit, namespace: claraId));
+ expect(readResponse.result, 'F');
+ readResponse = await storageClient.read(storage_api.Key(key: VSSPath. vehicleHmiPressureUnit, namespace: claraId));
+ expect(readResponse.result, 'PSI');
+
+ await storageClient.destroyDB();
+ });
+}