diff --git a/packages/devtools_app/lib/src/framework/scaffold/status_line.dart b/packages/devtools_app/lib/src/framework/scaffold/status_line.dart index a292d6ef68c..3570fc9ef07 100644 --- a/packages/devtools_app/lib/src/framework/scaffold/status_line.dart +++ b/packages/devtools_app/lib/src/framework/scaffold/status_line.dart @@ -90,9 +90,12 @@ class StatusLine extends StatelessWidget { final pageStatus = currentScreen.buildStatus(context); final widerThanXxs = screenWidth > MediaSize.xxs; final screenMetaData = ScreenMetaData.lookup(currentScreen.screenId); + // The video tutorial link is secondary, so it is the first thing to go when + // there is not enough room for the status items. final showVideoTutorial = - screenMetaData?.tutorialVideoTimestamp != null || - screenMetaData?.tutorialVideoUrl != null; + screenWidth > MediaSize.xs && + (screenMetaData?.tutorialVideoTimestamp != null || + screenMetaData?.tutorialVideoUrl != null); return [ Row( mainAxisSize: MainAxisSize.min, @@ -121,7 +124,7 @@ class StatusLine extends StatelessWidget { pageStatus, BulletSpacer(color: foregroundColor), ], - buildConnectionStatus(context, screenWidth), + Flexible(child: buildConnectionStatus(context, screenWidth)), if (widerThanXxs && isEmbedded) ...[ BulletSpacer(color: foregroundColor), Row( diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index ef42f6476b3..09198b340e8 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -202,66 +202,89 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> } final screenWidth = ScreenSize(context).width; + final isNarrow = screenWidth <= MediaSize.xs; + final recordButton = StartStopRecordingButton( + recording: _recording, + onPressed: () async => await controller.togglePolling(!_recording), + tooltipOverride: _recording + ? 'Stop recording network traffic' + : 'Resume recording network traffic', + minScreenWidthForText: double.infinity, + gaScreen: gac.network, + gaSelection: _recording ? gac.pause : gac.resume, + ); + final clearButton = ClearButton( + minScreenWidthForText: _NetworkProfilerControls._includeTextWidth, + gaScreen: gac.network, + gaSelection: gac.clear, + onPressed: controller.clear, + ); + // TODO(kenz): fix focus issue when state is refreshed + final searchField = SearchField( + searchController: controller, + searchFieldWidth: isNarrow + ? defaultSearchFieldWidth + : wideSearchFieldWidth, + ); + final filterField = StandaloneFilterField( + controller: controller, + filteredItem: 'request', + ); + final openSaveButtons = OpenSaveButtonGroup( + screenId: ScreenMetaData.network.id, + saveFormats: const [SaveFormat.devtools, SaveFormat.har], + gaItemForSaveFormatSelection: (SaveFormat format) => switch (format) { + SaveFormat.devtools => gac.saveFile, + SaveFormat.har => gac.NetworkEvent.downloadAsHar.name, + }, + onSave: (SaveFormat format) async { + switch (format) { + case SaveFormat.devtools: + await controller.fetchFullDataBeforeExport(); + controller.exportData(); + case SaveFormat.har: + await controller.exportAsHarFile(); + } + }, + ); + return Column( children: [ - Row( - children: [ - StartStopRecordingButton( - recording: _recording, - onPressed: () async => - await controller.togglePolling(!_recording), - tooltipOverride: _recording - ? 'Stop recording network traffic' - : 'Resume recording network traffic', - minScreenWidthForText: double.infinity, - gaScreen: gac.network, - gaSelection: _recording ? gac.pause : gac.resume, - ), - const SizedBox(width: denseSpacing), - ClearButton( - minScreenWidthForText: _NetworkProfilerControls._includeTextWidth, - gaScreen: gac.network, - gaSelection: gac.clear, - onPressed: controller.clear, - ), - const SizedBox(width: denseSpacing), - // TODO(kenz): fix focus issue when state is refreshed - Expanded( - child: SearchField( - searchController: controller, - searchFieldWidth: screenWidth <= MediaSize.xs - ? defaultSearchFieldWidth - : wideSearchFieldWidth, - ), - ), - const SizedBox(width: denseSpacing), - Expanded( - child: StandaloneFilterField( - controller: controller, - filteredItem: 'request', + if (isNarrow) + // On narrow screens the search and filter fields do not fit next to + // the buttons without overlapping, so they get their own rows. + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + recordButton, + const SizedBox(width: denseSpacing), + clearButton, + const Spacer(), + openSaveButtons, + ], ), - ), - const SizedBox(width: denseSpacing), - OpenSaveButtonGroup( - screenId: ScreenMetaData.network.id, - saveFormats: const [SaveFormat.devtools, SaveFormat.har], - gaItemForSaveFormatSelection: (SaveFormat format) => - switch (format) { - SaveFormat.devtools => gac.saveFile, - SaveFormat.har => gac.NetworkEvent.downloadAsHar.name, - }, - onSave: (SaveFormat format) async { - switch (format) { - case SaveFormat.devtools: - await controller.fetchFullDataBeforeExport(); - controller.exportData(); - case SaveFormat.har: - await controller.exportAsHarFile(); - } - }, - ), - ], - ), + const SizedBox(height: denseSpacing), + searchField, + const SizedBox(height: denseSpacing), + filterField, + ], + ) + else + Row( + children: [ + recordButton, + const SizedBox(width: denseSpacing), + clearButton, + const SizedBox(width: denseSpacing), + Expanded(child: searchField), + const SizedBox(width: denseSpacing), + Expanded(child: filterField), + const SizedBox(width: denseSpacing), + openSaveButtons, + ], + ), if (!_recording) const Padding( padding: EdgeInsets.only(top: intermediateSpacing), diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 86b2a491380..09966fe7567 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -44,7 +44,9 @@ TODO: Remove this section if there are not any updates. ## Network profiler updates -TODO: Remove this section if there are not any updates. +* Fixed the Network screen's toolbar controls overlapping, and the status bar +items being clipped, on narrow screens. +[#10076](https://github.com/flutter/devtools/pull/10076) ## Logging updates diff --git a/packages/devtools_app/test/framework/scaffold/scaffold_test.dart b/packages/devtools_app/test/framework/scaffold/scaffold_test.dart index 34dc608baa0..82c7748dc79 100644 --- a/packages/devtools_app/test/framework/scaffold/scaffold_test.dart +++ b/packages/devtools_app/test/framework/scaffold/scaffold_test.dart @@ -354,6 +354,29 @@ void main() { expect(find.byType(ExtensionSettingsAction), findsNothing); }, ); + + for (final width in const [410.0, 330.0]) { + testWidgetsWithWindowSize( + 'StatusLine does not overflow when embedded on a ${width}px wide screen', + Size(width, 800.0), + (WidgetTester tester) async { + setGlobal(IdeTheme, IdeTheme(embedMode: EmbedMode.embedOne)); + await tester.pumpWidget( + wrapScaffold( + const Scaffold( + body: StatusLine( + currentScreen: _screen1, + isEmbedded: true, + isConnected: false, + ), + ), + ), + ); + expect(find.byType(StatusLine), findsOneWidget); + expect(tester.takeException(), isNull); + }, + ); + } } class _TestScreen extends Screen { diff --git a/packages/devtools_app/test/screens/network/network_profiler_test.dart b/packages/devtools_app/test/screens/network/network_profiler_test.dart index 9175ccbf598..242fbba9181 100644 --- a/packages/devtools_app/test/screens/network/network_profiler_test.dart +++ b/packages/devtools_app/test/screens/network/network_profiler_test.dart @@ -9,6 +9,7 @@ import 'package:devtools_app/devtools_app.dart'; import 'package:devtools_app/src/screens/network/network_request_inspector.dart'; import 'package:devtools_app/src/screens/network/network_request_inspector_views.dart'; import 'package:devtools_app/src/shared/http/http.dart'; +import 'package:devtools_app/src/shared/ui/file_import.dart'; import 'package:devtools_app/src/shared/ui/tab.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; @@ -108,6 +109,37 @@ void main() { await clearTimeouts(tester); }); + for (final narrowSize in const [Size(410.0, 800.0), Size(330.0, 800.0)]) { + testWidgetsWithWindowSize( + 'controls do not overlap or overflow at ${narrowSize.width}px wide', + narrowSize, + (WidgetTester tester) async { + controller = NetworkController(); + await pumpNetworkScreen(tester); + await tester.pumpAndSettle(); + + expect(tester.takeException(), isNull); + + final searchRect = tester.getRect( + find.byType(SearchField), + ); + final filterRect = tester.getRect( + find.byType(StandaloneFilterField), + ); + final openSaveRect = tester.getRect(find.byType(OpenSaveButtonGroup)); + + expect(searchRect.overlaps(filterRect), isFalse); + expect(searchRect.overlaps(openSaveRect), isFalse); + expect(filterRect.overlaps(openSaveRect), isFalse); + for (final rect in [searchRect, filterRect, openSaveRect]) { + expect(rect.right, lessThanOrEqualTo(narrowSize.width)); + } + + await clearTimeouts(tester); + }, + ); + } + Future loadRequestsAndCheck(WidgetTester tester) async { expect(find.byType(StartStopRecordingButton), findsOneWidget); expect(find.byType(ClearButton), findsOneWidget);