From 4ae68f5be11d110f2df10d54377d970921e30a21 Mon Sep 17 00:00:00 2001 From: Scott Murray Date: Sun, 17 Dec 2023 15:48:21 -0500 Subject: Implement audio settings Changes: - Rework KUKSA.val "VAL" gRPC API implementation to separate it from the vehicle model + notifier, and more easily allow using it from other notifiers. - Move volume handling from the vehicle model + notifier to the audio set for clarity. - Wire up the new VSS audio signals in the audio notifier. The "rearFront" variable naming has been changed to "fade" in several places to match expected terminology. - Add a balance slider to the audio settings page. - Change the min/max labels on the fade slider to be Text instead of Icon's since we do not have the equivalent to use with the balance slider, and text seems like it'd be what you would want for any potential future internationalization. - Rework configuration file to be usable from anywhere via a RiverPod Provider instead of tied to the vehicle notifier code, and shifted the background and hybrid animation flags to be handled with it. This change removes the built-in asset with defaults in favor of maintaining the defaults for the ICS environment in the AppConfig and KuksaConfig classes, with a goal of avoiding the need for using async methods in the config provider. - Change some notifiers from using StateNotifier to the RiverPod 2.0 Notifier class for improved flexibility. The other notifiers will be updated in future work. - Added select's to several ref.watches in the new hybrid animation code to avoid unnecessary repaints. - Fix several spelling issues in method and parameter names across the codebase. Bug-AGL: SPEC-5001 Change-Id: Iefae417fa870405d659303497d96e519e6b6d1de Signed-off-by: Scott Murray --- app-config/config.yaml | 3 - lib/core/constants/constants.dart | 4 +- lib/core/constants/val_client_helper.dart | 58 ---- lib/core/constants/vss_path.dart | 12 + lib/data/data_providers/app_config_provider.dart | 162 +++++++++++ lib/data/data_providers/app_provider.dart | 17 +- lib/data/data_providers/audio_notifier.dart | 129 ++++++++- lib/data/data_providers/hybrid_notifier.dart | 42 +-- lib/data/data_providers/val_client.dart | 118 +++++++++ lib/data/data_providers/vehicle_notifier.dart | 253 ++---------------- lib/data/models/audio.dart | 35 ++- lib/data/models/hybrid.dart | 2 +- lib/data/models/vehicle.dart | 139 +++++----- lib/export.dart | 3 +- lib/presentation/common_widget/generic_button.dart | 6 +- lib/presentation/common_widget/volume_bar.dart | 8 +- .../dashboard/widgets/dashboard_content.dart | 1 + .../screens/dashboard/widgets/hybrid/hybrid.dart | 16 +- .../screens/dashboard/widgets/hybrid_mode.dart | 3 +- lib/presentation/screens/home/home.dart | 2 + .../media_player/widgets/media_volume_bar.dart | 8 +- .../audio_settings/widget/audio_content.dart | 16 +- .../audio_settings/widget/slider_widgets.dart | 295 ++++++++++++++++----- .../bluetooth/widgets/bluetooth_content.dart | 2 +- .../profiles/widgets/new_profile_screen.dart | 2 +- .../profiles/widgets/profiles_content.dart | 4 +- .../screens/splash/widget/splash_content.dart | 12 +- pubspec.yaml | 1 - 28 files changed, 825 insertions(+), 528 deletions(-) delete mode 100644 app-config/config.yaml delete mode 100644 lib/core/constants/val_client_helper.dart create mode 100644 lib/data/data_providers/app_config_provider.dart create mode 100644 lib/data/data_providers/val_client.dart diff --git a/app-config/config.yaml b/app-config/config.yaml deleted file mode 100644 index dd3ae08..0000000 --- a/app-config/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -hostname: localhost -port: 55556 -use_tls: false diff --git a/lib/core/constants/constants.dart b/lib/core/constants/constants.dart index 03c1f8a..65aa680 100644 --- a/lib/core/constants/constants.dart +++ b/lib/core/constants/constants.dart @@ -9,8 +9,8 @@ const maxSpeed = 240.0; const maxRpm = 8000; final GlobalKey homeScaffoldKey = GlobalKey(); const debugDisplay = bool.fromEnvironment('DEBUG_DISPLAY'); -const disableBkgAnimation = bool.fromEnvironment('DISABLE_BKG_ANIMATION'); -const randomHybridAnimation = bool.fromEnvironment('RANDOM_HYBRID_ANIMATION'); +const disableBkgAnimationDefault = bool.fromEnvironment('DISABLE_BKG_ANIMATION'); +const randomHybridAnimationDefault = bool.fromEnvironment('RANDOM_HYBRID_ANIMATION'); diff --git a/lib/core/constants/val_client_helper.dart b/lib/core/constants/val_client_helper.dart deleted file mode 100644 index 60b3842..0000000 --- a/lib/core/constants/val_client_helper.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:protos/protos.dart'; - -class ValClientHelper { - final VALClient stub; - final String authorization; - - ValClientHelper({required this.stub, required this.authorization}); - - void setUint32(String path, int value, [bool actuator = true]) async { - var dp = Datapoint()..uint32 = value; - set(path, dp, actuator); - } - - void setInt32(String path, int value, [bool actuator = true]) async { - var dp = Datapoint()..int32 = value; - set(path, dp, actuator); - } - - void setBool(String path, bool value, [bool actuator = true]) async { - var dp = Datapoint()..bool_12 = value; - set(path, dp, actuator); - } - - void setString(String path, String value, [bool actuator = true]) async { - var dp = Datapoint()..string = value; - set(path, dp, actuator); - } - - void setFloat(String path, double value, [bool actuator = true]) async { - var dp = Datapoint()..float = value; - set(path, dp, actuator); - } - - void setDouble(String path, double value, [bool actuator = true]) async { - var dp = Datapoint()..double_18 = value; - set(path, dp, actuator); - } - - void set(String path, Datapoint dp, bool actuator) async { - var entry = DataEntry()..path = path; - var update = EntryUpdate(); - if (actuator) { - entry.actuatorTarget = dp; - update.fields.add(Field.FIELD_ACTUATOR_TARGET); - } else { - entry.value = dp; - update.fields.add(Field.FIELD_VALUE); - } - update.entry = entry; - var request = SetRequest(); - request.updates.add(update); - Map metadata = {}; - if (authorization.isNotEmpty) { - metadata = {'authorization': "Bearer $authorization"}; - } - await stub.set(request, options: CallOptions(metadata: metadata)); - } -} diff --git a/lib/core/constants/vss_path.dart b/lib/core/constants/vss_path.dart index 99f3d60..afba8de 100644 --- a/lib/core/constants/vss_path.dart +++ b/lib/core/constants/vss_path.dart @@ -11,6 +11,14 @@ class VSSPath { 'Vehicle.Powertrain.FuelSystem.RelativeLevel'; static const String vehicleMediaVolume = 'Vehicle.Cabin.Infotainment.Media.Volume'; + static const String vehicleMediaBalance = + 'Vehicle.Cabin.Infotainment.Media.Audio.Balance'; + static const String vehicleMediaFade = + 'Vehicle.Cabin.Infotainment.Media.Audio.Fade'; + static const String vehicleMediaBass = + 'Vehicle.Cabin.Infotainment.Media.Audio.Bass'; + static const String vehicleMediaTreble = + 'Vehicle.Cabin.Infotainment.Media.Audio.Treble'; static const String vehicleIsChildLockActiveLeft = 'Vehicle.Cabin.Door.Row2.DriverSide.IsChildLockActive'; static const String vehicleIsChildLockActiveRight = @@ -47,6 +55,10 @@ class VSSPath { vehicleRange, vehicleFuelLevel, vehicleMediaVolume, + vehicleMediaBalance, + vehicleMediaFade, + vehicleMediaBass, + vehicleMediaTreble, vehicleIsChildLockActiveLeft, vehicleIsChildLockActiveRight, vehicleFrontLeftTire, diff --git a/lib/data/data_providers/app_config_provider.dart b/lib/data/data_providers/app_config_provider.dart new file mode 100644 index 0000000..7e0ddc6 --- /dev/null +++ b/lib/data/data_providers/app_config_provider.dart @@ -0,0 +1,162 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_ics_homescreen/core/constants/constants.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yaml/yaml.dart'; + +class KuksaConfig { + final String hostname; + final int port; + final String authorization; + final bool use_tls; + final List ca_certificate; + final String tls_server_name; + + static String defaultHostname = 'localhost'; + static int defaultPort = 55555; + static String defaultCaCertPath = '/etc/kuksa-val/CA.pem'; + + KuksaConfig( + {required this.hostname, + required this.port, + required this.authorization, + required this.use_tls, + required this.ca_certificate, + required this.tls_server_name}); + + static KuksaConfig defaultConfig() { + return KuksaConfig( + hostname: KuksaConfig.defaultHostname, + port: KuksaConfig.defaultPort, + authorization: "", + use_tls: false, + ca_certificate: [], + tls_server_name: ""); + } +} + +class AppConfig { + final bool disableBkgAnimation; + final bool randomHybridAnimation; + final KuksaConfig kuksaConfig; + + static String configFilePath = '/etc/xdg/AGL/ics-homescreen.yaml'; + + AppConfig({required this.disableBkgAnimation, required this.randomHybridAnimation, required this.kuksaConfig}); + + static KuksaConfig parseKuksaConfig(YamlMap kuksaMap) { + try { + String hostname = KuksaConfig.defaultHostname; + if (kuksaMap.containsKey('hostname')) { + hostname = kuksaMap['hostname']; + } + + int port = KuksaConfig.defaultPort; + if (kuksaMap.containsKey('port')) { + port = kuksaMap['port']; + } + + String token = ""; + if (kuksaMap.containsKey('authorization')) { + String s = kuksaMap['authorization']; + if (s.isNotEmpty) { + if (s.startsWith("/")) { + debugPrint("Reading authorization token $s"); + try { + token = File(s).readAsStringSync(); + } on Exception catch (_) { + print("ERROR: Could not read authorization token file $token"); + token = ""; + } + } else { + token = s; + } + } + } + //debugPrint("authorization = $token"); + + bool use_tls = false; + if (kuksaMap.containsKey('use-tls')) { + var value = kuksaMap['use-tls']; + if (value is bool) use_tls = value; + } + //debugPrint("Use TLS = $use_tls"); + + List ca_cert = []; + String ca_path = KuksaConfig.defaultCaCertPath; + if (kuksaMap.containsKey('ca-certificate')) { + ca_path = kuksaMap['ca-certificate']; + } + try { + ca_cert = File(ca_path).readAsBytesSync(); + } on Exception catch (_) { + print("ERROR: Could not read CA certificate file $ca_path"); + ca_cert = []; + } + //debugPrint("CA cert = $ca_cert"); + + String tls_server_name = ""; + if (kuksaMap.containsKey('tls-server-name')) { + tls_server_name = kuksaMap['tls_server_name']; + } + + return KuksaConfig( + hostname: hostname, + port: port, + authorization: token, + use_tls: use_tls, + ca_certificate: ca_cert, + tls_server_name: tls_server_name); + } on Exception catch (_) { + return KuksaConfig.defaultConfig(); + } + } +} + +final appConfigProvider = Provider((ref) { + final configFile = File(AppConfig.configFilePath); + try { + print("Reading configuration ${AppConfig.configFilePath}"); + String content = configFile.readAsStringSync(); + final dynamic yamlMap = loadYaml(content); + + KuksaConfig kuksaConfig; + if (yamlMap.containsKey('kuksa')) { + kuksaConfig = AppConfig.parseKuksaConfig(yamlMap['kuksa']); + } else { + kuksaConfig = KuksaConfig( + hostname: KuksaConfig.defaultHostname, + port: KuksaConfig.defaultPort, + authorization: "", + use_tls: false, + ca_certificate: [], + tls_server_name: ""); + } + + bool disableBkgAnimation = disableBkgAnimationDefault; + if (yamlMap.containsKey('disable-bg-animation')) { + var value = yamlMap['disable-bg-animation']; + if (value is bool) { + disableBkgAnimation = value; + } + } + + bool randomHybridAnimation = randomHybridAnimationDefault; + if (yamlMap.containsKey('random-hybrid-animation')) { + var value = yamlMap['random-hybrid-animation']; + if (value is bool) { + randomHybridAnimation = value; + } + } + + return AppConfig( + disableBkgAnimation: disableBkgAnimation, + randomHybridAnimation: randomHybridAnimation, + kuksaConfig: kuksaConfig); + } on Exception catch (_) { + return AppConfig( + disableBkgAnimation: false, + randomHybridAnimation: false, + kuksaConfig: KuksaConfig.defaultConfig()); + } +}); diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart index fb0a447..cfda370 100644 --- a/lib/data/data_providers/app_provider.dart +++ b/lib/data/data_providers/app_provider.dart @@ -4,6 +4,7 @@ import 'package:flutter_ics_homescreen/data/data_providers/time_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/units_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/audio_notifier.dart'; import 'package:flutter_ics_homescreen/data/data_providers/users_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/val_client.dart'; import 'package:flutter_ics_homescreen/export.dart'; import '../models/users.dart'; @@ -36,9 +37,15 @@ enum AppState { } final appProvider = StateProvider((ref) => AppState.splash); -final vehicleProvider = StateNotifierProvider((ref) { - return VehicleNotifier(const Vehicle.initial()); + +final valClientProvider = Provider((ref) { + KuksaConfig config = ref.watch(appConfigProvider).kuksaConfig; + return ValClient(config: config, ref: ref); }); + +final vehicleProvider = + NotifierProvider(VehicleNotifier.new); + final signalsProvider = StateNotifierProvider((ref) { return SignalNotifier(const Signals.initial()); }); @@ -46,9 +53,9 @@ final signalsProvider = StateNotifierProvider((ref) { final unitStateProvider = StateNotifierProvider((ref) { return UnitsNotifier(const Units.initial()); }); -final audioStateProvider = StateNotifierProvider((ref) { - return AudioNotifier(const Audio.initial()); -}); + +final audioStateProvider = + NotifierProvider(AudioNotifier.new); final usersProvider = StateNotifierProvider((ref) { return UsersNotifier(Users.initial()); diff --git a/lib/data/data_providers/audio_notifier.dart b/lib/data/data_providers/audio_notifier.dart index 1f8c1a9..32ab409 100644 --- a/lib/data/data_providers/audio_notifier.dart +++ b/lib/data/data_providers/audio_notifier.dart @@ -1,22 +1,131 @@ import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/protos.dart'; -class AudioNotifier extends StateNotifier