Skip to content

Commit 27e55f7

Browse files
authored
Add the ability to enable platform types from the device list in the sidebar (#6688)
* Add the ability to enable platform types from the device list in the sidebar For each unique platform type in the set of unsupported devices, this adds a new row ("Enable web for this project"). Clicking it triggers the same flow as VS Code (which currently involves a toast in the corner to confirm we can run `flutter create`). In future we might want to change this prompt to be in the sidebar (so it's less likely you'll miss it). Related PR for Dart-Code is at Dart-Code/Dart-Code#4831 See Dart-Code/Dart-Code#4820 See #6563 * Tweak analytics
1 parent a79c034 commit 27e55f7

8 files changed

Lines changed: 157 additions & 9 deletions

File tree

‎packages/devtools_app/lib/src/shared/analytics/constants/_vs_code_sidebar_constants.dart‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ enum VsCodeFlutterSidebar {
1414

1515
static String get id => VsCodeFlutterSidebar.vsCodeFlutterSidebar.name;
1616

17+
/// Analytics event for when a request to enable a new platform type is sent
18+
/// to VS Code.
19+
static String enablePlatformType(String platformType) =>
20+
'enablePlatformType-$platformType';
21+
1722
/// Analytics event that is sent when a DevTools screen is opened from the
1823
/// actions toolbar for a debug session.
1924
static String openDevToolsScreen(String screen) =>

‎packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,20 @@ class DartToolingApiImpl implements DartToolingApi {
4343
}
4444
final postMessageController = StreamController();
4545
postMessageController.stream.listen((message) {
46-
_log.info('==> $message');
46+
// TODO(dantup): Using fine here doesn't work even though the
47+
// `setDevToolsLoggingLevel` call above seems like it should show finest
48+
// logs. For now, use info (which always logs) with a condition here
49+
// and below.
50+
if (_enablePostMessageVerboseLogging) {
51+
_log.info('==> $message');
52+
}
4753
postMessage(message, '*');
4854
});
4955
final channel = StreamChannel(
5056
onPostMessage.map((event) {
51-
_log.info('<== ${jsonEncode(event.data)}');
57+
if (_enablePostMessageVerboseLogging) {
58+
_log.info('<== ${jsonEncode(event.data)}');
59+
}
5260
return event.data;
5361
}),
5462
postMessageController,

‎packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ final class VsCodeApiImpl extends ToolApiImpl implements VsCodeApi {
5959
);
6060
}
6161

62+
@override
63+
Future<bool> enablePlatformType(String platformType) {
64+
return sendRequest(
65+
VsCodeApi.jsonEnablePlatformTypeMethod,
66+
{VsCodeApi.jsonPlatformTypeParameter: platformType},
67+
);
68+
}
69+
6270
@override
6371
Future<void> openDevToolsPage(String debugSessionId, String page) {
6472
return sendRequest(
@@ -206,6 +214,7 @@ class VsCodeDevicesEventImpl implements VsCodeDevicesEvent {
206214
VsCodeDevicesEventImpl({
207215
required this.selectedDeviceId,
208216
required this.devices,
217+
required this.unsupportedDevices,
209218
});
210219

211220
VsCodeDevicesEventImpl.fromJson(Map<String, Object?> json)
@@ -216,6 +225,11 @@ class VsCodeDevicesEventImpl implements VsCodeDevicesEvent {
216225
.map((item) => Map<String, Object?>.from(item))
217226
.map((map) => VsCodeDeviceImpl.fromJson(map))
218227
.toList(),
228+
unsupportedDevices:
229+
(json[VsCodeDevicesEvent.jsonUnsupportedDevicesField] as List?)
230+
?.map((item) => Map<String, Object?>.from(item))
231+
.map((map) => VsCodeDeviceImpl.fromJson(map))
232+
.toList(),
219233
);
220234

221235
@override
@@ -224,9 +238,13 @@ class VsCodeDevicesEventImpl implements VsCodeDevicesEvent {
224238
@override
225239
final List<VsCodeDevice> devices;
226240

241+
@override
242+
final List<VsCodeDevice>? unsupportedDevices;
243+
227244
Map<String, Object?> toJson() => {
228245
VsCodeDevicesEvent.jsonSelectedDeviceIdField: selectedDeviceId,
229246
VsCodeDevicesEvent.jsonDevicesField: devices,
247+
VsCodeDevicesEvent.jsonUnsupportedDevicesField: unsupportedDevices,
230248
};
231249
}
232250

‎packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ abstract interface class VsCodeApi {
4747
/// Calling this API will update the device for the whole VS Code extension.
4848
Future<bool> selectDevice(String id);
4949

50+
/// Enables the selected platform type.
51+
///
52+
/// Calling this method may cause a UI prompt in the editor to ask the user to
53+
/// confirm they'd like to run `flutter create` to create the required files
54+
/// for the project to support this platform type.
55+
///
56+
/// This method has no capability because it is only (and always) valid to
57+
/// call if some `unsupportedDevices` were provided in a device event.
58+
Future<bool> enablePlatformType(String platformType);
59+
5060
/// Opens a specific DevTools [page] for the debug session with ID
5161
/// [debugSessionId].
5262
///
@@ -69,6 +79,7 @@ abstract interface class VsCodeApi {
6979
static const jsonOpenDevToolsPageMethod = 'openDevToolsPage';
7080
static const jsonHotReloadMethod = 'hotReload';
7181
static const jsonHotRestartMethod = 'hotRestart';
82+
static const jsonEnablePlatformTypeMethod = 'enablePlatformType';
7283

7384
static const jsonDevicesChangedEvent = 'devicesChanged';
7485
static const jsonDebugSessionsChangedEvent = 'debugSessionsChanged';
@@ -78,6 +89,7 @@ abstract interface class VsCodeApi {
7889
static const jsonIdParameter = 'id';
7990
static const jsonOpenPageParameter = 'page';
8091
static const jsonDebugSessionIdParameter = 'debugSessionId';
92+
static const jsonPlatformTypeParameter = 'platformType';
8193
}
8294

8395
/// This class defines a device exposed by the Dart/Flutter extensions in VS
@@ -163,8 +175,19 @@ abstract interface class VsCodeDevicesEvent {
163175
/// A list of the devices that are available to select.
164176
List<VsCodeDevice> get devices;
165177

178+
/// A list of the devices that are unavailable to select because the platform
179+
/// is not enabled.
180+
///
181+
/// A devices platform type can be enabled by calling the `enablePlatformType`
182+
/// method.
183+
///
184+
/// This field is nullable because it was not in the initial sidebar API so
185+
/// older versions of VS Code might not provide it.
186+
List<VsCodeDevice>? get unsupportedDevices;
187+
166188
static const jsonSelectedDeviceIdField = 'selectedDeviceId';
167189
static const jsonDevicesField = 'devices';
190+
static const jsonUnsupportedDevicesField = 'unsupportedDevices';
168191
}
169192

170193
/// This class defines a debug session event sent by the Dart/Flutter extensions

‎packages/devtools_app/lib/src/standalone_ui/vs_code/devices.dart‎

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ import '../../shared/analytics/constants.dart' as gac;
1212
import '../api/vs_code_api.dart';
1313

1414
class Devices extends StatelessWidget {
15-
const Devices(
16-
this.api,
17-
this.devices, {
15+
Devices(
16+
this.api, {
17+
required this.devices,
18+
required this.unsupportedDevices,
1819
required this.selectedDeviceId,
1920
super.key,
20-
});
21+
}) : unsupportedDevicePlatformTypes = unsupportedDevices
22+
.map((device) => device.platformType)
23+
.nonNulls
24+
.toSet();
2125

2226
final VsCodeApi api;
2327
final List<VsCodeDevice> devices;
28+
final List<VsCodeDevice> unsupportedDevices;
29+
final Set<String> unsupportedDevicePlatformTypes;
2430
final String? selectedDeviceId;
2531

2632
@override
@@ -45,6 +51,11 @@ class Devices extends StatelessWidget {
4551
device,
4652
isSelected: device.id == selectedDeviceId,
4753
),
54+
for (final platformType in unsupportedDevicePlatformTypes)
55+
_createPlatformTypeEnablerRow(
56+
theme,
57+
platformType,
58+
),
4859
],
4960
),
5061
],
@@ -93,6 +104,36 @@ class Devices extends StatelessWidget {
93104
],
94105
);
95106
}
107+
108+
TableRow _createPlatformTypeEnablerRow(ThemeData theme, String platformType) {
109+
final foregroundColor = theme.colorScheme.secondary;
110+
111+
return TableRow(
112+
children: [
113+
SizedBox(
114+
width: double.infinity,
115+
child: TextButton(
116+
style: TextButton.styleFrom(
117+
alignment: Alignment.centerLeft,
118+
shape: const ContinuousRectangleBorder(),
119+
textStyle: theme.regularTextStyle,
120+
),
121+
child: Text(
122+
'Enable $platformType for this project',
123+
style: theme.regularTextStyle.copyWith(color: foregroundColor),
124+
),
125+
onPressed: () {
126+
ga.select(
127+
gac.VsCodeFlutterSidebar.id,
128+
gac.VsCodeFlutterSidebar.enablePlatformType(platformType),
129+
);
130+
unawaited(api.enablePlatformType(platformType));
131+
},
132+
),
133+
),
134+
],
135+
);
136+
}
96137
}
97138

98139
extension on VsCodeDevice {

‎packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class _VsCodeConnectedPanelState extends State<_VsCodeConnectedPanel> {
7575
stream: widget.api.devicesChanged,
7676
builder: (context, devicesSnapshot) {
7777
final devices = devicesSnapshot.data?.devices ?? const [];
78+
final unsupportedDevices =
79+
devicesSnapshot.data?.unsupportedDevices ?? const [];
7880
final deviceMap = {for (final device in devices) device.id: device};
7981
return Column(
8082
crossAxisAlignment: CrossAxisAlignment.start,
@@ -95,7 +97,8 @@ class _VsCodeConnectedPanelState extends State<_VsCodeConnectedPanel> {
9597
if (widget.api.capabilities.selectDevice)
9698
Devices(
9799
widget.api,
98-
devices,
100+
devices: devices,
101+
unsupportedDevices: unsupportedDevices,
99102
selectedDeviceId: devicesSnapshot.data?.selectedDeviceId,
100103
),
101104
],

‎packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ TODO: Remove this section if there are not any general updates.
4747

4848
TODO: Remove this section if there are not any general updates.
4949

50+
## VS Code Sidebar updates
51+
52+
* The Flutter Sidebar provided to VS Code now has the ability to enable new
53+
platforms if a device is available for a platform that is not enabled for
54+
the current project. This also requires a corresponding Dart extension for
55+
VS Code update to appear
56+
- [#6688](https://github.com/flutter/devtools/pull/6688)
57+
5058
## Full commit history
5159

5260
To find a complete list of changes in this release, check out the

‎packages/devtools_app/test/test_infra/test_data/dart_tooling_api/mock_api.dart‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class MockDartToolingApi extends DartToolingApiImpl {
7373
server.registerMethod('vsCode.initialize', initialize);
7474
server.registerMethod('vsCode.executeCommand', executeCommand);
7575
server.registerMethod('vsCode.selectDevice', selectDevice);
76+
server.registerMethod('vsCode.enablePlatformType', enablePlatformType);
7677
server.registerMethod('vsCode.openDevToolsPage', noOpHandler);
7778
server.registerMethod('vsCode.hotReload', noOpHandler);
7879
server.registerMethod('vsCode.hotRestart', noOpHandler);
@@ -113,11 +114,36 @@ class MockDartToolingApi extends DartToolingApiImpl {
113114
platform: 'web-javascript',
114115
platformType: 'web',
115116
),
117+
VsCodeDeviceImpl(
118+
id: 'web-server',
119+
name: 'Web Server',
120+
category: 'web',
121+
emulator: false,
122+
emulatorId: null,
123+
ephemeral: true,
124+
platform: 'web-javascript',
125+
platformType: 'web',
126+
),
116127
];
117128

129+
/// The current set of enabled platform types.
130+
///
131+
/// Defauls are set in [connectDevices].
132+
final _enabledPlatformTypes = <String>{};
133+
118134
/// The current set of devices being presented to the embedded panel.
119135
final _devices = <VsCodeDevice>[];
120136

137+
/// The current set of devices whose platform types are enabled.
138+
List<VsCodeDevice> get _enabledDevices => _devices
139+
.where((device) => _enabledPlatformTypes.contains(device.platformType))
140+
.toList();
141+
142+
/// The current set of devices whose platform types are not enabled.
143+
List<VsCodeDevice> get _disabledDevices => _devices
144+
.where((device) => !_enabledPlatformTypes.contains(device.platformType))
145+
.toList();
146+
121147
/// The current set of debug sessions that are running.
122148
final _debugSessions = <VsCodeDebugSession>[];
123149

@@ -154,6 +180,18 @@ class MockDartToolingApi extends DartToolingApiImpl {
154180
return true;
155181
}
156182

183+
/// Simulates a request to enable a platform type to allow additional devices
184+
/// to be used.
185+
Future<bool> enablePlatformType(json_rpc_2.Parameters parameters) async {
186+
final params = parameters.asMap;
187+
_enabledPlatformTypes.add(params['platformType'] as String);
188+
// Add some delay because the real impl will need to prompt + run
189+
// `flutter create`.
190+
await Future.delayed(const Duration(seconds: 1));
191+
_sendDevicesChanged();
192+
return true;
193+
}
194+
157195
/// A no-op handler for method handlers that don't require an implementation
158196
/// but need to exist so that the request/response is successful.
159197
void noOpHandler(json_rpc_2.Parameters _) {}
@@ -164,7 +202,10 @@ class MockDartToolingApi extends DartToolingApiImpl {
164202
_devices
165203
..clear()
166204
..addAll(_mockDevices);
167-
_selectedDeviceId = _devices.lastOrNull?.id;
205+
_enabledPlatformTypes
206+
..clear()
207+
..addAll(['macos', 'android']);
208+
_selectedDeviceId = _enabledDevices.lastOrNull?.id;
168209
_sendDevicesChanged();
169210
}
170211

@@ -202,7 +243,8 @@ class MockDartToolingApi extends DartToolingApiImpl {
202243
server.sendNotification(
203244
'${VsCodeApi.jsonApiName}.${VsCodeApi.jsonDevicesChangedEvent}',
204245
VsCodeDevicesEventImpl(
205-
devices: _devices,
246+
devices: _enabledDevices,
247+
unsupportedDevices: _disabledDevices,
206248
selectedDeviceId: _selectedDeviceId,
207249
).toJson(),
208250
);

0 commit comments

Comments
 (0)