From d0bb545b32c7cc61dfa0cac345481b5e6bad5c86 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Mon, 22 Apr 2024 17:31:12 -0400 Subject: [PATCH 01/28] Removed singleton NT instance --- lib/services/nt_connection.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index cd617dfa..2f6f5634 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -3,11 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:elastic_dashboard/services/ds_interop.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -NTConnection get ntConnection => NTConnection.instance; - class NTConnection { - static NTConnection instance = NTConnection._internal(); - late NT4Client _ntClient; late DSInteropClient _dsClient; @@ -23,12 +19,6 @@ class NTConnection { bool get isDSConnected => _dsConnected; DSInteropClient get dsClient => _dsClient; - NTConnection._internal(); - - factory NTConnection() { - return instance; - } - void nt4Connect(String ipAddress) { _ntClient = NT4Client( serverBaseAddress: ipAddress, From b908c3d22b5fe73b0a23e0804e858424c881b630 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Mon, 22 Apr 2024 18:18:45 -0400 Subject: [PATCH 02/28] Began work on passing shared instances --- lib/main.dart | 26 ++++++-- lib/pages/dashboard_page.dart | 49 +++++++------- lib/services/nt_connection.dart | 23 ++++++- lib/services/nt_widget_builder.dart | 5 ++ lib/services/shuffleboard_nt_listener.dart | 65 +++++++++---------- .../network_tree/networktables_tree.dart | 13 ++-- .../network_tree/networktables_tree_row.dart | 14 ++-- .../nt_widgets/multi-topic/accelerometer.dart | 1 - .../multi-topic/basic_swerve_drive.dart | 1 - .../nt_widgets/multi-topic/camera_stream.dart | 1 - .../multi-topic/combo_box_chooser.dart | 1 - .../multi-topic/command_scheduler.dart | 1 - .../multi-topic/command_widget.dart | 1 - .../multi-topic/differential_drive.dart | 1 - .../multi-topic/encoder_widget.dart | 1 - .../nt_widgets/multi-topic/field_widget.dart | 9 --- .../nt_widgets/multi-topic/fms_info.dart | 1 - lib/widgets/nt_widgets/multi-topic/gyro.dart | 1 - .../multi-topic/motor_controller.dart | 1 - .../multi-topic/network_alerts.dart | 1 - .../multi-topic/pid_controller.dart | 1 - .../multi-topic/power_distribution.dart | 1 - .../multi-topic/profiled_pid_controller.dart | 1 - .../nt_widgets/multi-topic/relay_widget.dart | 1 - .../multi-topic/robot_preferences.dart | 3 +- .../multi-topic/split_button_chooser.dart | 1 - .../multi-topic/subsystem_widget.dart | 1 - .../multi-topic/three_axis_accelerometer.dart | 1 - .../nt_widgets/multi-topic/ultrasonic.dart | 1 - .../multi-topic/yagsl_swerve_drive.dart | 1 - lib/widgets/nt_widgets/nt_widget.dart | 24 ++++--- .../nt_widgets/single_topic/boolean_box.dart | 1 - .../nt_widgets/single_topic/match_time.dart | 1 - .../single_topic/multi_color_view.dart | 2 - .../nt_widgets/single_topic/number_bar.dart | 1 - .../single_topic/number_slider.dart | 1 - .../nt_widgets/single_topic/radial_gauge.dart | 1 - .../single_topic/single_color_view.dart | 1 - .../nt_widgets/single_topic/text_display.dart | 4 +- .../single_topic/toggle_button.dart | 1 - .../single_topic/toggle_switch.dart | 1 - .../nt_widgets/single_topic/voltage_view.dart | 6 +- lib/widgets/settings_dialog.dart | 65 ++++++++++--------- lib/widgets/tab_grid.dart | 16 +++-- 44 files changed, 182 insertions(+), 171 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 99f8435f..7646d872 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -77,9 +77,11 @@ void main() async { NTWidgetBuilder.ensureInitialized(); - Settings.ipAddress = + String ipAddress = preferences.getString(PrefKeys.ipAddress) ?? Settings.ipAddress; + NTConnection ntConnection = NTConnection(ipAddress); + ntConnection.nt4Connect(Settings.ipAddress); await FieldImages.loadFields('assets/fields/'); @@ -104,7 +106,15 @@ void main() async { await windowManager.show(); await windowManager.focus(); - runApp(Elastic(version: packageInfo.version, preferences: preferences)); + runApp( + Elastic( + elasticData: ( + ntConnection: ntConnection, + preferences: preferences, + ), + version: packageInfo.version, + ), + ); } Future _restoreWindowPosition(SharedPreferences preferences, @@ -187,10 +197,10 @@ Future _restorePreferencesFromBackup(String appFolderPath) async { } class Elastic extends StatefulWidget { - final SharedPreferences preferences; + final ElasticSharedData elasticData; final String version; - const Elastic({super.key, required this.version, required this.preferences}); + const Elastic({super.key, required this.elasticData, required this.version}); @override State createState() => _ElasticState(); @@ -198,7 +208,8 @@ class Elastic extends StatefulWidget { class _ElasticState extends State { late Color teamColor = Color( - widget.preferences.getInt(PrefKeys.teamColor) ?? Colors.blueAccent.value); + widget.elasticData.preferences.getInt(PrefKeys.teamColor) ?? + Colors.blueAccent.value); @override Widget build(BuildContext context) { @@ -212,11 +223,12 @@ class _ElasticState extends State { title: 'Elastic', theme: theme, home: DashboardPage( - preferences: widget.preferences, + elasticData: widget.elasticData, version: widget.version, onColorChanged: (color) => setState(() { teamColor = color; - widget.preferences.setInt(PrefKeys.teamColor, color.value); + widget.elasticData.preferences + .setInt(PrefKeys.teamColor, color.value); }), ), ); diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 66685dd0..ed8968d5 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -33,14 +33,16 @@ import 'package:elastic_dashboard/widgets/settings_dialog.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; import '../widgets/draggable_containers/models/layout_container_model.dart'; +typedef ElasticSharedData = ({NTConnection ntConnection, SharedPreferences preferences}); + class DashboardPage extends StatefulWidget { - final SharedPreferences preferences; final String version; final Function(Color color)? onColorChanged; + final ElasticSharedData elasticData; const DashboardPage({ super.key, - required this.preferences, + required this.elasticData, required this.version, this.onColorChanged, }); @@ -50,7 +52,7 @@ class DashboardPage extends StatefulWidget { } class _DashboardPageState extends State with WindowListener { - late final SharedPreferences _preferences; + late final SharedPreferences _preferences = widget.elasticData.preferences; late final UpdateChecker _updateChecker; final List _grids = []; @@ -69,7 +71,6 @@ class _DashboardPageState extends State with WindowListener { void initState() { super.initState(); - _preferences = widget.preferences; _updateChecker = UpdateChecker(currentVersion: widget.version); windowManager.addListener(this); @@ -81,7 +82,7 @@ class _DashboardPageState extends State with WindowListener { _setupShortcuts(); - ntConnection.dsClientConnect( + widget.elasticData.ntConnection.dsClientConnect( onIPAnnounced: (ip) async { if (Settings.ipAddressMode != IPAddressMode.driverStation) { return; @@ -93,7 +94,7 @@ class _DashboardPageState extends State with WindowListener { return; } - ntConnection.changeIPAddress(ip); + widget.elasticData.ntConnection.changeIPAddress(ip); }, onDriverStationDockChanged: (docked) { if (Settings.autoResizeToDS && docked) { @@ -104,7 +105,7 @@ class _DashboardPageState extends State with WindowListener { }, ); - ntConnection.addConnectedListener(() { + widget.elasticData.ntConnection.addConnectedListener(() { setState(() { for (TabGrid grid in _grids) { grid.onNTConnect(); @@ -112,7 +113,7 @@ class _DashboardPageState extends State with WindowListener { }); }); - ntConnection.addDisconnectedListener(() { + widget.elasticData.ntConnection.addDisconnectedListener(() { setState(() { for (TabGrid grid in _grids) { grid.onNTDisconnect(); @@ -121,6 +122,7 @@ class _DashboardPageState extends State with WindowListener { }); ShuffleboardNTListener apiListener = ShuffleboardNTListener( + elasticData: widget.elasticData, onTabChanged: (tab) { int? parsedTabIndex = int.tryParse(tab); @@ -160,6 +162,7 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, ), ); @@ -182,6 +185,7 @@ class _DashboardPageState extends State with WindowListener { _tabData.add(TabData(name: tabName)); _grids.add(TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, )); @@ -203,7 +207,7 @@ class _DashboardPageState extends State with WindowListener { Future.delayed(const Duration(seconds: 1), () { apiListener.initializeSubscriptions(); apiListener.initializeListeners(); - ntConnection.nt4Client.recallAnnounceListeners(); + widget.elasticData.ntConnection.recallTopicAnnounceListeners(); }); Future(() => _checkForUpdates(notifyIfLatest: false, notifyIfError: false)); @@ -565,6 +569,7 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid.fromJson( key: GlobalKey(), + elasticData: widget.elasticData, jsonData: data['grid_layout'], onAddWidgetPressed: _displayAddWidgetDialog, onJsonLoadingWarning: _showJsonLoadingWarning, @@ -591,10 +596,12 @@ class _DashboardPageState extends State with WindowListener { _grids.addAll([ TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, ), TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, ), ]); @@ -744,6 +751,7 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, ), ); @@ -867,7 +875,7 @@ class _DashboardPageState extends State with WindowListener { showDialog( context: context, builder: (context) => SettingsDialog( - preferences: widget.preferences, + elasticData: widget.elasticData, onTeamNumberChanged: (String? data) async { if (data == null) { return; @@ -907,7 +915,7 @@ class _DashboardPageState extends State with WindowListener { switch (mode) { case IPAddressMode.driverStation: - String? lastAnnouncedIP = ntConnection.dsClient.lastAnnouncedIP; + String? lastAnnouncedIP = widget.elasticData.ntConnection.dsClient.lastAnnouncedIP; if (lastAnnouncedIP == null) { break; @@ -1002,7 +1010,6 @@ class _DashboardPageState extends State with WindowListener { } setState(() { - Settings.gridSize = newGridSize; _gridSize = newGridSize; }); @@ -1024,8 +1031,6 @@ class _DashboardPageState extends State with WindowListener { } setState(() { - Settings.cornerRadius = newRadius; - for (TabGrid grid in _grids) { grid.refreshAllContainers(); } @@ -1035,9 +1040,7 @@ class _DashboardPageState extends State with WindowListener { }, onResizeToDSChanged: (value) async { setState(() { - Settings.autoResizeToDS = value; - - if (value && ntConnection.dsClient.driverStationDocked) { + if (value && widget.elasticData.ntConnection.dsClient.driverStationDocked) { _onDriverStationDocked(); } else { _onDriverStationUndocked(); @@ -1070,7 +1073,7 @@ class _DashboardPageState extends State with WindowListener { await _preferences.setDouble(PrefKeys.defaultPeriod, newPeriod); - setState(() => Settings.defaultPeriod = newPeriod); + setState(() {}); }, onDefaultGraphPeriodChanged: (value) async { if (value == null) { @@ -1092,14 +1095,13 @@ class _DashboardPageState extends State with WindowListener { } void _updateIPAddress(String newIPAddress) async { - if (newIPAddress == Settings.ipAddress) { + if (newIPAddress == _preferences.getString(PrefKeys.ipAddress)) { return; } await _preferences.setString(PrefKeys.ipAddress, newIPAddress); - Settings.ipAddress = newIPAddress; setState(() { - ntConnection.changeIPAddress(newIPAddress); + widget.elasticData.ntConnection.changeIPAddress(newIPAddress); }); } @@ -1471,6 +1473,7 @@ class _DashboardPageState extends State with WindowListener { _tabData.add(tab); _grids.add(TabGrid( key: GlobalKey(), + elasticData: widget.elasticData, onAddWidgetPressed: _displayAddWidgetDialog, )); }); @@ -1535,7 +1538,7 @@ class _DashboardPageState extends State with WindowListener { children: [ Expanded( child: StreamBuilder( - stream: ntConnection.connectionStatus(), + stream: widget.elasticData.ntConnection.connectionStatus(), builder: (context, snapshot) { bool connected = snapshot.data ?? false; @@ -1560,7 +1563,7 @@ class _DashboardPageState extends State with WindowListener { ), Expanded( child: StreamBuilder( - stream: ntConnection.latencyStream(), + stream: widget.elasticData.ntConnection.latencyStream(), builder: (context, snapshot) { int latency = snapshot.data ?? 0; diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index 2f6f5634..c580e616 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -14,11 +14,14 @@ class NTConnection { bool _dsConnected = false; bool get isNT4Connected => _ntConnected; - NT4Client get nt4Client => _ntClient; bool get isDSConnected => _dsConnected; DSInteropClient get dsClient => _dsClient; + NTConnection(String ipAddress) { + nt4Connect(ipAddress); + } + void nt4Connect(String ipAddress) { _ntClient = NT4Client( serverBaseAddress: ipAddress, @@ -60,6 +63,18 @@ class NTConnection { onDisconnectedListeners.add(callback); } + void addTopicAnnounceListener(Function(NT4Topic topic) onAnnounce) { + _ntClient.addTopicAnnounceListener(onAnnounce); + } + + void removeTopicAnnounceListener(Function(NT4Topic topic) onUnannounce) { + _ntClient.removeTopicAnnounceListener(onUnannounce); + } + + void recallTopicAnnounceListeners() { + _ntClient.recallAnnounceListeners(); + } + Future? subscribeAndRetrieveData(String topic, {period = 0.1, timeout = const Duration(seconds: 2, milliseconds: 500)}) async { @@ -106,8 +121,12 @@ class NTConnection { } } + Map announcedTopics() { + return _ntClient.announcedTopics; + } + Stream latencyStream() { - return nt4Client.latencyStream(); + return _ntClient.latencyStream(); } void changeIPAddress(String ipAddress) { diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 8a086edf..79efb2df 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -1,3 +1,4 @@ +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; @@ -49,6 +50,7 @@ class NTWidgetBuilder { static final Map< String, NTWidgetModel Function({ + required ElasticSharedData elasticData, required String topic, String dataType, double period, @@ -57,6 +59,7 @@ class NTWidgetBuilder { static final Map< String, NTWidgetModel Function({ + required ElasticSharedData elasticData, required Map jsonData, })> _modelJsonBuildMap = {}; @@ -344,6 +347,7 @@ class NTWidgetBuilder { } static NTWidgetModel buildNTModelFromJson( + ElasticSharedData elasticData, String type, Map jsonData, {Function(String message)? onWidgetTypeNotFound}) { ensureInitialized(); @@ -355,6 +359,7 @@ class NTWidgetBuilder { onWidgetTypeNotFound ?.call('Unknown widget type: \'$type\', defaulting to Empty Model.'); return NTWidgetModel.createDefault( + elasticData: elasticData, type: type, topic: tryCast(jsonData['topic']) ?? '', dataType: tryCast(jsonData['data_type']) ?? 'Unknown', diff --git a/lib/services/shuffleboard_nt_listener.dart b/lib/services/shuffleboard_nt_listener.dart index cf1714fb..fe2b1f76 100644 --- a/lib/services/shuffleboard_nt_listener.dart +++ b/lib/services/shuffleboard_nt_listener.dart @@ -1,7 +1,7 @@ import 'package:dot_cast/dot_cast.dart'; +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/widget_container_model.dart'; @@ -13,6 +13,7 @@ class ShuffleboardNTListener { static const String tabsEntry = '$metadataTable/Tabs'; static const String selectedEntry = '$metadataTable/Selected'; + final ElasticSharedData elasticData; final Function(Map widgetData)? onWidgetAdded; final Function(String tab)? onTabChanged; final Function(String tab)? onTabCreated; @@ -23,14 +24,18 @@ class ShuffleboardNTListener { Map> currentJsonData = {}; - final NetworkTableTreeRow shuffleboardTreeRoot = - NetworkTableTreeRow(topic: '/', rowName: ''); + late final NetworkTableTreeRow shuffleboardTreeRoot = + NetworkTableTreeRow(elasticData: elasticData, topic: '/', rowName: ''); - ShuffleboardNTListener( - {this.onTabChanged, this.onTabCreated, this.onWidgetAdded}); + ShuffleboardNTListener({ + required this.elasticData, + this.onTabChanged, + this.onTabCreated, + this.onWidgetAdded, + }); void initializeSubscriptions() { - selectedSubscription = ntConnection.subscribe(selectedEntry); + selectedSubscription = elasticData.ntConnection.subscribe(selectedEntry); } void initializeListeners() { @@ -48,19 +53,19 @@ class ShuffleboardNTListener { // Also clear data when connected in case if threads auto populate json after disconnection // Chances are low since the timing has to be just right but you never know - ntConnection.addConnectedListener(() { + elasticData.ntConnection.addConnectedListener(() { currentJsonData.clear(); shuffleboardTreeRoot.clearRows(); previousSelection = null; }); - ntConnection.addDisconnectedListener(() { + elasticData.ntConnection.addDisconnectedListener(() { currentJsonData.clear(); shuffleboardTreeRoot.clearRows(); previousSelection = null; }); - ntConnection.nt4Client.addTopicAnnounceListener((topic) async { + elasticData.ntConnection.addTopicAnnounceListener((topic) async { if (!topic.name.contains(shuffleboardTableRoot)) { return; } @@ -105,12 +110,8 @@ class ShuffleboardNTListener { if (name.contains('/Properties')) { String propertyTopic = metaHierarchy[metaHierarchy.length - 1]; - Object? subProperty = - await ntConnection.subscribeAndRetrieveData(propertyTopic); - - if (subProperty == null) { - return; - } + Object? subProperty = await elasticData.ntConnection + .subscribeAndRetrieveData(propertyTopic); String real = realHierarchy[realHierarchy.length - 1]; List realTopics = real.split('/'); @@ -184,12 +185,8 @@ class ShuffleboardNTListener { if (name.endsWith('/PreferredComponent')) { String componentTopic = metaHierarchy[metaHierarchy.length - 1]; - String? type = - await ntConnection.subscribeAndRetrieveData(componentTopic); - - if (type == null) { - return; - } + String? type = await elasticData.ntConnection + .subscribeAndRetrieveData(componentTopic); if (inLayout) { Map child = _createOrGetChild(jsonKey, widgetName); @@ -205,7 +202,8 @@ class ShuffleboardNTListener { String sizeTopic = metaHierarchy[metaHierarchy.length - 1]; List sizeRaw = - await ntConnection.subscribeAndRetrieveData(sizeTopic) ?? []; + await elasticData.ntConnection.subscribeAndRetrieveData(sizeTopic) ?? + []; List size = sizeRaw.whereType().toList(); if (size.length < 2) { @@ -229,8 +227,9 @@ class ShuffleboardNTListener { if (name.endsWith('/Position')) { String positionTopic = metaHierarchy[metaHierarchy.length - 1]; - List positionRaw = - await ntConnection.subscribeAndRetrieveData(positionTopic) ?? []; + List positionRaw = await elasticData.ntConnection + .subscribeAndRetrieveData(positionTopic) ?? + []; List position = positionRaw.whereType().toList(); if (position.length < 2) { @@ -302,13 +301,10 @@ class ShuffleboardNTListener { if (widgetRow.hasRow('.type')) { String typeTopic = widgetRow.getRow('.type').topic; - String? type = await ntConnection.subscribeAndRetrieveData(typeTopic, + String? type = await elasticData.ntConnection.subscribeAndRetrieveData( + typeTopic, timeout: const Duration(seconds: 3)); - if (type == null) { - return; - } - if (type == 'ShuffleboardLayout') { currentJsonData[jsonKey]!['layout'] = true; } @@ -348,7 +344,7 @@ class ShuffleboardNTListener { if (isCameraStream) { String? cameraStream = - await ntConnection.subscribeAndRetrieveData(topic.name); + await elasticData.ntConnection.subscribeAndRetrieveData(topic.name); if (cameraStream == null) { return; @@ -393,7 +389,7 @@ class ShuffleboardNTListener { ? Settings.defaultPeriod : Settings.defaultGraphPeriod); - if (ntConnection.isNT4Connected) { + if (elasticData.ntConnection.isNT4Connected) { onWidgetAdded?.call(currentJsonData[jsonKey]!); } @@ -461,8 +457,9 @@ class ShuffleboardNTListener { } if (isCameraStream) { - String? cameraStream = await ntConnection.subscribeAndRetrieveData( - childRow.getRow('.ShuffleboardURI').topic); + String? cameraStream = await elasticData.ntConnection + .subscribeAndRetrieveData( + childRow.getRow('.ShuffleboardURI').topic); if (cameraStream == null) { continue; @@ -501,7 +498,7 @@ class ShuffleboardNTListener { widget?.disposeModel(deleting: true); widget?.forceDispose(); } - if (ntConnection.isNT4Connected) { + if (elasticData.ntConnection.isNT4Connected) { onWidgetAdded?.call(currentJsonData[jsonKey]!); } }); diff --git a/lib/widgets/network_tree/networktables_tree.dart b/lib/widgets/network_tree/networktables_tree.dart index 05a7364b..dc6853b8 100644 --- a/lib/widgets/network_tree/networktables_tree.dart +++ b/lib/widgets/network_tree/networktables_tree.dart @@ -1,12 +1,12 @@ import 'dart:ui'; +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/list_layout_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/widget_container_model.dart'; @@ -18,6 +18,7 @@ typedef ListLayoutBuilder = ListLayoutModel Function({ }); class NetworkTableTree extends StatefulWidget { + final ElasticSharedData elasticData; final ListLayoutBuilder listLayoutBuilder; final Function(Offset globalPosition, WidgetContainerModel widget)? @@ -28,6 +29,7 @@ class NetworkTableTree extends StatefulWidget { const NetworkTableTree({ super.key, + required this.elasticData, required this.listLayoutBuilder, required this.hideMetadata, this.onDragUpdate, @@ -39,7 +41,7 @@ class NetworkTableTree extends StatefulWidget { } class _NetworkTableTreeState extends State { - final NetworkTableTreeRow root = NetworkTableTreeRow(topic: '/', rowName: ''); + late final NetworkTableTreeRow root = NetworkTableTreeRow(elasticData: widget.elasticData, topic: '/', rowName: ''); late final TreeController treeController; late final Function(Offset globalPosition, WidgetContainerModel widget)? @@ -65,8 +67,7 @@ class _NetworkTableTreeState extends State { }, ); - ntConnection.nt4Client - .addTopicAnnounceListener(onNewTopicAnnounced = (topic) { + widget.elasticData.ntConnection.addTopicAnnounceListener(onNewTopicAnnounced = (topic) { setState(() { treeController.rebuild(); }); @@ -75,7 +76,7 @@ class _NetworkTableTreeState extends State { @override void dispose() { - ntConnection.nt4Client.removeTopicAnnounceListener(onNewTopicAnnounced); + widget.elasticData.ntConnection.removeTopicAnnounceListener(onNewTopicAnnounced); super.dispose(); } @@ -126,7 +127,7 @@ class _NetworkTableTreeState extends State { Widget build(BuildContext context) { List topics = []; - for (NT4Topic topic in ntConnection.nt4Client.announcedTopics.values) { + for (NT4Topic topic in widget.elasticData.ntConnection.announcedTopics().values) { if (topic.name == 'Time') { continue; } diff --git a/lib/widgets/network_tree/networktables_tree_row.dart b/lib/widgets/network_tree/networktables_tree_row.dart index e3ae0b71..a5e4ba2d 100644 --- a/lib/widgets/network_tree/networktables_tree_row.dart +++ b/lib/widgets/network_tree/networktables_tree_row.dart @@ -1,7 +1,7 @@ +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_container.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; @@ -14,6 +14,7 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/boolean_box.da import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/text_display.dart'; class NetworkTableTreeRow { + final ElasticSharedData elasticData; final String topic; final String rowName; @@ -22,6 +23,7 @@ class NetworkTableTreeRow { List children = []; NetworkTableTreeRow({ + required this.elasticData, required this.topic, required this.rowName, this.ntTopic, @@ -74,8 +76,12 @@ class NetworkTableTreeRow { NetworkTableTreeRow createNewRow( {required String topic, required String name, NT4Topic? ntTopic}) { - NetworkTableTreeRow newRow = - NetworkTableTreeRow(topic: topic, rowName: name, ntTopic: ntTopic); + NetworkTableTreeRow newRow = NetworkTableTreeRow( + elasticData: elasticData, + topic: topic, + rowName: name, + ntTopic: ntTopic, + ); addRow(newRow); return newRow; @@ -162,7 +168,7 @@ class NetworkTableTreeRow { } Future getTypeString(String typeTopic) async { - return ntConnection.subscribeAndRetrieveData(typeTopic); + return elasticData.ntConnection.subscribeAndRetrieveData(typeTopic); } Future? getTypedWidget(String typeTopic) async { diff --git a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart index 196907f8..7b3439d8 100644 --- a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class AccelerometerModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart index 0e34b9a9..41777c23 100644 --- a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart @@ -6,7 +6,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:vector_math/vector_math_64.dart' show radians; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart index 19ca55b0..ff9962aa 100644 --- a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart +++ b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/custom_loading_indicator.dart'; import 'package:elastic_dashboard/widgets/mjpeg.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart index 390a5219..b8a31670 100644 --- a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart @@ -5,7 +5,6 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart index 6d1b7c5c..94eb7558 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart @@ -6,7 +6,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class CommandSchedulerModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/command_widget.dart b/lib/widgets/nt_widgets/multi-topic/command_widget.dart index bc22acac..7eff26e2 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_widget.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart index 9b79c51a..52cd62ce 100644 --- a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart @@ -7,7 +7,6 @@ import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class DifferentialDriveModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart index 7322ac25..b20a634b 100644 --- a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class EncoderModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/field_widget.dart b/lib/widgets/nt_widgets/multi-topic/field_widget.dart index fa4a27ca..31f954fb 100644 --- a/lib/widgets/nt_widgets/multi-topic/field_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/field_widget.dart @@ -10,7 +10,6 @@ import 'package:vector_math/vector_math_64.dart' show radians; import 'package:elastic_dashboard/services/field_images.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; @@ -292,10 +291,6 @@ class FieldWidgetModel extends NTWidgetModel { .getLastAnnouncedValue(objectTopic) ?.tryCast>(); - if (objectPositionRaw == null) { - continue; - } - bool isTrajectory = objectPositionRaw.length > 24; if (isTrajectory && !_showTrajectories) { @@ -557,10 +552,6 @@ class FieldWidget extends NTWidget { .getLastAnnouncedValue(objectTopic) ?.tryCast>(); - if (objectPositionRaw == null) { - continue; - } - bool isTrajectory = objectPositionRaw.length > 24; if (isTrajectory && !model.showTrajectories) { diff --git a/lib/widgets/nt_widgets/multi-topic/fms_info.dart b/lib/widgets/nt_widgets/multi-topic/fms_info.dart index 92026e6b..0dfd214c 100644 --- a/lib/widgets/nt_widgets/multi-topic/fms_info.dart +++ b/lib/widgets/nt_widgets/multi-topic/fms_info.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:patterns_canvas/patterns_canvas.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class FMSInfoModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/gyro.dart b/lib/widgets/nt_widgets/multi-topic/gyro.dart index a18aee09..b9b37f9e 100644 --- a/lib/widgets/nt_widgets/multi-topic/gyro.dart +++ b/lib/widgets/nt_widgets/multi-topic/gyro.dart @@ -5,7 +5,6 @@ import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart index 322de437..a87b3fdd 100644 --- a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart @@ -5,7 +5,6 @@ import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class MotorControllerModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart index c8f09dea..0cea366b 100644 --- a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart +++ b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class NetworkAlertsModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart index d9f08ec6..5b7b78d8 100644 --- a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart index a264b7ca..29934e50 100644 --- a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart +++ b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class PowerDistributionModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart index e2751d6b..87149b72 100644 --- a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart index 96529e91..006b5ba7 100644 --- a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class RelayModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart index d48ce330..615d9005 100644 --- a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart +++ b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart @@ -5,7 +5,6 @@ import 'package:provider/provider.dart'; import 'package:searchable_listview/searchable_listview.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class RobotPreferencesModel extends NTWidgetModel { @@ -56,7 +55,7 @@ class RobotPreferences extends NTWidget { model.preferenceTopics.addAll({nt4Topic.name: nt4Topic}); model.preferenceTextControllers.addAll({ nt4Topic.name: TextEditingController() - ..text = previousValue?.toString() ?? '' + ..text = previousValue.toString() ?? '' }); model.previousValues.addAll({nt4Topic.name: previousValue}); diff --git a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart index 9446b066..dd9538b7 100644 --- a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/combo_box_chooser.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart index b3b420c9..13756992 100644 --- a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class SubsystemModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart index 4c0bd293..fdd0aa1f 100644 --- a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class ThreeAxisAccelerometerModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart index 8075a821..c11fb90a 100644 --- a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart +++ b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart @@ -4,7 +4,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class UltrasonicModel extends NTWidgetModel { diff --git a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart index 4c1cb01e..6187a5a2 100644 --- a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart @@ -6,7 +6,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:vector_math/vector_math_64.dart' show radians; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index 9a81563d..cde243f1 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -1,10 +1,10 @@ +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/boolean_box.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/graph.dart'; @@ -20,6 +20,7 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_switch. import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.dart'; class NTWidgetModel extends ChangeNotifier { + final ElasticSharedData elasticData; String _typeOverride = 'NTWidget'; String get type => _typeOverride; @@ -42,6 +43,7 @@ class NTWidgetModel extends ChangeNotifier { bool _forceDispose = false; NTWidgetModel({ + required this.elasticData, required String topic, this.dataType = 'Unknown', double? period, @@ -52,6 +54,7 @@ class NTWidgetModel extends ChangeNotifier { } NTWidgetModel.createDefault({ + required this.elasticData, required String type, required String topic, this.dataType = 'Unknown', @@ -63,7 +66,8 @@ class NTWidgetModel extends ChangeNotifier { init(); } - NTWidgetModel.fromJson({required Map jsonData}) { + NTWidgetModel.fromJson( + {required this.elasticData, required Map jsonData}) { _topic = tryCast(jsonData['topic']) ?? ''; _period = tryCast(jsonData['period']) ?? Settings.defaultPeriod; dataType = tryCast(jsonData['data_type']) ?? dataType; @@ -73,7 +77,7 @@ class NTWidgetModel extends ChangeNotifier { @mustCallSuper Map toJson() { - if (dataType == 'Unknown' && ntConnection.isNT4Connected) { + if (dataType == 'Unknown' && elasticData.ntConnection.isNT4Connected) { createTopicIfNull(); dataType = ntTopic?.type ?? dataType; } @@ -140,16 +144,16 @@ class NTWidgetModel extends ChangeNotifier { @mustCallSuper void init() async { - subscription = ntConnection.subscribe(_topic, _period); + subscription = elasticData.ntConnection.subscribe(_topic, _period); } void createTopicIfNull() { - ntTopic ??= ntConnection.getTopicFromName(_topic); + ntTopic ??= elasticData.ntConnection.getTopicFromName(_topic); } void unSubscribe() { if (subscription != null) { - ntConnection.unSubscribe(subscription!); + elasticData.ntConnection.unSubscribe(subscription!); } refresh(); } @@ -158,7 +162,7 @@ class NTWidgetModel extends ChangeNotifier { void resetSubscription() { if (subscription == null) { - subscription = ntConnection.subscribe(_topic, _period); + subscription = elasticData.ntConnection.subscribe(_topic, _period); ntTopic = null; @@ -168,14 +172,14 @@ class NTWidgetModel extends ChangeNotifier { bool resetDataType = subscription!.topic != topic; - ntConnection.unSubscribe(subscription!); - subscription = ntConnection.subscribe(_topic, _period); + elasticData.ntConnection.unSubscribe(subscription!); + subscription = elasticData.ntConnection.subscribe(_topic, _period); ntTopic = null; createTopicIfNull(); if (resetDataType) { - if (ntTopic == null && ntConnection.isNT4Connected) { + if (ntTopic == null && elasticData.ntConnection.isNT4Connected) { dataType = 'Unknown'; } else { dataType = ntTopic?.type ?? dataType; diff --git a/lib/widgets/nt_widgets/single_topic/boolean_box.dart b/lib/widgets/nt_widgets/single_topic/boolean_box.dart index 528b8ab1..1cc8654d 100644 --- a/lib/widgets/nt_widgets/single_topic/boolean_box.dart +++ b/lib/widgets/nt_widgets/single_topic/boolean_box.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_color_picker.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/single_topic/match_time.dart b/lib/widgets/nt_widgets/single_topic/match_time.dart index 40ecb8de..577988c7 100644 --- a/lib/widgets/nt_widgets/single_topic/match_time.dart +++ b/lib/widgets/nt_widgets/single_topic/match_time.dart @@ -4,7 +4,6 @@ import 'package:flutter/services.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/single_topic/multi_color_view.dart b/lib/widgets/nt_widgets/single_topic/multi_color_view.dart index 6f33d23d..cb47ea5d 100644 --- a/lib/widgets/nt_widgets/single_topic/multi_color_view.dart +++ b/lib/widgets/nt_widgets/single_topic/multi_color_view.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class MultiColorView extends NTWidget { diff --git a/lib/widgets/nt_widgets/single_topic/number_bar.dart b/lib/widgets/nt_widgets/single_topic/number_bar.dart index 1d8002ec..d561a100 100644 --- a/lib/widgets/nt_widgets/single_topic/number_bar.dart +++ b/lib/widgets/nt_widgets/single_topic/number_bar.dart @@ -5,7 +5,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; diff --git a/lib/widgets/nt_widgets/single_topic/number_slider.dart b/lib/widgets/nt_widgets/single_topic/number_slider.dart index e04b824d..71521730 100644 --- a/lib/widgets/nt_widgets/single_topic/number_slider.dart +++ b/lib/widgets/nt_widgets/single_topic/number_slider.dart @@ -5,7 +5,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; diff --git a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart index ae3fe930..6dc57e48 100644 --- a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart +++ b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart @@ -5,7 +5,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; diff --git a/lib/widgets/nt_widgets/single_topic/single_color_view.dart b/lib/widgets/nt_widgets/single_topic/single_color_view.dart index 97e44914..3d3a45ad 100644 --- a/lib/widgets/nt_widgets/single_topic/single_color_view.dart +++ b/lib/widgets/nt_widgets/single_topic/single_color_view.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class SingleColorView extends NTWidget { diff --git a/lib/widgets/nt_widgets/single_topic/text_display.dart b/lib/widgets/nt_widgets/single_topic/text_display.dart index 90158a26..67350c7e 100644 --- a/lib/widgets/nt_widgets/single_topic/text_display.dart +++ b/lib/widgets/nt_widgets/single_topic/text_display.dart @@ -7,7 +7,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; @@ -142,8 +141,7 @@ class TextDisplay extends NTWidget { builder: (context, snapshot) { Object? data = snapshot.data; - if (data?.toString() != model.previousValue?.toString() && - data != null) { + if (data.toString() != model.previousValue?.toString()) { // Needed to prevent errors Future(() async { String displayString = data.toString(); diff --git a/lib/widgets/nt_widgets/single_topic/toggle_button.dart b/lib/widgets/nt_widgets/single_topic/toggle_button.dart index daee44e2..c34aeb4f 100644 --- a/lib/widgets/nt_widgets/single_topic/toggle_button.dart +++ b/lib/widgets/nt_widgets/single_topic/toggle_button.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class ToggleButton extends NTWidget { diff --git a/lib/widgets/nt_widgets/single_topic/toggle_switch.dart b/lib/widgets/nt_widgets/single_topic/toggle_switch.dart index caf5e6d7..a5802127 100644 --- a/lib/widgets/nt_widgets/single_topic/toggle_switch.dart +++ b/lib/widgets/nt_widgets/single_topic/toggle_switch.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; class ToggleSwitch extends NTWidget { diff --git a/lib/widgets/nt_widgets/single_topic/voltage_view.dart b/lib/widgets/nt_widgets/single_topic/voltage_view.dart index 5fd6a6bf..ded3ee45 100644 --- a/lib/widgets/nt_widgets/single_topic/voltage_view.dart +++ b/lib/widgets/nt_widgets/single_topic/voltage_view.dart @@ -5,7 +5,6 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_gauges/gauges.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -58,6 +57,7 @@ class VoltageViewModel extends NTWidgetModel { } VoltageViewModel({ + required super.elasticData, required super.topic, double minValue = 4.0, double maxValue = 13.0, @@ -73,7 +73,7 @@ class VoltageViewModel extends NTWidgetModel { _minValue = minValue, super(); - VoltageViewModel.fromJson({required Map jsonData}) + VoltageViewModel.fromJson({required super.elasticData, required Map jsonData}) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? 4.0; _maxValue = tryCast(jsonData['max_value']) ?? 13.0; @@ -203,7 +203,7 @@ class VoltageView extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.elasticData.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double voltage = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index 68d4d959..285f59dc 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -1,11 +1,10 @@ +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:dot_cast/dot_cast.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/ip_address_util.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_color_picker.dart'; @@ -14,7 +13,7 @@ import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart' import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; class SettingsDialog extends StatefulWidget { - final SharedPreferences preferences; + final ElasticSharedData elasticData; final Function(String? data)? onIPAddressChanged; final Function(String? data)? onTeamNumberChanged; @@ -31,7 +30,7 @@ class SettingsDialog extends StatefulWidget { const SettingsDialog({ super.key, - required this.preferences, + required this.elasticData, this.onTeamNumberChanged, this.onIPAddressModeChanged, this.onIPAddressChanged, @@ -99,17 +98,19 @@ class _SettingsDialogState extends State { } List _generalSettings() { - Color currentColor = Color(widget.preferences.getInt(PrefKeys.teamColor) ?? - Colors.blueAccent.value); + Color currentColor = Color( + widget.elasticData.preferences.getInt(PrefKeys.teamColor) ?? + Colors.blueAccent.value); return [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Flexible( child: DialogTextInput( - initialText: - widget.preferences.getInt(PrefKeys.teamNumber)?.toString() ?? - Settings.teamNumber.toString(), + initialText: widget.elasticData.preferences + .getInt(PrefKeys.teamNumber) + ?.toString() ?? + Settings.teamNumber.toString(), label: 'Team Number', onSubmit: (data) async { await widget.onTeamNumberChanged?.call(data); @@ -153,8 +154,8 @@ class _SettingsDialogState extends State { ), const SizedBox(height: 5), StreamBuilder( - stream: ntConnection.dsConnectionStatus(), - initialData: ntConnection.isDSConnected, + stream: widget.elasticData.ntConnection.dsConnectionStatus(), + initialData: widget.elasticData.ntConnection.isDSConnected, builder: (context, snapshot) { bool dsConnected = tryCast(snapshot.data) ?? false; @@ -162,7 +163,8 @@ class _SettingsDialogState extends State { enabled: Settings.ipAddressMode == IPAddressMode.custom || (Settings.ipAddressMode == IPAddressMode.driverStation && !dsConnected), - initialText: widget.preferences.getString(PrefKeys.ipAddress) ?? + initialText: widget.elasticData.preferences + .getString(PrefKeys.ipAddress) ?? Settings.ipAddress, label: 'IP Address', onSubmit: (String? data) async { @@ -186,8 +188,9 @@ class _SettingsDialogState extends State { children: [ Flexible( child: DialogToggleSwitch( - initialValue: widget.preferences.getBool(PrefKeys.showGrid) ?? - Settings.showGrid, + initialValue: + widget.elasticData.preferences.getBool(PrefKeys.showGrid) ?? + Settings.showGrid, label: 'Show Grid', onToggle: (value) { setState(() { @@ -198,9 +201,10 @@ class _SettingsDialogState extends State { ), Flexible( child: DialogTextInput( - initialText: - widget.preferences.getInt(PrefKeys.gridSize)?.toString() ?? - Settings.gridSize.toString(), + initialText: widget.elasticData.preferences + .getInt(PrefKeys.gridSize) + ?.toString() ?? + Settings.gridSize.toString(), label: 'Grid Size', onSubmit: (value) async { await widget.onGridSizeChanged?.call(value); @@ -218,7 +222,7 @@ class _SettingsDialogState extends State { Flexible( flex: 2, child: DialogTextInput( - initialText: widget.preferences + initialText: widget.elasticData.preferences .getDouble(PrefKeys.cornerRadius) ?.toString() ?? Settings.cornerRadius.toString(), @@ -234,9 +238,9 @@ class _SettingsDialogState extends State { Flexible( flex: 3, child: DialogToggleSwitch( - initialValue: - widget.preferences.getBool(PrefKeys.autoResizeToDS) ?? - Settings.autoResizeToDS, + initialValue: widget.elasticData.preferences + .getBool(PrefKeys.autoResizeToDS) ?? + Settings.autoResizeToDS, label: 'Resize to Driver Station Height', onToggle: (value) { setState(() { @@ -254,9 +258,9 @@ class _SettingsDialogState extends State { Flexible( flex: 5, child: DialogToggleSwitch( - initialValue: - widget.preferences.getBool(PrefKeys.rememberWindowPosition) ?? - false, + initialValue: widget.elasticData.preferences + .getBool(PrefKeys.rememberWindowPosition) ?? + false, label: 'Remember Window Position', onToggle: (value) { setState(() { @@ -268,7 +272,8 @@ class _SettingsDialogState extends State { Flexible( flex: 4, child: DialogToggleSwitch( - initialValue: widget.preferences.getBool(PrefKeys.layoutLocked) ?? + initialValue: widget.elasticData.preferences + .getBool(PrefKeys.layoutLocked) ?? Settings.layoutLocked, label: 'Lock Layout', onToggle: (value) { @@ -296,10 +301,10 @@ class _SettingsDialogState extends State { children: [ Flexible( child: DialogTextInput( - initialText: - (widget.preferences.getDouble(PrefKeys.defaultPeriod) ?? - Settings.defaultPeriod) - .toString(), + initialText: (widget.elasticData.preferences + .getDouble(PrefKeys.defaultPeriod) ?? + Settings.defaultPeriod) + .toString(), label: 'Default Period', onSubmit: (value) async { await widget.onDefaultPeriodChanged?.call(value); @@ -310,7 +315,7 @@ class _SettingsDialogState extends State { ), Flexible( child: DialogTextInput( - initialText: (widget.preferences + initialText: (widget.elasticData.preferences .getDouble(PrefKeys.defaultGraphPeriod) ?? Settings.defaultGraphPeriod) .toString(), diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 905fef69..f472f189 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; @@ -7,7 +8,6 @@ import 'package:flutter_box_transform/flutter_box_transform.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:provider/provider.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_list_layout.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_nt_widget_container.dart'; @@ -27,6 +27,7 @@ class TabGridModel extends ChangeNotifier { } class TabGrid extends StatelessWidget { + final ElasticSharedData elasticData; final List _widgetModels = []; MapEntry? _containerDraggingIn; @@ -35,10 +36,11 @@ class TabGrid extends StatelessWidget { TabGridModel? model; - TabGrid({super.key, required this.onAddWidgetPressed}); + TabGrid({super.key, required this.elasticData, required this.onAddWidgetPressed}); TabGrid.fromJson({ super.key, + required this.elasticData, required Map jsonData, required this.onAddWidgetPressed, Function(String message)? onJsonLoadingWarning, @@ -58,7 +60,7 @@ class TabGrid extends StatelessWidget { for (Map containerData in jsonData['containers']) { _widgetModels.add( NTWidgetContainerModel.fromJson( - enabled: ntConnection.isNT4Connected, + enabled: elasticData.ntConnection.isNT4Connected, jsonData: containerData, onJsonLoadingWarning: onJsonLoadingWarning, ), @@ -81,7 +83,7 @@ class TabGrid extends StatelessWidget { case 'List Layout': widget = ListLayoutModel.fromJson( jsonData: layoutData, - enabled: ntConnection.isNT4Connected, + enabled: elasticData.ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, minWidth: 128.0 * 2, @@ -484,7 +486,7 @@ class TabGrid extends StatelessWidget { widget.setPreviewRect(previewLocation); widget.tryCast()?.updateMinimumSize(); - widget.setEnabled(ntConnection.isNT4Connected); + widget.setEnabled(elasticData.ntConnection.isNT4Connected); // If dragging into layout if (widget is NTWidgetContainerModel && @@ -586,7 +588,7 @@ class TabGrid extends StatelessWidget { _widgetModels.add( ListLayoutModel.fromJson( jsonData: widgetData, - enabled: ntConnection.isNT4Connected, + enabled: elasticData.ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, minWidth: 128.0 * 2, @@ -598,7 +600,7 @@ class TabGrid extends StatelessWidget { } else { _widgetModels.add( NTWidgetContainerModel.fromJson( - enabled: ntConnection.isNT4Connected, + enabled: elasticData.ntConnection.isNT4Connected, jsonData: widgetData, ), ); From e5c9d4c2c922698f7ba9019aa596c2a86ae3dcd2 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Mon, 22 Apr 2024 22:52:14 -0400 Subject: [PATCH 03/28] Separately pass nt instance and preferences --- lib/main.dart | 24 ++++--- lib/pages/dashboard_page.dart | 66 +++++++++++------- lib/services/nt_connection.dart | 8 +++ lib/services/nt_widget_builder.dart | 22 +++--- lib/services/shuffleboard_nt_listener.dart | 54 ++++++++------- .../models/layout_container_model.dart | 2 + .../models/list_layout_model.dart | 8 +++ .../models/nt_widget_container_model.dart | 16 ++++- .../models/widget_container_model.dart | 4 ++ .../network_tree/networktables_tree.dart | 42 +++++++----- .../network_tree/networktables_tree_row.dart | 30 ++++++--- .../nt_widgets/multi-topic/accelerometer.dart | 12 +++- .../multi-topic/basic_swerve_drive.dart | 26 +++---- .../nt_widgets/multi-topic/camera_stream.dart | 29 +++++--- .../multi-topic/combo_box_chooser.dart | 27 ++++---- .../multi-topic/command_scheduler.dart | 23 ++++--- .../multi-topic/command_widget.dart | 18 +++-- .../multi-topic/differential_drive.dart | 37 +++++----- .../multi-topic/encoder_widget.dart | 24 ++++--- .../nt_widgets/multi-topic/field_widget.dart | 20 ++++-- .../nt_widgets/multi-topic/fms_info.dart | 42 +++++++----- lib/widgets/nt_widgets/multi-topic/gyro.dart | 21 +++--- .../multi-topic/motor_controller.dart | 17 +++-- .../multi-topic/network_alerts.dart | 21 ++++-- .../multi-topic/pid_controller.dart | 38 ++++++----- .../multi-topic/power_distribution.dart | 31 +++++---- .../multi-topic/profiled_pid_controller.dart | 47 +++++++------ .../nt_widgets/multi-topic/relay_widget.dart | 26 ++++--- .../multi-topic/robot_preferences.dart | 40 ++++++----- .../multi-topic/split_button_chooser.dart | 27 ++++---- .../multi-topic/subsystem_widget.dart | 17 +++-- .../multi-topic/three_axis_accelerometer.dart | 24 ++++--- .../nt_widgets/multi-topic/ultrasonic.dart | 17 +++-- .../multi-topic/yagsl_swerve_drive.dart | 35 +++++----- lib/widgets/nt_widgets/nt_widget.dart | 30 +++++---- .../nt_widgets/single_topic/boolean_box.dart | 24 ++++--- .../nt_widgets/single_topic/graph.dart | 4 +- .../nt_widgets/single_topic/match_time.dart | 25 ++++--- .../single_topic/multi_color_view.dart | 3 +- .../nt_widgets/single_topic/number_bar.dart | 9 ++- .../single_topic/number_slider.dart | 11 +-- .../nt_widgets/single_topic/radial_gauge.dart | 9 ++- .../single_topic/single_color_view.dart | 2 +- .../nt_widgets/single_topic/text_display.dart | 11 +-- .../single_topic/toggle_button.dart | 8 +-- .../single_topic/toggle_switch.dart | 8 +-- .../nt_widgets/single_topic/voltage_view.dart | 7 +- lib/widgets/settings_dialog.dart | 67 +++++++++---------- lib/widgets/tab_grid.dart | 35 +++++++--- 49 files changed, 703 insertions(+), 445 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 7646d872..68f842e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -108,10 +108,8 @@ void main() async { runApp( Elastic( - elasticData: ( - ntConnection: ntConnection, - preferences: preferences, - ), + ntConnection: ntConnection, + preferences: preferences, version: packageInfo.version, ), ); @@ -197,10 +195,15 @@ Future _restorePreferencesFromBackup(String appFolderPath) async { } class Elastic extends StatefulWidget { - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final String version; - const Elastic({super.key, required this.elasticData, required this.version}); + const Elastic( + {super.key, + required this.ntConnection, + required this.preferences, + required this.version}); @override State createState() => _ElasticState(); @@ -208,8 +211,7 @@ class Elastic extends StatefulWidget { class _ElasticState extends State { late Color teamColor = Color( - widget.elasticData.preferences.getInt(PrefKeys.teamColor) ?? - Colors.blueAccent.value); + widget.preferences.getInt(PrefKeys.teamColor) ?? Colors.blueAccent.value); @override Widget build(BuildContext context) { @@ -223,12 +225,12 @@ class _ElasticState extends State { title: 'Elastic', theme: theme, home: DashboardPage( - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, version: widget.version, onColorChanged: (color) => setState(() { teamColor = color; - widget.elasticData.preferences - .setInt(PrefKeys.teamColor, color.value); + widget.preferences.setInt(PrefKeys.teamColor, color.value); }), ), ); diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index ed8968d5..31516040 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -33,16 +33,16 @@ import 'package:elastic_dashboard/widgets/settings_dialog.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; import '../widgets/draggable_containers/models/layout_container_model.dart'; -typedef ElasticSharedData = ({NTConnection ntConnection, SharedPreferences preferences}); - class DashboardPage extends StatefulWidget { final String version; final Function(Color color)? onColorChanged; - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; const DashboardPage({ super.key, - required this.elasticData, + required this.ntConnection, + required this.preferences, required this.version, this.onColorChanged, }); @@ -52,7 +52,7 @@ class DashboardPage extends StatefulWidget { } class _DashboardPageState extends State with WindowListener { - late final SharedPreferences _preferences = widget.elasticData.preferences; + late final SharedPreferences _preferences = widget.preferences; late final UpdateChecker _updateChecker; final List _grids = []; @@ -82,7 +82,7 @@ class _DashboardPageState extends State with WindowListener { _setupShortcuts(); - widget.elasticData.ntConnection.dsClientConnect( + widget.ntConnection.dsClientConnect( onIPAnnounced: (ip) async { if (Settings.ipAddressMode != IPAddressMode.driverStation) { return; @@ -94,7 +94,7 @@ class _DashboardPageState extends State with WindowListener { return; } - widget.elasticData.ntConnection.changeIPAddress(ip); + widget.ntConnection.changeIPAddress(ip); }, onDriverStationDockChanged: (docked) { if (Settings.autoResizeToDS && docked) { @@ -105,7 +105,7 @@ class _DashboardPageState extends State with WindowListener { }, ); - widget.elasticData.ntConnection.addConnectedListener(() { + widget.ntConnection.addConnectedListener(() { setState(() { for (TabGrid grid in _grids) { grid.onNTConnect(); @@ -113,7 +113,7 @@ class _DashboardPageState extends State with WindowListener { }); }); - widget.elasticData.ntConnection.addDisconnectedListener(() { + widget.ntConnection.addDisconnectedListener(() { setState(() { for (TabGrid grid in _grids) { grid.onNTDisconnect(); @@ -122,7 +122,8 @@ class _DashboardPageState extends State with WindowListener { }); ShuffleboardNTListener apiListener = ShuffleboardNTListener( - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onTabChanged: (tab) { int? parsedTabIndex = int.tryParse(tab); @@ -162,7 +163,8 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), ); @@ -185,7 +187,8 @@ class _DashboardPageState extends State with WindowListener { _tabData.add(TabData(name: tabName)); _grids.add(TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, )); @@ -207,7 +210,7 @@ class _DashboardPageState extends State with WindowListener { Future.delayed(const Duration(seconds: 1), () { apiListener.initializeSubscriptions(); apiListener.initializeListeners(); - widget.elasticData.ntConnection.recallTopicAnnounceListeners(); + widget.ntConnection.recallTopicAnnounceListeners(); }); Future(() => _checkForUpdates(notifyIfLatest: false, notifyIfError: false)); @@ -569,7 +572,8 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid.fromJson( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, jsonData: data['grid_layout'], onAddWidgetPressed: _displayAddWidgetDialog, onJsonLoadingWarning: _showJsonLoadingWarning, @@ -596,12 +600,14 @@ class _DashboardPageState extends State with WindowListener { _grids.addAll([ TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), ]); @@ -751,7 +757,8 @@ class _DashboardPageState extends State with WindowListener { _grids.add( TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), ); @@ -875,7 +882,8 @@ class _DashboardPageState extends State with WindowListener { showDialog( context: context, builder: (context) => SettingsDialog( - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onTeamNumberChanged: (String? data) async { if (data == null) { return; @@ -915,7 +923,8 @@ class _DashboardPageState extends State with WindowListener { switch (mode) { case IPAddressMode.driverStation: - String? lastAnnouncedIP = widget.elasticData.ntConnection.dsClient.lastAnnouncedIP; + String? lastAnnouncedIP = + widget.ntConnection.dsClient.lastAnnouncedIP; if (lastAnnouncedIP == null) { break; @@ -1040,7 +1049,7 @@ class _DashboardPageState extends State with WindowListener { }, onResizeToDSChanged: (value) async { setState(() { - if (value && widget.elasticData.ntConnection.dsClient.driverStationDocked) { + if (value && widget.ntConnection.dsClient.driverStationDocked) { _onDriverStationDocked(); } else { _onDriverStationUndocked(); @@ -1101,7 +1110,7 @@ class _DashboardPageState extends State with WindowListener { await _preferences.setString(PrefKeys.ipAddress, newIPAddress); setState(() { - widget.elasticData.ntConnection.changeIPAddress(newIPAddress); + widget.ntConnection.changeIPAddress(newIPAddress); }); } @@ -1473,7 +1482,8 @@ class _DashboardPageState extends State with WindowListener { _tabData.add(tab); _grids.add(TabGrid( key: GlobalKey(), - elasticData: widget.elasticData, + ntConnection: widget.ntConnection, + preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, )); }); @@ -1505,6 +1515,8 @@ class _DashboardPageState extends State with WindowListener { tabViews: _grids, ), _AddWidgetDialog( + ntConnection: widget.ntConnection, + preferences: widget.preferences, grid: () => _grids[_currentTabIndex], visible: _addWidgetDialogVisible, onNTDragUpdate: (globalPosition, widget) { @@ -1538,7 +1550,7 @@ class _DashboardPageState extends State with WindowListener { children: [ Expanded( child: StreamBuilder( - stream: widget.elasticData.ntConnection.connectionStatus(), + stream: widget.ntConnection.connectionStatus(), builder: (context, snapshot) { bool connected = snapshot.data ?? false; @@ -1563,7 +1575,7 @@ class _DashboardPageState extends State with WindowListener { ), Expanded( child: StreamBuilder( - stream: widget.elasticData.ntConnection.latencyStream(), + stream: widget.ntConnection.latencyStream(), builder: (context, snapshot) { int latency = snapshot.data ?? 0; @@ -1585,6 +1597,8 @@ class _DashboardPageState extends State with WindowListener { } class _AddWidgetDialog extends StatefulWidget { + final NTConnection ntConnection; + final SharedPreferences preferences; final TabGrid Function() _grid; final bool _visible; @@ -1599,6 +1613,8 @@ class _AddWidgetDialog extends StatefulWidget { final Function()? _onClose; const _AddWidgetDialog({ + required this.ntConnection, + required this.preferences, required TabGrid Function() grid, required bool visible, required dynamic Function(Offset, WidgetContainerModel) onNTDragUpdate, @@ -1656,6 +1672,8 @@ class _AddWidgetDialogState extends State<_AddWidgetDialog> { child: TabBarView( children: [ NetworkTableTree( + ntConnection: widget.ntConnection, + preferences: widget.preferences, listLayoutBuilder: ( {required title, required children}) { return widget._grid().createListLayout( diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index c580e616..674b140e 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -153,6 +153,14 @@ class NTConnection { return _ntClient.getTopicFromName(topic); } + void publishTopic(NT4Topic topic) { + _ntClient.publishTopic(topic); + } + + NT4Topic publishNewTopic(String name, String type) { + return _ntClient.publishNewTopic(name, type); + } + bool isTopicPublished(NT4Topic? topic) { return _ntClient.isTopicPublished(topic); } diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 79efb2df..8d34d747 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -1,9 +1,10 @@ -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/log.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_container.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/accelerometer.dart'; @@ -50,7 +51,7 @@ class NTWidgetBuilder { static final Map< String, NTWidgetModel Function({ - required ElasticSharedData elasticData, + required NTConnection ntConnection, required String topic, String dataType, double period, @@ -59,7 +60,7 @@ class NTWidgetBuilder { static final Map< String, NTWidgetModel Function({ - required ElasticSharedData elasticData, + required NTConnection ntConnection, required Map jsonData, })> _modelJsonBuildMap = {}; @@ -321,6 +322,7 @@ class NTWidgetBuilder { } static NTWidgetModel buildNTModelFromType( + NTConnection ntConnection, String type, String topic, { String dataType = 'Unknown', @@ -332,6 +334,7 @@ class NTWidgetBuilder { if (_modelNameBuildMap.containsKey(type)) { return _modelNameBuildMap[type]!( + ntConnection: ntConnection, topic: topic, dataType: dataType, period: period, @@ -339,6 +342,7 @@ class NTWidgetBuilder { } return NTWidgetModel.createDefault( + ntConnection: ntConnection, type: type, topic: topic, dataType: dataType, @@ -346,20 +350,22 @@ class NTWidgetBuilder { ); } - static NTWidgetModel buildNTModelFromJson( - ElasticSharedData elasticData, - String type, Map jsonData, + static NTWidgetModel buildNTModelFromJson(NTConnection ntConnection, + SharedPreferences preferences, String type, Map jsonData, {Function(String message)? onWidgetTypeNotFound}) { ensureInitialized(); if (_modelJsonBuildMap.containsKey(type)) { - return _modelJsonBuildMap[type]!(jsonData: jsonData); + return _modelJsonBuildMap[type]!( + ntConnection: ntConnection, + jsonData: jsonData, + ); } onWidgetTypeNotFound ?.call('Unknown widget type: \'$type\', defaulting to Empty Model.'); return NTWidgetModel.createDefault( - elasticData: elasticData, + ntConnection: ntConnection, type: type, topic: tryCast(jsonData['topic']) ?? '', dataType: tryCast(jsonData['data_type']) ?? 'Unknown', diff --git a/lib/services/shuffleboard_nt_listener.dart b/lib/services/shuffleboard_nt_listener.dart index fe2b1f76..c964828c 100644 --- a/lib/services/shuffleboard_nt_listener.dart +++ b/lib/services/shuffleboard_nt_listener.dart @@ -1,7 +1,8 @@ import 'package:dot_cast/dot_cast.dart'; -import 'package:elastic_dashboard/pages/dashboard_page.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/widget_container_model.dart'; @@ -13,7 +14,8 @@ class ShuffleboardNTListener { static const String tabsEntry = '$metadataTable/Tabs'; static const String selectedEntry = '$metadataTable/Selected'; - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final Function(Map widgetData)? onWidgetAdded; final Function(String tab)? onTabChanged; final Function(String tab)? onTabCreated; @@ -24,18 +26,22 @@ class ShuffleboardNTListener { Map> currentJsonData = {}; - late final NetworkTableTreeRow shuffleboardTreeRoot = - NetworkTableTreeRow(elasticData: elasticData, topic: '/', rowName: ''); + late final NetworkTableTreeRow shuffleboardTreeRoot = NetworkTableTreeRow( + ntConnection: ntConnection, + preferences: preferences, + topic: '/', + rowName: ''); ShuffleboardNTListener({ - required this.elasticData, + required this.ntConnection, + required this.preferences, this.onTabChanged, this.onTabCreated, this.onWidgetAdded, }); void initializeSubscriptions() { - selectedSubscription = elasticData.ntConnection.subscribe(selectedEntry); + selectedSubscription = ntConnection.subscribe(selectedEntry); } void initializeListeners() { @@ -53,19 +59,19 @@ class ShuffleboardNTListener { // Also clear data when connected in case if threads auto populate json after disconnection // Chances are low since the timing has to be just right but you never know - elasticData.ntConnection.addConnectedListener(() { + ntConnection.addConnectedListener(() { currentJsonData.clear(); shuffleboardTreeRoot.clearRows(); previousSelection = null; }); - elasticData.ntConnection.addDisconnectedListener(() { + ntConnection.addDisconnectedListener(() { currentJsonData.clear(); shuffleboardTreeRoot.clearRows(); previousSelection = null; }); - elasticData.ntConnection.addTopicAnnounceListener((topic) async { + ntConnection.addTopicAnnounceListener((topic) async { if (!topic.name.contains(shuffleboardTableRoot)) { return; } @@ -110,8 +116,8 @@ class ShuffleboardNTListener { if (name.contains('/Properties')) { String propertyTopic = metaHierarchy[metaHierarchy.length - 1]; - Object? subProperty = await elasticData.ntConnection - .subscribeAndRetrieveData(propertyTopic); + Object? subProperty = + await ntConnection.subscribeAndRetrieveData(propertyTopic); String real = realHierarchy[realHierarchy.length - 1]; List realTopics = real.split('/'); @@ -185,8 +191,8 @@ class ShuffleboardNTListener { if (name.endsWith('/PreferredComponent')) { String componentTopic = metaHierarchy[metaHierarchy.length - 1]; - String? type = await elasticData.ntConnection - .subscribeAndRetrieveData(componentTopic); + String? type = + await ntConnection.subscribeAndRetrieveData(componentTopic); if (inLayout) { Map child = _createOrGetChild(jsonKey, widgetName); @@ -202,8 +208,7 @@ class ShuffleboardNTListener { String sizeTopic = metaHierarchy[metaHierarchy.length - 1]; List sizeRaw = - await elasticData.ntConnection.subscribeAndRetrieveData(sizeTopic) ?? - []; + await ntConnection.subscribeAndRetrieveData(sizeTopic) ?? []; List size = sizeRaw.whereType().toList(); if (size.length < 2) { @@ -227,9 +232,8 @@ class ShuffleboardNTListener { if (name.endsWith('/Position')) { String positionTopic = metaHierarchy[metaHierarchy.length - 1]; - List positionRaw = await elasticData.ntConnection - .subscribeAndRetrieveData(positionTopic) ?? - []; + List positionRaw = + await ntConnection.subscribeAndRetrieveData(positionTopic) ?? []; List position = positionRaw.whereType().toList(); if (position.length < 2) { @@ -301,8 +305,7 @@ class ShuffleboardNTListener { if (widgetRow.hasRow('.type')) { String typeTopic = widgetRow.getRow('.type').topic; - String? type = await elasticData.ntConnection.subscribeAndRetrieveData( - typeTopic, + String? type = await ntConnection.subscribeAndRetrieveData(typeTopic, timeout: const Duration(seconds: 3)); if (type == 'ShuffleboardLayout') { @@ -344,7 +347,7 @@ class ShuffleboardNTListener { if (isCameraStream) { String? cameraStream = - await elasticData.ntConnection.subscribeAndRetrieveData(topic.name); + await ntConnection.subscribeAndRetrieveData(topic.name); if (cameraStream == null) { return; @@ -389,7 +392,7 @@ class ShuffleboardNTListener { ? Settings.defaultPeriod : Settings.defaultGraphPeriod); - if (elasticData.ntConnection.isNT4Connected) { + if (ntConnection.isNT4Connected) { onWidgetAdded?.call(currentJsonData[jsonKey]!); } @@ -457,9 +460,8 @@ class ShuffleboardNTListener { } if (isCameraStream) { - String? cameraStream = await elasticData.ntConnection - .subscribeAndRetrieveData( - childRow.getRow('.ShuffleboardURI').topic); + String? cameraStream = await ntConnection.subscribeAndRetrieveData( + childRow.getRow('.ShuffleboardURI').topic); if (cameraStream == null) { continue; @@ -498,7 +500,7 @@ class ShuffleboardNTListener { widget?.disposeModel(deleting: true); widget?.forceDispose(); } - if (elasticData.ntConnection.isNT4Connected) { + if (ntConnection.isNT4Connected) { onWidgetAdded?.call(currentJsonData[jsonKey]!); } }); diff --git a/lib/widgets/draggable_containers/models/layout_container_model.dart b/lib/widgets/draggable_containers/models/layout_container_model.dart index 8c73c654..7e61d707 100644 --- a/lib/widgets/draggable_containers/models/layout_container_model.dart +++ b/lib/widgets/draggable_containers/models/layout_container_model.dart @@ -9,6 +9,7 @@ abstract class LayoutContainerModel extends WidgetContainerModel { String get type; LayoutContainerModel({ + required super.preferences, required super.initialPosition, required super.title, super.minWidth, @@ -17,6 +18,7 @@ abstract class LayoutContainerModel extends WidgetContainerModel { LayoutContainerModel.fromJson({ required super.jsonData, + required super.preferences, super.enabled, super.minWidth, super.minHeight, diff --git a/lib/widgets/draggable_containers/models/list_layout_model.dart b/lib/widgets/draggable_containers/models/list_layout_model.dart index 4872a713..08c51459 100644 --- a/lib/widgets/draggable_containers/models/list_layout_model.dart +++ b/lib/widgets/draggable_containers/models/list_layout_model.dart @@ -7,6 +7,7 @@ import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -25,6 +26,7 @@ class ListLayoutModel extends LayoutContainerModel { String labelPosition = 'TOP'; + final NTConnection ntConnection; final TabGrid tabGrid; final Function(WidgetContainerModel model)? onDragCancel; @@ -37,8 +39,10 @@ class ListLayoutModel extends LayoutContainerModel { ]; ListLayoutModel({ + required super.preferences, required super.initialPosition, required super.title, + required this.ntConnection, required this.tabGrid, required this.onDragCancel, List? children, @@ -53,6 +57,8 @@ class ListLayoutModel extends LayoutContainerModel { ListLayoutModel.fromJson({ required super.jsonData, + required super.preferences, + required this.ntConnection, required this.tabGrid, required this.onDragCancel, super.enabled, @@ -121,6 +127,8 @@ class ListLayoutModel extends LayoutContainerModel { for (Map childData in jsonData['children']) { children.add( NTWidgetContainerModel.fromJson( + ntConnection: ntConnection, + preferences: preferences, jsonData: childData, enabled: enabled, onJsonLoadingWarning: onJsonLoadingWarning, diff --git a/lib/widgets/draggable_containers/models/nt_widget_container_model.dart b/lib/widgets/draggable_containers/models/nt_widget_container_model.dart index 26138adc..067d05a4 100644 --- a/lib/widgets/draggable_containers/models/nt_widget_container_model.dart +++ b/lib/widgets/draggable_containers/models/nt_widget_container_model.dart @@ -4,6 +4,7 @@ import 'package:dot_cast/dot_cast.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:provider/provider.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; @@ -15,10 +16,13 @@ import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; import 'widget_container_model.dart'; class NTWidgetContainerModel extends WidgetContainerModel { + final NTConnection ntConnection; late NTWidget child; late NTWidgetModel childModel; NTWidgetContainerModel({ + required this.ntConnection, + required super.preferences, required super.initialPosition, required super.title, required this.childModel, @@ -26,7 +30,9 @@ class NTWidgetContainerModel extends WidgetContainerModel { }); NTWidgetContainerModel.fromJson({ + required this.ntConnection, required super.jsonData, + required super.preferences, super.enabled, super.onJsonLoadingWarning, }) : super.fromJson(); @@ -82,8 +88,13 @@ class NTWidgetContainerModel extends WidgetContainerModel { String type = tryCast(jsonData['type']) ?? ''; - childModel = NTWidgetBuilder.buildNTModelFromJson(type, widgetProperties, - onWidgetTypeNotFound: onJsonLoadingWarning); + childModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + type, + widgetProperties, + onWidgetTypeNotFound: onJsonLoadingWarning, + ); } @override @@ -300,6 +311,7 @@ class NTWidgetContainerModel extends WidgetContainerModel { childModel.forceDispose(); childModel = NTWidgetBuilder.buildNTModelFromType( + ntConnection, type, childModel.topic, dataType: childModel.dataType, diff --git a/lib/widgets/draggable_containers/models/widget_container_model.dart b/lib/widgets/draggable_containers/models/widget_container_model.dart index 31307287..625c78bc 100644 --- a/lib/widgets/draggable_containers/models/widget_container_model.dart +++ b/lib/widgets/draggable_containers/models/widget_container_model.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -9,6 +10,7 @@ import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_ abstract class WidgetContainerModel extends ChangeNotifier { final Key key = UniqueKey(); + final SharedPreferences preferences; String? title; @@ -40,6 +42,7 @@ abstract class WidgetContainerModel extends ChangeNotifier { late Rect dragStartLocation; WidgetContainerModel({ + required this.preferences, required Rect initialPosition, required this.title, this.enabled = false, @@ -52,6 +55,7 @@ abstract class WidgetContainerModel extends ChangeNotifier { WidgetContainerModel.fromJson({ required Map jsonData, + required this.preferences, this.enabled = false, this.minWidth = 128.0, this.minHeight = 128.0, diff --git a/lib/widgets/network_tree/networktables_tree.dart b/lib/widgets/network_tree/networktables_tree.dart index dc6853b8..ec16d970 100644 --- a/lib/widgets/network_tree/networktables_tree.dart +++ b/lib/widgets/network_tree/networktables_tree.dart @@ -1,12 +1,13 @@ import 'dart:ui'; -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/list_layout_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/widget_container_model.dart'; @@ -18,7 +19,8 @@ typedef ListLayoutBuilder = ListLayoutModel Function({ }); class NetworkTableTree extends StatefulWidget { - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final ListLayoutBuilder listLayoutBuilder; final Function(Offset globalPosition, WidgetContainerModel widget)? @@ -29,7 +31,8 @@ class NetworkTableTree extends StatefulWidget { const NetworkTableTree({ super.key, - required this.elasticData, + required this.ntConnection, + required this.preferences, required this.listLayoutBuilder, required this.hideMetadata, this.onDragUpdate, @@ -41,7 +44,11 @@ class NetworkTableTree extends StatefulWidget { } class _NetworkTableTreeState extends State { - late final NetworkTableTreeRow root = NetworkTableTreeRow(elasticData: widget.elasticData, topic: '/', rowName: ''); + late final NetworkTableTreeRow root = NetworkTableTreeRow( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + topic: '/', + rowName: ''); late final TreeController treeController; late final Function(Offset globalPosition, WidgetContainerModel widget)? @@ -67,7 +74,7 @@ class _NetworkTableTreeState extends State { }, ); - widget.elasticData.ntConnection.addTopicAnnounceListener(onNewTopicAnnounced = (topic) { + widget.ntConnection.addTopicAnnounceListener(onNewTopicAnnounced = (topic) { setState(() { treeController.rebuild(); }); @@ -76,7 +83,7 @@ class _NetworkTableTreeState extends State { @override void dispose() { - widget.elasticData.ntConnection.removeTopicAnnounceListener(onNewTopicAnnounced); + widget.ntConnection.removeTopicAnnounceListener(onNewTopicAnnounced); super.dispose(); } @@ -127,7 +134,7 @@ class _NetworkTableTreeState extends State { Widget build(BuildContext context) { List topics = []; - for (NT4Topic topic in widget.elasticData.ntConnection.announcedTopics().values) { + for (NT4Topic topic in widget.ntConnection.announcedTopics().values) { if (topic.name == 'Time') { continue; } @@ -147,6 +154,7 @@ class _NetworkTableTreeState extends State { (BuildContext context, TreeEntry entry) { return TreeTile( key: UniqueKey(), + preferences: widget.preferences, entry: entry, listLayoutBuilder: widget.listLayoutBuilder, onDragUpdate: onDragUpdate, @@ -164,15 +172,7 @@ class _NetworkTableTreeState extends State { } class TreeTile extends StatelessWidget { - TreeTile({ - super.key, - required this.entry, - required this.onTap, - required this.listLayoutBuilder, - this.onDragUpdate, - this.onDragEnd, - }); - + final SharedPreferences preferences; final TreeEntry entry; final VoidCallback onTap; @@ -184,6 +184,16 @@ class TreeTile extends StatelessWidget { WidgetContainerModel? draggingWidget; + TreeTile({ + super.key, + required this.preferences, + required this.entry, + required this.onTap, + required this.listLayoutBuilder, + this.onDragUpdate, + this.onDragEnd, + }); + @override Widget build(BuildContext context) { TextStyle trailingStyle = diff --git a/lib/widgets/network_tree/networktables_tree_row.dart b/lib/widgets/network_tree/networktables_tree_row.dart index a5e4ba2d..1307e8e8 100644 --- a/lib/widgets/network_tree/networktables_tree_row.dart +++ b/lib/widgets/network_tree/networktables_tree_row.dart @@ -1,7 +1,9 @@ -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_container.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; @@ -14,7 +16,8 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/boolean_box.da import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/text_display.dart'; class NetworkTableTreeRow { - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final String topic; final String rowName; @@ -23,7 +26,8 @@ class NetworkTableTreeRow { List children = []; NetworkTableTreeRow({ - required this.elasticData, + required this.ntConnection, + required this.preferences, required this.topic, required this.rowName, this.ntTopic, @@ -77,7 +81,8 @@ class NetworkTableTreeRow { NetworkTableTreeRow createNewRow( {required String topic, required String name, NT4Topic? ntTopic}) { NetworkTableTreeRow newRow = NetworkTableTreeRow( - elasticData: elasticData, + ntConnection: ntConnection, + preferences: preferences, topic: topic, rowName: name, ntTopic: ntTopic, @@ -107,7 +112,8 @@ class NetworkTableTreeRow { children.clear(); } - static NTWidgetModel? getNTWidgetFromTopic(NT4Topic ntTopic) { + static NTWidgetModel? getNTWidgetFromTopic( + NTConnection ntConnection, NT4Topic ntTopic) { switch (ntTopic.type) { case NT4TypeStr.kFloat64: case NT4TypeStr.kInt: @@ -119,11 +125,13 @@ class NetworkTableTreeRow { case NT4TypeStr.kString: case NT4TypeStr.kStringArr: return TextDisplayModel( + ntConnection: ntConnection, topic: ntTopic.name, dataType: ntTopic.type, ); case NT4TypeStr.kBool: return BooleanBoxModel( + ntConnection: ntConnection, topic: ntTopic.name, dataType: ntTopic.type, ); @@ -146,7 +154,7 @@ class NetworkTableTreeRow { (hasRow('description') || hasRow('connected')); if (isCameraStream) { - return CameraStreamModel(topic: topic); + return CameraStreamModel(ntConnection: ntConnection, topic: topic); } if (hasRows([ @@ -158,17 +166,17 @@ class NetworkTableTreeRow { 'sizeFrontBack', 'sizeLeftRight', ])) { - return YAGSLSwerveDriveModel(topic: topic); + return YAGSLSwerveDriveModel(ntConnection: ntConnection, topic: topic); } return null; } - return getNTWidgetFromTopic(ntTopic!); + return getNTWidgetFromTopic(ntConnection, ntTopic!); } Future getTypeString(String typeTopic) async { - return elasticData.ntConnection.subscribeAndRetrieveData(typeTopic); + return ntConnection.subscribeAndRetrieveData(typeTopic); } Future? getTypedWidget(String typeTopic) async { @@ -178,7 +186,7 @@ class NetworkTableTreeRow { return null; } - return NTWidgetBuilder.buildNTModelFromType(type, topic); + return NTWidgetBuilder.buildNTModelFromType(ntConnection, type, topic); } Future?> getListLayoutChildren() async { @@ -233,6 +241,8 @@ class NetworkTableTreeRow { double height = NTWidgetBuilder.getDefaultHeight(primary); return NTWidgetContainerModel( + ntConnection: ntConnection, + preferences: preferences, initialPosition: Rect.fromLTWH(0.0, 0.0, width, height), title: rowName, childModel: primary, diff --git a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart index 7b3439d8..0365274a 100644 --- a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart @@ -14,10 +14,16 @@ class AccelerometerModel extends NTWidgetModel { String get valueTopic => '$topic/Value'; - AccelerometerModel({required super.topic, super.dataType, super.period}) + AccelerometerModel( + {required super.ntConnection, + required super.topic, + super.dataType, + super.period}) : super(); - AccelerometerModel.fromJson({required super.jsonData}) : super.fromJson(); + AccelerometerModel.fromJson( + {required super.ntConnection, required super.jsonData}) + : super.fromJson(); @override void init() { @@ -56,7 +62,7 @@ class AccelerometerWidget extends NTWidget { return StreamBuilder( stream: model.valueSubscription.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.valueTopic), + initialData: model.ntConnection.getLastAnnouncedValue(model.valueTopic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart index 41777c23..d6843ce7 100644 --- a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart @@ -36,6 +36,7 @@ class BasicSwerveModel extends NTWidgetModel { String _rotationUnit = 'Radians'; BasicSwerveModel({ + required super.ntConnection, required super.topic, bool showRobotRotation = true, String rotationUnit = 'Radians', @@ -45,7 +46,8 @@ class BasicSwerveModel extends NTWidgetModel { _showRobotRotation = showRobotRotation, super(); - BasicSwerveModel.fromJson({required Map jsonData}) + BasicSwerveModel.fromJson( + {required super.ntConnection, required Map jsonData}) : super.fromJson(jsonData: jsonData) { _showRobotRotation = tryCast(jsonData['show_robot_rotation']) ?? true; _rotationUnit = tryCast(jsonData['rotation_unit']) ?? 'Degrees'; @@ -192,36 +194,36 @@ class SwerveDriveWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double frontLeftAngle = tryCast(ntConnection + double frontLeftAngle = tryCast(model.ntConnection .getLastAnnouncedValue(model.frontLeftAngleTopic)) ?? 0.0; - double frontLeftVelocity = tryCast(ntConnection + double frontLeftVelocity = tryCast(model.ntConnection .getLastAnnouncedValue(model.frontLeftVelocityTopic)) ?? 0.0; - double frontRightAngle = tryCast(ntConnection + double frontRightAngle = tryCast(model.ntConnection .getLastAnnouncedValue(model.frontRightAngleTopic)) ?? 0.0; - double frontRightVelocity = tryCast(ntConnection + double frontRightVelocity = tryCast(model.ntConnection .getLastAnnouncedValue(model.frontRightVelocityTopic)) ?? 0.0; - double backLeftAngle = tryCast( - ntConnection.getLastAnnouncedValue(model.backLeftAngleTopic)) ?? + double backLeftAngle = tryCast(model.ntConnection + .getLastAnnouncedValue(model.backLeftAngleTopic)) ?? 0.0; - double backLeftVelocity = tryCast(ntConnection + double backLeftVelocity = tryCast(model.ntConnection .getLastAnnouncedValue(model.backLeftVelocityTopic)) ?? 0.0; - double backRightAngle = tryCast(ntConnection + double backRightAngle = tryCast(model.ntConnection .getLastAnnouncedValue(model.backRightAngleTopic)) ?? 0.0; - double backRightVelocity = tryCast(ntConnection + double backRightVelocity = tryCast(model.ntConnection .getLastAnnouncedValue(model.backRightVelocityTopic)) ?? 0.0; - double robotAngle = tryCast( - ntConnection.getLastAnnouncedValue(model.robotAngleTopic)) ?? + double robotAngle = tryCast(model.ntConnection + .getLastAnnouncedValue(model.robotAngleTopic)) ?? 0.0; if (model.rotationUnit == 'Degrees') { diff --git a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart index ff9962aa..d4586879 100644 --- a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart +++ b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart @@ -30,10 +30,17 @@ class CameraStreamModel extends NTWidgetModel { set clientOpen(value) => _clientOpen = value; - CameraStreamModel({required super.topic, super.dataType, super.period}) - : super(); - - CameraStreamModel.fromJson({required super.jsonData}) : super.fromJson(); + CameraStreamModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + CameraStreamModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void init() { @@ -100,16 +107,16 @@ class CameraStreamWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - if (!ntConnection.isNT4Connected && model._clientOpen) { + if (!model.ntConnection.isNT4Connected && model._clientOpen) { model.closeClient(); } bool createNewWidget = model._streamWidget == null || - (!model.clientOpen && ntConnection.isNT4Connected); + (!model.clientOpen && model.ntConnection.isNT4Connected); - List rawStreams = - tryCast(ntConnection.getLastAnnouncedValue(model.streamsTopic)) ?? - []; + List rawStreams = tryCast( + model.ntConnection.getLastAnnouncedValue(model.streamsTopic)) ?? + []; List streams = []; for (Object? stream in rawStreams) { @@ -122,7 +129,7 @@ class CameraStreamWidget extends NTWidget { streams.add(stream.substring('mjpg:'.length)); } - if (streams.isEmpty || !ntConnection.isNT4Connected) { + if (streams.isEmpty || !model.ntConnection.isNT4Connected) { return Stack( fit: StackFit.expand, children: [ @@ -141,7 +148,7 @@ class CameraStreamWidget extends NTWidget { CustomLoadingIndicator(), const SizedBox(height: 10), Text( - (ntConnection.isNT4Connected) + (model.ntConnection.isNT4Connected) ? 'Waiting for Camera Stream connection...' : 'Waiting for Network Tables Connection...', textAlign: TextAlign.center, diff --git a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart index b8a31670..d2c9a4b6 100644 --- a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart @@ -36,6 +36,7 @@ class ComboBoxChooserModel extends NTWidgetModel { } ComboBoxChooserModel({ + required super.ntConnection, required super.topic, bool sortOptions = false, super.dataType, @@ -43,8 +44,10 @@ class ComboBoxChooserModel extends NTWidgetModel { }) : _sortOptions = sortOptions, super(); - ComboBoxChooserModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + ComboBoxChooserModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _sortOptions = tryCast(jsonData['sort_options']) ?? _sortOptions; } @@ -81,8 +84,8 @@ class ComboBoxChooserModel extends NTWidgetModel { return; } - _selectedTopic ??= ntConnection.nt4Client - .publishNewTopic(selectedTopicName, NT4TypeStr.kString); + _selectedTopic ??= + ntConnection.publishNewTopic(selectedTopicName, NT4TypeStr.kString); ntConnection.updateDataFromTopic(_selectedTopic!, selected); } @@ -101,7 +104,7 @@ class ComboBoxChooserModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_activeTopic!); + ntConnection.publishTopic(_activeTopic!); } ntConnection.updateDataFromTopic(_activeTopic!, active); @@ -146,7 +149,7 @@ class ComboBoxChooser extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List rawOptions = ntConnection + List rawOptions = model.ntConnection .getLastAnnouncedValue(model.optionsTopicName) ?.tryCast>() ?? []; @@ -157,25 +160,25 @@ class ComboBoxChooser extends NTWidget { options.sort(); } - String? active = - tryCast(ntConnection.getLastAnnouncedValue(model.activeTopicName)); + String? active = tryCast( + model.ntConnection.getLastAnnouncedValue(model.activeTopicName)); if (active != null && active == '') { active = null; } String? selected = tryCast( - ntConnection.getLastAnnouncedValue(model.selectedTopicName)); + model.ntConnection.getLastAnnouncedValue(model.selectedTopicName)); if (selected != null && selected == '') { selected = null; } - String? defaultOption = - tryCast(ntConnection.getLastAnnouncedValue(model.defaultTopicName)); + String? defaultOption = tryCast( + model.ntConnection.getLastAnnouncedValue(model.defaultTopicName)); if (defaultOption != null && defaultOption == '') { defaultOption = null; } - if (!ntConnection.isNT4Connected) { + if (!model.ntConnection.isNT4Connected) { active = null; selected = null; defaultOption = null; diff --git a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart index 94eb7558..06b6e7d7 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart @@ -18,10 +18,17 @@ class CommandSchedulerModel extends NTWidgetModel { String get idsTopicName => '$topic/Ids'; String get cancelTopicName => '$topic/Cancel'; - CommandSchedulerModel({required super.topic, super.dataType, super.period}) - : super(); - - CommandSchedulerModel.fromJson({required super.jsonData}) : super.fromJson(); + CommandSchedulerModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + CommandSchedulerModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void resetSubscription() { @@ -41,8 +48,8 @@ class CommandSchedulerModel extends NTWidgetModel { currentCancellations.add(id); - _cancelTopic ??= ntConnection.nt4Client - .publishNewTopic(cancelTopicName, NT4TypeStr.kIntArr); + _cancelTopic ??= + ntConnection.publishNewTopic(cancelTopicName, NT4TypeStr.kIntArr); if (_cancelTopic == null) { return; @@ -82,12 +89,12 @@ class CommandSchedulerWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List rawNames = ntConnection + List rawNames = model.ntConnection .getLastAnnouncedValue(model.namesTopicName) ?.tryCast>() ?? []; - List rawIds = ntConnection + List rawIds = model.ntConnection .getLastAnnouncedValue(model.idsTopicName) ?.tryCast>() ?? []; diff --git a/lib/widgets/nt_widgets/multi-topic/command_widget.dart b/lib/widgets/nt_widgets/multi-topic/command_widget.dart index 7eff26e2..5f7f04ba 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_widget.dart @@ -26,6 +26,7 @@ class CommandModel extends NTWidgetModel { } CommandModel({ + required super.ntConnection, required super.topic, bool showType = true, super.dataType, @@ -33,8 +34,10 @@ class CommandModel extends NTWidgetModel { }) : _showType = showType, super(); - CommandModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + CommandModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _showType = tryCast(jsonData['show_type']) ?? _showType; } @@ -91,11 +94,11 @@ class CommandWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - bool running = ntConnection + bool running = model.ntConnection .getLastAnnouncedValue(model.runningTopicName) ?.tryCast() ?? false; - String name = ntConnection + String name = model.ntConnection .getLastAnnouncedValue(model.nameTopicName) ?.tryCast() ?? 'Unknown'; @@ -119,17 +122,18 @@ class CommandWidget extends NTWidget { bool publishTopic = model.runningTopic == null; model.runningTopic = - ntConnection.getTopicFromName(model.runningTopicName); + model.ntConnection.getTopicFromName(model.runningTopicName); if (model.runningTopic == null) { return; } if (publishTopic) { - ntConnection.nt4Client.publishTopic(model.runningTopic!); + model.ntConnection.publishTopic(model.runningTopic!); } - ntConnection.updateDataFromTopic(model.runningTopic!, !running); + model.ntConnection + .updateDataFromTopic(model.runningTopic!, !running); }, child: AnimatedContainer( duration: const Duration(milliseconds: 50), diff --git a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart index 52cd62ce..59913050 100644 --- a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart @@ -41,10 +41,17 @@ class DifferentialDriveModel extends NTWidgetModel { set rightSpeedCurrentValue(value) => _rightSpeedCurrentValue = value; - DifferentialDriveModel({required super.topic, super.dataType, super.period}) - : super(); - - DifferentialDriveModel.fromJson({required super.jsonData}) : super.fromJson(); + DifferentialDriveModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + DifferentialDriveModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void resetSubscription() { @@ -90,10 +97,10 @@ class DifferentialDrive extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double leftSpeed = tryCast( - ntConnection.getLastAnnouncedValue(model.leftSpeedTopicName)) ?? + double leftSpeed = tryCast(model.ntConnection + .getLastAnnouncedValue(model.leftSpeedTopicName)) ?? 0.0; - double rightSpeed = tryCast(ntConnection + double rightSpeed = tryCast(model.ntConnection .getLastAnnouncedValue(model.rightSpeedTopicName)) ?? 0.0; @@ -133,19 +140,18 @@ class DifferentialDrive extends NTWidget { onChangeEnd: (value) { bool publishTopic = model.leftSpeedTopic == null; - model.leftSpeedTopic ??= - ntConnection.getTopicFromName(model.leftSpeedTopicName); + model.leftSpeedTopic ??= model.ntConnection + .getTopicFromName(model.leftSpeedTopicName); if (model.leftSpeedTopic == null) { return; } if (publishTopic) { - ntConnection.nt4Client - .publishTopic(model.leftSpeedTopic!); + model.ntConnection.publishTopic(model.leftSpeedTopic!); } - ntConnection.updateDataFromTopic( + model.ntConnection.updateDataFromTopic( model.leftSpeedTopic!, model.leftSpeedCurrentValue); model.leftSpeedPreviousValue = model.leftSpeedCurrentValue; @@ -206,7 +212,7 @@ class DifferentialDrive extends NTWidget { onChangeEnd: (value) { bool publishTopic = model.rightSpeedTopic == null; - model.rightSpeedTopic ??= ntConnection + model.rightSpeedTopic ??= model.ntConnection .getTopicFromName(model.rightSpeedTopicName); if (model.rightSpeedTopic == null) { @@ -214,11 +220,10 @@ class DifferentialDrive extends NTWidget { } if (publishTopic) { - ntConnection.nt4Client - .publishTopic(model.rightSpeedTopic!); + model.ntConnection.publishTopic(model.rightSpeedTopic!); } - ntConnection.updateDataFromTopic( + model.ntConnection.updateDataFromTopic( model.rightSpeedTopic!, model.rightSpeedCurrentValue); model.rightSpeedPreviousValue = diff --git a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart index b20a634b..22b32f3c 100644 --- a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart @@ -12,9 +12,17 @@ class EncoderModel extends NTWidgetModel { String get distanceTopic => '$topic/Distance'; String get speedTopic => '$topic/Speed'; - EncoderModel({required super.topic, super.dataType, super.period}) : super(); + EncoderModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); - EncoderModel.fromJson({required super.jsonData}) : super.fromJson(); + EncoderModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override List getCurrentData() { @@ -39,12 +47,12 @@ class EncoderWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double distance = - tryCast(ntConnection.getLastAnnouncedValue(model.distanceTopic)) ?? - 0.0; - double speed = - tryCast(ntConnection.getLastAnnouncedValue(model.speedTopic)) ?? - 0.0; + double distance = tryCast(model.ntConnection + .getLastAnnouncedValue(model.distanceTopic)) ?? + 0.0; + double speed = tryCast( + model.ntConnection.getLastAnnouncedValue(model.speedTopic)) ?? + 0.0; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/widgets/nt_widgets/multi-topic/field_widget.dart b/lib/widgets/nt_widgets/multi-topic/field_widget.dart index 31f954fb..b34a2e1d 100644 --- a/lib/widgets/nt_widgets/multi-topic/field_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/field_widget.dart @@ -44,6 +44,7 @@ class FieldWidgetModel extends NTWidgetModel { late Function(NT4Topic topic) topicAnnounceListener; FieldWidgetModel({ + required super.ntConnection, required super.topic, String? fieldName, bool showOtherObjects = true, @@ -62,7 +63,8 @@ class FieldWidgetModel extends NTWidgetModel { _field = FieldImages.getFieldFromGame(_fieldGame)!; } - FieldWidgetModel.fromJson({required Map jsonData}) + FieldWidgetModel.fromJson( + {required super.ntConnection, required Map jsonData}) : super.fromJson(jsonData: jsonData) { _fieldGame = tryCast(jsonData['field_game']) ?? _fieldGame; @@ -96,7 +98,7 @@ class FieldWidgetModel extends NTWidgetModel { } }; - ntConnection.nt4Client.addTopicAnnounceListener(topicAnnounceListener); + ntConnection.addTopicAnnounceListener(topicAnnounceListener); } @override @@ -113,7 +115,7 @@ class FieldWidgetModel extends NTWidgetModel { if (deleting) { _field.dispose(); - ntConnection.nt4Client.removeTopicAnnounceListener(topicAnnounceListener); + ntConnection.removeTopicAnnounceListener(topicAnnounceListener); } _widgetSize = null; @@ -291,6 +293,10 @@ class FieldWidgetModel extends NTWidgetModel { .getLastAnnouncedValue(objectTopic) ?.tryCast>(); + if (objectPositionRaw == null) { + continue; + } + bool isTrajectory = objectPositionRaw.length > 24; if (isTrajectory && !_showTrajectories) { @@ -492,7 +498,7 @@ class FieldWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List robotPositionRaw = ntConnection + List robotPositionRaw = model.ntConnection .getLastAnnouncedValue(model._robotTopicName) ?.tryCast>() ?? []; @@ -548,10 +554,14 @@ class FieldWidget extends NTWidget { if (model.showOtherObjects || model.showTrajectories) { for (String objectTopic in model._otherObjectTopics) { - List? objectPositionRaw = ntConnection + List? objectPositionRaw = model.ntConnection .getLastAnnouncedValue(objectTopic) ?.tryCast>(); + if (objectPositionRaw == null) { + continue; + } + bool isTrajectory = objectPositionRaw.length > 24; if (isTrajectory && !model.showTrajectories) { diff --git a/lib/widgets/nt_widgets/multi-topic/fms_info.dart b/lib/widgets/nt_widgets/multi-topic/fms_info.dart index 0dfd214c..5c0a3018 100644 --- a/lib/widgets/nt_widgets/multi-topic/fms_info.dart +++ b/lib/widgets/nt_widgets/multi-topic/fms_info.dart @@ -18,9 +18,17 @@ class FMSInfoModel extends NTWidgetModel { String get replayNumberTopic => '$topic/ReplayNumber'; String get stationNumberTopic => '$topic/StationNumber'; - FMSInfoModel({required super.topic, super.dataType, super.period}); + FMSInfoModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }); - FMSInfoModel.fromJson({required super.jsonData}) : super.fromJson(); + FMSInfoModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override List getCurrentData() { @@ -84,23 +92,23 @@ class FMSInfo extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - String eventName = - tryCast(ntConnection.getLastAnnouncedValue(model.eventNameTopic)) ?? - ''; - int controlData = tryCast( - ntConnection.getLastAnnouncedValue(model.controlDataTopic)) ?? + String eventName = tryCast(model.ntConnection + .getLastAnnouncedValue(model.eventNameTopic)) ?? + ''; + int controlData = tryCast(model.ntConnection + .getLastAnnouncedValue(model.controlDataTopic)) ?? 32; - bool redAlliance = - tryCast(ntConnection.getLastAnnouncedValue(model.allianceTopic)) ?? - true; - int matchNumber = tryCast( - ntConnection.getLastAnnouncedValue(model.matchNumberTopic)) ?? + bool redAlliance = tryCast(model.ntConnection + .getLastAnnouncedValue(model.allianceTopic)) ?? + true; + int matchNumber = tryCast(model.ntConnection + .getLastAnnouncedValue(model.matchNumberTopic)) ?? 0; - int matchType = - tryCast(ntConnection.getLastAnnouncedValue(model.matchTypeTopic)) ?? - 0; - int replayNumber = tryCast( - ntConnection.getLastAnnouncedValue(model.replayNumberTopic)) ?? + int matchType = tryCast(model.ntConnection + .getLastAnnouncedValue(model.matchTypeTopic)) ?? + 0; + int replayNumber = tryCast(model.ntConnection + .getLastAnnouncedValue(model.replayNumberTopic)) ?? 0; String eventNameDisplay = '$eventName${(eventName != '') ? ' ' : ''}'; diff --git a/lib/widgets/nt_widgets/multi-topic/gyro.dart b/lib/widgets/nt_widgets/multi-topic/gyro.dart index b9b37f9e..68e1bfea 100644 --- a/lib/widgets/nt_widgets/multi-topic/gyro.dart +++ b/lib/widgets/nt_widgets/multi-topic/gyro.dart @@ -25,16 +25,19 @@ class GyroModel extends NTWidgetModel { refresh(); } - GyroModel( - {required super.topic, - bool counterClockwisePositive = false, - super.dataType, - super.period}) - : _counterClockwisePositive = counterClockwisePositive, + GyroModel({ + required super.ntConnection, + required super.topic, + bool counterClockwisePositive = false, + super.dataType, + super.period, + }) : _counterClockwisePositive = counterClockwisePositive, super(); - GyroModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + GyroModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _counterClockwisePositive = tryCast(jsonData['counter_clockwise_positive']) ?? false; } @@ -105,7 +108,7 @@ class Gyro extends NTWidget { return StreamBuilder( stream: model.valueSubscription.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.valueTopic), + initialData: model.ntConnection.getLastAnnouncedValue(model.valueTopic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart index a87b3fdd..772c55c4 100644 --- a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart @@ -15,10 +15,17 @@ class MotorControllerModel extends NTWidgetModel { late NT4Subscription valueSubscription; - MotorControllerModel({required super.topic, super.dataType, super.period}) - : super(); - - MotorControllerModel.fromJson({required super.jsonData}) : super.fromJson(); + MotorControllerModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + MotorControllerModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void init() { @@ -55,7 +62,7 @@ class MotorController extends NTWidget { return StreamBuilder( stream: model.valueSubscription.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.valueTopic), + initialData: model.ntConnection.getLastAnnouncedValue(model.valueTopic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart index 0cea366b..2372199e 100644 --- a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart +++ b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart @@ -13,10 +13,17 @@ class NetworkAlertsModel extends NTWidgetModel { String get warningsTopicName => '$topic/warnings'; String get infosTopicName => '$topic/infos'; - NetworkAlertsModel({required super.topic, super.dataType, super.period}) - : super(); - - NetworkAlertsModel.fromJson({required super.jsonData}) : super.fromJson(); + NetworkAlertsModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + NetworkAlertsModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override List getCurrentData() { @@ -55,17 +62,17 @@ class NetworkAlerts extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List errorsRaw = ntConnection + List errorsRaw = model.ntConnection .getLastAnnouncedValue(model.errorsTopicName) ?.tryCast>() ?? []; - List warningsRaw = ntConnection + List warningsRaw = model.ntConnection .getLastAnnouncedValue(model.warningsTopicName) ?.tryCast>() ?? []; - List infosRaw = ntConnection + List infosRaw = model.ntConnection .getLastAnnouncedValue(model.infosTopicName) ?.tryCast>() ?? []; diff --git a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart index 5b7b78d8..6bbfb6b2 100644 --- a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart @@ -48,10 +48,16 @@ class PIDControllerModel extends NTWidgetModel { set setpointLastValue(value) => _setpointLastValue = value; - PIDControllerModel({required super.topic, super.dataType, super.period}) + PIDControllerModel( + {required super.ntConnection, + required super.topic, + super.dataType, + super.period}) : super(); - PIDControllerModel.fromJson({required super.jsonData}) : super.fromJson(); + PIDControllerModel.fromJson( + {required super.ntConnection, required super.jsonData}) + : super.fromJson(); @override void resetSubscription() { @@ -75,7 +81,7 @@ class PIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kpTopic!); + ntConnection.publishTopic(_kpTopic!); } ntConnection.updateDataFromTopic(_kpTopic!, data); @@ -93,7 +99,7 @@ class PIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kiTopic!); + ntConnection.publishTopic(_kiTopic!); } ntConnection.updateDataFromTopic(_kiTopic!, data); @@ -111,7 +117,7 @@ class PIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kdTopic!); + ntConnection.publishTopic(_kdTopic!); } ntConnection.updateDataFromTopic(_kdTopic!, data); @@ -129,7 +135,7 @@ class PIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_setpointTopic!); + ntConnection.publishTopic(_setpointTopic!); } ntConnection.updateDataFromTopic(_setpointTopic!, data); @@ -172,16 +178,16 @@ class PIDControllerWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double kP = - tryCast(ntConnection.getLastAnnouncedValue(model.kpTopicName)) ?? - 0.0; - double kI = - tryCast(ntConnection.getLastAnnouncedValue(model.kiTopicName)) ?? - 0.0; - double kD = - tryCast(ntConnection.getLastAnnouncedValue(model.kdTopicName)) ?? - 0.0; - double setpoint = tryCast(ntConnection + double kP = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kpTopicName)) ?? + 0.0; + double kI = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kiTopicName)) ?? + 0.0; + double kD = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kdTopicName)) ?? + 0.0; + double setpoint = tryCast(model.ntConnection .getLastAnnouncedValue(model.setpointTopicName)) ?? 0.0; diff --git a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart index 29934e50..f4029c04 100644 --- a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart +++ b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart @@ -16,10 +16,17 @@ class PowerDistributionModel extends NTWidgetModel { String get voltageTopic => '$topic/Voltage'; String get currentTopic => '$topic/TotalCurrent'; - PowerDistributionModel({required super.topic, super.dataType, super.period}) - : super(); - - PowerDistributionModel.fromJson({required super.jsonData}) : super.fromJson(); + PowerDistributionModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + PowerDistributionModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void init() { @@ -70,7 +77,7 @@ class PowerDistribution extends NTWidget { List channels = []; for (int channel = start; channel <= end; channel++) { - double current = tryCast(ntConnection + double current = tryCast(model.ntConnection .getLastAnnouncedValue(model.channelTopics[channel])) ?? 0.0; @@ -108,7 +115,7 @@ class PowerDistribution extends NTWidget { List channels = []; for (int channel = start; channel >= end; channel--) { - double current = tryCast(ntConnection + double current = tryCast(model.ntConnection .getLastAnnouncedValue(model.channelTopics[channel])) ?? 0.0; @@ -149,12 +156,12 @@ class PowerDistribution extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double voltage = - tryCast(ntConnection.getLastAnnouncedValue(model.voltageTopic)) ?? - 0.0; - double totalCurrent = - tryCast(ntConnection.getLastAnnouncedValue(model.currentTopic)) ?? - 0.0; + double voltage = tryCast( + model.ntConnection.getLastAnnouncedValue(model.voltageTopic)) ?? + 0.0; + double totalCurrent = tryCast( + model.ntConnection.getLastAnnouncedValue(model.currentTopic)) ?? + 0.0; return Column( children: [ diff --git a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart index 87149b72..bf8db006 100644 --- a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart @@ -48,12 +48,17 @@ class ProfiledPIDControllerModel extends NTWidgetModel { set goalLastValue(value) => _goalLastValue = value; - ProfiledPIDControllerModel( - {required super.topic, super.dataType, super.period}) - : super(); - - ProfiledPIDControllerModel.fromJson({required super.jsonData}) - : super.fromJson(); + ProfiledPIDControllerModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + ProfiledPIDControllerModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void resetSubscription() { @@ -77,7 +82,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kpTopic!); + ntConnection.publishTopic(_kpTopic!); } ntConnection.updateDataFromTopic(_kpTopic!, data); @@ -95,7 +100,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kiTopic!); + ntConnection.publishTopic(_kiTopic!); } ntConnection.updateDataFromTopic(_kiTopic!, data); @@ -113,7 +118,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_kdTopic!); + ntConnection.publishTopic(_kdTopic!); } ntConnection.updateDataFromTopic(_kdTopic!, data); @@ -131,7 +136,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_goalTopic!); + ntConnection.publishTopic(_goalTopic!); } ntConnection.updateDataFromTopic(_goalTopic!, data); @@ -174,17 +179,17 @@ class ProfiledPIDControllerWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - double kP = - tryCast(ntConnection.getLastAnnouncedValue(model.kpTopicName)) ?? - 0.0; - double kI = - tryCast(ntConnection.getLastAnnouncedValue(model.kiTopicName)) ?? - 0.0; - double kD = - tryCast(ntConnection.getLastAnnouncedValue(model.kdTopicName)) ?? - 0.0; - double goal = tryCast( - ntConnection.getLastAnnouncedValue(model.goalTopicName)) ?? + double kP = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kpTopicName)) ?? + 0.0; + double kI = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kiTopicName)) ?? + 0.0; + double kD = tryCast(model.ntConnection + .getLastAnnouncedValue(model.kdTopicName)) ?? + 0.0; + double goal = tryCast(model.ntConnection + .getLastAnnouncedValue(model.goalTopicName)) ?? 0.0; // Creates the text editing controllers if they are null diff --git a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart index 006b5ba7..d42af9ac 100644 --- a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart @@ -17,9 +17,17 @@ class RelayModel extends NTWidgetModel { final List selectedOptions = ['Off', 'On', 'Forward', 'Reverse']; - RelayModel({required super.topic, super.dataType, super.period}) : super(); - - RelayModel.fromJson({required super.jsonData}) : super.fromJson(); + RelayModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + RelayModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void init() { @@ -58,7 +66,8 @@ class RelayWidget extends NTWidget { return StreamBuilder( stream: model.valueSubscription.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.valueTopicName), + initialData: + model.ntConnection.getLastAnnouncedValue(model.valueTopicName), builder: (context, snapshot) { String selected = tryCast(snapshot.data) ?? 'Off'; @@ -83,18 +92,19 @@ class RelayWidget extends NTWidget { bool publishTopic = model.valueTopic == null; - model.valueTopic ??= - ntConnection.getTopicFromName(model.valueTopicName); + model.valueTopic ??= model.ntConnection + .getTopicFromName(model.valueTopicName); if (model.valueTopic == null) { return; } if (publishTopic) { - ntConnection.nt4Client.publishTopic(model.valueTopic!); + model.ntConnection.publishTopic(model.valueTopic!); } - ntConnection.updateDataFromTopic(model.valueTopic!, option); + model.ntConnection + .updateDataFromTopic(model.valueTopic!, option); }, isSelected: model.selectedOptions.map((e) => selected == e).toList(), diff --git a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart index 615d9005..64b88f5e 100644 --- a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart +++ b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart @@ -20,10 +20,17 @@ class RobotPreferencesModel extends NTWidgetModel { PreferenceSearch? searchWidget; - RobotPreferencesModel({required super.topic, super.dataType, super.period}) - : super(); - - RobotPreferencesModel.fromJson({required super.jsonData}) : super.fromJson(); + RobotPreferencesModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + RobotPreferencesModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); } class RobotPreferences extends NTWidget { @@ -40,8 +47,7 @@ class RobotPreferences extends NTWidget { builder: (context, snapshot) { bool rebuildWidget = model.searchWidget == null; - for (NT4Topic nt4Topic - in ntConnection.nt4Client.announcedTopics.values) { + for (NT4Topic nt4Topic in model.ntConnection.announcedTopics().values) { if (!nt4Topic.name.contains(model.topic) || model.preferenceTopicNames.contains(nt4Topic.name) || nt4Topic.name.contains('.type')) { @@ -49,13 +55,13 @@ class RobotPreferences extends NTWidget { } Object? previousValue = - ntConnection.getLastAnnouncedValue(nt4Topic.name); + model.ntConnection.getLastAnnouncedValue(nt4Topic.name); model.preferenceTopicNames.add(nt4Topic.name); model.preferenceTopics.addAll({nt4Topic.name: nt4Topic}); model.preferenceTextControllers.addAll({ nt4Topic.name: TextEditingController() - ..text = previousValue.toString() ?? '' + ..text = previousValue?.toString() ?? '' }); model.previousValues.addAll({nt4Topic.name: previousValue}); @@ -63,9 +69,9 @@ class RobotPreferences extends NTWidget { } Iterable announcedTopics = - ntConnection.nt4Client.announcedTopics.values.map( - (e) => e.name, - ); + model.ntConnection.announcedTopics().values.map( + (e) => e.name, + ); for (String topic in model.preferenceTopicNames) { if (!announcedTopics.contains(topic)) { @@ -80,13 +86,13 @@ class RobotPreferences extends NTWidget { continue; } - if (ntConnection.getLastAnnouncedValue(topic).toString() != + if (model.ntConnection.getLastAnnouncedValue(topic).toString() != model.previousValues[topic].toString()) { model.preferenceTextControllers[topic]?.text = - ntConnection.getLastAnnouncedValue(topic).toString(); + model.ntConnection.getLastAnnouncedValue(topic).toString(); model.previousValues[topic] = - ntConnection.getLastAnnouncedValue(topic); + model.ntConnection.getLastAnnouncedValue(topic); } } @@ -133,9 +139,9 @@ class RobotPreferences extends NTWidget { return; } - ntConnection.nt4Client.publishTopic(nt4Topic); - ntConnection.updateDataFromTopic(nt4Topic, formattedData); - ntConnection.nt4Client.unpublishTopic(nt4Topic); + model.ntConnection.publishTopic(nt4Topic); + model.ntConnection.updateDataFromTopic(nt4Topic, formattedData); + model.ntConnection.unpublishTopic(nt4Topic); model.preferenceTextControllers[topic]?.text = formattedData.toString(); diff --git a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart index dd9538b7..fbdbc6e9 100644 --- a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart @@ -24,13 +24,16 @@ class SplitButtonChooserModel extends NTWidgetModel { NT4Topic? _activeTopic; SplitButtonChooserModel({ + required super.ntConnection, required super.topic, super.dataType, super.period, }) : super(); - SplitButtonChooserModel.fromJson({required super.jsonData}) - : super.fromJson(); + SplitButtonChooserModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void resetSubscription() { @@ -44,8 +47,8 @@ class SplitButtonChooserModel extends NTWidgetModel { return; } - _selectedTopic ??= ntConnection.nt4Client - .publishNewTopic(selectedTopicName, NT4TypeStr.kString); + _selectedTopic ??= + ntConnection.publishNewTopic(selectedTopicName, NT4TypeStr.kString); ntConnection.updateDataFromTopic(_selectedTopic!, selected); } @@ -64,7 +67,7 @@ class SplitButtonChooserModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(_activeTopic!); + ntConnection.publishTopic(_activeTopic!); } ntConnection.updateDataFromTopic(_activeTopic!, active); @@ -104,32 +107,32 @@ class SplitButtonChooser extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List rawOptions = ntConnection + List rawOptions = model.ntConnection .getLastAnnouncedValue(model.optionsTopicName) ?.tryCast>() ?? []; List options = rawOptions.whereType().toList(); - String? active = - tryCast(ntConnection.getLastAnnouncedValue(model.activeTopicName)); + String? active = tryCast( + model.ntConnection.getLastAnnouncedValue(model.activeTopicName)); if (active != null && active == '') { active = null; } String? selected = tryCast( - ntConnection.getLastAnnouncedValue(model.selectedTopicName)); + model.ntConnection.getLastAnnouncedValue(model.selectedTopicName)); if (selected != null && selected == '') { selected = null; } - String? defaultOption = - tryCast(ntConnection.getLastAnnouncedValue(model.defaultTopicName)); + String? defaultOption = tryCast( + model.ntConnection.getLastAnnouncedValue(model.defaultTopicName)); if (defaultOption != null && defaultOption == '') { defaultOption = null; } - if (!ntConnection.isNT4Connected) { + if (!model.ntConnection.isNT4Connected) { active = null; selected = null; defaultOption = null; diff --git a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart index 13756992..7c04d849 100644 --- a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart @@ -12,10 +12,17 @@ class SubsystemModel extends NTWidgetModel { String get defaultCommandTopic => '$topic/.default'; String get currentCommandTopic => '$topic/.command'; - SubsystemModel({required super.topic, super.dataType, super.period}) - : super(); + SubsystemModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); - SubsystemModel.fromJson({required super.jsonData}) : super.fromJson(); + SubsystemModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override List getCurrentData() { @@ -42,10 +49,10 @@ class SubsystemWidget extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - String defaultCommand = tryCast(ntConnection + String defaultCommand = tryCast(model.ntConnection .getLastAnnouncedValue(model.defaultCommandTopic)) ?? 'none'; - String currentCommand = tryCast(ntConnection + String currentCommand = tryCast(model.ntConnection .getLastAnnouncedValue(model.currentCommandTopic)) ?? 'none'; diff --git a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart index fdd0aa1f..1b0dd4a5 100644 --- a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart @@ -13,12 +13,17 @@ class ThreeAxisAccelerometerModel extends NTWidgetModel { String get yTopic => '$topic/Y'; String get zTopic => '$topic/Z'; - ThreeAxisAccelerometerModel( - {required super.topic, super.dataType, super.period}) - : super(); + ThreeAxisAccelerometerModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); - ThreeAxisAccelerometerModel.fromJson({required super.jsonData}) - : super.fromJson(); + ThreeAxisAccelerometerModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override List getCurrentData() { @@ -43,11 +48,14 @@ class ThreeAxisAccelerometer extends NTWidget { stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { double xAccel = - tryCast(ntConnection.getLastAnnouncedValue(model.xTopic)) ?? 0.0; + tryCast(model.ntConnection.getLastAnnouncedValue(model.xTopic)) ?? + 0.0; double yAccel = - tryCast(ntConnection.getLastAnnouncedValue(model.yTopic)) ?? 0.0; + tryCast(model.ntConnection.getLastAnnouncedValue(model.yTopic)) ?? + 0.0; double zAccel = - tryCast(ntConnection.getLastAnnouncedValue(model.zTopic)) ?? 0.0; + tryCast(model.ntConnection.getLastAnnouncedValue(model.zTopic)) ?? + 0.0; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart index c11fb90a..4fba9159 100644 --- a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart +++ b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart @@ -14,10 +14,17 @@ class UltrasonicModel extends NTWidgetModel { late NT4Subscription valueSubscription; - UltrasonicModel({required super.topic, super.dataType, super.period}) - : super(); - - UltrasonicModel.fromJson({required super.jsonData}) : super.fromJson(); + UltrasonicModel({ + required super.ntConnection, + required super.topic, + super.dataType, + super.period, + }) : super(); + + UltrasonicModel.fromJson({ + required super.ntConnection, + required super.jsonData, + }) : super.fromJson(); @override void init() { @@ -54,7 +61,7 @@ class Ultrasonic extends NTWidget { return StreamBuilder( stream: model.valueSubscription.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.valueTopic), + initialData: model.ntConnection.getLastAnnouncedValue(model.valueTopic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart index 6187a5a2..1ababd2d 100644 --- a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart @@ -39,6 +39,7 @@ class YAGSLSwerveDriveModel extends NTWidgetModel { } YAGSLSwerveDriveModel({ + required super.ntConnection, required super.topic, bool showRobotRotation = true, bool showDesiredStates = true, @@ -48,8 +49,10 @@ class YAGSLSwerveDriveModel extends NTWidgetModel { _showRobotRotation = showRobotRotation, super(); - YAGSLSwerveDriveModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + YAGSLSwerveDriveModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _showRobotRotation = tryCast(jsonData['show_robot_rotation']) ?? true; _showDesiredStates = tryCast(jsonData['show_desired_states']) ?? true; } @@ -141,11 +144,11 @@ class YAGSLSwerveDrive extends NTWidget { return StreamBuilder( stream: model.multiTopicPeriodicStream, builder: (context, snapshot) { - List measuredStatesRaw = tryCast(ntConnection + List measuredStatesRaw = tryCast(model.ntConnection .getLastAnnouncedValue(model.measuredStatesTopic)) ?? []; - List desiredStatesRaw = tryCast( - ntConnection.getLastAnnouncedValue(model.desiredStatesTopic)) ?? + List desiredStatesRaw = tryCast(model.ntConnection + .getLastAnnouncedValue(model.desiredStatesTopic)) ?? []; List measuredStates = @@ -153,11 +156,11 @@ class YAGSLSwerveDrive extends NTWidget { List desiredStates = desiredStatesRaw.whereType().toList(); - double width = tryCast( - ntConnection.getLastAnnouncedValue(model.robotWidthTopic)) ?? + double width = tryCast(model.ntConnection + .getLastAnnouncedValue(model.robotWidthTopic)) ?? 1.0; - double length = tryCast( - ntConnection.getLastAnnouncedValue(model.robotLengthTopic)) ?? + double length = tryCast(model.ntConnection + .getLastAnnouncedValue(model.robotLengthTopic)) ?? width; if (width <= 0.0) { @@ -170,12 +173,12 @@ class YAGSLSwerveDrive extends NTWidget { double sizeRatio = min(length, width) / max(length, width); double lengthWidthRatio = length / width; - String rotationUnit = tryCast( - ntConnection.getLastAnnouncedValue(model.rotationUnitTopic)) ?? + String rotationUnit = tryCast(model.ntConnection + .getLastAnnouncedValue(model.rotationUnitTopic)) ?? 'radians'; - double robotAngle = tryCast( - ntConnection.getLastAnnouncedValue(model.robotRotationTopic)) ?? + double robotAngle = tryCast(model.ntConnection + .getLastAnnouncedValue(model.robotRotationTopic)) ?? 0.0; if (rotationUnit == 'degrees') { @@ -184,9 +187,9 @@ class YAGSLSwerveDrive extends NTWidget { robotAngle *= 2 * pi; } - double maxSpeed = - tryCast(ntConnection.getLastAnnouncedValue(model.maxSpeedTopic)) ?? - 4.5; + double maxSpeed = tryCast(model.ntConnection + .getLastAnnouncedValue(model.maxSpeedTopic)) ?? + 4.5; if (maxSpeed <= 0.0) { maxSpeed = 4.5; diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index cde243f1..87aff379 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -1,10 +1,10 @@ -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/boolean_box.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/graph.dart'; @@ -20,7 +20,7 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_switch. import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.dart'; class NTWidgetModel extends ChangeNotifier { - final ElasticSharedData elasticData; + final NTConnection ntConnection; String _typeOverride = 'NTWidget'; String get type => _typeOverride; @@ -43,7 +43,7 @@ class NTWidgetModel extends ChangeNotifier { bool _forceDispose = false; NTWidgetModel({ - required this.elasticData, + required this.ntConnection, required String topic, this.dataType = 'Unknown', double? period, @@ -54,7 +54,7 @@ class NTWidgetModel extends ChangeNotifier { } NTWidgetModel.createDefault({ - required this.elasticData, + required this.ntConnection, required String type, required String topic, this.dataType = 'Unknown', @@ -66,8 +66,10 @@ class NTWidgetModel extends ChangeNotifier { init(); } - NTWidgetModel.fromJson( - {required this.elasticData, required Map jsonData}) { + NTWidgetModel.fromJson({ + required this.ntConnection, + required Map jsonData, + }) { _topic = tryCast(jsonData['topic']) ?? ''; _period = tryCast(jsonData['period']) ?? Settings.defaultPeriod; dataType = tryCast(jsonData['data_type']) ?? dataType; @@ -77,7 +79,7 @@ class NTWidgetModel extends ChangeNotifier { @mustCallSuper Map toJson() { - if (dataType == 'Unknown' && elasticData.ntConnection.isNT4Connected) { + if (dataType == 'Unknown' && ntConnection.isNT4Connected) { createTopicIfNull(); dataType = ntTopic?.type ?? dataType; } @@ -144,16 +146,16 @@ class NTWidgetModel extends ChangeNotifier { @mustCallSuper void init() async { - subscription = elasticData.ntConnection.subscribe(_topic, _period); + subscription = ntConnection.subscribe(_topic, _period); } void createTopicIfNull() { - ntTopic ??= elasticData.ntConnection.getTopicFromName(_topic); + ntTopic ??= ntConnection.getTopicFromName(_topic); } void unSubscribe() { if (subscription != null) { - elasticData.ntConnection.unSubscribe(subscription!); + ntConnection.unSubscribe(subscription!); } refresh(); } @@ -162,7 +164,7 @@ class NTWidgetModel extends ChangeNotifier { void resetSubscription() { if (subscription == null) { - subscription = elasticData.ntConnection.subscribe(_topic, _period); + subscription = ntConnection.subscribe(_topic, _period); ntTopic = null; @@ -172,14 +174,14 @@ class NTWidgetModel extends ChangeNotifier { bool resetDataType = subscription!.topic != topic; - elasticData.ntConnection.unSubscribe(subscription!); - subscription = elasticData.ntConnection.subscribe(_topic, _period); + ntConnection.unSubscribe(subscription!); + subscription = ntConnection.subscribe(_topic, _period); ntTopic = null; createTopicIfNull(); if (resetDataType) { - if (ntTopic == null && elasticData.ntConnection.isNT4Connected) { + if (ntTopic == null && ntConnection.isNT4Connected) { dataType = 'Unknown'; } else { dataType = ntTopic?.type ?? dataType; diff --git a/lib/widgets/nt_widgets/single_topic/boolean_box.dart b/lib/widgets/nt_widgets/single_topic/boolean_box.dart index 1cc8654d..452a772d 100644 --- a/lib/widgets/nt_widgets/single_topic/boolean_box.dart +++ b/lib/widgets/nt_widgets/single_topic/boolean_box.dart @@ -55,21 +55,23 @@ class BooleanBoxModel extends NTWidgetModel { refresh(); } - BooleanBoxModel( - {required super.topic, - Color trueColor = Colors.green, - Color falseColor = Colors.red, - String trueIcon = 'None', - String falseIcon = 'None', - super.dataType, - super.period}) - : _falseColor = falseColor, + BooleanBoxModel({ + required super.ntConnection, + required super.topic, + Color trueColor = Colors.green, + Color falseColor = Colors.red, + String trueIcon = 'None', + String falseIcon = 'None', + super.dataType, + super.period, + }) : _falseColor = falseColor, _trueColor = trueColor, _trueIcon = trueIcon, _falseIcon = falseIcon, super(); - BooleanBoxModel.fromJson({required Map jsonData}) + BooleanBoxModel.fromJson( + {required super.ntConnection, required Map jsonData}) : super.fromJson(jsonData: jsonData) { int? trueColorValue = tryCast(jsonData['true_color']) ?? tryCast(jsonData['colorWhenTrue']); @@ -199,7 +201,7 @@ class BooleanBox extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { bool value = tryCast(snapshot.data) ?? false; diff --git a/lib/widgets/nt_widgets/single_topic/graph.dart b/lib/widgets/nt_widgets/single_topic/graph.dart index 13eabdd0..3c8d69c9 100644 --- a/lib/widgets/nt_widgets/single_topic/graph.dart +++ b/lib/widgets/nt_widgets/single_topic/graph.dart @@ -61,6 +61,7 @@ class GraphModel extends NTWidgetModel { _GraphWidgetGraph? _graphWidget; GraphModel({ + required super.ntConnection, required super.topic, double timeDisplayed = 5.0, double? minValue, @@ -76,7 +77,8 @@ class GraphModel extends NTWidgetModel { _lineWidth = lineWidth, super(); - GraphModel.fromJson({required Map jsonData}) + GraphModel.fromJson( + {required super.ntConnection, required Map jsonData}) : super.fromJson(jsonData: jsonData) { _timeDisplayed = tryCast(jsonData['time_displayed']) ?? tryCast(jsonData['visibleTime']) ?? diff --git a/lib/widgets/nt_widgets/single_topic/match_time.dart b/lib/widgets/nt_widgets/single_topic/match_time.dart index 577988c7..cc4f5010 100644 --- a/lib/widgets/nt_widgets/single_topic/match_time.dart +++ b/lib/widgets/nt_widgets/single_topic/match_time.dart @@ -43,20 +43,23 @@ class MatchTimeModel extends NTWidgetModel { refresh(); } - MatchTimeModel( - {required super.topic, - String timeDisplayMode = 'Minutes and Seconds', - int redStartTime = 15, - int yellowStartTime = 30, - super.dataType, - super.period}) - : _timeDisplayMode = timeDisplayMode, + MatchTimeModel({ + required super.ntConnection, + required super.topic, + String timeDisplayMode = 'Minutes and Seconds', + int redStartTime = 15, + int yellowStartTime = 30, + super.dataType, + super.period, + }) : _timeDisplayMode = timeDisplayMode, _yellowStartTime = yellowStartTime, _redStartTime = redStartTime, super(); - MatchTimeModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + MatchTimeModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _timeDisplayMode = tryCast(jsonData['time_display_mode']) ?? 'Minutes and Seconds'; @@ -177,7 +180,7 @@ class MatchTimeWidget extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double time = tryCast(snapshot.data) ?? -1.0; time = time.floorToDouble(); diff --git a/lib/widgets/nt_widgets/single_topic/multi_color_view.dart b/lib/widgets/nt_widgets/single_topic/multi_color_view.dart index cb47ea5d..8c428a2d 100644 --- a/lib/widgets/nt_widgets/single_topic/multi_color_view.dart +++ b/lib/widgets/nt_widgets/single_topic/multi_color_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; @@ -15,7 +16,7 @@ class MultiColorView extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { List hexStringsRaw = snapshot.data?.tryCast>() ?? []; diff --git a/lib/widgets/nt_widgets/single_topic/number_bar.dart b/lib/widgets/nt_widgets/single_topic/number_bar.dart index d561a100..17977797 100644 --- a/lib/widgets/nt_widgets/single_topic/number_bar.dart +++ b/lib/widgets/nt_widgets/single_topic/number_bar.dart @@ -57,6 +57,7 @@ class NumberBarModel extends NTWidgetModel { } NumberBarModel({ + required super.ntConnection, required super.topic, double minValue = -1.0, double maxValue = 1.0, @@ -72,8 +73,10 @@ class NumberBarModel extends NTWidgetModel { _minValue = minValue, super(); - NumberBarModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + NumberBarModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? -1.0; _maxValue = tryCast(jsonData['max_value']) ?? 1.0; _divisions = tryCast(jsonData['divisions']); @@ -202,7 +205,7 @@ class NumberBar extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/single_topic/number_slider.dart b/lib/widgets/nt_widgets/single_topic/number_slider.dart index 71521730..24fb088b 100644 --- a/lib/widgets/nt_widgets/single_topic/number_slider.dart +++ b/lib/widgets/nt_widgets/single_topic/number_slider.dart @@ -57,6 +57,7 @@ class NumberSliderModel extends NTWidgetModel { set dragging(value) => _dragging = value; NumberSliderModel({ + required super.ntConnection, required super.topic, double minValue = -1.0, double maxValue = 1.0, @@ -70,8 +71,10 @@ class NumberSliderModel extends NTWidgetModel { _maxValue = maxValue, super(); - NumberSliderModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + NumberSliderModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? tryCast(jsonData['min']) ?? -1.0; _maxValue = @@ -179,7 +182,7 @@ class NumberSliderModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(ntTopic!); + ntConnection.publishTopic(ntTopic!); } ntConnection.updateDataFromTopic(ntTopic!, value); @@ -197,7 +200,7 @@ class NumberSlider extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart index 6dc57e48..032feadf 100644 --- a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart +++ b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart @@ -82,6 +82,7 @@ class RadialGaugeModel extends NTWidgetModel { } RadialGaugeModel({ + required super.ntConnection, required super.topic, double startAngle = -140.0, double endAngle = 140.0, @@ -103,8 +104,10 @@ class RadialGaugeModel extends NTWidgetModel { _endAngle = endAngle, super(); - RadialGaugeModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + RadialGaugeModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _startAngle = tryCast(jsonData['start_angle']) ?? _startAngle; _endAngle = tryCast(jsonData['end_angle']) ?? _endAngle; _minValue = tryCast(jsonData['min_value']) ?? _minValue; @@ -297,7 +300,7 @@ class RadialGauge extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double value = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/nt_widgets/single_topic/single_color_view.dart b/lib/widgets/nt_widgets/single_topic/single_color_view.dart index 3d3a45ad..ba1cc780 100644 --- a/lib/widgets/nt_widgets/single_topic/single_color_view.dart +++ b/lib/widgets/nt_widgets/single_topic/single_color_view.dart @@ -16,7 +16,7 @@ class SingleColorView extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { String hexString = tryCast(snapshot.data) ?? ''; diff --git a/lib/widgets/nt_widgets/single_topic/text_display.dart b/lib/widgets/nt_widgets/single_topic/text_display.dart index 67350c7e..e897d408 100644 --- a/lib/widgets/nt_widgets/single_topic/text_display.dart +++ b/lib/widgets/nt_widgets/single_topic/text_display.dart @@ -28,6 +28,7 @@ class TextDisplayModel extends NTWidgetModel { } TextDisplayModel({ + required super.ntConnection, required super.topic, bool showSubmitButton = false, super.dataType, @@ -35,8 +36,10 @@ class TextDisplayModel extends NTWidgetModel { }) : _showSubmitButton = showSubmitButton, super(); - TextDisplayModel.fromJson({required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + TextDisplayModel.fromJson({ + required super.ntConnection, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _showSubmitButton = tryCast(jsonData['show_submit_button']) ?? _showSubmitButton; } @@ -115,7 +118,7 @@ class TextDisplayModel extends NTWidgetModel { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(ntTopic!); + ntConnection.publishTopic(ntTopic!); } if (formattedData != null) { @@ -137,7 +140,7 @@ class TextDisplay extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { Object? data = snapshot.data; diff --git a/lib/widgets/nt_widgets/single_topic/toggle_button.dart b/lib/widgets/nt_widgets/single_topic/toggle_button.dart index c34aeb4f..5ec5c3e4 100644 --- a/lib/widgets/nt_widgets/single_topic/toggle_button.dart +++ b/lib/widgets/nt_widgets/single_topic/toggle_button.dart @@ -16,7 +16,7 @@ class ToggleButton extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { bool value = tryCast(snapshot.data) ?? false; @@ -30,7 +30,7 @@ class ToggleButton extends NTWidget { return GestureDetector( onTapUp: (_) { bool publishTopic = model.ntTopic == null || - !ntConnection.isTopicPublished(model.ntTopic); + !model.ntConnection.isTopicPublished(model.ntTopic); model.createTopicIfNull(); @@ -39,10 +39,10 @@ class ToggleButton extends NTWidget { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(model.ntTopic!); + model.ntConnection.publishTopic(model.ntTopic!); } - ntConnection.updateDataFromTopic(model.ntTopic!, !value); + model.ntConnection.updateDataFromTopic(model.ntTopic!, !value); }, child: Padding( padding: EdgeInsets.symmetric( diff --git a/lib/widgets/nt_widgets/single_topic/toggle_switch.dart b/lib/widgets/nt_widgets/single_topic/toggle_switch.dart index a5802127..0b38758d 100644 --- a/lib/widgets/nt_widgets/single_topic/toggle_switch.dart +++ b/lib/widgets/nt_widgets/single_topic/toggle_switch.dart @@ -16,7 +16,7 @@ class ToggleSwitch extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { bool value = tryCast(snapshot.data) ?? false; @@ -24,7 +24,7 @@ class ToggleSwitch extends NTWidget { value: value, onChanged: (bool value) { bool publishTopic = model.ntTopic == null || - !ntConnection.isTopicPublished(model.ntTopic); + !model.ntConnection.isTopicPublished(model.ntTopic); model.createTopicIfNull(); @@ -33,10 +33,10 @@ class ToggleSwitch extends NTWidget { } if (publishTopic) { - ntConnection.nt4Client.publishTopic(model.ntTopic!); + model.ntConnection.publishTopic(model.ntTopic!); } - ntConnection.updateDataFromTopic(model.ntTopic!, value); + model.ntConnection.updateDataFromTopic(model.ntTopic!, value); }, ); }, diff --git a/lib/widgets/nt_widgets/single_topic/voltage_view.dart b/lib/widgets/nt_widgets/single_topic/voltage_view.dart index ded3ee45..004ebd9b 100644 --- a/lib/widgets/nt_widgets/single_topic/voltage_view.dart +++ b/lib/widgets/nt_widgets/single_topic/voltage_view.dart @@ -57,7 +57,7 @@ class VoltageViewModel extends NTWidgetModel { } VoltageViewModel({ - required super.elasticData, + required super.ntConnection, required super.topic, double minValue = 4.0, double maxValue = 13.0, @@ -73,7 +73,8 @@ class VoltageViewModel extends NTWidgetModel { _minValue = minValue, super(); - VoltageViewModel.fromJson({required super.elasticData, required Map jsonData}) + VoltageViewModel.fromJson( + {required super.ntConnection, required Map jsonData}) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? 4.0; _maxValue = tryCast(jsonData['max_value']) ?? 13.0; @@ -203,7 +204,7 @@ class VoltageView extends NTWidget { return StreamBuilder( stream: model.subscription?.periodicStream(yieldAll: false), - initialData: model.elasticData.ntConnection.getLastAnnouncedValue(model.topic), + initialData: model.ntConnection.getLastAnnouncedValue(model.topic), builder: (context, snapshot) { double voltage = tryCast(snapshot.data) ?? 0.0; diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index 285f59dc..abd2d262 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -1,10 +1,11 @@ -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:dot_cast/dot_cast.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/ip_address_util.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/text_formatter_builder.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_color_picker.dart'; @@ -13,7 +14,8 @@ import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart' import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; class SettingsDialog extends StatefulWidget { - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final Function(String? data)? onIPAddressChanged; final Function(String? data)? onTeamNumberChanged; @@ -30,7 +32,8 @@ class SettingsDialog extends StatefulWidget { const SettingsDialog({ super.key, - required this.elasticData, + required this.ntConnection, + required this.preferences, this.onTeamNumberChanged, this.onIPAddressModeChanged, this.onIPAddressChanged, @@ -98,19 +101,17 @@ class _SettingsDialogState extends State { } List _generalSettings() { - Color currentColor = Color( - widget.elasticData.preferences.getInt(PrefKeys.teamColor) ?? - Colors.blueAccent.value); + Color currentColor = Color(widget.preferences.getInt(PrefKeys.teamColor) ?? + Colors.blueAccent.value); return [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Flexible( child: DialogTextInput( - initialText: widget.elasticData.preferences - .getInt(PrefKeys.teamNumber) - ?.toString() ?? - Settings.teamNumber.toString(), + initialText: + widget.preferences.getInt(PrefKeys.teamNumber)?.toString() ?? + Settings.teamNumber.toString(), label: 'Team Number', onSubmit: (data) async { await widget.onTeamNumberChanged?.call(data); @@ -154,8 +155,8 @@ class _SettingsDialogState extends State { ), const SizedBox(height: 5), StreamBuilder( - stream: widget.elasticData.ntConnection.dsConnectionStatus(), - initialData: widget.elasticData.ntConnection.isDSConnected, + stream: widget.ntConnection.dsConnectionStatus(), + initialData: widget.ntConnection.isDSConnected, builder: (context, snapshot) { bool dsConnected = tryCast(snapshot.data) ?? false; @@ -163,8 +164,7 @@ class _SettingsDialogState extends State { enabled: Settings.ipAddressMode == IPAddressMode.custom || (Settings.ipAddressMode == IPAddressMode.driverStation && !dsConnected), - initialText: widget.elasticData.preferences - .getString(PrefKeys.ipAddress) ?? + initialText: widget.preferences.getString(PrefKeys.ipAddress) ?? Settings.ipAddress, label: 'IP Address', onSubmit: (String? data) async { @@ -188,9 +188,8 @@ class _SettingsDialogState extends State { children: [ Flexible( child: DialogToggleSwitch( - initialValue: - widget.elasticData.preferences.getBool(PrefKeys.showGrid) ?? - Settings.showGrid, + initialValue: widget.preferences.getBool(PrefKeys.showGrid) ?? + Settings.showGrid, label: 'Show Grid', onToggle: (value) { setState(() { @@ -201,10 +200,9 @@ class _SettingsDialogState extends State { ), Flexible( child: DialogTextInput( - initialText: widget.elasticData.preferences - .getInt(PrefKeys.gridSize) - ?.toString() ?? - Settings.gridSize.toString(), + initialText: + widget.preferences.getInt(PrefKeys.gridSize)?.toString() ?? + Settings.gridSize.toString(), label: 'Grid Size', onSubmit: (value) async { await widget.onGridSizeChanged?.call(value); @@ -222,7 +220,7 @@ class _SettingsDialogState extends State { Flexible( flex: 2, child: DialogTextInput( - initialText: widget.elasticData.preferences + initialText: widget.preferences .getDouble(PrefKeys.cornerRadius) ?.toString() ?? Settings.cornerRadius.toString(), @@ -238,9 +236,9 @@ class _SettingsDialogState extends State { Flexible( flex: 3, child: DialogToggleSwitch( - initialValue: widget.elasticData.preferences - .getBool(PrefKeys.autoResizeToDS) ?? - Settings.autoResizeToDS, + initialValue: + widget.preferences.getBool(PrefKeys.autoResizeToDS) ?? + Settings.autoResizeToDS, label: 'Resize to Driver Station Height', onToggle: (value) { setState(() { @@ -258,9 +256,9 @@ class _SettingsDialogState extends State { Flexible( flex: 5, child: DialogToggleSwitch( - initialValue: widget.elasticData.preferences - .getBool(PrefKeys.rememberWindowPosition) ?? - false, + initialValue: + widget.preferences.getBool(PrefKeys.rememberWindowPosition) ?? + false, label: 'Remember Window Position', onToggle: (value) { setState(() { @@ -272,8 +270,7 @@ class _SettingsDialogState extends State { Flexible( flex: 4, child: DialogToggleSwitch( - initialValue: widget.elasticData.preferences - .getBool(PrefKeys.layoutLocked) ?? + initialValue: widget.preferences.getBool(PrefKeys.layoutLocked) ?? Settings.layoutLocked, label: 'Lock Layout', onToggle: (value) { @@ -301,10 +298,10 @@ class _SettingsDialogState extends State { children: [ Flexible( child: DialogTextInput( - initialText: (widget.elasticData.preferences - .getDouble(PrefKeys.defaultPeriod) ?? - Settings.defaultPeriod) - .toString(), + initialText: + (widget.preferences.getDouble(PrefKeys.defaultPeriod) ?? + Settings.defaultPeriod) + .toString(), label: 'Default Period', onSubmit: (value) async { await widget.onDefaultPeriodChanged?.call(value); @@ -315,7 +312,7 @@ class _SettingsDialogState extends State { ), Flexible( child: DialogTextInput( - initialText: (widget.elasticData.preferences + initialText: (widget.preferences .getDouble(PrefKeys.defaultGraphPeriod) ?? Settings.defaultGraphPeriod) .toString(), diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index f472f189..54ec8eaa 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -1,13 +1,14 @@ import 'dart:math'; -import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:flutter_box_transform/flutter_box_transform.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_list_layout.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_nt_widget_container.dart'; @@ -27,7 +28,8 @@ class TabGridModel extends ChangeNotifier { } class TabGrid extends StatelessWidget { - final ElasticSharedData elasticData; + final NTConnection ntConnection; + final SharedPreferences preferences; final List _widgetModels = []; MapEntry? _containerDraggingIn; @@ -36,11 +38,16 @@ class TabGrid extends StatelessWidget { TabGridModel? model; - TabGrid({super.key, required this.elasticData, required this.onAddWidgetPressed}); + TabGrid( + {super.key, + required this.ntConnection, + required this.preferences, + required this.onAddWidgetPressed}); TabGrid.fromJson({ super.key, - required this.elasticData, + required this.ntConnection, + required this.preferences, required Map jsonData, required this.onAddWidgetPressed, Function(String message)? onJsonLoadingWarning, @@ -60,7 +67,9 @@ class TabGrid extends StatelessWidget { for (Map containerData in jsonData['containers']) { _widgetModels.add( NTWidgetContainerModel.fromJson( - enabled: elasticData.ntConnection.isNT4Connected, + ntConnection: ntConnection, + preferences: preferences, + enabled: ntConnection.isNT4Connected, jsonData: containerData, onJsonLoadingWarning: onJsonLoadingWarning, ), @@ -82,8 +91,10 @@ class TabGrid extends StatelessWidget { switch (layoutData['type']) { case 'List Layout': widget = ListLayoutModel.fromJson( + ntConnection: ntConnection, + preferences: preferences, jsonData: layoutData, - enabled: elasticData.ntConnection.isNT4Connected, + enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, minWidth: 128.0 * 2, @@ -486,7 +497,7 @@ class TabGrid extends StatelessWidget { widget.setPreviewRect(previewLocation); widget.tryCast()?.updateMinimumSize(); - widget.setEnabled(elasticData.ntConnection.isNT4Connected); + widget.setEnabled(ntConnection.isNT4Connected); // If dragging into layout if (widget is NTWidgetContainerModel && @@ -525,6 +536,8 @@ class TabGrid extends StatelessWidget { ListLayoutModel createListLayout( {String title = 'List Layout', List? children}) { return ListLayoutModel( + ntConnection: ntConnection, + preferences: preferences, title: title, initialPosition: Rect.fromLTWH( 0.0, @@ -587,8 +600,10 @@ class TabGrid extends StatelessWidget { case 'List Layout': _widgetModels.add( ListLayoutModel.fromJson( + ntConnection: ntConnection, + preferences: preferences, jsonData: widgetData, - enabled: elasticData.ntConnection.isNT4Connected, + enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, minWidth: 128.0 * 2, @@ -600,7 +615,9 @@ class TabGrid extends StatelessWidget { } else { _widgetModels.add( NTWidgetContainerModel.fromJson( - enabled: elasticData.ntConnection.isNT4Connected, + ntConnection: ntConnection, + preferences: preferences, + enabled: ntConnection.isNT4Connected, jsonData: widgetData, ), ); From 18154e63a7071f44768e1b7278565f0b32768010 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Tue, 23 Apr 2024 00:14:47 -0400 Subject: [PATCH 04/28] Pass a shared preferences instance to every widget --- lib/main.dart | 22 +-- lib/pages/dashboard_page.dart | 169 ++++++++++-------- lib/services/nt_widget_builder.dart | 26 ++- lib/services/settings.dart | 30 +++- lib/services/shuffleboard_nt_listener.dart | 88 ++++++--- .../draggable_widget_container.dart | 10 +- .../models/list_layout_model.dart | 16 +- .../models/nt_widget_container_model.dart | 11 +- .../models/widget_container_model.dart | 44 +++-- lib/widgets/editable_tab_bar.dart | 34 +++- .../network_tree/networktables_tree_row.dart | 20 ++- .../nt_widgets/multi-topic/accelerometer.dart | 23 +-- .../multi-topic/basic_swerve_drive.dart | 9 +- .../nt_widgets/multi-topic/camera_stream.dart | 2 + .../multi-topic/combo_box_chooser.dart | 2 + .../multi-topic/command_scheduler.dart | 2 + .../multi-topic/command_widget.dart | 2 + .../multi-topic/differential_drive.dart | 2 + .../multi-topic/encoder_widget.dart | 2 + .../nt_widgets/multi-topic/field_widget.dart | 12 +- .../nt_widgets/multi-topic/fms_info.dart | 2 + lib/widgets/nt_widgets/multi-topic/gyro.dart | 2 + .../multi-topic/motor_controller.dart | 2 + .../multi-topic/network_alerts.dart | 2 + .../multi-topic/pid_controller.dart | 23 +-- .../multi-topic/power_distribution.dart | 2 + .../multi-topic/profiled_pid_controller.dart | 2 + .../nt_widgets/multi-topic/relay_widget.dart | 2 + .../multi-topic/robot_preferences.dart | 2 + .../multi-topic/split_button_chooser.dart | 2 + .../multi-topic/subsystem_widget.dart | 2 + .../multi-topic/three_axis_accelerometer.dart | 2 + .../nt_widgets/multi-topic/ultrasonic.dart | 2 + .../multi-topic/yagsl_swerve_drive.dart | 2 + lib/widgets/nt_widgets/nt_widget.dart | 21 ++- .../nt_widgets/single_topic/boolean_box.dart | 9 +- .../nt_widgets/single_topic/graph.dart | 9 +- .../nt_widgets/single_topic/match_time.dart | 2 + .../nt_widgets/single_topic/number_bar.dart | 2 + .../single_topic/number_slider.dart | 2 + .../nt_widgets/single_topic/radial_gauge.dart | 2 + .../nt_widgets/single_topic/text_display.dart | 4 +- .../nt_widgets/single_topic/voltage_view.dart | 9 +- lib/widgets/settings_dialog.dart | 28 +-- lib/widgets/tab_grid.dart | 27 ++- 45 files changed, 452 insertions(+), 238 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 68f842e4..d2e5ff8d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -54,36 +54,16 @@ void main() async { await windowManager.ensureInitialized(); - Settings.teamNumber = - preferences.getInt(PrefKeys.teamNumber) ?? Settings.teamNumber; Settings.ipAddressMode = IPAddressMode.fromIndex(preferences.getInt(PrefKeys.ipAddressMode)); - Settings.layoutLocked = - preferences.getBool(PrefKeys.layoutLocked) ?? Settings.layoutLocked; - Settings.gridSize = - preferences.getInt(PrefKeys.gridSize) ?? Settings.gridSize; - Settings.showGrid = - preferences.getBool(PrefKeys.showGrid) ?? Settings.showGrid; - Settings.cornerRadius = - preferences.getDouble(PrefKeys.cornerRadius) ?? Settings.cornerRadius; - Settings.autoResizeToDS = - preferences.getBool(PrefKeys.autoResizeToDS) ?? Settings.autoResizeToDS; - Settings.defaultPeriod = - preferences.getDouble(PrefKeys.defaultPeriod) ?? Settings.defaultPeriod; - Settings.defaultGraphPeriod = - preferences.getDouble(PrefKeys.defaultGraphPeriod) ?? - Settings.defaultGraphPeriod; - NTWidgetBuilder.ensureInitialized(); String ipAddress = - preferences.getString(PrefKeys.ipAddress) ?? Settings.ipAddress; + preferences.getString(PrefKeys.ipAddress) ?? Defaults.ipAddress; NTConnection ntConnection = NTConnection(ipAddress); - ntConnection.nt4Connect(Settings.ipAddress); - await FieldImages.loadFields('assets/fields/'); Display primaryDisplay = await screenRetriever.getPrimaryDisplay(); diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 31516040..fdf96082 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -52,7 +52,7 @@ class DashboardPage extends StatefulWidget { } class _DashboardPageState extends State with WindowListener { - late final SharedPreferences _preferences = widget.preferences; + late final SharedPreferences preferences = widget.preferences; late final UpdateChecker _updateChecker; final List _grids = []; @@ -61,7 +61,8 @@ class _DashboardPageState extends State with WindowListener { final Function _mapEquals = const DeepCollectionEquality().equals; - int _gridSize = Settings.gridSize; + late int _gridSize = + preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize; int _currentTabIndex = 0; @@ -88,8 +89,8 @@ class _DashboardPageState extends State with WindowListener { return; } - if (_preferences.getString(PrefKeys.ipAddress) != ip) { - await _preferences.setString(PrefKeys.ipAddress, ip); + if (preferences.getString(PrefKeys.ipAddress) != ip) { + await preferences.setString(PrefKeys.ipAddress, ip); } else { return; } @@ -97,7 +98,9 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.changeIPAddress(ip); }, onDriverStationDockChanged: (docked) { - if (Settings.autoResizeToDS && docked) { + if ((preferences.getBool(PrefKeys.autoResizeToDS) ?? + Defaults.autoResizeToDS) && + docked) { _onDriverStationDocked(); } else { _onDriverStationUndocked(); @@ -149,7 +152,8 @@ class _DashboardPageState extends State with WindowListener { }); }, onTabCreated: (tab) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } @@ -170,7 +174,8 @@ class _DashboardPageState extends State with WindowListener { ); }, onWidgetAdded: (widgetData) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } // Needs to be done in case if widget data gets erased by the listener @@ -219,7 +224,7 @@ class _DashboardPageState extends State with WindowListener { @override void onWindowClose() async { Map savedJson = - jsonDecode(_preferences.getString(PrefKeys.layout) ?? '{}'); + jsonDecode(preferences.getString(PrefKeys.layout) ?? '{}'); Map currentJson = _toJson(); bool showConfirmation = !_mapEquals(savedJson, currentJson); @@ -270,7 +275,7 @@ class _DashboardPageState extends State with WindowListener { TextTheme textTheme = Theme.of(context).textTheme; bool successful = - await _preferences.setString(PrefKeys.layout, jsonEncode(jsonData)); + await preferences.setString(PrefKeys.layout, jsonEncode(jsonData)); await _saveWindowPosition(); if (successful) { @@ -328,7 +333,7 @@ class _DashboardPageState extends State with WindowListener { String positionString = jsonEncode(positionArray); - await _preferences.setString(PrefKeys.windowPosition, positionString); + await preferences.setString(PrefKeys.windowPosition, positionString); } void _checkForUpdates( @@ -468,7 +473,7 @@ class _DashboardPageState extends State with WindowListener { } void _importLayout() async { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } const XTypeGroup jsonTypeGroup = XTypeGroup( @@ -512,13 +517,13 @@ class _DashboardPageState extends State with WindowListener { return; } - await _preferences.setString(PrefKeys.layout, jsonEncode(jsonData)); + await preferences.setString(PrefKeys.layout, jsonEncode(jsonData)); setState(() => _loadLayoutFromJsonData(jsonString)); } void _loadLayout() { - String? jsonString = _preferences.getString(PrefKeys.layout); + String? jsonString = preferences.getString(PrefKeys.layout); if (jsonString == null) { _createDefaultTabs(); @@ -548,8 +553,7 @@ class _DashboardPageState extends State with WindowListener { if (jsonData.containsKey('grid_size')) { _gridSize = tryCast(jsonData['grid_size']) ?? _gridSize; - Settings.gridSize = _gridSize; - _preferences.setInt(PrefKeys.gridSize, _gridSize); + preferences.setInt(PrefKeys.gridSize, _gridSize); } _tabData.clear(); @@ -747,7 +751,8 @@ class _DashboardPageState extends State with WindowListener { modifiers: [KeyModifier.control], ), callback: () { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } String newTabName = 'Tab ${_tabData.length + 1}'; @@ -773,7 +778,8 @@ class _DashboardPageState extends State with WindowListener { modifiers: [KeyModifier.control], ), callback: () { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } if (_tabData.length <= 1) { @@ -804,16 +810,14 @@ class _DashboardPageState extends State with WindowListener { for (TabGrid grid in _grids) { grid.lockLayout(); } - Settings.layoutLocked = true; - await _preferences.setBool(PrefKeys.layoutLocked, true); + await preferences.setBool(PrefKeys.layoutLocked, true); } void _unlockLayout() async { for (TabGrid grid in _grids) { grid.unlockLayout(); } - Settings.layoutLocked = false; - await _preferences.setBool(PrefKeys.layoutLocked, false); + await preferences.setBool(PrefKeys.layoutLocked, false); } void _displayAddWidgetDialog() { @@ -892,13 +896,11 @@ class _DashboardPageState extends State with WindowListener { int? newTeamNumber = int.tryParse(data); if (newTeamNumber == null || - (newTeamNumber == Settings.teamNumber && - Settings.teamNumber != 9999)) { + (newTeamNumber == preferences.getInt(PrefKeys.teamNumber))) { return; } - await _preferences.setInt(PrefKeys.teamNumber, newTeamNumber); - Settings.teamNumber = newTeamNumber; + await preferences.setInt(PrefKeys.teamNumber, newTeamNumber); switch (Settings.ipAddressMode) { case IPAddressMode.roboRIOmDNS: @@ -917,7 +919,7 @@ class _DashboardPageState extends State with WindowListener { if (mode == Settings.ipAddressMode) { return; } - await _preferences.setInt(PrefKeys.ipAddressMode, mode.index); + await preferences.setInt(PrefKeys.ipAddressMode, mode.index); Settings.ipAddressMode = mode; @@ -933,12 +935,14 @@ class _DashboardPageState extends State with WindowListener { _updateIPAddress(lastAnnouncedIP); break; case IPAddressMode.roboRIOmDNS: - _updateIPAddress( - IPAddressUtil.teamNumberToRIOmDNS(Settings.teamNumber)); + _updateIPAddress(IPAddressUtil.teamNumberToRIOmDNS( + preferences.getInt(PrefKeys.teamNumber) ?? + Defaults.teamNumber)); break; case IPAddressMode.teamNumber: - _updateIPAddress( - IPAddressUtil.teamNumberToIP(Settings.teamNumber)); + _updateIPAddress(IPAddressUtil.teamNumberToIP( + preferences.getInt(PrefKeys.teamNumber) ?? + Defaults.teamNumber)); break; case IPAddressMode.localhost: _updateIPAddress('localhost'); @@ -949,18 +953,17 @@ class _DashboardPageState extends State with WindowListener { } }, onIPAddressChanged: (String? data) async { - if (data == null || data == Settings.ipAddress) { + if (data == null || + data == preferences.getString(PrefKeys.ipAddress)) { return; } _updateIPAddress(data); }, onGridToggle: (value) async { - setState(() { - Settings.showGrid = value; - }); + await preferences.setBool(PrefKeys.showGrid, value); - await _preferences.setBool(PrefKeys.showGrid, value); + setState(() {}); }, onGridSizeChanged: (gridSize) async { if (gridSize == null) { @@ -1022,7 +1025,7 @@ class _DashboardPageState extends State with WindowListener { _gridSize = newGridSize; }); - await _preferences.setInt(PrefKeys.gridSize, newGridSize); + await preferences.setInt(PrefKeys.gridSize, newGridSize); for (TabGrid grid in _grids) { grid.resizeGrid(_gridSize, _gridSize); @@ -1035,17 +1038,18 @@ class _DashboardPageState extends State with WindowListener { double? newRadius = double.tryParse(radius); - if (newRadius == null || newRadius == Settings.cornerRadius) { + if (newRadius == null || + newRadius == preferences.getDouble(PrefKeys.cornerRadius)) { return; } + await preferences.setDouble(PrefKeys.cornerRadius, newRadius); + setState(() { for (TabGrid grid in _grids) { grid.refreshAllContainers(); } }); - - await _preferences.setDouble(PrefKeys.cornerRadius, newRadius); }, onResizeToDSChanged: (value) async { setState(() { @@ -1056,19 +1060,18 @@ class _DashboardPageState extends State with WindowListener { } }); - await _preferences.setBool(PrefKeys.autoResizeToDS, value); + await preferences.setBool(PrefKeys.autoResizeToDS, value); }, onRememberWindowPositionChanged: (value) async { - await _preferences.setBool(PrefKeys.rememberWindowPosition, value); + await preferences.setBool(PrefKeys.rememberWindowPosition, value); }, onLayoutLock: (value) { - setState(() { - if (value) { - _lockLayout(); - } else { - _unlockLayout(); - } - }); + if (value) { + _lockLayout(); + } else { + _unlockLayout(); + } + setState(() {}); }, onDefaultPeriodChanged: (value) async { if (value == null) { @@ -1076,11 +1079,12 @@ class _DashboardPageState extends State with WindowListener { } double? newPeriod = double.tryParse(value); - if (newPeriod == null || newPeriod == Settings.defaultPeriod) { + if (newPeriod == null || + newPeriod == preferences.getDouble(PrefKeys.defaultPeriod)) { return; } - await _preferences.setDouble(PrefKeys.defaultPeriod, newPeriod); + await preferences.setDouble(PrefKeys.defaultPeriod, newPeriod); setState(() {}); }, @@ -1090,13 +1094,14 @@ class _DashboardPageState extends State with WindowListener { } double? newPeriod = double.tryParse(value); - if (newPeriod == null || newPeriod == Settings.defaultGraphPeriod) { + if (newPeriod == null || + newPeriod == preferences.getDouble(PrefKeys.defaultGraphPeriod)) { return; } - await _preferences.setDouble(PrefKeys.defaultGraphPeriod, newPeriod); + await preferences.setDouble(PrefKeys.defaultGraphPeriod, newPeriod); - setState(() => Settings.defaultGraphPeriod = newPeriod); + setState(() {}); }, onColorChanged: widget.onColorChanged, ), @@ -1104,10 +1109,10 @@ class _DashboardPageState extends State with WindowListener { } void _updateIPAddress(String newIPAddress) async { - if (newIPAddress == _preferences.getString(PrefKeys.ipAddress)) { + if (newIPAddress == preferences.getString(PrefKeys.ipAddress)) { return; } - await _preferences.setString(PrefKeys.ipAddress, newIPAddress); + await preferences.setString(PrefKeys.ipAddress, newIPAddress); setState(() { widget.ntConnection.changeIPAddress(newIPAddress); @@ -1213,7 +1218,8 @@ class _DashboardPageState extends State with WindowListener { } void _moveTabLeft() { - if (Settings.layoutLocked) { + if (!(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked)) { return; } if (_currentTabIndex <= 0) { @@ -1240,7 +1246,8 @@ class _DashboardPageState extends State with WindowListener { } void _moveTabRight() { - if (Settings.layoutLocked) { + if (!(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked)) { return; } if (_currentTabIndex >= _tabData.length - 1) { @@ -1300,8 +1307,10 @@ class _DashboardPageState extends State with WindowListener { // Open Layout MenuItemButton( style: menuButtonStyle, - onPressed: - (!Settings.layoutLocked) ? () => _importLayout() : null, + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) + ? () => _importLayout() + : null, shortcut: const SingleActivator(LogicalKeyboardKey.keyO, control: true), child: const Text( @@ -1344,7 +1353,8 @@ class _DashboardPageState extends State with WindowListener { // Clear layout MenuItemButton( style: menuButtonStyle, - onPressed: (!Settings.layoutLocked) + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) ? () { setState(() { _grids[_currentTabIndex].clearWidgets(context); @@ -1358,19 +1368,21 @@ class _DashboardPageState extends State with WindowListener { MenuItemButton( style: menuButtonStyle, onPressed: () { - setState(() { - if (Settings.layoutLocked) { - _unlockLayout(); - } else { - _lockLayout(); - } - }); + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { + _unlockLayout(); + } else { + _lockLayout(); + } + + setState(() {}); }, - leadingIcon: (Settings.layoutLocked) + leadingIcon: (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) ? const Icon(Icons.lock_open) : const Icon(Icons.lock_outline), child: Text( - '${(Settings.layoutLocked) ? 'Unlock' : 'Lock'} Layout'), + '${(preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) ? 'Unlock' : 'Lock'} Layout'), ) ], child: const Text( @@ -1420,11 +1432,14 @@ class _DashboardPageState extends State with WindowListener { MenuItemButton( style: menuButtonStyle, leadingIcon: const Icon(Icons.add), - onPressed: - (!Settings.layoutLocked) ? () => _displayAddWidgetDialog() : null, + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) + ? () => _displayAddWidgetDialog() + : null, child: const Text('Add Widget'), ), - if (Settings.layoutLocked) ...[ + if ((preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked)) ...[ const VerticalDivider(), // Unlock Layout Tooltip( @@ -1437,9 +1452,8 @@ class _DashboardPageState extends State with WindowListener { const MaterialStatePropertyAll(Size(36.0, double.infinity)), ), onPressed: () { - setState(() { - _unlockLayout(); - }); + _unlockLayout(); + setState(() {}); }, child: const Icon(Icons.lock_outline), ), @@ -1465,6 +1479,7 @@ class _DashboardPageState extends State with WindowListener { child: Stack( children: [ EditableTabBar( + preferences: preferences, currentIndex: _currentTabIndex, onTabMoveLeft: () { _moveTabLeft(); @@ -1555,7 +1570,7 @@ class _DashboardPageState extends State with WindowListener { bool connected = snapshot.data ?? false; String connectedText = (connected) - ? 'Network Tables: Connected (${_preferences.getString(PrefKeys.ipAddress)})' + ? 'Network Tables: Connected (${preferences.getString(PrefKeys.ipAddress)})' : 'Network Tables: Disconnected'; return Text( @@ -1569,7 +1584,7 @@ class _DashboardPageState extends State with WindowListener { ), Expanded( child: Text( - 'Team ${_preferences.getInt(PrefKeys.teamNumber)?.toString() ?? 'Unknown'}', + 'Team ${preferences.getInt(PrefKeys.teamNumber)?.toString() ?? 'Unknown'}', textAlign: TextAlign.center, ), ), diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 8d34d747..74be3fae 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -52,6 +52,7 @@ class NTWidgetBuilder { String, NTWidgetModel Function({ required NTConnection ntConnection, + required SharedPreferences preferences, required String topic, String dataType, double period, @@ -61,6 +62,7 @@ class NTWidgetBuilder { String, NTWidgetModel Function({ required NTConnection ntConnection, + required SharedPreferences preferences, required Map jsonData, })> _modelJsonBuildMap = {}; @@ -323,18 +325,21 @@ class NTWidgetBuilder { static NTWidgetModel buildNTModelFromType( NTConnection ntConnection, + SharedPreferences preferences, String type, String topic, { String dataType = 'Unknown', double? period, }) { - period ??= Settings.defaultPeriod; + period ??= + preferences.getDouble(PrefKeys.defaultPeriod) ?? Defaults.defaultPeriod; ensureInitialized(); if (_modelNameBuildMap.containsKey(type)) { return _modelNameBuildMap[type]!( ntConnection: ntConnection, + preferences: preferences, topic: topic, dataType: dataType, period: period, @@ -343,6 +348,7 @@ class NTWidgetBuilder { return NTWidgetModel.createDefault( ntConnection: ntConnection, + preferences: preferences, type: type, topic: topic, dataType: dataType, @@ -350,14 +356,19 @@ class NTWidgetBuilder { ); } - static NTWidgetModel buildNTModelFromJson(NTConnection ntConnection, - SharedPreferences preferences, String type, Map jsonData, - {Function(String message)? onWidgetTypeNotFound}) { + static NTWidgetModel buildNTModelFromJson( + NTConnection ntConnection, + SharedPreferences preferences, + String type, + Map jsonData, { + Function(String message)? onWidgetTypeNotFound, + }) { ensureInitialized(); if (_modelJsonBuildMap.containsKey(type)) { return _modelJsonBuildMap[type]!( ntConnection: ntConnection, + preferences: preferences, jsonData: jsonData, ); } @@ -366,6 +377,7 @@ class NTWidgetBuilder { ?.call('Unknown widget type: \'$type\', defaulting to Empty Model.'); return NTWidgetModel.createDefault( ntConnection: ntConnection, + preferences: preferences, type: type, topic: tryCast(jsonData['topic']) ?? '', dataType: tryCast(jsonData['data_type']) ?? 'Unknown', @@ -379,7 +391,8 @@ class NTWidgetBuilder { if (_minimumWidthMap.containsKey(widget.type)) { return _minimumWidthMap[widget.type]!; } else { - return Settings.gridSize.toDouble(); + return (widget.preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble(); } } @@ -389,7 +402,8 @@ class NTWidgetBuilder { if (_minimumHeightMap.containsKey(widget.type)) { return _minimumHeightMap[widget.type]!; } else { - return Settings.gridSize.toDouble(); + return (widget.preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble(); } } diff --git a/lib/services/settings.dart b/lib/services/settings.dart index f85ed4c2..a2436ea9 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -7,21 +7,33 @@ class Settings { static IPAddressMode ipAddressMode = IPAddressMode.driverStation; - static String ipAddress = '127.0.0.1'; - static int teamNumber = 9999; - static int gridSize = 128; - static bool layoutLocked = false; - static double cornerRadius = 15.0; - static bool showGrid = true; - static bool autoResizeToDS = false; + // static String ipAddress = '127.0.0.1'; + // static int teamNumber = 9999; + // static int gridSize = 128; + // static bool layoutLocked = false; + // static double cornerRadius = 15.0; + // static bool showGrid = true; + // static bool autoResizeToDS = false; // window_manager doesn't support drag disable/maximize // disable on some platforms, this is a dumb workaround for it static bool isWindowDraggable = true; static bool isWindowMaximizable = true; +} + +class Defaults { + static IPAddressMode ipAddressMode = IPAddressMode.driverStation; + + static const String ipAddress = '127.0.0.1'; + static const int teamNumber = 9999; + static const int gridSize = 128; + static const bool layoutLocked = false; + static const double cornerRadius = 15.0; + static const bool showGrid = true; + static const bool autoResizeToDS = false; - static double defaultPeriod = 0.06; - static double defaultGraphPeriod = 0.033; + static const double defaultPeriod = 0.06; + static const double defaultGraphPeriod = 0.033; } class PrefKeys { diff --git a/lib/services/shuffleboard_nt_listener.dart b/lib/services/shuffleboard_nt_listener.dart index c964828c..0a7eefa8 100644 --- a/lib/services/shuffleboard_nt_listener.dart +++ b/lib/services/shuffleboard_nt_listener.dart @@ -218,13 +218,27 @@ class ShuffleboardNTListener { if (inLayout) { Map child = _createOrGetChild(jsonKey, widgetName); - child.putIfAbsent('width', () => size[0] * Settings.gridSize); - child.putIfAbsent('height', () => size[1] * Settings.gridSize); + child.putIfAbsent( + 'width', + () => + size[0] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); + child.putIfAbsent( + 'height', + () => + size[1] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } else { - currentJsonData[jsonKey]! - .putIfAbsent('width', () => size[0] * Settings.gridSize); - currentJsonData[jsonKey]! - .putIfAbsent('height', () => size[1] * Settings.gridSize); + currentJsonData[jsonKey]!.putIfAbsent( + 'width', + () => + size[0] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); + currentJsonData[jsonKey]!.putIfAbsent( + 'height', + () => + size[1] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } } @@ -243,13 +257,27 @@ class ShuffleboardNTListener { if (inLayout) { Map child = _createOrGetChild(jsonKey, widgetName); - child.putIfAbsent('x', () => position[0] * Settings.gridSize); - child.putIfAbsent('y', () => position[1] * Settings.gridSize); + child.putIfAbsent( + 'x', + () => + position[0] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); + child.putIfAbsent( + 'y', + () => + position[1] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } else { - currentJsonData[jsonKey]! - .putIfAbsent('x', () => position[0] * Settings.gridSize); - currentJsonData[jsonKey]! - .putIfAbsent('y', () => position[1] * Settings.gridSize); + currentJsonData[jsonKey]!.putIfAbsent( + 'x', + () => + position[0] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); + currentJsonData[jsonKey]!.putIfAbsent( + 'y', + () => + position[1] * + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } } } @@ -374,12 +402,14 @@ class ShuffleboardNTListener { 'width', () => (!isCameraStream) ? widget!.displayRect.width - : Settings.gridSize * 2); + : (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) * + 2); currentJsonData[jsonKey]!.putIfAbsent( 'height', () => (!isCameraStream) ? widget!.displayRect.height - : Settings.gridSize * 2); + : (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) * + 2); currentJsonData[jsonKey]!.putIfAbsent('tab', () => tabName); currentJsonData[jsonKey]!.putIfAbsent('type', () => type); currentJsonData[jsonKey]! @@ -389,8 +419,10 @@ class ShuffleboardNTListener { currentJsonData[jsonKey]!['properties'].putIfAbsent( 'period', () => (type != 'Graph') - ? Settings.defaultPeriod - : Settings.defaultGraphPeriod); + ? preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod + : preferences.getDouble(PrefKeys.defaultGraphPeriod) ?? + Defaults.defaultGraphPeriod); if (ntConnection.isNT4Connected) { onWidgetAdded?.call(currentJsonData[jsonKey]!); @@ -420,10 +452,14 @@ class ShuffleboardNTListener { currentJsonData[jsonKey]!.putIfAbsent('title', () => componentName); currentJsonData[jsonKey]!.putIfAbsent('x', () => 0.0); currentJsonData[jsonKey]!.putIfAbsent('y', () => 0.0); - currentJsonData[jsonKey]! - .putIfAbsent('width', () => Settings.gridSize.toDouble()); - currentJsonData[jsonKey]! - .putIfAbsent('height', () => Settings.gridSize.toDouble()); + currentJsonData[jsonKey]!.putIfAbsent( + 'width', + () => (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble()); + currentJsonData[jsonKey]!.putIfAbsent( + 'height', + () => (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble()); currentJsonData[jsonKey]!.putIfAbsent('type', () => 'List Layout'); currentJsonData[jsonKey]!.putIfAbsent('tab', () => tabName); currentJsonData[jsonKey]! @@ -482,19 +518,23 @@ class ShuffleboardNTListener { 'width', () => (!isCameraStream) ? widget!.displayRect.width - : Settings.gridSize * 2); + : (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) * + 2); child.putIfAbsent( 'height', () => (!isCameraStream) ? widget!.displayRect.height - : Settings.gridSize * 2); + : (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) * + 2); child['properties']!.putIfAbsent('topic', () => childRow.topic); child['properties']!.putIfAbsent( 'period', () => (type != 'Graph') - ? Settings.defaultPeriod - : Settings.defaultGraphPeriod); + ? preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod + : preferences.getDouble(PrefKeys.defaultGraphPeriod) ?? + Defaults.defaultGraphPeriod); widget?.unSubscribe(); widget?.disposeModel(deleting: true); diff --git a/lib/widgets/draggable_containers/draggable_widget_container.dart b/lib/widgets/draggable_containers/draggable_widget_container.dart index 51bd874c..df4016a4 100644 --- a/lib/widgets/draggable_containers/draggable_widget_container.dart +++ b/lib/widgets/draggable_containers/draggable_widget_container.dart @@ -32,7 +32,7 @@ class DraggableWidgetContainer extends StatelessWidget { }); static double snapToGrid(double value, [double? gridSize]) { - gridSize ??= Settings.gridSize.toDouble(); + gridSize ??= Defaults.gridSize.toDouble(); return (value / gridSize).roundToDouble() * gridSize; } @@ -160,6 +160,7 @@ class WidgetContainer extends StatelessWidget { this.opacity = 1.0, this.horizontalPadding = 7.5, this.verticalPadding = 7.5, + this.cornerRadius = Defaults.cornerRadius, }); final double opacity; @@ -169,6 +170,7 @@ class WidgetContainer extends StatelessWidget { final double height; final double horizontalPadding; final double verticalPadding; + final double cornerRadius; @override Widget build(BuildContext context) { @@ -183,7 +185,7 @@ class WidgetContainer extends StatelessWidget { opacity: opacity, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(Settings.cornerRadius), + borderRadius: BorderRadius.circular(cornerRadius), color: const Color.fromARGB(255, 40, 40, 40), boxShadow: const [ BoxShadow( @@ -203,8 +205,8 @@ class WidgetContainer extends StatelessWidget { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.only( - topLeft: Radius.circular(Settings.cornerRadius), - topRight: Radius.circular(Settings.cornerRadius), + topLeft: Radius.circular(cornerRadius), + topRight: Radius.circular(cornerRadius), ), color: theme.colorScheme.primaryContainer, ), diff --git a/lib/widgets/draggable_containers/models/list_layout_model.dart b/lib/widgets/draggable_containers/models/list_layout_model.dart index 08c51459..cb1eefe4 100644 --- a/lib/widgets/draggable_containers/models/list_layout_model.dart +++ b/lib/widgets/draggable_containers/models/list_layout_model.dart @@ -462,7 +462,8 @@ class ListLayoutModel extends LayoutContainerModel { .whereNot((element) => element == PointerDeviceKind.trackpad) .toSet(), onPanDown: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } widget.cursorGlobalLocation = details.globalPosition; @@ -476,7 +477,8 @@ class ListLayoutModel extends LayoutContainerModel { }); }, onPanUpdate: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } widget.cursorGlobalLocation = details.globalPosition; @@ -487,7 +489,8 @@ class ListLayoutModel extends LayoutContainerModel { tabGrid.layoutDragOutUpdate(widget, location); }, onPanEnd: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } Future(() => setDraggable(true)); @@ -510,7 +513,8 @@ class ListLayoutModel extends LayoutContainerModel { tabGrid.layoutDragOutEnd(widget); }, onPanCancel: () { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } Future(() { @@ -553,6 +557,8 @@ class ListLayoutModel extends LayoutContainerModel { title: title, width: draggingRect.width, height: draggingRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, opacity: 0.80, horizontalPadding: 5.0, verticalPadding: 5.0, @@ -580,6 +586,8 @@ class ListLayoutModel extends LayoutContainerModel { title: title, width: displayRect.width, height: displayRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, opacity: (previewVisible) ? 0.25 : 1.00, horizontalPadding: 5.0, verticalPadding: 5.0, diff --git a/lib/widgets/draggable_containers/models/nt_widget_container_model.dart b/lib/widgets/draggable_containers/models/nt_widget_container_model.dart index 067d05a4..0dd78022 100644 --- a/lib/widgets/draggable_containers/models/nt_widget_container_model.dart +++ b/lib/widgets/draggable_containers/models/nt_widget_container_model.dart @@ -269,6 +269,8 @@ class NTWidgetContainerModel extends WidgetContainerModel { title: title, width: draggingRect.width, height: draggingRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, opacity: 0.80, child: ChangeNotifierProvider.value( value: childModel, @@ -283,6 +285,8 @@ class NTWidgetContainerModel extends WidgetContainerModel { title: title, width: displayRect.width, height: displayRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, opacity: (previewVisible) ? 0.25 : 1.00, child: Opacity( opacity: (enabled) ? 1.00 : 0.50, @@ -312,11 +316,14 @@ class NTWidgetContainerModel extends WidgetContainerModel { childModel = NTWidgetBuilder.buildNTModelFromType( ntConnection, + preferences, type, childModel.topic, dataType: childModel.dataType, - period: - (type != 'Graph') ? childModel.period : Settings.defaultGraphPeriod, + period: (type != 'Graph') + ? childModel.period + : preferences.getDouble(PrefKeys.defaultGraphPeriod) ?? + Defaults.defaultGraphPeriod, ); NTWidget? newWidget = NTWidgetBuilder.buildNTWidgetFromModel(childModel); diff --git a/lib/widgets/draggable_containers/models/widget_container_model.dart b/lib/widgets/draggable_containers/models/widget_container_model.dart index 625c78bc..4fa34bf0 100644 --- a/lib/widgets/draggable_containers/models/widget_container_model.dart +++ b/lib/widgets/draggable_containers/models/widget_container_model.dart @@ -14,20 +14,30 @@ abstract class WidgetContainerModel extends ChangeNotifier { String? title; - bool draggable = !Settings.layoutLocked; + late bool draggable = + !(preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked); bool _disposed = false; bool _forceDispose = false; - Rect draggingRect = Rect.fromLTWH( - 0, 0, Settings.gridSize.toDouble(), Settings.gridSize.toDouble()); + late Rect draggingRect = Rect.fromLTWH( + 0, + 0, + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(), + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble()); Offset cursorGlobalLocation = const Offset(double.nan, double.nan); - Rect displayRect = Rect.fromLTWH( - 0, 0, Settings.gridSize.toDouble(), Settings.gridSize.toDouble()); + late Rect displayRect = Rect.fromLTWH( + 0, + 0, + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(), + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble()); - Rect previewRect = Rect.fromLTWH( - 0, 0, Settings.gridSize.toDouble(), Settings.gridSize.toDouble()); + late Rect previewRect = Rect.fromLTWH( + 0, + 0, + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(), + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble()); bool enabled = false; bool dragging = false; @@ -36,8 +46,10 @@ abstract class WidgetContainerModel extends ChangeNotifier { bool previewVisible = false; bool validLocation = true; - double minWidth = Settings.gridSize.toDouble(); - double minHeight = Settings.gridSize.toDouble(); + late double minWidth = + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(); + late double minHeight = + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(); late Rect dragStartLocation; @@ -110,9 +122,11 @@ abstract class WidgetContainerModel extends ChangeNotifier { double y = tryCast(jsonData['y']) ?? 0.0; - double width = tryCast(jsonData['width']) ?? Settings.gridSize.toDouble(); + double width = tryCast(jsonData['width']) ?? + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(); - double height = tryCast(jsonData['height']) ?? Settings.gridSize.toDouble(); + double height = tryCast(jsonData['height']) ?? + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize).toDouble(); displayRect = Rect.fromLTWH(x, y, width, height); } @@ -260,6 +274,8 @@ abstract class WidgetContainerModel extends ChangeNotifier { title: title, width: draggingRect.width, height: draggingRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, opacity: 0.80, child: Container(), ); @@ -270,6 +286,8 @@ abstract class WidgetContainerModel extends ChangeNotifier { title: title, width: displayRect.width, height: displayRect.height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, child: Container(), ); } @@ -287,7 +305,9 @@ abstract class WidgetContainerModel extends ChangeNotifier { color: (validLocation) ? Colors.white.withOpacity(0.25) : Colors.black.withOpacity(0.1), - borderRadius: BorderRadius.circular(Settings.cornerRadius), + borderRadius: BorderRadius.circular( + preferences.getDouble(PrefKeys.cornerRadius) ?? + Defaults.cornerRadius), border: Border.all( color: (validLocation) ? Colors.lightGreenAccent.shade400 diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 3317edc0..917acda6 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:elastic_dashboard/services/settings.dart'; @@ -16,6 +17,8 @@ class TabData { } class EditableTabBar extends StatelessWidget { + final SharedPreferences preferences; + final List tabViews; final List tabData; @@ -30,6 +33,7 @@ class EditableTabBar extends StatelessWidget { const EditableTabBar({ super.key, + required this.preferences, required this.currentIndex, required this.tabData, required this.tabViews, @@ -124,7 +128,8 @@ class EditableTabBar extends StatelessWidget { onTabChanged.call(index); }, onSecondaryTapUp: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } ContextMenu contextMenu = ContextMenu( @@ -194,11 +199,15 @@ class EditableTabBar extends StatelessWidget { ), ), Visibility( - visible: !Settings.layoutLocked, + visible: !(preferences + .getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked), child: const SizedBox(width: 10), ), Visibility( - visible: !Settings.layoutLocked, + visible: !(preferences + .getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked), child: IconButton( onPressed: () { closeTab(index); @@ -229,7 +238,8 @@ class EditableTabBar extends StatelessWidget { children: [ IconButton( style: endButtonStyle, - onPressed: (!Settings.layoutLocked) + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) ? () => onTabMoveLeft.call() : null, alignment: Alignment.center, @@ -237,14 +247,17 @@ class EditableTabBar extends StatelessWidget { ), IconButton( style: endButtonStyle, - onPressed: - (!Settings.layoutLocked) ? () => createTab() : null, + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) + ? () => createTab() + : null, alignment: Alignment.center, icon: const Icon(Icons.add), ), IconButton( style: endButtonStyle, - onPressed: (!Settings.layoutLocked) + onPressed: !(preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) ? () => onTabMoveRight.call() : null, alignment: Alignment.center, @@ -262,10 +275,13 @@ class EditableTabBar extends StatelessWidget { child: Stack( children: [ Visibility( - visible: Settings.showGrid, + visible: + preferences.getBool(PrefKeys.showGrid) ?? Defaults.showGrid, child: GridPaper( color: const Color.fromARGB(50, 195, 232, 243), - interval: Settings.gridSize.toDouble(), + interval: (preferences.getInt(PrefKeys.gridSize) ?? + Defaults.gridSize) + .toDouble(), divisions: 1, subdivisions: 1, child: Container(), diff --git a/lib/widgets/network_tree/networktables_tree_row.dart b/lib/widgets/network_tree/networktables_tree_row.dart index 1307e8e8..9ab4bfa9 100644 --- a/lib/widgets/network_tree/networktables_tree_row.dart +++ b/lib/widgets/network_tree/networktables_tree_row.dart @@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_container.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/nt_widget_container_model.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/models/widget_container_model.dart'; @@ -112,8 +113,8 @@ class NetworkTableTreeRow { children.clear(); } - static NTWidgetModel? getNTWidgetFromTopic( - NTConnection ntConnection, NT4Topic ntTopic) { + static NTWidgetModel? getNTWidgetFromTopic(NTConnection ntConnection, + SharedPreferences preferences, NT4Topic ntTopic) { switch (ntTopic.type) { case NT4TypeStr.kFloat64: case NT4TypeStr.kInt: @@ -126,12 +127,14 @@ class NetworkTableTreeRow { case NT4TypeStr.kStringArr: return TextDisplayModel( ntConnection: ntConnection, + preferences: preferences, topic: ntTopic.name, dataType: ntTopic.type, ); case NT4TypeStr.kBool: return BooleanBoxModel( ntConnection: ntConnection, + preferences: preferences, topic: ntTopic.name, dataType: ntTopic.type, ); @@ -154,7 +157,8 @@ class NetworkTableTreeRow { (hasRow('description') || hasRow('connected')); if (isCameraStream) { - return CameraStreamModel(ntConnection: ntConnection, topic: topic); + return CameraStreamModel( + ntConnection: ntConnection, preferences: preferences, topic: topic); } if (hasRows([ @@ -166,13 +170,14 @@ class NetworkTableTreeRow { 'sizeFrontBack', 'sizeLeftRight', ])) { - return YAGSLSwerveDriveModel(ntConnection: ntConnection, topic: topic); + return YAGSLSwerveDriveModel( + ntConnection: ntConnection, preferences: preferences, topic: topic); } return null; } - return getNTWidgetFromTopic(ntConnection, ntTopic!); + return getNTWidgetFromTopic(ntConnection, preferences, ntTopic!); } Future getTypeString(String typeTopic) async { @@ -186,7 +191,8 @@ class NetworkTableTreeRow { return null; } - return NTWidgetBuilder.buildNTModelFromType(ntConnection, type, topic); + return NTWidgetBuilder.buildNTModelFromType( + ntConnection, preferences, type, topic); } Future?> getListLayoutChildren() async { @@ -267,6 +273,8 @@ class NetworkTableTreeRow { title: rowName, width: width, height: height, + cornerRadius: + preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius, child: widget, ); } diff --git a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart index 0365274a..081147ad 100644 --- a/lib/widgets/nt_widgets/multi-topic/accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/accelerometer.dart @@ -14,16 +14,19 @@ class AccelerometerModel extends NTWidgetModel { String get valueTopic => '$topic/Value'; - AccelerometerModel( - {required super.ntConnection, - required super.topic, - super.dataType, - super.period}) - : super(); - - AccelerometerModel.fromJson( - {required super.ntConnection, required super.jsonData}) - : super.fromJson(); + AccelerometerModel({ + required super.ntConnection, + required super.preferences, + required super.topic, + super.dataType, + super.period, + }) : super(); + + AccelerometerModel.fromJson({ + required super.ntConnection, + required super.preferences, + required super.jsonData, + }) : super.fromJson(); @override void init() { diff --git a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart index d6843ce7..252648c8 100644 --- a/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart @@ -37,6 +37,7 @@ class BasicSwerveModel extends NTWidgetModel { BasicSwerveModel({ required super.ntConnection, + required super.preferences, required super.topic, bool showRobotRotation = true, String rotationUnit = 'Radians', @@ -46,9 +47,11 @@ class BasicSwerveModel extends NTWidgetModel { _showRobotRotation = showRobotRotation, super(); - BasicSwerveModel.fromJson( - {required super.ntConnection, required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + BasicSwerveModel.fromJson({ + required super.ntConnection, + required super.preferences, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _showRobotRotation = tryCast(jsonData['show_robot_rotation']) ?? true; _rotationUnit = tryCast(jsonData['rotation_unit']) ?? 'Degrees'; } diff --git a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart index d4586879..788967f0 100644 --- a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart +++ b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart @@ -32,6 +32,7 @@ class CameraStreamModel extends NTWidgetModel { CameraStreamModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -39,6 +40,7 @@ class CameraStreamModel extends NTWidgetModel { CameraStreamModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart index d2c9a4b6..2f837b70 100644 --- a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart @@ -37,6 +37,7 @@ class ComboBoxChooserModel extends NTWidgetModel { ComboBoxChooserModel({ required super.ntConnection, + required super.preferences, required super.topic, bool sortOptions = false, super.dataType, @@ -46,6 +47,7 @@ class ComboBoxChooserModel extends NTWidgetModel { ComboBoxChooserModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _sortOptions = tryCast(jsonData['sort_options']) ?? _sortOptions; diff --git a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart index 06b6e7d7..99e58bba 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_scheduler.dart @@ -20,6 +20,7 @@ class CommandSchedulerModel extends NTWidgetModel { CommandSchedulerModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -27,6 +28,7 @@ class CommandSchedulerModel extends NTWidgetModel { CommandSchedulerModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/command_widget.dart b/lib/widgets/nt_widgets/multi-topic/command_widget.dart index 5f7f04ba..2937ff46 100644 --- a/lib/widgets/nt_widgets/multi-topic/command_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/command_widget.dart @@ -27,6 +27,7 @@ class CommandModel extends NTWidgetModel { CommandModel({ required super.ntConnection, + required super.preferences, required super.topic, bool showType = true, super.dataType, @@ -36,6 +37,7 @@ class CommandModel extends NTWidgetModel { CommandModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _showType = tryCast(jsonData['show_type']) ?? _showType; diff --git a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart index 59913050..93a9ddbe 100644 --- a/lib/widgets/nt_widgets/multi-topic/differential_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/differential_drive.dart @@ -43,6 +43,7 @@ class DifferentialDriveModel extends NTWidgetModel { DifferentialDriveModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -50,6 +51,7 @@ class DifferentialDriveModel extends NTWidgetModel { DifferentialDriveModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart index 22b32f3c..2d0f39be 100644 --- a/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/encoder_widget.dart @@ -14,6 +14,7 @@ class EncoderModel extends NTWidgetModel { EncoderModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -21,6 +22,7 @@ class EncoderModel extends NTWidgetModel { EncoderModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/field_widget.dart b/lib/widgets/nt_widgets/multi-topic/field_widget.dart index b34a2e1d..080c0319 100644 --- a/lib/widgets/nt_widgets/multi-topic/field_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/field_widget.dart @@ -45,6 +45,7 @@ class FieldWidgetModel extends NTWidgetModel { FieldWidgetModel({ required super.ntConnection, + required super.preferences, required super.topic, String? fieldName, bool showOtherObjects = true, @@ -63,9 +64,11 @@ class FieldWidgetModel extends NTWidgetModel { _field = FieldImages.getFieldFromGame(_fieldGame)!; } - FieldWidgetModel.fromJson( - {required super.ntConnection, required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + FieldWidgetModel.fromJson({ + required super.ntConnection, + required super.preferences, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _fieldGame = tryCast(jsonData['field_game']) ?? _fieldGame; _robotWidthMeters = tryCast(jsonData['robot_width']) ?? 0.85; @@ -319,7 +322,8 @@ class FieldWidgetModel extends NTWidgetModel { Stream get multiTopicPeriodicStream async* { final Duration delayTime = Duration( microseconds: ((subscription?.options.periodicRateSeconds ?? - Settings.defaultPeriod) * + preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod) * 1e6) .round()); diff --git a/lib/widgets/nt_widgets/multi-topic/fms_info.dart b/lib/widgets/nt_widgets/multi-topic/fms_info.dart index 5c0a3018..52a9e076 100644 --- a/lib/widgets/nt_widgets/multi-topic/fms_info.dart +++ b/lib/widgets/nt_widgets/multi-topic/fms_info.dart @@ -20,6 +20,7 @@ class FMSInfoModel extends NTWidgetModel { FMSInfoModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -27,6 +28,7 @@ class FMSInfoModel extends NTWidgetModel { FMSInfoModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/gyro.dart b/lib/widgets/nt_widgets/multi-topic/gyro.dart index 68e1bfea..c93ec51b 100644 --- a/lib/widgets/nt_widgets/multi-topic/gyro.dart +++ b/lib/widgets/nt_widgets/multi-topic/gyro.dart @@ -27,6 +27,7 @@ class GyroModel extends NTWidgetModel { GyroModel({ required super.ntConnection, + required super.preferences, required super.topic, bool counterClockwisePositive = false, super.dataType, @@ -36,6 +37,7 @@ class GyroModel extends NTWidgetModel { GyroModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _counterClockwisePositive = diff --git a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart index 772c55c4..91205635 100644 --- a/lib/widgets/nt_widgets/multi-topic/motor_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/motor_controller.dart @@ -17,6 +17,7 @@ class MotorControllerModel extends NTWidgetModel { MotorControllerModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -24,6 +25,7 @@ class MotorControllerModel extends NTWidgetModel { MotorControllerModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart index 2372199e..b6501804 100644 --- a/lib/widgets/nt_widgets/multi-topic/network_alerts.dart +++ b/lib/widgets/nt_widgets/multi-topic/network_alerts.dart @@ -15,6 +15,7 @@ class NetworkAlertsModel extends NTWidgetModel { NetworkAlertsModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -22,6 +23,7 @@ class NetworkAlertsModel extends NTWidgetModel { NetworkAlertsModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart index 6bbfb6b2..dc4486a1 100644 --- a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart @@ -48,16 +48,19 @@ class PIDControllerModel extends NTWidgetModel { set setpointLastValue(value) => _setpointLastValue = value; - PIDControllerModel( - {required super.ntConnection, - required super.topic, - super.dataType, - super.period}) - : super(); - - PIDControllerModel.fromJson( - {required super.ntConnection, required super.jsonData}) - : super.fromJson(); + PIDControllerModel({ + required super.ntConnection, + required super.preferences, + required super.topic, + super.dataType, + super.period, + }) : super(); + + PIDControllerModel.fromJson({ + required super.ntConnection, + required super.preferences, + required super.jsonData, + }) : super.fromJson(); @override void resetSubscription() { diff --git a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart index f4029c04..0d090758 100644 --- a/lib/widgets/nt_widgets/multi-topic/power_distribution.dart +++ b/lib/widgets/nt_widgets/multi-topic/power_distribution.dart @@ -18,6 +18,7 @@ class PowerDistributionModel extends NTWidgetModel { PowerDistributionModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -25,6 +26,7 @@ class PowerDistributionModel extends NTWidgetModel { PowerDistributionModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart index bf8db006..e3326f98 100644 --- a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart @@ -50,6 +50,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { ProfiledPIDControllerModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -57,6 +58,7 @@ class ProfiledPIDControllerModel extends NTWidgetModel { ProfiledPIDControllerModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart index d42af9ac..76b80172 100644 --- a/lib/widgets/nt_widgets/multi-topic/relay_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/relay_widget.dart @@ -19,6 +19,7 @@ class RelayModel extends NTWidgetModel { RelayModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -26,6 +27,7 @@ class RelayModel extends NTWidgetModel { RelayModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart index 64b88f5e..c1f7779a 100644 --- a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart +++ b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart @@ -22,6 +22,7 @@ class RobotPreferencesModel extends NTWidgetModel { RobotPreferencesModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -29,6 +30,7 @@ class RobotPreferencesModel extends NTWidgetModel { RobotPreferencesModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); } diff --git a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart index fbdbc6e9..229ab3a0 100644 --- a/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/split_button_chooser.dart @@ -25,6 +25,7 @@ class SplitButtonChooserModel extends NTWidgetModel { SplitButtonChooserModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -32,6 +33,7 @@ class SplitButtonChooserModel extends NTWidgetModel { SplitButtonChooserModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart index 7c04d849..ba6cca45 100644 --- a/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/subsystem_widget.dart @@ -14,6 +14,7 @@ class SubsystemModel extends NTWidgetModel { SubsystemModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -21,6 +22,7 @@ class SubsystemModel extends NTWidgetModel { SubsystemModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart index 1b0dd4a5..27e60076 100644 --- a/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart +++ b/lib/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart @@ -15,6 +15,7 @@ class ThreeAxisAccelerometerModel extends NTWidgetModel { ThreeAxisAccelerometerModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -22,6 +23,7 @@ class ThreeAxisAccelerometerModel extends NTWidgetModel { ThreeAxisAccelerometerModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart index 4fba9159..70bfc321 100644 --- a/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart +++ b/lib/widgets/nt_widgets/multi-topic/ultrasonic.dart @@ -16,6 +16,7 @@ class UltrasonicModel extends NTWidgetModel { UltrasonicModel({ required super.ntConnection, + required super.preferences, required super.topic, super.dataType, super.period, @@ -23,6 +24,7 @@ class UltrasonicModel extends NTWidgetModel { UltrasonicModel.fromJson({ required super.ntConnection, + required super.preferences, required super.jsonData, }) : super.fromJson(); diff --git a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart index 1ababd2d..29b436a4 100644 --- a/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart +++ b/lib/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart @@ -40,6 +40,7 @@ class YAGSLSwerveDriveModel extends NTWidgetModel { YAGSLSwerveDriveModel({ required super.ntConnection, + required super.preferences, required super.topic, bool showRobotRotation = true, bool showDesiredStates = true, @@ -51,6 +52,7 @@ class YAGSLSwerveDriveModel extends NTWidgetModel { YAGSLSwerveDriveModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _showRobotRotation = tryCast(jsonData['show_robot_rotation']) ?? true; diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index 87aff379..8e814320 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; import 'package:elastic_dashboard/services/nt_connection.dart'; @@ -21,6 +22,8 @@ import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.d class NTWidgetModel extends ChangeNotifier { final NTConnection ntConnection; + final SharedPreferences preferences; + String _typeOverride = 'NTWidget'; String get type => _typeOverride; @@ -44,34 +47,43 @@ class NTWidgetModel extends ChangeNotifier { NTWidgetModel({ required this.ntConnection, + required this.preferences, required String topic, this.dataType = 'Unknown', double? period, }) : _topic = topic { - this.period = period ?? Settings.defaultPeriod; + this.period = period ?? + preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod; init(); } NTWidgetModel.createDefault({ required this.ntConnection, + required this.preferences, required String type, required String topic, this.dataType = 'Unknown', double? period, }) : _typeOverride = type, _topic = topic { - this.period = period ?? Settings.defaultPeriod; + this.period = period ?? + preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod; init(); } NTWidgetModel.fromJson({ required this.ntConnection, + required this.preferences, required Map jsonData, }) { _topic = tryCast(jsonData['topic']) ?? ''; - _period = tryCast(jsonData['period']) ?? Settings.defaultPeriod; + _period = tryCast(jsonData['period']) ?? + preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod; dataType = tryCast(jsonData['data_type']) ?? dataType; init(); @@ -201,7 +213,8 @@ class NTWidgetModel extends ChangeNotifier { Stream get multiTopicPeriodicStream async* { final Duration delayTime = Duration( microseconds: ((subscription?.options.periodicRateSeconds ?? - Settings.defaultPeriod) * + preferences.getDouble(PrefKeys.defaultPeriod) ?? + Defaults.defaultPeriod) * 1e6) .round()); diff --git a/lib/widgets/nt_widgets/single_topic/boolean_box.dart b/lib/widgets/nt_widgets/single_topic/boolean_box.dart index 452a772d..207a9f7a 100644 --- a/lib/widgets/nt_widgets/single_topic/boolean_box.dart +++ b/lib/widgets/nt_widgets/single_topic/boolean_box.dart @@ -57,6 +57,7 @@ class BooleanBoxModel extends NTWidgetModel { BooleanBoxModel({ required super.ntConnection, + required super.preferences, required super.topic, Color trueColor = Colors.green, Color falseColor = Colors.red, @@ -70,9 +71,11 @@ class BooleanBoxModel extends NTWidgetModel { _falseIcon = falseIcon, super(); - BooleanBoxModel.fromJson( - {required super.ntConnection, required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + BooleanBoxModel.fromJson({ + required super.ntConnection, + required super.preferences, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { int? trueColorValue = tryCast(jsonData['true_color']) ?? tryCast(jsonData['colorWhenTrue']); int? falseColorValue = diff --git a/lib/widgets/nt_widgets/single_topic/graph.dart b/lib/widgets/nt_widgets/single_topic/graph.dart index 3c8d69c9..416a4cb0 100644 --- a/lib/widgets/nt_widgets/single_topic/graph.dart +++ b/lib/widgets/nt_widgets/single_topic/graph.dart @@ -62,6 +62,7 @@ class GraphModel extends NTWidgetModel { GraphModel({ required super.ntConnection, + required super.preferences, required super.topic, double timeDisplayed = 5.0, double? minValue, @@ -77,9 +78,11 @@ class GraphModel extends NTWidgetModel { _lineWidth = lineWidth, super(); - GraphModel.fromJson( - {required super.ntConnection, required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + GraphModel.fromJson({ + required super.ntConnection, + required super.preferences, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _timeDisplayed = tryCast(jsonData['time_displayed']) ?? tryCast(jsonData['visibleTime']) ?? 5.0; diff --git a/lib/widgets/nt_widgets/single_topic/match_time.dart b/lib/widgets/nt_widgets/single_topic/match_time.dart index cc4f5010..4df92a37 100644 --- a/lib/widgets/nt_widgets/single_topic/match_time.dart +++ b/lib/widgets/nt_widgets/single_topic/match_time.dart @@ -45,6 +45,7 @@ class MatchTimeModel extends NTWidgetModel { MatchTimeModel({ required super.ntConnection, + required super.preferences, required super.topic, String timeDisplayMode = 'Minutes and Seconds', int redStartTime = 15, @@ -58,6 +59,7 @@ class MatchTimeModel extends NTWidgetModel { MatchTimeModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _timeDisplayMode = diff --git a/lib/widgets/nt_widgets/single_topic/number_bar.dart b/lib/widgets/nt_widgets/single_topic/number_bar.dart index 17977797..7c8fc224 100644 --- a/lib/widgets/nt_widgets/single_topic/number_bar.dart +++ b/lib/widgets/nt_widgets/single_topic/number_bar.dart @@ -58,6 +58,7 @@ class NumberBarModel extends NTWidgetModel { NumberBarModel({ required super.ntConnection, + required super.preferences, required super.topic, double minValue = -1.0, double maxValue = 1.0, @@ -75,6 +76,7 @@ class NumberBarModel extends NTWidgetModel { NumberBarModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? -1.0; diff --git a/lib/widgets/nt_widgets/single_topic/number_slider.dart b/lib/widgets/nt_widgets/single_topic/number_slider.dart index 24fb088b..902b9ac4 100644 --- a/lib/widgets/nt_widgets/single_topic/number_slider.dart +++ b/lib/widgets/nt_widgets/single_topic/number_slider.dart @@ -58,6 +58,7 @@ class NumberSliderModel extends NTWidgetModel { NumberSliderModel({ required super.ntConnection, + required super.preferences, required super.topic, double minValue = -1.0, double maxValue = 1.0, @@ -73,6 +74,7 @@ class NumberSliderModel extends NTWidgetModel { NumberSliderModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _minValue = diff --git a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart index 032feadf..6fe2bebd 100644 --- a/lib/widgets/nt_widgets/single_topic/radial_gauge.dart +++ b/lib/widgets/nt_widgets/single_topic/radial_gauge.dart @@ -83,6 +83,7 @@ class RadialGaugeModel extends NTWidgetModel { RadialGaugeModel({ required super.ntConnection, + required super.preferences, required super.topic, double startAngle = -140.0, double endAngle = 140.0, @@ -106,6 +107,7 @@ class RadialGaugeModel extends NTWidgetModel { RadialGaugeModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _startAngle = tryCast(jsonData['start_angle']) ?? _startAngle; diff --git a/lib/widgets/nt_widgets/single_topic/text_display.dart b/lib/widgets/nt_widgets/single_topic/text_display.dart index e897d408..0665db98 100644 --- a/lib/widgets/nt_widgets/single_topic/text_display.dart +++ b/lib/widgets/nt_widgets/single_topic/text_display.dart @@ -29,6 +29,7 @@ class TextDisplayModel extends NTWidgetModel { TextDisplayModel({ required super.ntConnection, + required super.preferences, required super.topic, bool showSubmitButton = false, super.dataType, @@ -38,6 +39,7 @@ class TextDisplayModel extends NTWidgetModel { TextDisplayModel.fromJson({ required super.ntConnection, + required super.preferences, required Map jsonData, }) : super.fromJson(jsonData: jsonData) { _showSubmitButton = @@ -144,7 +146,7 @@ class TextDisplay extends NTWidget { builder: (context, snapshot) { Object? data = snapshot.data; - if (data.toString() != model.previousValue?.toString()) { + if (data?.toString() != model.previousValue?.toString()) { // Needed to prevent errors Future(() async { String displayString = data.toString(); diff --git a/lib/widgets/nt_widgets/single_topic/voltage_view.dart b/lib/widgets/nt_widgets/single_topic/voltage_view.dart index 004ebd9b..9c15c364 100644 --- a/lib/widgets/nt_widgets/single_topic/voltage_view.dart +++ b/lib/widgets/nt_widgets/single_topic/voltage_view.dart @@ -58,6 +58,7 @@ class VoltageViewModel extends NTWidgetModel { VoltageViewModel({ required super.ntConnection, + required super.preferences, required super.topic, double minValue = 4.0, double maxValue = 13.0, @@ -73,9 +74,11 @@ class VoltageViewModel extends NTWidgetModel { _minValue = minValue, super(); - VoltageViewModel.fromJson( - {required super.ntConnection, required Map jsonData}) - : super.fromJson(jsonData: jsonData) { + VoltageViewModel.fromJson({ + required super.ntConnection, + required super.preferences, + required Map jsonData, + }) : super.fromJson(jsonData: jsonData) { _minValue = tryCast(jsonData['min_value']) ?? 4.0; _maxValue = tryCast(jsonData['max_value']) ?? 13.0; _divisions = tryCast(jsonData['divisions']); diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index abd2d262..c31d200a 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -109,9 +109,9 @@ class _SettingsDialogState extends State { children: [ Flexible( child: DialogTextInput( - initialText: - widget.preferences.getInt(PrefKeys.teamNumber)?.toString() ?? - Settings.teamNumber.toString(), + initialText: (widget.preferences.getInt(PrefKeys.teamNumber) ?? + Defaults.teamNumber) + .toString(), label: 'Team Number', onSubmit: (data) async { await widget.onTeamNumberChanged?.call(data); @@ -165,7 +165,7 @@ class _SettingsDialogState extends State { (Settings.ipAddressMode == IPAddressMode.driverStation && !dsConnected), initialText: widget.preferences.getString(PrefKeys.ipAddress) ?? - Settings.ipAddress, + Defaults.ipAddress, label: 'IP Address', onSubmit: (String? data) async { await widget.onIPAddressChanged?.call(data); @@ -189,7 +189,7 @@ class _SettingsDialogState extends State { Flexible( child: DialogToggleSwitch( initialValue: widget.preferences.getBool(PrefKeys.showGrid) ?? - Settings.showGrid, + Defaults.showGrid, label: 'Show Grid', onToggle: (value) { setState(() { @@ -202,7 +202,7 @@ class _SettingsDialogState extends State { child: DialogTextInput( initialText: widget.preferences.getInt(PrefKeys.gridSize)?.toString() ?? - Settings.gridSize.toString(), + Defaults.gridSize.toString(), label: 'Grid Size', onSubmit: (value) async { await widget.onGridSizeChanged?.call(value); @@ -220,10 +220,10 @@ class _SettingsDialogState extends State { Flexible( flex: 2, child: DialogTextInput( - initialText: widget.preferences - .getDouble(PrefKeys.cornerRadius) - ?.toString() ?? - Settings.cornerRadius.toString(), + initialText: + (widget.preferences.getDouble(PrefKeys.cornerRadius) ?? + Defaults.cornerRadius.toString()) + .toString(), label: 'Corner Radius', onSubmit: (value) { setState(() { @@ -238,7 +238,7 @@ class _SettingsDialogState extends State { child: DialogToggleSwitch( initialValue: widget.preferences.getBool(PrefKeys.autoResizeToDS) ?? - Settings.autoResizeToDS, + Defaults.autoResizeToDS, label: 'Resize to Driver Station Height', onToggle: (value) { setState(() { @@ -271,7 +271,7 @@ class _SettingsDialogState extends State { flex: 4, child: DialogToggleSwitch( initialValue: widget.preferences.getBool(PrefKeys.layoutLocked) ?? - Settings.layoutLocked, + Defaults.layoutLocked, label: 'Lock Layout', onToggle: (value) { setState(() { @@ -300,7 +300,7 @@ class _SettingsDialogState extends State { child: DialogTextInput( initialText: (widget.preferences.getDouble(PrefKeys.defaultPeriod) ?? - Settings.defaultPeriod) + Defaults.defaultPeriod) .toString(), label: 'Default Period', onSubmit: (value) async { @@ -314,7 +314,7 @@ class _SettingsDialogState extends State { child: DialogTextInput( initialText: (widget.preferences .getDouble(PrefKeys.defaultGraphPeriod) ?? - Settings.defaultGraphPeriod) + Defaults.defaultGraphPeriod) .toString(), label: 'Default Graph Period', onSubmit: (value) async { diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 54ec8eaa..61404920 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -315,13 +315,13 @@ class TabGrid extends StatelessWidget { if (previewWidth < model.minWidth) { previewWidth = DraggableWidgetContainer.snapToGrid( constrainedRect.width.clamp(model.minWidth, double.infinity) + - Settings.gridSize); + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } if (previewHeight < model.minHeight) { previewHeight = DraggableWidgetContainer.snapToGrid( constrainedRect.height.clamp(model.minHeight, double.infinity) + - Settings.gridSize); + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize)); } Rect preview = @@ -542,8 +542,12 @@ class TabGrid extends StatelessWidget { initialPosition: Rect.fromLTWH( 0.0, 0.0, - Settings.gridSize.toDouble() * 2, - Settings.gridSize.toDouble() * 2, + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble() * + 2, + (preferences.getInt(PrefKeys.gridSize) ?? Defaults.gridSize) + .toDouble() * + 2, ), children: children, minWidth: 128.0, @@ -787,8 +791,9 @@ class TabGrid extends StatelessWidget { child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), - borderRadius: - BorderRadius.circular(Settings.cornerRadius), + borderRadius: BorderRadius.circular( + preferences.getDouble(PrefKeys.cornerRadius) ?? + Defaults.cornerRadius), border: Border.all(color: Colors.yellow, width: 5.0), ), ), @@ -803,7 +808,8 @@ class TabGrid extends StatelessWidget { GestureDetector( onTap: () {}, onSecondaryTapUp: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } List menuEntries = [ @@ -902,7 +908,9 @@ class TabGrid extends StatelessWidget { color: (validLocation) ? Colors.white.withOpacity(0.25) : Colors.black.withOpacity(0.1), - borderRadius: BorderRadius.circular(Settings.cornerRadius), + borderRadius: BorderRadius.circular( + preferences.getDouble(PrefKeys.cornerRadius) ?? + Defaults.cornerRadius), border: Border.all(color: borderColor, width: 5.0), ), ), @@ -914,7 +922,8 @@ class TabGrid extends StatelessWidget { behavior: HitTestBehavior.translucent, onTap: () {}, onSecondaryTapUp: (details) { - if (Settings.layoutLocked) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? + Defaults.layoutLocked) { return; } ContextMenu contextMenu = ContextMenu( From 1013556a927a102870a0e09177b8eda5977047fa Mon Sep 17 00:00:00 2001 From: Gold87 Date: Tue, 23 Apr 2024 00:18:56 -0400 Subject: [PATCH 05/28] Change IP address mode to work off of shared preferences --- lib/main.dart | 3 --- lib/pages/dashboard_page.dart | 10 +++++----- lib/services/settings.dart | 2 -- lib/widgets/settings_dialog.dart | 9 ++++++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d2e5ff8d..82f3da9e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -54,9 +54,6 @@ void main() async { await windowManager.ensureInitialized(); - Settings.ipAddressMode = - IPAddressMode.fromIndex(preferences.getInt(PrefKeys.ipAddressMode)); - NTWidgetBuilder.ensureInitialized(); String ipAddress = diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index fdf96082..8d1cccd7 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -85,7 +85,8 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.dsClientConnect( onIPAnnounced: (ip) async { - if (Settings.ipAddressMode != IPAddressMode.driverStation) { + if (preferences.getInt(PrefKeys.ipAddressMode) != + IPAddressMode.driverStation.index) { return; } @@ -902,7 +903,8 @@ class _DashboardPageState extends State with WindowListener { await preferences.setInt(PrefKeys.teamNumber, newTeamNumber); - switch (Settings.ipAddressMode) { + switch (IPAddressMode.fromIndex( + preferences.getInt(PrefKeys.ipAddressMode))) { case IPAddressMode.roboRIOmDNS: _updateIPAddress( IPAddressUtil.teamNumberToRIOmDNS(newTeamNumber)); @@ -916,13 +918,11 @@ class _DashboardPageState extends State with WindowListener { } }, onIPAddressModeChanged: (mode) async { - if (mode == Settings.ipAddressMode) { + if (mode.index == preferences.getInt(PrefKeys.ipAddressMode)) { return; } await preferences.setInt(PrefKeys.ipAddressMode, mode.index); - Settings.ipAddressMode = mode; - switch (mode) { case IPAddressMode.driverStation: String? lastAnnouncedIP = diff --git a/lib/services/settings.dart b/lib/services/settings.dart index a2436ea9..9a098d03 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -5,8 +5,6 @@ class Settings { 'https://github.com/Gold872/elastic-dashboard'; static const String releasesLink = '$repositoryLink/releases/latest'; - static IPAddressMode ipAddressMode = IPAddressMode.driverStation; - // static String ipAddress = '127.0.0.1'; // static int teamNumber = 9999; // static int gridSize = 128; diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index c31d200a..2f0e180b 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -151,7 +151,8 @@ class _SettingsDialogState extends State { setState(() {}); }, choices: IPAddressMode.values, - initialValue: Settings.ipAddressMode, + initialValue: IPAddressMode.fromIndex( + widget.preferences.getInt(PrefKeys.ipAddressMode)), ), const SizedBox(height: 5), StreamBuilder( @@ -161,8 +162,10 @@ class _SettingsDialogState extends State { bool dsConnected = tryCast(snapshot.data) ?? false; return DialogTextInput( - enabled: Settings.ipAddressMode == IPAddressMode.custom || - (Settings.ipAddressMode == IPAddressMode.driverStation && + enabled: widget.preferences.getInt(PrefKeys.ipAddressMode) == + IPAddressMode.custom.index || + (widget.preferences.getInt(PrefKeys.ipAddressMode) == + IPAddressMode.driverStation.index && !dsConnected), initialText: widget.preferences.getString(PrefKeys.ipAddress) ?? Defaults.ipAddress, From 4b0fddb1e27e373d55bf88ce82d8edad889eb70f Mon Sep 17 00:00:00 2001 From: Gold87 Date: Tue, 23 Apr 2024 13:18:00 -0400 Subject: [PATCH 06/28] Fixed unit tests --- lib/main.dart | 1 - lib/pages/dashboard_page.dart | 6 +- test/main_test.dart | 5 +- test/pages/dashboard_page_test.dart | 44 +++++----- .../shuffleboard_nt_listener_test.dart | 33 +++---- test/test_util.dart | 18 ++-- test/widgets/editable_tab_bar_test.dart | 87 ++++++++++++++++--- test/widgets/settings_dialog_test.dart | 49 ++++++----- test/widgets/tab_grid_test.dart | 14 ++- 9 files changed, 161 insertions(+), 96 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 82f3da9e..be2c9c62 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,6 @@ import 'package:window_manager/window_manager.dart'; import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:elastic_dashboard/services/field_images.dart'; -import 'package:elastic_dashboard/services/ip_address_util.dart'; import 'package:elastic_dashboard/services/log.dart'; import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 8d1cccd7..b5626d50 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -1218,8 +1218,7 @@ class _DashboardPageState extends State with WindowListener { } void _moveTabLeft() { - if (!(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked)) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } if (_currentTabIndex <= 0) { @@ -1246,8 +1245,7 @@ class _DashboardPageState extends State with WindowListener { } void _moveTabRight() { - if (!(preferences.getBool(PrefKeys.layoutLocked) ?? - Defaults.layoutLocked)) { + if (preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } if (_currentTabIndex >= _tabData.length - 1) { diff --git a/test/main_test.dart b/test/main_test.dart index c3e4cca7..36b29e0f 100644 --- a/test/main_test.dart +++ b/test/main_test.dart @@ -13,8 +13,6 @@ import 'test_util.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - setupMockOfflineNT4(); - testWidgets('Full app test', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; @@ -33,8 +31,9 @@ void main() { await widgetTester.pumpWidget( Elastic( - version: '0.0.0.0', + ntConnection: createMockOfflineNT4(), preferences: preferences, + version: '0.0.0.0', ), ); diff --git a/test/pages/dashboard_page_test.dart b/test/pages/dashboard_page_test.dart index e33559d1..9b735c75 100644 --- a/test/pages/dashboard_page_test.dart +++ b/test/pages/dashboard_page_test.dart @@ -12,7 +12,6 @@ import 'package:titlebar_buttons/titlebar_buttons.dart'; import 'package:elastic_dashboard/pages/dashboard_page.dart'; import 'package:elastic_dashboard/services/field_images.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/custom_appbar.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -55,11 +54,11 @@ void main() { testWidgets('Dashboard page loading offline', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -79,11 +78,11 @@ void main() { testWidgets('Dashboard page loading online', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOnlineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOnlineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -103,11 +102,11 @@ void main() { testWidgets('Save layout (button)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -135,11 +134,12 @@ void main() { testWidgets('Add widget dialog (widgets)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOnlineNT4(); + createMockOnlineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOnlineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -198,11 +198,11 @@ void main() { testWidgets('Add widget dialog (layouts)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOnlineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOnlineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -253,11 +253,11 @@ void main() { testWidgets('List Layouts', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOnlineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOnlineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -348,7 +348,6 @@ void main() { // A custom mock is set up to reproduce behavior when actually running final mockNT4Connection = MockNTConnection(); - final mockNT4Client = MockNT4Client(); final mockSubscription = MockNT4Subscription(); when(mockNT4Connection.isNT4Connected).thenReturn(true); @@ -359,13 +358,11 @@ void main() { when(mockSubscription.periodicStream()) .thenAnswer((_) => Stream.value(null)); - when(mockNT4Client.addTopicAnnounceListener(any)) + when(mockNT4Connection.addTopicAnnounceListener(any)) .thenAnswer((realInvocation) { fakeAnnounceCallbacks.add(realInvocation.positionalArguments[0]); }); - when(mockNT4Connection.nt4Client).thenReturn(mockNT4Client); - when(mockNT4Connection.getLastAnnouncedValue(any)).thenReturn(null); when(mockNT4Connection.subscribe(any, any)).thenReturn(mockSubscription); @@ -396,11 +393,10 @@ void main() { '/Shuffleboard/Test-Tab/Shuffleboard Test Layout/.type')) .thenAnswer((realInvocation) => Future.value('ShuffleboardLayout')); - NTConnection.instance = mockNT4Connection; - await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: mockNT4Connection, preferences: preferences, version: '0.0.0.0', ), @@ -475,11 +471,11 @@ void main() { testWidgets('About dialog', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -507,11 +503,11 @@ void main() { testWidgets('Changing tabs', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -536,11 +532,11 @@ void main() { testWidgets('Creating new tab', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -566,11 +562,11 @@ void main() { testWidgets('Closing tab', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -608,11 +604,11 @@ void main() { testWidgets('Reordering tabs', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -666,11 +662,11 @@ void main() { testWidgets('Renaming tab', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -718,11 +714,11 @@ void main() { testWidgets('Minimizing window', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -742,11 +738,11 @@ void main() { testWidgets('Maximizing/unmaximizing window', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -774,11 +770,11 @@ void main() { testWidgets('Closing window (All changes saved)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -810,11 +806,11 @@ void main() { testWidgets('Closing window (Unsaved changes)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), @@ -852,11 +848,11 @@ void main() { testWidgets('Opening settings', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget( MaterialApp( home: DashboardPage( + ntConnection: createMockOfflineNT4(), preferences: preferences, version: '0.0.0.0', ), diff --git a/test/services/shuffleboard_nt_listener_test.dart b/test/services/shuffleboard_nt_listener_test.dart index c0d8bf55..9891c490 100644 --- a/test/services/shuffleboard_nt_listener_test.dart +++ b/test/services/shuffleboard_nt_listener_test.dart @@ -1,13 +1,20 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/shuffleboard_nt_listener.dart'; import '../test_util.mocks.dart'; void main() { + late SharedPreferences preferences; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + }); + test('Shuffleboard NT listener', () async { List topicAnnounceListeners = []; Map lastAnnouncedValues = { @@ -16,21 +23,15 @@ void main() { }; final mockNT4Connection = MockNTConnection(); - final mockNT4Client = MockNT4Client(); final mockSubscription = MockNT4Subscription(); - when(mockNT4Client.lastAnnouncedValues).thenReturn(lastAnnouncedValues); - when(mockNT4Client.topicAnnounceListeners) - .thenReturn(topicAnnounceListeners); - when(mockNT4Client.addTopicAnnounceListener(any)).thenAnswer( + when(mockNT4Connection.addTopicAnnounceListener(any)).thenAnswer( (realInvocation) => topicAnnounceListeners.add(realInvocation.positionalArguments[0])); when(mockSubscription.periodicStream()) .thenAnswer((_) => Stream.value(null)); - when(mockNT4Connection.nt4Client).thenReturn(mockNT4Client); - when(mockNT4Connection.isNT4Connected).thenReturn(true); when(mockNT4Connection.latencyStream()).thenAnswer((_) => Stream.value(0)); @@ -48,11 +49,13 @@ void main() { when(mockNT4Connection.subscribe(any)).thenReturn(mockSubscription); - NTConnection.instance = mockNT4Connection; + // NTConnection.instance = mockNT4Connection; Map announcedWidgetData = {}; ShuffleboardNTListener ntListener = ShuffleboardNTListener( + ntConnection: mockNT4Connection, + preferences: preferences, onWidgetAdded: (widgetData) { widgetData.forEach( (key, value) => announcedWidgetData.putIfAbsent(key, () => value)); @@ -61,9 +64,9 @@ void main() { ..initializeSubscriptions() ..initializeListeners(); - expect(ntConnection.nt4Client.topicAnnounceListeners.isNotEmpty, true); + expect(topicAnnounceListeners.isNotEmpty, true); - for (final callback in ntConnection.nt4Client.topicAnnounceListeners) { + for (final callback in topicAnnounceListeners) { callback.call(NT4Topic( name: '/Shuffleboard/.metadata/Test-Tab/Test Number/Position', type: NT4TypeStr.kFloat32Arr, @@ -93,9 +96,9 @@ void main() { expect(announcedWidgetData.containsKey('width'), true); expect(announcedWidgetData.containsKey('height'), true); - expect(announcedWidgetData['x'], Settings.gridSize.toDouble()); - expect(announcedWidgetData['y'], Settings.gridSize.toDouble()); - expect(announcedWidgetData['width'], Settings.gridSize.toDouble() * 2.0); - expect(announcedWidgetData['height'], Settings.gridSize.toDouble() * 2.0); + expect(announcedWidgetData['x'], Defaults.gridSize.toDouble()); + expect(announcedWidgetData['y'], Defaults.gridSize.toDouble()); + expect(announcedWidgetData['width'], Defaults.gridSize.toDouble() * 2.0); + expect(announcedWidgetData['height'], Defaults.gridSize.toDouble() * 2.0); }); } diff --git a/test/test_util.dart b/test/test_util.dart index 2f117a4f..0b77f17a 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -14,16 +14,15 @@ import 'test_util.mocks.dart'; MockSpec(), MockSpec() ]) -void setupMockOfflineNT4() { +MockNTConnection createMockOfflineNT4() { HttpOverrides.global = null; final mockNT4Connection = MockNTConnection(); - final mockNT4Client = MockNT4Client(); final mockSubscription = MockNT4Subscription(); - when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); + when(mockNT4Connection.announcedTopics()).thenReturn({}); - when(mockNT4Connection.nt4Client).thenReturn(mockNT4Client); + when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); when(mockNT4Connection.isNT4Connected).thenReturn(false); @@ -44,17 +43,16 @@ void setupMockOfflineNT4() { when(mockNT4Connection.getTopicFromName(any)) .thenReturn(NT4Topic(name: '', type: NT4TypeStr.kString, properties: {})); - NTConnection.instance = mockNT4Connection; + return mockNT4Connection; } -void setupMockOnlineNT4() { +MockNTConnection createMockOnlineNT4() { HttpOverrides.global = null; final mockNT4Connection = MockNTConnection(); - final mockNT4Client = MockNT4Client(); final mockSubscription = MockNT4Subscription(); - when(mockNT4Client.announcedTopics).thenReturn({ + when(mockNT4Connection.announcedTopics()).thenReturn({ 1: NT4Topic( name: '/SmartDashboard/Test Value 1', type: NT4TypeStr.kInt, @@ -69,8 +67,6 @@ void setupMockOnlineNT4() { when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); - when(mockNT4Connection.nt4Client).thenReturn(mockNT4Client); - when(mockNT4Connection.isNT4Connected).thenReturn(true); when(mockNT4Connection.connectionStatus()) @@ -90,7 +86,7 @@ void setupMockOnlineNT4() { when(mockNT4Connection.getTopicFromName(any)) .thenReturn(NT4Topic(name: '', type: NT4TypeStr.kString, properties: {})); - NTConnection.instance = mockNT4Connection; + return mockNT4Connection; } void ignoreOverflowErrors( diff --git a/test/widgets/editable_tab_bar_test.dart b/test/widgets/editable_tab_bar_test.dart index dccb8583..f60879aa 100644 --- a/test/widgets/editable_tab_bar_test.dart +++ b/test/widgets/editable_tab_bar_test.dart @@ -4,11 +4,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/editable_tab_bar.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; import '../test_util.dart'; +import '../test_util.mocks.dart'; import 'editable_tab_bar_test.mocks.dart'; @GenerateNiceMocks([MockSpec()]) @@ -28,9 +30,14 @@ class FakeTabBarFunctions { void main() { late MockFakeTabBarFunctions tabBarFunctions; + late MockNTConnection mockNTConnection; + late SharedPreferences preferences; - setUp(() { + setUp(() async { tabBarFunctions = MockFakeTabBarFunctions(); + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + mockNTConnection = createMockOfflineNT4(); }); testWidgets('Editable tab bar', (widgetTester) async { @@ -40,14 +47,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) {}, onTabDestroy: (index) {}, @@ -80,14 +96,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) { tabBarFunctions.onTabCreate(); @@ -131,14 +156,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) { tabBarFunctions.onTabCreate(); @@ -182,14 +216,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) { tabBarFunctions.onTabCreate(); @@ -246,14 +289,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) { tabBarFunctions.onTabCreate(); @@ -321,14 +373,23 @@ void main() { MaterialApp( home: Scaffold( body: EditableTabBar( + preferences: preferences, currentIndex: 0, tabData: [ TabData(name: 'Teleoperated'), TabData(name: 'Autonomous'), ], tabViews: [ - TabGrid(onAddWidgetPressed: () {}), - TabGrid(onAddWidgetPressed: () {}), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), + TabGrid( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ], onTabCreate: (tab) { tabBarFunctions.onTabCreate(); diff --git a/test/widgets/settings_dialog_test.dart b/test/widgets/settings_dialog_test.dart index d231f0f5..11f3291f 100644 --- a/test/widgets/settings_dialog_test.dart +++ b/test/widgets/settings_dialog_test.dart @@ -76,11 +76,11 @@ void main() { testWidgets('Settings Dialog', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), preferences: preferences, ), ), @@ -112,17 +112,17 @@ void main() { testWidgets('Change team number', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onTeamNumberChanged: (data) async { fakeSettings.changeTeamNumber(); await preferences.setInt(PrefKeys.teamNumber, int.parse(data!)); }, - preferences: preferences, ), ), )); @@ -146,17 +146,17 @@ void main() { testWidgets('Change team color', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOnlineNT4(), + preferences: preferences, onColorChanged: (color) async { fakeSettings.changeColor(); await preferences.setInt(PrefKeys.teamColor, color.value); }, - preferences: preferences, ), ), )); @@ -202,17 +202,17 @@ void main() { testWidgets('Toggle grid', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onGridToggle: (value) async { fakeSettings.changeShowGrid(); await preferences.setBool(PrefKeys.showGrid, value); }, - preferences: preferences, ), ), )); @@ -244,17 +244,17 @@ void main() { testWidgets('Change grid size', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onGridSizeChanged: (gridSize) async { fakeSettings.changeGridSize(); await preferences.setInt(PrefKeys.gridSize, int.parse(gridSize!)); }, - preferences: preferences, ), ), )); @@ -275,18 +275,19 @@ void main() { testWidgets('Change corner radius', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); + createMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onCornerRadiusChanged: (radius) async { fakeSettings.changeCornerRadius(); await preferences.setDouble( PrefKeys.cornerRadius, double.parse(radius!)); }, - preferences: preferences, ), ), )); @@ -308,17 +309,17 @@ void main() { testWidgets('Toggle driver station auto resize', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onResizeToDSChanged: (value) async { fakeSettings.changeDSAutoResize(); await preferences.setBool(PrefKeys.autoResizeToDS, value); }, - preferences: preferences, ), ), )); @@ -351,17 +352,17 @@ void main() { testWidgets('Toggle remember window position', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onRememberWindowPositionChanged: (value) async { fakeSettings.changeRememberWindow(); await preferences.setBool(PrefKeys.rememberWindowPosition, value); }, - preferences: preferences, ), ), )); @@ -394,17 +395,17 @@ void main() { testWidgets('Toggle lock layout', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + preferences: preferences, + ntConnection: createMockOfflineNT4(), onLayoutLock: (value) async { fakeSettings.changeLockLayout(); await preferences.setBool(PrefKeys.layoutLocked, value); }, - preferences: preferences, ), ), )); @@ -437,11 +438,11 @@ void main() { testWidgets('Change IP address mode', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), preferences: preferences, onIPAddressModeChanged: (mode) { fakeSettings.changeIPAddressMode(); @@ -482,17 +483,17 @@ void main() { testWidgets('Change IP address', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onIPAddressChanged: (data) async { fakeSettings.changeIPAddress(); await preferences.setString(PrefKeys.ipAddress, data!); }, - preferences: preferences, ), ), )); @@ -513,18 +514,18 @@ void main() { testWidgets('Change default period', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onDefaultPeriodChanged: (period) async { fakeSettings.changeDefaultPeriod(); await preferences.setDouble( PrefKeys.defaultPeriod, double.parse(period!)); }, - preferences: preferences, ), ), )); @@ -545,18 +546,18 @@ void main() { testWidgets('Change default graph period', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; - setupMockOfflineNT4(); await widgetTester.pumpWidget(MaterialApp( home: Scaffold( body: SettingsDialog( + ntConnection: createMockOfflineNT4(), + preferences: preferences, onDefaultGraphPeriodChanged: (period) async { fakeSettings.changeDefaultGraphPeriod(); await preferences.setDouble( PrefKeys.defaultGraphPeriod, double.parse(period!)); }, - preferences: preferences, ), ), )); diff --git a/test/widgets/tab_grid_test.dart b/test/widgets/tab_grid_test.dart index c0c21897..b25c7b8b 100644 --- a/test/widgets/tab_grid_test.dart +++ b/test/widgets/tab_grid_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:elastic_dashboard/services/field_images.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -45,9 +46,9 @@ void main() async { late String jsonString; late Map jsonData; + late SharedPreferences preferences; setUpAll(() async { - setupMockOfflineNT4(); await FieldImages.loadFields('assets/fields/'); String filePath = @@ -55,6 +56,9 @@ void main() async { jsonString = File(filePath).readAsStringSync(); jsonData = jsonDecode(jsonString); + + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); }); testWidgets('Tab grid loading (Tab 1)', (widgetTester) async { @@ -76,6 +80,8 @@ void main() async { body: ChangeNotifierProvider( create: (context) => TabGridModel(), child: TabGrid.fromJson( + ntConnection: createMockOfflineNT4(), + preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), @@ -123,6 +129,8 @@ void main() async { body: ChangeNotifierProvider( create: (context) => TabGridModel(), child: TabGrid.fromJson( + ntConnection: createMockOfflineNT4(), + preferences: preferences, jsonData: jsonData['tabs'][1]['grid_layout'], onAddWidgetPressed: () {}, ), @@ -164,6 +172,8 @@ void main() async { create: (context) => TabGridModel(), child: TabGrid.fromJson( key: GlobalKey(), + ntConnection: createMockOfflineNT4(), + preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), @@ -240,6 +250,8 @@ void main() async { create: (context) => TabGridModel(), child: TabGrid.fromJson( key: GlobalKey(), + ntConnection: createMockOfflineNT4(), + preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), From 6655d8c6850181e381e074d86ccc62285b3ab115 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 18 May 2024 17:28:38 -0400 Subject: [PATCH 07/28] Began adding unit tests to nt widgets --- lib/services/nt_connection.dart | 5 + lib/services/nt_widget_builder.dart | 1 + .../nt_widgets/multi-topic/camera_stream.dart | 6 +- .../multi-topic/combo_box_chooser.dart | 9 +- test/test_util.dart | 70 +++++++- .../multi-topic/accelerometer_test.dart | 94 ++++++++++ .../multi-topic/basic_swerve_drive_test.dart | 106 ++++++++++++ .../multi-topic/camera_stream_test.dart | 141 +++++++++++++++ .../multi-topic/combo_box_chooser_test.dart | 162 ++++++++++++++++++ .../command_scheduler_widget_test.dart | 131 ++++++++++++++ .../multi-topic/command_widget_test.dart | 124 ++++++++++++++ .../multi-topic/differential_drive_test.dart | 122 +++++++++++++ 12 files changed, 958 insertions(+), 13 deletions(-) create mode 100644 test/widgets/nt_widgets/multi-topic/accelerometer_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/basic_swerve_drive_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/camera_stream_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/combo_box_chooser_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/command_scheduler_widget_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/command_widget_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/differential_drive_test.dart diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index 877b37be..6d967e49 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -180,4 +180,9 @@ class NTConnection { void updateDataFromTopic(NT4Topic topic, dynamic data) { _ntClient.addSample(topic, data); } + + @visibleForTesting + void updateDataFromTopicName(String topic, dynamic data) { + _ntClient.addSampleFromName(topic, data); + } } diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 2daa229c..f94480de 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -147,6 +147,7 @@ class NTWidgetBuilder { SwerveDriveWidget.widgetType: BasicSwerveModel.fromJson, CameraStreamWidget.widgetType: CameraStreamModel.fromJson, ComboBoxChooser.widgetType: ComboBoxChooserModel.fromJson, + 'String Chooser': ComboBoxChooserModel.fromJson, CommandSchedulerWidget.widgetType: CommandSchedulerModel.fromJson, CommandWidget.widgetType: CommandModel.fromJson, DifferentialDrive.widgetType: DifferentialDriveModel.fromJson, diff --git a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart index bd291a84..1aec534a 100644 --- a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart +++ b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart @@ -1,11 +1,11 @@ -import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:dot_cast/dot_cast.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/widgets/custom_loading_indicator.dart'; +import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/mjpeg.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; @@ -298,7 +298,7 @@ class CameraStreamWidget extends NTWidget { Text( (model.ntConnection.isNT4Connected) ? 'Waiting for Camera Stream connection...' - : 'Waiting for Network Tables Connection...', + : 'Waiting for Network Tables connection...', textAlign: TextAlign.center, ), ], diff --git a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart index 2f837b70..7da578b6 100644 --- a/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart +++ b/lib/widgets/nt_widgets/multi-topic/combo_box_chooser.dart @@ -19,7 +19,14 @@ class ComboBoxChooserModel extends NTWidgetModel { final TextEditingController _searchController = TextEditingController(); - String? selectedChoice; + String? _selectedChoice; + + String? get selectedChoice => _selectedChoice; + + set selectedChoice(value) { + _selectedChoice = value; + refresh(); + } StringChooserData? previousData; diff --git a/test/test_util.dart b/test/test_util.dart index 0b77f17a..24e738ed 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -40,30 +40,42 @@ MockNTConnection createMockOfflineNT4() { when(mockNT4Connection.subscribe(any)).thenReturn(mockSubscription); - when(mockNT4Connection.getTopicFromName(any)) - .thenReturn(NT4Topic(name: '', type: NT4TypeStr.kString, properties: {})); + when(mockNT4Connection.getTopicFromName(any)).thenReturn(null); return mockNT4Connection; } -MockNTConnection createMockOnlineNT4() { +MockNTConnection createMockOnlineNT4({ + List? virtualTopics, + Map? virtualValues, +}) { HttpOverrides.global = null; final mockNT4Connection = MockNTConnection(); final mockSubscription = MockNT4Subscription(); - when(mockNT4Connection.announcedTopics()).thenReturn({ - 1: NT4Topic( + virtualTopics ??= [ + NT4Topic( name: '/SmartDashboard/Test Value 1', type: NT4TypeStr.kInt, properties: {}, ), - 2: NT4Topic( + NT4Topic( name: '/SmartDashboard/Test Value 2', type: NT4TypeStr.kFloat32, properties: {}, ), - }); + ]; + + virtualValues ??= {}; + + Map virtualTopicsMap = {}; + + for (int i = 0; i < virtualTopics.length; i++) { + virtualTopicsMap.addAll({i + 1: virtualTopics[i]}); + } + + when(mockNT4Connection.announcedTopics()).thenReturn(virtualTopicsMap); when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); @@ -83,8 +95,48 @@ MockNTConnection createMockOnlineNT4() { when(mockNT4Connection.subscribe(any)).thenReturn(mockSubscription); - when(mockNT4Connection.getTopicFromName(any)) - .thenReturn(NT4Topic(name: '', type: NT4TypeStr.kString, properties: {})); + when(mockNT4Connection.getTopicFromName(any)).thenReturn(null); + + when(mockNT4Connection.publishNewTopic(any, any)).thenAnswer((invocation) { + NT4Topic newTopic = NT4Topic( + name: invocation.positionalArguments[0], + type: invocation.positionalArguments[1], + properties: {}); + + virtualTopicsMap[virtualTopicsMap.length] = newTopic; + return newTopic; + }); + + when(mockNT4Connection.updateDataFromTopic(any, any)) + .thenAnswer((invocation) { + NT4Topic topic = invocation.positionalArguments[0]; + Object? data = invocation.positionalArguments[1]; + + virtualValues![topic.name] = data; + }); + + when(mockNT4Connection.updateDataFromTopicName(any, any)) + .thenAnswer((invocation) { + String topic = invocation.positionalArguments[0]; + Object? data = invocation.positionalArguments[1]; + + virtualValues![topic] = data; + }); + + for (NT4Topic topic in virtualTopics) { + MockNT4Subscription topicSubscription = MockNT4Subscription(); + + when(mockNT4Connection.getTopicFromName(topic.name)).thenReturn(topic); + + when(topicSubscription.periodicStream()) + .thenAnswer((_) => Stream.value(virtualValues?[topic.name])); + + when(mockNT4Connection.getLastAnnouncedValue(topic.name)) + .thenAnswer((_) => virtualValues?[topic.name]); + + when(mockNT4Connection.subscribe(topic.name, any)) + .thenReturn(topicSubscription); + } return mockNT4Connection; } diff --git a/test/widgets/nt_widgets/multi-topic/accelerometer_test.dart b/test/widgets/nt_widgets/multi-topic/accelerometer_test.dart new file mode 100644 index 00000000..61ae3736 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/accelerometer_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/accelerometer.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map accelerometerJson = { + 'topic': 'Test/Test Accelerometer', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Test Accelerometer/Value', + type: NT4TypeStr.kFloat32, + properties: {}, + ) + ], + virtualValues: { + 'Test/Test Accelerometer/Value': 0.50, + }, + ); + }); + + test('Creating accelerometer from json', () { + NTWidgetModel accelerometerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Accelerometer', + accelerometerJson, + ); + + expect(accelerometerModel.type, 'Accelerometer'); + expect(accelerometerModel.runtimeType, AccelerometerModel); + + expect((accelerometerModel as AccelerometerModel).valueTopic, + 'Test/Test Accelerometer/Value'); + }); + + test('Saving accelerometer to json', () { + AccelerometerModel accelerometerModel = AccelerometerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Test Accelerometer', + period: 0.100, + ); + + expect(accelerometerModel.toJson(), accelerometerJson); + }); + + testWidgets('Accelerometer widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel accelerometerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Accelerometer', + accelerometerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: accelerometerModel, + child: const AccelerometerWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('0.50 g'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/basic_swerve_drive_test.dart b/test/widgets/nt_widgets/multi-topic/basic_swerve_drive_test.dart new file mode 100644 index 00000000..8bdd0759 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/basic_swerve_drive_test.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/basic_swerve_drive.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late SharedPreferences preferences; + late NTConnection ntConnection; + + final Map swerveJson = { + 'topic': 'Test/Basic Swerve Drive', + 'period': 0.100, + 'show_robot_rotation': false, + 'rotation_unit': 'Radians', + }; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4(); + }); + + test('Basic swerve model from json', () { + NTWidgetModel swerveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'SwerveDrive', + swerveJson, + ); + + expect(swerveModel.type, 'SwerveDrive'); + expect(swerveModel.runtimeType, BasicSwerveModel); + + if (swerveModel is! BasicSwerveModel) { + return; + } + + expect(swerveModel.showRobotRotation, isFalse); + expect(swerveModel.rotationUnit, 'Radians'); + + expect(swerveModel.frontLeftAngleTopic, + 'Test/Basic Swerve Drive/Front Left Angle'); + expect(swerveModel.frontLeftVelocityTopic, + 'Test/Basic Swerve Drive/Front Left Velocity'); + + expect(swerveModel.frontRightAngleTopic, + 'Test/Basic Swerve Drive/Front Right Angle'); + expect(swerveModel.frontRightVelocityTopic, + 'Test/Basic Swerve Drive/Front Right Velocity'); + + expect(swerveModel.backLeftAngleTopic, + 'Test/Basic Swerve Drive/Back Left Angle'); + expect(swerveModel.backLeftVelocityTopic, + 'Test/Basic Swerve Drive/Back Left Velocity'); + + expect(swerveModel.backRightAngleTopic, + 'Test/Basic Swerve Drive/Back Right Angle'); + expect(swerveModel.backRightVelocityTopic, + 'Test/Basic Swerve Drive/Back Right Velocity'); + }); + + test('Basic swerve model to json', () { + BasicSwerveModel swerveModel = BasicSwerveModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Basic Swerve Drive', + period: 0.100, + rotationUnit: 'Radians', + showRobotRotation: false, + ); + + expect(swerveModel.toJson(), swerveJson); + }); + + testWidgets('Basic swerve widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel swerveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, preferences, 'SwerveDrive', swerveJson); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: swerveModel, + child: const SwerveDriveWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(CustomPaint), findsWidgets); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/camera_stream_test.dart b/test/widgets/nt_widgets/multi-topic/camera_stream_test.dart new file mode 100644 index 00000000..b5ac84ca --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/camera_stream_test.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/custom_loading_indicator.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/camera_stream.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map cameraStreamJson = { + 'topic': 'Test/Camera Stream', + 'period': 0.100, + 'compression': 50, + 'fps': 60, + 'resolution': [100.0, 100.0], + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4(); + }); + + test('Camera stream from json', () { + NTWidgetModel cameraStreamModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Camera Stream', + cameraStreamJson, + ); + + expect(cameraStreamModel.type, 'Camera Stream'); + expect(cameraStreamModel.runtimeType, CameraStreamModel); + + if (cameraStreamModel is! CameraStreamModel) { + return; + } + + expect(cameraStreamModel.fps, 60); + expect(cameraStreamModel.quality, 50); + expect(cameraStreamModel.resolution, const Size(100.0, 100.0)); + + expect(cameraStreamModel.getUrlWithParameters('0.0.0.0'), + '0.0.0.0?resolution=100x100&fps=60&compression=50'); + + cameraStreamModel.fps = null; + + expect(cameraStreamModel.getUrlWithParameters('0.0.0.0'), + '0.0.0.0?resolution=100x100&compression=50'); + + cameraStreamModel.resolution = const Size(0.0, 100); + + expect(cameraStreamModel.getUrlWithParameters('0.0.0.0'), + '0.0.0.0?compression=50'); + + cameraStreamModel.quality = null; + + expect(cameraStreamModel.getUrlWithParameters('0.0.0.0'), '0.0.0.0?'); + }); + + test('Camera stream to json', () { + CameraStreamModel cameraStreamModel = CameraStreamModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Camera Stream', + period: 0.100, + compression: 50, + fps: 60, + resolution: const Size(100.0, 100.0), + ); + + expect(cameraStreamModel.toJson(), cameraStreamJson); + }); + + testWidgets('Camera stream online widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel cameraStreamModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Camera Stream', + cameraStreamJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: cameraStreamModel, + child: const CameraStreamWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(CustomLoadingIndicator), findsOneWidget); + expect( + find.text('Waiting for Camera Stream connection...'), findsOneWidget); + }); + + testWidgets('Camera stream offline widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel cameraStreamModel = NTWidgetBuilder.buildNTModelFromJson( + createMockOfflineNT4(), + preferences, + 'Camera Stream', + cameraStreamJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: cameraStreamModel, + child: const CameraStreamWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(CustomLoadingIndicator), findsOneWidget); + expect( + find.text('Waiting for Network Tables connection...'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/combo_box_chooser_test.dart b/test/widgets/nt_widgets/multi-topic/combo_box_chooser_test.dart new file mode 100644 index 00000000..1632988b --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/combo_box_chooser_test.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/combo_box_chooser.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map comboBoxChooserJson = { + 'topic': 'Test/Combo Box Chooser', + 'period': 0.100, + 'sort_options': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Combo Box Chooser/options', + type: NT4TypeStr.kStringArr, + properties: {}), + NT4Topic( + name: 'Test/Combo Box Chooser/active', + type: NT4TypeStr.kString, + properties: {}), + NT4Topic( + name: 'Test/Combo Box Chooser/selected', + type: NT4TypeStr.kString, + properties: {}), + NT4Topic( + name: 'Test/Combo Box Chooser/default', + type: NT4TypeStr.kString, + properties: {}), + ], + virtualValues: { + 'Test/Combo Box Chooser/options': ['One', 'Two', 'Three'], + 'Test/Combo Box Chooser/active': 'Two', + }, + ); + }); + + test('Combo box chooser from json', () { + NTWidgetModel comboBoxChooserModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'ComboBox Chooser', + comboBoxChooserJson, + ); + + expect(comboBoxChooserModel.type, 'ComboBox Chooser'); + expect(comboBoxChooserModel.runtimeType, ComboBoxChooserModel); + + if (comboBoxChooserModel is! ComboBoxChooserModel) { + return; + } + + expect(comboBoxChooserModel.sortOptions, isTrue); + }); + + test('Combo box chooser alias name', () { + NTWidgetModel comboBoxChooserModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'String Chooser', + comboBoxChooserJson, + ); + + expect(comboBoxChooserModel.type, 'ComboBox Chooser'); + expect(comboBoxChooserModel.runtimeType, ComboBoxChooserModel); + + if (comboBoxChooserModel is! ComboBoxChooserModel) { + return; + } + + expect(comboBoxChooserModel.sortOptions, isTrue); + }); + + test('Combo box chooser to json', () { + ComboBoxChooserModel comboBoxChooserModel = ComboBoxChooserModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Combo Box Chooser', + period: 0.100, + sortOptions: true, + ); + + expect(comboBoxChooserModel.toJson(), comboBoxChooserJson); + }); + + testWidgets('Combo box chooser widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel comboBoxChooserModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'ComboBox Chooser', + comboBoxChooserJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: comboBoxChooserModel, + child: const ComboBoxChooser(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(DropdownButton2), findsOneWidget); + expect(find.text('One'), findsNothing); + expect(find.text('Two'), findsOneWidget); + expect(find.text('Three'), findsNothing); + expect( + (comboBoxChooserModel as ComboBoxChooserModel).selectedChoice, 'Two'); + expect(find.byIcon(Icons.check), findsOneWidget); + + await widgetTester.tap(find.byType(DropdownButton2)); + await widgetTester.pumpAndSettle(); + + expect(find.text('One'), findsOneWidget); + expect(find.text('Two'), findsNWidgets(2)); + expect(find.text('Three'), findsOneWidget); + + await widgetTester.tap(find.text('One')); + await widgetTester.pumpAndSettle(); + + expect(find.text('One'), findsOneWidget); + expect(find.text('Two'), findsNothing); + expect(find.text('Three'), findsNothing); + + expect(comboBoxChooserModel.selectedChoice, 'One'); + expect(find.byIcon(Icons.priority_high), findsOneWidget); + + ntConnection.updateDataFromTopicName( + comboBoxChooserModel.activeTopicName, 'One'); + + comboBoxChooserModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.priority_high), findsNothing); + expect(find.byIcon(Icons.check), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/command_scheduler_widget_test.dart b/test/widgets/nt_widgets/multi-topic/command_scheduler_widget_test.dart new file mode 100644 index 00000000..27030bb6 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/command_scheduler_widget_test.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/command_scheduler.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map commandSchedulerJson = { + 'topic': 'Test/Command Scheduler', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Command Scheduler/Names', + type: NT4TypeStr.kStringArr, + properties: {}), + NT4Topic( + name: 'Test/Command Scheduler/Ids', + type: NT4TypeStr.kIntArr, + properties: {}), + NT4Topic( + name: 'Test/Command Scheduler/Cancel', + type: NT4TypeStr.kIntArr, + properties: {}), + ], + virtualValues: { + 'Test/Command Scheduler/Names': ['Command 1', 'Command 2'], + 'Test/Command Scheduler/Ids': [1, 2], + }, + ); + }); + + test('Command scheduler from json', () { + NTWidgetModel commandSchedulerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Scheduler', + commandSchedulerJson, + ); + + expect(commandSchedulerModel.type, 'Scheduler'); + expect(commandSchedulerModel.runtimeType, CommandSchedulerModel); + }); + + test('Command scheduler to json', () { + CommandSchedulerModel commandSchedulerModel = CommandSchedulerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Command Scheduler', + period: 0.100, + ); + + expect(commandSchedulerModel.toJson(), commandSchedulerJson); + }); + + testWidgets('Command scheduler widget', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel commandSchedulerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Scheduler', + commandSchedulerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: commandSchedulerModel, + child: const CommandSchedulerWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(ListView), findsOneWidget); + expect(find.byType(ListTile), findsNWidgets(2)); + + expect(find.text('Command 1'), findsOneWidget); + expect(find.text('Command 2'), findsOneWidget); + + expect(find.text('ID: 1'), findsOneWidget); + expect(find.text('ID: 2'), findsOneWidget); + + expect(find.byIcon(Icons.cancel_outlined), findsNWidgets(2)); + + await widgetTester.tap(find.byIcon(Icons.cancel_outlined).first); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Command Scheduler/Cancel'), + [1]); + + ntConnection.updateDataFromTopicName('Test/Command Scheduler/Ids', [2]); + ntConnection + .updateDataFromTopicName('Test/Command Scheduler/Names', ['Command 2']); + + commandSchedulerModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.byType(ListTile), findsOneWidget); + expect(find.text('Command 1'), findsNothing); + expect(find.text('ID: 1'), findsNothing); + + await widgetTester.tap(find.byIcon(Icons.cancel_outlined)); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Command Scheduler/Cancel'), + [1, 2]); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/command_widget_test.dart b/test/widgets/nt_widgets/multi-topic/command_widget_test.dart new file mode 100644 index 00000000..87430a5b --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/command_widget_test.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/command_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map commandWidgetJson = { + 'topic': 'Test/Command', + 'period': 0.100, + 'show_type': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Command/running', + type: NT4TypeStr.kBool, + properties: {}, + ), + NT4Topic( + name: 'Test/Command/name', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Commnad/running': false, + 'Test/Command/name': 'Test Command', + }, + ); + }); + + test('Command widget from json', () { + NTWidgetModel commandModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Command', + commandWidgetJson, + ); + + expect(commandModel.type, 'Command'); + expect(commandModel.runtimeType, CommandModel); + + if (commandModel is! CommandModel) { + return; + } + + expect(commandModel.showType, isTrue); + }); + + test('Command widget to json', () { + CommandModel commandModel = CommandModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Command', + period: 0.100, + showType: true, + ); + + expect(commandModel.toJson(), commandWidgetJson); + }); + + testWidgets('Command widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel commandModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Command', + commandWidgetJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: commandModel, + child: const CommandWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Command'), findsOneWidget); + expect(find.text('Type: Test Command'), findsOneWidget); + + await widgetTester.tap(find.text('Command')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Command/running'), isTrue); + + commandModel.refresh(); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.text('Command')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Command/running'), isFalse); + + (commandModel as CommandModel).showType = false; + await widgetTester.pumpAndSettle(); + + expect(find.text('Type: Test Command'), findsNothing); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/differential_drive_test.dart b/test/widgets/nt_widgets/multi-topic/differential_drive_test.dart new file mode 100644 index 00000000..38d3e524 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/differential_drive_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/differential_drive.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map differentialDriveJson = { + 'topic': 'Test/Differential Drive', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Differential Drive/Left Motor Speed', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Differential Drive/Right Motor Speed', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Differential Drive/Left Motor Speed': 0.50, + 'Test/Differential Drive/Right Motor Speed': 0.50, + }, + ); + }); + + test('Differential drive from json', () { + NTWidgetModel differentialDriveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'DifferentialDrive', + differentialDriveJson, + ); + + expect(differentialDriveModel.type, 'DifferentialDrive'); + expect(differentialDriveModel.runtimeType, DifferentialDriveModel); + }); + + test('Differential drive to json', () { + DifferentialDriveModel differentialDriveModel = DifferentialDriveModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Differential Drive', + period: 0.100, + ); + + expect(differentialDriveModel.toJson(), differentialDriveJson); + }); + + testWidgets('Differential drive widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel differentialDriveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'DifferentialDrive', + differentialDriveJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: differentialDriveModel, + child: const DifferentialDrive(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(CustomPaint), findsWidgets); + expect(find.byType(SfLinearGauge), findsNWidgets(2)); + expect(find.byType(LinearShapePointer), findsNWidgets(2)); + + await widgetTester.drag( + find.byType(LinearShapePointer).first, const Offset(0.0, 200.0)); + await widgetTester.pumpAndSettle(); + + expect( + ntConnection + .getLastAnnouncedValue('Test/Differential Drive/Left Motor Speed'), + isNot(0.50)); + expect( + ntConnection + .getLastAnnouncedValue('Test/Differential Drive/Right Motor Speed'), + 0.50); + + await widgetTester.drag( + find.byType(LinearShapePointer).last, const Offset(0.0, 300.0)); + await widgetTester.pumpAndSettle(); + + expect( + ntConnection + .getLastAnnouncedValue('Test/Differential Drive/Right Motor Speed'), + isNot(0.50)); + }); +} From 6f4a20b5f1b010a82d33e740facb2ec93d2b1df7 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Tue, 21 May 2024 21:16:18 -0400 Subject: [PATCH 08/28] Manage subscription sharing through nt connection wrapper --- lib/services/nt4_client.dart | 97 ++++++++------------------------- lib/services/nt_connection.dart | 54 +++++++++++++++++- test/services/nt_test.dart | 52 ++++++++++-------- 3 files changed, 101 insertions(+), 102 deletions(-) diff --git a/lib/services/nt4_client.dart b/lib/services/nt4_client.dart index 1701f428..00ea3dba 100644 --- a/lib/services/nt4_client.dart +++ b/lib/services/nt4_client.dart @@ -58,8 +58,6 @@ class NT4Subscription { final NT4SubscriptionOptions options; final int uid; - int useCount = 0; - Object? currentValue; int timestamp = 0; @@ -153,7 +151,7 @@ class NT4Subscription { other.options == options; @override - int get hashCode => Object.hashAllUnordered([topic, options]); + int get hashCode => Object.hashAll([topic, options]); } class NT4SubscriptionOptions { @@ -189,7 +187,7 @@ class NT4SubscriptionOptions { @override int get hashCode => - Object.hashAllUnordered([periodicRateSeconds, all, topicsOnly, prefix]); + Object.hashAll([periodicRateSeconds, all, topicsOnly, prefix]); } class NT4Topic { @@ -326,22 +324,16 @@ class NT4Client { } } - NT4Subscription subscribe(String topic, [double period = 0.1]) { + NT4Subscription subscribe({ + required String topic, + NT4SubscriptionOptions options = const NT4SubscriptionOptions(), + }) { NT4Subscription newSub = NT4Subscription( topic: topic, uid: getNewSubUID(), - options: NT4SubscriptionOptions(periodicRateSeconds: period), + options: options, ); - if (_subscribedTopics.contains(newSub)) { - NT4Subscription subscription = _subscribedTopics.lookup(newSub)!; - subscription.useCount++; - - return subscription; - } - - newSub.useCount++; - _subscriptions[newSub.uid] = newSub; _subscribedTopics.add(newSub); _wsSubscribe(newSub); @@ -355,73 +347,28 @@ class NT4Client { return newSub; } - NT4Subscription subscribeAllSamples(String topic, [double period = 0.1]) { - NT4Subscription newSub = NT4Subscription( - topic: topic, - uid: getNewSubUID(), - options: const NT4SubscriptionOptions(all: true), - ); - - if (_subscribedTopics.contains(newSub)) { - NT4Subscription subscription = _subscribedTopics.lookup(newSub)!; - subscription.useCount++; - - return subscription; - } - - newSub.useCount++; - - _subscriptions[newSub.uid] = newSub; - _wsSubscribe(newSub); - return newSub; - } - - NT4Subscription subscribeTopicsOnly(String topic) { - NT4Subscription newSub = NT4Subscription( - topic: topic, - uid: getNewSubUID(), - options: const NT4SubscriptionOptions(topicsOnly: true), - ); - - if (_subscribedTopics.contains(newSub)) { - NT4Subscription subscription = _subscribedTopics.lookup(newSub)!; - subscription.useCount++; - - return subscription; - } - - newSub.useCount++; - - _subscriptions[newSub.uid] = newSub; - _wsSubscribe(newSub); - return newSub; - } - void unSubscribe(NT4Subscription sub) { - sub.useCount--; - - if (sub.useCount <= 0) { - _subscriptions.remove(sub.uid); - _subscribedTopics.remove(sub); - _wsUnsubscribe(sub); - - // If there are no other subscriptions that are in the same table/tree - if (!_subscribedTopics.any((element) => - element.topic.startsWith('${sub.topic}/') || - '${sub.topic}/'.startsWith(element.topic))) { - // If there are any topics associated with the table/tree, unpublish them - for (NT4Topic topic in _clientPublishedTopics.values.where((element) => - element.name.startsWith('${sub.topic}/') || - '${sub.topic}/'.startsWith(element.name))) { - Future(() => unpublishTopic(topic)); - } + _subscriptions.remove(sub.uid); + _subscribedTopics.remove(sub); + _wsUnsubscribe(sub); + + // If there are no other subscriptions that are in the same table/tree + if (!_subscribedTopics.any((element) => + element.topic.startsWith('${sub.topic}/') || + sub.topic.startsWith('${element.topic}/') || + sub.topic == element.topic)) { + // If there are any topics associated with the table/tree, unpublish them + for (NT4Topic topic in _clientPublishedTopics.values.where((element) => + element.name.startsWith('${sub.topic}/') || + sub.topic.startsWith('${element.name}/') || + sub.topic == element.name)) { + Future(() => unpublishTopic(topic)); } } } void clearAllSubscriptions() { for (NT4Subscription sub in _subscriptions.values) { - sub.useCount = 0; unSubscribe(sub); } _subscriptions.clear(); diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index 6d967e49..9ddcc0a7 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -3,6 +3,11 @@ import 'package:flutter/foundation.dart'; import 'package:elastic_dashboard/services/ds_interop.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; +typedef SubscriptionIdentification = ({ + String topic, + NT4SubscriptionOptions options +}); + class NTConnection { late NT4Client _ntClient; late DSInteropClient _dsClient; @@ -18,6 +23,15 @@ class NTConnection { bool get isDSConnected => _dsConnected; DSInteropClient get dsClient => _dsClient; + @visibleForTesting + List get subscriptions => subscriptionUseCount.keys.toList(); + + @visibleForTesting + String get serverBaseAddress => _ntClient.serverBaseAddress; + + Map subscriptionMap = {}; + Map subscriptionUseCount = {}; + NTConnection(String ipAddress) { nt4Connect(ipAddress); } @@ -41,7 +55,10 @@ class NTConnection { }); // Allows all published topics to be announced - _ntClient.subscribeTopicsOnly('/'); + _ntClient.subscribe( + topic: '/', + options: const NT4SubscriptionOptions(topicsOnly: true), + ); } void dsClientConnect( @@ -138,11 +155,42 @@ class NTConnection { } NT4Subscription subscribe(String topic, [double period = 0.1]) { - return _ntClient.subscribe(topic, period); + NT4SubscriptionOptions subscriptionOptions = + NT4SubscriptionOptions(periodicRateSeconds: period); + + int hashCode = Object.hash(topic, subscriptionOptions); + + if (subscriptionMap.containsKey(hashCode)) { + NT4Subscription existingSubscription = subscriptionMap[hashCode]!; + subscriptionUseCount.update(existingSubscription, (value) => value + 1); + + return existingSubscription; + } + + NT4Subscription newSubscription = + _ntClient.subscribe(topic: topic, options: subscriptionOptions); + + subscriptionMap[hashCode] = newSubscription; + subscriptionUseCount[newSubscription] = 1; + + return newSubscription; } void unSubscribe(NT4Subscription subscription) { - _ntClient.unSubscribe(subscription); + if (!subscriptionUseCount.containsKey(subscription)) { + _ntClient.unSubscribe(subscription); + return; + } + + int hashCode = Object.hash(subscription.topic, subscription.options); + + subscriptionUseCount.update(subscription, (value) => value - 1); + + if (subscriptionUseCount[subscription]! <= 0) { + subscriptionMap.remove(hashCode); + subscriptionUseCount.remove(subscription); + _ntClient.unSubscribe(subscription); + } } NT4Topic? getTopicFromSubscription(NT4Subscription subscription) { diff --git a/test/services/nt_test.dart b/test/services/nt_test.dart index 7710761e..c7189ec6 100644 --- a/test/services/nt_test.dart +++ b/test/services/nt_test.dart @@ -1,61 +1,65 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; void main() { test('NT4 Client', () { - bool connected = false; + NTConnection ntConnection = NTConnection('10.3.5.32'); - NT4Client client = NT4Client( - serverBaseAddress: '10.3.53.2', - onConnect: () => connected = true, - onDisconnect: () => connected = false, - ); - - expect(connected, false); + expect(ntConnection.isNT4Connected, false); // Subscribing NT4Subscription subscription1 = - client.subscribe('/SmartDashboard/Test Number'); + ntConnection.subscribe('/SmartDashboard/Test Number'); - expect(client.subscriptions.length, greaterThanOrEqualTo(1)); + expect(ntConnection.subscriptions.length, greaterThanOrEqualTo(1)); - expect(client.lastAnnouncedValues.isEmpty, true); + expect(ntConnection.getLastAnnouncedValue('/SmartDashboard/Test Number'), + isNull); // Publishing and adding to the last announced values - client.addSample( + ntConnection.updateDataFromSubscription(subscription1, 3.53); + + expect(ntConnection.getLastAnnouncedValue('/SmartDashboard/Test Number'), + isNull); + + ntConnection.updateDataFromTopic( NT4Topic( name: '/SmartDashboard/Test Number', type: NT4TypeStr.kFloat32, properties: {}), 3.53); - expect(client.lastAnnouncedValues.isEmpty, false); + expect(ntConnection.getLastAnnouncedValue('/SmartDashboard/Test Number'), + 3.53); expect(subscription1.currentValue != null, true); NT4Subscription subscription2 = - client.subscribe('/SmartDashboard/Test Number'); + ntConnection.subscribe('/SmartDashboard/Test Number'); // If the subscriptions are shared - expect(client.subscribedTopics.length, 1); + expect(ntConnection.subscriptions.length, 1); - client.unSubscribe(subscription1); + ntConnection.unSubscribe(subscription1); - expect(client.subscribedTopics.length, 1); + expect(ntConnection.subscriptions.length, 1); - client.unSubscribe(subscription2); + ntConnection.unSubscribe(subscription2); - expect(client.subscribedTopics.length, 0); + expect(ntConnection.subscriptions.length, 0); // Changing ip address - expect(client.lastAnnouncedValues.isEmpty, false); + expect(ntConnection.getLastAnnouncedValue('/SmartDashboard/Test Number'), + 3.53); - client.setServerBaseAddreess('10.26.01.2'); + ntConnection.changeIPAddress('10.30.15.2'); - expect(client.serverBaseAddress, '10.26.01.2'); + expect(ntConnection.serverBaseAddress, '10.30.15.2'); - expect(client.announcedTopics.isEmpty, true); - expect(client.lastAnnouncedValues.isEmpty, false); + expect(ntConnection.announcedTopics().length, 0); + expect(ntConnection.getLastAnnouncedValue('/SmartDashboard/Test Number'), + 3.53); }); } From 049a4bb5bed023ae7c8e588987bed02c21870f94 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 15:58:17 -0400 Subject: [PATCH 09/28] Added encoder widget test --- .../multi-topic/encoder_widget_test.dart | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart diff --git a/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart b/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart new file mode 100644 index 00000000..44d24912 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart @@ -0,0 +1,118 @@ +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/encoder_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map encoderWidgetJson = { + 'topic': 'Test/Encoder', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Encoder/Distance', + type: NT4TypeStr.kFloat32, + properties: {}), + NT4Topic( + name: 'Test/Encoder/Speed', + type: NT4TypeStr.kFloat32, + properties: {}), + ], + virtualValues: { + 'Test/Encoder/Distance': 5.50, + 'Test/Encoder/Speed': -10.0, + }, + ); + }); + + test('Encoder from json', () { + NTWidgetModel encoderWidgetModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Encoder', + encoderWidgetJson, + ); + + expect(encoderWidgetModel.type, 'Encoder'); + expect(encoderWidgetModel.runtimeType, EncoderModel); + }); + + test('Encoder alias name', () { + NTWidgetModel encoderWidgetModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Quadrature Encoder', + encoderWidgetJson, + ); + + expect(encoderWidgetModel.type, 'Encoder'); + expect(encoderWidgetModel.runtimeType, EncoderModel); + }); + + test('Encoder to json', () { + EncoderModel encoderWidgetModel = EncoderModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Encoder', + period: 0.100, + ); + + expect(encoderWidgetModel.toJson(), encoderWidgetJson); + }); + + testWidgets('Encoder widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel encoderWidgetModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Encoder', + encoderWidgetJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: encoderWidgetModel, + child: const EncoderWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Distance'), findsOneWidget); + expect(find.text('Speed'), findsOneWidget); + + expect( + find.descendant( + of: find.byType(SelectableText), + matching: find.textContaining('5.50')), + findsOneWidget); + expect( + find.descendant( + of: find.byType(SelectableText), + matching: find.textContaining('-10.00')), + findsOneWidget); + }); +} From 9b3da1a6ca591b628c170e5b3cfdff428b0c316e Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 16:43:25 -0400 Subject: [PATCH 10/28] Added FMSInfo test --- .../multi-topic/encoder_widget_test.dart | 11 +- .../nt_widgets/multi-topic/fms_info_test.dart | 309 ++++++++++++++++++ 2 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 test/widgets/nt_widgets/multi-topic/fms_info_test.dart diff --git a/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart b/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart index 44d24912..742b228d 100644 --- a/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart +++ b/test/widgets/nt_widgets/multi-topic/encoder_widget_test.dart @@ -1,13 +1,14 @@ -import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; -import 'package:elastic_dashboard/services/nt_widget_builder.dart'; -import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/encoder_widget.dart'; -import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; import 'package:flutter/material.dart'; + import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/encoder_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; import '../../../test_util.dart'; void main() { diff --git a/test/widgets/nt_widgets/multi-topic/fms_info_test.dart b/test/widgets/nt_widgets/multi-topic/fms_info_test.dart new file mode 100644 index 00000000..d664cfe7 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/fms_info_test.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/fms_info.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map fmsInfoJson = { + 'topic': 'Test/FMSInfo', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + NTConnection createNTConnection({ + String eventName = '', + bool redAlliance = false, + int matchNumber = 0, + int matchType = 0, + int replayNumber = 0, + bool enabled = false, + bool auto = false, + bool test = false, + bool estop = false, + bool fmsAttached = false, + bool dsAttached = false, + }) { + int fmsControlData = 0; + if (enabled) { + fmsControlData |= FMSInfo.ENABLED_FLAG; + } + if (auto) { + fmsControlData |= FMSInfo.AUTO_FLAG; + } + if (test) { + fmsControlData |= FMSInfo.TEST_FLAG; + } + if (estop) { + fmsControlData |= FMSInfo.EMERGENCY_STOP_FLAG; + } + if (fmsAttached) { + fmsControlData |= FMSInfo.FMS_ATTACHED_FLAG; + } + if (dsAttached) { + fmsControlData |= FMSInfo.DS_ATTACHED_FLAG; + } + + return createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/FMSInfo/EventName', + type: NT4TypeStr.kString, + properties: {}, + ), + NT4Topic( + name: 'Test/FMSInfo/FMSControlData', + type: NT4TypeStr.kInt, + properties: {}, + ), + NT4Topic( + name: 'Test/FMSInfo/IsRedAlliance', + type: NT4TypeStr.kBool, + properties: {}, + ), + NT4Topic( + name: 'Test/FMSInfo/MatchNumber', + type: NT4TypeStr.kInt, + properties: {}, + ), + NT4Topic( + name: 'Test/FMSInfo/MatchType', + type: NT4TypeStr.kInt, + properties: {}, + ), + NT4Topic( + name: 'Test/FMSInfo/ReplayNumber', + type: NT4TypeStr.kInt, + properties: {}, + ), + ], + virtualValues: { + 'Test/FMSInfo/EventName': eventName, + 'Test/FMSInfo/FMSControlData': fmsControlData, + 'Test/FMSInfo/IsRedAlliance': redAlliance, + 'Test/FMSInfo/MatchNumber': matchNumber, + 'Test/FMSInfo/MatchType': matchType, + 'Test/FMSInfo/ReplayNumber': replayNumber, + }, + ); + } + + Future pushFMSInfoWidget( + WidgetTester widgetTester, NTConnection ntConnection) async { + NTWidgetModel fmsInfoModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, preferences, 'FMSInfo', fmsInfoJson); + + return widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: fmsInfoModel, + child: const FMSInfo(), + ), + ), + ), + ); + } + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createNTConnection( + eventName: 'CMPTX', + redAlliance: false, + matchNumber: 15, + matchType: 3, + replayNumber: 1, + enabled: true, + fmsAttached: true, + dsAttached: true, + ); + }); + + test('FMSInfo from json', () { + NTWidgetModel fmsInfoModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'FMSInfo', + fmsInfoJson, + ); + + expect(fmsInfoModel.type, 'FMSInfo'); + expect(fmsInfoModel.runtimeType, FMSInfoModel); + }); + + test('FMSInfo to json', () { + FMSInfoModel fmsInfoModel = FMSInfoModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/FMSInfo', + period: 0.100, + ); + + expect(fmsInfoModel.toJson(), fmsInfoJson); + }); + + testWidgets('FMSInfo CMPTX E15, Teleop Enabled', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await pushFMSInfoWidget(widgetTester, ntConnection); + + await widgetTester.pumpAndSettle(); + + expect(find.text('CMPTX Elimination match 15 (replay 1)'), findsOneWidget); + expect(find.text('DriverStation Connected'), findsOneWidget); + expect(find.text('FMS Connected'), findsOneWidget); + expect(find.byIcon(Icons.check), findsNWidgets(2)); + + expect(find.text('Robot State: Teleoperated'), findsOneWidget); + expect( + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color == Colors.blue.shade900), + findsOneWidget); + }); + + testWidgets('FMSInfo NYSU Q72, Auto Enabled', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await pushFMSInfoWidget( + widgetTester, + createNTConnection( + eventName: 'NYSU', + redAlliance: false, + matchNumber: 72, + matchType: 2, + replayNumber: 1, + enabled: true, + auto: true, + fmsAttached: true, + dsAttached: true, + )); + + await widgetTester.pumpAndSettle(); + + expect(find.text('NYSU Qualification match 72 (replay 1)'), findsOneWidget); + expect(find.text('DriverStation Connected'), findsOneWidget); + expect(find.text('FMS Connected'), findsOneWidget); + expect(find.byIcon(Icons.check), findsNWidgets(2)); + + expect(find.text('Robot State: Autonomous'), findsOneWidget); + expect( + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color == Colors.blue.shade900), + findsOneWidget); + }); + + testWidgets('FMSInfo NYLI2 P7, Estopped', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await pushFMSInfoWidget( + widgetTester, + createNTConnection( + eventName: 'NYLI2', + redAlliance: true, + matchNumber: 7, + matchType: 1, + replayNumber: 1, + estop: true, + fmsAttached: true, + dsAttached: true, + )); + + await widgetTester.pumpAndSettle(); + + expect(find.text('NYLI2 Practice match 7 (replay 1)'), findsOneWidget); + expect(find.text('DriverStation Connected'), findsOneWidget); + expect(find.text('FMS Connected'), findsOneWidget); + expect(find.byIcon(Icons.check), findsNWidgets(2)); + + expect(find.text('EMERGENCY STOPPED'), findsOneWidget); + expect(find.byType(CustomPaint), findsAtLeastNWidgets(2)); + expect( + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color == Colors.red.shade900), + findsOneWidget); + }); + + testWidgets('FMSInfo Unkown Match, test enabled', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await pushFMSInfoWidget( + widgetTester, + createNTConnection( + eventName: '', + redAlliance: true, + matchNumber: 0, + matchType: 0, + replayNumber: 0, + enabled: true, + test: true, + fmsAttached: false, + dsAttached: true, + )); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Unknown match 0'), findsOneWidget); + expect(find.text('DriverStation Connected'), findsOneWidget); + expect(find.text('FMS Disconnected'), findsOneWidget); + expect(find.byIcon(Icons.check), findsNWidgets(1)); + expect(find.byIcon(Icons.clear), findsNWidgets(1)); + + expect(find.text('Robot State: Test'), findsOneWidget); + expect( + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color == Colors.red.shade900), + findsOneWidget); + }); + + testWidgets('FMSInfo Unknown Match, everything disconnected', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + await pushFMSInfoWidget( + widgetTester, + createNTConnection( + eventName: '', + redAlliance: true, + matchNumber: 0, + matchType: 0, + replayNumber: 0, + )); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Unknown match 0'), findsOneWidget); + expect(find.text('DriverStation Disconnected'), findsOneWidget); + expect(find.text('FMS Disconnected'), findsOneWidget); + expect(find.byIcon(Icons.check), findsNWidgets(0)); + expect(find.byIcon(Icons.clear), findsNWidgets(2)); + + expect(find.text('Robot State: Disabled'), findsOneWidget); + expect( + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color == Colors.red.shade900), + findsOneWidget); + }); +} From a26d98832b7d95428cd4a9514506920f298ddc6f Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 16:48:15 -0400 Subject: [PATCH 11/28] Removed broken method --- lib/services/nt_connection.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index 9ddcc0a7..bade59a8 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -88,10 +88,6 @@ class NTConnection { _ntClient.removeTopicAnnounceListener(onUnannounce); } - void recallTopicAnnounceListeners() { - _ntClient.recallAnnounceListeners(); - } - Future? subscribeAndRetrieveData(String topic, {period = 0.1, timeout = const Duration(seconds: 2, milliseconds: 500)}) async { From ef6c48ee25a1ba14f0250535b1a62180a8593728 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 17:56:48 -0400 Subject: [PATCH 12/28] Added tests for gyro, motor controller, network alerts, and pid controller --- .../nt_widgets/multi-topic/gyro_test.dart | 101 +++++++++++ .../multi-topic/motor_controller_test.dart | 106 +++++++++++ .../multi-topic/network_alerts_test.dart | 114 ++++++++++++ .../multi-topic/pid_controller_test.dart | 168 ++++++++++++++++++ 4 files changed, 489 insertions(+) create mode 100644 test/widgets/nt_widgets/multi-topic/gyro_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/motor_controller_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/network_alerts_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/pid_controller_test.dart diff --git a/test/widgets/nt_widgets/multi-topic/gyro_test.dart b/test/widgets/nt_widgets/multi-topic/gyro_test.dart new file mode 100644 index 00000000..e110daf5 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/gyro_test.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/gyro.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map gyroJson = { + 'topic': 'Test/Gyro', + 'period': 0.100, + 'counter_clockwise_positive': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Gyro/Value', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Gyro/Value': 183.5, + }, + ); + }); + + test('Gyro from json', () { + NTWidgetModel gyroModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Gyro', + gyroJson, + ); + + expect(gyroModel.type, 'Gyro'); + expect(gyroModel.runtimeType, GyroModel); + + if (gyroModel is! GyroModel) { + return; + } + + expect(gyroModel.counterClockwisePositive, isTrue); + }); + + test('Gyro to json', () { + GyroModel gyroModel = GyroModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Gyro', + period: 0.100, + counterClockwisePositive: true, + ); + + expect(gyroModel.toJson(), gyroJson); + }); + + testWidgets('Gyro widget', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel gyroModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Gyro', + gyroJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: gyroModel, + child: const Gyro(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('176.50'), findsOneWidget); + expect(find.byType(SfRadialGauge), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/motor_controller_test.dart b/test/widgets/nt_widgets/multi-topic/motor_controller_test.dart new file mode 100644 index 00000000..306d9b10 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/motor_controller_test.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/motor_controller.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map motorControllerJson = { + 'topic': 'Test/Motor Controller', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Motor Controller/Value', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Motor Controller/Value': -0.5, + }, + ); + }); + + test('Motor controller from json', () { + NTWidgetModel motorControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Motor Controller', + motorControllerJson, + ); + + expect(motorControllerModel.type, 'Motor Controller'); + expect(motorControllerModel.runtimeType, MotorControllerModel); + }); + + test('Nidec brushless from json', () { + NTWidgetModel motorControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Nidec Brushless', + motorControllerJson, + ); + + expect(motorControllerModel.type, 'Motor Controller'); + expect(motorControllerModel.runtimeType, MotorControllerModel); + }); + + test('Motor controller to json', () { + MotorControllerModel motorControllerModel = MotorControllerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Motor Controller', + period: 0.100, + ); + + expect(motorControllerModel.toJson(), motorControllerJson); + }); + + testWidgets('Motor controller widget', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel motorControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Motor Controller', + motorControllerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: motorControllerModel, + child: const MotorController(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-0.50'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + expect(find.byType(LinearShapePointer), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/network_alerts_test.dart b/test/widgets/nt_widgets/multi-topic/network_alerts_test.dart new file mode 100644 index 00000000..7263ba11 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/network_alerts_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/network_alerts.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map networkAlertsJson = { + 'topic': 'Test/Alerts', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Alerts/errors', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + NT4Topic( + name: 'Test/Alerts/warnings', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + NT4Topic( + name: 'Test/Alerts/infos', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/Alerts/errors': ['Test Error 1', 'Test Error 2'], + 'Test/Alerts/warnings': ['Test Warning 1', 'Test Warning 2'], + 'Test/Alerts/infos': ['Test Info 1', 'Test Info 2'], + }, + ); + }); + + test('Network alerts from json', () { + NTWidgetModel networkAlertsModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Alerts', + networkAlertsJson, + ); + + expect(networkAlertsModel.type, 'Alerts'); + expect(networkAlertsModel.runtimeType, NetworkAlertsModel); + }); + + test('Network alerts to json', () { + NetworkAlertsModel networkAlertsModel = NetworkAlertsModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Alerts', + period: 0.100, + ); + + expect(networkAlertsModel.toJson(), networkAlertsJson); + }); + + testWidgets('Network alerts widget', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel networkAlertsModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Alerts', + networkAlertsJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: networkAlertsModel, + child: const NetworkAlerts(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.cancel), findsNWidgets(2)); + expect(find.byIcon(Icons.warning), findsNWidgets(2)); + expect(find.byIcon(Icons.info), findsNWidgets(2)); + + expect(find.text('Test Error 1'), findsOneWidget); + expect(find.text('Test Error 2'), findsOneWidget); + + expect(find.text('Test Warning 1'), findsOneWidget); + expect(find.text('Test Warning 2'), findsOneWidget); + + expect(find.text('Test Info 1'), findsOneWidget); + expect(find.text('Test Info 2'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/pid_controller_test.dart b/test/widgets/nt_widgets/multi-topic/pid_controller_test.dart new file mode 100644 index 00000000..7a7af810 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/pid_controller_test.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/pid_controller.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map pidControllerJson = { + 'topic': 'Test/PID Controller', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/PID Controller/p', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/PID Controller/i', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/PID Controller/d', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/PID Controller/setpoint', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/PID Controller/p': 0.0, + 'Test/PID Controller/i': 0.0, + 'Test/PID Controller/d': 0.0, + 'Test/PID Controller/setpoint': 0.0, + }, + ); + }); + + test('PID controller from json', () { + NTWidgetModel pidControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PIDController', + pidControllerJson, + ); + + expect(pidControllerModel.type, 'PIDController'); + expect(pidControllerModel.runtimeType, PIDControllerModel); + }); + + test('PID controller from alias name', () { + NTWidgetModel pidControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PID Controller', + pidControllerJson, + ); + + expect(pidControllerModel.type, 'PIDController'); + expect(pidControllerModel.runtimeType, PIDControllerModel); + }); + + test('PID controller to json', () { + PIDControllerModel pidControllerModel = PIDControllerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/PID Controller', + period: 0.100, + ); + + expect(pidControllerModel.toJson(), pidControllerJson); + }); + + testWidgets('PID controller widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel pidControllerModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PIDController', + pidControllerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: pidControllerModel, + child: const PIDControllerWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(TextField), findsNWidgets(4)); + expect(find.widgetWithText(TextField, 'kP'), findsOneWidget); + expect(find.widgetWithText(TextField, 'kI'), findsOneWidget); + expect(find.widgetWithText(TextField, 'kD'), findsOneWidget); + expect(find.widgetWithText(TextField, 'Setpoint'), findsOneWidget); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kP'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/p'), 0.0); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kI'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/i'), 0.0); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kD'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/d'), 0.0); + + await widgetTester.enterText( + find.widgetWithText(TextField, 'Setpoint'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/setpoint'), + 0.0); + + pidControllerModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.priority_high), findsOneWidget); + + expect( + find.widgetWithText(OutlinedButton, 'Publish Values'), findsOneWidget); + await widgetTester + .tap(find.widgetWithText(OutlinedButton, 'Publish Values')); + + pidControllerModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/p'), 0.1); + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/i'), 0.1); + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/d'), 0.1); + expect(ntConnection.getLastAnnouncedValue('Test/PID Controller/setpoint'), + 0.1); + + expect(find.byIcon(Icons.priority_high), findsNothing); + }); +} From 3d8dee3761ea1e29ef35d753795a2d1544e64e33 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 18:54:45 -0400 Subject: [PATCH 13/28] Added tests for pdp, profiled pid controller, and relay --- .../multi-topic/power_distribution_test.dart | 138 +++++++++++++++ .../profiled_pid_controller_test.dart | 167 ++++++++++++++++++ .../multi-topic/relay_widget_test.dart | 115 ++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 test/widgets/nt_widgets/multi-topic/power_distribution_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/profiled_pid_controller_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/relay_widget_test.dart diff --git a/test/widgets/nt_widgets/multi-topic/power_distribution_test.dart b/test/widgets/nt_widgets/multi-topic/power_distribution_test.dart new file mode 100644 index 00000000..f8ace9d6 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/power_distribution_test.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/power_distribution.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map powerDistributionJson = { + 'topic': 'Test/Power Distribution', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + List channelTopics = []; + Map virtualChannelValues = {}; + + for (int i = 0; i <= PowerDistributionModel.numberOfChannels; i++) { + channelTopics.add(NT4Topic( + name: 'Test/Power Distribution/Chan$i', + type: NT4TypeStr.kFloat32, + properties: {}, + )); + + virtualChannelValues.addAll({'Test/Power Distribution/Chan$i': 0.00}); + } + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Power Distribution/Voltage', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Power Distribution/TotalCurrent', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ...channelTopics, + ], + virtualValues: { + 'Test/Power Distribution/Voltage': 12.00, + 'Test/Power Distribution/TotalCurrent': 100.0, + ...virtualChannelValues, + }, + ); + }); + + test('Power distribution from json', () { + NTWidgetModel powerDistributionModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PowerDistribution', + powerDistributionJson, + ); + + expect(powerDistributionModel.type, 'PowerDistribution'); + expect(powerDistributionModel.runtimeType, PowerDistributionModel); + }); + + test('Power distribution from alias name', () { + NTWidgetModel powerDistributionModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PDP', + powerDistributionJson, + ); + + expect(powerDistributionModel.type, 'PowerDistribution'); + expect(powerDistributionModel.runtimeType, PowerDistributionModel); + }); + + test('Power distribution to json', () { + PowerDistributionModel powerDistributionModel = PowerDistributionModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Power Distribution', + period: 0.100, + ); + + expect(powerDistributionModel.toJson(), powerDistributionJson); + }); + + testWidgets('Power distribution widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel powerDistributionModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'PowerDistribution', + powerDistributionJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: powerDistributionModel, + child: const PowerDistribution(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Voltage'), findsOneWidget); + expect(find.text('12.00 V'), findsOneWidget); + + expect(find.text('Total Current'), findsOneWidget); + expect(find.text('100.00 A'), findsOneWidget); + + expect(find.text('00.00 A'), findsNWidgets(24)); + + for (int i = 0; i <= 9; i++) { + expect(find.text('Ch. $i '), findsOneWidget); + } + + for (int i = 10; i <= 23; i++) { + expect(find.text('Ch. $i'), findsOneWidget); + } + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/profiled_pid_controller_test.dart b/test/widgets/nt_widgets/multi-topic/profiled_pid_controller_test.dart new file mode 100644 index 00000000..f9f3168d --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/profiled_pid_controller_test.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map profiledPIDControllerJson = { + 'topic': 'Test/Profiled PID Controller', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Profiled PID Controller/p', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Profiled PID Controller/i', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Profiled PID Controller/d', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Profiled PID Controller/goal', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Profiled PID Controller/p': 0.0, + 'Test/Profiled PID Controller/i': 0.0, + 'Test/Profiled PID Controller/d': 0.0, + 'Test/Profiled PID Controller/goal': 0.0, + }, + ); + }); + + test('PID controller from json', () { + NTWidgetModel profiledPIDControllerModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'ProfiledPIDController', + profiledPIDControllerJson, + ); + + expect(profiledPIDControllerModel.type, 'ProfiledPIDController'); + expect(profiledPIDControllerModel.runtimeType, ProfiledPIDControllerModel); + }); + + test('Profiled PID controller to json', () { + ProfiledPIDControllerModel profiledPIDControllerModel = + ProfiledPIDControllerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Profiled PID Controller', + period: 0.100, + ); + + expect(profiledPIDControllerModel.toJson(), profiledPIDControllerJson); + }); + + testWidgets('Profiled PID controller widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel profiledPIDControllerModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'ProfiledPIDController', + profiledPIDControllerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: profiledPIDControllerModel, + child: const ProfiledPIDControllerWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(TextField), findsNWidgets(4)); + expect(find.widgetWithText(TextField, 'kP'), findsOneWidget); + expect(find.widgetWithText(TextField, 'kI'), findsOneWidget); + expect(find.widgetWithText(TextField, 'kD'), findsOneWidget); + expect(find.widgetWithText(TextField, 'Goal'), findsOneWidget); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kP'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/p'), + 0.0); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kI'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/i'), + 0.0); + + await widgetTester.enterText(find.widgetWithText(TextField, 'kD'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/d'), + 0.0); + + await widgetTester.enterText( + find.widgetWithText(TextField, 'Goal'), '0.100'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect( + ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/goal'), + 0.0); + + profiledPIDControllerModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.priority_high), findsOneWidget); + + expect( + find.widgetWithText(OutlinedButton, 'Publish Values'), findsOneWidget); + await widgetTester + .tap(find.widgetWithText(OutlinedButton, 'Publish Values')); + + profiledPIDControllerModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/p'), + 0.1); + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/i'), + 0.1); + expect(ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/d'), + 0.1); + expect( + ntConnection.getLastAnnouncedValue('Test/Profiled PID Controller/goal'), + 0.1); + + expect(find.byIcon(Icons.priority_high), findsNothing); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/relay_widget_test.dart b/test/widgets/nt_widgets/multi-topic/relay_widget_test.dart new file mode 100644 index 00000000..3fc070d2 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/relay_widget_test.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/relay_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map relayJson = { + 'topic': 'Test/Relay', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Relay/Value', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Relay/Value': 'Off', + }, + ); + }); + + test('Relay from json', () { + NTWidgetModel relayModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Relay', + relayJson, + ); + + expect(relayModel.type, 'Relay'); + expect(relayModel.runtimeType, RelayModel); + }); + + test('Relay to json', () { + RelayModel relayModel = RelayModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Relay', + period: 0.100, + ); + + expect(relayModel.toJson(), relayJson); + }); + + testWidgets('Relay widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel relayModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Relay', + relayJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: relayModel, + child: const RelayWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(ToggleButtons), findsOneWidget); + + expect(find.text('On'), findsOneWidget); + await widgetTester.tap(find.text('On')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Relay/Value'), 'On'); + + expect(find.text('Forward'), findsOneWidget); + await widgetTester.tap(find.text('Forward')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Relay/Value'), 'Forward'); + + expect(find.text('Reverse'), findsOneWidget); + await widgetTester.tap(find.text('Reverse')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Relay/Value'), 'Reverse'); + + expect(find.text('Off'), findsOneWidget); + await widgetTester.tap(find.text('Off')); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Relay/Value'), 'Off'); + }); +} From 41fe484750e52e6ef6dafdcd8a46d9fd761115b9 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 18:55:41 -0400 Subject: [PATCH 14/28] Added test for robot preferences and fixed search bug --- .../multi-topic/robot_preferences.dart | 9 +- .../multi-topic/robot_preferences_test.dart | 158 ++++++++++++++++++ 2 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 test/widgets/nt_widgets/multi-topic/robot_preferences_test.dart diff --git a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart index c1f7779a..ab8c49b4 100644 --- a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart +++ b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart @@ -196,12 +196,15 @@ class PreferenceSearch extends StatelessWidget { spaceBetweenSearchAndList: 15, filter: (query) { return preferenceTopicNames - .where((element) => - element.toLowerCase().contains(query.toLowerCase())) + .where((element) => element + .split('/') + .last + .toLowerCase() + .contains(query.toLowerCase())) .toList(); }, initialList: preferenceTopicNames, - builder: (displayedList, itemIndex, item) { + itemBuilder: (item) { TextEditingController? textController = preferenceTextControllers[item]; return _RobotPreference( diff --git a/test/widgets/nt_widgets/multi-topic/robot_preferences_test.dart b/test/widgets/nt_widgets/multi-topic/robot_preferences_test.dart new file mode 100644 index 00000000..9df07cc5 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/robot_preferences_test.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/robot_preferences.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map robotPreferencesJson = { + 'topic': 'Test/Preferences', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Preferences/Test Preference', + type: NT4TypeStr.kInt, + properties: {}, + ), + NT4Topic( + name: 'Test/Preferences/Preference 1', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Preferences/Preference 2', + type: NT4TypeStr.kBool, + properties: {}, + ), + NT4Topic( + name: 'Test/Preferences/Preference 3', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Preferences/Test Preference': 0, + 'Test/Preferences/Preference 1': 0.100, + 'Test/Preferences/Preference 2': false, + 'Test/Preferences/Preference 3': 'Original String' + }, + ); + }); + + test('Robot preferences from json', () { + NTWidgetModel preferencesModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'RobotPreferences', + robotPreferencesJson, + ); + + expect(preferencesModel.type, 'RobotPreferences'); + expect(preferencesModel.runtimeType, RobotPreferencesModel); + }); + + test('Robot preferences to json', () { + RobotPreferencesModel preferencesModel = RobotPreferencesModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Preferences', + period: 0.100, + ); + + expect(preferencesModel.toJson(), robotPreferencesJson); + }); + + testWidgets('Robot preferences widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel preferencesModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'RobotPreferences', + robotPreferencesJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: preferencesModel, + child: const RobotPreferences(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(TextField), findsNWidgets(5)); + + expect(find.widgetWithText(TextField, 'Test Preference'), findsOneWidget); + await widgetTester.enterText( + find.widgetWithText(TextField, 'Test Preference'), '1'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect( + ntConnection.getLastAnnouncedValue('Test/Preferences/Test Preference'), + 1); + + expect(find.widgetWithText(TextField, 'Preference 1'), findsOneWidget); + await widgetTester.enterText( + find.widgetWithText(TextField, 'Preference 1'), '0.250'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Preferences/Preference 1'), + 0.250); + + expect(find.widgetWithText(TextField, 'Preference 2'), findsOneWidget); + await widgetTester.enterText( + find.widgetWithText(TextField, 'Preference 2'), 'true'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Preferences/Preference 2'), + isTrue); + + expect(find.widgetWithText(TextField, 'Preference 3'), findsOneWidget); + await widgetTester.enterText( + find.widgetWithText(TextField, 'Preference 3'), 'Edited String'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + + expect(ntConnection.getLastAnnouncedValue('Test/Preferences/Preference 3'), + 'Edited String'); + + // Searching + final searchField = find.widgetWithText(TextField, 'Search'); + expect(searchField, findsOneWidget); + + await widgetTester.enterText(searchField, 'Preference'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(find.byType(TextField), findsNWidgets(5)); + + await widgetTester.enterText(searchField, 'Test'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(find.byType(TextField), findsNWidgets(2)); + }); +} From 893e4f81c0f331fa42578b107acc5e9cd9681223 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 19:39:03 -0400 Subject: [PATCH 15/28] Added remaining multi topic widget tests * Split button chooser * Subsystem * Three axis accelerometer * Ultrasonic * YAGSL swerve drive --- .../split_button_chooser_test.dart | 127 ++++++++++++++++++ .../multi-topic/subsystem_widget_test.dart | 97 +++++++++++++ .../three_axis_accelerometer_test.dart | 123 +++++++++++++++++ .../multi-topic/ultrasonic_test.dart | 92 +++++++++++++ .../multi-topic/yagsl_swerve_drive_test.dart | 90 +++++++++++++ 5 files changed, 529 insertions(+) create mode 100644 test/widgets/nt_widgets/multi-topic/split_button_chooser_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/subsystem_widget_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/three_axis_accelerometer_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/ultrasonic_test.dart create mode 100644 test/widgets/nt_widgets/multi-topic/yagsl_swerve_drive_test.dart diff --git a/test/widgets/nt_widgets/multi-topic/split_button_chooser_test.dart b/test/widgets/nt_widgets/multi-topic/split_button_chooser_test.dart new file mode 100644 index 00000000..1dadc76b --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/split_button_chooser_test.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/split_button_chooser.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map splitButtonChooserJson = { + 'topic': 'Test/Split Button Chooser', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Split Button Chooser/options', + type: NT4TypeStr.kStringArr, + properties: {}), + NT4Topic( + name: 'Test/Split Button Chooser/active', + type: NT4TypeStr.kString, + properties: {}), + NT4Topic( + name: 'Test/Split Button Chooser/selected', + type: NT4TypeStr.kString, + properties: {}), + NT4Topic( + name: 'Test/Split Button Chooser/default', + type: NT4TypeStr.kString, + properties: {}), + ], + virtualValues: { + 'Test/Split Button Chooser/options': ['One', 'Two', 'Three'], + 'Test/Split Button Chooser/active': 'Two', + }, + ); + }); + + test('Split button chooser from json', () { + NTWidgetModel splitButtonChooserModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Split Button Chooser', + splitButtonChooserJson, + ); + + expect(splitButtonChooserModel.type, 'Split Button Chooser'); + expect(splitButtonChooserModel.runtimeType, SplitButtonChooserModel); + }); + + test('Split button chooser to json', () { + SplitButtonChooserModel splitButtonChooserModel = SplitButtonChooserModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Split Button Chooser', + period: 0.100, + ); + + expect(splitButtonChooserModel.toJson(), splitButtonChooserJson); + }); + + testWidgets('Split button chooser widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel splitButtonChooserModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Split Button Chooser', + splitButtonChooserJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: splitButtonChooserModel, + child: const SplitButtonChooser(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(ToggleButtons), findsOneWidget); + expect(find.text('One'), findsOneWidget); + expect(find.text('Two'), findsOneWidget); + expect(find.text('Three'), findsOneWidget); + expect((splitButtonChooserModel as SplitButtonChooserModel).selectedChoice, + 'Two'); + expect(find.byIcon(Icons.check), findsOneWidget); + + await widgetTester.tap(find.text('One')); + splitButtonChooserModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(splitButtonChooserModel.selectedChoice, 'One'); + expect(find.byIcon(Icons.priority_high), findsOneWidget); + + ntConnection.updateDataFromTopicName( + splitButtonChooserModel.activeTopicName, 'One'); + + splitButtonChooserModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.priority_high), findsNothing); + expect(find.byIcon(Icons.check), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/subsystem_widget_test.dart b/test/widgets/nt_widgets/multi-topic/subsystem_widget_test.dart new file mode 100644 index 00000000..eca1d9f8 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/subsystem_widget_test.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/subsystem_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map subsystemJson = { + 'topic': 'Test/Subsystem', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Subsystem/.default', + type: NT4TypeStr.kString, + properties: {}, + ), + NT4Topic( + name: 'Test/Subsystem/.command', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Subsystem/.command': 'TestCommand', + }, + ); + }); + + test('Subsystem model from json', () { + NTWidgetModel subsystemModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Subsystem', + subsystemJson, + ); + + expect(subsystemModel.type, 'Subsystem'); + expect(subsystemModel.runtimeType, SubsystemModel); + }); + + test('Subsystem model to json', () { + SubsystemModel subsystemModel = SubsystemModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Subsystem', + period: 0.100, + ); + + expect(subsystemModel.toJson(), subsystemJson); + }); + + testWidgets('Subsystem widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel subsystemModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Subsystem', + subsystemJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: subsystemModel, + child: const SubsystemWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Default Command: none'), findsOneWidget); + expect(find.text('Current Command: TestCommand'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/three_axis_accelerometer_test.dart b/test/widgets/nt_widgets/multi-topic/three_axis_accelerometer_test.dart new file mode 100644 index 00000000..4a3163ed --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/three_axis_accelerometer_test.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/three_axis_accelerometer.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map threeAxisAccelerometerJson = { + 'topic': 'Test/Three Axis Accelerometer', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Three Axis Accelerometer/X', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Three Axis Accelerometer/Y', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + NT4Topic( + name: 'Test/Three Axis Accelerometer/Z', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Three Axis Accelerometer/X': 0.100, + 'Test/Three Axis Accelerometer/Y': 0.200, + 'Test/Three Axis Accelerometer/Z': 0.300, + }, + ); + }); + + test('Three axis accelerometer from json', () { + NTWidgetModel threeAxisAccelerometerModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + '3-Axis Accelerometer', + threeAxisAccelerometerJson, + ); + + expect(threeAxisAccelerometerModel.type, '3-Axis Accelerometer'); + expect( + threeAxisAccelerometerModel.runtimeType, ThreeAxisAccelerometerModel); + }); + + test('Three axis accelerometer from alias name', () { + NTWidgetModel threeAxisAccelerometerModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + '3AxisAccelerometer', + threeAxisAccelerometerJson, + ); + + expect(threeAxisAccelerometerModel.type, '3-Axis Accelerometer'); + expect( + threeAxisAccelerometerModel.runtimeType, ThreeAxisAccelerometerModel); + }); + + test('Three axis accelerometer to json', () { + ThreeAxisAccelerometerModel threeAxisAccelerometerModel = + ThreeAxisAccelerometerModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Three Axis Accelerometer', + period: 0.100, + ); + + expect(threeAxisAccelerometerModel.toJson(), threeAxisAccelerometerJson); + }); + + testWidgets('Three axis accelerometer widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel threeAxisAccelerometerModel = + NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + '3-Axis Accelerometer', + threeAxisAccelerometerJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: threeAxisAccelerometerModel, + child: const ThreeAxisAccelerometer(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('0.10 g'), findsOneWidget); + expect(find.text('0.20 g'), findsOneWidget); + expect(find.text('0.30 g'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/ultrasonic_test.dart b/test/widgets/nt_widgets/multi-topic/ultrasonic_test.dart new file mode 100644 index 00000000..70d1db05 --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/ultrasonic_test.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/ultrasonic.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map ultrasonicJson = { + 'topic': 'Test/Ultrasonic', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Ultrasonic/Value', + type: NT4TypeStr.kFloat32, + properties: {}, + ), + ], + virtualValues: { + 'Test/Ultrasonic/Value': 0.12, + }, + ); + }); + + test('Ultrasonic from json', () { + NTWidgetModel ultrasonicModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Ultrasonic', + ultrasonicJson, + ); + + expect(ultrasonicModel.type, 'Ultrasonic'); + expect(ultrasonicModel.runtimeType, UltrasonicModel); + }); + + test('Ultrasonic to json', () { + UltrasonicModel ultrasonicModel = UltrasonicModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Ultrasonic', + period: 0.100, + ); + + expect(ultrasonicModel.toJson(), ultrasonicJson); + }); + + testWidgets('Ultrasonic widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel ultrasonicModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Ultrasonic', + ultrasonicJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: ultrasonicModel, + child: const Ultrasonic(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Range'), findsOneWidget); + expect(find.text('0.12000 in'), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/multi-topic/yagsl_swerve_drive_test.dart b/test/widgets/nt_widgets/multi-topic/yagsl_swerve_drive_test.dart new file mode 100644 index 00000000..8d770c1e --- /dev/null +++ b/test/widgets/nt_widgets/multi-topic/yagsl_swerve_drive_test.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/yagsl_swerve_drive.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map yagslSwerveJson = { + 'topic': 'Test/YAGSL Swerve Drive', + 'period': 0.100, + 'show_robot_rotation': true, + 'show_desired_states': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4(); + }); + + test('YAGSL swerve drive from json', () { + NTWidgetModel yagslSwerveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'YAGSL Swerve Drive', + yagslSwerveJson, + ); + + expect(yagslSwerveModel.type, 'YAGSL Swerve Drive'); + expect(yagslSwerveModel.runtimeType, YAGSLSwerveDriveModel); + + if (yagslSwerveModel is! YAGSLSwerveDriveModel) { + return; + } + + expect(yagslSwerveModel.showRobotRotation, isTrue); + expect(yagslSwerveModel.showDesiredStates, isTrue); + }); + + test('YAGSL swerve drive to json', () { + YAGSLSwerveDriveModel yagslSwerveModel = YAGSLSwerveDriveModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/YAGSL Swerve Drive', + period: 0.100, + showRobotRotation: true, + showDesiredStates: true, + ); + + expect(yagslSwerveModel.toJson(), yagslSwerveJson); + }); + + testWidgets('YAGSL swerve drive widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel yagslSwerveModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'YAGSL Swerve Drive', + yagslSwerveJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: yagslSwerveModel, + child: const YAGSLSwerveDrive(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(CustomPaint), findsWidgets); + }); +} From 9ecf59b16ad422af37c0feb6c470df929e75b28f Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 19:44:50 -0400 Subject: [PATCH 16/28] Make NTWidgetBuilder constructor private --- lib/services/nt_widget_builder.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index f94480de..03d24d98 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -74,6 +74,8 @@ class NTWidgetBuilder { static const double _normalSize = 128.0; + NTWidgetBuilder._(); + static bool _initialized = false; static void ensureInitialized() { if (_initialized) { From 60a5349daac4fbf915d977f3001f2bb641f60773 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sat, 25 May 2024 21:20:31 -0400 Subject: [PATCH 17/28] Added boolean box, graph, and match time tests --- .../nt_widgets/single_topic/graph.dart | 4 +- test/test_util.dart | 2 +- .../single-topic/boolean_box_test.dart | 150 +++++++++++++++++ .../nt_widgets/single-topic/graph_test.dart | 121 ++++++++++++++ .../single-topic/match_time_test.dart | 156 ++++++++++++++++++ test_resources/test-layout.json | 2 - 6 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 test/widgets/nt_widgets/single-topic/boolean_box_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/graph_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/match_time_test.dart diff --git a/lib/widgets/nt_widgets/single_topic/graph.dart b/lib/widgets/nt_widgets/single_topic/graph.dart index 416a4cb0..ac350ab3 100644 --- a/lib/widgets/nt_widgets/single_topic/graph.dart +++ b/lib/widgets/nt_widgets/single_topic/graph.dart @@ -97,8 +97,8 @@ class GraphModel extends NTWidgetModel { return { ...super.toJson(), 'time_displayed': _timeDisplayed, - 'min_value': _minValue, - 'max_value': _maxValue, + if (_minValue != null) 'min_value': _minValue, + if (_maxValue != null) 'max_value': _maxValue, 'color': _mainColor.value, 'line_width': _lineWidth, }; diff --git a/test/test_util.dart b/test/test_util.dart index 24e738ed..6a70b51b 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -128,7 +128,7 @@ MockNTConnection createMockOnlineNT4({ when(mockNT4Connection.getTopicFromName(topic.name)).thenReturn(topic); - when(topicSubscription.periodicStream()) + when(topicSubscription.periodicStream(yieldAll: anyNamed('yieldAll'))) .thenAnswer((_) => Stream.value(virtualValues?[topic.name])); when(mockNT4Connection.getLastAnnouncedValue(topic.name)) diff --git a/test/widgets/nt_widgets/single-topic/boolean_box_test.dart b/test/widgets/nt_widgets/single-topic/boolean_box_test.dart new file mode 100644 index 00000000..e7530820 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/boolean_box_test.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/boolean_box.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map booleanBoxJson = { + 'topic': 'Test/Boolean Value', + 'period': 0.100, + 'data_type': 'boolean', + 'true_color': Colors.green.value, + 'false_color': Colors.red.value, + 'true_icon': 'None', + 'false_icon': 'None', + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + Finder findColor(Color color) => find.byWidgetPredicate( + (widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color != null && + (widget.decoration as BoxDecoration).color!.value == color.value, + ); + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Boolean Value', + type: NT4TypeStr.kBool, + properties: {}, + ), + ], + virtualValues: { + 'Test/Boolean Value': false, + }, + ); + }); + + test('Boolean box from json', () { + NTWidgetModel booleanBoxModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Boolean Box', + booleanBoxJson, + ); + + expect(booleanBoxModel.type, 'Boolean Box'); + expect(booleanBoxModel.runtimeType, BooleanBoxModel); + expect( + booleanBoxModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Boolean Box', + 'Toggle Switch', + 'Toggle Button', + 'Text Display', + ])); + + if (booleanBoxModel is! BooleanBoxModel) { + return; + } + + expect(booleanBoxModel.trueColor, Color(Colors.green.value)); + expect(booleanBoxModel.falseColor, Color(Colors.red.value)); + expect(booleanBoxModel.trueIcon, 'None'); + expect(booleanBoxModel.falseIcon, 'None'); + }); + + test('Boolean box to json', () { + BooleanBoxModel booleanBoxModel = BooleanBoxModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Boolean Value', + dataType: 'boolean', + period: 0.100, + trueColor: Colors.green, + falseColor: Colors.red, + trueIcon: 'None', + falseIcon: 'None', + ); + + expect(booleanBoxModel.toJson(), booleanBoxJson); + }); + + testWidgets('Boolean box widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel booleanBoxModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Boolean Box', + booleanBoxJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: booleanBoxModel as BooleanBoxModel, + child: const BooleanBox(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(findColor(Colors.green), findsNothing); + expect(findColor(Colors.red), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Boolean Value', true); + booleanBoxModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(findColor(Colors.green), findsOneWidget); + expect(findColor(Colors.red), findsNothing); + + booleanBoxModel.trueIcon = 'Checkmark'; + await widgetTester.pumpAndSettle(); + + expect(findColor(Colors.green), findsNothing); + expect(find.byIcon(Icons.check), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Boolean Value', false); + booleanBoxModel.falseIcon = 'X'; + await widgetTester.pumpAndSettle(); + + expect(find.byIcon(Icons.clear), findsOneWidget); + + booleanBoxModel.falseIcon = 'Exclamation Point'; + await widgetTester.pumpAndSettle(); + expect(find.byIcon(Icons.priority_high), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/graph_test.dart b/test/widgets/nt_widgets/single-topic/graph_test.dart new file mode 100644 index 00000000..08d8e9c9 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/graph_test.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/graph.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map graphJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'time_displayed': 10.0, + 'max_value': 1.0, + 'color': Colors.green.value, + 'line_width': 3.0, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': 0.0, + }, + ); + }); + + test('Graph from json', () { + NTWidgetModel graphModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Graph', + graphJson, + ); + + expect(graphModel.type, 'Graph'); + expect(graphModel.runtimeType, GraphModel); + expect( + graphModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (graphModel is! GraphModel) { + return; + } + + expect(graphModel.timeDisplayed, 10.0); + expect(graphModel.minValue, isNull); + expect(graphModel.maxValue, 1.0); + expect(graphModel.mainColor, Color(Colors.green.value)); + expect(graphModel.lineWidth, 3.0); + }); + + test('Graph model to json', () { + GraphModel graphModel = GraphModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + timeDisplayed: 10.0, + maxValue: 1.0, + mainColor: Colors.green, + lineWidth: 3.0, + ); + + expect(graphModel.toJson(), graphJson); + }); + + testWidgets('Graph widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel graphModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Graph', + graphJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: graphModel, + child: const GraphWidget(), + ), + ), + ), + ); + + expect(find.byType(SfCartesianChart), findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/match_time_test.dart b/test/widgets/nt_widgets/single-topic/match_time_test.dart new file mode 100644 index 00000000..05f29a6a --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/match_time_test.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/match_time.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map matchTimeJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'time_display_mode': 'Minutes and Seconds', + 'red_start_time': 15, + 'yellow_start_time': 25.0, + }; + + Finder coloredText(String text, Color color) => find.byWidgetPredicate( + (widget) => + widget is Text && + widget.data == text && + widget.style != null && + widget.style!.color != null && + widget.style!.color!.value == color.value, + ); + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': 96.0, + }, + ); + }); + + test('Match time from json', () { + NTWidgetModel matchTimeModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Match Time', + matchTimeJson, + ); + + expect(matchTimeModel.type, 'Match Time'); + expect(matchTimeModel.runtimeType, MatchTimeModel); + expect( + matchTimeModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (matchTimeModel is! MatchTimeModel) { + return; + } + + expect(matchTimeModel.timeDisplayMode, 'Minutes and Seconds'); + expect(matchTimeModel.redStartTime, 15); + expect(matchTimeModel.yellowStartTime, 25); + }); + + test('Match time to json', () { + MatchTimeModel matchTimeModel = MatchTimeModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + timeDisplayMode: 'Minutes and Seconds', + redStartTime: 15, + yellowStartTime: 25, + ); + + expect(matchTimeModel.toJson(), matchTimeJson); + }); + + testWidgets('Match time widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel matchTimeModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Match Time', + matchTimeJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: matchTimeModel as MatchTimeModel, + child: const MatchTimeWidget(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('1:36'), findsOneWidget); + expect(coloredText('1:36', Colors.blue), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Double Value', 55.0); + matchTimeModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.text('0:55'), findsOneWidget); + expect(coloredText('0:55', Colors.green), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Double Value', 22.0); + matchTimeModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.text('0:22'), findsOneWidget); + expect(coloredText('0:22', Colors.yellow), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Double Value', 13.0); + matchTimeModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(find.text('0:13'), findsOneWidget); + expect(coloredText('0:13', Colors.red), findsOneWidget); + + ntConnection.updateDataFromTopicName('Test/Double Value', 96.0); + matchTimeModel.timeDisplayMode = 'Seconds Only'; + await widgetTester.pumpAndSettle(); + + expect(find.text('96'), findsOneWidget); + expect(coloredText('96', Colors.blue), findsOneWidget); + }); +} diff --git a/test_resources/test-layout.json b/test_resources/test-layout.json index 0cfaae7f..d7362120 100644 --- a/test_resources/test-layout.json +++ b/test_resources/test-layout.json @@ -376,8 +376,6 @@ "topic": "/SmartDashboard/Voltage", "period": 0.033, "time_displayed": 5.0, - "min_value": null, - "max_value": null, "color": 4278238420, "line_width": 2.0 } From 51c5c2518a83a2fc2c3fb1e62e9931e2b3b011e1 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 00:03:34 -0400 Subject: [PATCH 18/28] Added more single topic widget tests * Number Bar * Number Slider * Multi Color View * Radial Gauge * Single Color View --- .../nt_widgets/single_topic/number_bar.dart | 10 +- .../single_topic/number_slider.dart | 6 +- .../single-topic/multi_color_view_test.dart | 215 +++++++++++++++++ .../single-topic/number_bar_test.dart | 212 +++++++++++++++++ .../single-topic/number_slider_test.dart | 198 ++++++++++++++++ .../single-topic/radial_gauge_test.dart | 218 ++++++++++++++++++ .../single-topic/single_color_view_test.dart | 112 +++++++++ test_resources/test-layout.json | 2 +- 8 files changed, 965 insertions(+), 8 deletions(-) create mode 100644 test/widgets/nt_widgets/single-topic/multi_color_view_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/number_bar_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/number_slider_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/radial_gauge_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/single_color_view_test.dart diff --git a/lib/widgets/nt_widgets/single_topic/number_bar.dart b/lib/widgets/nt_widgets/single_topic/number_bar.dart index 7c8fc224..6fea9230 100644 --- a/lib/widgets/nt_widgets/single_topic/number_bar.dart +++ b/lib/widgets/nt_widgets/single_topic/number_bar.dart @@ -90,11 +90,11 @@ class NumberBarModel extends NTWidgetModel { Map toJson() { return { ...super.toJson(), - 'min_value': _minValue, - 'max_value': _maxValue, - 'divisions': _divisions, - 'inverted': _inverted, - 'orientation': _orientation, + 'min_value': minValue, + 'max_value': maxValue, + if (divisions != null) 'divisions': divisions, + 'inverted': inverted, + 'orientation': orientation, }; } diff --git a/lib/widgets/nt_widgets/single_topic/number_slider.dart b/lib/widgets/nt_widgets/single_topic/number_slider.dart index 902b9ac4..7c619ac7 100644 --- a/lib/widgets/nt_widgets/single_topic/number_slider.dart +++ b/lib/widgets/nt_widgets/single_topic/number_slider.dart @@ -85,7 +85,9 @@ class NumberSliderModel extends NTWidgetModel { tryCast(jsonData['numOfTickMarks']) ?? 5; - _updateContinuously = tryCast(jsonData['update_continuously']) ?? false; + _updateContinuously = tryCast(jsonData['update_continuously']) ?? + tryCast(jsonData['publish_all']) ?? + false; } @override @@ -95,7 +97,7 @@ class NumberSliderModel extends NTWidgetModel { 'min_value': _minValue, 'max_value': _maxValue, 'divisions': _divisions, - 'publish_all': _updateContinuously, + 'update_continuously': _updateContinuously, }; } diff --git a/test/widgets/nt_widgets/single-topic/multi_color_view_test.dart b/test/widgets/nt_widgets/single-topic/multi_color_view_test.dart new file mode 100644 index 00000000..a253570c --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/multi_color_view_test.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/multi_color_view.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map multiColorViewJson = { + 'topic': 'Test/String Array', + 'data_type': 'string[]', + 'period': 0.100, + }; + + Finder findGradient(List expectedColors) => + find.byWidgetPredicate((widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).gradient != null && + (widget.decoration as BoxDecoration) + .gradient! + .colors + .equals(expectedColors)); + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/String Array', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/String Array': [ + Colors.red.toHexString(includeHashSign: true), + Colors.orange.toHexString(includeHashSign: true), + Colors.yellow.toHexString(includeHashSign: true), + Colors.green.toHexString(includeHashSign: true), + Colors.blue.toHexString(includeHashSign: true), + Colors.purple.toHexString(includeHashSign: true), + ], + }, + ); + }); + + test('Multi color view from json', () { + NTWidgetModel multiColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Multi Color View', + multiColorViewJson, + ); + + expect(multiColorViewModel.type, 'Multi Color View'); + expect(multiColorViewModel.runtimeType, NTWidgetModel); + expect( + multiColorViewModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Multi Color View', + 'Text Display', + ])); + }); + + test('Multi color view to json', () { + NTWidgetModel multiColorViewModel = NTWidgetModel.createDefault( + ntConnection: ntConnection, + preferences: preferences, + type: 'Multi Color View', + topic: 'Test/String Array', + dataType: 'string[]', + period: 0.100, + ); + + expect(multiColorViewModel.toJson(), multiColorViewJson); + }); + + testWidgets('Multi color view widget test full gradient', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel multiColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Multi Color View', + multiColorViewJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: multiColorViewModel, + child: const MultiColorView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + List expectedColors = [ + Color(Colors.red.value), + Color(Colors.orange.value), + Color(Colors.yellow.value), + Color(Colors.green.value), + Color(Colors.blue.value), + Color(Colors.purple.value), + ]; + + expect(findGradient(expectedColors), findsOneWidget); + }); + + testWidgets('Multi color view widget test one color', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel multiColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/String Array', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/String Array': [ + Colors.red.toHexString(includeHashSign: true), + ], + }, + ), + preferences, + 'Multi Color View', + multiColorViewJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: multiColorViewModel, + child: const MultiColorView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect( + findGradient([ + Color(Colors.red.value), + Color(Colors.red.value), + ]), + findsOneWidget); + }); + + testWidgets('Multi color view widget test no colors', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel multiColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/String Array', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/String Array': [], + }, + ), + preferences, + 'Multi Color View', + multiColorViewJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: multiColorViewModel, + child: const MultiColorView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect( + findGradient([ + Color(Colors.transparent.value), + Color(Colors.transparent.value), + ]), + findsOneWidget); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/number_bar_test.dart b/test/widgets/nt_widgets/single-topic/number_bar_test.dart new file mode 100644 index 00000000..6e2337f6 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/number_bar_test.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/number_bar.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map numberBarJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'min_value': -5.0, + 'max_value': 5.0, + 'inverted': false, + 'orientation': 'horizontal', + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': -1.0, + }, + ); + }); + + test('Number bar from json', () { + NTWidgetModel numberBarModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Number Bar', + numberBarJson, + ); + + expect(numberBarModel.type, 'Number Bar'); + expect(numberBarModel.runtimeType, NumberBarModel); + expect( + numberBarModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (numberBarModel is! NumberBarModel) { + return; + } + + expect(numberBarModel.minValue, -5.0); + expect(numberBarModel.maxValue, 5.0); + expect(numberBarModel.divisions, isNull); + expect(numberBarModel.inverted, isFalse); + expect(numberBarModel.orientation, 'horizontal'); + }); + + test('Number bar to json', () { + NumberBarModel numberBarModel = NumberBarModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: null, + inverted: false, + orientation: 'horizontal', + ); + + expect(numberBarModel.toJson(), numberBarJson); + }); + + testWidgets('Number bar widget test horizontal', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel numberBarModel = NumberBarModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: null, + inverted: false, + orientation: 'horizontal', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: numberBarModel, + child: const NumberBar(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-1.00'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .orientation, + LinearGaugeOrientation.horizontal); + }); + + testWidgets('Number bar widget test vertical', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel numberBarModel = NumberBarModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: null, + inverted: false, + orientation: 'vertical', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: numberBarModel, + child: const NumberBar(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-1.00'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .orientation, + LinearGaugeOrientation.vertical); + }); + + testWidgets('Number bar widget test with divisions', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel numberBarModel = NumberBarModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: 11, + inverted: false, + orientation: 'horizontal', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: numberBarModel, + child: const NumberBar(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-1.00'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .interval, + 1.0); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/number_slider_test.dart b/test/widgets/nt_widgets/single-topic/number_slider_test.dart new file mode 100644 index 00000000..2c9e062a --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/number_slider_test.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/number_slider.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map numberSliderJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'min_value': -5.0, + 'max_value': 5.0, + 'divisions': 5, + 'update_continuously': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': -1.0, + }, + ); + }); + + test('Number slider from json', () { + NTWidgetModel numberSliderModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Number Slider', + numberSliderJson, + ); + + expect(numberSliderModel.type, 'Number Slider'); + expect(numberSliderModel.runtimeType, NumberSliderModel); + expect( + numberSliderModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (numberSliderModel is! NumberSliderModel) { + return; + } + + expect(numberSliderModel.minValue, -5.0); + expect(numberSliderModel.maxValue, 5.0); + expect(numberSliderModel.divisions, 5); + expect(numberSliderModel.updateContinuously, isTrue); + }); + + test('Number slider to json', () { + NumberSliderModel numberSliderModel = NumberSliderModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: 5, + updateContinuously: true, + ); + + expect(numberSliderModel.toJson(), numberSliderJson); + }); + + testWidgets('Number slider widget test continuous update', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel numberSliderModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Number Slider', + numberSliderJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: numberSliderModel, + child: const NumberSlider(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-1.00'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + expect(find.byType(LinearShapePointer), findsOneWidget); + + Future pointerDrag = widgetTester.timedDrag( + find.byType(LinearShapePointer), + const Offset(100.0, 0.0), + const Duration(seconds: 1), + ); + + Future.delayed( + const Duration(milliseconds: 500), + () { + expect((numberSliderModel as NumberSliderModel).dragging, isTrue); + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), + isNot(-1.0)); + }, + ); + + await pointerDrag; + + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), + greaterThan(0.0)); + }); + + testWidgets('Number slider widget test non-continuous update', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NumberSliderModel numberSliderModel = NumberSliderModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: 5, + updateContinuously: false, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: numberSliderModel, + child: const NumberSlider(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('-1.00'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + expect(find.byType(LinearShapePointer), findsOneWidget); + + Future pointerDrag = widgetTester.timedDrag( + find.byType(LinearShapePointer), + const Offset(100.0, 0.0), + const Duration(seconds: 1), + ); + + Future.delayed( + const Duration(milliseconds: 500), + () { + expect((numberSliderModel).dragging, isTrue); + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), -1.0); + }, + ); + + await pointerDrag; + + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), + greaterThan(0.0)); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/radial_gauge_test.dart b/test/widgets/nt_widgets/single-topic/radial_gauge_test.dart new file mode 100644 index 00000000..12a645ec --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/radial_gauge_test.dart @@ -0,0 +1,218 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/radial_gauge.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map radialGaugeJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'start_angle': -140.0, + 'end_angle': 140.0, + 'min_value': -1.0, + 'max_value': 1.0, + 'number_of_labels': 10, + 'wrap_value': false, + 'show_pointer': true, + 'show_ticks': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': -0.50, + }, + ); + }); + + test('Radial gauge from json', () { + NTWidgetModel radialGaugeModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Radial Gauge', + radialGaugeJson, + ); + + expect(radialGaugeModel.type, 'Radial Gauge'); + expect(radialGaugeModel.runtimeType, RadialGaugeModel); + expect( + radialGaugeModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (radialGaugeModel is! RadialGaugeModel) { + return; + } + + expect(radialGaugeModel.startAngle, -140.0); + expect(radialGaugeModel.endAngle, 140.0); + expect(radialGaugeModel.minValue, -1.0); + expect(radialGaugeModel.maxValue, 1.0); + expect(radialGaugeModel.numberOfLabels, 10); + expect(radialGaugeModel.wrapValue, isFalse); + expect(radialGaugeModel.showPointer, isTrue); + expect(radialGaugeModel.showTicks, isTrue); + }); + + test('Radial gauge from alias name', () { + NTWidgetModel radialGaugeModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Simple Dial', + radialGaugeJson, + ); + + expect(radialGaugeModel.type, 'Radial Gauge'); + expect(radialGaugeModel.runtimeType, RadialGaugeModel); + expect( + radialGaugeModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (radialGaugeModel is! RadialGaugeModel) { + return; + } + + expect(radialGaugeModel.startAngle, -140.0); + expect(radialGaugeModel.endAngle, 140.0); + expect(radialGaugeModel.minValue, -1.0); + expect(radialGaugeModel.maxValue, 1.0); + expect(radialGaugeModel.numberOfLabels, 10); + expect(radialGaugeModel.wrapValue, isFalse); + expect(radialGaugeModel.showPointer, isTrue); + expect(radialGaugeModel.showTicks, isTrue); + }); + + test('Radial gauge to json', () { + RadialGaugeModel radialGaugeModel = RadialGaugeModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + startAngle: -140.0, + endAngle: 140.0, + minValue: -1.0, + maxValue: 1.0, + numberOfLabels: 10, + wrapValue: false, + showPointer: true, + showTicks: true, + ); + + expect(radialGaugeModel.toJson(), radialGaugeJson); + }); + + testWidgets('Radial gauge widget test with pointer', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + RadialGaugeModel radialGaugeModel = RadialGaugeModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + startAngle: -140.0, + endAngle: 140.0, + minValue: -1.0, + maxValue: 1.0, + numberOfLabels: 10, + wrapValue: false, + showPointer: true, + showTicks: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: radialGaugeModel, + child: const RadialGauge(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(SfRadialGauge), findsOneWidget); + expect(find.text('-0.50'), findsOneWidget); + expect(find.byType(NeedlePointer), findsOneWidget); + }); + + testWidgets('Radial gauge widget test no pointer', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + RadialGaugeModel radialGaugeModel = RadialGaugeModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + startAngle: -140.0, + endAngle: 140.0, + minValue: -1.0, + maxValue: 1.0, + numberOfLabels: 10, + wrapValue: false, + showPointer: false, + showTicks: false, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: radialGaugeModel, + child: const RadialGauge(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(SfRadialGauge), findsOneWidget); + expect(find.text('-0.50'), findsOneWidget); + expect(find.byType(NeedlePointer), findsNothing); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/single_color_view_test.dart b/test/widgets/nt_widgets/single-topic/single_color_view_test.dart new file mode 100644 index 00000000..e10b3ee8 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/single_color_view_test.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/single_color_view.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map singleColorViewJson = { + 'topic': 'Test/String Value', + 'data_type': 'string', + 'period': 0.100, + }; + + Finder findColor(Color color) => find.byWidgetPredicate( + (widget) => + widget is Container && + widget.decoration is BoxDecoration && + (widget.decoration as BoxDecoration).color != null && + (widget.decoration as BoxDecoration).color!.value == color.value, + ); + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/String Value', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/String Value': Colors.red.toHexString( + includeHashSign: true, + enableAlpha: false, + ), + }, + ); + }); + + test('Single color view from json', () { + NTWidgetModel singleColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Single Color View', + singleColorViewJson, + ); + + expect(singleColorViewModel.type, 'Single Color View'); + expect(singleColorViewModel.runtimeType, NTWidgetModel); + expect( + singleColorViewModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Single Color View', + ])); + }); + + test('Single color view to json', () { + NTWidgetModel singleColorViewModel = NTWidgetModel.createDefault( + ntConnection: ntConnection, + preferences: preferences, + type: 'Single Color View', + topic: 'Test/String Value', + dataType: 'string', + period: 0.100, + ); + + expect(singleColorViewModel.toJson(), singleColorViewJson); + }); + + testWidgets('Single color view widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel singleColorViewModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Single Color View', + singleColorViewJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: singleColorViewModel, + child: const SingleColorView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(findColor(Colors.red), findsOneWidget); + }); +} diff --git a/test_resources/test-layout.json b/test_resources/test-layout.json index d7362120..951427b9 100644 --- a/test_resources/test-layout.json +++ b/test_resources/test-layout.json @@ -362,7 +362,7 @@ "min_value": -1.0, "max_value": 1.0, "divisions": 5, - "publish_all": false + "update_continuously": false } }, { From 0ee90fc73c9403a50d07a035ee1d77a906a63627 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 00:21:15 -0400 Subject: [PATCH 19/28] Fixed number slider tests --- .../single-topic/number_slider_test.dart | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/test/widgets/nt_widgets/single-topic/number_slider_test.dart b/test/widgets/nt_widgets/single-topic/number_slider_test.dart index 2c9e062a..553069fd 100644 --- a/test/widgets/nt_widgets/single-topic/number_slider_test.dart +++ b/test/widgets/nt_widgets/single-topic/number_slider_test.dart @@ -96,7 +96,7 @@ void main() { testWidgets('Number slider widget test continuous update', (widgetTester) async { - FlutterError.onError = ignoreOverflowErrors; + // FlutterError.onError = ignoreOverflowErrors; NTWidgetModel numberSliderModel = NTWidgetBuilder.buildNTModelFromJson( ntConnection, @@ -128,17 +128,20 @@ void main() { const Duration(seconds: 1), ); - Future.delayed( - const Duration(milliseconds: 500), - () { - expect((numberSliderModel as NumberSliderModel).dragging, isTrue); - expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), - isNot(-1.0)); - }, - ); + // Stupid workaround since expect can't be used during a drag + bool? draggingDuringDrag; + Object? valueDuringDrag; + + Future.delayed(const Duration(milliseconds: 500), () { + draggingDuringDrag = (numberSliderModel as NumberSliderModel).dragging; + valueDuringDrag = ntConnection.getLastAnnouncedValue('Test/Double Value'); + }); await pointerDrag; + expect(draggingDuringDrag, isTrue); + expect(valueDuringDrag, isNot(-1.0)); + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), greaterThan(0.0)); }); @@ -182,16 +185,20 @@ void main() { const Duration(seconds: 1), ); - Future.delayed( - const Duration(milliseconds: 500), - () { - expect((numberSliderModel).dragging, isTrue); - expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), -1.0); - }, - ); + // Stupid workaround since expect can't be used during a drag + bool? draggingDuringDrag; + Object? valueDuringDrag; + + Future.delayed(const Duration(milliseconds: 500), () { + draggingDuringDrag = numberSliderModel.dragging; + valueDuringDrag = ntConnection.getLastAnnouncedValue('Test/Double Value'); + }); await pointerDrag; + expect(draggingDuringDrag, isTrue); + expect(valueDuringDrag, -1.0); + expect(ntConnection.getLastAnnouncedValue('Test/Double Value'), greaterThan(0.0)); }); From 69c28ef0dd6332f4e793f06ffa7b401c901e4e61 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 00:48:08 -0400 Subject: [PATCH 20/28] Added text display test --- .../nt_widgets/single_topic/text_display.dart | 2 +- .../single-topic/number_slider_test.dart | 2 +- .../single-topic/text_display_test.dart | 456 ++++++++++++++++++ 3 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 test/widgets/nt_widgets/single-topic/text_display_test.dart diff --git a/lib/widgets/nt_widgets/single_topic/text_display.dart b/lib/widgets/nt_widgets/single_topic/text_display.dart index 0665db98..b71ff55e 100644 --- a/lib/widgets/nt_widgets/single_topic/text_display.dart +++ b/lib/widgets/nt_widgets/single_topic/text_display.dart @@ -63,7 +63,7 @@ class TextDisplayModel extends NTWidgetModel { Map toJson() { return { ...super.toJson(), - 'show_submit_button': _showSubmitButton, + 'show_submit_button': showSubmitButton, }; } diff --git a/test/widgets/nt_widgets/single-topic/number_slider_test.dart b/test/widgets/nt_widgets/single-topic/number_slider_test.dart index 553069fd..2fecbb1d 100644 --- a/test/widgets/nt_widgets/single-topic/number_slider_test.dart +++ b/test/widgets/nt_widgets/single-topic/number_slider_test.dart @@ -96,7 +96,7 @@ void main() { testWidgets('Number slider widget test continuous update', (widgetTester) async { - // FlutterError.onError = ignoreOverflowErrors; + FlutterError.onError = ignoreOverflowErrors; NTWidgetModel numberSliderModel = NTWidgetBuilder.buildNTModelFromJson( ntConnection, diff --git a/test/widgets/nt_widgets/single-topic/text_display_test.dart b/test/widgets/nt_widgets/single-topic/text_display_test.dart new file mode 100644 index 00000000..81159cc1 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/text_display_test.dart @@ -0,0 +1,456 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/text_display.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map textDisplayJson = { + 'topic': 'Test/Display Value', + 'data_type': 'double', + 'period': 0.100, + 'show_submit_button': true, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': 0.000001, + }, + ); + }); + + test('Text display from json', () { + NTWidgetModel textDisplayModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Text Display', + textDisplayJson, + ); + + expect(textDisplayModel.type, 'Text Display'); + expect(textDisplayModel.runtimeType, TextDisplayModel); + + if (textDisplayModel is! TextDisplayModel) { + return; + } + + expect(textDisplayModel.showSubmitButton, isTrue); + }); + + test('Text display from alias name', () { + NTWidgetModel textDisplayModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Text View', + textDisplayJson, + ); + + expect(textDisplayModel.type, 'Text Display'); + expect(textDisplayModel.runtimeType, TextDisplayModel); + + if (textDisplayModel is! TextDisplayModel) { + return; + } + + expect(textDisplayModel.showSubmitButton, isTrue); + }); + + test('Text display to json', () { + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'double', + period: 0.100, + showSubmitButton: true, + ); + + expect(textDisplayModel.toJson(), textDisplayJson); + }); + + testWidgets('Text display widget test (double)', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'double', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('0.000001'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), '3.53'); + expect(ntConnection.getLastAnnouncedValue('Test/Display Value'), 0.000001); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Display Value'), 3.53); + }); + + testWidgets('Text display widget test (int)', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection intNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: intNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kInt, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': 0, + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'int', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('0'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), '3000'); + expect(intNTConnection.getLastAnnouncedValue('Test/Display Value'), 0); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(intNTConnection.getLastAnnouncedValue('Test/Display Value'), 3000); + }); + + testWidgets('Text display widget test (string)', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection stringNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: stringNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': 'Hello', + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'string', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Hello'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), 'I Edited This Text'); + expect(stringNTConnection.getLastAnnouncedValue('Test/Display Value'), + 'Hello'); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(stringNTConnection.getLastAnnouncedValue('Test/Display Value'), + 'I Edited This Text'); + }); + + testWidgets('Text display widget test (int[])', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection intArrNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: intArrNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kIntArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': [0, 0], + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'int[]', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('[0, 0]'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), '[1, 2, 3]'); + expect( + intArrNTConnection.getLastAnnouncedValue('Test/Display Value'), [0, 0]); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(intArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + [1, 2, 3]); + }); + + testWidgets('Text display widget test (double[])', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection doubleArrNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: doubleArrNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kFloat64Arr, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': [0.0, 0.0], + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'double[]', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('[0.0, 0.0]'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), '[1.0, 2.0, 3.0]'); + expect(doubleArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + [0.0, 0.0]); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(doubleArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + [1.0, 2.0, 3.0]); + }); + + testWidgets('Text display widget test (string[])', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection stringArrNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: stringArrNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kStringArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': ['Hello', 'There'], + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'string[]', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('[Hello, There]'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText( + find.byType(TextField), '["I", "am", "very", "tired"]'); + expect(stringArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + ['Hello', 'There']); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(stringArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + ['I', 'am', 'very', 'tired']); + }); + + testWidgets('Text display widget test no submit button', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection stringNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: stringNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kString, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': 'There isn\'t a submit button', + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'string', + period: 0.100, + showSubmitButton: false, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('There isn\'t a submit button'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsNothing); + expect(find.byIcon(Icons.exit_to_app), findsNothing); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText( + find.byType(TextField), 'I\'m submitting this without a button!'); + expect(stringNTConnection.getLastAnnouncedValue('Test/Display Value'), + 'There isn\'t a submit button'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(stringNTConnection.getLastAnnouncedValue('Test/Display Value'), + 'I\'m submitting this without a button!'); + }); +} From 4f7e64c3f73c0d359929f9344bea301b1dd519c7 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 00:57:46 -0400 Subject: [PATCH 21/28] added bool and bool array tests for text display --- .../single-topic/text_display_test.dart | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/widgets/nt_widgets/single-topic/text_display_test.dart b/test/widgets/nt_widgets/single-topic/text_display_test.dart index 81159cc1..06e87f6c 100644 --- a/test/widgets/nt_widgets/single-topic/text_display_test.dart +++ b/test/widgets/nt_widgets/single-topic/text_display_test.dart @@ -182,6 +182,60 @@ void main() { expect(intNTConnection.getLastAnnouncedValue('Test/Display Value'), 3000); }); + testWidgets('Text display widget test (boolean)', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection boolNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: boolNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kBool, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': false, + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'boolean', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('false'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), 'true'); + expect( + boolNTConnection.getLastAnnouncedValue('Test/Display Value'), isFalse); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect( + boolNTConnection.getLastAnnouncedValue('Test/Display Value'), isTrue); + }); + testWidgets('Text display widget test (string)', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; @@ -290,6 +344,60 @@ void main() { [1, 2, 3]); }); + testWidgets('Text display widget test (boolean[])', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTConnection boolArrNTConnection; + + TextDisplayModel textDisplayModel = TextDisplayModel( + ntConnection: boolArrNTConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Display Value', + type: NT4TypeStr.kBoolArr, + properties: {}, + ), + ], + virtualValues: { + 'Test/Display Value': [false, true], + }, + ), + preferences: preferences, + topic: 'Test/Display Value', + dataType: 'boolean[]', + period: 0.100, + showSubmitButton: true, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: textDisplayModel, + child: const TextDisplay(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('[false, true]'), findsOneWidget); + expect(find.byTooltip('Publish Data'), findsOneWidget); + expect(find.byIcon(Icons.exit_to_app), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); + + await widgetTester.enterText(find.byType(TextField), '[true, false, true]'); + expect(boolArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + [false, true]); + + await widgetTester.tap(find.byIcon(Icons.exit_to_app)); + await widgetTester.pumpAndSettle(); + + expect(boolArrNTConnection.getLastAnnouncedValue('Test/Display Value'), + [true, false, true]); + }); + testWidgets('Text display widget test (double[])', (widgetTester) async { FlutterError.onError = ignoreOverflowErrors; From 2d6b7c6c7512a0725cd4c36ae26d9aed8c1407ef Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 02:20:28 -0400 Subject: [PATCH 22/28] Replaced deprecated APIs --- lib/main.dart | 2 +- lib/widgets/editable_tab_bar.dart | 10 +++++----- lib/widgets/nt_widgets/multi-topic/camera_stream.dart | 4 ++-- lib/widgets/nt_widgets/multi-topic/pid_controller.dart | 2 +- .../multi-topic/profiled_pid_controller.dart | 2 +- .../nt_widgets/multi-topic/robot_preferences.dart | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b96ebc68..8e4917c8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter/material.dart'; import 'package:dot_cast/dot_cast.dart'; +import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:screen_retriever/screen_retriever.dart'; diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 3317edc0..c7705a98 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -93,11 +93,11 @@ class EditableTabBar extends StatelessWidget { Widget build(BuildContext context) { ThemeData theme = Theme.of(context); ButtonStyle endButtonStyle = const ButtonStyle( - shape: MaterialStatePropertyAll(RoundedRectangleBorder()), - maximumSize: MaterialStatePropertyAll(Size.square(34.0)), - minimumSize: MaterialStatePropertyAll(Size.zero), - padding: MaterialStatePropertyAll(EdgeInsets.all(4.0)), - iconSize: MaterialStatePropertyAll(24.0), + shape: WidgetStatePropertyAll(RoundedRectangleBorder()), + maximumSize: WidgetStatePropertyAll(Size.square(34.0)), + minimumSize: WidgetStatePropertyAll(Size.zero), + padding: WidgetStatePropertyAll(EdgeInsets.all(4.0)), + iconSize: WidgetStatePropertyAll(24.0), ); return Column( diff --git a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart index 9869114c..f3130f60 100644 --- a/lib/widgets/nt_widgets/multi-topic/camera_stream.dart +++ b/lib/widgets/nt_widgets/multi-topic/camera_stream.dart @@ -1,12 +1,12 @@ -import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:dot_cast/dot_cast.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/widgets/custom_loading_indicator.dart'; +import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/mjpeg.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; diff --git a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart index d9f08ec6..05981712 100644 --- a/lib/widgets/nt_widgets/multi-topic/pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/pid_controller.dart @@ -321,7 +321,7 @@ class PIDControllerWidget extends NTWidget { model.publishSetpoint(); }, style: ButtonStyle( - shape: MaterialStatePropertyAll( + shape: WidgetStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), diff --git a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart index e2751d6b..25b5c599 100644 --- a/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart +++ b/lib/widgets/nt_widgets/multi-topic/profiled_pid_controller.dart @@ -323,7 +323,7 @@ class ProfiledPIDControllerWidget extends NTWidget { model.publishGoal(); }, style: ButtonStyle( - shape: MaterialStatePropertyAll( + shape: WidgetStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), ), diff --git a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart index d48ce330..9d188ec5 100644 --- a/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart +++ b/lib/widgets/nt_widgets/multi-topic/robot_preferences.dart @@ -194,7 +194,7 @@ class PreferenceSearch extends StatelessWidget { .toList(); }, initialList: preferenceTopicNames, - builder: (displayedList, itemIndex, item) { + itemBuilder: (item) { TextEditingController? textController = preferenceTextControllers[item]; return _RobotPreference( From b9f1fe6e59fad47467f838382bcecf248a797df5 Mon Sep 17 00:00:00 2001 From: Gold87 Date: Sun, 26 May 2024 13:26:44 -0400 Subject: [PATCH 23/28] Added remaining single topic widget tests * Toggle button * Toggle switch * Voltage view --- .../nt_widgets/single_topic/voltage_view.dart | 10 +- .../single-topic/toggle_button_test.dart | 114 ++++++++++ .../single-topic/toggle_switch_test.dart | 114 ++++++++++ .../single-topic/voltage_view_test.dart | 212 ++++++++++++++++++ 4 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 test/widgets/nt_widgets/single-topic/toggle_button_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/toggle_switch_test.dart create mode 100644 test/widgets/nt_widgets/single-topic/voltage_view_test.dart diff --git a/lib/widgets/nt_widgets/single_topic/voltage_view.dart b/lib/widgets/nt_widgets/single_topic/voltage_view.dart index 9c15c364..c300e63b 100644 --- a/lib/widgets/nt_widgets/single_topic/voltage_view.dart +++ b/lib/widgets/nt_widgets/single_topic/voltage_view.dart @@ -90,11 +90,11 @@ class VoltageViewModel extends NTWidgetModel { Map toJson() { return { ...super.toJson(), - 'min_value': _minValue, - 'max_value': _maxValue, - 'divisions': _divisions, - 'inverted': _inverted, - 'orientation': _orientation, + 'min_value': minValue, + 'max_value': maxValue, + if (divisions != null) 'divisions': divisions, + 'inverted': inverted, + 'orientation': orientation, }; } diff --git a/test/widgets/nt_widgets/single-topic/toggle_button_test.dart b/test/widgets/nt_widgets/single-topic/toggle_button_test.dart new file mode 100644 index 00000000..31335ffc --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/toggle_button_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_button.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map toggleButtonJson = { + 'topic': 'Test/Boolean Value', + 'data_type': 'boolean', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Boolean Value', + type: NT4TypeStr.kBool, + properties: {}, + ), + ], + virtualValues: { + 'Test/Boolean Value': false, + }, + ); + }); + + test('Toggle button from json', () { + NTWidgetModel toggleButtonModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Toggle Button', + toggleButtonJson, + ); + + expect(toggleButtonModel.type, 'Toggle Button'); + expect(toggleButtonModel.runtimeType, NTWidgetModel); + expect( + toggleButtonModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Boolean Box', + 'Toggle Button', + 'Toggle Switch', + 'Text Display', + ])); + }); + + test('Toggle button to json', () { + NTWidgetModel toggleButtonModel = NTWidgetModel.createDefault( + ntConnection: ntConnection, + preferences: preferences, + type: 'Toggle Button', + topic: 'Test/Boolean Value', + dataType: 'boolean', + period: 0.100, + ); + + expect(toggleButtonModel.toJson(), toggleButtonJson); + }); + + testWidgets('Toggle button widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel toggleButtonModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Toggle Button', + toggleButtonJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: toggleButtonModel, + child: const ToggleButton(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Boolean Value'), findsOneWidget); + + await widgetTester.tap(find.text('Boolean Value')); + toggleButtonModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Boolean Value'), isTrue); + + await widgetTester.tap(find.text('Boolean Value')); + toggleButtonModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Boolean Value'), isFalse); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/toggle_switch_test.dart b/test/widgets/nt_widgets/single-topic/toggle_switch_test.dart new file mode 100644 index 00000000..1cb143c9 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/toggle_switch_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/toggle_switch.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map toggleSwitchJson = { + 'topic': 'Test/Boolean Value', + 'data_type': 'boolean', + 'period': 0.100, + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Boolean Value', + type: NT4TypeStr.kBool, + properties: {}, + ), + ], + virtualValues: { + 'Test/Boolean Value': false, + }, + ); + }); + + test('Toggle switch from json', () { + NTWidgetModel toggleSwitchModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Toggle Switch', + toggleSwitchJson, + ); + + expect(toggleSwitchModel.type, 'Toggle Switch'); + expect(toggleSwitchModel.runtimeType, NTWidgetModel); + expect( + toggleSwitchModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Boolean Box', + 'Toggle Button', + 'Toggle Switch', + 'Text Display', + ])); + }); + + test('Toggle switch to json', () { + NTWidgetModel toggleSwitchModel = NTWidgetModel.createDefault( + ntConnection: ntConnection, + preferences: preferences, + type: 'Toggle Switch', + topic: 'Test/Boolean Value', + dataType: 'boolean', + period: 0.100, + ); + + expect(toggleSwitchModel.toJson(), toggleSwitchJson); + }); + + testWidgets('Toggle switch widget test', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel toggleSwitchModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Toggle Switch', + toggleSwitchJson, + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: toggleSwitchModel, + child: const ToggleSwitch(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(Switch), findsOneWidget); + + await widgetTester.tap(find.byType(Switch)); + toggleSwitchModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Boolean Value'), isTrue); + + await widgetTester.tap(find.byType(Switch)); + toggleSwitchModel.refresh(); + await widgetTester.pumpAndSettle(); + + expect(ntConnection.getLastAnnouncedValue('Test/Boolean Value'), isFalse); + }); +} diff --git a/test/widgets/nt_widgets/single-topic/voltage_view_test.dart b/test/widgets/nt_widgets/single-topic/voltage_view_test.dart new file mode 100644 index 00000000..4415df35 --- /dev/null +++ b/test/widgets/nt_widgets/single-topic/voltage_view_test.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; + +import 'package:elastic_dashboard/services/nt4_client.dart'; +import 'package:elastic_dashboard/services/nt_connection.dart'; +import 'package:elastic_dashboard/services/nt_widget_builder.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +import 'package:elastic_dashboard/widgets/nt_widgets/single_topic/voltage_view.dart'; +import '../../../test_util.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final Map voltageViewJson = { + 'topic': 'Test/Double Value', + 'data_type': 'double', + 'period': 0.100, + 'min_value': 4.0, + 'max_value': 13.0, + 'inverted': false, + 'orientation': 'horizontal', + }; + + late SharedPreferences preferences; + late NTConnection ntConnection; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + preferences = await SharedPreferences.getInstance(); + + ntConnection = createMockOnlineNT4( + virtualTopics: [ + NT4Topic( + name: 'Test/Double Value', + type: NT4TypeStr.kFloat64, + properties: {}, + ), + ], + virtualValues: { + 'Test/Double Value': 12.0, + }, + ); + }); + + test('Voltage view from json', () { + NTWidgetModel voltageViewModel = NTWidgetBuilder.buildNTModelFromJson( + ntConnection, + preferences, + 'Voltage View', + voltageViewJson, + ); + + expect(voltageViewModel.type, 'Voltage View'); + expect(voltageViewModel.runtimeType, VoltageViewModel); + expect( + voltageViewModel.getAvailableDisplayTypes(), + unorderedEquals([ + 'Text Display', + 'Number Bar', + 'Number Slider', + 'Graph', + 'Voltage View', + 'Radial Gauge', + 'Match Time', + ])); + + if (voltageViewModel is! VoltageViewModel) { + return; + } + + expect(voltageViewModel.minValue, 4.0); + expect(voltageViewModel.maxValue, 13.0); + expect(voltageViewModel.divisions, isNull); + expect(voltageViewModel.inverted, isFalse); + expect(voltageViewModel.orientation, 'horizontal'); + }); + + test('Voltage view to json', () { + VoltageViewModel voltageViewModel = VoltageViewModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: 4.0, + maxValue: 13.0, + divisions: null, + inverted: false, + orientation: 'horizontal', + ); + + expect(voltageViewModel.toJson(), voltageViewJson); + }); + + testWidgets('Voltage view widget test horizontal', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel voltageViewModel = VoltageViewModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: -5.0, + maxValue: 5.0, + divisions: null, + inverted: false, + orientation: 'horizontal', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: voltageViewModel, + child: const VoltageView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('12.00 V'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .orientation, + LinearGaugeOrientation.horizontal); + }); + + testWidgets('Voltage view widget test vertical', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel voltageViewModel = VoltageViewModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: 4.0, + maxValue: 13.0, + divisions: null, + inverted: false, + orientation: 'vertical', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: voltageViewModel, + child: const VoltageView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('12.00 V'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .orientation, + LinearGaugeOrientation.vertical); + }); + + testWidgets('Voltage view widget test with divisions', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + NTWidgetModel voltageViewModel = VoltageViewModel( + ntConnection: ntConnection, + preferences: preferences, + topic: 'Test/Double Value', + dataType: 'double', + period: 0.100, + minValue: 4.0, + maxValue: 13.0, + divisions: 11, + inverted: false, + orientation: 'horizontal', + ); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider.value( + value: voltageViewModel, + child: const VoltageView(), + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('12.00 V'), findsOneWidget); + expect(find.byType(SfLinearGauge), findsOneWidget); + + expect( + (find.byType(SfLinearGauge).evaluate().first.widget as SfLinearGauge) + .interval, + 0.9); + }); +} From 0f43387b6a7fa9ac9e9420cc72f3f16333cc28a0 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Mon, 24 Jun 2024 21:21:20 -0400 Subject: [PATCH 24/28] Refactor notifications to new API changes --- lib/pages/dashboard_page.dart | 2 +- lib/services/nt_connection.dart | 7 ++++++- lib/services/robot_notifications_listener.dart | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index cd7a4a7d..6d888c04 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -224,7 +224,7 @@ class _DashboardPageState extends State with WindowListener { Future(() => _checkForUpdates(notifyIfLatest: false, notifyIfError: false)); _robotNotificationListener = RobotNotificationsListener( - connection: ntConnection, + ntConnection: widget.ntConnection, onNotification: (title, description, icon) { setState(() { ColorScheme colorScheme = Theme.of(context).colorScheme; diff --git a/lib/services/nt_connection.dart b/lib/services/nt_connection.dart index 69f85753..f08dd861 100644 --- a/lib/services/nt_connection.dart +++ b/lib/services/nt_connection.dart @@ -173,7 +173,12 @@ class NTConnection { } NT4Subscription subscribeAll(String topic, [double period = 0.1]) { - return _ntClient.subscribeAll(topic, period); + return _ntClient.subscribe( + topic: topic, + options: NT4SubscriptionOptions( + periodicRateSeconds: period, + all: true, + )); } void unSubscribe(NT4Subscription subscription) { diff --git a/lib/services/robot_notifications_listener.dart b/lib/services/robot_notifications_listener.dart index de3c1b3d..de529f56 100644 --- a/lib/services/robot_notifications_listener.dart +++ b/lib/services/robot_notifications_listener.dart @@ -8,11 +8,11 @@ import 'package:elastic_dashboard/services/nt_connection.dart'; class RobotNotificationsListener { bool _alertFirstRun = true; - final NTConnection connection; + final NTConnection ntConnection; final Function(String title, String description, Icon icon) onNotification; RobotNotificationsListener({ - required this.connection, + required this.ntConnection, required this.onNotification, }); From 160e9336ea278f39c00f4d33edc03516369c69e6 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:13:05 -0400 Subject: [PATCH 25/28] Restructure tab grid state (#47) --- lib/pages/dashboard_page.dart | 181 +++++++++--------- lib/util/tab_data.dart | 11 ++ .../draggable_layout_container.dart | 8 +- .../draggable_list_layout.dart | 8 +- .../draggable_nt_widget_container.dart | 8 +- .../draggable_widget_container.dart | 55 +++--- .../models/list_layout_model.dart | 32 ++-- lib/widgets/editable_tab_bar.dart | 24 +-- lib/widgets/tab_grid.dart | 143 +++++++------- test/widgets/editable_tab_bar_test.dart | 169 ++++++++-------- test/widgets/tab_grid_test.dart | 26 ++- 11 files changed, 334 insertions(+), 331 deletions(-) create mode 100644 lib/util/tab_data.dart diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 5da4e2b1..12400dc8 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -24,6 +24,7 @@ import 'package:elastic_dashboard/services/robot_notifications_listener.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/shuffleboard_nt_listener.dart'; import 'package:elastic_dashboard/services/update_checker.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/custom_appbar.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/layout_drag_tile.dart'; @@ -58,8 +59,6 @@ class _DashboardPageState extends State with WindowListener { late final UpdateChecker _updateChecker; late final RobotNotificationsListener _robotNotificationListener; - final List _grids = []; - final List _tabData = []; final Function _mapEquals = const DeepCollectionEquality().equals; @@ -114,7 +113,7 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.addConnectedListener(() { setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.onNTConnect(); } }); @@ -122,7 +121,7 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.addDisconnectedListener(() { setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.onNTDisconnect(); } }); @@ -167,15 +166,14 @@ class _DashboardPageState extends State with WindowListener { return; } - _tabData.add(TabData(name: tab)); - _grids.add( - TabGrid( - key: GlobalKey(), + _tabData.add(TabData( + name: tab, + tabGrid: TabGridModel( ntConnection: widget.ntConnection, preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), - ); + )); }, onWidgetAdded: (widgetData) { if (preferences.getBool(PrefKeys.layoutLocked) ?? @@ -193,13 +191,16 @@ class _DashboardPageState extends State with WindowListener { String tabName = widgetDataCopy['tab']; if (!tabNamesList.contains(tabName)) { - _tabData.add(TabData(name: tabName)); - _grids.add(TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, - )); + _tabData.add( + TabData( + name: tabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), + ), + ); tabNamesList.add(tabName); } @@ -210,7 +211,7 @@ class _DashboardPageState extends State with WindowListener { return; } - _grids[tabIndex].addWidgetFromTabJson(widgetDataCopy); + _tabData[tabIndex].tabGrid.addWidgetFromTabJson(widgetDataCopy); setState(() {}); }, @@ -288,11 +289,10 @@ class _DashboardPageState extends State with WindowListener { for (int i = 0; i < _tabData.length; i++) { TabData data = _tabData[i]; - TabGrid grid = _grids[i]; gridData.add({ 'name': data.name, - 'grid_layout': grid.toJson(), + 'grid_layout': data.tabGrid.toJson(), }); } @@ -584,7 +584,6 @@ class _DashboardPageState extends State with WindowListener { } _tabData.clear(); - _grids.clear(); for (Map data in jsonData['tabs']) { if (tryCast(data['name']) == null) { @@ -598,48 +597,47 @@ class _DashboardPageState extends State with WindowListener { continue; } - _tabData.add(TabData(name: data['name'])); - - _grids.add( - TabGrid.fromJson( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - jsonData: data['grid_layout'], - onAddWidgetPressed: _displayAddWidgetDialog, - onJsonLoadingWarning: _showJsonLoadingWarning, + _tabData.add( + TabData( + name: data['name'], + tabGrid: TabGridModel.fromJson( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + jsonData: data['grid_layout'], + onAddWidgetPressed: _displayAddWidgetDialog, + onJsonLoadingWarning: _showJsonLoadingWarning, + ), ), ); } _createDefaultTabs(); - if (_currentTabIndex >= _grids.length) { - _currentTabIndex = _grids.length - 1; + if (_currentTabIndex >= _tabData.length) { + _currentTabIndex = _tabData.length - 1; } } void _createDefaultTabs() { - if (_tabData.isEmpty || _grids.isEmpty) { + if (_tabData.isEmpty) { logger.info('Creating default Teleoperated and Autonomous tabs'); setState(() { _tabData.addAll([ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ]); - - _grids.addAll([ - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), ]); }); @@ -783,13 +781,14 @@ class _DashboardPageState extends State with WindowListener { String newTabName = 'Tab ${_tabData.length + 1}'; int newTabIndex = _tabData.length; - _tabData.add(TabData(name: newTabName)); - _grids.add( - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + _tabData.add( + TabData( + name: newTabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), ); @@ -820,11 +819,10 @@ class _DashboardPageState extends State with WindowListener { _currentTabIndex--; } - _grids[oldTabIndex].onDestroy(); + _tabData[oldTabIndex].tabGrid.onDestroy(); setState(() { _tabData.removeAt(oldTabIndex); - _grids.removeAt(oldTabIndex); }); }); }, @@ -832,14 +830,14 @@ class _DashboardPageState extends State with WindowListener { } void _lockLayout() async { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.lockLayout(); } await preferences.setBool(PrefKeys.layoutLocked, true); } void _unlockLayout() async { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.unlockLayout(); } await preferences.setBool(PrefKeys.layoutLocked, false); @@ -1051,7 +1049,7 @@ class _DashboardPageState extends State with WindowListener { await preferences.setInt(PrefKeys.gridSize, newGridSize); - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.resizeGrid(_gridSize, _gridSize); } }, @@ -1070,7 +1068,7 @@ class _DashboardPageState extends State with WindowListener { await preferences.setDouble(PrefKeys.cornerRadius, newRadius); setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.refreshAllContainers(); } }); @@ -1254,16 +1252,11 @@ class _DashboardPageState extends State with WindowListener { logger.info('Moving current tab at index $_currentTabIndex to the left'); setState(() { - // Swap the tab data + // Swap the tabs TabData tempData = _tabData[_currentTabIndex - 1]; _tabData[_currentTabIndex - 1] = _tabData[_currentTabIndex]; _tabData[_currentTabIndex] = tempData; - // Swap the tab grids - TabGrid tempGrid = _grids[_currentTabIndex - 1]; - _grids[_currentTabIndex - 1] = _grids[_currentTabIndex]; - _grids[_currentTabIndex] = tempGrid; - _currentTabIndex -= 1; }); } @@ -1281,16 +1274,11 @@ class _DashboardPageState extends State with WindowListener { logger.info('Moving current tab at index $_currentTabIndex to the right'); setState(() { - // Swap the tab data + // Swap the tabs TabData tempData = _tabData[_currentTabIndex + 1]; _tabData[_currentTabIndex + 1] = _tabData[_currentTabIndex]; _tabData[_currentTabIndex] = tempData; - // Swap the tab grids - TabGrid tempGrid = _grids[_currentTabIndex + 1]; - _grids[_currentTabIndex + 1] = _grids[_currentTabIndex]; - _grids[_currentTabIndex] = tempGrid; - _currentTabIndex += 1; }); } @@ -1379,7 +1367,9 @@ class _DashboardPageState extends State with WindowListener { Defaults.layoutLocked) ? () { setState(() { - _grids[_currentTabIndex].clearWidgets(context); + _tabData[_currentTabIndex] + .tabGrid + .clearWidgets(context); }); } : null, @@ -1514,15 +1504,18 @@ class _DashboardPageState extends State with WindowListener { _tabData[index] = newData; }); }, - onTabCreate: (tab) { + onTabCreate: () { + String tabName = 'Tab ${_tabData.length + 1}'; setState(() { - _tabData.add(tab); - _grids.add(TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, - )); + _tabData.add( + TabData( + name: tabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + )), + ); }); }, onTabDestroy: (index) { @@ -1537,11 +1530,10 @@ class _DashboardPageState extends State with WindowListener { _currentTabIndex--; } - _grids[index].onDestroy(); + _tabData[index].tabGrid.onDestroy(); setState(() { _tabData.removeAt(index); - _grids.removeAt(index); }); }); }, @@ -1549,26 +1541,31 @@ class _DashboardPageState extends State with WindowListener { setState(() => _currentTabIndex = index); }, tabData: _tabData, - tabViews: _grids, ), _AddWidgetDialog( ntConnection: widget.ntConnection, preferences: widget.preferences, - grid: () => _grids[_currentTabIndex], + grid: () => _tabData[_currentTabIndex].tabGrid, visible: _addWidgetDialogVisible, onNTDragUpdate: (globalPosition, widget) { - _grids[_currentTabIndex] + _tabData[_currentTabIndex] + .tabGrid .addDragInWidget(widget, globalPosition); }, onNTDragEnd: (widget) { - _grids[_currentTabIndex].placeDragInWidget(widget); + _tabData[_currentTabIndex] + .tabGrid + .placeDragInWidget(widget); }, onLayoutDragUpdate: (globalPosition, widget) { - _grids[_currentTabIndex] + _tabData[_currentTabIndex] + .tabGrid .addDragInWidget(widget, globalPosition); }, onLayoutDragEnd: (widget) { - _grids[_currentTabIndex].placeDragInWidget(widget); + _tabData[_currentTabIndex] + .tabGrid + .placeDragInWidget(widget); }, onClose: () { setState(() => _addWidgetDialogVisible = false); @@ -1636,7 +1633,7 @@ class _DashboardPageState extends State with WindowListener { class _AddWidgetDialog extends StatefulWidget { final NTConnection ntConnection; final SharedPreferences preferences; - final TabGrid Function() _grid; + final TabGridModel Function() _grid; final bool _visible; final Function(Offset globalPosition, WidgetContainerModel widget) @@ -1652,7 +1649,7 @@ class _AddWidgetDialog extends StatefulWidget { const _AddWidgetDialog({ required this.ntConnection, required this.preferences, - required TabGrid Function() grid, + required TabGridModel Function() grid, required bool visible, required dynamic Function(Offset, WidgetContainerModel) onNTDragUpdate, required dynamic Function(WidgetContainerModel) onNTDragEnd, diff --git a/lib/util/tab_data.dart b/lib/util/tab_data.dart new file mode 100644 index 00000000..78dc7cfd --- /dev/null +++ b/lib/util/tab_data.dart @@ -0,0 +1,11 @@ +import 'package:elastic_dashboard/widgets/tab_grid.dart'; + +class TabData { + String name; + TabGridModel tabGrid; + + TabData({ + required this.name, + required this.tabGrid, + }); +} diff --git a/lib/widgets/draggable_containers/draggable_layout_container.dart b/lib/widgets/draggable_containers/draggable_layout_container.dart index 0bfa1064..13e13008 100644 --- a/lib/widgets/draggable_containers/draggable_layout_container.dart +++ b/lib/widgets/draggable_containers/draggable_layout_container.dart @@ -3,12 +3,6 @@ import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_ abstract class DraggableLayoutContainer extends DraggableWidgetContainer { const DraggableLayoutContainer({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); } diff --git a/lib/widgets/draggable_containers/draggable_list_layout.dart b/lib/widgets/draggable_containers/draggable_list_layout.dart index 41e6db90..a1725666 100644 --- a/lib/widgets/draggable_containers/draggable_list_layout.dart +++ b/lib/widgets/draggable_containers/draggable_list_layout.dart @@ -8,13 +8,7 @@ import 'models/list_layout_model.dart'; class DraggableListLayout extends DraggableLayoutContainer { const DraggableListLayout({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); @override diff --git a/lib/widgets/draggable_containers/draggable_nt_widget_container.dart b/lib/widgets/draggable_containers/draggable_nt_widget_container.dart index ec850baa..00b6c910 100644 --- a/lib/widgets/draggable_containers/draggable_nt_widget_container.dart +++ b/lib/widgets/draggable_containers/draggable_nt_widget_container.dart @@ -8,13 +8,7 @@ import 'models/nt_widget_container_model.dart'; class DraggableNTWidgetContainer extends DraggableWidgetContainer { const DraggableNTWidgetContainer({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); @override diff --git a/lib/widgets/draggable_containers/draggable_widget_container.dart b/lib/widgets/draggable_containers/draggable_widget_container.dart index df4016a4..cbfb5446 100644 --- a/lib/widgets/draggable_containers/draggable_widget_container.dart +++ b/lib/widgets/draggable_containers/draggable_widget_container.dart @@ -4,31 +4,26 @@ import 'package:flutter_box_transform/flutter_box_transform.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/settings.dart'; -import 'package:elastic_dashboard/widgets/tab_grid.dart'; import 'models/widget_container_model.dart'; +typedef DraggableContainerUpdateFunctions = ({ + Function(WidgetContainerModel widget, Rect newRect, + TransformResult result) onUpdate, + Function(WidgetContainerModel widget) onDragBegin, + Function(WidgetContainerModel widget, Rect releaseRect, + {Offset? globalPosition}) onDragEnd, + Function(WidgetContainerModel widget) onDragCancel, + Function(WidgetContainerModel widget) onResizeBegin, + Function(WidgetContainerModel widget, Rect releaseRect) onResizeEnd, + bool Function(WidgetContainerModel widget, Rect location) isValidMoveLocation, +}); + class DraggableWidgetContainer extends StatelessWidget { - final TabGrid tabGrid; - - final Function( - WidgetContainerModel widget, Rect newRect, TransformResult result)? - onUpdate; - final Function(WidgetContainerModel widget)? onDragBegin; - final Function(WidgetContainerModel widget, Rect releaseRect, - {Offset? globalPosition})? onDragEnd; - final Function(WidgetContainerModel widget)? onDragCancel; - final Function(WidgetContainerModel widget)? onResizeBegin; - final Function(WidgetContainerModel widget, Rect releaseRect)? onResizeEnd; + final DraggableContainerUpdateFunctions? updateFunctions; const DraggableWidgetContainer({ super.key, - required this.tabGrid, - this.onUpdate, - this.onDragBegin, - this.onDragEnd, - this.onDragCancel, - this.onResizeBegin, - this.onResizeEnd, + this.updateFunctions, }); static double snapToGrid(double value, [double? gridSize]) { @@ -65,9 +60,10 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragStartLocation(model.displayRect); model.setPreviewRect(model.dragStartLocation); model.setValidLocation( - tabGrid.isValidMoveLocation(model, model.previewRect)); + updateFunctions?.isValidMoveLocation(model, model.previewRect) ?? + true); - onDragBegin?.call(model); + updateFunctions?.onDragBegin(model); controller?.setRect(model.draggingRect); }, @@ -79,21 +75,22 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragStartLocation(model.displayRect); model.setPreviewRect(model.dragStartLocation); model.setValidLocation( - tabGrid.isValidMoveLocation(model, model.previewRect)); + updateFunctions?.isValidMoveLocation(model, model.previewRect) ?? + true); - onResizeBegin?.call(model); + updateFunctions?.onResizeBegin.call(model); controller?.setRect(model.draggingRect); }, onChanged: (result, event) { if (!model.dragging && !model.resizing) { - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); return; } model.setCursorGlobalLocation(event.globalPosition); - onUpdate?.call(model, result.rect, result); + updateFunctions?.onUpdate(model, result.rect, result); controller?.setRect(model.draggingRect); }, @@ -103,7 +100,7 @@ class DraggableWidgetContainer extends StatelessWidget { } model.setDragging(false); - onDragEnd?.call(model, model.draggingRect, + updateFunctions?.onDragEnd(model, model.draggingRect, globalPosition: model.cursorGlobalLocation); controller?.setRect(model.draggingRect); @@ -113,7 +110,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); }); - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); controller?.setRect(model.draggingRect); }, @@ -124,7 +121,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); model.setResizing(false); - onResizeEnd?.call(model, model.draggingRect); + updateFunctions?.onResizeEnd(model, model.draggingRect); controller?.setRect(model.draggingRect); }, @@ -132,7 +129,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); model.setResizing(false); - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); controller?.setRect(model.draggingRect); }, diff --git a/lib/widgets/draggable_containers/models/list_layout_model.dart b/lib/widgets/draggable_containers/models/list_layout_model.dart index cb1eefe4..9ee32ae6 100644 --- a/lib/widgets/draggable_containers/models/list_layout_model.dart +++ b/lib/widgets/draggable_containers/models/list_layout_model.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -26,8 +26,14 @@ class ListLayoutModel extends LayoutContainerModel { String labelPosition = 'TOP'; - final NTConnection ntConnection; - final TabGrid tabGrid; + final TabGridModel tabGrid; + final NTWidgetContainerModel? Function( + SharedPreferences preferences, + Map jsonData, + bool enabled, { + Function(String errorMessage)? onJsonLoadingWarning, + })? ntWidgetBuilder; + final Function(WidgetContainerModel model)? onDragCancel; static List labelPositions = const [ @@ -42,9 +48,9 @@ class ListLayoutModel extends LayoutContainerModel { required super.preferences, required super.initialPosition, required super.title, - required this.ntConnection, required this.tabGrid, required this.onDragCancel, + this.ntWidgetBuilder, List? children, super.minWidth, super.minHeight, @@ -58,7 +64,7 @@ class ListLayoutModel extends LayoutContainerModel { ListLayoutModel.fromJson({ required super.jsonData, required super.preferences, - required this.ntConnection, + required this.ntWidgetBuilder, required this.tabGrid, required this.onDragCancel, super.enabled, @@ -125,15 +131,13 @@ class ListLayoutModel extends LayoutContainerModel { } for (Map childData in jsonData['children']) { - children.add( - NTWidgetContainerModel.fromJson( - ntConnection: ntConnection, - preferences: preferences, - jsonData: childData, - enabled: enabled, - onJsonLoadingWarning: onJsonLoadingWarning, - ), - ); + NTWidgetContainerModel? widgetModel = ntWidgetBuilder!( + preferences, childData, enabled, + onJsonLoadingWarning: onJsonLoadingWarning); + + if (widgetModel != null) { + children.add(widgetModel); + } } } diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 83d2f648..5a736b53 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -7,22 +7,16 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:elastic_dashboard/services/settings.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; -class TabData { - String name; - - TabData({required this.name}); -} - class EditableTabBar extends StatelessWidget { final SharedPreferences preferences; - final List tabViews; final List tabData; - final Function(TabData tab) onTabCreate; + final Function() onTabCreate; final Function(int index) onTabDestroy; final Function() onTabMoveLeft; final Function() onTabMoveRight; @@ -36,7 +30,6 @@ class EditableTabBar extends StatelessWidget { required this.preferences, required this.currentIndex, required this.tabData, - required this.tabViews, required this.onTabCreate, required this.onTabDestroy, required this.onTabMoveLeft, @@ -79,10 +72,7 @@ class EditableTabBar extends StatelessWidget { } void createTab() { - String tabName = 'Tab ${tabData.length + 1}'; - TabData data = TabData(name: tabName); - - onTabCreate.call(data); + onTabCreate(); } void closeTab(int index) { @@ -291,10 +281,10 @@ class EditableTabBar extends StatelessWidget { curve: Curves.decelerate, index: currentIndex, children: [ - for (TabGrid grid in tabViews) - ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: grid, + for (TabGridModel grid in tabData.map((e) => e.tabGrid)) + ChangeNotifierProvider.value( + value: grid, + child: const TabGrid(), ), ], ), diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 61404920..5d5472e2 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -22,30 +22,21 @@ import 'draggable_containers/models/widget_container_model.dart'; // Used to refresh the tab grid when a widget is added or removed // This doesn't use a stateful widget since everything has to be rendered at program startup or data will be lost class TabGridModel extends ChangeNotifier { - void notify() { - notifyListeners(); - } -} - -class TabGrid extends StatelessWidget { final NTConnection ntConnection; final SharedPreferences preferences; final List _widgetModels = []; MapEntry? _containerDraggingIn; + BuildContext? tabGridContext; final VoidCallback onAddWidgetPressed; - TabGridModel? model; - - TabGrid( - {super.key, - required this.ntConnection, + TabGridModel( + {required this.ntConnection, required this.preferences, required this.onAddWidgetPressed}); - TabGrid.fromJson({ - super.key, + TabGridModel.fromJson({ required this.ntConnection, required this.preferences, required Map jsonData, @@ -91,9 +82,16 @@ class TabGrid extends StatelessWidget { switch (layoutData['type']) { case 'List Layout': widget = ListLayoutModel.fromJson( - ntConnection: ntConnection, preferences: preferences, jsonData: layoutData, + ntWidgetBuilder: (preferences, jsonData, enabled, + {onJsonLoadingWarning}) => + NTWidgetContainerModel.fromJson( + ntConnection: ntConnection, + jsonData: jsonData, + preferences: preferences, + onJsonLoadingWarning: onJsonLoadingWarning, + ), enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, @@ -127,13 +125,12 @@ class TabGrid extends StatelessWidget { } Offset getLocalPosition(Offset globalPosition) { - BuildContext? context = (key as GlobalKey).currentContext; - - if (context == null) { + if (tabGridContext == null) { return Offset.zero; } - RenderBox? ancestor = context.findAncestorRenderObjectOfType(); + RenderBox? ancestor = + tabGridContext!.findAncestorRenderObjectOfType(); Offset localPosition = ancestor!.globalToLocal(globalPosition); @@ -156,10 +153,9 @@ class TabGrid extends StatelessWidget { /// /// This only applies to widgets that already have a place on the grid bool isValidMoveLocation(WidgetContainerModel widget, Rect location) { - BuildContext? context = (key as GlobalKey).currentContext; Size? gridSize; - if (context != null) { - gridSize = MediaQuery.of(context).size; + if (tabGridContext != null) { + gridSize = MediaQuery.of(tabGridContext!).size; } for (WidgetContainerModel container in _widgetModels) { @@ -536,7 +532,6 @@ class TabGrid extends StatelessWidget { ListLayoutModel createListLayout( {String title = 'List Layout', List? children}) { return ListLayoutModel( - ntConnection: ntConnection, preferences: preferences, title: title, initialPosition: Rect.fromLTWH( @@ -604,9 +599,16 @@ class TabGrid extends StatelessWidget { case 'List Layout': _widgetModels.add( ListLayoutModel.fromJson( - ntConnection: ntConnection, preferences: preferences, jsonData: widgetData, + ntWidgetBuilder: (preferences, jsonData, enabled, + {onJsonLoadingWarning}) => + NTWidgetContainerModel.fromJson( + ntConnection: ntConnection, + jsonData: jsonData, + preferences: preferences, + onJsonLoadingWarning: onJsonLoadingWarning, + ), enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, @@ -692,12 +694,6 @@ class TabGrid extends StatelessWidget { _widgetModels.clear(); } - void refresh() { - Future(() async { - model?.notify(); - }); - } - void resizeGrid(int oldSize, int newSize) { for (WidgetContainerModel widget in _widgetModels) { widget.updateGridSize(oldSize, newSize); @@ -713,37 +709,51 @@ class TabGrid extends StatelessWidget { }); } + void refresh() { + notifyListeners(); + } +} + +class TabGrid extends StatelessWidget { + const TabGrid({super.key}); + @override Widget build(BuildContext context) { - model = context.watch(); + TabGridModel model = context.watch(); - Widget getWidgetFromModel(WidgetContainerModel model) { - if (model is NTWidgetContainerModel) { + model.tabGridContext = context; + + Widget getWidgetFromModel(WidgetContainerModel widgetModel) { + if (widgetModel is NTWidgetContainerModel) { return ChangeNotifierProvider.value( - value: model, + value: widgetModel, child: DraggableNTWidgetContainer( - key: model.key, - tabGrid: this, - onUpdate: _ntContainerOnUpdate, - onDragBegin: _ntContainerOnDragBegin, - onDragEnd: _ntContainerOnDragEnd, - onDragCancel: _ntContainerOnDragCancel, - onResizeBegin: _ntContainerOnResizeBegin, - onResizeEnd: _ntContainerOnResizeEnd, + key: widgetModel.key, + updateFunctions: ( + onUpdate: model._ntContainerOnUpdate, + onDragBegin: model._ntContainerOnDragBegin, + onDragEnd: model._ntContainerOnDragEnd, + onDragCancel: model._ntContainerOnDragCancel, + onResizeBegin: model._ntContainerOnResizeBegin, + onResizeEnd: model._ntContainerOnResizeEnd, + isValidMoveLocation: model.isValidMoveLocation, + ), ), ); - } else if (model is ListLayoutModel) { + } else if (widgetModel is ListLayoutModel) { return ChangeNotifierProvider.value( - value: model, + value: widgetModel, child: DraggableListLayout( - key: model.key, - tabGrid: this, - onUpdate: _layoutContainerOnUpdate, - onDragBegin: _layoutContainerOnDragBegin, - onDragEnd: _layoutContainerOnDragEnd, - onDragCancel: _layoutContainerOnDragCancel, - onResizeBegin: _layoutContainerOnResizeBegin, - onResizeEnd: _layoutContainerOnResizeEnd, + key: widgetModel.key, + updateFunctions: ( + onUpdate: model._layoutContainerOnUpdate, + onDragBegin: model._layoutContainerOnDragBegin, + onDragEnd: model._layoutContainerOnDragEnd, + onDragCancel: model._layoutContainerOnDragCancel, + onResizeBegin: model._layoutContainerOnResizeBegin, + onResizeEnd: model._layoutContainerOnResizeEnd, + isValidMoveLocation: model.isValidMoveLocation, + ), ), ); } @@ -755,7 +765,7 @@ class TabGrid extends StatelessWidget { List draggingInWidgets = []; List previewOutlines = []; - for (WidgetContainerModel container in _widgetModels) { + for (WidgetContainerModel container in model._widgetModels) { if (container.dragging) { draggingWidgets.add( Positioned( @@ -773,7 +783,7 @@ class TabGrid extends StatelessWidget { ); } else { LayoutContainerModel? layoutContainer = - getLayoutAtLocation(container.cursorGlobalLocation); + model.getLayoutAtLocation(container.cursorGlobalLocation); if (layoutContainer == null) { previewOutlines.add( @@ -792,7 +802,7 @@ class TabGrid extends StatelessWidget { decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular( - preferences.getDouble(PrefKeys.cornerRadius) ?? + model.preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius), border: Border.all(color: Colors.yellow, width: 5.0), ), @@ -808,7 +818,7 @@ class TabGrid extends StatelessWidget { GestureDetector( onTap: () {}, onSecondaryTapUp: (details) { - if (preferences.getBool(PrefKeys.layoutLocked) ?? + if (model.preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } @@ -830,7 +840,7 @@ class TabGrid extends StatelessWidget { label: 'Remove', icon: Icons.delete_outlined, onSelected: () { - removeWidget(container); + model.removeWidget(container); }), ]; @@ -861,8 +871,8 @@ class TabGrid extends StatelessWidget { } // Also render any containers that are being dragged into the grid - if (_containerDraggingIn != null) { - WidgetContainerModel container = _containerDraggingIn!.key; + if (model._containerDraggingIn != null) { + WidgetContainerModel container = model._containerDraggingIn!.key; draggingWidgets.add( Positioned( @@ -882,15 +892,16 @@ class TabGrid extends StatelessWidget { Rect previewLocation = Rect.fromLTWH(previewX, previewY, container.displayRect.width, container.displayRect.height); - bool validLocation = isValidMoveLocation(container, previewLocation) || - isValidLayoutLocation(container.cursorGlobalLocation); + bool validLocation = + model.isValidMoveLocation(container, previewLocation) || + model.isValidLayoutLocation(container.cursorGlobalLocation); Color borderColor = (validLocation) ? Colors.lightGreenAccent.shade400 : Colors.red; - if (isValidLayoutLocation(container.cursorGlobalLocation)) { + if (model.isValidLayoutLocation(container.cursorGlobalLocation)) { LayoutContainerModel layoutContainer = - getLayoutAtLocation(container.cursorGlobalLocation)!; + model.getLayoutAtLocation(container.cursorGlobalLocation)!; previewLocation = layoutContainer.displayRect; @@ -909,7 +920,7 @@ class TabGrid extends StatelessWidget { ? Colors.white.withOpacity(0.25) : Colors.black.withOpacity(0.1), borderRadius: BorderRadius.circular( - preferences.getDouble(PrefKeys.cornerRadius) ?? + model.preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius), border: Border.all(color: borderColor, width: 5.0), ), @@ -922,7 +933,7 @@ class TabGrid extends StatelessWidget { behavior: HitTestBehavior.translucent, onTap: () {}, onSecondaryTapUp: (details) { - if (preferences.getBool(PrefKeys.layoutLocked) ?? + if (model.preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } @@ -934,12 +945,12 @@ class TabGrid extends StatelessWidget { MenuItem( label: 'Add Widget', icon: Icons.add, - onSelected: () => onAddWidgetPressed.call(), + onSelected: () => model.onAddWidgetPressed.call(), ), MenuItem( label: 'Clear Layout', icon: Icons.clear, - onSelected: () => clearWidgets(context), + onSelected: () => model.clearWidgets(context), ), ], ); diff --git a/test/widgets/editable_tab_bar_test.dart b/test/widgets/editable_tab_bar_test.dart index f60879aa..19eb6735 100644 --- a/test/widgets/editable_tab_bar_test.dart +++ b/test/widgets/editable_tab_bar_test.dart @@ -6,6 +6,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/editable_tab_bar.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; @@ -50,22 +51,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) {}, + onTabCreate: () {}, onTabDestroy: (index) {}, onTabMoveLeft: () {}, onTabMoveRight: () {}, @@ -99,22 +102,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -159,22 +164,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -219,22 +226,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -292,22 +301,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -376,22 +387,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { diff --git a/test/widgets/tab_grid_test.dart b/test/widgets/tab_grid_test.dart index b25c7b8b..cfc7fe6d 100644 --- a/test/widgets/tab_grid_test.dart +++ b/test/widgets/tab_grid_test.dart @@ -77,14 +77,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -126,14 +126,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][1]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -168,15 +168,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( - key: GlobalKey(), + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -246,15 +245,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( - key: GlobalKey(), + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), From cb6cba15ac144e43936f4487d8ea076698433d73 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:35:47 -0400 Subject: [PATCH 26/28] Fixed unit tests --- test/pages/dashboard_page_test.dart | 2 ++ test/test_util.dart | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/test/pages/dashboard_page_test.dart b/test/pages/dashboard_page_test.dart index ca5b8fab..532e0933 100644 --- a/test/pages/dashboard_page_test.dart +++ b/test/pages/dashboard_page_test.dart @@ -358,6 +358,8 @@ void main() { when(mockSubscription.periodicStream()) .thenAnswer((_) => Stream.value(null)); + when(mockSubscription.listen(any)).thenAnswer((realInvocation) {}); + when(mockNT4Connection.addTopicAnnounceListener(any)) .thenAnswer((realInvocation) { fakeAnnounceCallbacks.add(realInvocation.positionalArguments[0]); diff --git a/test/test_util.dart b/test/test_util.dart index 6a70b51b..ad58f335 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -24,6 +24,8 @@ MockNTConnection createMockOfflineNT4() { when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); + when(mockSubscription.listen(any)).thenAnswer((realInvocation) {}); + when(mockNT4Connection.isNT4Connected).thenReturn(false); when(mockNT4Connection.connectionStatus()) @@ -40,6 +42,8 @@ MockNTConnection createMockOfflineNT4() { when(mockNT4Connection.subscribe(any)).thenReturn(mockSubscription); + when(mockNT4Connection.subscribeAll(any, any)).thenReturn(mockSubscription); + when(mockNT4Connection.getTopicFromName(any)).thenReturn(null); return mockNT4Connection; @@ -79,6 +83,8 @@ MockNTConnection createMockOnlineNT4({ when(mockSubscription.periodicStream()).thenAnswer((_) => Stream.value(null)); + when(mockSubscription.listen(any)).thenAnswer((realInvocation) {}); + when(mockNT4Connection.isNT4Connected).thenReturn(true); when(mockNT4Connection.connectionStatus()) @@ -95,6 +101,8 @@ MockNTConnection createMockOnlineNT4({ when(mockNT4Connection.subscribe(any)).thenReturn(mockSubscription); + when(mockNT4Connection.subscribeAll(any, any)).thenReturn(mockSubscription); + when(mockNT4Connection.getTopicFromName(any)).thenReturn(null); when(mockNT4Connection.publishNewTopic(any, any)).thenAnswer((invocation) { @@ -131,11 +139,16 @@ MockNTConnection createMockOnlineNT4({ when(topicSubscription.periodicStream(yieldAll: anyNamed('yieldAll'))) .thenAnswer((_) => Stream.value(virtualValues?[topic.name])); + when(topicSubscription.listen(any)).thenAnswer((realInvocation) {}); + when(mockNT4Connection.getLastAnnouncedValue(topic.name)) .thenAnswer((_) => virtualValues?[topic.name]); when(mockNT4Connection.subscribe(topic.name, any)) .thenReturn(topicSubscription); + + when(mockNT4Connection.subscribeAll(topic.name, any)) + .thenReturn(topicSubscription); } return mockNT4Connection; From 942f7b0368780a04bc0c7b0b1ff72fab31cf7be0 Mon Sep 17 00:00:00 2001 From: EmeraldWither <68785503+EmeraldWither@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:52:34 -0400 Subject: [PATCH 27/28] Create unit tests for robot notifications (#51) Co-authored-by: Gold87 <91761103+Gold872@users.noreply.github.com> --- test/pages/dashboard_page_test.dart | 78 ++++++++++++ .../robot_notifications_listener_test.dart | 114 ++++++++++++++++++ test/test_util.dart | 15 ++- 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 test/services/robot_notifications_listener_test.dart diff --git a/test/pages/dashboard_page_test.dart b/test/pages/dashboard_page_test.dart index 33440590..7f331c3f 100644 --- a/test/pages/dashboard_page_test.dart +++ b/test/pages/dashboard_page_test.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:elegant_notification/elegant_notification.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -912,4 +913,81 @@ void main() { expect(find.byType(SettingsDialog), findsOneWidget); }); + + testWidgets( + 'Robot Notifications', + (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + final Map data = { + 'title': 'Robot Notification Title', + 'description': 'Robot Notification Description', + 'level': 'INFO' + }; + + MockNTConnection connection = createMockOnlineNT4(virtualTopics: [ + NT4Topic( + name: '/Elastic/RobotNotifications', + type: NT4TypeStr.kString, + properties: {}, + ) + ], virtualValues: { + '/Elastic/RobotNotifications': jsonEncode(data) + }); + MockNT4Subscription mockSub = MockNT4Subscription(); + + List listeners = []; + when(mockSub.listen(any)).thenAnswer( + (realInvocation) { + listeners.add(realInvocation.positionalArguments[0]); + mockSub.updateValue(jsonEncode(data), 0); + }, + ); + + when(mockSub.updateValue(any, any)).thenAnswer( + (invoc) { + for (var value in listeners) { + value.call( + invoc.positionalArguments[0], invoc.positionalArguments[1]); + } + }, + ); + + when(connection.subscribeAll(any, any)).thenAnswer( + (realInvocation) { + return mockSub; + }, + ); + + final notificationWidget = + find.widgetWithText(ElegantNotification, data['title']); + + await widgetTester.pumpWidget( + MaterialApp( + home: DashboardPage( + ntConnection: connection, + preferences: preferences, + version: '0.0.0.0', + ), + ), + ); + expect(notificationWidget, findsNothing); + + await widgetTester.pumpAndSettle(); + connection + .subscribeAll('/Elastic/robotnotifications', 0.2) + .updateValue(jsonEncode(data), 1); + + await widgetTester.pump(); + + expect(notificationWidget, findsOneWidget); + + await widgetTester.pumpAndSettle(); + + expect(notificationWidget, findsNothing); + + connection + .subscribeAll('/Elastic/robotnotifications', 0.2) + .updateValue(jsonEncode(data), 1); + }, + ); } diff --git a/test/services/robot_notifications_listener_test.dart b/test/services/robot_notifications_listener_test.dart new file mode 100644 index 00000000..0c0905bc --- /dev/null +++ b/test/services/robot_notifications_listener_test.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import 'package:elastic_dashboard/services/robot_notifications_listener.dart'; +import '../test_util.dart'; +import '../test_util.mocks.dart'; + +class MockNotificationCallback extends Mock { + void call(String? title, String? description, Icon? icon); +} + +void main() { + test("Robot Notifications (Initial Connection | No Existing Data) ", () { + MockNTConnection mockConnection = createMockOnlineNT4(); + + // Create a mock for the onNotification callback + MockNotificationCallback mockOnNotification = MockNotificationCallback(); + + RobotNotificationsListener notifications = RobotNotificationsListener( + ntConnection: mockConnection, + onNotification: mockOnNotification.call, + ); + + notifications.listen(); + + // Verify that subscribeAll was called with the specific parameters + verify(mockConnection.subscribeAll('/Elastic/robotnotifications', 0.2)) + .called(1); + verify(mockConnection.addDisconnectedListener(any)).called(1); + + // Verify that no other interactions have been made with the mockConnection + verifyNoMoreInteractions(mockConnection); + + // Verify that the onNotification callback was never called + verifyNever(mockOnNotification.call(any, any, any)); + }); + + test("Robot Notifications (Initial Connection | Existing Data) ", () { + MockNTConnection mockConnection = createMockOnlineNT4(); + MockNT4Subscription mockSub = MockNT4Subscription(); + + Map data = { + 'title': 'Title1', + 'description': 'Description1', + 'level': 'Info' + }; + + List listeners = []; + when(mockSub.listen(any)).thenAnswer( + (realInvocation) { + listeners.add(realInvocation.positionalArguments[0]); + mockSub.updateValue(jsonEncode(data), 0); + }, + ); + + when(mockSub.updateValue(any, any)).thenAnswer( + (invoc) { + for (var value in listeners) { + value.call( + invoc.positionalArguments[0], invoc.positionalArguments[1]); + } + }, + ); + + when(mockConnection.subscribeAll(any, any)).thenAnswer( + (realInvocation) { + mockSub.updateValue(jsonEncode(data), 0); + return mockSub; + }, + ); + + // Create a mock for the onNotification callback + MockNotificationCallback mockOnNotification = MockNotificationCallback(); + + RobotNotificationsListener notifications = RobotNotificationsListener( + ntConnection: mockConnection, + onNotification: mockOnNotification.call, + ); + + notifications.listen(); + + // Verify that subscribeAll was called with the specific parameters + verify(mockConnection.subscribeAll('/Elastic/robotnotifications', 0.2)) + .called(1); + verify(mockConnection.addDisconnectedListener(any)).called(1); + + // Verify that no other interactions have been made with the mockConnection + verifyNoMoreInteractions(mockConnection); + + // Verify that the onNotification callback was never called + verifyNever(mockOnNotification(any, any, any)); + + // Publish some data and expect an update + data['title'] = 'Title2'; + data['description'] = 'Description2'; + data['level'] = 'INFO'; + mockSub.updateValue(jsonEncode(data), 2); + + verify(mockOnNotification(data['title'], data['description'], any)); + + // Try malformed data + data['title'] = null; + data['description'] = null; + data['level'] = 'malformedlevel'; + + mockSub.updateValue(jsonEncode(data), 3); + reset(mockOnNotification); + verifyNever(mockOnNotification(any, any, any)); + }); +} diff --git a/test/test_util.dart b/test/test_util.dart index ad58f335..891b39b8 100644 --- a/test/test_util.dart +++ b/test/test_util.dart @@ -75,6 +75,8 @@ MockNTConnection createMockOnlineNT4({ Map virtualTopicsMap = {}; + List subscriptionListeners = []; + for (int i = 0; i < virtualTopics.length; i++) { virtualTopicsMap.addAll({i + 1: virtualTopics[i]}); } @@ -139,7 +141,18 @@ MockNTConnection createMockOnlineNT4({ when(topicSubscription.periodicStream(yieldAll: anyNamed('yieldAll'))) .thenAnswer((_) => Stream.value(virtualValues?[topic.name])); - when(topicSubscription.listen(any)).thenAnswer((realInvocation) {}); + when(topicSubscription.listen(any)).thenAnswer((realInvocation) { + subscriptionListeners.add(realInvocation.positionalArguments[0]); + }); + + when(topicSubscription.updateValue(any, any)).thenAnswer( + (invoc) { + for (var value in subscriptionListeners) { + value.call( + invoc.positionalArguments[0], invoc.positionalArguments[1]); + } + }, + ); when(mockNT4Connection.getLastAnnouncedValue(topic.name)) .thenAnswer((_) => virtualValues?[topic.name]); From 8c40ba8177ae990ee34c107e31aaab5677395f97 Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:35:48 -0400 Subject: [PATCH 28/28] Changed location of default theme variables --- lib/main.dart | 13 ++++++------- lib/services/settings.dart | 13 +++---------- lib/widgets/settings_dialog.dart | 11 ++++------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d41906cf..8d968a70 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,6 @@ import 'package:elastic_dashboard/services/log.dart'; import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/nt_widget_builder.dart'; import 'package:elastic_dashboard/services/settings.dart'; -import 'package:elastic_dashboard/widgets/settings_dialog.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -191,7 +190,7 @@ class Elastic extends StatefulWidget { class _ElasticState extends State { late Color teamColor = Color( widget.preferences.getInt(PrefKeys.teamColor) ?? Colors.blueAccent.value); - late FlexSchemeVariant flexSchemeVariant = FlexSchemeVariant.values + late FlexSchemeVariant themeVariant = FlexSchemeVariant.values .firstWhereOrNull((element) => element.variantName == widget.preferences.getString(PrefKeys.themeVariant)) ?? @@ -204,7 +203,7 @@ class _ElasticState extends State { colorScheme: SeedColorScheme.fromSeeds( primaryKey: teamColor, brightness: Brightness.dark, - variant: Settings.themeVariant, + variant: themeVariant, ), ); return MaterialApp( @@ -220,10 +219,10 @@ class _ElasticState extends State { widget.preferences.setInt(PrefKeys.teamColor, color.value); }), onThemeVariantChanged: (variant) async { - flexSchemeVariant = variant; - if (variant == SettingsDialog.defaultVariant) { - await widget.preferences.setString( - PrefKeys.themeVariant, SettingsDialog.defaultVariantName); + themeVariant = variant; + if (variant == Defaults.themeVariant) { + await widget.preferences + .setString(PrefKeys.themeVariant, Defaults.defaultVariantName); } else { await widget.preferences .setString(PrefKeys.themeVariant, variant.variantName); diff --git a/lib/services/settings.dart b/lib/services/settings.dart index a594ab45..60945529 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -7,16 +7,6 @@ class Settings { 'https://github.com/Gold872/elastic-dashboard'; static const String releasesLink = '$repositoryLink/releases/latest'; - // static String ipAddress = '127.0.0.1'; - // static int teamNumber = 9999; - // static int gridSize = 128; - // static bool layoutLocked = false; - // static double cornerRadius = 15.0; - // static bool showGrid = true; - // static bool autoResizeToDS = false; - - static FlexSchemeVariant themeVariant = FlexSchemeVariant.material3Legacy; - // window_manager doesn't support drag disable/maximize // disable on some platforms, this is a dumb workaround for it static bool isWindowDraggable = true; @@ -26,6 +16,9 @@ class Settings { class Defaults { static IPAddressMode ipAddressMode = IPAddressMode.driverStation; + static FlexSchemeVariant themeVariant = FlexSchemeVariant.material3Legacy; + static const String defaultVariantName = 'Material-3 Legacy (Default)'; + static const String ipAddress = '127.0.0.1'; static const int teamNumber = 9999; static const int gridSize = 128; diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index e90b7a08..9a6d4742 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -17,14 +17,12 @@ import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.da class SettingsDialog extends StatefulWidget { final NTConnection ntConnection; - static const FlexSchemeVariant defaultVariant = - FlexSchemeVariant.material3Legacy; - static const String defaultVariantName = 'Material-3 Legacy (Default)'; + static final List themeVariants = FlexSchemeVariant.values - .whereNot((variant) => variant == defaultVariant) + .whereNot((variant) => variant == Defaults.themeVariant) .map((variant) => variant.variantName) .toList() - ..add(defaultVariantName) + ..add(Defaults.defaultVariantName) ..sort(); final SharedPreferences preferences; @@ -172,7 +170,6 @@ class _SettingsDialogState extends State { (e) => e.variantName == variantName) ?? FlexSchemeVariant.material3Legacy; - Settings.themeVariant = variant; widget.onThemeVariantChanged?.call(variant); setState(() {}); }, @@ -180,7 +177,7 @@ class _SettingsDialogState extends State { themeVariantsOverride ?? SettingsDialog.themeVariants, initialValue: widget.preferences.getString(PrefKeys.themeVariant) ?? - SettingsDialog.defaultVariantName), + Defaults.defaultVariantName), ), ], ),