diff options
28 files changed, 825 insertions, 528 deletions
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<ScaffoldState> 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<String, String> 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<int> 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<int> 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<AppState>((ref) => AppState.splash); -final vehicleProvider = StateNotifierProvider<VehicleNotifier, Vehicle>((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, Vehicle>(VehicleNotifier.new); + final signalsProvider = StateNotifierProvider<SignalNotifier, Signals>((ref) { return SignalNotifier(const Signals.initial()); }); @@ -46,9 +53,9 @@ final signalsProvider = StateNotifierProvider<SignalNotifier, Signals>((ref) { final unitStateProvider = StateNotifierProvider<UnitsNotifier, Units>((ref) { return UnitsNotifier(const Units.initial()); }); -final audioStateProvider = StateNotifierProvider<AudioNotifier, Audio>((ref) { - return AudioNotifier(const Audio.initial()); -}); + +final audioStateProvider = + NotifierProvider<AudioNotifier, Audio>(AudioNotifier.new); final usersProvider = StateNotifierProvider<UsersNotifier, Users>((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<Audio> { - AudioNotifier(super.state); +class AudioNotifier extends Notifier<Audio> { + @override + Audio build() { + return Audio.initial(); + } void resetToDefaults() { - state = state.copyWith(treble: 5.0, bass: 5.0, rearFront: 5.0); + state = state.copyWith(balance: 5.0, fade: 5.0, treble: 5.0, bass: 5.0); + var valClient = ref.read(valClientProvider); + int value = (state.balance * 20).toInt() - 100; + valClient.setInt32( + VSSPath.vehicleMediaBalance, + value, + true, + ); + value = (state.fade * 20).toInt() - 100; + valClient.setInt32( + VSSPath.vehicleMediaFade, + value, + true, + ); + value = (state.treble * 20).toInt() - 100; + valClient.setInt32( + VSSPath.vehicleMediaTreble, + value, + true, + ); + value = (state.bass * 20).toInt() - 100; + valClient.setInt32( + VSSPath.vehicleMediaBass, + value, + true, + ); } - void setTreble(double newVal) { - state = state.copyWith(treble: newVal); + bool handleSignalsUpdate(EntryUpdate update) { + bool handled = true; + switch (update.entry.path) { + case VSSPath.vehicleMediaVolume: + if (update.entry.value.hasUint32()) { + double value = update.entry.value.uint32.toDouble(); + state = state.copyWith(volume: value); + } + break; + case VSSPath.vehicleMediaBalance: + if (update.entry.value.hasInt32()) { + double value = (update.entry.value.int32 + 100) / 20.0; + state = state.copyWith(balance: value); + } + break; + case VSSPath.vehicleMediaFade: + if (update.entry.value.hasInt32()) { + double value = (update.entry.value.int32 + 100) / 20.0; + state = state.copyWith(fade: value); + } + break; + case VSSPath.vehicleMediaTreble: + if (update.entry.value.hasInt32()) { + double value = (update.entry.value.int32 + 100) / 20.0; + state = state.copyWith(treble: value); + } + break; + case VSSPath.vehicleMediaBass: + if (update.entry.value.hasInt32()) { + double value = (update.entry.value.int32 + 100) / 20.0; + state = state.copyWith(bass: value); + } + break; + default: + handled = false; + } + return handled; } - void setBass(double newVal) { - state = state.copyWith(bass: newVal); + void setVolume(double newValue) { + state = state.copyWith(volume: newValue); + var valClient = ref.read(valClientProvider); + valClient.setUint32( + VSSPath.vehicleMediaVolume, + newValue.toInt(), + true, + ); } - void setRearFront(double newVal) { - state = state.copyWith(rearFront: newVal); + void setBalance(double newValue) { + state = state.copyWith(balance: newValue); + int value = (newValue * 20).toInt() - 100; + var valClient = ref.read(valClientProvider); + valClient.setInt32( + VSSPath.vehicleMediaBalance, + value, + true, + ); + } + + void setFade(double newValue) { + state = state.copyWith(fade: newValue); + int value = (newValue * 20).toInt() - 100; + var valClient = ref.read(valClientProvider); + valClient.setInt32( + VSSPath.vehicleMediaFade, + value, + true, + ); } -} + void setTreble(double newValue) { + state = state.copyWith(treble: newValue); + int value = (newValue * 20).toInt() - 100; + var valClient = ref.read(valClientProvider); + valClient.setInt32( + VSSPath.vehicleMediaTreble, + value, + true, + ); + } + + void setBass(double newValue) { + state = state.copyWith(bass: newValue); + int value = (newValue * 20).toInt() - 100; + var valClient = ref.read(valClientProvider); + valClient.setInt32( + VSSPath.vehicleMediaBass, + value, + true, + ); + } +} diff --git a/lib/data/data_providers/hybrid_notifier.dart b/lib/data/data_providers/hybrid_notifier.dart index 3668f61..7381f5c 100644 --- a/lib/data/data_providers/hybrid_notifier.dart +++ b/lib/data/data_providers/hybrid_notifier.dart @@ -1,6 +1,6 @@ // ignore_for_file: unused_local_variable -import '../../export.dart'; +import 'package:flutter_ics_homescreen/export.dart'; class HybridNotifier extends StateNotifier<Hybrid> { HybridNotifier(super.state); @@ -9,38 +9,44 @@ class HybridNotifier extends StateNotifier<Hybrid> { switch (hybridState) { case HybridState.idle: state = state.copyWith( - topArrowState: ArrowState.blue, - leftArrowState: ArrowState.blue, - rightArrowState: ArrowState.blue, - batteryState: BatteryState.white, + hybridState: hybridState, + topArrowState: ArrowState.blue, + leftArrowState: ArrowState.blue, + rightArrowState: ArrowState.blue, + batteryState: BatteryState.white, ); break; case HybridState.engineOutput: state = state.copyWith( - topArrowState: ArrowState.red, - leftArrowState: ArrowState.red, - rightArrowState: ArrowState.blue, - batteryState: BatteryState.red, + hybridState: hybridState, + topArrowState: ArrowState.red, + leftArrowState: ArrowState.red, + rightArrowState: ArrowState.blue, + batteryState: BatteryState.red, ); break; case HybridState.regenerativeBreaking: state = state.copyWith( + hybridState: hybridState, topArrowState: ArrowState.blue, leftArrowState: ArrowState.blue, rightArrowState: ArrowState.green, - batteryState: BatteryState.green); - + batteryState: BatteryState.green + ); break; - case HybridState.baterryOutput: + case HybridState.batteryOutput: state = state.copyWith( + hybridState: hybridState, topArrowState: ArrowState.blue, leftArrowState: ArrowState.blue, rightArrowState: ArrowState.yellow, - batteryState: BatteryState.yellow); + batteryState: BatteryState.yellow + ); break; default: + state = state.copyWith(hybridState: hybridState); + break; } - state = state.copyWith(hybridState: hybridState); } void updateHybridState(double speed, double engineSpeed, bool brake) { @@ -55,25 +61,23 @@ class HybridNotifier extends StateNotifier<Hybrid> { // Variable for storing the average value of RPM double avgRpm = 0.0; - if (speed == 0 && engineSpeed == 0) { // Set idle state. currentState = HybridState.idle; } else if (engineSpeed > 0 && speed > 0) { - // Set stan na engine output state.. + // Set engine output state.. currentState = HybridState.engineOutput; } else if (speed < 0 && brake) { - // Set regenerative breaking state + // Set regenerative breaking state currentState = HybridState.regenerativeBreaking; } else if (speed > 0 && engineSpeed <= 0) { // Set battery output state - currentState = HybridState.baterryOutput; + currentState = HybridState.batteryOutput; } // Update hybrid state if (currentState != previousState) { - //state = state.copyWith(hybridState: currentState); setHybridState(currentState); } } diff --git a/lib/data/data_providers/val_client.dart b/lib/data/data_providers/val_client.dart new file mode 100644 index 0000000..28bb480 --- /dev/null +++ b/lib/data/data_providers/val_client.dart @@ -0,0 +1,118 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/protos.dart'; + +class ValClient { + final KuksaConfig config; + final Ref ref; + late ClientChannel channel; + late VALClient stub; + late String authorization; + + ValClient({required this.config, required this.ref}) { + debugPrint("Using ${config.hostname}:${config.port}"); + ChannelCredentials creds; + if (config.use_tls && config.ca_certificate.isNotEmpty) { + print("Using TLS"); + if (config.tls_server_name.isNotEmpty) { + creds = ChannelCredentials.secure( + certificates: config.ca_certificate, + authority: config.tls_server_name); + } else { + creds = ChannelCredentials.secure(certificates: config.ca_certificate); + } + } else { + creds = const ChannelCredentials.insecure(); + } + channel = ClientChannel(config.hostname, + port: config.port, options: ChannelOptions(credentials: creds)); + debugPrint('Start Listen on port: ${config.port}'); + stub = VALClient(channel); + } + + void startListen() async { + List<String> fewSignals = VSSPath().getSignalsList(); + var request = SubscribeRequest(); + Map<String, String> metadata = {}; + if (config.authorization.isNotEmpty) { + metadata = {'authorization': "Bearer ${config.authorization}"}; + } + for (int i = 0; i < fewSignals.length; i++) { + var entry = SubscribeEntry(); + entry.path = fewSignals[i]; + entry.fields.add(Field.FIELD_PATH); + entry.fields.add(Field.FIELD_VALUE); + request.entries.add(entry); + } + try { + var responseStream = + stub.subscribe(request, options: CallOptions(metadata: metadata)); + responseStream.listen((value) async { + for (var update in value.updates) { + if (!(update.hasEntry() && update.entry.hasPath())) continue; + handleSignalsUpdate(update); + } + }, onError: (stacktrace, errorDescriptor) { + debugPrint(stacktrace.toString()); + }); + } catch (e) { + debugPrint(e.toString()); + } + } + + bool handleSignalsUpdate(EntryUpdate update) { + if (ref.read(vehicleProvider.notifier).handleSignalsUpdate(update)) { + return true; + } + return ref.read(audioStateProvider.notifier).handleSignalsUpdate(update); + } + + 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<String, String> metadata = {}; + if (config.authorization.isNotEmpty) { + metadata = {'authorization': "Bearer ${config.authorization}"}; + } + await stub.set(request, options: CallOptions(metadata: metadata)); + } +} diff --git a/lib/data/data_providers/vehicle_notifier.dart b/lib/data/data_providers/vehicle_notifier.dart index 3a385a3..78c5328 100644 --- a/lib/data/data_providers/vehicle_notifier.dart +++ b/lib/data/data_providers/vehicle_notifier.dart @@ -3,43 +3,20 @@ import 'dart:async'; import 'package:flutter_ics_homescreen/export.dart'; -import 'package:flutter/services.dart'; import 'package:protos/protos.dart'; -class KuksaConfig { - final String hostname; - final int port; - final String authorization; - final bool use_tls; - final List<int> ca_certificate; - final String tls_server_name; - - static String configFilePath = '/etc/xdg/AGL/ics-homescreen.yaml'; - 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}); -} - -class VehicleNotifier extends StateNotifier<Vehicle> { - VehicleNotifier(super.state); - - late ClientChannel channel; - late String authorization; - late VALClient stub; +class VehicleNotifier extends Notifier<Vehicle> { + @override + Vehicle build() { + return Vehicle.initial(); + } void updateSpeed(double newValue) { state = state.copyWith(speed: newValue); } - void handleSignalsUpdate(EntryUpdate update) { + bool handleSignalsUpdate(EntryUpdate update) { + bool handled = true; switch (update.entry.path) { case VSSPath.vehicleSpeed: if (update.entry.value.hasFloat()) { @@ -66,11 +43,6 @@ class VehicleNotifier extends StateNotifier<Vehicle> { state = state.copyWith(fuelLevel: update.entry.value.uint32); } break; - case VSSPath.vehicleMediaVolume: - if (update.entry.value.hasUint32()) { - state = state.copyWith(mediaVolume: update.entry.value.uint32); - } - break; case VSSPath.vehicleIsChildLockActiveLeft: if (update.entry.value.hasBool_12()) { state = @@ -108,7 +80,6 @@ class VehicleNotifier extends StateNotifier<Vehicle> { state = state.copyWith(rearRightTire: update.entry.value.uint32); } break; - case VSSPath.vehicleIsAirConditioningActive: if (update.entry.value.hasBool_12()) { state = state.copyWith( @@ -142,8 +113,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { fanSpeed = 3; else if (value > 33) fanSpeed = 2; - else if (value > 0) - fanSpeed = 1; + else if (value > 0) fanSpeed = 1; state = state.copyWith(fanSpeed: fanSpeed); } break; @@ -158,183 +128,18 @@ class VehicleNotifier extends StateNotifier<Vehicle> { state.copyWith(passengerTemperature: update.entry.value.int32); } break; - // default: - // debugPrint("ERROR: Unexpected path ${update.entry.path}"); - // break; - } - } - - Future<KuksaConfig> readConfig() async { - String hostname = KuksaConfig.defaultHostname; - int port = KuksaConfig.defaultPort; - bool useTls = false; - String caPath = KuksaConfig.defaultCaCertPath; - List<int> caCert = []; - String tlsServerName = ""; - String token = ""; - - // Read build time configuration from bundle - try { - var data = await rootBundle.loadString('app-config/config.yaml'); - final dynamic yamlMap = loadYaml(data); - - if (yamlMap.containsKey('hostname')) { - hostname = yamlMap['hostname']; - } - - if (yamlMap.containsKey('port')) { - port = yamlMap['port']; - } - - if (yamlMap.containsKey('use-tls')) { - var value = yamlMap['use-tls']; - if (value is bool) { - useTls = value; - } - } - - if (useTls) { - if (yamlMap.containsKey('ca-certificate')) { - caPath = yamlMap['ca-certificate']; - } - - if (yamlMap.containsKey('tls-server-name')) { - tlsServerName = yamlMap['tls_server_name']; - } - } - - if (yamlMap.containsKey('authorization')) { - token = yamlMap['authorization']; - } - } catch (e) { - //debugPrint('ERROR: Could not read from file: $configFile'); - debugPrint(e.toString()); - } - - // Try reading from configuration file in /etc - final configFile = File(KuksaConfig.configFilePath); - try { - print("Reading configuration ${KuksaConfig.configFilePath}"); - String content = await configFile.readAsString(); - final dynamic yamlMap = loadYaml(content); - - if (yamlMap.containsKey('hostname')) { - hostname = yamlMap['hostname']; - } - - if (yamlMap.containsKey('port')) { - port = yamlMap['port']; - } - - if (yamlMap.containsKey('use-tls')) { - var value = yamlMap['use-tls']; - if (value is bool) { - useTls = value; - } - } - //debugPrint("Use TLS = $use_tls"); - - if (useTls) { - if (yamlMap.containsKey('ca-certificate')) { - caPath = yamlMap['ca-certificate']; - } - try { - caCert = File(caPath).readAsBytesSync(); - } on Exception catch (_) { - print("ERROR: Could not read CA certificate file $caPath"); - caCert = []; - } - //debugPrint("CA cert = $ca_cert"); - - if (yamlMap.containsKey('tls-server-name')) { - tlsServerName = yamlMap['tls_server_name']; - } - } - - if (yamlMap.containsKey('authorization')) { - token = yamlMap['authorization']; - } - if (token.isNotEmpty) { - if (token.startsWith("/")) { - debugPrint("Reading authorization token $token"); - String tokenFile = token; - try { - token = await File(tokenFile).readAsString(); - } on Exception catch (_) { - print("ERROR: Could not read authorization token file $token"); - token = ""; - } - } - } - //debugPrint("authorization = $token"); - } catch (e) { - debugPrint('WARNING: Could not read from file: $configFile'); - //debugPrint(e.toString()); - } - return KuksaConfig( - hostname: hostname, - port: port, - authorization: token, - use_tls: useTls, - ca_certificate: caCert, - tls_server_name: tlsServerName); - } - - void startListen() async { - KuksaConfig config = await readConfig(); - ChannelCredentials creds; - if (config.use_tls && config.ca_certificate.isNotEmpty) { - print("Using TLS"); - if (config.tls_server_name.isNotEmpty) { - creds = ChannelCredentials.secure( - certificates: config.ca_certificate, - authority: config.tls_server_name); - } else { - creds = ChannelCredentials.secure(certificates: config.ca_certificate); - } - } else { - creds = const ChannelCredentials.insecure(); - } - channel = ClientChannel(config.hostname, - port: config.port, options: ChannelOptions(credentials: creds)); - debugPrint('Start Listen on port: ${config.port}'); - stub = VALClient(channel); - authorization = config.authorization; - List<String> fewSignals = VSSPath().getSignalsList(); - var request = SubscribeRequest(); - Map<String, String> metadata = {}; - if (authorization.isNotEmpty) { - metadata = {'authorization': "Bearer ${authorization}"}; - } - for (int i = 0; i < fewSignals.length; i++) { - var entry = SubscribeEntry(); - entry.path = fewSignals[i]; - entry.fields.add(Field.FIELD_PATH); - entry.fields.add(Field.FIELD_VALUE); - request.entries.add(entry); - } - try { - var responseStream = stub.subscribe(request, options: CallOptions(metadata: metadata)); - responseStream.listen((value) async { - for (var update in value.updates) { - if (!(update.hasEntry() && update.entry.hasPath())) continue; - handleSignalsUpdate(update); - } - }, onError: (stacktrace, errorDescriptor) { - debugPrint(stacktrace.toString()); - state = const Vehicle.initialForDebug(); - }); - } catch (e) { - debugPrint(e.toString()); + default: + handled = false; } + return handled; } void setChildLock({required String side}) async { - var helper = ValClientHelper(stub: stub, authorization: authorization); + var valClient = ref.read(valClientProvider); try { switch (side) { case 'left': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsChildLockActiveLeft, !state.isChildLockActiveLeft, false, @@ -343,7 +148,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { isChildLockActiveLeft: !state.isChildLockActiveLeft); break; case 'right': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsChildLockActiveRight, !state.isChildLockActiveRight, false, @@ -360,22 +165,12 @@ class VehicleNotifier extends StateNotifier<Vehicle> { } } - void setVolume(double newVal) { - state = state.copyWith(mediaVolume: newVal.toInt()); - var helper = ValClientHelper(stub: stub, authorization: authorization); - helper.setUint32( - VSSPath.vehicleMediaVolume, - newVal.toInt(), - true, - ); - } - void setTemperature({required Side side, required int value}) { - var helper = ValClientHelper(stub: stub, authorization: authorization); + var valClient = ref.read(valClientProvider); try { switch (side) { case Side.left: - helper.setInt32( + valClient.setInt32( VSSPath.vehicleDriverTemperature, value, true, @@ -383,7 +178,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { state = state.copyWith(driverTemperature: value); break; case Side.right: - helper.setInt32( + valClient.setInt32( VSSPath.vehiclePassengerTemperature, value, true, @@ -419,8 +214,8 @@ class VehicleNotifier extends StateNotifier<Vehicle> { default: break; } - var helper = ValClientHelper(stub: stub, authorization: authorization); - helper.setUint32( + var valClient = ref.read(valClientProvider); + valClient.setUint32( VSSPath.vehicleFanSpeed, targetFanSpeed, true, @@ -429,11 +224,11 @@ class VehicleNotifier extends StateNotifier<Vehicle> { } void setHVACMode({required String mode}) { - var helper = ValClientHelper(stub: stub, authorization: authorization); + var valClient = ref.read(valClientProvider); try { switch (mode) { case 'airCondition': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsAirConditioningActive, !state.isAirConditioningActive, true, @@ -442,7 +237,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { isAirConditioningActive: !state.isAirConditioningActive); break; case 'frontDefrost': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsFrontDefrosterActive, !state.isFrontDefrosterActive, true, @@ -451,7 +246,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { isFrontDefrosterActive: !state.isFrontDefrosterActive); break; case 'rearDefrost': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsRearDefrosterActive, !state.isRearDefrosterActive, true, @@ -460,7 +255,7 @@ class VehicleNotifier extends StateNotifier<Vehicle> { isRearDefrosterActive: !state.isRearDefrosterActive); break; case 'recirculation': - helper.setBool( + valClient.setBool( VSSPath.vehicleIsRecirculationActive, !state.isRecirculationActive, true, diff --git a/lib/data/models/audio.dart b/lib/data/models/audio.dart index 69df18b..65490f9 100644 --- a/lib/data/models/audio.dart +++ b/lib/data/models/audio.dart @@ -5,52 +5,59 @@ import 'package:flutter_ics_homescreen/export.dart'; @immutable class Audio { final double volume; + final double balance; + final double fade; final double treble; final double bass; - final double rearFront; const Audio({ required this.volume, + required this.balance, + required this.fade, required this.treble, required this.bass, - required this.rearFront, }); const Audio.initial() : volume = 5.0, + balance = 5.0, + fade = 5.0, treble = 5.0, - bass = 5.0, - rearFront = 5.0; + bass = 5.0; Audio copyWith({ double? volume, + double? balance, + double? fade, double? treble, double? bass, - double? rearFront, }) { return Audio( volume: volume ?? this.volume, + balance: balance ?? this.balance, + fade: fade ?? this.fade, treble: treble ?? this.treble, bass: bass ?? this.bass, - rearFront: rearFront ?? this.rearFront, ); } Map<String, dynamic> toMap() { return { 'volume': volume, + 'balance': balance, + 'fade': fade, 'treble': treble, 'bass': bass, - 'rearFront': rearFront, }; } factory Audio.fromMap(Map<String, dynamic> map) { return Audio( volume: map['volume']?.toDouble() ?? 0.0, + balance: map['balance']?.toDouble() ?? 0.0, + fade: map['fade']?.toDouble() ?? 0.0, treble: map['treble']?.toDouble() ?? 0.0, bass: map['bass']?.toDouble() ?? 0.0, - rearFront: map['rearFront']?.toDouble() ?? 0.0, ); } @@ -60,7 +67,7 @@ class Audio { @override String toString() { - return 'Audio(volume: $volume, treble: $treble, bass: $bass, rearFront: $rearFront)'; + return 'Audio(volume: $volume, balance: $balance, fade: $fade, treble: $treble, bass: $bass)'; } @override @@ -69,16 +76,18 @@ class Audio { return other is Audio && other.volume == volume && + other.balance == balance && + other.fade == fade && other.treble == treble && - other.bass == bass && - other.rearFront == rearFront; + other.bass == bass; } @override int get hashCode { return volume.hashCode ^ + balance.hashCode ^ + fade.hashCode ^ treble.hashCode ^ - bass.hashCode ^ - rearFront.hashCode; + bass.hashCode; } } diff --git a/lib/data/models/hybrid.dart b/lib/data/models/hybrid.dart index d567f14..5746033 100644 --- a/lib/data/models/hybrid.dart +++ b/lib/data/models/hybrid.dart @@ -2,7 +2,7 @@ enum HybridState { idle, engineOutput, regenerativeBreaking, - baterryOutput, + batteryOutput, } enum ArrowState { blue, red, green, yellow } diff --git a/lib/data/models/vehicle.dart b/lib/data/models/vehicle.dart index 2dea928..ad76620 100644 --- a/lib/data/models/vehicle.dart +++ b/lib/data/models/vehicle.dart @@ -10,7 +10,6 @@ class Vehicle { final double outsideTemperature; final int range; final int fuelLevel; - final int mediaVolume; final bool isChildLockActiveLeft; final bool isChildLockActiveRight; final int frontLeftTire; @@ -27,27 +26,26 @@ class Vehicle { final bool temperatureSynced; const Vehicle( - this.speed, - this.engineSpeed, - this.insideTemperature, - this.outsideTemperature, - this.range, - this.fuelLevel, - this.mediaVolume, - this.isChildLockActiveLeft, - this.isChildLockActiveRight, - this.frontLeftTire, - this.frontRightTire, - this.rearLeftTire, - this.rearRightTire, - this.isAirConditioningActive, - this.isFrontDefrosterActive, - this.isRearDefrosterActive, - this.isRecirculationActive, - this.fanSpeed, - this.driverTemperature, - this.passengerTemperature, - this.temperatureSynced, + this.speed, + this.engineSpeed, + this.insideTemperature, + this.outsideTemperature, + this.range, + this.fuelLevel, + this.isChildLockActiveLeft, + this.isChildLockActiveRight, + this.frontLeftTire, + this.frontRightTire, + this.rearLeftTire, + this.rearRightTire, + this.isAirConditioningActive, + this.isFrontDefrosterActive, + this.isRearDefrosterActive, + this.isRecirculationActive, + this.fanSpeed, + this.driverTemperature, + this.passengerTemperature, + this.temperatureSynced, ); const Vehicle.initial() @@ -57,7 +55,6 @@ class Vehicle { outsideTemperature = 0, range = 0, fuelLevel = 0, - mediaVolume = 50, isChildLockActiveLeft = false, isChildLockActiveRight = true, frontLeftTire = 33, @@ -80,7 +77,6 @@ class Vehicle { outsideTemperature = 32.0, range = 21, fuelLevel = 49, - mediaVolume = 50, isChildLockActiveLeft = false, isChildLockActiveRight = true, frontLeftTire = 33, @@ -96,51 +92,49 @@ class Vehicle { passengerTemperature = 26, temperatureSynced = true; - Vehicle copyWith( - {double? speed, - double? engineSpeed, - double? insideTemperature, - double? outsideTemperature, - int? range, - int? fuelLevel, - int? mediaVolume, - bool? isChildLockActiveLeft, - bool? isChildLockActiveRight, - int? frontLeftTire, - int? frontRightTire, - int? rearLeftTire, - int? rearRightTire, - bool? isAirConditioningActive, - bool? isFrontDefrosterActive, - bool? isRearDefrosterActive, - bool? isRecirculationActive, - int? fanSpeed, - int? driverTemperature, - int? passengerTemperature, - bool? temperatureSynced, + Vehicle copyWith({ + double? speed, + double? engineSpeed, + double? insideTemperature, + double? outsideTemperature, + int? range, + int? fuelLevel, + bool? isChildLockActiveLeft, + bool? isChildLockActiveRight, + int? frontLeftTire, + int? frontRightTire, + int? rearLeftTire, + int? rearRightTire, + bool? isAirConditioningActive, + bool? isFrontDefrosterActive, + bool? isRearDefrosterActive, + bool? isRecirculationActive, + int? fanSpeed, + int? driverTemperature, + int? passengerTemperature, + bool? temperatureSynced, }) { return Vehicle( - speed ?? this.speed, - engineSpeed ?? this.engineSpeed, - insideTemperature ?? this.insideTemperature, - outsideTemperature ?? this.outsideTemperature, - range ?? this.range, - fuelLevel ?? this.fuelLevel, - mediaVolume ?? this.mediaVolume, - isChildLockActiveLeft ?? this.isChildLockActiveLeft, - isChildLockActiveRight ?? this.isChildLockActiveRight, - frontLeftTire ?? this.frontLeftTire, - frontRightTire ?? this.frontRightTire, - rearLeftTire ?? this.rearLeftTire, - rearRightTire ?? this.rearRightTire, - isAirConditioningActive ?? this.isAirConditioningActive, - isFrontDefrosterActive ?? this.isFrontDefrosterActive, - isRearDefrosterActive ?? this.isRearDefrosterActive, - isRecirculationActive ?? this.isRecirculationActive, - fanSpeed ?? this.fanSpeed, - driverTemperature ?? this.driverTemperature, - passengerTemperature ?? this.passengerTemperature, - temperatureSynced ?? this.temperatureSynced, + speed ?? this.speed, + engineSpeed ?? this.engineSpeed, + insideTemperature ?? this.insideTemperature, + outsideTemperature ?? this.outsideTemperature, + range ?? this.range, + fuelLevel ?? this.fuelLevel, + isChildLockActiveLeft ?? this.isChildLockActiveLeft, + isChildLockActiveRight ?? this.isChildLockActiveRight, + frontLeftTire ?? this.frontLeftTire, + frontRightTire ?? this.frontRightTire, + rearLeftTire ?? this.rearLeftTire, + rearRightTire ?? this.rearRightTire, + isAirConditioningActive ?? this.isAirConditioningActive, + isFrontDefrosterActive ?? this.isFrontDefrosterActive, + isRearDefrosterActive ?? this.isRearDefrosterActive, + isRecirculationActive ?? this.isRecirculationActive, + fanSpeed ?? this.fanSpeed, + driverTemperature ?? this.driverTemperature, + passengerTemperature ?? this.passengerTemperature, + temperatureSynced ?? this.temperatureSynced, ); } @@ -152,7 +146,6 @@ class Vehicle { 'outsideTemperature': outsideTemperature, 'range': range, 'fuelLevel': fuelLevel, - 'mediaVolume': mediaVolume, 'isChildLockActiveLeft': isChildLockActiveLeft, 'isChildLockActiveRight': isChildLockActiveRight, 'frontLeftTire': frontLeftTire, @@ -178,7 +171,6 @@ class Vehicle { map['outsideTemperature']?.toDouble() ?? 0.0, map['range']?.toInt() ?? 0, map['fuelLevel']?.toDouble() ?? 0.0, - map['mediaVolume']?.toInt() ?? 0, map['isChildLockActiveLeft'] ?? false, map['isChildLockActiveRight'] ?? false, map['frontLeftTire']?.toInt() ?? 0, @@ -203,7 +195,7 @@ class Vehicle { @override String toString() { - return 'Vehicle(speed: $speed, insideTemperature: $insideTemperature, outsideTemperature: $outsideTemperature, range: $range, fuelLevel: $fuelLevel, mediaVolume: $mediaVolume, isChildLockActiveLeft: $isChildLockActiveLeft, isChildLockActiveRight: $isChildLockActiveRight, engineSpeed: $engineSpeed, frontLeftTire: $frontLeftTire, frontRightTire: $frontRightTire, rearLeftTire: $rearLeftTire, rearRightTire: $rearRightTire, isAirConditioningActive: $isAirConditioningActive, isFrontDefrosterActive: $isFrontDefrosterActive, isRearDefrosterActive: $isRearDefrosterActive, isRecirculationActive: $isRecirculationActive,fanSpeed:$fanSpeed,driverTemperature:$driverTemperature, passengerTemperature:$passengerTemperature)'; + return 'Vehicle(speed: $speed, insideTemperature: $insideTemperature, outsideTemperature: $outsideTemperature, range: $range, fuelLevel: $fuelLevel, isChildLockActiveLeft: $isChildLockActiveLeft, isChildLockActiveRight: $isChildLockActiveRight, engineSpeed: $engineSpeed, frontLeftTire: $frontLeftTire, frontRightTire: $frontRightTire, rearLeftTire: $rearLeftTire, rearRightTire: $rearRightTire, isAirConditioningActive: $isAirConditioningActive, isFrontDefrosterActive: $isFrontDefrosterActive, isRearDefrosterActive: $isRearDefrosterActive, isRecirculationActive: $isRecirculationActive,fanSpeed:$fanSpeed,driverTemperature:$driverTemperature, passengerTemperature:$passengerTemperature)'; } @override @@ -216,7 +208,6 @@ class Vehicle { other.outsideTemperature == outsideTemperature && other.range == range && other.fuelLevel == fuelLevel && - other.mediaVolume == mediaVolume && other.isChildLockActiveLeft == isChildLockActiveLeft && other.isChildLockActiveRight == isChildLockActiveRight && other.engineSpeed == engineSpeed && @@ -241,7 +232,6 @@ class Vehicle { outsideTemperature.hashCode ^ range.hashCode ^ fuelLevel.hashCode ^ - mediaVolume.hashCode ^ isChildLockActiveLeft.hashCode ^ isChildLockActiveRight.hashCode ^ engineSpeed.hashCode ^ @@ -258,9 +248,4 @@ class Vehicle { passengerTemperature.hashCode ^ temperatureSynced.hashCode; } -// } -// / class VehicleNotifier extends StateNotifier<Vehicle> { -// // VehicleNotifier() : super(Vehicle()); - -// // } } diff --git a/lib/export.dart b/lib/export.dart index 2fe6356..17cab07 100644 --- a/lib/export.dart +++ b/lib/export.dart @@ -1,4 +1,5 @@ export 'data/data_providers/app.dart'; +export 'data/data_providers/app_config_provider.dart'; export 'data/data_providers/app_provider.dart'; export 'presentation/router/routes/routes.dart'; export 'data/theme/theme.dart'; @@ -60,7 +61,6 @@ export 'presentation/screens/clock/clock.dart'; export 'core/utils/widgets/back_button.dart'; export 'core/constants/vss_path.dart'; -export 'core/constants/val_client_helper.dart'; export 'core/constants/constants.dart'; //Common widgets export 'presentation/common_widget/settings_top_bar.dart'; @@ -75,7 +75,6 @@ export 'package:yaml/yaml.dart'; export 'package:lottie/lottie.dart'; //export 'package:new_virtual_keyboard/virtual_keyboard.dart'; - //export 'package:intl/intl.dart'; //export 'package:protos/protos.dart'; diff --git a/lib/presentation/common_widget/generic_button.dart b/lib/presentation/common_widget/generic_button.dart index cca354f..e418cd5 100644 --- a/lib/presentation/common_widget/generic_button.dart +++ b/lib/presentation/common_widget/generic_button.dart @@ -1,14 +1,14 @@ import 'package:flutter_ics_homescreen/export.dart'; class GenericButton extends StatefulWidget { - final double heigth; + final double height; final double width; final String text; final Function onTap; const GenericButton({ super.key, - required this.heigth, + required this.height, required this.width, required this.text, required this.onTap, @@ -57,7 +57,7 @@ class _GenericButtonState extends State<GenericButton> { widget.onTap(); }, child: Container( - height: widget.heigth, + height: widget.height, width: widget.width, decoration: BoxDecoration( gradient: Gradient.lerp(gradientEnable1, gradientEnable2, 0.5), diff --git a/lib/presentation/common_widget/volume_bar.dart b/lib/presentation/common_widget/volume_bar.dart index f494332..b54283e 100644 --- a/lib/presentation/common_widget/volume_bar.dart +++ b/lib/presentation/common_widget/volume_bar.dart @@ -22,7 +22,7 @@ class VolumeBarState extends ConsumerState<VolumeBar> { val = 100; } setState(() { - ref.read(vehicleProvider.notifier).setVolume(val); + ref.read(audioStateProvider.notifier).setVolume(val); }); } @@ -32,14 +32,14 @@ class VolumeBarState extends ConsumerState<VolumeBar> { val = 0; } setState(() { - ref.read(vehicleProvider.notifier).setVolume(val); + ref.read(audioStateProvider.notifier).setVolume(val); }); } void setVolume(double newWalue) { setState(() { val = newWalue; - ref.read(vehicleProvider.notifier).setVolume(val); + ref.read(audioStateProvider.notifier).setVolume(val); }); } @@ -48,7 +48,7 @@ class VolumeBarState extends ConsumerState<VolumeBar> { @override Widget build(BuildContext context) { final volumeValue = - ref.watch(vehicleProvider.select((vehicle) => vehicle.mediaVolume)); + ref.watch(audioStateProvider.select((audio) => audio.volume)); val = volumeValue.toDouble(); return Column( // mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/presentation/screens/dashboard/widgets/dashboard_content.dart b/lib/presentation/screens/dashboard/widgets/dashboard_content.dart index 28cf944..7e4c469 100644 --- a/lib/presentation/screens/dashboard/widgets/dashboard_content.dart +++ b/lib/presentation/screens/dashboard/widgets/dashboard_content.dart @@ -39,6 +39,7 @@ class DashBoardState extends ConsumerState<DashBoard> }); } + bool randomHybridAnimation = ref.read(appConfigProvider).randomHybridAnimation; if (randomHybridAnimation) { timer = Timer.periodic(const Duration(seconds: 5), (timer) { Random random = Random(); diff --git a/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart b/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart index 6badf62..24eabd6 100644 --- a/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart +++ b/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart @@ -1,7 +1,7 @@ import 'package:flutter_ics_homescreen/export.dart'; -class HybridBackround extends StatelessWidget { - const HybridBackround({ +class HybridBackground extends StatelessWidget { + const HybridBackground({ super.key, }); @@ -21,9 +21,9 @@ class TopArrow extends StatelessWidget { return Align( alignment: const Alignment(0, -0.75), child: Consumer(builder: (context, ref, child) { - final state = ref.watch(hybridStateProvider.select((hybrid) => hybrid)); + final arrowState = ref.watch(hybridStateProvider.select((hybrid) => hybrid.topArrowState)); Widget? widget; - switch (state.topArrowState) { + switch (arrowState) { case ArrowState.blue: widget = SvgPicture.asset( 'animations/hybrid_model/top_blue.svg', @@ -56,9 +56,9 @@ class LeftArrow extends StatelessWidget { return Align( alignment: const Alignment(-0.7, 0.5), child: Consumer(builder: (context, ref, child) { - final state = ref.watch(hybridStateProvider.select((hybrid) => hybrid)); + final arrowState = ref.watch(hybridStateProvider.select((hybrid) => hybrid.leftArrowState)); Widget? widget; - switch (state.leftArrowState) { + switch (arrowState) { case ArrowState.blue: widget = SvgPicture.asset( 'animations/hybrid_model/left_blue.svg', @@ -92,10 +92,10 @@ class RightArrow extends StatelessWidget { return Align( alignment: const Alignment(0.70, 0.5), child: Consumer(builder: (context, ref, child) { - final state = ref.watch(hybridStateProvider.select((hybrid) => hybrid)); + final arrowState = ref.watch(hybridStateProvider.select((hybrid) => hybrid.rightArrowState)); Widget? widget; - switch (state.rightArrowState) { + switch (arrowState) { case ArrowState.blue: widget = SvgPicture.asset( 'animations/hybrid_model/right_blue.svg', diff --git a/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart b/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart index f5f1286..01fb981 100644 --- a/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart +++ b/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart @@ -11,6 +11,7 @@ class HybridModelState extends ConsumerState<HybridModel> { @override Widget build(BuildContext context) { + bool randomHybridAnimation = ref.watch(appConfigProvider).randomHybridAnimation; if (!randomHybridAnimation) { ref.listen<Vehicle>(vehicleProvider, (Vehicle? previous, Vehicle next) { ref.watch(hybridStateProvider.notifier).updateHybridState( @@ -25,7 +26,7 @@ class HybridModelState extends ConsumerState<HybridModel> { height: 500, child: Stack( children: [ - HybridBackround(), + HybridBackground(), TopArrow(), LeftArrow(), RightArrow(), diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart index da20753..86da46f 100644 --- a/lib/presentation/screens/home/home.dart +++ b/lib/presentation/screens/home/home.dart @@ -28,6 +28,8 @@ class HomeScreenState extends ConsumerState<HomeScreen> { ) { return Consumer(builder: (context, ref, child) { final state = ref.read(appProvider); + final bool disableBkgAnimation = + ref.read(appConfigProvider).disableBkgAnimation; if (disableBkgAnimation) { print('Background animation: disabled'); } diff --git a/lib/presentation/screens/media_player/widgets/media_volume_bar.dart b/lib/presentation/screens/media_player/widgets/media_volume_bar.dart index ed962a7..dd59ee0 100644 --- a/lib/presentation/screens/media_player/widgets/media_volume_bar.dart +++ b/lib/presentation/screens/media_player/widgets/media_volume_bar.dart @@ -19,7 +19,7 @@ class CustomVolumeSliderState extends ConsumerState<CustomVolumeSlider> { _currentVal = 100; } setState(() { - ref.read(vehicleProvider.notifier).setVolume(_currentVal); + ref.read(audioStateProvider.notifier).setVolume(_currentVal); }); } @@ -29,7 +29,7 @@ class CustomVolumeSliderState extends ConsumerState<CustomVolumeSlider> { _currentVal = 0; } setState(() { - ref.read(vehicleProvider.notifier).setVolume(_currentVal); + ref.read(audioStateProvider.notifier).setVolume(_currentVal); }); } @@ -37,7 +37,7 @@ class CustomVolumeSliderState extends ConsumerState<CustomVolumeSlider> { @override Widget build(BuildContext context) { final volumeValue = - ref.watch(vehicleProvider.select((audio) => audio.mediaVolume)); + ref.watch(audioStateProvider.select((audio) => audio.volume)); return Column( //crossAxisAlignment: CrossAxisAlignment.center, @@ -106,7 +106,7 @@ class CustomVolumeSliderState extends ConsumerState<CustomVolumeSlider> { max: 100, value: volumeValue.toDouble(), onChanged: (newValue) { - ref.read(vehicleProvider.notifier).setVolume(newValue); + ref.read(audioStateProvider.notifier).setVolume(newValue); _currentVal = newValue; }, ), diff --git a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart index a08796d..d662272 100644 --- a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart +++ b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart @@ -16,22 +16,26 @@ class AudioContent extends ConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CustomTrebleSlider(), + CustomBalanceSlider(), SizedBox( - height: 120, + height: 60, ), - CustomBassSlider(), + CustomFaderSlider(), + SizedBox( + height: 60, + ), + CustomTrebleSlider(), SizedBox( - height: 120, + height: 60, ), - CustomRearFrontSlider(), + CustomBassSlider(), ], ), ), Padding( padding: const EdgeInsets.only(bottom: 150), child: GenericButton( - heigth: 130, + height: 130, width: 420, text: 'Reset to Default', onTap: () { diff --git a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart index 36e45e3..fefd9ed 100644 --- a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart +++ b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart @@ -1,31 +1,32 @@ import 'package:flutter_ics_homescreen/export.dart'; import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; -class CustomTrebleSlider extends ConsumerStatefulWidget { - const CustomTrebleSlider({ +class CustomBalanceSlider extends ConsumerStatefulWidget { + const CustomBalanceSlider({ super.key, }); @override - CustomTrebleSliderState createState() => CustomTrebleSliderState(); + CustomBalanceState createState() => CustomBalanceState(); } -class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { +class CustomBalanceState extends ConsumerState<CustomBalanceSlider> { bool isPressed = false; + void _increase() { setState(() { if (_currentVal < 10) { _currentVal++; - ref.read(audioStateProvider.notifier).setTreble(_currentVal); + ref.read(audioStateProvider.notifier).setBalance(_currentVal); } }); } - void _dercrease() { + void _decrease() { setState(() { if (_currentVal > 0) { _currentVal--; - ref.read(audioStateProvider.notifier).setTreble(_currentVal); + ref.read(audioStateProvider.notifier).setBalance(_currentVal); } }); } @@ -33,15 +34,15 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { double _currentVal = 5; @override Widget build(BuildContext context) { - final trebleValue = - ref.watch(audioStateProvider.select((audio) => audio.treble)); + final balanceValue = + ref.watch(audioStateProvider.select((audio) => audio.balance)); return Column( //crossAxisAlignment: CrossAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( - 'Treble', + 'Balance', style: TextStyle(fontSize: 40), ), ), @@ -70,15 +71,17 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { Padding( padding: const EdgeInsets.only(left: 40), child: InkWell( - onTap: () { - _dercrease(); - }, - child: const Icon( - Icons.remove, - color: AGLDemoColors.periwinkleColor, - size: 48, - ), - ), + onTap: () { + _decrease(); + }, + child: Text( + 'LEFT', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AGLDemoColors.periwinkleColor, + ), + )), ), SizedBox( width: 584, @@ -86,7 +89,7 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { data: SliderThemeData( showValueIndicator: ShowValueIndicator.always, trackShape: CustomRoundedRectSliderTrackShape( - silderVal: trebleValue), + sliderVal: balanceValue, isFrontRear: true), activeTickMarkColor: Colors.transparent, inactiveTickMarkColor: Colors.transparent, inactiveTrackColor: AGLDemoColors.backgroundInsetColor, @@ -98,9 +101,11 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { divisions: 10, min: 0, max: 10, - value: trebleValue, + value: balanceValue, onChanged: (newValue) { - ref.read(audioStateProvider.notifier).setTreble(newValue); + ref + .read(audioStateProvider.notifier) + .setBalance(newValue); _currentVal = newValue; }, onChangeEnd: (value) { @@ -117,17 +122,18 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { ), ), Padding( - padding: const EdgeInsets.only( - right: 40, - ), + padding: const EdgeInsets.only(right: 40), child: InkWell( onTap: () { _increase(); }, - child: const Icon( - Icons.add, - color: AGLDemoColors.periwinkleColor, - size: 48, + child: Text( + 'RIGHT', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AGLDemoColors.periwinkleColor, + ), )), ), ], @@ -138,32 +144,32 @@ class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { } } -class CustomBassSlider extends ConsumerStatefulWidget { - const CustomBassSlider({ +class CustomFaderSlider extends ConsumerStatefulWidget { + const CustomFaderSlider({ super.key, }); @override - CustomBassSliderState createState() => CustomBassSliderState(); + CustomFaderState createState() => CustomFaderState(); } -class CustomBassSliderState extends ConsumerState<CustomBassSlider> { +class CustomFaderState extends ConsumerState<CustomFaderSlider> { bool isPressed = false; void _increase() { setState(() { if (_currentVal < 10) { _currentVal++; - ref.read(audioStateProvider.notifier).setBass(_currentVal); + ref.read(audioStateProvider.notifier).setFade(_currentVal); } }); } - void _dercrease() { + void _decrease() { setState(() { if (_currentVal > 0) { _currentVal--; - ref.read(audioStateProvider.notifier).setBass(_currentVal); + ref.read(audioStateProvider.notifier).setFade(_currentVal); } }); } @@ -171,16 +177,15 @@ class CustomBassSliderState extends ConsumerState<CustomBassSlider> { double _currentVal = 5; @override Widget build(BuildContext context) { - final bassValue = - ref.watch(audioStateProvider.select((audio) => audio.bass)); - + final faderValue = + ref.watch(audioStateProvider.select((audio) => audio.fade)); return Column( //crossAxisAlignment: CrossAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( - 'Bass', + 'Fade', style: TextStyle(fontSize: 40), ), ), @@ -210,21 +215,24 @@ class CustomBassSliderState extends ConsumerState<CustomBassSlider> { padding: const EdgeInsets.only(left: 40), child: InkWell( onTap: () { - _dercrease(); + _decrease(); }, - child: const Icon( - Icons.remove, - color: AGLDemoColors.periwinkleColor, - size: 48, - )), + child: Text( + 'REAR', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AGLDemoColors.periwinkleColor, + ), + )), ), SizedBox( width: 584, child: SliderTheme( data: SliderThemeData( showValueIndicator: ShowValueIndicator.always, - trackShape: - CustomRoundedRectSliderTrackShape(silderVal: bassValue), + trackShape: CustomRoundedRectSliderTrackShape( + sliderVal: faderValue, isFrontRear: true), activeTickMarkColor: Colors.transparent, inactiveTickMarkColor: Colors.transparent, inactiveTrackColor: AGLDemoColors.backgroundInsetColor, @@ -236,9 +244,11 @@ class CustomBassSliderState extends ConsumerState<CustomBassSlider> { divisions: 10, min: 0, max: 10, - value: bassValue, + value: faderValue, onChanged: (newValue) { - ref.read(audioStateProvider.notifier).setBass(newValue); + ref + .read(audioStateProvider.notifier) + .setFade(newValue); _currentVal = newValue; }, onChangeEnd: (value) { @@ -260,6 +270,146 @@ class CustomBassSliderState extends ConsumerState<CustomBassSlider> { onTap: () { _increase(); }, + child: Text( + 'FRONT', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AGLDemoColors.periwinkleColor, + ), + )), + ), + ], + ), + ), + ], + ); + } +} + +class CustomTrebleSlider extends ConsumerStatefulWidget { + const CustomTrebleSlider({ + super.key, + }); + + @override + CustomTrebleSliderState createState() => CustomTrebleSliderState(); +} + +class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { + bool isPressed = false; + void _increase() { + setState(() { + if (_currentVal < 10) { + _currentVal++; + ref.read(audioStateProvider.notifier).setTreble(_currentVal); + } + }); + } + + void _decrease() { + setState(() { + if (_currentVal > 0) { + _currentVal--; + ref.read(audioStateProvider.notifier).setTreble(_currentVal); + } + }); + } + + double _currentVal = 5; + @override + Widget build(BuildContext context) { + final trebleValue = + ref.watch(audioStateProvider.select((audio) => audio.treble)); + return Column( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Treble', + style: TextStyle(fontSize: 40), + ), + ), + Container( + width: 792, + height: 160, + decoration: const ShapeDecoration( + gradient: LinearGradient( + colors: <Color>[ + AGLDemoColors.neonBlueColor, + AGLDemoColors.resolutionBlueColor, + Color.fromARGB(127, 20, 31, 100), + Color(0xFF2962FF) + ], + stops: [0, 0, 1, 1], + ), + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 40), + child: InkWell( + onTap: () { + _decrease(); + }, + child: const Icon( + Icons.remove, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ), + ), + SizedBox( + width: 584, + child: SliderTheme( + data: SliderThemeData( + showValueIndicator: ShowValueIndicator.always, + trackShape: CustomRoundedRectSliderTrackShape( + sliderVal: trebleValue), + activeTickMarkColor: Colors.transparent, + inactiveTickMarkColor: Colors.transparent, + inactiveTrackColor: AGLDemoColors.backgroundInsetColor, + thumbShape: PolygonSliderThumb( + sliderValue: 3, thumbRadius: 23, isPressed: isPressed), + trackHeight: 16, + ), + child: Slider( + divisions: 10, + min: 0, + max: 10, + value: trebleValue, + onChanged: (newValue) { + ref.read(audioStateProvider.notifier).setTreble(newValue); + _currentVal = newValue; + }, + onChangeEnd: (value) { + setState(() { + isPressed = false; + }); + }, + onChangeStart: (value) { + setState(() { + isPressed = true; + }); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + right: 40, + ), + child: InkWell( + onTap: () { + _increase(); + }, child: const Icon( Icons.add, color: AGLDemoColors.periwinkleColor, @@ -274,32 +424,32 @@ class CustomBassSliderState extends ConsumerState<CustomBassSlider> { } } -class CustomRearFrontSlider extends ConsumerStatefulWidget { - const CustomRearFrontSlider({ +class CustomBassSlider extends ConsumerStatefulWidget { + const CustomBassSlider({ super.key, }); @override - CustomRearFrontState createState() => CustomRearFrontState(); + CustomBassSliderState createState() => CustomBassSliderState(); } -class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { +class CustomBassSliderState extends ConsumerState<CustomBassSlider> { bool isPressed = false; void _increase() { setState(() { if (_currentVal < 10) { _currentVal++; - ref.read(audioStateProvider.notifier).setRearFront(_currentVal); + ref.read(audioStateProvider.notifier).setBass(_currentVal); } }); } - void _dercrease() { + void _decrease() { setState(() { if (_currentVal > 0) { _currentVal--; - ref.read(audioStateProvider.notifier).setRearFront(_currentVal); + ref.read(audioStateProvider.notifier).setBass(_currentVal); } }); } @@ -307,15 +457,16 @@ class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { double _currentVal = 5; @override Widget build(BuildContext context) { - final rearFrontValue = - ref.watch(audioStateProvider.select((audio) => audio.rearFront)); + final bassValue = + ref.watch(audioStateProvider.select((audio) => audio.bass)); + return Column( //crossAxisAlignment: CrossAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( - 'Rear/Front', + 'Bass', style: TextStyle(fontSize: 40), ), ), @@ -345,10 +496,10 @@ class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { padding: const EdgeInsets.only(left: 40), child: InkWell( onTap: () { - _dercrease(); + _decrease(); }, child: const Icon( - CustomIcons.slider_rear, + Icons.remove, color: AGLDemoColors.periwinkleColor, size: 48, )), @@ -358,8 +509,8 @@ class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { child: SliderTheme( data: SliderThemeData( showValueIndicator: ShowValueIndicator.always, - trackShape: CustomRoundedRectSliderTrackShape( - silderVal: rearFrontValue, isFrontRear: true), + trackShape: + CustomRoundedRectSliderTrackShape(sliderVal: bassValue), activeTickMarkColor: Colors.transparent, inactiveTickMarkColor: Colors.transparent, inactiveTrackColor: AGLDemoColors.backgroundInsetColor, @@ -371,11 +522,9 @@ class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { divisions: 10, min: 0, max: 10, - value: rearFrontValue, + value: bassValue, onChanged: (newValue) { - ref - .read(audioStateProvider.notifier) - .setRearFront(newValue); + ref.read(audioStateProvider.notifier).setBass(newValue); _currentVal = newValue; }, onChangeEnd: (value) { @@ -398,7 +547,7 @@ class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { _increase(); }, child: const Icon( - CustomIcons.slider_front, + Icons.add, color: AGLDemoColors.periwinkleColor, size: 48, )), @@ -467,11 +616,11 @@ class PolygonSliderThumb extends SliderComponentShape { //TODO add border to custom track Shape class CustomRoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape { - final double silderVal; + final double sliderVal; final bool? isFrontRear; CustomRoundedRectSliderTrackShape({ - required this.silderVal, + required this.sliderVal, this.isFrontRear = false, }); @override @@ -552,10 +701,10 @@ class CustomRoundedRectSliderTrackShape extends SliderTrackShape topRight: const Radius.circular(25), bottomLeft: const Radius.circular(25), bottomRight: const Radius.circular(25)), - //silderVal > 5 ? leftTrackPaint : rightTrackPaint); + //sliderVal > 5 ? leftTrackPaint : rightTrackPaint); isFrontRear! ? rightTrackPaint - : silderVal > 5 + : sliderVal > 5 ? leftTrackPaint : rightTrackPaint); //active diff --git a/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart index 446a3b5..3fbb75f 100644 --- a/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart +++ b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart @@ -204,7 +204,7 @@ class BluetoothContentState extends ConsumerState<BluetoothContent> { Padding( padding: const EdgeInsets.only(bottom: 150.0), child: GenericButton( - heigth: 130, + height: 130, width: 501, text: 'Scan for New Device', onTap: () {}, diff --git a/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart b/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart index 0cf1ddb..78b1422 100644 --- a/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart +++ b/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart @@ -226,7 +226,7 @@ class NewProfilePageState extends ConsumerState<NewProfilePage> { Padding( padding: const EdgeInsets.only(bottom: 350.0), child: GenericButton( - heigth: 130, + height: 130, width: 493, text: 'Save Profile', onTap: () { diff --git a/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart b/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart index eb89553..48e1565 100644 --- a/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart +++ b/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart @@ -98,7 +98,7 @@ class ProfilesContentState extends ConsumerState<ProfilesContent> { child: Column( children: [ GenericButton( - heigth: 122, + height: 122, width: 317, text: 'New Profile', onTap: () { @@ -110,7 +110,7 @@ class ProfilesContentState extends ConsumerState<ProfilesContent> { const SizedBox(height: 20), GenericButton( - heigth: 122, + height: 122, width: 412, text: 'Reset to Default', onTap: () {}, diff --git a/lib/presentation/screens/splash/widget/splash_content.dart b/lib/presentation/screens/splash/widget/splash_content.dart index 991be84..d93be4f 100644 --- a/lib/presentation/screens/splash/widget/splash_content.dart +++ b/lib/presentation/screens/splash/widget/splash_content.dart @@ -66,7 +66,7 @@ class SplashContentState extends ConsumerState<SplashContent> @override void didChangeDependencies() { - ref.read(vehicleProvider.notifier).startListen(); + ref.read(valClientProvider).startListen(); super.didChangeDependencies(); } @@ -113,16 +113,19 @@ class SplashContentState extends ConsumerState<SplashContent> height: 488, child: Text( splashWarning, - style: TextStyle(color: Colors.white, fontSize: 40, height: 1.7, fontWeight: FontWeight.w400), + style: TextStyle( + color: Colors.white, + fontSize: 40, + height: 1.7, + fontWeight: FontWeight.w400), textAlign: TextAlign.left, - ), ), ], ), ), GenericButton( - heigth: 122, + height: 122, width: 452, text: 'Continue', onTap: () { @@ -132,7 +135,6 @@ class SplashContentState extends ConsumerState<SplashContent> .update((state) => state = AppState.dashboard); }, ), - const SizedBox( height: 72, ) diff --git a/pubspec.yaml b/pubspec.yaml index 310eb23..8768c94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,7 +83,6 @@ flutter: - assets/ - animations/ - animations/hybrid_model/ - - app-config/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware |