Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
137 changes: 80 additions & 57 deletions packages/devtools_app/lib/src/screens/network/network_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<NetworkController>(
searchController: controller,
searchFieldWidth: isNarrow
? defaultSearchFieldWidth
: wideSearchFieldWidth,
);
final filterField = StandaloneFilterField<NetworkRequest>(
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<NetworkController>(
searchController: controller,
searchFieldWidth: screenWidth <= MediaSize.xs
? defaultSearchFieldWidth
: wideSearchFieldWidth,
),
),
const SizedBox(width: denseSpacing),
Expanded(
child: StandaloneFilterField<NetworkRequest>(
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),
Expand Down
4 changes: 3 additions & 1 deletion packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<NetworkController>),
);
final filterRect = tester.getRect(
find.byType(StandaloneFilterField<NetworkRequest>),
);
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<void> loadRequestsAndCheck(WidgetTester tester) async {
expect(find.byType(StartStopRecordingButton), findsOneWidget);
expect(find.byType(ClearButton), findsOneWidget);
Expand Down
Loading