Skip to content

Commit bebfe49

Browse files
Show a screen unavailable message for disabled screens. (#7958)
1 parent adf025b commit bebfe49

File tree

5 files changed

+115
-28
lines changed

5 files changed

+115
-28
lines changed

packages/devtools_app/lib/src/app.dart

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,9 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
230230
child: DevToolsScaffold.withChild(
231231
key: const Key('not-found'),
232232
embedMode: params.embedMode,
233-
child: PageNotFound(
234-
page: page,
233+
child: ScreenUnavailable(
234+
title: "The '$page' cannot be found.",
235+
embedMode: params.embedMode,
235236
routerDelegate: routerDelegate,
236237
),
237238
),
@@ -279,7 +280,24 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
279280
removeHiddenScreens(screens, queryParams);
280281

281282
DevToolsScaffold scaffold;
282-
if (screens.isEmpty) {
283+
284+
if (page != null &&
285+
ScreenMetaData.lookup(page) != null &&
286+
!screens.containsWhere((s) => s.screenId == page)) {
287+
// The requested [page] is one of the first-party DevTools screens,
288+
// but is not available in list of available screens for this
289+
// scaffold.
290+
scaffold = DevToolsScaffold.withChild(
291+
key: const Key('screen-disabled'),
292+
embedMode: embedMode,
293+
child: ScreenUnavailable(
294+
title: "The '$page' screen is unavailable.",
295+
description: _screenUnavailableReason(page),
296+
routerDelegate: routerDelegate,
297+
embedMode: embedMode,
298+
),
299+
);
300+
} else if (screens.isEmpty) {
283301
// TODO(https://github.com/dart-lang/pub-dev/issues/7216): add an
284302
// extensions store or a link to a pub.dev query for packages with
285303
// extensions.
@@ -376,7 +394,8 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
376394
routerDelegate.refreshPages();
377395
}
378396

379-
List<Screen> _visibleScreens() => _screens.where(shouldShowScreen).toList();
397+
List<Screen> _visibleScreens() =>
398+
_screens.where((screen) => shouldShowScreen(screen).show).toList();
380399

381400
List<Provider> _providedControllers({bool offline = false}) {
382401
// We use [widget.originalScreens] here instead of [_screens] because
@@ -475,6 +494,25 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
475494
screens.removeWhere((s) => s is! ExtensionScreen);
476495
}
477496
}
497+
498+
String? _screenUnavailableReason(String screenId) {
499+
final screenMetaData = ScreenMetaData.lookup(screenId);
500+
String? disabledReason;
501+
if (screenMetaData != null) {
502+
final screen =
503+
_originalScreens.firstWhere((s) => s.screenId == screenMetaData.id);
504+
final reason = shouldShowScreen(screen).disabledReason;
505+
if (reason == ScreenDisabledReason.requiresDartLibrary) {
506+
// Special case for screens that require a library since the message
507+
// needs to be generated dynamically.
508+
disabledReason = 'The ${screen.title} screen requires library '
509+
'${screen.requiresLibrary}, but the library was not detected.';
510+
} else if (reason?.message != null) {
511+
disabledReason = 'The ${screen.title} screen ${reason!.message!}';
512+
}
513+
}
514+
return disabledReason;
515+
}
478516
}
479517

480518
/// DevTools screen wrapper that is responsible for creating and providing the
@@ -553,30 +591,45 @@ class _AlternateCheckedModeBanner extends StatelessWidget {
553591
}
554592
}
555593

556-
class PageNotFound extends StatelessWidget {
557-
const PageNotFound({
594+
class ScreenUnavailable extends StatelessWidget {
595+
const ScreenUnavailable({
558596
super.key,
559-
required this.page,
597+
required this.title,
598+
required this.embedMode,
560599
required this.routerDelegate,
600+
this.description,
561601
});
562602

563-
final String page;
564-
603+
final String title;
565604
final DevToolsRouterDelegate routerDelegate;
605+
final EmbedMode embedMode;
606+
final String? description;
566607

567608
@override
568609
Widget build(BuildContext context) {
610+
final theme = Theme.of(context);
569611
return Center(
570612
child: Column(
571613
mainAxisSize: MainAxisSize.min,
572614
children: [
573-
Text("'$page' not found."),
574-
const SizedBox(height: defaultSpacing),
575-
ElevatedButton(
576-
onPressed: () =>
577-
routerDelegate.navigateHome(clearScreenParam: true),
578-
child: const Text('Go to Home screen'),
615+
Text(
616+
title,
617+
style: theme.textTheme.titleLarge,
579618
),
619+
const SizedBox(height: denseSpacing),
620+
if (description != null)
621+
Text(
622+
description!,
623+
style: theme.regularTextStyle,
624+
),
625+
if (embedMode == EmbedMode.none) ...[
626+
const SizedBox(height: defaultSpacing),
627+
ElevatedButton(
628+
onPressed: () =>
629+
routerDelegate.navigateHome(clearScreenParam: true),
630+
child: const Text('Go to Home screen'),
631+
),
632+
],
580633
],
581634
),
582635
);

packages/devtools_app/lib/src/shared/screen.dart

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,25 +448,35 @@ abstract class Screen {
448448
}
449449

450450
/// Check whether a screen should be shown in the UI.
451-
bool shouldShowScreen(Screen screen) {
451+
({bool show, ScreenDisabledReason? disabledReason}) shouldShowScreen(
452+
Screen screen,
453+
) {
452454
_log.finest('shouldShowScreen: ${screen.screenId}');
453455
if (offlineDataController.showingOfflineData.value) {
454456
_log.finest('for offline mode: returning ${screen.worksWithOfflineData}');
455-
return screen.worksWithOfflineData;
457+
return (
458+
show: screen.worksWithOfflineData,
459+
disabledReason: screen.worksWithOfflineData
460+
? null
461+
: ScreenDisabledReason.offlineDataNotSupported,
462+
);
456463
}
457464

458465
final serviceReady = serviceConnection.serviceManager.isServiceAvailable &&
459466
serviceConnection.serviceManager.connectedApp!.connectedAppInitialized;
460467
if (!serviceReady) {
461468
if (!screen.requiresConnection) {
462469
_log.finest('screen does not require connection: returning true');
463-
return true;
470+
return (show: true, disabledReason: null);
464471
} else {
465472
// All of the following checks require a connected vm service, so verify
466473
// that one exists. This also avoids odd edge cases where we could show
467474
// screens while the ServiceManager is still initializing.
468475
_log.finest('service not ready: returning false');
469-
return false;
476+
return (
477+
show: false,
478+
disabledReason: ScreenDisabledReason.serviceNotReady,
479+
);
470480
}
471481
}
472482

@@ -478,36 +488,45 @@ bool shouldShowScreen(Screen screen) {
478488
_log.finest(
479489
'screen requires library ${screen.requiresLibrary}: returning false',
480490
);
481-
return false;
491+
return (
492+
show: false,
493+
disabledReason: ScreenDisabledReason.requiresDartLibrary,
494+
);
482495
}
483496
}
484497
if (screen.requiresDartVm) {
485498
if (serviceConnection.serviceManager.connectedApp!.isRunningOnDartVM !=
486499
true) {
487500
_log.finest('screen requires Dart VM: returning false');
488-
return false;
501+
return (show: false, disabledReason: ScreenDisabledReason.requiresDartVm);
489502
}
490503
}
491504
if (screen.requiresFlutter &&
492505
serviceConnection.serviceManager.connectedApp!.isFlutterAppNow == false) {
493506
_log.finest('screen requires Flutter: returning false');
494-
return false;
507+
return (show: false, disabledReason: ScreenDisabledReason.requiresFlutter);
495508
}
496509
if (screen.requiresDebugBuild) {
497510
if (serviceConnection.serviceManager.connectedApp!.isProfileBuildNow ==
498511
true) {
499512
_log.finest('screen requires debug build: returning false');
500-
return false;
513+
return (
514+
show: false,
515+
disabledReason: ScreenDisabledReason.requiresDebugBuild,
516+
);
501517
}
502518
}
503519
if (screen.requiresVmDeveloperMode) {
504520
if (!preferences.vmDeveloperModeEnabled.value) {
505521
_log.finest('screen requires vm developer mode: returning false');
506-
return false;
522+
return (
523+
show: false,
524+
disabledReason: ScreenDisabledReason.requiresVmDeveloperMode,
525+
);
507526
}
508527
}
509528
_log.finest('${screen.screenId} screen supported: returning true');
510-
return true;
529+
return (show: true, disabledReason: null);
511530
}
512531

513532
class BadgePainter extends CustomPainter {
@@ -573,3 +592,17 @@ class ShortcutsConfiguration {
573592

574593
bool get isEmpty => shortcuts.isEmpty && actions.isEmpty;
575594
}
595+
596+
enum ScreenDisabledReason {
597+
offlineDataNotSupported('does not support offline data.'),
598+
requiresDartLibrary(null),
599+
requiresDartVm('requires the Dart VM, but it is not available.'),
600+
requiresDebugBuild('only supports debug builds.'),
601+
requiresFlutter('only supports Flutter applications.'),
602+
requiresVmDeveloperMode('only works when VM Developer Mode is enabled'),
603+
serviceNotReady('requires a connection but the VM service is not ready.');
604+
605+
const ScreenDisabledReason(this.message);
606+
607+
final String? message;
608+
}

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ To learn more about DevTools, check out the
1010

1111
## General updates
1212

13-
TODO: Remove this section if there are not any general updates.
13+
* Improve messaging when a screen is unavailable for the platform of the
14+
connected app. - [#7958](https://github.com/flutter/devtools/pull/7958)
1415

1516
## Inspector updates
1617

packages/devtools_app/test/shared/visible_screens_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,6 @@ void main() {
274274

275275
List<Type> get visibleScreenTypes => defaultScreens()
276276
.map((s) => s.screen)
277-
.where(shouldShowScreen)
277+
.where((s) => shouldShowScreen(s).show)
278278
.map((s) => s.runtimeType)
279279
.toList();

packages/devtools_test/lib/src/helpers/actions.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ List<String> generateVisibleScreenIds() {
5959
final availableScreenIds = <String>[];
6060
// ignore: invalid_use_of_visible_for_testing_member, valid use from package:devtools_test
6161
for (final screen in devtoolsScreens!) {
62-
if (shouldShowScreen(screen.screen)) {
62+
if (shouldShowScreen(screen.screen).show) {
6363
availableScreenIds.add(screen.screen.screenId);
6464
}
6565
}

0 commit comments

Comments
 (0)