diff --git a/lib/widgets/dialog_widgets/dialog_color_picker.dart b/lib/widgets/dialog_widgets/dialog_color_picker.dart index 7595e86e..187c7004 100644 --- a/lib/widgets/dialog_widgets/dialog_color_picker.dart +++ b/lib/widgets/dialog_widgets/dialog_color_picker.dart @@ -7,12 +7,15 @@ class DialogColorPicker extends StatefulWidget { final Function(Color color) onColorPicked; final String label; final Color initialColor; + final Color? defaultColor; - const DialogColorPicker( - {super.key, - required this.onColorPicked, - required this.label, - required this.initialColor}); + const DialogColorPicker({ + super.key, + required this.onColorPicked, + required this.label, + required this.initialColor, + this.defaultColor, + }); @override State createState() => _DialogColorPickerState(); @@ -106,6 +109,18 @@ class _DialogColorPickerState extends State { }, child: const Text('Cancel'), ), + if (widget.defaultColor != null) + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + widget.onColorPicked.call(widget.defaultColor!); + + setState(() { + selectedColor = widget.defaultColor!; + }); + }, + child: const Text('Restore Default'), + ), TextButton( onPressed: () { Navigator.of(context).pop(false); diff --git a/lib/widgets/nt_widgets/multi-topic/field_widget.dart b/lib/widgets/nt_widgets/multi-topic/field_widget.dart index 26d8a1a9..d6b572fd 100644 --- a/lib/widgets/nt_widgets/multi-topic/field_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/field_widget.dart @@ -360,6 +360,7 @@ class FieldWidgetModel extends NTWidgetModel { }, label: 'Robot Color', initialColor: robotColor, + defaultColor: Colors.red, ), ), ), @@ -372,6 +373,7 @@ class FieldWidgetModel extends NTWidgetModel { }, label: 'Trajectory Color', initialColor: trajectoryColor, + defaultColor: Colors.white, ), ), ), diff --git a/lib/widgets/nt_widgets/single_topic/boolean_box.dart b/lib/widgets/nt_widgets/single_topic/boolean_box.dart index 207a9f7a..940a28cb 100644 --- a/lib/widgets/nt_widgets/single_topic/boolean_box.dart +++ b/lib/widgets/nt_widgets/single_topic/boolean_box.dart @@ -140,6 +140,7 @@ class BooleanBoxModel extends NTWidgetModel { }, label: 'True Color', initialColor: _trueColor, + defaultColor: Colors.green, ), const SizedBox(width: 10), DialogColorPicker( @@ -148,6 +149,7 @@ class BooleanBoxModel extends NTWidgetModel { }, label: 'False Color', initialColor: _falseColor, + defaultColor: Colors.red, ), ], ), diff --git a/lib/widgets/nt_widgets/single_topic/graph.dart b/lib/widgets/nt_widgets/single_topic/graph.dart index ac350ab3..6e5fc954 100644 --- a/lib/widgets/nt_widgets/single_topic/graph.dart +++ b/lib/widgets/nt_widgets/single_topic/graph.dart @@ -113,11 +113,13 @@ class GraphModel extends NTWidgetModel { children: [ Flexible( child: DialogColorPicker( - onColorPicked: (color) { - mainColor = color; - }, - label: 'Graph Color', - initialColor: _mainColor), + onColorPicked: (color) { + mainColor = color; + }, + label: 'Graph Color', + initialColor: _mainColor, + defaultColor: Colors.cyan, + ), ), Flexible( child: DialogTextInput( diff --git a/lib/widgets/settings_dialog.dart b/lib/widgets/settings_dialog.dart index 9a6d4742..a998bdf8 100644 --- a/lib/widgets/settings_dialog.dart +++ b/lib/widgets/settings_dialog.dart @@ -153,6 +153,7 @@ class _SettingsDialogState extends State { onColorPicked: (color) => widget.onColorChanged?.call(color), label: 'Team Color', initialColor: currentColor, + defaultColor: Colors.blueAccent, ), ), ], diff --git a/test/widgets/dialog_widgets/dialog_color_picker_test.dart b/test/widgets/dialog_widgets/dialog_color_picker_test.dart new file mode 100644 index 00000000..8c1c1a26 --- /dev/null +++ b/test/widgets/dialog_widgets/dialog_color_picker_test.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_color_picker.dart'; +import '../../test_util.dart'; + +class MockColorCallback extends Mock { + void onColorChanged(Color? color); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Color picker select', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + MockColorCallback mockCallback = MockColorCallback(); + + Color? calledBackColor; + + when(mockCallback.onColorChanged(any)).thenAnswer((realInvocation) { + calledBackColor = realInvocation.positionalArguments[0]; + }); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DialogColorPicker( + onColorPicked: mockCallback.onColorChanged, + label: 'Color Picker', + initialColor: Colors.green, + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Color Picker'), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + + await widgetTester.tap(find.byType(ElevatedButton)); + await widgetTester.pumpAndSettle(); + + expect(find.text('Cancel'), findsOneWidget); + expect(find.text('Restore Default'), findsNothing); + expect(find.text('Save'), findsOneWidget); + + final hexInput = find.widgetWithText(TextField, 'Hex Code'); + + expect(hexInput, findsOneWidget); + + await widgetTester.enterText(hexInput, '0000FF'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(calledBackColor, isNull); + + await widgetTester.tap(find.text('Save')); + await widgetTester.pumpAndSettle(); + + expect(calledBackColor, isNotNull); + expect(calledBackColor!.value, 0xFF0000FF); + }); + + testWidgets('Color picker cancel', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + MockColorCallback mockCallback = MockColorCallback(); + + Color? calledBackColor; + + when(mockCallback.onColorChanged(any)).thenAnswer((realInvocation) { + calledBackColor = realInvocation.positionalArguments[0]; + }); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DialogColorPicker( + onColorPicked: mockCallback.onColorChanged, + label: 'Color Picker', + initialColor: Colors.green, + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Color Picker'), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + + await widgetTester.tap(find.byType(ElevatedButton)); + await widgetTester.pumpAndSettle(); + + expect(find.text('Cancel'), findsOneWidget); + expect(find.text('Restore Default'), findsNothing); + expect(find.text('Save'), findsOneWidget); + + final hexInput = find.widgetWithText(TextField, 'Hex Code'); + + expect(hexInput, findsOneWidget); + + await widgetTester.enterText(hexInput, '0000FF'); + await widgetTester.testTextInput.receiveAction(TextInputAction.done); + await widgetTester.pumpAndSettle(); + + expect(calledBackColor, isNull); + + await widgetTester.tap(find.text('Cancel')); + await widgetTester.pumpAndSettle(); + + expect(calledBackColor, isNotNull); + expect(calledBackColor!.value, Colors.green.value); + }); + + testWidgets('Color picker restore default', (widgetTester) async { + FlutterError.onError = ignoreOverflowErrors; + + MockColorCallback mockCallback = MockColorCallback(); + + Color? calledBackColor; + + when(mockCallback.onColorChanged(any)).thenAnswer((realInvocation) { + calledBackColor = realInvocation.positionalArguments[0]; + }); + + await widgetTester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DialogColorPicker( + onColorPicked: mockCallback.onColorChanged, + label: 'Color Picker', + initialColor: Colors.green, + defaultColor: Colors.red, + ), + ), + ), + ); + + await widgetTester.pumpAndSettle(); + + expect(find.text('Color Picker'), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + + await widgetTester.tap(find.byType(ElevatedButton)); + await widgetTester.pumpAndSettle(); + + expect(find.text('Cancel'), findsOneWidget); + expect(find.text('Restore Default'), findsOneWidget); + expect(find.text('Save'), findsOneWidget); + + await widgetTester.tap(find.text('Restore Default')); + await widgetTester.pumpAndSettle(); + + expect(calledBackColor, isNotNull); + expect(calledBackColor!.value, Colors.red.value); + }); +}