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)
+
+    ![Link to watch a DevTools tutorial video](images/watch_tutorial_link.png "Link to watch a DevTools tutorial video")
+
+* 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