diff options
author | Ludwig Schwiedrzik <ludwig.schwiedrzik@d-fine.com> | 2024-08-14 11:50:33 +0200 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2024-09-12 16:58:42 +0000 |
commit | 7ea2d37528da61ff40a50da5843397d51fc0e789 (patch) | |
tree | de3b9dcafda931aa3d0297c60bc2586c89e03782 /lib | |
parent | a2dcd701777968a65d3176eaf28aa7023d97c16b (diff) |
Integration of Storage API to flutter - Users, Units
Updated user and units notifiers to make use of persistent storage. New
users are stored with a name and id in the default namespace, and their
unit preferences (distance, temperature, tire pressure) are stored in
namespaces corresponding to each user's id.
Added new initialize_settings to load user and unit settings from
storage on startup. For first-time startup, default users with ids have
been added to storage as well.
Added unit tests for new user and unit handling.
Bug-AGL: [SPEC-5228]
Change-Id: I9cbcfc3ea5045dcb27db1b05e332f89abfc284a1
Signed-off-by: Tom Kronsbein <tom.kronsbein@d-fine.com>
Signed-off-by: Ludwig Schwiedrzik <ludwig.schwiedrzik@d-fine.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/core/constants/users_path.dart | 6 | ||||
-rw-r--r-- | lib/data/data_providers/app.dart | 6 | ||||
-rw-r--r-- | lib/data/data_providers/initialize_settings.dart | 12 | ||||
-rw-r--r-- | lib/data/data_providers/units_notifier.dart | 83 | ||||
-rw-r--r-- | lib/data/data_providers/users_notifier.dart | 128 | ||||
-rw-r--r-- | lib/data/models/users.dart | 2 | ||||
-rw-r--r-- | lib/export.dart | 1 | ||||
-rw-r--r-- | lib/main.dart | 27 |
8 files changed, 241 insertions, 24 deletions
diff --git a/lib/core/constants/users_path.dart b/lib/core/constants/users_path.dart new file mode 100644 index 0000000..47c2486 --- /dev/null +++ b/lib/core/constants/users_path.dart @@ -0,0 +1,6 @@ +class UsersPath { + static const String InfotainmentCurrentUser = + 'Infotainment.Users.selectedUser'; + static const String InfotainmentUsers = + 'Infotainment.Users'; +} diff --git a/lib/data/data_providers/app.dart b/lib/data/data_providers/app.dart index 3368a83..05e56f0 100644 --- a/lib/data/data_providers/app.dart +++ b/lib/data/data_providers/app.dart @@ -12,13 +12,11 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - return ProviderScope( - child: MaterialApp( + return MaterialApp( debugShowCheckedModeBanner: false, theme: theme, home: const AppView(), - ), - ); + ); } } diff --git a/lib/data/data_providers/initialize_settings.dart b/lib/data/data_providers/initialize_settings.dart new file mode 100644 index 0000000..b5a5e80 --- /dev/null +++ b/lib/data/data_providers/initialize_settings.dart @@ -0,0 +1,12 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +Future<void> initializeSettings(ProviderContainer container) async { + await container.read(usersProvider.notifier).loadSettingsUsers(); + await container.read(unitStateProvider.notifier).loadSettingsUnits(); + // Initialize other settings or providers if needed. +} + +Future<void> initializeSettingsUser(Ref ref) async { + await ref.read(unitStateProvider.notifier).loadSettingsUnits(); + // Initialize other settings or providers if needed. +} diff --git a/lib/data/data_providers/units_notifier.dart b/lib/data/data_providers/units_notifier.dart index 68c9e65..daf9c92 100644 --- a/lib/data/data_providers/units_notifier.dart +++ b/lib/data/data_providers/units_notifier.dart @@ -1,12 +1,53 @@ 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) { @@ -40,7 +81,7 @@ class UnitsNotifier extends Notifier<Units> { return handled; } - void setDistanceUnit(DistanceUnit unit) { + Future <void> setDistanceUnit(DistanceUnit unit) async { state = state.copyWith(distanceUnit: unit); var valClient = ref.read(valClientProvider); valClient.setString( @@ -48,9 +89,21 @@ class UnitsNotifier extends Notifier<Units> { 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( @@ -58,9 +111,21 @@ class UnitsNotifier extends Notifier<Units> { 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( @@ -68,5 +133,17 @@ class UnitsNotifier extends Notifier<Units> { 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'); + } } } diff --git a/lib/data/data_providers/users_notifier.dart b/lib/data/data_providers/users_notifier.dart index 8b48382..c2755f6 100644 --- a/lib/data/data_providers/users_notifier.dart +++ b/lib/data/data_providers/users_notifier.dart @@ -4,9 +4,65 @@ import 'package:uuid/uuid.dart'; 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() { @@ -18,27 +74,81 @@ class UsersNotifier extends StateNotifier<Users> { 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) { diff --git a/lib/data/models/users.dart b/lib/data/models/users.dart index 9b4d027..0bb4070 100644 --- a/lib/data/models/users.dart +++ b/lib/data/models/users.dart @@ -16,7 +16,7 @@ class Users { Users.initial() //: users = <User>[], : users = [], - selectedUser = const User(id: '', name: ''); + selectedUser = const User(id: '0', name: ''); Users copyWith({ List<User>? users, diff --git a/lib/export.dart b/lib/export.dart index 90ed196..be4026f 100644 --- a/lib/export.dart +++ b/lib/export.dart @@ -60,6 +60,7 @@ export 'presentation/screens/clock/clock.dart'; export 'core/utils/widgets/back_button.dart'; export 'core/constants/vss_path.dart'; +export 'core/constants/users_path.dart'; export 'core/constants/constants.dart'; //Common widgets export 'presentation/common_widget/settings_top_bar.dart'; diff --git a/lib/main.dart b/lib/main.dart index c7d84ee..0699f06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,27 @@ import 'package:device_preview/device_preview.dart'; import 'export.dart'; +import 'data/data_providers/initialize_settings.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - runApp(DevicePreview( - enabled: debugDisplay, - tools: const [ - ...DevicePreview.defaultTools, - ], - builder: (context) => const App(), - )); + + // Initialize settings from storage API. + final container = ProviderContainer(); + await initializeSettings(container); + + // Pass the container to ProviderScope and then run the app. + runApp( + ProviderScope( + parent: container, + child: DevicePreview( + enabled: debugDisplay, + tools: const [ + ...DevicePreview.defaultTools, + ], + builder: (context) => const App(), + ), + ), + ); } + |