diff --git a/dev/conductor/ui/lib/widgets/common/tooltip.dart b/dev/conductor/ui/lib/widgets/common/tooltip.dart new file mode 100644 index 0000000000..8fb08f9a5f --- /dev/null +++ b/dev/conductor/ui/lib/widgets/common/tooltip.dart @@ -0,0 +1,30 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +/// Displays detailed info message in a tooltip widget. +class InfoTooltip extends StatelessWidget { + const InfoTooltip({ + Key? key, + required this.tooltipName, + required this.tooltipMessage, + }) : super(key: key); + + final String tooltipName; + final String tooltipMessage; + + @override + Widget build(BuildContext context) { + return Tooltip( + padding: const EdgeInsets.all(10.0), + message: tooltipMessage, + child: Icon( + Icons.info, + size: 16.0, + key: Key('${tooltipName}Tooltip'), + ), + ); + } +} diff --git a/dev/conductor/ui/lib/widgets/conductor_status.dart b/dev/conductor/ui/lib/widgets/conductor_status.dart index acbeb5b422..75cbf19aff 100644 --- a/dev/conductor/ui/lib/widgets/conductor_status.dart +++ b/dev/conductor/ui/lib/widgets/conductor_status.dart @@ -6,6 +6,8 @@ import 'package:conductor_core/conductor_core.dart'; import 'package:conductor_core/proto.dart' as pb; import 'package:flutter/material.dart'; +import 'common/tooltip.dart'; + /// Displays the current conductor state. class ConductorStatus extends StatefulWidget { const ConductorStatus({ @@ -142,44 +144,6 @@ class ConductorStatusState extends State { } } -/// Displays explanations for each status type as a tooltip. -class StatusTooltip extends StatefulWidget { - const StatusTooltip({ - Key? key, - this.engineOrFramework, - }) : super(key: key); - - final String? engineOrFramework; - - @override - State createState() => _StatusTooltipState(); -} - -class _StatusTooltipState extends State { - @override - Widget build(BuildContext context) { - return Row( - children: [ - const Text('Status'), - const SizedBox(width: 10.0), - Tooltip( - padding: const EdgeInsets.all(10.0), - message: ''' -PENDING: The cherrypick has not yet been applied. -PENDING_WITH_CONFLICT: The cherrypick has not been applied and will require manual resolution. -COMPLETED: The cherrypick has been successfully applied to the local checkout. -ABANDONED: The cherrypick will NOT be applied in this release.''', - child: Icon( - Icons.info, - size: 16.0, - key: Key('${widget.engineOrFramework}ConductorStatusTooltip'), - ), - ), - ], - ); - } -} - /// Widget for showing the engine and framework cherrypicks applied to the current release. /// /// Shows the cherrypicks' SHA and status in two separate table DataRow cells. @@ -210,7 +174,22 @@ class CherrypickTableState extends State { decoration: BoxDecoration(border: Border.all(color: Colors.grey)), columns: [ DataColumn(label: Text('${widget.engineOrFramework == 'engine' ? 'Engine' : 'Framework'} Cherrypicks')), - DataColumn(label: StatusTooltip(engineOrFramework: widget.engineOrFramework)), + DataColumn( + label: Row( + children: [ + const Text('Status'), + const SizedBox(width: 10.0), + InfoTooltip( + tooltipName: widget.engineOrFramework, + tooltipMessage: ''' +PENDING: The cherrypick has not yet been applied. +PENDING_WITH_CONFLICT: The cherrypick has not been applied and will require manual resolution. +COMPLETED: The cherrypick has been successfully applied to the local checkout. +ABANDONED: The cherrypick will NOT be applied in this release.''', + ), + ], + ), + ), ], rows: cherrypicks.map((Map cherrypick) { return DataRow( diff --git a/dev/conductor/ui/lib/widgets/create_release_substeps.dart b/dev/conductor/ui/lib/widgets/create_release_substeps.dart index 7ffa4defb3..1926ae80ea 100644 --- a/dev/conductor/ui/lib/widgets/create_release_substeps.dart +++ b/dev/conductor/ui/lib/widgets/create_release_substeps.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; +import 'common/tooltip.dart'; + /// Displays all substeps related to the 1st step. /// /// Uses input fields and dropdowns to capture all the parameters of the conductor start command. @@ -162,6 +164,21 @@ class CheckboxListTileDropdown extends StatelessWidget { CreateReleaseSubsteps.substepTitles[index], style: Theme.of(context).textTheme.subtitle1!.copyWith(color: Colors.grey[700]), ), + // Only add a tooltip for the increment dropdown + if (index == 7) + const Padding( + padding: EdgeInsets.fromLTRB(10.0, 0, 0, 0), + child: InfoTooltip( + tooltipName: 'ReleaseIncrement', + // m: has one less space than the other lines, because otherwise, + // it would display on the app one more space than the other lines + tooltipMessage: ''' +m: Indicates a standard dev release. +n: Indicates a hotfix to a dev or beta release. +y: Indicates the first dev release after a beta release. +z: Indicates a hotfix to a stable release.''', + ), + ), const SizedBox(width: 20.0), DropdownButton( hint: const Text('-'), // Dropdown initially displays the hint when no option is selected. diff --git a/dev/conductor/ui/test/widgets/common/tooltip_test.dart b/dev/conductor/ui/test/widgets/common/tooltip_test.dart new file mode 100644 index 0000000000..6428c238ed --- /dev/null +++ b/dev/conductor/ui/test/widgets/common/tooltip_test.dart @@ -0,0 +1,44 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:conductor_ui/widgets/common/tooltip.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('When the cursor hovers over the tooltip, it displays the message.', (WidgetTester tester) async { + await tester.pumpWidget( + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return MaterialApp( + home: Material( + child: Column( + children: const [ + InfoTooltip(tooltipName: 'tooltipTest', tooltipMessage: 'tooltipTestMessage'), + ], + ), + ), + ); + }, + ), + ); + + expect(find.byType(InfoTooltip), findsOneWidget); + + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + addTearDown(gesture.removePointer); + await gesture.addPointer(location: Offset.zero); + + /// Tests if the tooltip is displaying the message upon cursor hovering. + /// + /// Before hovering, the message is not found. + /// When the cursor hovers over the icon, the message is displayed and found. + expect(find.textContaining('tooltipTestMessage'), findsNothing); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byKey(const Key('tooltipTestTooltip')))); + await tester.pumpAndSettle(); + expect(find.textContaining('tooltipTestMessage'), findsOneWidget); + }); +} diff --git a/dev/conductor/ui/test/widgets/conductor_status_test.dart b/dev/conductor/ui/test/widgets/conductor_status_test.dart index 1d0ab8466c..58d29e8e30 100644 --- a/dev/conductor/ui/test/widgets/conductor_status_test.dart +++ b/dev/conductor/ui/test/widgets/conductor_status_test.dart @@ -5,7 +5,6 @@ import 'package:conductor_core/conductor_core.dart'; import 'package:conductor_core/proto.dart' as pb; import 'package:conductor_ui/widgets/conductor_status.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -119,20 +118,6 @@ void main() { expect(find.text(engineCherrypick1), findsOneWidget); expect(find.text(engineCherrypick2), findsOneWidget); expect(find.text(frameworkCherrypick), findsOneWidget); - - final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - addTearDown(gesture.removePointer); - await gesture.addPointer(location: Offset.zero); - - /// Tests the tooltip is displaying status explanations upon cursor hovering. - /// - /// Before hovering, status explanations are not found. - /// When the cursor hovers over the info icon, the explanations are displayed and found. - expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsNothing); - await tester.pump(); - await gesture.moveTo(tester.getCenter(find.byKey(const Key('engineConductorStatusTooltip')))); - await tester.pumpAndSettle(); - expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsOneWidget); }); testWidgets('Conductor_status displays correct status with a null state file except a releaseChannel', @@ -166,20 +151,6 @@ void main() { } expect(find.text(releaseChannel), findsOneWidget); expect(find.text('Unknown'), findsNWidgets(11)); - - final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - addTearDown(gesture.removePointer); - await gesture.addPointer(location: Offset.zero); - - /// Tests the tooltip is displaying status explanations upon cursor hovering. - /// - /// Before hovering, status explanations are not found. - /// When the cursor hovers over the info icon, the explanations are displayed and found. - expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsNothing); - await tester.pump(); - await gesture.moveTo(tester.getCenter(find.byKey(const Key('engineConductorStatusTooltip')))); - await tester.pumpAndSettle(); - expect(find.textContaining('PENDING: The cherrypick has not yet been applied.'), findsOneWidget); }); testWidgets('Repo Info section displays corresponding info in a dropdown fashion', (WidgetTester tester) async {