From 5f2074d48c1809d6909d7d6ca07bcf185ae675b8 Mon Sep 17 00:00:00 2001 From: Gold872 Date: Sun, 17 Dec 2023 19:26:52 -0500 Subject: [PATCH] Added option to change default period and graph period --- lib/main.dart | 5 + lib/pages/dashboard_page.dart | 28 ++ lib/services/nt_widget_builder.dart | 4 +- lib/services/settings.dart | 6 +- lib/widgets/nt_widgets/nt_widget.dart | 4 +- .../nt_widgets/single_topic/graph.dart | 3 +- lib/widgets/settings_dialog.dart | 382 +++++++++++------- test/widgets/settings_dialog_test.dart | 75 ++++ 8 files changed, 350 insertions(+), 157 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f3b882fd..5da1cb66 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,6 +62,11 @@ void main() async { 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(); diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 76e6fb86..1182eb83 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -881,6 +881,34 @@ class _DashboardPageState extends State with WindowListener { await _preferences.setBool(PrefKeys.autoResizeToDS, value); }, + onDefaultPeriodChanged: (value) async { + if (value == null) { + return; + } + double? newPeriod = double.tryParse(value); + + if (newPeriod == null) { + return; + } + + await _preferences.setDouble(PrefKeys.defaultPeriod, newPeriod); + + setState(() => Settings.defaultPeriod = newPeriod); + }, + onDefaultGraphPeriodChanged: (value) async { + if (value == null) { + return; + } + double? newPeriod = double.tryParse(value); + + if (newPeriod == null) { + return; + } + + await _preferences.setDouble(PrefKeys.defaultGraphPeriod, newPeriod); + + setState(() => Settings.defaultGraphPeriod = newPeriod); + }, onColorChanged: widget.onColorChanged, ), ); diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 48a44251..e938ddeb 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -267,8 +267,10 @@ class NTWidgetBuilder { String type, String topic, { String dataType = 'Unknown', - double period = Settings.defaultPeriod, + double? period, }) { + period ??= Settings.defaultPeriod; + ensureInitialized(); if (_widgetNameBuildMap.containsKey(type)) { diff --git a/lib/services/settings.dart b/lib/services/settings.dart index 42b6277d..07d1f9ca 100644 --- a/lib/services/settings.dart +++ b/lib/services/settings.dart @@ -19,8 +19,8 @@ class Settings { static bool isWindowDraggable = true; static bool isWindowMaximizable = true; - static const double defaultPeriod = 0.1; - static const double defaultGraphPeriod = 0.033; + static double defaultPeriod = 0.1; + static double defaultGraphPeriod = 0.033; } class PrefKeys { @@ -33,4 +33,6 @@ class PrefKeys { static String cornerRadius = 'corner_radius'; static String showGrid = 'show_grid'; static String autoResizeToDS = 'auto_resize_to_driver_station'; + static String defaultPeriod = 'default_period'; + static String defaultGraphPeriod = 'default_graph_period'; } diff --git a/lib/widgets/nt_widgets/nt_widget.dart b/lib/widgets/nt_widgets/nt_widget.dart index 8633ebc1..63c050e6 100644 --- a/lib/widgets/nt_widgets/nt_widget.dart +++ b/lib/widgets/nt_widgets/nt_widget.dart @@ -41,8 +41,10 @@ abstract class NTWidget extends StatelessWidget { super.key, required this.topic, this.dataType = 'Unknown', - this.period = Settings.defaultPeriod, + double? period, }) { + this.period = period ?? Settings.defaultPeriod; + init(); } diff --git a/lib/widgets/nt_widgets/single_topic/graph.dart b/lib/widgets/nt_widgets/single_topic/graph.dart index 9c23c9f6..1da2ae2c 100644 --- a/lib/widgets/nt_widgets/single_topic/graph.dart +++ b/lib/widgets/nt_widgets/single_topic/graph.dart @@ -8,7 +8,6 @@ import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:elastic_dashboard/services/nt4_client.dart'; -import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_color_picker.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; @@ -34,7 +33,7 @@ class GraphWidget extends NTWidget { this.maxValue, this.mainColor = Colors.cyan, super.dataType, - super.period = Settings.defaultGraphPeriod, + super.period, }) : super(); GraphWidget.fromJson({super.key, required Map jsonData}) diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index b70b556c..3baac154 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -23,6 +23,8 @@ class SettingsDialog extends StatefulWidget { final Function(String? gridSize)? onGridSizeChanged; final Function(String? radius)? onCornerRadiusChanged; final Function(bool value)? onResizeToDSChanged; + final Function(String? value)? onDefaultPeriodChanged; + final Function(String? value)? onDefaultGraphPeriodChanged; const SettingsDialog({ super.key, @@ -35,6 +37,8 @@ class SettingsDialog extends StatefulWidget { this.onGridSizeChanged, this.onCornerRadiusChanged, this.onResizeToDSChanged, + this.onDefaultPeriodChanged, + this.onDefaultGraphPeriodChanged, }); @override @@ -44,162 +48,38 @@ class SettingsDialog extends StatefulWidget { class _SettingsDialogState extends State { @override Widget build(BuildContext context) { - Color currentColor = Color( - widget.preferences.getInt('team_color') ?? Colors.blueAccent.value); - return AlertDialog( title: const Text('Settings'), - content: SizedBox( - width: 350, - child: Column( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), + content: Container( + constraints: const BoxConstraints( + maxHeight: 250, + maxWidth: 750, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - child: DialogTextInput( - initialText: widget.preferences - .getInt(PrefKeys.teamNumber) - .toString(), - label: 'Team Number', - onSubmit: (data) { - setState(() { - widget.onTeamNumberChanged?.call(data); - }); - }, - formatter: - FilteringTextInputFormatter.allow(RegExp(r"[0-9]")), - ), - ), - Flexible( - child: DialogColorPicker( - onColorPicked: (color) => - widget.onColorChanged?.call(color), - label: 'Team Color', - initialColor: currentColor, - ), - ), - ], + Flexible( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ..._generalSettings(), + const Divider(), + ..._ipAddressSettings(), + ], + ), ), - const Divider(), - const Align( - alignment: Alignment.topLeft, - child: Text('IP Address Settings'), - ), - const SizedBox(height: 5), - const Text('IP Address Mode'), - DialogDropdownChooser( - onSelectionChanged: (mode) { - if (mode == null) { - return; - } - - widget.onIPAddressModeChanged?.call(mode); - - setState(() {}); - }, - choices: IPAddressMode.values, - initialValue: Settings.ipAddressMode, - ), - const SizedBox(height: 5), - StreamBuilder( - stream: ntConnection.dsConnectionStatus(), - initialData: ntConnection.isDSConnected, - builder: (context, snapshot) { - bool dsConnected = tryCast(snapshot.data) ?? false; - - return DialogTextInput( - enabled: Settings.ipAddressMode == IPAddressMode.custom || - (Settings.ipAddressMode == - IPAddressMode.driverStation && - !dsConnected), - initialText: - widget.preferences.getString(PrefKeys.ipAddress), - label: 'IP Address', - onSubmit: (String? data) { - setState(() { - widget.onIPAddressChanged?.call(data); - }); - }, - ); - }), - const Divider(), - const Align( - alignment: Alignment.topLeft, - child: Text('Grid Settings'), - ), - const SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - child: DialogToggleSwitch( - initialValue: - widget.preferences.getBool(PrefKeys.showGrid) ?? - Settings.showGrid, - label: 'Show Grid', - onToggle: (value) { - setState(() { - widget.onGridToggle?.call(value); - }); - }, - ), - ), - Flexible( - child: DialogTextInput( - initialText: widget.preferences - .getInt(PrefKeys.gridSize) - ?.toString() ?? - Settings.gridSize.toString(), - label: 'Grid Size', - onSubmit: (value) { - setState(() { - widget.onGridSizeChanged?.call(value); - }); - }, - formatter: - FilteringTextInputFormatter.allow(RegExp(r"[0-9]")), - ), - ) - ], - ), - const Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - flex: 2, - child: DialogTextInput( - initialText: widget.preferences - .getDouble(PrefKeys.cornerRadius) - ?.toString() ?? - Settings.cornerRadius.toString(), - label: 'Corner Radius', - onSubmit: (value) { - setState(() { - widget.onCornerRadiusChanged?.call(value); - }); - }, - formatter: - FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")), - ), - ), - Flexible( - flex: 3, - child: DialogToggleSwitch( - initialValue: - widget.preferences.getBool(PrefKeys.autoResizeToDS) ?? - Settings.autoResizeToDS, - label: 'Resize to Driver Station Height', - onToggle: (value) { - setState(() { - widget.onResizeToDSChanged?.call(value); - }); - }, - ), - ), - ], + const VerticalDivider(), + Flexible( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ..._gridSettings(), + const Divider(), + ..._networkTablesSettings(), + ], + ), ), ], ), @@ -212,4 +92,204 @@ class _SettingsDialogState extends State { ], ); } + + List _generalSettings() { + Color currentColor = Color( + widget.preferences.getInt('team_color') ?? Colors.blueAccent.value); + return [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: DialogTextInput( + initialText: + widget.preferences.getInt(PrefKeys.teamNumber).toString(), + label: 'Team Number', + onSubmit: (data) { + setState(() { + widget.onTeamNumberChanged?.call(data); + }); + }, + formatter: FilteringTextInputFormatter.allow(RegExp(r"[0-9]")), + ), + ), + Flexible( + child: DialogColorPicker( + onColorPicked: (color) => widget.onColorChanged?.call(color), + label: 'Team Color', + initialColor: currentColor, + ), + ), + ], + ), + ]; + } + + List _ipAddressSettings() { + return [ + const Align( + alignment: Alignment.topLeft, + child: Text('IP Address Settings'), + ), + const SizedBox(height: 5), + const Text('IP Address Mode'), + DialogDropdownChooser( + onSelectionChanged: (mode) { + if (mode == null) { + return; + } + + widget.onIPAddressModeChanged?.call(mode); + + setState(() {}); + }, + choices: IPAddressMode.values, + initialValue: Settings.ipAddressMode, + ), + const SizedBox(height: 5), + StreamBuilder( + stream: ntConnection.dsConnectionStatus(), + initialData: ntConnection.isDSConnected, + builder: (context, snapshot) { + bool dsConnected = tryCast(snapshot.data) ?? false; + + return DialogTextInput( + enabled: Settings.ipAddressMode == IPAddressMode.custom || + (Settings.ipAddressMode == IPAddressMode.driverStation && + !dsConnected), + initialText: widget.preferences.getString(PrefKeys.ipAddress), + label: 'IP Address', + onSubmit: (String? data) { + setState(() { + widget.onIPAddressChanged?.call(data); + }); + }, + ); + }) + ]; + } + + List _gridSettings() { + return [ + const Align( + alignment: Alignment.topLeft, + child: Text('Grid Settings'), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: DialogToggleSwitch( + initialValue: widget.preferences.getBool(PrefKeys.showGrid) ?? + Settings.showGrid, + label: 'Show Grid', + onToggle: (value) { + setState(() { + widget.onGridToggle?.call(value); + }); + }, + ), + ), + Flexible( + child: DialogTextInput( + initialText: + widget.preferences.getInt(PrefKeys.gridSize)?.toString() ?? + Settings.gridSize.toString(), + label: 'Grid Size', + onSubmit: (value) { + setState(() { + widget.onGridSizeChanged?.call(value); + }); + }, + formatter: FilteringTextInputFormatter.allow(RegExp(r"[0-9]")), + ), + ) + ], + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + flex: 2, + child: DialogTextInput( + initialText: widget.preferences + .getDouble(PrefKeys.cornerRadius) + ?.toString() ?? + Settings.cornerRadius.toString(), + label: 'Corner Radius', + onSubmit: (value) { + setState(() { + widget.onCornerRadiusChanged?.call(value); + }); + }, + formatter: FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")), + ), + ), + Flexible( + flex: 3, + child: DialogToggleSwitch( + initialValue: + widget.preferences.getBool(PrefKeys.autoResizeToDS) ?? + Settings.autoResizeToDS, + label: 'Resize to Driver Station Height', + onToggle: (value) { + setState(() { + widget.onResizeToDSChanged?.call(value); + }); + }, + ), + ), + ], + ), + ]; + } + + List _networkTablesSettings() { + return [ + const Align( + alignment: Alignment.topLeft, + child: Text('Network Tables Settings'), + ), + const SizedBox(height: 5), + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: DialogTextInput( + initialText: + (widget.preferences.getDouble(PrefKeys.defaultPeriod) ?? + Settings.defaultPeriod) + .toString(), + label: 'Default Period (Seconds)', + onSubmit: (value) { + setState(() { + widget.onDefaultPeriodChanged?.call(value); + }); + }, + formatter: + FilteringTextInputFormatter.allow(RegExp(r"[0-9.]"))), + ), + Flexible( + child: DialogTextInput( + initialText: (widget.preferences + .getDouble(PrefKeys.defaultGraphPeriod) ?? + Settings.defaultGraphPeriod) + .toString(), + label: 'Default Graph Period', + onSubmit: (value) { + setState(() { + widget.onDefaultGraphPeriodChanged?.call(value); + }); + }, + formatter: + FilteringTextInputFormatter.allow(RegExp(r"[0-9.]"))), + ), + ], + ), + ), + ]; + } } diff --git a/test/widgets/settings_dialog_test.dart b/test/widgets/settings_dialog_test.dart index edaebd1c..6579d0c0 100644 --- a/test/widgets/settings_dialog_test.dart +++ b/test/widgets/settings_dialog_test.dart @@ -33,6 +33,10 @@ class FakeSettingsMethods { void changeCornerRadius() {} void changeDSAutoResize() {} + + void changeDefaultPeriod() {} + + void changeDefaultGraphPeriod() {} } void main() { @@ -51,6 +55,8 @@ void main() { PrefKeys.gridSize: 128, PrefKeys.cornerRadius: 15.0, PrefKeys.autoResizeToDS: false, + PrefKeys.defaultPeriod: 0.10, + PrefKeys.defaultGraphPeriod: 0.033, }); preferences = await SharedPreferences.getInstance(); @@ -85,6 +91,8 @@ void main() { expect(find.text('Grid Size'), findsWidgets); expect(find.text('Corner Radius'), findsOneWidget); expect(find.text('Resize to Driver Station Height'), findsOneWidget); + expect(find.text('Default Period (Seconds)'), findsOneWidget); + expect(find.text('Default Graph Period'), findsOneWidget); final closeButton = find.widgetWithText(TextButton, 'Close'); @@ -408,4 +416,71 @@ void main() { expect(preferences.getBool(PrefKeys.autoResizeToDS), false); verify(fakeSettings.changeDSAutoResize()).called(2); }); + + testWidgets('Change default period', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + setupMockOfflineNT4(); + + await widgetTester.pumpWidget(MaterialApp( + home: Scaffold( + body: SettingsDialog( + onDefaultPeriodChanged: (period) async { + fakeSettings.changeDefaultPeriod(); + + await preferences.setDouble( + PrefKeys.defaultPeriod, double.parse(period!)); + }, + preferences: preferences, + ), + ), + )); + + await widgetTester.pumpAndSettle(); + + final periodField = + find.widgetWithText(DialogTextInput, 'Default Period (Seconds)'); + + expect(periodField, findsOneWidget); + + await widgetTester.enterText(periodField, '0.05'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(preferences.getDouble(PrefKeys.defaultPeriod), 0.05); + verify(fakeSettings.changeDefaultPeriod()).called(greaterThanOrEqualTo(1)); + }); + + testWidgets('Change default graph period', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + setupMockOfflineNT4(); + + await widgetTester.pumpWidget(MaterialApp( + home: Scaffold( + body: SettingsDialog( + onDefaultGraphPeriodChanged: (period) async { + fakeSettings.changeDefaultGraphPeriod(); + + await preferences.setDouble( + PrefKeys.defaultGraphPeriod, double.parse(period!)); + }, + preferences: preferences, + ), + ), + )); + + await widgetTester.pumpAndSettle(); + + final periodField = + find.widgetWithText(DialogTextInput, 'Default Graph Period'); + + expect(periodField, findsOneWidget); + + await widgetTester.enterText(periodField, '0.05'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(preferences.getDouble(PrefKeys.defaultGraphPeriod), 0.05); + verify(fakeSettings.changeDefaultGraphPeriod()) + .called(greaterThanOrEqualTo(1)); + }); }