diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/bottom_panel.dart | 84 | ||||
-rw-r--r-- | lib/config.dart | 115 | ||||
-rw-r--r-- | lib/homescreen.dart | 125 | ||||
-rw-r--r-- | lib/homescreen_model.dart | 68 | ||||
-rw-r--r-- | lib/main.dart | 18 | ||||
-rw-r--r-- | lib/page_apps.dart | 43 | ||||
-rw-r--r-- | lib/page_dashboard.dart | 218 | ||||
-rw-r--r-- | lib/page_hvac.dart | 313 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_config.dart | 29 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_connected_widget.dart | 65 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_connection_widget.dart | 41 | ||||
-rw-r--r-- | lib/vehicle-signals/viss_methods.dart | 116 | ||||
-rw-r--r-- | lib/vehicle-signals/vss_path.dart | 44 | ||||
-rw-r--r-- | lib/vehicle-signals/vss_providers.dart | 130 | ||||
-rw-r--r-- | lib/volume_slider.dart | 50 |
15 files changed, 766 insertions, 693 deletions
diff --git a/lib/bottom_panel.dart b/lib/bottom_panel.dart new file mode 100644 index 0000000..f61c59d --- /dev/null +++ b/lib/bottom_panel.dart @@ -0,0 +1,84 @@ +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:jovial_svg/jovial_svg.dart'; +import 'package:flutter_homescreen/volume_slider.dart'; + +final StackIndexProvider = StateProvider<int>((ref) => 0); + +class BottomPanelWidget extends ConsumerStatefulWidget { + final double height; + final Color? color; + + BottomPanelWidget({Key? key, required this.height, required this.color}) + : super(key: key); + + @override + _BottomPanelWidgetState createState() => _BottomPanelWidgetState(); +} + +class _BottomPanelWidgetState extends ConsumerState<BottomPanelWidget> { + final iconColor = const Color(0xff4ee6f5); + late RestartableTimer timer; + + initState() { + super.initState(); + timer = new RestartableTimer(Duration(seconds: 3), _timerExpired); + } + + void _timerExpired() { + ref.read(StackIndexProvider.notifier).state = 0; + timer.cancel(); + } + + @override + Widget build(BuildContext context) { + final int index = ref.watch(StackIndexProvider); + + return SizedBox( + height: widget.height, + child: IndexedStack(index: index, children: <Widget>[ + GestureDetector( + child: Container( + color: widget.color, + child: Align( + alignment: Alignment.center, + child: ScalableImageWidget.fromSISource( + si: ScalableImageSource.fromSvg( + rootBundle, 'images/Utility_Logo_Grey-01.svg')))), + onTap: () { + ref.read(StackIndexProvider.notifier).state = 1; + timer.reset(); + }, + ), + Container( + color: widget.color, + child: Padding( + padding: EdgeInsets.fromLTRB(24, 8, 24, 8), + child: Stack(children: [ + Column(children: [ + Center( + child: Text("Volume", + style: TextStyle( + fontSize: 32, color: Colors.white))), + Spacer() + ]), + Row(children: [ + Container(width: 24), + Text("0 %", + style: TextStyle(fontSize: 32, color: Colors.white)), + Expanded( + child: VolumeSlider( + thumbColor: Colors.white, + activeColor: iconColor, + inactiveColor: Colors.grey.shade600, + activityTimer: timer)), + Text("100 %", + style: TextStyle(fontSize: 32, color: Colors.white)), + Container(width: 24) + ]), + ]))) + ])); + } +} diff --git a/lib/config.dart b/lib/config.dart new file mode 100644 index 0000000..6da02ed --- /dev/null +++ b/lib/config.dart @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yaml/yaml.dart'; + +import 'vehicle-signals/viss_connection_widget.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(ConfigStateprovider.notifier); + + String configFilePath = '/etc/xdg/AGL/homescreen_config.yaml'; + + final configFile = File(configFilePath); + configFile.readAsString().then((content) { + final dynamic yamlMap = loadYaml(content); + configStateProvider.update( + read: true, + hostname: yamlMap['hostname'], + port: yamlMap['port'], + kuksaAuthToken: yamlMap['kuskaAuthToken'], + ); + }).catchError((content) { + configStateProvider.update(read: true); + }); + }); + } + + @override + Widget build(BuildContext context) { + final config = ref.watch(ConfigStateprovider); + if (!config.read) { + return Container( + child: const Text("Reading configuration file!", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white))); + } else if (config.hostname == "" || + config.port == 0 || + config.kuksaAuthToken == "") { + return Container( + child: const Text("Invalid configuration file!", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white))); + } + return VISServerConnectionWidget(client: widget.client); + } +} + +class Config { + Config({ + required this.read, + required this.hostname, + required this.port, + required this.kuksaAuthToken, + }); + final bool read; + final String hostname; + final int port; + final String kuksaAuthToken; + + Config copywith({ + bool? read, + String? hostname, + int? port, + String? kuksaAuthToken, + }) => + Config( + read: read ?? this.read, + hostname: hostname ?? this.hostname, + port: port ?? this.port, + kuksaAuthToken: kuksaAuthToken ?? this.kuksaAuthToken, + ); +} + +final ConfigStateprovider = StateNotifierProvider<ConfigStateNotifier, Config>( + (ref) => ConfigStateNotifier()); + +class ConfigStateNotifier extends StateNotifier<Config> { + ConfigStateNotifier() : super(_initialValue); + static final Config _initialValue = Config( + read: false, + hostname: "", + port: 0, + kuksaAuthToken: "", + ); + void update({ + bool? read, + String? hostname, + int? port, + String? kuksaAuthToken, + }) { + state = state.copywith( + read: read, + hostname: hostname, + port: port, + kuksaAuthToken: kuksaAuthToken, + ); + } +} diff --git a/lib/homescreen.dart b/lib/homescreen.dart index d666bd3..7501292 100644 --- a/lib/homescreen.dart +++ b/lib/homescreen.dart @@ -1,17 +1,18 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_homescreen/config.dart'; import 'package:grpc/grpc.dart'; -import 'package:jovial_svg/jovial_svg.dart'; import 'package:flutter_homescreen/generated/applauncher.pbgrpc.dart'; -import 'package:flutter_homescreen/homescreen_model.dart'; import 'package:flutter_homescreen/page_apps.dart'; import 'package:flutter_homescreen/widget_clock.dart'; +import 'package:flutter_homescreen/bottom_panel.dart'; enum PageIndex { home, dashboard, hvac, media } class Homescreen extends StatefulWidget { - Homescreen({Key? key}) : super(key: key); + Homescreen({Key? key, required this.client}) : super(key: key); + final HttpClient client; @override _HomescreenState createState() => _HomescreenState(); @@ -49,7 +50,8 @@ class _HomescreenState extends State<Homescreen> with TickerProviderStateMixin { activateApp(String id) async { try { - agl_shell_channel.invokeMethod('activate_app', { 'app_id': id, 'index': 0 }); + agl_shell_channel + .invokeMethod('activate_app', {'app_id': id, 'index': 0}); } catch (e) { print('Could not invoke flutter/agl_shell/activate_app: $e'); } @@ -82,7 +84,7 @@ class _HomescreenState extends State<Homescreen> with TickerProviderStateMixin { } } } - } catch(e) { + } catch (e) { print(e); } } @@ -149,9 +151,9 @@ class _HomescreenState extends State<Homescreen> with TickerProviderStateMixin { var railSize = 160.0; var iconSize = railSize / 2; var foregroundColor = Theme.of(context) - .navigationBarTheme - .iconTheme! - .resolve({MaterialState.pressed})!.color!; + .navigationBarTheme + .iconTheme! + .resolve({MaterialState.pressed})!.color!; return Scaffold( body: Column( @@ -211,9 +213,8 @@ class _HomescreenState extends State<Homescreen> with TickerProviderStateMixin { endIndent: railSize / 16)), Container( color: NavigationBarTheme.of(context).backgroundColor, - child: ClockWidget( - textColor: foregroundColor, - size: railSize)), + child: + ClockWidget(textColor: foregroundColor, size: railSize)), Container( color: NavigationBarTheme.of(context).backgroundColor, child: VerticalDivider( @@ -225,70 +226,64 @@ class _HomescreenState extends State<Homescreen> with TickerProviderStateMixin { Container( color: NavigationBarTheme.of(context).backgroundColor, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: <Widget>[ - Icon(Icons.bluetooth, color: foregroundColor, size: 32), - Icon(Icons.wifi, color: foregroundColor, size: 32), - Icon(Icons.signal_cellular_4_bar, color: foregroundColor, size: 32), - ])), + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: <Widget>[ + Icon(Icons.bluetooth, color: foregroundColor, size: 32), + Icon(Icons.wifi, color: foregroundColor, size: 32), + Icon(Icons.signal_cellular_4_bar, + color: foregroundColor, size: 32), + ])), SizedBox( width: 16, child: Container( - color: - Theme.of(context).navigationBarTheme.backgroundColor)), + color: Theme.of(context) + .navigationBarTheme + .backgroundColor)), ]), ), // This is the main content. Expanded( - child: ChangeNotifierProvider( - // See: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple - // Also: https://docs.flutter.dev/development/data-and-backend/state-mgmt/options - create: (context) => HomescreenModel(), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - reverseDuration: const Duration(milliseconds: 500), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - transitionBuilder: (Widget child, Animation<double> animation) { - if (child.key != ValueKey(_selectedIndex)) { - return FadeTransition( - opacity: Tween<double>(begin: 1.0, end: 1.0) - .animate(animation), - child: child, - ); - } - Offset beginOffset = new Offset( - 0.0, (_selectedIndex > _previousIndex ? 1.0 : -1.0)); - return SlideTransition( - position: - Tween<Offset>(begin: beginOffset, end: Offset.zero) - .animate(animation), - child: FadeTransition( - opacity: Tween<double>(begin: 0.0, end: 1.0).animate( - CurvedAnimation( - parent: animation, - curve: Interval(0.5, 1.0), - ), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + reverseDuration: const Duration(milliseconds: 500), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (Widget child, Animation<double> animation) { + if (child.key != ValueKey(_selectedIndex)) { + return FadeTransition( + opacity: + Tween<double>(begin: 1.0, end: 1.0).animate(animation), + child: child, + ); + } + Offset beginOffset = new Offset( + 0.0, (_selectedIndex > _previousIndex ? 1.0 : -1.0)); + return SlideTransition( + position: Tween<Offset>(begin: beginOffset, end: Offset.zero) + .animate(animation), + child: FadeTransition( + opacity: Tween<double>(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: animation, + curve: Interval(0.5, 1.0), ), - child: child, ), - ); - }, - child: _childForIndex(_selectedIndex), - ), + child: child, + ), + ); + }, + child: _childForIndex(_selectedIndex), ), ), - SizedBox( - height: railSize, - child: Container( - color: NavigationBarTheme.of(context).backgroundColor, - child: Align( - alignment: Alignment.center, - child: ScalableImageWidget.fromSISource( - si: ScalableImageSource.fromSvg(rootBundle, - 'images/Utility_Logo_Grey-01.svg')))) - ) + Stack(children: [ + BottomPanelWidget( + height: railSize, + color: NavigationBarTheme.of(context).backgroundColor), + Align( + alignment: Alignment.bottomLeft, + child: GetConfig(client: widget.client)) + ]), ], ), ); diff --git a/lib/homescreen_model.dart b/lib/homescreen_model.dart deleted file mode 100644 index 7c1a26f..0000000 --- a/lib/homescreen_model.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:collection'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -enum SwitchId { - hvacLeftSeat, - hvacRigthSeat, - hvacAc, - hvacAuto, - hvacCirculation, - hvacFan, - hvacAirDown, - hvacAirUp, - hvacFront, - hvacRear, -} - -enum TemperatureId { leftSeat, rightSeat } - -class HomescreenModel extends ChangeNotifier { - // HVAC page - - // fan speed - double _fanSpeed = 20; - - double get fanSpeed => _fanSpeed; - - set fanSpeed(double newhvacFanSpeed) { - _fanSpeed = newhvacFanSpeed; - notifyListeners(); - } - - // switch buttons - HashMap _switches = new HashMap<SwitchId, bool>(); - - bool getSwitchState(SwitchId id) => _switches[id] ?? false; - - void setSwitchState(SwitchId id, bool newValue) { - _switches[id] = newValue; - notifyListeners(); - } - - void flipSwitch(SwitchId id) { - _switches[id] = !_switches[id]; - notifyListeners(); - } - - // temperatures - HashMap _temperatures = new HashMap<TemperatureId, int>(); - - int getTemperature(TemperatureId id) => _temperatures[id] ?? 22; - - void setTemperature(TemperatureId id, int newTemp) { - _temperatures[id] = newTemp; - notifyListeners(); - } - - HomescreenModel() { - // initialize the values - for (var id in SwitchId.values) { - _switches[id] = false; - } - for (var id in TemperatureId.values) { - _temperatures[id] = 22; - } - } -} diff --git a/lib/main.dart b/lib/main.dart index c09f328..f5ea0f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,22 @@ +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_homescreen/homescreen.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_homescreen/vehicle-signals/viss_config.dart'; -void main() { - runApp(MyApp()); +Future<void> main() async { + WidgetsFlutterBinding.ensureInitialized(); + HttpClient client = await initializeClient(); + runApp(ProviderScope(child: MyApp(client: client))); } -class MyApp extends StatelessWidget { +class MyApp extends ConsumerWidget { + MyApp({Key? key, required this.client}) : super(key: key); + final HttpClient client; + // This widget is the root of your application. @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { const navBarColor = const Color(0xff181818); const navBarIconColor = Colors.white; @@ -24,7 +32,7 @@ class MyApp extends StatelessWidget { color: navBarIconColor, ))), ), - home: Homescreen(), + home: Homescreen(client: client), ); } } diff --git a/lib/page_apps.dart b/lib/page_apps.dart index 39fb754..9ea6c92 100644 --- a/lib/page_apps.dart +++ b/lib/page_apps.dart @@ -1,9 +1,7 @@ import 'dart:io'; import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:jovial_svg/jovial_svg.dart'; -import 'package:flutter_homescreen/homescreen.dart'; import 'package:flutter_homescreen/layout_size_helper.dart'; import 'package:flutter_homescreen/generated/applauncher.pb.dart'; @@ -11,7 +9,7 @@ import 'package:flutter_homescreen/generated/applauncher.pb.dart'; class AppsPage extends StatefulWidget { final Future<List<AppInfo>> Function() getApps; final Function(String id) startApp; - + const AppsPage({Key? key, required this.getApps, required this.startApp}) : super(key: key); @@ -26,7 +24,7 @@ class _AppsPageState extends State<AppsPage> { initState() { widget.getApps().then((val) => setState(() { apps = val; - })); + })); super.initState(); } @@ -94,8 +92,8 @@ class _AppsPageEntryState extends State<_AppsPageEntry> { void initState() { if (widget.iconPath.endsWith(".svg")) { readSvgIcon().then((val) => setState(() { - svgIconLoaded = val; - })); + svgIconLoaded = val; + })); } super.initState(); } @@ -104,7 +102,8 @@ class _AppsPageEntryState extends State<_AppsPageEntry> { if (widget.iconPath.endsWith(".svg")) { var iconFile = File(widget.iconPath); if (await iconFile.exists()) { - svgIcon = await ScalableImage.fromSvgStream(iconFile.openRead().transform(utf8.decoder)); + svgIcon = await ScalableImage.fromSvgStream( + iconFile.openRead().transform(utf8.decoder)); return true; } } @@ -114,25 +113,21 @@ class _AppsPageEntryState extends State<_AppsPageEntry> { Widget buildIcon() { if (svgIconLoaded) { return GestureDetector( - onTap: () { - widget.appSelected(widget.id); - }, - child: SizedBox.expand( - child: ScalableImageWidget(si: svgIcon)) - ); + onTap: () { + widget.appSelected(widget.id); + }, + child: SizedBox.expand(child: ScalableImageWidget(si: svgIcon))); } else { return OutlinedButton( - style: ElevatedButton.styleFrom( - shape: CircleBorder(), - padding: EdgeInsets.all(8), - side: BorderSide(width: 4, color: iconColor), - ), - onPressed: () { - widget.appSelected(widget.id); - }, - child: Icon(Icons.question_mark, - color: iconColor, - size: 160.0)); + style: ElevatedButton.styleFrom( + shape: CircleBorder(), + padding: EdgeInsets.all(8), + side: BorderSide(width: 4, color: iconColor), + ), + onPressed: () { + widget.appSelected(widget.id); + }, + child: Icon(Icons.question_mark, color: iconColor, size: 160.0)); } } diff --git a/lib/page_dashboard.dart b/lib/page_dashboard.dart deleted file mode 100644 index c354415..0000000 --- a/lib/page_dashboard.dart +++ /dev/null @@ -1,218 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_homescreen/layout_size_helper.dart'; - -// The Dashboard page. -class DashboardPage extends StatefulWidget { - DashboardPage({Key? key}) : super(key: key); - - @override - _DashboardPageState createState() => _DashboardPageState(); -} - -class _DashboardPageState extends State<DashboardPage> { - late Timer _timer; - - double speed = 20; - // between 0 and 1.0 - double rpm = 0.2; - // between 0 and 1.0 - double fuel = 0.2; - - @override - void initState() { - _timer = new Timer.periodic( - Duration(milliseconds: 10), - (Timer timer) { - setState(() { - double now = DateTime.now().millisecondsSinceEpoch / 2000; - speed = 50 + 40 * sin(now); - rpm = 0.5 + sin(now) / 3.0; - fuel = 0.6 + cos(now) / 4.0; - }); - }, - ); - // Animate the values for the demo. - // Eventually, we will get the state of the car from the API. - super.initState(); - } - - @override - void dispose() { - _timer.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [Colors.teal.shade900, Colors.grey.shade900])), - constraints: BoxConstraints.expand(), - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - height: sizeHelper.largeIconSize * 1, - width: sizeHelper.largeIconSize * 2, - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).primaryColorLight, - width: sizeHelper.defaultBorder, - ), - borderRadius: BorderRadius.all( - Radius.circular(sizeHelper.largeIconSize / 2.0))), - child: Center( - child: Text( - '${speed.floor()} kpm', - style: Theme.of(context).textTheme.headline2, - ), - ), - ), - _RPMWidget(rpm), - _FuelWidget(fuel), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _TireWidget('Left front tire', 21, CrossAxisAlignment.end), - _TireWidget('Left rear tire', 23, CrossAxisAlignment.end), - ], - ), - Image.asset( - 'images/HMI_Dashboard_Car_720.png', - width: 2.0 * sizeHelper.largeIconSize, - fit: BoxFit.contain, - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _TireWidget('Right front tire', 21, CrossAxisAlignment.start), - _TireWidget('Right rear tire', 23, CrossAxisAlignment.start), - ], - ), - ], - ), - ], - ), - ); - } -} - -// The RPM indicator. -class _RPMWidget extends StatelessWidget { - final double rpm; - - _RPMWidget(this.rpm); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Stack( - alignment: Alignment.center, - children: [ - Text( - 'RPM', - style: Theme.of(context).textTheme.headline4, - ), - Container( - height: sizeHelper.largeIconSize * 1.5, - width: sizeHelper.largeIconSize * 1.5, - child: RotatedBox( - quarterTurns: 2, - child: CircularProgressIndicator( - value: rpm, - color: HSLColor.fromColor(Colors.redAccent) - .withSaturation(rpm) - .toColor(), - strokeWidth: sizeHelper.largeIconSize / 2.0, - semanticsLabel: 'RPM indicator', - ), - ), - ) - ], - ); - } -} - -// The fuel indicator. -class _FuelWidget extends StatelessWidget { - final double fuel; - - _FuelWidget(this.fuel); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Row( - children: [ - Container( - height: sizeHelper.largeIconSize / 4.0, - width: sizeHelper.largeIconSize / 2.0, - child: Center( - child: Text( - 'Fuel', - style: Theme.of(context).textTheme.headline4, - ), - ), - ), - Container( - height: sizeHelper.largeIconSize / 4.0, - width: sizeHelper.largeIconSize * 1.5, - margin: EdgeInsets.fromLTRB( - 0, sizeHelper.largePadding, 0, sizeHelper.largePadding), - child: LinearProgressIndicator( - value: fuel, - color: HSLColor.fromColor(Colors.blueAccent) - .withSaturation(fuel) - .toColor(), - semanticsLabel: 'RPM indicator', - ), - ) - ], - ); - } -} - -// The small indicator for the state of each tire. -class _TireWidget extends StatelessWidget { - final String label; - final int value; - final CrossAxisAlignment crossAlign; - - _TireWidget(this.label, this.value, this.crossAlign); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: crossAlign, - children: [ - Text( - label, - style: Theme.of(context).textTheme.headline6, - ), - Text( - '$value PSI', - style: Theme.of(context).textTheme.headline4, - ), - ], - ); - } -} diff --git a/lib/page_hvac.dart b/lib/page_hvac.dart deleted file mode 100644 index 65d77b6..0000000 --- a/lib/page_hvac.dart +++ /dev/null @@ -1,313 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_homescreen/homescreen_model.dart'; -import 'package:flutter_homescreen/layout_size_helper.dart'; -import 'package:flutter_homescreen/switchable_image.dart'; -import 'package:numberpicker/numberpicker.dart'; -import 'package:provider/provider.dart'; - -// image assets -const String LEFT_SEAT = 'images/HMI_HVAC_Left_Chair_ON.png'; -const String RIGHT_SEAT = 'images/HMI_HVAC_Right_Chair_ON.png'; -const String CIRCULATION = 'images/HMI_HVAC_Circulation_Active.png'; -const String AIRDOWN = 'images/HMI_HVAC_AirDown_Active.png'; -const String AIRUP = 'images/HMI_HVAC_AirUp_Active.png'; -const String FRONT = 'images/HMI_HVAC_Front_Active.png'; -const String REAR = 'images/HMI_HVAC_Rear_Active.png'; - -// The page for heating, ventilation, and air conditioning. -class HVACPage extends StatelessWidget { - HVACPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - - Widget fanSpeedControl = Container( - padding: EdgeInsets.symmetric( - vertical: sizeHelper.defaultPadding, - horizontal: 3.0 * sizeHelper.defaultPadding, - ), - child: Row( - children: [ - Expanded( - flex: 3, - child: HVACFanSpeed(), - ), - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerLeft, - child: Image.asset('images/HMI_HVAC_Fan_Icon.png', - width: sizeHelper.defaultIconSize, - height: sizeHelper.defaultIconSize, - fit: BoxFit.contain)), - ), - ], - - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - ), - ); - - Widget centerView = Container( - padding: EdgeInsets.all(sizeHelper.defaultPadding), - child: Column( - children: [ - _HVACToggleButton( - label: 'A/C', - switchId: SwitchId.hvacAc, - ), - _HVACToggleButton( - label: 'Auto', - switchId: SwitchId.hvacAuto, - ), - _HVACToggleButton( - imageAssetId: CIRCULATION, - switchId: SwitchId.hvacCirculation, - ), - ], - ), - ); - - Widget actions = - Consumer<HomescreenModel>(builder: (context, model, child) { - return Column( - children: [ - _ActionButton(switchId: SwitchId.hvacAirDown, imageAssetId: AIRDOWN), - SizedBox(height: sizeHelper.defaultPadding), - _ActionButton(switchId: SwitchId.hvacAirUp, imageAssetId: AIRUP), - SizedBox(height: sizeHelper.defaultPadding), - _ActionButton(switchId: SwitchId.hvacFront, imageAssetId: FRONT), - SizedBox(height: sizeHelper.defaultPadding), - _ActionButton(switchId: SwitchId.hvacRear, imageAssetId: REAR), - ], - ); - }); - - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [Colors.blueGrey.shade900, Colors.grey.shade900])), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - fanSpeedControl, - Row(children: [ - Expanded( - flex: 1, - child: _SeatButton( - switchId: SwitchId.hvacLeftSeat, - temperatureId: TemperatureId.leftSeat, - imageAssetId: LEFT_SEAT, - )), - Expanded(flex: 1, child: centerView), - Expanded( - flex: 1, - child: _SeatButton( - switchId: SwitchId.hvacRigthSeat, - temperatureId: TemperatureId.rightSeat, - imageAssetId: RIGHT_SEAT, - )), - Expanded(flex: 1, child: actions) - ]) - ], - )); - } -} - -// The temperature selector. -class _TemperatureSelector extends StatelessWidget { - final TemperatureId temperatureId; - - _TemperatureSelector({Key? key, required this.temperatureId}) - : super(key: key); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Consumer<HomescreenModel>( - builder: (context, model, child) { - return NumberPicker( - value: model.getTemperature(temperatureId), - minValue: 18, - maxValue: 25, - onChanged: (value) => model.setTemperature(temperatureId, value), - textStyle: DefaultTextStyle.of(context).style.copyWith( - color: Colors.teal.shade200, - fontSize: sizeHelper.baseFontSize, - ), - selectedTextStyle: DefaultTextStyle.of(context).style.copyWith( - fontSize: sizeHelper.baseFontSize * 1.5, - ), - itemHeight: sizeHelper.baseFontSize * 3, - itemWidth: sizeHelper.baseFontSize * 6, - ); - }, - ); - } -} - -/// The fan speed control. -class HVACFanSpeed extends StatelessWidget { - const HVACFanSpeed({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SliderTheme( - data: SliderThemeData( - thumbColor: Colors.greenAccent.shade700, - activeTrackColor: Colors.greenAccent.shade700, - inactiveTrackColor: Colors.blueGrey.shade200, - ), - child: Consumer<HomescreenModel>( - builder: (context, model, child) { - return Slider( - value: model.fanSpeed, - min: 0, - max: 300, - label: model.fanSpeed.round().toString(), - onChanged: (double newValue) { - model.fanSpeed = newValue; - }, - ); - }, - ), - ); - } -} - -// the button to enable A/C on each seat -class _SeatButton extends StatelessWidget { - final SwitchId switchId; - final TemperatureId temperatureId; - final String imageAssetId; - - const _SeatButton({ - Key? key, - required this.switchId, - required this.temperatureId, - required this.imageAssetId, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Container( - padding: EdgeInsets.all(sizeHelper.defaultPadding), - child: Column( - children: [ - Consumer<HomescreenModel>( - builder: (context, model, child) { - return IconButton( - onPressed: () => model.flipSwitch(switchId), - iconSize: sizeHelper.largeIconSize, - icon: SwitchableImage( - value: model.getSwitchState(switchId), - imageAssetId: imageAssetId, - width: sizeHelper.largeIconSize, - height: sizeHelper.largeIconSize, - ), - ); - }, - ), - SizedBox(height: sizeHelper.defaultPadding), - _TemperatureSelector(temperatureId: temperatureId), - ], - ), - ); - } -} - -// Each one of the large toggle buttons in the UI. -class _HVACToggleButton extends StatelessWidget { - final String? label; - final String? imageAssetId; - final SwitchId switchId; - - _HVACToggleButton( - {Key? key, required this.switchId, this.label, this.imageAssetId}) - : super(key: key); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - TextStyle buttonTextStyle = DefaultTextStyle.of(context).style.copyWith( - fontSize: sizeHelper.baseFontSize, - fontWeight: FontWeight.bold, - ); - TextStyle unselectedButtonTextStyle = buttonTextStyle.copyWith( - color: Colors.grey, - fontWeight: FontWeight.normal, - ); - - return Container( - width: sizeHelper.defaultButtonWidth, - height: sizeHelper.defaultButtonHeight, - margin: EdgeInsets.all(sizeHelper.defaultPadding), - child: Consumer<HomescreenModel>( - builder: (context, model, child) { - return OutlinedButton( - onPressed: () => model.flipSwitch(switchId), - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(sizeHelper.defaultButtonHeight / 4.0), - ), - side: BorderSide( - width: sizeHelper.defaultBorder, - color: - model.getSwitchState(switchId) ? Colors.green : Colors.grey, - style: BorderStyle.solid, - ), - ), - child: (imageAssetId != null) - ? SwitchableImage( - value: model.getSwitchState(switchId), - imageAssetId: imageAssetId ?? '', - width: sizeHelper.defaultIconSize, - height: sizeHelper.defaultIconSize, - ) - : Text( - label ?? '', - style: model.getSwitchState(switchId) - ? buttonTextStyle - : unselectedButtonTextStyle, - ), - ); - }, - ), - ); - } -} - -// Each one of the small action buttons. -class _ActionButton extends StatelessWidget { - final SwitchId switchId; - final String imageAssetId; - - const _ActionButton( - {Key? key, required this.switchId, required this.imageAssetId}) - : super(key: key); - - @override - Widget build(BuildContext context) { - var sizeHelper = LayoutSizeHelper(context); - return Consumer<HomescreenModel>( - builder: (context, model, child) { - return IconButton( - onPressed: () => model.flipSwitch(switchId), - iconSize: sizeHelper.defaultIconSize, - icon: SwitchableImage( - value: model.getSwitchState(switchId), - imageAssetId: imageAssetId, - width: sizeHelper.defaultIconSize, - height: sizeHelper.defaultIconSize, - ), - ); - }, - ); - } -} diff --git a/lib/vehicle-signals/viss_config.dart b/lib/vehicle-signals/viss_config.dart new file mode 100644 index 0000000..c2be5ee --- /dev/null +++ b/lib/vehicle-signals/viss_config.dart @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +//import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_homescreen/config.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +//import 'package:http/http.dart' as http; + +final sockConnectprovider = FutureProvider.family<WebSocket, HttpClient>( + (ref, client) => connect(client, ref)); + +Future<HttpClient> initializeClient() async { + SecurityContext ctx = SecurityContext.defaultContext; + + 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(ConfigStateprovider); + WebSocket socket = await WebSocket.connect( + "wss://${config.hostname}:${config.port}", + customClient: client); + return socket; +} diff --git a/lib/vehicle-signals/viss_connected_widget.dart b/lib/vehicle-signals/viss_connected_widget.dart new file mode 100644 index 0000000..dd3e4aa --- /dev/null +++ b/lib/vehicle-signals/viss_connected_widget.dart @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_homescreen/vehicle-signals/viss_config.dart'; +import 'package:flutter_homescreen/vehicle-signals/viss_methods.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_providers.dart'; + +class VISServerConnectedWidget extends ConsumerStatefulWidget { + const VISServerConnectedWidget( + {Key? key, required this.client, required this.socket}) + : super(key: key); + final WebSocket socket; + final HttpClient client; + + @override + ConsumerState<VISServerConnectedWidget> createState() => + _VISServerConnectedWidgetState(); +} + +class _VISServerConnectedWidgetState + extends ConsumerState<VISServerConnectedWidget> { + late Timer _timer; + + void _updateSocket() { + ref.read(VISServerSocketProvider.notifier).update(widget.socket); + } + + @override + void initState() { + super.initState(); + VISS.init(widget.socket, ref); + Future.delayed(Duration.zero, () => _updateSocket()); + _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) { + 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) { + return Container(); + } +} diff --git a/lib/vehicle-signals/viss_connection_widget.dart b/lib/vehicle-signals/viss_connection_widget.dart new file mode 100644 index 0000000..a2abee7 --- /dev/null +++ b/lib/vehicle-signals/viss_connection_widget.dart @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:io'; +import 'package:flutter_homescreen/vehicle-signals/viss_config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'viss_connected_widget.dart'; + +class VISServerConnectionWidget extends ConsumerWidget { + VISServerConnectionWidget({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 VISServerConnectedWidget(client: client, socket: this.socket); + }, + error: (e, stk) { + print(e); + ref.refresh(sockConnectprovider(client)); + return Container( + child: const Text('Connection Error', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white))); + }, + loading: () => Container( + child: Text('Connected', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white)))); + } +} diff --git a/lib/vehicle-signals/viss_methods.dart b/lib/vehicle-signals/viss_methods.dart new file mode 100644 index 0000000..8adcc80 --- /dev/null +++ b/lib/vehicle-signals/viss_methods.dart @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_homescreen/vehicle-signals/vss_providers.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_path.dart'; +import 'package:flutter_homescreen/config.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class VISS { + static const requestId = "test-id"; + + static void init(WebSocket socket, WidgetRef ref) { + authorize(socket, ref); + subscribe(socket, ref, VSSPath.vehicleMediaVolume); + } + + static void update(WebSocket socket, WidgetRef ref) { + get(socket, ref, VSSPath.vehicleMediaVolume); + } + + static void authorize(WebSocket socket, WidgetRef ref) { + final config = ref.read(ConfigStateprovider); + + 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(ConfigStateprovider); + + 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(ConfigStateprovider); + 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(ConfigStateprovider); + + Map<String, dynamic> map = { + "action": "subscribe", + "tokens": config.kuksaAuthToken, + "path": path, + "requestId": requestId + }; + socket.add(jsonEncode(map)); + } + + static void parseData(WidgetRef ref, String data) { + 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 VSSPath.vehicleMediaVolume: + ref + .read(vehicleSignalMediaVolumeProvider.notifier) + .update(volume: dp["value"]); + break; + case VSSPath.vehicleInsideTemperature: + ref + .read(vehicleSignalInsideTempProvider.notifier) + .update(temp: dp["value"]); + break; + case VSSPath.vehicleOutsideTemperature: + ref + .read(vehicleSignalOutsideTempProvider.notifier) + .update(temp: dp["value"]); + break; + default: + break; + } + } else { + print("ERROR: Invalid VIS response, data not available"); + } + } else { + print("ERROR: Invalid VIS response, no 'value' key"); + } + } else if ((!dataMap["data"] as Map<String, dynamic>) + .containsKey("path")) { + print("ERROR: Invalid VIS response, no 'path' key"); + } else if ((dataMap["data"] as Map<String, dynamic>) + .containsKey("dp")) { + print("ERROR: Invalid VIS response, no 'dp' key"); + } + } else { + print("ERROR: Invalid VIS response, no 'data' key"); + } + } + } +} diff --git a/lib/vehicle-signals/vss_path.dart b/lib/vehicle-signals/vss_path.dart new file mode 100644 index 0000000..3bfc9c1 --- /dev/null +++ b/lib/vehicle-signals/vss_path.dart @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +class VSSPath { + static const String vehicleSpeed = "Vehicle.Speed"; + + static const String vehicleEngineRPM = + "Vehicle.Powertrain.CombustionEngine.Speed"; + + static const String vehicleFuelLevel = "Vehicle.Powertrain.FuelSystem.Level"; + + static const String vehicleInsideTemperature = + "Vehicle.Cabin.HVAC.AmbientAirTemperature"; + + static const String vehicleOutsideTemperature = + "Vehicle.Exterior.AirTemperature"; + + static const String vehicleFrontLeftTire = + "Vehicle.Chassis.Axle.Row1.Wheel.Left.Tire.Pressure"; + + static const String vehicleFrontRightTire = + "Vehicle.Chassis.Axle.Row1.Wheel.Right.Tire.Pressure"; + + static const String vehicleRearLeftTire = + "Vehicle.Chassis.Axle.Row2.Wheel.Left.Tire.Pressure"; + + static const String vehicleRearRightTire = + "Vehicle.Chassis.Axle.Row2.Wheel.Right.Tire.Pressure"; + + static const String vehicleIsChildLockActiveLeft = + "Vehicle.Cabin.Door.Row2.Left.IsChildLockActive"; + + static const String vehicleIsChildLockActiveRight = + "Vehicle.Cabin.Door.Row2.Right.IsChildLockActive"; + + static const String vehicleCurrentLongitude = + "Vehicle.CurrentLocation.Longitude"; + + static const String vehicleCurrentLatitude = + "Vehicle.CurrentLocation.Latitude"; + + static const String vehicleFuelRate = "Vehicle.OBD.FuelRate"; + + static const String vehicleMediaVolume = + "Vehicle.Cabin.Infotainment.Media.Volume"; +} diff --git a/lib/vehicle-signals/vss_providers.dart b/lib/vehicle-signals/vss_providers.dart new file mode 100644 index 0000000..630a273 --- /dev/null +++ b/lib/vehicle-signals/vss_providers.dart @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +//import 'dart:ffi'; +import 'dart:io'; +import 'package:meta/meta.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +@immutable +class VISServerSocket { + const VISServerSocket({required this.socket}); + + final WebSocket? socket; + + VISServerSocket copyWith({WebSocket? socket}) { + return VISServerSocket(socket: socket ?? this.socket); + } +} + +class VISServerSocketNotifier extends StateNotifier<VISServerSocket> { + VISServerSocketNotifier() : super(_initialValue); + + static final VISServerSocket _initialValue = VISServerSocket(socket: null); + + void update(WebSocket socket) { + state = state.copyWith(socket: socket); + } +} + +final VISServerSocketProvider = + StateNotifierProvider<VISServerSocketNotifier, VISServerSocket>((ref) { + return VISServerSocketNotifier(); +}); + +// Media Volume + +@immutable +class VehicleSignalMediaVolume { + const VehicleSignalMediaVolume({required this.volume}); + + final int volume; + + VehicleSignalMediaVolume copyWith({int? volume}) { + return VehicleSignalMediaVolume(volume: volume ?? this.volume); + } +} + +class VehicleSignalMediaVolumeNotifier + extends StateNotifier<VehicleSignalMediaVolume> { + VehicleSignalMediaVolumeNotifier() : super(_initialValue); + + static final VehicleSignalMediaVolume _initialValue = + VehicleSignalMediaVolume(volume: 50); + + void update({int? volume}) { + int? n = volume; + if (n != null) { + n = n.toUnsigned(8); + if (n > 100) n = 100; + } + state = state.copyWith(volume: n); + } +} + +//final vehicleSignalMediaVolumeProvider = StateNotifierProvider< +// VehicleSignalMediaVolumeNotifier, VehicleSignalMediaVolume>((ref) { +// return VehicleSignalMediaVolumeNotifier(); +//}); + +final vehicleSignalMediaVolumeProvider = StateNotifierProvider< + VehicleSignalMediaVolumeNotifier, + VehicleSignalMediaVolume>((ref) => VehicleSignalMediaVolumeNotifier()); + +// Inside Temperature + +@immutable +class VehicleSignalInsideTemp { + const VehicleSignalInsideTemp({required this.temp}); + + final double temp; + + VehicleSignalInsideTemp copyWith({double? temp}) { + return VehicleSignalInsideTemp(temp: temp ?? this.temp); + } +} + +class VehicleSignalInsideTempNotifier + extends StateNotifier<VehicleSignalInsideTemp> { + VehicleSignalInsideTempNotifier() : super(_initialValue); + + static final VehicleSignalInsideTemp _initialValue = + VehicleSignalInsideTemp(temp: 25); + + void update({double? temp}) { + state = state.copyWith(temp: temp); + } +} + +final vehicleSignalInsideTempProvider = StateNotifierProvider< + VehicleSignalInsideTempNotifier, VehicleSignalInsideTemp>( + (ref) => VehicleSignalInsideTempNotifier(), +); + +// Outside Temperature + +@immutable +class VehicleSignalOutsideTemp { + const VehicleSignalOutsideTemp({required this.temp}); + + final double temp; + + VehicleSignalOutsideTemp copyWith({double? temp}) { + return VehicleSignalOutsideTemp(temp: temp ?? this.temp); + } +} + +class VehicleSignalOutsideTempNotifier + extends StateNotifier<VehicleSignalOutsideTemp> { + VehicleSignalOutsideTempNotifier() : super(_initialValue); + + static final VehicleSignalOutsideTemp _initialValue = + VehicleSignalOutsideTemp(temp: 32); + + void update({double? temp}) { + state = state.copyWith(temp: temp); + } +} + +final vehicleSignalOutsideTempProvider = StateNotifierProvider< + VehicleSignalOutsideTempNotifier, VehicleSignalOutsideTemp>( + (ref) => VehicleSignalOutsideTempNotifier(), +); diff --git a/lib/volume_slider.dart b/lib/volume_slider.dart new file mode 100644 index 0000000..0665491 --- /dev/null +++ b/lib/volume_slider.dart @@ -0,0 +1,50 @@ +import 'dart:io'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_providers.dart'; +import 'package:flutter_homescreen/vehicle-signals/vss_path.dart'; +import 'package:flutter_homescreen/vehicle-signals/viss_methods.dart'; + +class VolumeSlider extends ConsumerWidget { + final Color thumbColor; + final Color activeColor; + final Color inactiveColor; + final RestartableTimer? activityTimer; + + VolumeSlider( + {Key? key, + required this.thumbColor, + required this.activeColor, + required this.inactiveColor, + required this.activityTimer}) + : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final signal = ref.watch(vehicleSignalMediaVolumeProvider); + return Slider( + thumbColor: thumbColor, + activeColor: activeColor, + inactiveColor: inactiveColor, + value: signal.volume.toDouble(), + max: 100, + label: "Volume", + onChangeStart: (value) { + activityTimer?.cancel(); + }, + onChangeEnd: (value) { + activityTimer?.reset(); + }, + onChanged: (value) { + ref + .read(vehicleSignalMediaVolumeProvider.notifier) + .update(volume: value.toInt()); + WebSocket? s = ref.read(VISServerSocketProvider).socket; + if (s != null) { + VISS.set( + s, ref, VSSPath.vehicleMediaVolume, value.toInt().toString()); + } + }); + } +} |