Link to DevTools YouTube video from footer (#6554) add missing dependency Fix release notes merge
diff --git a/packages/devtools_app/lib/src/framework/status_line.dart b/packages/devtools_app/lib/src/framework/status_line.dart index 51ea5f6..0e42a3d 100644 --- a/packages/devtools_app/lib/src/framework/status_line.dart +++ b/packages/devtools_app/lib/src/framework/status_line.dart
@@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:vm_service/vm_service.dart'; -import '../../devtools.dart' as devtools; import '../shared/analytics/constants.dart' as gac; import '../shared/common_widgets.dart'; import '../shared/globals.dart'; @@ -75,8 +74,27 @@ final screenWidth = ScreenSize(context).width; final Widget? pageStatus = currentScreen.buildStatus(context); final widerThanXxs = screenWidth > MediaSize.xxs; + final screenMetaData = ScreenMetaData.lookup(currentScreen.screenId); + final showVideoTutorial = screenMetaData?.tutorialVideoTimestamp != null; return [ - buildHelpUrlStatus(context, currentScreen, screenWidth), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + DocumentationLink( + screen: currentScreen, + screenWidth: screenWidth, + isConnected: isConnected, + ), + if (showVideoTutorial) ...[ + BulletSpacer(color: color), + VideoTutorialLink( + screenMetaData: screenMetaData!, + screenWidth: screenWidth, + isConnected: isConnected, + ), + ], + ], + ), BulletSpacer(color: color), if (widerThanXxs && showIsolateSelector) ...[ const IsolateSelector(), @@ -100,46 +118,6 @@ ]; } - Widget buildHelpUrlStatus( - BuildContext context, - Screen currentScreen, - MediaSize screenWidth, - ) { - final theme = Theme.of(context); - final style = theme.linkTextStyle; - final String? docPageId = currentScreen.docPageId; - if (docPageId != null) { - return RichText( - text: LinkTextSpan( - link: Link( - display: screenWidth <= MediaSize.xs - ? docPageId - : 'flutter.dev/devtools/$docPageId', - url: 'https://flutter.dev/devtools/$docPageId', - gaScreenName: currentScreen.screenId, - gaSelectedItemDescription: gac.documentationLink, - ), - style: isConnected - ? style.copyWith(color: theme.colorScheme.onPrimary) - : style, - context: context, - ), - ); - } else { - // Use a placeholder for pages with no explicit documentation. - return Flexible( - child: Text( - '${screenWidth <= MediaSize.xs ? '' : 'DevTools '}${devtools.version}', - overflow: TextOverflow.ellipsis, - style: isConnected - ? theme.regularTextStyle - .copyWith(color: theme.colorScheme.onPrimary) - : theme.regularTextStyle, - ), - ); - } - } - Widget buildConnectionStatus(BuildContext context, MediaSize screenWidth) { final theme = Theme.of(context); final textTheme = theme.textTheme; @@ -213,6 +191,75 @@ } } +/// A widget that links to DevTools documentation on docs.flutter.dev for the +/// given [screen]. +class DocumentationLink extends StatelessWidget { + const DocumentationLink({ + super.key, + required this.screen, + required this.screenWidth, + required this.isConnected, + }); + + final Screen screen; + + final MediaSize screenWidth; + + final bool isConnected; + + @override + Widget build(BuildContext context) { + final color = isConnected ? Theme.of(context).colorScheme.onPrimary : null; + final docPageId = screen.docPageId ?? ''; + return LinkIconLabel( + icon: Icons.library_books_outlined, + link: Link( + display: screenWidth <= MediaSize.xs ? 'Docs' : 'Read docs', + url: 'https://docs.flutter.dev/tools/devtools/$docPageId', + gaScreenName: screen.screenId, + gaSelectedItemDescription: gac.documentationLink, + ), + color: color, + ); + } +} + +/// A widget that links to the "Dive in to DevTools" YouTube video at the +/// chapter for the given [screenMetaData]. +class VideoTutorialLink extends StatelessWidget { + const VideoTutorialLink({ + super.key, + required this.screenMetaData, + required this.screenWidth, + required this.isConnected, + }); + + final ScreenMetaData screenMetaData; + + final MediaSize screenWidth; + + final bool isConnected; + + static const _devToolsYouTubeVideoUrl = 'https://youtu.be/_EYk-E29edo'; + + @override + Widget build(BuildContext context) { + final color = isConnected ? Theme.of(context).colorScheme.onPrimary : null; + return LinkIconLabel( + icon: Icons.ondemand_video_rounded, + link: Link( + display: screenWidth <= MediaSize.xs ? 'Tutorial' : 'Watch tutorial', + url: + '$_devToolsYouTubeVideoUrl${screenMetaData.tutorialVideoTimestamp}', + gaScreenName: screenMetaData.id, + gaSelectedItemDescription: + '${gac.videoTutorialLink}-${screenMetaData.id}', + ), + color: color, + ); + } +} + class IsolateSelector extends StatelessWidget { const IsolateSelector({Key? key}) : super(key: key);
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index c1e09e8..c51c3d5 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart
@@ -141,6 +141,7 @@ /// Documentation actions shared across screens. const documentationLink = 'documentationLink'; +const videoTutorialLink = 'videoTutorialLink'; String topicDocumentationButton(String topic) => '${topic}DocumentationButton'; String topicDocumentationLink(String topic) => '${topic}DocumentationLink';
diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index bf4f20e..3345b93 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart
@@ -1409,6 +1409,51 @@ } } +class LinkIconLabel extends StatelessWidget { + const LinkIconLabel({ + super.key, + required this.icon, + required this.link, + required this.color, + }); + + final IconData icon; + final Link link; + final Color? color; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: _onLinkTap, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: defaultIconSize, + color: color, + ), + const SizedBox(width: densePadding), + Padding( + padding: const EdgeInsets.only(bottom: densePadding), + child: RichText( + text: TextSpan( + text: link.display, + style: Theme.of(context).linkTextStyle.copyWith(color: color), + ), + ), + ), + ], + ), + ); + } + + void _onLinkTap() { + unawaited(launchUrl(link.url)); + ga.select(link.gaScreenName, link.gaSelectedItemDescription); + } +} + class LinkTextSpan extends TextSpan { LinkTextSpan({ required Link link,
diff --git a/packages/devtools_app/lib/src/shared/screen.dart b/packages/devtools_app/lib/src/shared/screen.dart index e3abd60..3e662c8 100644 --- a/packages/devtools_app/lib/src/shared/screen.dart +++ b/packages/devtools_app/lib/src/shared/screen.dart
@@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:collection/collection.dart'; import 'package:devtools_app_shared/service.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/foundation.dart'; @@ -21,6 +22,7 @@ 'home', icon: Icons.home_rounded, requiresConnection: false, + tutorialVideoTimestamp: '?t=0', ), inspector( 'inspector', @@ -28,12 +30,14 @@ icon: Octicons.deviceMobile, requiresFlutter: true, requiresDebugBuild: true, + tutorialVideoTimestamp: '?t=172', ), performance( 'performance', title: 'Performance', icon: Octicons.pulse, worksOffline: true, + tutorialVideoTimestamp: '?t=261', ), cpuProfiler( 'cpu-profiler', @@ -41,26 +45,35 @@ icon: Octicons.dashboard, requiresDartVm: true, worksOffline: true, + tutorialVideoTimestamp: '?t=340', ), memory( 'memory', title: 'Memory', icon: Octicons.package, requiresDartVm: true, + tutorialVideoTimestamp: '?t=420', ), debugger( 'debugger', title: 'Debugger', icon: Octicons.bug, requiresDebugBuild: true, + tutorialVideoTimestamp: '?t=513', ), network( 'network', title: 'Network', icon: Icons.network_check, requiresDartVm: true, + tutorialVideoTimestamp: '?t=547', ), - logging('logging', title: 'Logging', icon: Octicons.clippy), + logging( + 'logging', + title: 'Logging', + icon: Octicons.clippy, + tutorialVideoTimestamp: '?t=558', + ), provider( 'provider', title: 'Provider', @@ -74,6 +87,7 @@ icon: Octicons.fileZip, requiresConnection: false, requiresDartVm: true, + tutorialVideoTimestamp: '?t=575', ), deepLinks( 'deep-links', @@ -101,6 +115,7 @@ this.requiresVmDeveloperMode = false, this.worksOffline = false, this.requiresLibrary, + this.tutorialVideoTimestamp, }); final String id; @@ -113,6 +128,18 @@ final bool requiresVmDeveloperMode; final bool worksOffline; final String? requiresLibrary; + + /// The timestamp for the chapter of "Dive in to DevTools" YouTube video that + /// correlates to a screen. + /// + /// This value will be appended to "https://youtu.be/_EYk-E29edo" to link to + /// a particular chapter. + final String? tutorialVideoTimestamp; + + /// Looks up the [ScreenMetaData] value for the screen [id]. + static ScreenMetaData? lookup(String id) { + return ScreenMetaData.values.firstWhereOrNull((screen) => screen.id == id); + } } /// Defines a page shown in the DevTools [TabBar].
diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index ad738f5..a9a6252 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml
@@ -66,6 +66,7 @@ vm_service: ^11.10.0 # TODO https://github.com/dart-lang/sdk/issues/52853 - unpin this version vm_snapshot_analysis: 0.7.2 + web: ^0.3.0 web_socket_channel: ^2.1.0 # widget_icons: ^0.0.1
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index d48505b..035821c 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -1,9 +1,9 @@ This is draft for future release notes, that are going to land on [the Flutter website](https://docs.flutter.dev/tools/devtools/release-notes). -# DevTools 2.29.0 release notes +# DevTools 2.28.3 release notes -The 2.29.0 release of the Dart and Flutter DevTools +The 2.28.3 release of the Dart and Flutter DevTools includes the following changes among other general improvements. To learn more about DevTools, check out the [DevTools overview](https://docs.flutter.dev/tools/devtools/overview). @@ -15,6 +15,18 @@ [@bartekpacia](https://github.com/bartekpacia) for this change! - [#6644](https://github.com/flutter/devtools/pull/6644) +* Added a link to the new "Dive in to DevTools" YouTube +[video](https://www.youtube.com/watch?v=_EYk-E29edo) in the bottom status bar. This +video provides a brief tutorial for each DevTools screen. +[#6554](https://github.com/flutter/devtools/pull/6554) + +  + +* Enabled DevTools extensions when debugging a Dart entry point that is not +under `lib` (e.g. a unit test or integration test). Thanks to +[@bartekpacia](https://github.com/bartekpacia) for this change! - +[#6644](https://github.com/flutter/devtools/pull/6644) + ## Inspector updates TODO: Remove this section if there are not any general updates.
diff --git a/packages/devtools_app/release_notes/images/watch_tutorial_link.png b/packages/devtools_app/release_notes/images/watch_tutorial_link.png new file mode 100644 index 0000000..f0ef457 --- /dev/null +++ b/packages/devtools_app/release_notes/images/watch_tutorial_link.png Binary files differ