diff --git a/packages/devtools_app/lib/src/extensions/extension_screen.dart b/packages/devtools_app/lib/src/extensions/extension_screen.dart index 22a3a464f99..0c3a9d86c19 100644 --- a/packages/devtools_app/lib/src/extensions/extension_screen.dart +++ b/packages/devtools_app/lib/src/extensions/extension_screen.dart @@ -86,7 +86,7 @@ class _ExtensionScreenBodyState extends State<_ExtensionScreenBody> { Widget build(BuildContext context) { return ExtensionView( controller: extensionController!, - extension: widget.extensionConfig, + ext: widget.extensionConfig, ); } } @@ -95,19 +95,20 @@ class ExtensionView extends StatelessWidget { const ExtensionView({ super.key, required this.controller, - required this.extension, + required this.ext, }); final EmbeddedExtensionController controller; - final DevToolsExtensionConfig extension; + final DevToolsExtensionConfig ext; @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ EmbeddedExtensionHeader( - extension: extension, + ext: ext, onForceReload: () => controller.postMessage(DevToolsExtensionEventType.forceReload), ), @@ -115,7 +116,7 @@ class ExtensionView extends StatelessWidget { Expanded( child: ValueListenableBuilder( valueListenable: extensionService.enabledStateListenable( - extension.name, + ext.name, ), builder: (context, activationState, _) { if (activationState == ExtensionEnabledState.enabled) { @@ -126,7 +127,7 @@ class ExtensionView extends StatelessWidget { ); } return EnableExtensionPrompt( - extension: controller.extensionConfig, + ext: controller.extensionConfig, ); }, ), diff --git a/packages/devtools_app/lib/src/extensions/extension_screen_controls.dart b/packages/devtools_app/lib/src/extensions/extension_screen_controls.dart index 3f487e3f638..8c3afac63c0 100644 --- a/packages/devtools_app/lib/src/extensions/extension_screen_controls.dart +++ b/packages/devtools_app/lib/src/extensions/extension_screen_controls.dart @@ -18,114 +18,140 @@ import '../shared/routing.dart'; class EmbeddedExtensionHeader extends StatelessWidget { const EmbeddedExtensionHeader({ super.key, - required this.extension, + required this.ext, required this.onForceReload, }); - final DevToolsExtensionConfig extension; + final DevToolsExtensionConfig ext; final VoidCallback onForceReload; @override Widget build(BuildContext context) { final theme = Theme.of(context); - final extensionName = extension.displayName; - return Row( - children: [ - Padding( - padding: const EdgeInsets.only(left: borderPadding), - child: RichText( - text: TextSpan( - text: 'package:$extensionName extension', - style: - theme.regularTextStyle.copyWith(fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: ' (v${extension.version})', - style: theme.subtleTextStyle, - ), - ], - ), - ), - ), - const Spacer(), - RichText( - text: GaLinkTextSpan( - link: GaLink( - display: 'Report an issue', - url: extension.issueTrackerLink, - gaScreenName: gac.DevToolsExtensionEvents.extensionScreenId.name, - gaSelectedItemDescription: - gac.DevToolsExtensionEvents.extensionFeedback(extension), + final extensionName = ext.displayName; + return SizedBox( + width: double.infinity, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + alignment: WrapAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: borderPadding), + child: RichText( + text: TextSpan( + text: 'package:$extensionName extension', + style: theme.regularTextStyle + .copyWith(fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: ' (v${ext.version})', + style: theme.subtleTextStyle, + ), + ], + ), ), - context: context, ), - ), - const SizedBox(width: denseSpacing), - ValueListenableBuilder( - valueListenable: - extensionService.enabledStateListenable(extension.displayName), - builder: (context, activationState, _) { - if (activationState == ExtensionEnabledState.enabled) { - return ContextMenuButton( - iconSize: defaultIconSize, - buttonWidth: buttonMinWidth, - menuChildren: [ - PointerInterceptor( - child: MenuItemButton( - onPressed: () { - // Do not send analytics here because the user must - // confirm that they want to disable the extension from - // the [DisableExtensionDialog]. Analytics will be sent - // there if they confirm that they'd like to disable the - // extension. - unawaited( - showDialog( - context: context, - builder: (_) => - DisableExtensionDialog(extension: extension), - ), - ); - }, - child: const MaterialIconLabel( - label: 'Disable extension', - iconData: Icons.extension_off_outlined, - ), - ), + const SizedBox(width: denseSpacing), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + RichText( + text: GaLinkTextSpan( + link: GaLink( + display: 'Report an issue', + url: ext.issueTrackerLink, + gaScreenName: + gac.DevToolsExtensionEvents.extensionScreenId.name, + gaSelectedItemDescription: + gac.DevToolsExtensionEvents.extensionFeedback(ext), ), - PointerInterceptor( - child: MenuItemButton( - onPressed: () { - ga.select( - gac.DevToolsExtensionEvents.extensionScreenId.name, - gac.DevToolsExtensionEvents.extensionForceReload( - extension, - ), - ); - onForceReload(); - }, - child: const MaterialIconLabel( - label: 'Force reload extension', - iconData: Icons.refresh, - ), + context: context, + ), + ), + const SizedBox(width: denseSpacing), + _ExtensionContextMenuButton( + ext: ext, + onForceReload: onForceReload, + ), + ], + ), + ], + ), + ); + } +} + +class _ExtensionContextMenuButton extends StatelessWidget { + const _ExtensionContextMenuButton({ + required this.ext, + required this.onForceReload, + }); + + final DevToolsExtensionConfig ext; + + final VoidCallback onForceReload; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: extensionService.enabledStateListenable(ext.displayName), + builder: (context, activationState, _) { + if (activationState != ExtensionEnabledState.enabled) { + return const SizedBox.shrink(); + } + return ContextMenuButton( + iconSize: defaultIconSize, + buttonWidth: buttonMinWidth, + menuChildren: [ + PointerInterceptor( + child: MenuItemButton( + onPressed: () { + // Do not send analytics here because the user must + // confirm that they want to disable the extension from + // the [DisableExtensionDialog]. Analytics will be sent + // there if they confirm that they'd like to disable the + // extension. + unawaited( + showDialog( + context: context, + builder: (_) => DisableExtensionDialog(ext: ext), ), - ), - ], - ); - } - return const SizedBox.shrink(); - }, - ), - ], + ); + }, + child: const MaterialIconLabel( + label: 'Disable extension', + iconData: Icons.extension_off_outlined, + ), + ), + ), + PointerInterceptor( + child: MenuItemButton( + onPressed: () { + ga.select( + gac.DevToolsExtensionEvents.extensionScreenId.name, + gac.DevToolsExtensionEvents.extensionForceReload(ext), + ); + onForceReload(); + }, + child: const MaterialIconLabel( + label: 'Force reload extension', + iconData: Icons.refresh, + ), + ), + ), + ], + ); + }, ); } } @visibleForTesting class DisableExtensionDialog extends StatelessWidget { - const DisableExtensionDialog({super.key, required this.extension}); + const DisableExtensionDialog({super.key, required this.ext}); - final DevToolsExtensionConfig extension; + final DevToolsExtensionConfig ext; @override Widget build(BuildContext context) { @@ -142,7 +168,7 @@ class DisableExtensionDialog extends StatelessWidget { style: theme.regularTextStyle, children: [ TextSpan( - text: extension.displayName, + text: ext.displayName, style: theme.fixedFontStyle, ), const TextSpan(text: ' extension?'), @@ -176,13 +202,10 @@ class DisableExtensionDialog extends StatelessWidget { onPressed: () { ga.select( gac.DevToolsExtensionEvents.extensionScreenId.name, - gac.DevToolsExtensionEvents.extensionDisableManual(extension), + gac.DevToolsExtensionEvents.extensionDisableManual(ext), ); unawaited( - extensionService.setExtensionEnabledState( - extension, - enable: false, - ), + extensionService.setExtensionEnabledState(ext, enable: false), ); Navigator.of(context).pop(dialogDefaultContext); DevToolsRouterDelegate.of(context) @@ -197,12 +220,9 @@ class DisableExtensionDialog extends StatelessWidget { } class EnableExtensionPrompt extends StatelessWidget { - const EnableExtensionPrompt({ - super.key, - required this.extension, - }); + const EnableExtensionPrompt({super.key, required this.ext}); - final DevToolsExtensionConfig extension; + final DevToolsExtensionConfig ext; @override Widget build(BuildContext context) { @@ -218,7 +238,7 @@ class EnableExtensionPrompt extends StatelessWidget { style: theme.regularTextStyle, children: [ TextSpan( - text: extension.name, + text: ext.name, style: theme.fixedFontStyle, ), const TextSpan( @@ -246,13 +266,13 @@ class EnableExtensionPrompt extends StatelessWidget { label: 'Enable', gaScreen: gac.DevToolsExtensionEvents.extensionScreenId.name, gaSelection: gac.DevToolsExtensionEvents.extensionEnablePrompt( - extension, + ext, ), elevated: true, onPressed: () { unawaited( extensionService.setExtensionEnabledState( - extension, + ext, enable: true, ), ); @@ -263,12 +283,12 @@ class EnableExtensionPrompt extends StatelessWidget { label: 'No, hide this screen', gaScreen: gac.DevToolsExtensionEvents.extensionScreenId.name, gaSelection: gac.DevToolsExtensionEvents.extensionDisablePrompt( - extension, + ext, ), onPressed: () { unawaited( extensionService.setExtensionEnabledState( - extension, + ext, enable: false, ), ); diff --git a/packages/devtools_app/lib/src/framework/scaffold.dart b/packages/devtools_app/lib/src/framework/scaffold.dart index b9b7a2734e7..d8a441b2458 100644 --- a/packages/devtools_app/lib/src/framework/scaffold.dart +++ b/packages/devtools_app/lib/src/framework/scaffold.dart @@ -73,11 +73,10 @@ class DevToolsScaffold extends StatefulWidget { isEmbedded() ? 2.0 : intermediateSpacing, ); - // Note: when changing this value, also update `flameChartContainerOffset` - // from flame_chart.dart. /// Horizontal padding around the content in the DevTools UI. - static EdgeInsets get horizontalPadding => - EdgeInsets.symmetric(horizontal: isEmbedded() ? 2.0 : 16.0); + static EdgeInsets get horizontalPadding => EdgeInsets.symmetric( + horizontal: isEmbedded() ? densePadding : largeSpacing, + ); /// All of the [Screen]s that it's possible to navigate to from this Scaffold. final List screens;