diff options
Diffstat (limited to 'lib')
28 files changed, 2620 insertions, 0 deletions
diff --git a/lib/cluster_config.dart b/lib/cluster_config.dart new file mode 100644 index 0000000..10e152d --- /dev/null +++ b/lib/cluster_config.dart @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_cluster_dashboard/vehicle_signal/initial_socket_connection.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yaml/yaml.dart'; + +class GetConfig extends ConsumerStatefulWidget { + const GetConfig({Key? key, required this.client}) : super(key: key); + final HttpClient client; + + @override + ConsumerState<GetConfig> createState() => _GetConfigState(); +} + +class _GetConfigState extends ConsumerState<GetConfig> { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final configStateProvider = ref.read(clusterConfigStateprovider.notifier); + + String configFilePath = '/etc/xdg/AGL/flutter-cluster-dashboard.yaml'; + String orsKeyFilePath = '/etc/default/openroutekey'; + + String keyContent = ""; + + final configFile = File(configFilePath); + final orsKeyFile = File(orsKeyFilePath); + + configFile.readAsString().then((content) { + final dynamic yamlMap = loadYaml(content); + configStateProvider.update( + hostname: yamlMap['hostname'], + port: yamlMap['port'], + homeLat: yamlMap['homeLat'], + homeLng: yamlMap['homeLng'], + orsPathParam: yamlMap['orsPathParam'], + kuksaAuthToken: yamlMap['kuskaAuthToken'], + ); + }); + + orsKeyFile.readAsString().then((content) { + keyContent = content.split(':')[1].trim(); + if (keyContent.isNotEmpty && keyContent != 'YOU_NEED_TO_SET_IT_IN_LOCAL_CONF') { + configStateProvider.update(orsApiKey: keyContent); + } else { + print("WARNING: openrouteservice API Key not found !"); + } + }); + }); + } + + @override + Widget build(BuildContext context) { + final config = ref.watch(clusterConfigStateprovider); + if (config.hostname == "" || + config.port == 0 || + config.kuksaAuthToken == "" || + config.homeLat == 0 || + config.homeLng == 0 || + config.orsPathParam == "") { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + Text("ERROR", + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + Text( + "Something Wrong with config file! Check config.yaml file and restart"), + ], + )), + ); + } + return InitialScreen(client: widget.client); + } +} + +class ClusterConfig { + ClusterConfig({ + required this.hostname, + required this.port, + required this.kuksaAuthToken, + required this.orsApiKey, + required this.orsPathParam, + required this.homeLat, + required this.homeLng, + }); + final String hostname; + final int port; + final String kuksaAuthToken; + final double homeLat; + final double homeLng; + final String orsApiKey; + final String orsPathParam; + + ClusterConfig copywith({ + String? hostname, + int? port, + String? kuksaAuthToken, + double? homeLat, + double? homeLng, + String? orsApiKey, + String? orsPathParam, + }) => + ClusterConfig( + hostname: hostname ?? this.hostname, + port: port ?? this.port, + kuksaAuthToken: kuksaAuthToken ?? this.kuksaAuthToken, + orsApiKey: orsApiKey ?? this.orsApiKey, + orsPathParam: orsPathParam ?? this.orsPathParam, + homeLat: homeLat ?? this.homeLat, + homeLng: homeLng ?? this.homeLng, + ); +} + +class ClusterConfigStateNotifier extends StateNotifier<ClusterConfig> { + ClusterConfigStateNotifier() : super(_initialValue); + static final ClusterConfig _initialValue = ClusterConfig( + hostname: "", + port: 0, + kuksaAuthToken: "", + orsApiKey: "", + orsPathParam: "", + homeLat: 0, + homeLng: 0, + ); + void update({ + String? hostname, + int? port, + String? kuksaAuthToken, + double? homeLat, + double? homeLng, + String? orsApiKey, + String? orsPathParam, + }) { + state = state.copywith( + hostname: hostname, + port: port, + kuksaAuthToken: kuksaAuthToken, + homeLat: homeLat, + homeLng: homeLng, + orsApiKey: orsApiKey, + orsPathParam: orsPathParam, + ); + } +} + +final clusterConfigStateprovider = + StateNotifierProvider<ClusterConfigStateNotifier, ClusterConfig>( + (ref) => ClusterConfigStateNotifier()); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..c1198ce --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter_cluster_dashboard/cluster_config.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_config.dart';
+
+Future<void> main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ HttpClient client = await initializeClient();
+ runApp(
+ ProviderScope(
+ child: MaterialApp(
+ home: GetConfig(client: client),
+ ),
+ ),
+ );
+}
diff --git a/lib/map/navigationHome.dart b/lib/map/navigationHome.dart new file mode 100644 index 0000000..5218de7 --- /dev/null +++ b/lib/map/navigationHome.dart @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_cluster_dashboard/cluster_config.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_cluster_dashboard/map/networkPolyline.dart'; +import 'package:flutter_cluster_dashboard/provider.dart'; +import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart'; +import 'package:latlong2/latlong.dart'; + +class NavigationHome extends ConsumerStatefulWidget { + const NavigationHome({Key? key}) : super(key: key); + + @override + ConsumerState<NavigationHome> createState() => _NavigationHomeState(); +} + +class _NavigationHomeState extends ConsumerState<NavigationHome> { + late Timer timerCurrLocation; + Timer timerPolyline = Timer.periodic(const Duration(hours: 10), ((timer) {})); + double pathStroke = 5; + late MapController mapController; + // randomly initialization of the variable + LatLng src = LatLng(31.71, 76.95); + LatLng markerLocation = LatLng(31.71, 76.95); + + @override + void initState() { + super.initState(); + mapController = MapController(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + var vehicle = ref.read(vehicleSignalProvider); + var polylineDB = ref.read(polyLineStateProvider); + final polylineDBNotifier = ref.read(polyLineStateProvider.notifier); + final clusterConfig = ref.read(clusterConfigStateprovider); + // timer for updating map center and zoom + timerCurrLocation = Timer.periodic(const Duration(seconds: 2), (timer) { + polylineDB = ref.read(polyLineStateProvider); + + vehicle = ref.read(vehicleSignalProvider); + markerLocation = LatLng(vehicle.currLat, vehicle.currLng); + // move and center + mapController.move(LatLng(vehicle.currLat, vehicle.currLng), 15); + + // rotate + double rotationDegree = 0; + int n = polylineDB.currPolyLineList.length; + if (polylineDB.currPolyLineList.isNotEmpty && n > 1) { + rotationDegree = calcAngle( + polylineDB.currPolyLineList[0], polylineDB.currPolyLineList[1]); + + rotationDegree = (rotationDegree.isNaN) ? 0 : rotationDegree; + } + // print("Rotation:$rotationDegree"); + mapController.rotate(-1 * rotationDegree); + }); + + // update polyline in polyline db + if (polylineDB.currPolyLineList.isEmpty && clusterConfig.orsApiKey.isNotEmpty) { + timerPolyline.cancel(); + timerPolyline = + Timer.periodic(const Duration(seconds: 10), (timer) async { + List data = await getJsonData(ref, vehicle.currLat, vehicle.currLng, + vehicle.desLat, vehicle.desLng); + List<LatLng> currList = + data.map((element) => LatLng(element[1], element[0])).toList(); + polylineDBNotifier.update(currPolyLineList: currList); + }); + } + }); + } + + @override + void dispose() { + super.dispose(); + timerCurrLocation.cancel(); + timerPolyline.cancel(); + } + + double tempangle = 0; + @override + Widget build(BuildContext context) { + final currListProvider = ref.watch(polyLineStateProvider); + List<LatLng> currPolyLineList = currListProvider.currPolyLineList; + + return FlutterMap( + mapController: mapController, + options: MapOptions( + rotation: 0, + center: src, + minZoom: 12, + zoom: 12, + maxZoom: 22.0, + keepAlive: true, + ), + layers: [ + TileLayerOptions( + maxZoom: 22, + maxNativeZoom: 18, + subdomains: ["a", "b", "c"], + urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), + if (currPolyLineList.isNotEmpty) + PolylineLayerOptions( + polylineCulling: false, + polylines: [ + if (currPolyLineList.isNotEmpty) + Polyline( + strokeWidth: pathStroke, + points: currPolyLineList, + color: Colors.blue, + ), + ], + ), + if (currPolyLineList.isNotEmpty) + MarkerLayerOptions( + rotate: true, + markers: [ + Marker( + point: markerLocation, + width: 70, + height: 70, + builder: (context) => Image.asset( + "images/car.png", + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/map/networkPolyline.dart b/lib/map/networkPolyline.dart new file mode 100644 index 0000000..0241bec --- /dev/null +++ b/lib/map/networkPolyline.dart @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:math'; +import 'package:flutter_cluster_dashboard/cluster_config.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:latlong2/latlong.dart'; + +class NetworkHelper { + NetworkHelper( + {required this.startLng, + required this.startLat, + required this.endLng, + required this.endLat}); + + final String url = 'https://api.openrouteservice.org/v2/directions/'; + + final double startLng; + final double startLat; + final double endLng; + final double endLat; + + Future getData(WidgetRef ref) async { + final config = ref.read(clusterConfigStateprovider); + String uriStr = + '$url${config.orsPathParam}?api_key=${config.orsApiKey}&start=$startLng,$startLat&end=$endLng,$endLat'; + http.Response response = await http.get(Uri.parse(uriStr)); + + if (response.statusCode == 200) { + String data = response.body; + return jsonDecode(data); + } else { + print("Warning: API Response Code: ${response.statusCode}"); + } + } +} + +Future getJsonData( + WidgetRef ref, + double startLat, + double startLng, + double endLat, + double endLng, +) async { + if (startLat == endLat && startLng == endLng) { + return []; + } else { + NetworkHelper network = NetworkHelper( + startLat: startLat, + startLng: startLng, + endLat: endLat, + endLng: endLng, + ); + try { + final apikey = ref.read(clusterConfigStateprovider).orsApiKey; + if (apikey.isNotEmpty) { + var data = await network.getData(ref); + return data['features'][0]['geometry']['coordinates']; + } + else { + return []; + } + } catch (error) { + print('Warning: Something Wrong with openstreet API Key !'); + return []; + } + } +} + +double calcAngle(LatLng a, LatLng b) { + List<double> newA = convertCoord(a); + List<double> newB = convertCoord(b); + double slope = (newB[1] - newA[1]) / (newB[0] - newA[0]); + // -1 * deg + 180 + return ((atan(slope) * 180) / pi); +} + +List<double> convertCoord(LatLng coord) { + double oldLat = coord.latitude; + double oldLong = coord.longitude; + double newLong = (oldLong * 20037508.34 / 180); + double newlat = + (log(tan((90 + oldLat) * pi / 360)) / (pi / 180)) * (20037508.34 / 180); + return [newlat, newLong]; +} diff --git a/lib/provider.dart b/lib/provider.dart new file mode 100644 index 0000000..c96c8e4 --- /dev/null +++ b/lib/provider.dart @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+import 'dart:math';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:latlong2/latlong.dart';
+
+// -------------------------------------------------------------
+final clockProvider = StateNotifierProvider<Clock, DateTime>((ref) {
+ return Clock();
+});
+
+class Clock extends StateNotifier<DateTime> {
+ late final Timer _timer;
+ Clock() : super(DateTime.now()) {
+ _timer = Timer.periodic(const Duration(seconds: 5), (_) {
+ state = DateTime.now();
+ });
+ }
+
+ @override
+ void dispose() {
+ _timer.cancel();
+ super.dispose();
+ }
+}
+
+// -------------------------------------------------------------
+class PolyLinesDB {
+ final List<LatLng> currPolyLineList;
+ final List<LatLng> polyLineList;
+ PolyLinesDB({required this.currPolyLineList, required this.polyLineList});
+ PolyLinesDB copyWith({
+ List<LatLng>? currPolyLineList,
+ List<LatLng>? polyLineList,
+ }) {
+ return PolyLinesDB(
+ currPolyLineList: currPolyLineList ?? this.currPolyLineList,
+ polyLineList: polyLineList ?? this.polyLineList,
+ );
+ }
+}
+
+class PolyLineNotifier extends StateNotifier<PolyLinesDB> {
+ static final PolyLinesDB initialvalue = PolyLinesDB(
+ currPolyLineList: [],
+ polyLineList: [],
+ );
+ PolyLineNotifier() : super(initialvalue);
+ void update({
+ List<LatLng>? currPolyLineList,
+ List<LatLng>? polyLineList,
+ }) {
+ state = state.copyWith(
+ currPolyLineList: currPolyLineList,
+ polyLineList: polyLineList,
+ );
+ }
+}
+
+final polyLineStateProvider =
+ StateNotifierProvider<PolyLineNotifier, PolyLinesDB>(
+ (ref) => PolyLineNotifier(),
+);
+
+// -------------------------------------------------------------
+
+class Gear {
+ static String parking = "P";
+ static String drive = "D";
+ static String neutral = "N";
+ static String reverse = "R";
+}
+
+double calculateDistance(point1, point2) {
+ double p = 0.017453292519943295;
+
+ double halfCosLatDiff = cos((point2.latitude - point1.latitude) * p) / 2;
+ double halfCosLngDiff = cos((point2.longitude - point1.longitude) * p) / 2;
+
+ double dist = 0.5 - halfCosLatDiff + cos(point1.latitude * p) * cos(point2.latitude * p) * (0.5 - halfCosLngDiff);
+
+ return 12742 * asin(sqrt(dist));
+}
diff --git a/lib/screen/home.dart b/lib/screen/home.dart new file mode 100644 index 0000000..70bc5c6 --- /dev/null +++ b/lib/screen/home.dart @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_cluster_dashboard/map/navigationHome.dart';
+import 'package:flutter_cluster_dashboard/provider.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_props.dart';
+import 'package:flutter_cluster_dashboard/screen/paints/bottombar_paint.dart';
+import 'package:flutter_cluster_dashboard/screen/paints/topbar_paint.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/rpm_guage_animation_wrapper.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/left_bar.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/performance_mode.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/right_bar.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/speed_guage_animation_wrapper.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/signals.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/turn_signal.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+import 'package:intl/intl.dart';
+
+class Home extends ConsumerWidget {
+ const Home({Key? key}) : super(key: key);
+ GuageColors? getGuageColor(String mode) {
+ return (mode == "economy")
+ ? GuageProps.ecoModeColor
+ : (mode == "sport")
+ ? GuageProps.sportModeColor
+ : null;
+ }
+
+ String addZero(int val) {
+ String res = val.toString();
+ if (res.length < 2) {
+ if (res.isEmpty) {
+ return "00";
+ } else if (res.length == 1) {
+ return "0$res";
+ }
+ }
+ return res;
+ }
+
+ double calcPadding(double value, double height) {
+ // values wrt to values at 720 height
+ return (value * height) / 720;
+ }
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final vehicle = ref.watch(vehicleSignalProvider);
+ final clock = ref.watch(clockProvider);
+ final windowHeight = MediaQuery.of(context).size.height;
+ final windowWidth = MediaQuery.of(context).size.width;
+
+ double screenHeight = windowHeight;
+ double screenWidth = windowWidth;
+
+ double ratHeight = (windowWidth * 9) / 16;
+ double ratWidth = (windowHeight * 16) / 9;
+
+ if (ratWidth <= windowWidth) {
+ screenHeight = windowHeight;
+ screenWidth = ratWidth;
+ } else {
+ screenHeight = ratHeight;
+ screenWidth = windowWidth;
+ }
+
+ return Scaffold(
+ backgroundColor: GuageProps.bgColor,
+ body: SafeArea(
+ child: Center(
+ child: Center(
+ child: SizedBox(
+ width: screenWidth,
+ height: screenHeight,
+ child: Flex(
+ direction: Axis.vertical,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ //TopBarPainter
+ Flexible(
+ flex: 1,
+ child: Stack(
+ children: [
+ TurnSignal(
+ screenHeight: screenHeight,
+ isLefton: vehicle.isLeftIndicator,
+ isRighton: vehicle.isRightIndicator,
+ ),
+ Flex(
+ direction: Axis.horizontal,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // mid section of top bar
+ Flexible(
+ flex: 1,
+ child: SizedBox(
+ width: (400 * screenHeight) / 480,
+ height: (65 * screenHeight) / 480,
+ child: CustomPaint(
+ painter: TopBarPainter(),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ DateFormat('EEEE')
+ .format(clock)
+ .substring(0, 3),
+ style: TextStyle(
+ color: const Color.fromARGB(
+ 255, 184, 183, 183),
+ fontSize: (20 * screenHeight) / 480,
+ fontWeight: FontWeight.bold),
+ ),
+ SizedBox(
+ width: (40 * screenHeight) / 480),
+ Text(
+ "${clock.hour}:${addZero(clock.minute)}",
+ style: TextStyle(
+ color: const Color.fromARGB(
+ 255, 255, 255, 255),
+ fontSize: (30 * screenHeight) / 480,
+ fontWeight: FontWeight.bold),
+ ),
+ SizedBox(
+ width: (30 * screenHeight) / 480),
+ Text(
+ "${vehicle.ambientAirTemp} ${"\u00B0"}C",
+ style: TextStyle(
+ color: const Color.fromARGB(
+ 255, 184, 183, 183),
+ fontSize: (20 * screenHeight) / 480,
+ fontWeight: FontWeight.bold),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ // mid section
+ Flexible(
+ flex: 4,
+ fit: FlexFit.tight,
+ child: Stack(
+ children: [
+ //left and right arc
+ Padding(
+ padding: EdgeInsets.fromLTRB(
+ calcPadding(60, screenHeight),
+ calcPadding(60, screenHeight),
+ calcPadding(60, screenHeight),
+ 0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Stack(
+ alignment: Alignment.bottomRight,
+ children: [
+ LeftArc(screenHeight: screenHeight),
+ Positioned(
+ child: Image.asset(
+ "images/temperature-icon.png",
+ color: Colors.white,
+ width: (13 * screenHeight) / 480,
+ ),
+ ),
+ ],
+ ),
+ Stack(
+ alignment: Alignment.bottomLeft,
+ children: [
+ RightArc(screenHeight: screenHeight),
+ Positioned(
+ child: Image.asset(
+ "images/fuel-icon.png",
+ color: Colors.white,
+ width: (18 * screenHeight) / 480,
+ ))
+ ],
+ ),
+ ],
+ ),
+ ),
+ //logo area
+ Padding(
+ padding: EdgeInsets.fromLTRB(
+ calcPadding(60, screenHeight),
+ calcPadding(10, screenHeight),
+ calcPadding(60, screenHeight),
+ 0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: (550 * screenHeight) / 720,
+ height: (450 * screenHeight) / 720,
+ child: Flex(
+ direction: Axis.vertical,
+ mainAxisAlignment:
+ MainAxisAlignment.spaceEvenly,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ // performance mode
+ Flexible(
+ flex: 1,
+ child: PerformanceMode(
+ size: Size((90 * screenHeight) / 480,
+ (20 * screenHeight) / 480),
+ mode: vehicle.performanceMode),
+ ),
+ // logo
+ Flexible(
+ flex: 6,
+ fit: FlexFit.tight,
+ child: SizedBox(
+ width: (330 * screenHeight) / 720,
+ child: (vehicle.isSteeringInfo)
+ ? const NavigationHome()
+ : Padding(
+ padding: EdgeInsets.all(
+ (48.0 * screenHeight) /
+ 720),
+ child: Image.asset(
+ "images/logo_agl.png",
+ width:
+ (90 * screenHeight) / 480,
+ color: Colors.grey.shade600,
+ ),
+ ),
+ ),
+ ),
+ const Flexible(
+ flex: 1,
+ child: SizedBox(),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ //warning signals
+ Padding(
+ padding: EdgeInsets.fromLTRB(
+ calcPadding(0, screenHeight),
+ calcPadding(430, screenHeight),
+ calcPadding(0, screenHeight),
+ 0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Signals(
+ screenHeight: screenHeight,
+ vehicle: vehicle,
+ ),
+ ],
+ ),
+ ),
+ // guages
+ Padding(
+ padding: EdgeInsets.fromLTRB(
+ calcPadding(70, screenHeight),
+ calcPadding(30, screenHeight),
+ calcPadding(70, screenHeight),
+ 0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ // Speed Guage
+ SpeedGauge(
+ screenHeight: screenHeight,
+ guageColor:
+ getGuageColor(vehicle.performanceMode),
+ ),
+ //RPM Guage
+ RPMGauge(
+ screenHeight: screenHeight,
+ guageColor:
+ getGuageColor(vehicle.performanceMode),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ // bottomBarPainter
+ Flexible(
+ flex: 1,
+ child: SizedBox(
+ width: (400 * screenHeight) / 480,
+ height: (50 * screenHeight) / 480,
+ child: CustomPaint(
+ size: Size((400 * screenHeight) / 480,
+ (50 * screenHeight) / 480),
+ painter: BottomBarPainter(),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ (vehicle.selectedGear == Gear.parking)
+ ? Text("P",
+ style: GuageProps.activeGearIconStyle(
+ screenHeight))
+ : Text("P",
+ style: GuageProps.gearIconStyle(
+ screenHeight)),
+ (vehicle.selectedGear == Gear.reverse)
+ ? Text("R",
+ style: GuageProps.activeGearIconStyle(
+ screenHeight))
+ : Text("R",
+ style: GuageProps.gearIconStyle(
+ screenHeight)),
+ (vehicle.selectedGear == Gear.neutral)
+ ? Text("N",
+ style: GuageProps.activeGearIconStyle(
+ screenHeight))
+ : Text("N",
+ style: GuageProps.gearIconStyle(
+ screenHeight)),
+ (vehicle.selectedGear == Gear.drive)
+ ? Text("D",
+ style: GuageProps.activeGearIconStyle(
+ screenHeight))
+ : Text("D",
+ style: GuageProps.gearIconStyle(
+ screenHeight)),
+ ]),
+ ),
+ )),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screen/paints/arc_painter.dart b/lib/screen/paints/arc_painter.dart new file mode 100644 index 0000000..66e38d4 --- /dev/null +++ b/lib/screen/paints/arc_painter.dart @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:math';
+import 'package:flutter/material.dart';
+
+class LeftPainter extends CustomPainter {
+ LeftPainter(
+ {required this.radi,
+ required this.currentValue,
+ required this.bottomPadding,
+ required this.color});
+
+ late final double radi;
+ late final double currentValue;
+ late final double bottomPadding;
+ late final Color color;
+
+ Offset getLeftPoints(Size size, double radius, double value) {
+ final double diam = 2 * radius;
+ final double arcHalfAngle = asin(size.height / diam); //thetha
+ final double currentAngle = (arcHalfAngle * value) / 50; //alpha
+ return Offset(
+ (radi * cos(arcHalfAngle)) +
+ (size.width) -
+ (radi * cos(arcHalfAngle - currentAngle)),
+ (radi * sin(arcHalfAngle)) + (radi * sin(arcHalfAngle - currentAngle)));
+ }
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ Offset startPoint = getLeftPoints(size, radi, bottomPadding);
+
+ final paint = Paint()
+ ..color = const Color.fromARGB(255, 49, 48, 48)
+ ..style = PaintingStyle.stroke
+ ..strokeCap = StrokeCap.round
+ ..strokeWidth = radi / 15;
+ final paint2 = Paint()
+ ..color = color
+ ..strokeCap = StrokeCap.round
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = radi / 15;
+
+ final path = Path()
+ ..moveTo(startPoint.dx, startPoint.dy)
+ ..arcToPoint(Offset(size.width, 0), radius: Radius.circular(radi));
+ canvas.drawPath(path, paint);
+ final path2 = Path()
+ ..moveTo(startPoint.dx, startPoint.dy)
+ ..arcToPoint(
+ getLeftPoints(size, radi,
+ bottomPadding + ((1 - (bottomPadding / 100)) * currentValue)),
+ radius: Radius.circular(radi));
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path2, paint2);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
+}
+
+class RightPainter extends CustomPainter {
+ RightPainter(
+ {required this.radi,
+ required this.currentValue,
+ required this.bottomPadding,
+ required this.color});
+ final double radi;
+ final double currentValue;
+ late final double bottomPadding;
+ late final Color color;
+
+ Offset getRightPoints(Size size, double radius, double value) {
+ final double diam = 2 * radius;
+ final double arcHalfAngle = asin(size.height / diam); //thetha
+ final double currentAngle = (arcHalfAngle * value) / 50; //alpha
+ return Offset(
+ (radi * cos(arcHalfAngle - currentAngle)) - (radi * cos(arcHalfAngle)),
+ (radi * sin(arcHalfAngle)) + (radi * sin(arcHalfAngle - currentAngle)));
+ }
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ Offset startPoint = getRightPoints(size, radi, bottomPadding);
+ final paint = Paint()
+ ..color = const Color.fromARGB(255, 49, 48, 48)
+ ..style = PaintingStyle.stroke
+ ..strokeCap = StrokeCap.round
+ ..strokeWidth = radi / 15;
+
+ final paint2 = Paint()
+ ..color = color
+ ..style = PaintingStyle.stroke
+ ..strokeCap = StrokeCap.round
+ ..strokeWidth = radi / 15;
+ final path = Path()
+ ..moveTo(startPoint.dx, startPoint.dy)
+ ..arcToPoint(
+ const Offset(0, 0),
+ radius: Radius.circular(radi),
+ clockwise: false,
+ );
+ final path2 = Path()
+ ..moveTo(startPoint.dx, startPoint.dy)
+ ..arcToPoint(
+ getRightPoints(size, radi,
+ bottomPadding + ((1 - (bottomPadding / 100)) * currentValue)),
+ radius: Radius.circular(radi),
+ clockwise: false);
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path2, paint2);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
+}
diff --git a/lib/screen/paints/bottombar_paint.dart b/lib/screen/paints/bottombar_paint.dart new file mode 100644 index 0000000..85c129e --- /dev/null +++ b/lib/screen/paints/bottombar_paint.dart @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:math';
+import 'dart:ui' as ui;
+import 'package:flutter/material.dart';
+
+class BottomBarPainter extends CustomPainter {
+ @override
+ void paint(Canvas canvas, Size size) {
+ final double height = size.height;
+ const double angle = 40;
+
+ double x = height * tan((angle / 180) * pi);
+ double y = height;
+
+ final double width = size.width - 2 * x;
+ final paint = Paint()
+ ..color = const ui.Color.fromARGB(255, 36, 36, 36)
+ ..strokeWidth = 5
+ ..strokeCap = StrokeCap.round
+ ..style = PaintingStyle.stroke;
+ final paint2 = Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.linear(
+ Offset((x + width / 2), 0), Offset((x + width / 2), height), [
+ const ui.Color.fromARGB(255, 25, 24, 24),
+ Colors.black,
+ ]);
+
+ final path = Path()
+ ..moveTo(0, size.height)
+ ..lineTo(x, size.height - y)
+ ..lineTo(x + width, size.height - y)
+ ..lineTo(size.width, size.height);
+
+ final path2 = Path()
+ ..moveTo(0, size.height)
+ ..lineTo(x, size.height - y)
+ ..lineTo(x + width, size.height - y)
+ ..lineTo(size.width, size.height);
+
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path2, paint2);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+}
diff --git a/lib/screen/paints/guage_paint.dart b/lib/screen/paints/guage_paint.dart new file mode 100644 index 0000000..22f7544 --- /dev/null +++ b/lib/screen/paints/guage_paint.dart @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'dart:math';
+import 'dart:ui' as ui;
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_props.dart';
+
+class GuagePainter extends CustomPainter {
+ final double low, high;
+ double currentSpeed;
+ final Color? outPrimaryColor;
+ final Color? inPrimaryColor;
+ final Color? secondaryColor;
+ GuagePainter({
+ required this.low,
+ required this.high,
+ required this.currentSpeed,
+ this.outPrimaryColor,
+ this.inPrimaryColor,
+ this.secondaryColor,
+ });
+ @override
+ void paint(Canvas canvas, Size size) {
+ Offset center = Offset(size.width / 2, size.height / 2);
+ double radius = min(size.width / 2, size.height / 2);
+ double radius1 = radius - ((20 / 200) * (radius));
+ double speedAngle =
+ GuageProps.degToRad((GuageProps.majorAngle / high) * currentSpeed);
+
+ final zeroTickPaint = Paint()
+ ..strokeWidth = ((7 / 200) * (radius))
+ ..shader = ui.Gradient.radial(center, radius1,
+ [Colors.black, const Color.fromARGB(255, 244, 242, 231)], [1, 0.5]);
+ final maxTickPaint = Paint()
+ ..strokeWidth = ((7 / 200) * (radius))
+ ..shader = ui.Gradient.radial(center, radius1,
+ [Colors.black, const Color.fromARGB(255, 244, 242, 231)], [1, 0.5]);
+
+ final speedPathStrokePaint = Paint()
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = ((7 / 200) * (radius))
+ ..shader = ui.Gradient.radial(
+ center, radius1, [Colors.black, Colors.white], [0.6, 1]);
+
+ final speedPathFillPaint = Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.radial(center, radius1, [
+ const ui.Color.fromARGB(0, 0, 0, 0),
+ secondaryColor ?? const Color.fromARGB(156, 226, 226, 200)
+ ], [
+ 0.8,
+ 1
+ ]);
+
+ final outerPathPaint = ui.Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.radial(
+ center,
+ radius,
+ [
+ Colors.black,
+ outPrimaryColor ?? const Color.fromARGB(255, 120, 120, 120)
+ ],
+ [0.8, 0.9],
+ );
+
+ final innerPathPaint = Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.radial(center, radius1, [
+ Colors.black,
+ inPrimaryColor ?? const Color.fromARGB(255, 67, 67, 67)
+ ], [
+ 0.65,
+ 0.9
+ ]);
+
+ final outerPathPaintRed = ui.Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.radial(
+ center,
+ radius,
+ [Colors.black, const Color.fromARGB(255, 187, 59, 57)],
+ [0.8, 0.9],
+ );
+
+ final innerPathPaintRed = Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.radial(center, radius1,
+ [Colors.black, const Color.fromARGB(255, 142, 35, 39)], [0.65, 0.9]);
+
+ for (double i = 0; i < 13; i++) {
+ double startAngle = GuageProps.degToRad(i * 20);
+ double gapAngle = GuageProps.degToRad(19);
+
+ var outerPath = Path();
+ outerPath.addArc(Rect.fromCircle(center: center, radius: radius),
+ startAngle, gapAngle);
+ outerPath.lineTo(center.dx, center.dy);
+ outerPath.close();
+
+ var innerPath = Path();
+ innerPath.addArc(Rect.fromCircle(center: center, radius: radius1),
+ startAngle, gapAngle);
+ innerPath.lineTo(center.dx, center.dy);
+ innerPath.close();
+ if (i >= 11) {
+ canvas.drawPath(outerPath, outerPathPaintRed);
+ canvas.drawPath(innerPath, innerPathPaintRed);
+ } else {
+ canvas.drawPath(outerPath, outerPathPaint);
+ canvas.drawPath(innerPath, innerPathPaint);
+ }
+ }
+
+ var speedStrokePath = Path();
+ speedStrokePath.moveTo(center.dx, center.dy);
+ speedStrokePath.addArc(
+ Rect.fromCircle(center: center, radius: radius), 0, speedAngle);
+ speedStrokePath.lineTo(center.dx, center.dy);
+
+ var speedFillPath = Path();
+ speedFillPath.addArc(
+ Rect.fromCircle(center: center, radius: radius), 0, speedAngle);
+ speedFillPath.lineTo(center.dx, center.dy);
+ speedFillPath.close();
+
+ canvas.drawPath(speedFillPath, speedPathFillPaint);
+ canvas.drawPath(speedStrokePath, speedPathStrokePaint);
+ canvas.drawLine(
+ center,
+ Offset(center.dx + (radius + ((3 / 200) * (radius))), center.dy),
+ zeroTickPaint);
+ canvas.drawLine(
+ center,
+ Offset(center.dx + (radius) * cos(GuageProps.majorAngleRad),
+ center.dy + (radius) * sin(GuageProps.majorAngleRad)),
+ maxTickPaint);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) {
+ return true;
+ }
+}
diff --git a/lib/screen/paints/topbar_paint.dart b/lib/screen/paints/topbar_paint.dart new file mode 100644 index 0000000..f7c79e8 --- /dev/null +++ b/lib/screen/paints/topbar_paint.dart @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:math';
+import 'dart:ui' as ui;
+import 'package:flutter/material.dart';
+
+class TopBarPainter extends CustomPainter {
+ @override
+ void paint(Canvas canvas, Size size) {
+ final double height = size.height;
+ const double angle = 40;
+
+ double x = height * tan((angle / 180) * pi);
+ double y = height;
+
+ final double width = size.width - 2 * x;
+ final paint = Paint()
+ ..color = const ui.Color.fromARGB(255, 49, 47, 47)
+ ..strokeWidth = 5
+ ..strokeCap = StrokeCap.round
+ ..style = PaintingStyle.stroke;
+ final paint2 = Paint()
+ ..style = PaintingStyle.fill
+ ..shader = ui.Gradient.linear(
+ Offset((x + width / 2), 0),
+ Offset((x + width / 2), height),
+ [Colors.black, const ui.Color.fromARGB(255, 32, 31, 31)]);
+ final path = Path()
+ ..lineTo(x, y)
+ ..lineTo(x + width, y)
+ ..lineTo(size.width, 0);
+ final path2 = Path()
+ ..lineTo(x, y)
+ ..lineTo(x + width, y)
+ ..lineTo(size.width, 0);
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path2, paint2);
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+}
diff --git a/lib/screen/widgets/guages/guage_props.dart b/lib/screen/widgets/guages/guage_props.dart new file mode 100644 index 0000000..bb56a31 --- /dev/null +++ b/lib/screen/widgets/guages/guage_props.dart @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:math';
+import 'package:flutter/material.dart';
+
+class GuageProps {
+ static GuageColors normalModeColor = GuageColors(
+ inPrimary: const Color.fromARGB(255, 67, 67, 67),
+ outPrimary: const Color.fromARGB(255, 120, 120, 120),
+ secondary: const Color.fromARGB(156, 226, 226, 200),
+ );
+ static GuageColors sportModeColor = GuageColors(
+ inPrimary: Colors.deepPurple,
+ outPrimary: Colors.blue,
+ secondary: const Color.fromARGB(214, 202, 202, 202));
+ static GuageColors ecoModeColor = GuageColors(
+ inPrimary: const Color.fromARGB(255, 85, 165, 87),
+ outPrimary: const Color.fromARGB(255, 40, 92, 42),
+ secondary: const Color.fromARGB(202, 194, 238, 195));
+ static double majorAngle = 260;
+ static double majorAngleRad = 260 * (pi / 180);
+ static double minorAngle = 360 - majorAngle;
+ static Color bgColor = const Color.fromARGB(255, 0, 0, 0);
+ static const leftLowColor = Color(0x000000ff);
+ static const leftHighColor = Color(0x00ff0000);
+
+ static double degToRad(double deg) => deg * (pi / 180.0);
+ static TextStyle gearIconStyle(screenHeight) {
+ return TextStyle(
+ color: const Color.fromARGB(255, 84, 83, 83),
+ fontSize: (20 * screenHeight) / 480,
+ fontWeight: FontWeight.bold);
+ }
+
+ static TextStyle activeGearIconStyle(screenHeight) {
+ return TextStyle(
+ color: Colors.white,
+ fontSize: (20 * screenHeight) / 480,
+ fontWeight: FontWeight.bold);
+ }
+}
+
+class GuageColors {
+ Color? inPrimary;
+ Color? outPrimary;
+ Color? secondary;
+ GuageColors({this.inPrimary, this.outPrimary, this.secondary});
+}
diff --git a/lib/screen/widgets/guages/guage_widget.dart b/lib/screen/widgets/guages/guage_widget.dart new file mode 100644 index 0000000..fa43958 --- /dev/null +++ b/lib/screen/widgets/guages/guage_widget.dart @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:math';
+import 'package:flutter/material.dart';
+import 'package:flutter_cluster_dashboard/screen/paints/guage_paint.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_props.dart';
+
+class CustomGuage extends StatelessWidget {
+ const CustomGuage({
+ Key? key,
+ required this.mainValue,
+ required this.low,
+ required this.high,
+ required this.label,
+ this.zeroTickLabel,
+ this.maxTickLabel,
+ this.distanceBWTicks,
+ this.size,
+ this.distLabelTop,
+ this.distMainTop,
+ this.distTicksBottom,
+ this.inPrimaryColor,
+ this.outPrimaryColor,
+ this.secondaryColor,
+ }) : super(key: key);
+
+ final double mainValue;
+ final double low;
+ final double high;
+ final String label;
+ final String? zeroTickLabel;
+ final String? maxTickLabel;
+ final double? distanceBWTicks;
+ final double? distTicksBottom;
+ final double? distMainTop;
+ final double? distLabelTop;
+ final double? size;
+ final Color? outPrimaryColor;
+ final Color? inPrimaryColor;
+ final Color? secondaryColor;
+
+ @override
+ Widget build(BuildContext context) {
+ TextStyle tickStyle = TextStyle(
+ color: Colors.white,
+ fontSize: ((26 / 400) * (size ?? 400)),
+ fontWeight: FontWeight.bold); //20
+ TextStyle mainValueTextStyle = TextStyle(
+ color: Colors.white,
+ fontSize: ((85 / 400) * (size ?? 400)),
+ fontWeight: FontWeight.bold); //65
+ TextStyle labelTextStyle = TextStyle(
+ color: Colors.white,
+ fontSize: ((26 / 400) * (size ?? 400)),
+ fontWeight: FontWeight.normal); //20
+ return SizedBox(
+ width: size ?? 400,
+ height: size ?? 400,
+ child: Stack(
+ alignment: Alignment.topCenter,
+ children: [
+ // Guage painter
+ Positioned(
+ top: 0,
+ child: Transform.rotate(
+ angle: (pi / 2) + (GuageProps.minorAngle * (pi / 360.0)),
+ child: CustomPaint(
+ size: Size(size ?? 400, size ?? 400),
+ painter: GuagePainter(
+ low: low,
+ high: high,
+ currentSpeed: mainValue,
+ inPrimaryColor: inPrimaryColor,
+ outPrimaryColor: outPrimaryColor,
+ secondaryColor: secondaryColor,
+ ),
+ ),
+ ),
+ ),
+ // Guage Label
+ Positioned(
+ top: distLabelTop ?? ((100 / 400) * (size ?? 400)),
+ child: Text(label, style: labelTextStyle),
+ ),
+ // Guage Main Value
+ Positioned(
+ top: distMainTop ?? ((150 / 400) * (size ?? 400)),
+ child: Text("${mainValue.toInt()}", style: mainValueTextStyle),
+ ),
+ // Guage Ticks value
+ Positioned(
+ bottom: distTicksBottom ?? ((80 / 400) * (size ?? 400)),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(zeroTickLabel ?? "", style: tickStyle),
+ SizedBox(
+ width: (size != null)
+ ? ((180 * size!) / 400)
+ : (distanceBWTicks ?? 180)),
+ Text(maxTickLabel ?? "", style: tickStyle)
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/screen/widgets/guages/rpm_guage_animation_wrapper.dart b/lib/screen/widgets/guages/rpm_guage_animation_wrapper.dart new file mode 100644 index 0000000..95403dd --- /dev/null +++ b/lib/screen/widgets/guages/rpm_guage_animation_wrapper.dart @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_props.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_widget.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+
+class RPMGauge extends HookConsumerWidget {
+ final double screenHeight;
+ final GuageColors? guageColor;
+ const RPMGauge({Key? key, required this.screenHeight, this.guageColor})
+ : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final VehicleSignal vehicle = ref.watch(vehicleSignalProvider);
+
+ const double minRPM = 0;
+ const double maxRPM = 8000;
+ const Duration sweepDuration = Duration(milliseconds: 200);
+
+ final animationController = useAnimationController(
+ lowerBound: minRPM,
+ upperBound: maxRPM,
+ )..animateTo(vehicle.rpm,
+ duration: sweepDuration, curve: Curves.linearToEaseOut);
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: CustomGuage(
+ size: (248 * screenHeight) / 480,
+ low: minRPM,
+ high: maxRPM,
+ mainValue: animationController.value,
+ label: "rpm",
+ zeroTickLabel: minRPM.toInt().toString(),
+ maxTickLabel: maxRPM.toInt().toString(),
+ inPrimaryColor: guageColor?.inPrimary,
+ outPrimaryColor: guageColor?.outPrimary,
+ secondaryColor: guageColor?.secondary,
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/screen/widgets/guages/speed_guage_animation_wrapper.dart b/lib/screen/widgets/guages/speed_guage_animation_wrapper.dart new file mode 100644 index 0000000..8704fcd --- /dev/null +++ b/lib/screen/widgets/guages/speed_guage_animation_wrapper.dart @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_props.dart';
+import 'package:flutter_cluster_dashboard/screen/widgets/guages/guage_widget.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+
+class SpeedGauge extends HookConsumerWidget {
+ final double screenHeight;
+ final GuageColors? guageColor;
+ const SpeedGauge({Key? key, required this.screenHeight, this.guageColor})
+ : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final VehicleSignal vehicle = ref.watch(vehicleSignalProvider);
+
+ const double minSpeed = 0;
+ const double maxSpeed = 240;
+ const Duration sweepDuration = Duration(milliseconds: 200);
+ double speedScaling =
+ (vehicle.vehicleDistanceUnit == "mi") ? 0.621504 : 1.0;
+
+ final animationController = useAnimationController(
+ lowerBound: minSpeed,
+ upperBound: maxSpeed,
+ )..animateTo(speedScaling * (vehicle.speed),
+ duration: sweepDuration, curve: Curves.linearToEaseOut);
+
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: CustomGuage(
+ size: (248 * screenHeight) / 480,
+ low: minSpeed,
+ high: maxSpeed,
+ mainValue: animationController.value,
+ label: (vehicle.vehicleDistanceUnit == "mi") ? "mph" : "Km/h",
+ zeroTickLabel: minSpeed.toInt().toString(),
+ maxTickLabel: maxSpeed.toInt().toString(),
+ inPrimaryColor: guageColor?.inPrimary,
+ outPrimaryColor: guageColor?.outPrimary,
+ secondaryColor: guageColor?.secondary,
+ ),
+ );
+ });
+ }
+}
+
+final guageColorProvider = Provider.family<GuageColors, String>((ref, mode) {
+ switch (mode) {
+ case "normal":
+ return GuageColors(inPrimary: Colors.red);
+ case "sports":
+ return GuageColors(inPrimary: Colors.blue);
+ case "eco":
+ return GuageColors(inPrimary: Colors.green);
+ default:
+ return GuageColors();
+ }
+});
diff --git a/lib/screen/widgets/left_bar.dart b/lib/screen/widgets/left_bar.dart new file mode 100644 index 0000000..3192c28 --- /dev/null +++ b/lib/screen/widgets/left_bar.dart @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:flutter_cluster_dashboard/screen/paints/arc_painter.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+
+class LeftArc extends HookConsumerWidget {
+ final double screenHeight;
+ const LeftArc({Key? key, required this.screenHeight}) : super(key: key);
+ // final Color color;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final VehicleSignal vehicle = ref.watch(vehicleSignalProvider);
+ final animationController = useAnimationController(
+ lowerBound: 0,
+ upperBound: 100,
+ )..animateTo(vehicle.coolantTemp,
+ duration: const Duration(milliseconds: 1000));
+
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return CustomPaint(
+ size: Size(0, (220 * screenHeight) / 480),
+ painter: LeftPainter(
+ radi: (170 * screenHeight) / 480,
+ currentValue: animationController.value,
+ bottomPadding: 15,
+ color: Color.lerp(Colors.yellow, Colors.red,
+ (animationController.value / 100)) ??
+ Colors.orange,
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/screen/widgets/left_signal.dart b/lib/screen/widgets/left_signal.dart new file mode 100644 index 0000000..693c762 --- /dev/null +++ b/lib/screen/widgets/left_signal.dart @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+class LeftSignal extends HookConsumerWidget {
+ final double screenHeight;
+ const LeftSignal({Key? key, required this.screenHeight}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final animationController = useAnimationController(
+ lowerBound: 0.9,
+ upperBound: 1.1,
+ duration: const Duration(milliseconds: 1000),
+ )..repeat();
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return Image.asset(
+ "images/left.png",
+ color: Color.lerp(
+ Colors.black,
+ const Color.fromARGB(255, 99, 251, 104),
+ animationController.value.floorToDouble()),
+ width: 0.125 * screenHeight,
+ );
+ });
+ }
+}
diff --git a/lib/screen/widgets/performance_mode.dart b/lib/screen/widgets/performance_mode.dart new file mode 100644 index 0000000..256365b --- /dev/null +++ b/lib/screen/widgets/performance_mode.dart @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+
+class PerformanceMode extends StatelessWidget {
+ const PerformanceMode({Key? key, this.size, required this.mode})
+ : super(key: key);
+ final Size? size;
+ final String mode;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: size?.width ?? 20,
+ height: size?.height ?? 40,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10),
+ color: (mode == "sport")
+ ? Colors.deepPurple
+ : (mode == "economy")
+ ? Colors.green
+ : Colors.transparent),
+ child: Center(
+ child: Text(
+ mode.toUpperCase(),
+ style: const TextStyle(
+ color: Colors.black, fontWeight: FontWeight.bold, fontSize: 12),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screen/widgets/right_bar.dart b/lib/screen/widgets/right_bar.dart new file mode 100644 index 0000000..e5ed44d --- /dev/null +++ b/lib/screen/widgets/right_bar.dart @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:flutter_cluster_dashboard/screen/paints/arc_painter.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+
+class RightArc extends HookConsumerWidget {
+ final double screenHeight;
+ const RightArc({Key? key, required this.screenHeight}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final VehicleSignal vehicle = ref.watch(vehicleSignalProvider);
+ final animationController = useAnimationController(
+ lowerBound: 0,
+ upperBound: 100,
+ )..animateTo(vehicle.fuelLevel,
+ duration: const Duration(milliseconds: 500));
+
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return CustomPaint(
+ size: Size(0, (220 * screenHeight) / 480),
+ painter: RightPainter(
+ radi: (170 * screenHeight) / 480,
+ currentValue: animationController.value,
+ bottomPadding: 17,
+ color: Color.lerp(Colors.red, Colors.green,
+ (animationController.value / 100)) ??
+ Colors.blue),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/screen/widgets/right_signal.dart b/lib/screen/widgets/right_signal.dart new file mode 100644 index 0000000..5c3aa5b --- /dev/null +++ b/lib/screen/widgets/right_signal.dart @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+class RightSignal extends HookConsumerWidget {
+ final double screenHeight;
+ const RightSignal({Key? key, required this.screenHeight}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final animationController = useAnimationController(
+ lowerBound: 0.9,
+ upperBound: 1.1,
+ duration: const Duration(milliseconds: 1000),
+ )..repeat();
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return Image.asset(
+ "images/right.png",
+ color: Color.lerp(
+ Colors.black,
+ const Color.fromARGB(255, 99, 251, 104),
+ animationController.value.floorToDouble()),
+ width: 0.125 * screenHeight,
+ );
+ });
+ }
+}
diff --git a/lib/screen/widgets/signals.dart b/lib/screen/widgets/signals.dart new file mode 100644 index 0000000..788d379 --- /dev/null +++ b/lib/screen/widgets/signals.dart @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+
+class Signals extends StatelessWidget {
+ final VehicleSignal vehicle;
+ final double screenHeight;
+ static Color idleColor = const Color.fromARGB(194, 55, 53, 53);
+ const Signals({
+ Key? key,
+ required this.screenHeight,
+ required this.vehicle,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Wrap(
+ spacing: 14,
+ runAlignment: WrapAlignment.spaceBetween,
+ alignment: WrapAlignment.spaceEvenly,
+ children: [
+ (vehicle.isLowBeam)
+ ? Image.asset("images/low-beam.png",
+ color: Colors.green, width: (20 * screenHeight) / 480)
+ : (vehicle.isHighBeam)
+ ? Image.asset("images/high-beam.png",
+ color: Colors.green, width: (20 * screenHeight) / 480)
+ : Image.asset("images/high-beam.png",
+ color: idleColor, width: (20 * screenHeight) / 480),
+ Image.asset("images/hazard.png",
+ color: (vehicle.isHazardLightOn) ? Colors.red : idleColor,
+ width: (20 * screenHeight) / 480),
+ Image.asset("images/parking.png",
+ color: (vehicle.isParkingOn) ? Colors.green : idleColor,
+ width: (20 * screenHeight) / 480),
+ Image.asset("images/battery.png",
+ color: (vehicle.isBatteryCharging) ? Colors.green : Colors.red,
+ width: (20 * screenHeight) / 480),
+ Image.asset("images/malfunction.png",
+ color: (vehicle.isMILon) ? Colors.red : idleColor,
+ width: (20 * screenHeight) / 480),
+ //
+ Image.asset("images/openDoor.png",
+ color: (vehicle.isMILon) ? Colors.white : idleColor,
+ width: (20 * screenHeight) / 480),
+
+ Image.asset("images/seatBelt.png",
+ color: (vehicle.isMILon) ? Colors.white : idleColor,
+ width: (20 * screenHeight) / 480),
+
+ //
+ Image.asset("images/lane.png",
+ color: (vehicle.isSteeringLaneWarning) ? Colors.white : idleColor,
+ width: (25 * screenHeight) / 480),
+ Image.asset("images/cruise.png",
+ color: (vehicle.isSteeringCruiseEnable)
+ ? (vehicle.isSteeringCruiseSet)
+ ? Colors.green
+ : Colors.orange
+ : idleColor,
+ width: (20 * screenHeight) / 480),
+ ],
+ );
+ }
+}
diff --git a/lib/screen/widgets/turn_signal.dart b/lib/screen/widgets/turn_signal.dart new file mode 100644 index 0000000..a447cbe --- /dev/null +++ b/lib/screen/widgets/turn_signal.dart @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+class TurnSignal extends HookConsumerWidget {
+ final double screenHeight;
+ final bool isLefton;
+ final bool isRighton;
+ const TurnSignal({
+ Key? key,
+ required this.screenHeight,
+ required this.isLefton,
+ required this.isRighton,
+ }) : super(key: key);
+ double calcPadding(double value, double height) {
+ // values wrt to values at 720 height
+ return (value * height) / 720;
+ }
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final animationController = useAnimationController(
+ lowerBound: 0.9,
+ upperBound: 1.1,
+ duration: const Duration(milliseconds: 1000),
+ )..repeat();
+ return AnimatedBuilder(
+ animation: animationController,
+ builder: (context, child) {
+ return Padding(
+ padding: EdgeInsets.fromLTRB(calcPadding(150, screenHeight), 0,
+ calcPadding(150, screenHeight), 0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Image.asset(
+ "images/left.png",
+ color: (isLefton)
+ ? Color.lerp(
+ Colors.black,
+ const Color.fromARGB(255, 99, 251, 104),
+ animationController.value.floorToDouble())
+ : const Color.fromARGB(255, 49, 48, 48),
+ width: 0.125 * screenHeight,
+ ),
+ Image.asset(
+ "images/right.png",
+ color: (isRighton)
+ ? Color.lerp(
+ Colors.black,
+ const Color.fromARGB(255, 99, 251, 104),
+ animationController.value.floorToDouble())
+ : const Color.fromARGB(255, 49, 48, 48),
+ width: 0.125 * screenHeight,
+ ),
+ ],
+ ),
+ );
+ });
+ }
+}
diff --git a/lib/vehicle_signal/initial_socket_connection.dart b/lib/vehicle_signal/initial_socket_connection.dart new file mode 100644 index 0000000..6b9305a --- /dev/null +++ b/lib/vehicle_signal/initial_socket_connection.dart @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/listen_stream.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_config.dart';
+
+class InitialScreen extends ConsumerWidget {
+ InitialScreen({Key? key, required this.client}) : super(key: key);
+ final HttpClient client;
+ late WebSocket socket;
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final sockConnect = ref.watch(sockConnectprovider(client));
+
+ return sockConnect.when(
+ data: (socket) {
+ this.socket = socket;
+ this.socket.pingInterval = const Duration(seconds: 2);
+ return OnBoardingPage(client: client, socket: this.socket);
+ },
+ error: (e, stk) {
+ print(e);
+ Future.delayed(const Duration(milliseconds: 700), (() {
+ ref.refresh(sockConnectprovider(client));
+ }));
+ return const Scaffold(
+ backgroundColor: Colors.black,
+ body: NoticeWidget(
+ assetImageName: "images/server_error.png",
+ text1: "Server Unavailable",
+ text2: "Retrying to conncect!",
+ ),
+ );
+ },
+ loading: () => const Scaffold(
+ backgroundColor: Colors.black,
+ body: NoticeWidget(
+ assetImageName: "images/server.png",
+ text1: "Hi!",
+ text2: "Connecting...!",
+ ),
+ ),
+ );
+ }
+}
+
+class NoticeWidget extends StatelessWidget {
+ const NoticeWidget({
+ Key? key,
+ required this.assetImageName,
+ required this.text1,
+ required this.text2,
+ this.loadingColor,
+ }) : super(key: key);
+
+ final String assetImageName;
+ final String text1;
+ final String text2;
+ final Color? loadingColor;
+
+ @override
+ Widget build(BuildContext context) {
+ return LoadingContainer(
+ child: Flex(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ direction: Axis.vertical,
+ children: [
+ Flexible(
+ child: SizedBox(
+ height: 100,
+ child:
+ Image(image: AssetImage(assetImageName), fit: BoxFit.fitWidth),
+ ),
+ ),
+ Flexible(
+ child: Text(text1,
+ style: const TextStyle(fontWeight: FontWeight.bold))),
+ Flexible(
+ child: Text(text2,
+ style: const TextStyle(fontWeight: FontWeight.bold))),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(25, 0, 25, 20),
+ child: LinearProgressIndicator(color: loadingColor ?? Colors.red),
+ ),
+ )
+ ],
+ ));
+ }
+}
+
+class LoadingContainer extends StatelessWidget {
+ const LoadingContainer({Key? key, required this.child}) : super(key: key);
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Center(
+ child: Container(
+ width: MediaQuery.of(context).size.width / 2,
+ height: MediaQuery.of(context).size.height * 3 / 4,
+ decoration: BoxDecoration(
+ color: Colors.white, borderRadius: BorderRadius.circular(40)),
+ child: child,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/vehicle_signal/listen_stream.dart b/lib/vehicle_signal/listen_stream.dart new file mode 100644 index 0000000..0e2772d --- /dev/null +++ b/lib/vehicle_signal/listen_stream.dart @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_cluster_dashboard/screen/home.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_config.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_methods.dart';
+
+class OnBoardingPage extends ConsumerStatefulWidget {
+ const OnBoardingPage({Key? key, required this.client, required this.socket})
+ : super(key: key);
+ final WebSocket socket;
+ final HttpClient client;
+
+ @override
+ ConsumerState<OnBoardingPage> createState() => _OnBoardingPageState();
+}
+
+class _OnBoardingPageState extends ConsumerState<OnBoardingPage> {
+ late Timer _timer;
+
+ @override
+ void initState() {
+ super.initState();
+ VISS.init(widget.socket, ref);
+ _timer = Timer.periodic(const Duration(seconds: 2), (timer) {
+ if (widget.socket.readyState == 3) {
+ ref.refresh(sockConnectprovider(widget.client));
+ }
+ });
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ widget.socket.listen(
+ (data) {
+ // print(data);
+ VISS.parseData(ref, data);
+ },
+ onError: (e, stk) {
+ print(e.toString());
+ ref.refresh(sockConnectprovider(widget.client));
+ },
+ );
+ });
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _timer.cancel();
+ widget.socket.close(786887, "Connection lost with server!");
+ }
+
+ @override
+ Widget build(BuildContext context) => const Home();
+}
diff --git a/lib/vehicle_signal/vehicle_signal_config.dart b/lib/vehicle_signal/vehicle_signal_config.dart new file mode 100644 index 0000000..3f0ff1d --- /dev/null +++ b/lib/vehicle_signal/vehicle_signal_config.dart @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:io';
+import 'package:flutter/services.dart';
+import 'package:flutter_cluster_dashboard/cluster_config.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+final sockConnectprovider = FutureProvider.family<WebSocket, HttpClient>(
+ (ref, client) => connect(client, ref));
+
+// load certificates and set context and returns http client
+Future<HttpClient> initializeClient() async {
+ ByteData dataCA = await rootBundle.load('assets/cert/CA.pem');
+ ByteData dataCert = await rootBundle.load('assets/cert/Client.pem');
+ ByteData dataKey = await rootBundle.load('assets/cert/Client.key');
+
+ SecurityContext ctx = SecurityContext.defaultContext;
+ ctx.useCertificateChainBytes(dataCert.buffer.asUint8List());
+ ctx.usePrivateKeyBytes(dataKey.buffer.asUint8List());
+ ctx.setTrustedCertificatesBytes(dataCA.buffer.asUint8List());
+ HttpClient client = HttpClient(context: ctx)
+ ..findProxy = null
+ ..badCertificateCallback = (cert, host, port) {
+ return true;
+ };
+ return client;
+}
+
+Future<WebSocket> connect(HttpClient client, ref) async {
+ final config = ref.read(clusterConfigStateprovider);
+ WebSocket socket = await WebSocket.connect(
+ "wss://${config.hostname}:${config.port}",
+ customClient: client);
+ return socket;
+}
diff --git a/lib/vehicle_signal/vehicle_signal_methods.dart b/lib/vehicle_signal/vehicle_signal_methods.dart new file mode 100644 index 0000000..21341c2 --- /dev/null +++ b/lib/vehicle_signal/vehicle_signal_methods.dart @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:convert';
+import 'dart:io';
+import 'package:flutter_cluster_dashboard/cluster_config.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_cluster_dashboard/map/networkPolyline.dart';
+import 'package:flutter_cluster_dashboard/provider.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_path.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_provider.dart';
+import 'package:latlong2/latlong.dart';
+
+class VISS {
+ static const requestId = "flutter-cluster-app";
+ static void init(WebSocket socket, WidgetRef ref) {
+ authorize(socket, ref);
+ subscribe(socket, ref, VSPath.vehicleSpeed);
+ subscribe(socket, ref, VSPath.vehicleEngineRPM);
+ subscribe(socket, ref, VSPath.vehicleLeftIndicator);
+ subscribe(socket, ref, VSPath.vehicleRightIndicator);
+ subscribe(socket, ref, VSPath.vehicleFuelLevel);
+ subscribe(socket, ref, VSPath.vehicleCoolantTemp);
+ subscribe(socket, ref, VSPath.vehicleHazardLightOn);
+ subscribe(socket, ref, VSPath.vehicleHighBeamOn);
+ subscribe(socket, ref, VSPath.vehicleLowBeamOn);
+ subscribe(socket, ref, VSPath.vehicleSelectedGear);
+ subscribe(socket, ref, VSPath.vehiclePerformanceMode);
+ subscribe(socket, ref, VSPath.vehicleAmbientAirTemperature);
+ subscribe(socket, ref, VSPath.vehicleParkingLightOn);
+ subscribe(socket, ref, VSPath.vehicleTrunkLocked);
+ subscribe(socket, ref, VSPath.vehicleTrunkOpen);
+ subscribe(socket, ref, VSPath.vehicleAmbientAirTemperature);
+ subscribe(socket, ref, VSPath.vehicleMIL);
+ subscribe(socket, ref, VSPath.vehicleCruiseControlError);
+ subscribe(socket, ref, VSPath.vehicleCruiseControlSpeedSet);
+ subscribe(socket, ref, VSPath.vehicleCruiseControlSpeedisActive);
+ subscribe(socket, ref, VSPath.vehicleBatteryChargingStatus);
+
+ //
+ subscribe(socket, ref, VSPath.steeringCruiseEnable);
+ subscribe(socket, ref, VSPath.steeringCruiseSet);
+ subscribe(socket, ref, VSPath.steeringCruiseResume);
+ subscribe(socket, ref, VSPath.steeringCruiseCancel);
+ subscribe(socket, ref, VSPath.steeringInfo);
+ subscribe(socket, ref, VSPath.steeringLaneDepWarn);
+ subscribe(socket, ref, VSPath.vehicleDistanceUnit);
+ //
+ subscribe(socket, ref, VSPath.vehicleCurrLat);
+ subscribe(socket, ref, VSPath.vehicleCurrLng);
+ subscribe(socket, ref, VSPath.vehicleDesLat);
+ subscribe(socket, ref, VSPath.vehicleDesLng);
+
+ update(socket, ref);
+ }
+
+ static void update(WebSocket socket, WidgetRef ref) {
+ get(socket, ref, VSPath.vehicleSpeed);
+ get(socket, ref, VSPath.vehicleEngineRPM);
+ get(socket, ref, VSPath.vehicleLeftIndicator);
+ get(socket, ref, VSPath.vehicleRightIndicator);
+ get(socket, ref, VSPath.vehicleFuelLevel);
+ get(socket, ref, VSPath.vehicleCoolantTemp);
+ get(socket, ref, VSPath.vehicleHazardLightOn);
+ get(socket, ref, VSPath.vehicleHighBeamOn);
+ get(socket, ref, VSPath.vehicleLowBeamOn);
+ get(socket, ref, VSPath.vehicleSelectedGear);
+ get(socket, ref, VSPath.vehiclePerformanceMode);
+ get(socket, ref, VSPath.vehicleAmbientAirTemperature);
+ get(socket, ref, VSPath.vehicleParkingLightOn);
+ get(socket, ref, VSPath.vehicleTrunkLocked);
+ get(socket, ref, VSPath.vehicleTrunkOpen);
+ get(socket, ref, VSPath.vehicleAmbientAirTemperature);
+ get(socket, ref, VSPath.vehicleMIL);
+ get(socket, ref, VSPath.vehicleCruiseControlError);
+ get(socket, ref, VSPath.vehicleCruiseControlSpeedSet);
+ get(socket, ref, VSPath.vehicleCruiseControlSpeedisActive);
+ get(socket, ref, VSPath.vehicleBatteryChargingStatus);
+ get(socket, ref, VSPath.vehicleDistanceUnit);
+ //
+ get(socket, ref, VSPath.vehicleCurrLat);
+ get(socket, ref, VSPath.vehicleCurrLng);
+ get(socket, ref, VSPath.vehicleDesLat);
+ get(socket, ref, VSPath.vehicleDesLng);
+ }
+
+ static void authorize(WebSocket socket, WidgetRef ref) {
+ final config = ref.read(clusterConfigStateprovider);
+ Map<String, dynamic> map = {
+ "action": "authorize",
+ "tokens": config.kuksaAuthToken,
+ "requestId": requestId
+ };
+ socket.add(jsonEncode(map));
+ }
+
+ static void get(WebSocket socket, WidgetRef ref, String path) {
+ final config = ref.read(clusterConfigStateprovider);
+ Map<String, dynamic> map = {
+ "action": "get",
+ "tokens": config.kuksaAuthToken,
+ "path": path,
+ "requestId": requestId
+ };
+ socket.add(jsonEncode(map));
+ }
+
+ static void set(WebSocket socket, WidgetRef ref, String path, String value) {
+ final config = ref.read(clusterConfigStateprovider);
+ Map<String, dynamic> map = {
+ "action": "set",
+ "tokens": config.kuksaAuthToken,
+ "path": path,
+ "requestId": requestId,
+ "value": value
+ };
+ socket.add(jsonEncode(map));
+ }
+
+ static void subscribe(WebSocket socket, WidgetRef ref, String path) {
+ final config = ref.read(clusterConfigStateprovider);
+ Map<String, dynamic> map = {
+ "action": "subscribe",
+ "tokens": config.kuksaAuthToken,
+ "path": path,
+ "requestId": requestId
+ };
+ socket.add(jsonEncode(map));
+ }
+
+ static String? numToGear(int? number) {
+ switch (number) {
+ case -1:
+ return 'R';
+ case 0:
+ return 'N';
+ case 126:
+ return 'P';
+ case 127:
+ return 'D';
+ default:
+ return null;
+ }
+ }
+
+ static void parseData(WidgetRef ref, String data) {
+ final vehicleSignal = ref.read(vehicleSignalProvider.notifier);
+ final polylineDBNotifier = ref.read(polyLineStateProvider.notifier);
+ Map<String, dynamic> dataMap = jsonDecode(data);
+ if (dataMap["action"] == "subscription" || dataMap["action"] == "get") {
+ if (dataMap.containsKey("data")) {
+ if ((dataMap["data"] as Map<String, dynamic>).containsKey("dp") &&
+ (dataMap["data"] as Map<String, dynamic>).containsKey("path")) {
+ String path = dataMap["data"]["path"];
+ Map<String, dynamic> dp = dataMap["data"]["dp"];
+ if (dp.containsKey("value")) {
+ if (dp["value"] != "---") {
+ switch (path) {
+ case VSPath.vehicleSpeed:
+ vehicleSignal.update(speed: double.parse(dp["value"]));
+ break;
+ case VSPath.vehicleEngineRPM:
+ vehicleSignal.update(rpm: double.parse(dp["value"]));
+ break;
+ case VSPath.vehicleFuelLevel:
+ vehicleSignal.update(fuelLevel: double.parse(dp["value"]));
+ break;
+ case VSPath.vehicleCoolantTemp:
+ vehicleSignal.update(coolantTemp: double.parse(dp["value"]));
+ break;
+ case VSPath.vehicleLeftIndicator:
+ vehicleSignal.update(isLeftIndicator: dp["value"]);
+ break;
+ case VSPath.vehicleRightIndicator:
+ vehicleSignal.update(isRightIndicator: dp["value"]);
+ break;
+ case VSPath.vehicleHighBeamOn:
+ if (dp["value"]) {
+ vehicleSignal.update(isHighBeam: true);
+ vehicleSignal.update(isLowBeam: false);
+ } else {
+ vehicleSignal.update(isHighBeam: dp["value"]);
+ }
+ break;
+ case VSPath.vehicleParkingLightOn:
+ vehicleSignal.update(isParkingOn: dp["value"]);
+ break;
+ case VSPath.vehicleLowBeamOn:
+ if (dp["value"]) {
+ vehicleSignal.update(isHighBeam: false);
+ vehicleSignal.update(isLowBeam: true);
+ } else {
+ vehicleSignal.update(isLowBeam: dp["value"]);
+ }
+ break;
+ case VSPath.vehicleHazardLightOn:
+ vehicleSignal.update(isHazardLightOn: dp["value"]);
+ break;
+ case VSPath.vehicleSelectedGear:
+ vehicleSignal.update(
+ selectedGear: numToGear(int.parse(dp["value"])));
+ break;
+ case VSPath.vehiclePerformanceMode:
+ vehicleSignal.update(performanceMode: dp['value']);
+ break;
+ case VSPath.vehicleTravelledDistance:
+ vehicleSignal.update(travelledDistance: dp['value']);
+ break;
+ case VSPath.vehicleTrunkLocked:
+ vehicleSignal.update(isTrunkLocked: dp['value']);
+ break;
+ case VSPath.vehicleTrunkOpen:
+ vehicleSignal.update(isTrunkOpen: dp['value']);
+ break;
+ case VSPath.vehicleAmbientAirTemperature:
+ vehicleSignal.update(ambientAirTemp: dp['value']);
+ break;
+ case VSPath.vehicleMIL:
+ vehicleSignal.update(isMILon: dp['value']);
+ break;
+ case VSPath.vehicleCruiseControlError:
+ vehicleSignal.update(isCruiseControlError: dp['value']);
+ break;
+ case VSPath.vehicleCruiseControlSpeedSet:
+ vehicleSignal.update(
+ cruiseControlSpeed: double.parse(dp['value']));
+ break;
+ case VSPath.vehicleCruiseControlSpeedisActive:
+ vehicleSignal.update(isCruiseControlActive: dp['value']);
+ break;
+ case VSPath.vehicleBatteryChargingStatus:
+ vehicleSignal.update(isBatteryCharging: dp['value']);
+ break;
+ //
+ case VSPath.steeringCruiseEnable:
+ if (dp['value']) {
+ if (vehicleSignal.state.isSteeringCruiseEnable) {
+ vehicleSignal.update(isSteeringCruiseEnable: false);
+ vehicleSignal.update(isSteeringCruiseSet: false);
+ } else {
+ vehicleSignal.update(isSteeringCruiseEnable: dp['value']);
+ }
+ }
+ break;
+ case VSPath.steeringCruiseSet:
+ if (dp['value'] &&
+ vehicleSignal.state.isSteeringCruiseEnable) {
+ vehicleSignal.update(isSteeringCruiseSet: dp['value']);
+ }
+ break;
+ case VSPath.steeringCruiseResume:
+ if (dp['value'] &&
+ vehicleSignal.state.isSteeringCruiseEnable) {
+ vehicleSignal.update(isSteeringCruiseSet: dp['value']);
+ }
+ break;
+ case VSPath.steeringCruiseCancel:
+ if (dp['value']) {
+ vehicleSignal.update(isSteeringCruiseSet: false);
+ }
+ break;
+ case VSPath.steeringInfo:
+ if (dp['value']) {
+ vehicleSignal.update(
+ isSteeringInfo: !vehicleSignal.state.isSteeringInfo);
+ }
+ break;
+ case VSPath.steeringLaneDepWarn:
+ if (dp['value']) {
+ vehicleSignal.update(
+ isSteeringLaneWarning:
+ !(vehicleSignal.state.isSteeringLaneWarning));
+ }
+ break;
+ case VSPath.vehicleDistanceUnit:
+ vehicleSignal.update(vehicleDistanceUnit: dp['value']);
+ break;
+ //
+ case VSPath.vehicleCurrLat:
+ vehicleSignal.update(currLat: double.parse(dp['value']));
+ break;
+ case VSPath.vehicleCurrLng:
+ vehicleSignal.update(currLng: double.parse(dp['value']));
+ break;
+ case VSPath.vehicleDesLat:
+ vehicleSignal.update(desLat: double.parse(dp['value']));
+ polylineDBNotifier.update(currPolyLineList: []);
+
+ break;
+ case VSPath.vehicleDesLng:
+ vehicleSignal.update(desLng: double.parse(dp['value']));
+ polylineDBNotifier.update(currPolyLineList: []);
+ break;
+ default:
+ print("$path Not Available yet!");
+ }
+ } else {
+ print("ERROR:Value not available yet! Set Value of $path");
+ }
+ } else {
+ print("ERROR:'value': Key not found!");
+ }
+ } else if ((!dataMap["data"] as Map<String, dynamic>)
+ .containsKey("path")) {
+ print("ERROR:'path':key not found !");
+ } else if ((dataMap["data"] as Map<String, dynamic>)
+ .containsKey("dp")) {
+ print("ERROR:'dp':key not found !");
+ }
+ } else {
+ print("ERROR:'data':key not found!");
+ }
+ }
+ }
+}
+
+void updatePolyline(WidgetRef ref, vehicleSignal, polyLineState) {
+ getJsonData(ref, vehicleSignal.state.currLat, vehicleSignal.state.currLng,
+ vehicleSignal.state.desLat, vehicleSignal.state.desLng)
+ .then((polylineList) {
+ polyLineState.update(
+ currPolyLineList: polylineList
+ .map((element) => LatLng(element[1], element[0]))
+ .toList());
+ });
+}
diff --git a/lib/vehicle_signal/vehicle_signal_model.dart b/lib/vehicle_signal/vehicle_signal_model.dart new file mode 100644 index 0000000..90f76e7 --- /dev/null +++ b/lib/vehicle_signal/vehicle_signal_model.dart @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0
+
+class VehicleSignal {
+ VehicleSignal({
+ required this.speed,
+ required this.rpm,
+ required this.fuelLevel,
+ required this.coolantTemp,
+ required this.isLeftIndicator,
+ required this.isRightIndicator,
+ required this.selectedGear,
+ required this.isLowBeam,
+ required this.isHighBeam,
+ required this.isHazardLightOn,
+ required this.travelledDistance,
+ required this.isParkingOn,
+ required this.performanceMode,
+ required this.ambientAirTemp,
+ required this.cruiseControlSpeed,
+ required this.isCruiseControlActive,
+ required this.isCruiseControlError,
+ required this.isMILon,
+ required this.isTrunkLocked,
+ required this.isTrunkOpen,
+ required this.isBatteryCharging,
+ //steering switches
+ required this.vehicleDistanceUnit,
+ required this.isSteeringCruiseEnable,
+ required this.isSteeringCruiseSet,
+ required this.isSteeringCruiseResume,
+ required this.isSteeringCruiseCancel,
+ required this.isSteeringLaneWarning,
+ required this.isSteeringInfo,
+ // map coordinates
+ required this.currLat,
+ required this.currLng,
+ required this.desLat,
+ required this.desLng,
+ });
+ final double speed;
+ final double rpm;
+ final double fuelLevel;
+ final double coolantTemp;
+ final double cruiseControlSpeed;
+ final bool isLeftIndicator;
+ final bool isRightIndicator;
+ final String selectedGear;
+ final String performanceMode;
+ final String ambientAirTemp;
+ final bool isLowBeam;
+ final bool isHighBeam;
+ final bool isParkingOn;
+ final bool isHazardLightOn;
+ final bool isTrunkOpen;
+ final bool isTrunkLocked;
+ final bool isMILon;
+ final bool isCruiseControlActive;
+ final bool isCruiseControlError;
+ final bool isBatteryCharging;
+ final double travelledDistance;
+
+ final String vehicleDistanceUnit;
+ final bool isSteeringCruiseEnable;
+ final bool isSteeringCruiseSet;
+ final bool isSteeringCruiseResume;
+ final bool isSteeringCruiseCancel;
+ final bool isSteeringLaneWarning;
+ final bool isSteeringInfo;
+
+ final double currLat;
+ final double currLng;
+ final double desLat;
+ final double desLng;
+
+ VehicleSignal copyWith({
+ double? speed,
+ double? rpm,
+ double? fuelLevel,
+ double? coolantTemp,
+ bool? isLeftIndicator,
+ bool? isRightIndicator,
+ String? selectedGear,
+ String? performanceMode,
+ String? ambientAirTemp,
+ bool? isLowBeam,
+ bool? isHighBeam,
+ bool? isHazardLightOn,
+ bool? isParkingOn,
+ bool? isTrunkLocked,
+ bool? isTrunkOpen,
+ bool? isMILon,
+ bool? isCruiseControlError,
+ bool? isCruiseControlActive,
+ bool? isBatteryCharging,
+ double? travelledDistance,
+ double? cruiseControlSpeed,
+ // Steering
+ String? vehicleDistanceUnit,
+ bool? isSteeringCruiseEnable,
+ bool? isSteeringCruiseSet,
+ bool? isSteeringCruiseResume,
+ bool? isSteeringCruiseCancel,
+ bool? isSteeringLaneWarning,
+ bool? isSteeringInfo,
+ // map coordinates
+ double? currLat,
+ double? currLng,
+ double? desLat,
+ double? desLng,
+ }) {
+ return VehicleSignal(
+ speed: speed ?? (this.speed),
+ rpm: rpm ?? this.rpm,
+ fuelLevel: fuelLevel ?? this.fuelLevel,
+ coolantTemp: coolantTemp ?? this.coolantTemp,
+ isLeftIndicator: isLeftIndicator ?? this.isLeftIndicator,
+ isRightIndicator: isRightIndicator ?? this.isRightIndicator,
+ selectedGear: selectedGear ?? this.selectedGear,
+ isLowBeam: isLowBeam ?? this.isLowBeam,
+ isHighBeam: isHighBeam ?? this.isHighBeam,
+ isHazardLightOn: isHazardLightOn ?? this.isHazardLightOn,
+ travelledDistance: travelledDistance ?? this.travelledDistance,
+ isParkingOn: isParkingOn ?? this.isParkingOn,
+ performanceMode: performanceMode ?? this.performanceMode,
+ isTrunkLocked: isTrunkLocked ?? this.isTrunkLocked,
+ isTrunkOpen: isTrunkOpen ?? this.isTrunkOpen,
+ ambientAirTemp: ambientAirTemp ?? this.ambientAirTemp,
+ isMILon: isMILon ?? this.isMILon,
+ isCruiseControlActive:
+ isCruiseControlActive ?? this.isCruiseControlActive,
+ cruiseControlSpeed: cruiseControlSpeed ?? this.cruiseControlSpeed,
+ isCruiseControlError: isCruiseControlError ?? this.isCruiseControlError,
+ isBatteryCharging: isBatteryCharging ?? this.isBatteryCharging,
+ isSteeringCruiseEnable:
+ isSteeringCruiseEnable ?? this.isSteeringCruiseEnable,
+ isSteeringCruiseSet: isSteeringCruiseSet ?? this.isSteeringCruiseSet,
+ isSteeringCruiseResume:
+ isSteeringCruiseResume ?? this.isSteeringCruiseResume,
+ isSteeringCruiseCancel:
+ isSteeringCruiseCancel ?? this.isSteeringCruiseCancel,
+ isSteeringInfo: isSteeringInfo ?? this.isSteeringInfo,
+ isSteeringLaneWarning:
+ isSteeringLaneWarning ?? this.isSteeringLaneWarning,
+ vehicleDistanceUnit: vehicleDistanceUnit ?? this.vehicleDistanceUnit,
+ //
+ currLat: currLat ?? this.currLat,
+ currLng: currLng ?? this.currLng,
+ desLat: desLat ?? this.desLat,
+ desLng: desLng ?? this.desLng,
+ );
+ }
+}
diff --git a/lib/vehicle_signal/vehicle_signal_path.dart b/lib/vehicle_signal/vehicle_signal_path.dart new file mode 100644 index 0000000..72b016d --- /dev/null +++ b/lib/vehicle_signal/vehicle_signal_path.dart @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0
+
+class VSPath {
+ static const String vehicleSpeed = "Vehicle.Speed";
+ static const String vehicleEngineRPM =
+ "Vehicle.Powertrain.CombustionEngine.Engine.Speed";
+ static const String vehicleFuelLevel = "Vehicle.Powertrain.FuelSystem.Level";
+ static const String vehicleCoolantTemp =
+ "Vehicle.Powertrain.CombustionEngine.Engine.ECT";
+ static const String vehicleLeftIndicator =
+ "Vehicle.Body.Lights.IsLeftIndicatorOn";
+ static const String vehicleRightIndicator =
+ "Vehicle.Body.Lights.IsRightIndicatorOn";
+ //Selected Gear output=> 0=Neutral, 1/2/..=Forward, -1/..=Reverse, 126=Park, 127=Drive
+ static const String vehicleSelectedGear =
+ "Vehicle.Powertrain.Transmission.SelectedGear";
+ static const String vehicleLowBeamOn = "Vehicle.Body.Lights.IsLowBeamOn";
+ static const String vehicleHighBeamOn = "Vehicle.Body.Lights.IsHighBeamOn";
+ static const String vehicleParkingLightOn = "Vehicle.Body.Lights.IsParkingOn";
+ static const String vehicleHazardLightOn = "Vehicle.Body.Lights.IsHazardOn";
+ static const String vehicleTravelledDistance = "Vehicle.TravelledDistance";
+ static const String vehicleTrunkLocked = "Vehicle.Body.Trunk.IsLocked";
+ static const String vehicleTrunkOpen = "Vehicle.Body.Trunk.IsOpen";
+ // \"normal\",\"sport\",\"economy\",\"snow\",\"rain\"]
+ static const String vehiclePerformanceMode =
+ "Vehicle.Powertrain.Transmission.PerformanceMode";
+ static const String vehicleAmbientAirTemperature =
+ "Vehicle.AmbientAirTemperature";
+ static const String vehicleMIL = "Vehicle.OBD.Status.MIL";
+ static const String vehicleCruiseControlError =
+ "Vehicle.ADAS.CruiseControl.Error";
+ static const String vehicleCruiseControlSpeedSet =
+ "Vehicle.ADAS.CruiseControl.SpeedSet";
+ static const String vehicleCruiseControlSpeedisActive =
+ "Vehicle.ADAS.CruiseControl.IsActive";
+ static const String vehicleBatteryChargingStatus =
+ "Vehicle.Powertrain.Battery.Charging.Status";
+
+ static const String steeringCruiseEnable =
+ "Vehicle.Cabin.SteeringWheel.Switches.CruiseEnable";
+ static const String steeringCruiseSet =
+ "Vehicle.Cabin.SteeringWheel.Switches.CruiseSet";
+ static const String steeringCruiseResume =
+ "Vehicle.Cabin.SteeringWheel.Switches.CruiseResume";
+ static const String steeringCruiseCancel =
+ "Vehicle.Cabin.SteeringWheel.Switches.CruiseCancel";
+ static const String steeringLaneDepWarn =
+ "Vehicle.Cabin.SteeringWheel.Switches.LaneDepartureWarning";
+ static const String steeringInfo =
+ "Vehicle.Cabin.SteeringWheel.Switches.Info";
+ static const String vehicleDistanceUnit =
+ "Vehicle.Cabin.Infotainment.HMI.DistanceUnit";
+
+ static const String vehicleCurrLat =
+ "Vehicle.Cabin.Infotainment.Navigation.CurrentLocation.Latitude";
+ static const String vehicleCurrLng =
+ "Vehicle.Cabin.Infotainment.Navigation.CurrentLocation.Longitude";
+ static const String vehicleDesLat =
+ "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude";
+ static const String vehicleDesLng =
+ "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude";
+}
diff --git a/lib/vehicle_signal/vehicle_signal_provider.dart b/lib/vehicle_signal/vehicle_signal_provider.dart new file mode 100644 index 0000000..e75afff --- /dev/null +++ b/lib/vehicle_signal/vehicle_signal_provider.dart @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0
+
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_cluster_dashboard/vehicle_signal/vehicle_signal_model.dart';
+
+final vehicleSignalProvider =
+ StateNotifierProvider<VehicleSignalNotifier, VehicleSignal>(
+ (ref) => VehicleSignalNotifier(),
+);
+
+class VehicleSignalNotifier extends StateNotifier<VehicleSignal> {
+ VehicleSignalNotifier() : super(_initialValue);
+ static final VehicleSignal _initialValue = VehicleSignal(
+ speed: 140,
+ rpm: 7000,
+ fuelLevel: 90,
+ coolantTemp: 90,
+ isLeftIndicator: false,
+ isRightIndicator: false,
+ selectedGear: "P",
+ performanceMode: "normal",
+ isHazardLightOn: false,
+ isHighBeam: true,
+ isLowBeam: false,
+ isParkingOn: true,
+ travelledDistance: 888,
+ ambientAirTemp: '25',
+ cruiseControlSpeed: 60,
+ isCruiseControlActive: false,
+ isCruiseControlError: false,
+ isMILon: false,
+ isTrunkLocked: true,
+ isTrunkOpen: false,
+ isBatteryCharging: true,
+ isSteeringCruiseEnable: false,
+ isSteeringCruiseSet: false,
+ isSteeringCruiseResume: false,
+ isSteeringCruiseCancel: false,
+ isSteeringInfo: false,
+ isSteeringLaneWarning: false,
+ vehicleDistanceUnit: 'km',
+ currLat: 31.71,
+ currLng: 76.95,
+ desLat: 31.71,
+ desLng: 76.95,
+ );
+ void update({
+ double? speed,
+ double? rpm,
+ double? fuelLevel,
+ double? coolantTemp,
+ bool? isLeftIndicator,
+ bool? isRightIndicator,
+ String? selectedGear,
+ String? performanceMode,
+ String? ambientAirTemp,
+ bool? isLowBeam,
+ bool? isHighBeam,
+ bool? isHazardLightOn,
+ bool? isMILon,
+ bool? isParkingOn,
+ bool? isTrunkOpen,
+ bool? isTrunkLocked,
+ bool? isCruiseControlActive,
+ bool? isCruiseControlError,
+ bool? isBatteryCharging,
+ double? travelledDistance,
+ double? cruiseControlSpeed,
+ //
+ String? vehicleDistanceUnit,
+ bool? isSteeringCruiseEnable,
+ bool? isSteeringCruiseSet,
+ bool? isSteeringCruiseResume,
+ bool? isSteeringCruiseCancel,
+ bool? isSteeringLaneWarning,
+ bool? isSteeringInfo,
+ //
+ double? currLat,
+ double? currLng,
+ double? desLat,
+ double? desLng,
+ }) {
+ state = state.copyWith(
+ speed: speed,
+ rpm: rpm,
+ fuelLevel: fuelLevel,
+ coolantTemp: coolantTemp,
+ isLeftIndicator: isLeftIndicator,
+ isRightIndicator: isRightIndicator,
+ selectedGear: selectedGear,
+ isLowBeam: isLowBeam,
+ isHighBeam: isHighBeam,
+ isHazardLightOn: isHazardLightOn,
+ travelledDistance: travelledDistance,
+ performanceMode: performanceMode,
+ isParkingOn: isParkingOn,
+ isTrunkOpen: isTrunkOpen,
+ isTrunkLocked: isTrunkLocked,
+ isMILon: isMILon,
+ ambientAirTemp: ambientAirTemp,
+ isCruiseControlActive: isCruiseControlActive,
+ isCruiseControlError: isCruiseControlError,
+ cruiseControlSpeed: cruiseControlSpeed,
+ isBatteryCharging: isBatteryCharging,
+ //
+ isSteeringCruiseEnable: isSteeringCruiseEnable,
+ isSteeringCruiseSet: isSteeringCruiseSet,
+ isSteeringCruiseResume: isSteeringCruiseResume,
+ isSteeringCruiseCancel: isSteeringCruiseCancel,
+ isSteeringInfo: isSteeringInfo,
+ isSteeringLaneWarning: isSteeringLaneWarning,
+ vehicleDistanceUnit: vehicleDistanceUnit,
+ //
+ currLat: currLat,
+ currLng: currLng,
+ desLat: desLat,
+ desLng: desLng,
+ );
+ }
+}
|