Move `copyToClipboard` and `isEmbedded` helpers to `devtools_app_shared` (#8118)

diff --git a/packages/devtools_app/lib/src/extensions/embedded/view.dart b/packages/devtools_app/lib/src/extensions/embedded/view.dart
index 51d64fe..e919785 100644
--- a/packages/devtools_app/lib/src/extensions/embedded/view.dart
+++ b/packages/devtools_app/lib/src/extensions/embedded/view.dart
@@ -8,7 +8,6 @@
 import '../../shared/analytics/analytics.dart' as ga;
 import '../../shared/analytics/constants.dart' as gac;
 import '../../shared/globals.dart';
-import '../../shared/utils.dart';
 import '_view_desktop.dart' if (dart.library.js_interop) '_view_web.dart';
 import 'controller.dart';
 
diff --git a/packages/devtools_app/lib/src/extensions/extension_screen.dart b/packages/devtools_app/lib/src/extensions/extension_screen.dart
index ce412f9..22a3a46 100644
--- a/packages/devtools_app/lib/src/extensions/extension_screen.dart
+++ b/packages/devtools_app/lib/src/extensions/extension_screen.dart
@@ -12,7 +12,6 @@
 import '../shared/common_widgets.dart';
 import '../shared/globals.dart';
 import '../shared/screen.dart';
-import '../shared/utils.dart';
 import 'embedded/controller.dart';
 import 'embedded/view.dart';
 import 'extension_screen_controls.dart';
diff --git a/packages/devtools_app/lib/src/framework/disconnect_observer.dart b/packages/devtools_app/lib/src/framework/disconnect_observer.dart
index bb4edb9..9b2152e 100644
--- a/packages/devtools_app/lib/src/framework/disconnect_observer.dart
+++ b/packages/devtools_app/lib/src/framework/disconnect_observer.dart
@@ -16,7 +16,6 @@
 import '../shared/globals.dart';
 import '../shared/query_parameters.dart';
 import '../shared/routing.dart';
-import '../shared/utils.dart';
 
 class DisconnectObserver extends StatefulWidget {
   const DisconnectObserver({
diff --git a/packages/devtools_app/lib/src/framework/release_notes/release_notes.dart b/packages/devtools_app/lib/src/framework/release_notes/release_notes.dart
index 9b8a716..35f58be 100644
--- a/packages/devtools_app/lib/src/framework/release_notes/release_notes.dart
+++ b/packages/devtools_app/lib/src/framework/release_notes/release_notes.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'package:devtools_app_shared/ui.dart';
 import 'package:devtools_app_shared/utils.dart';
 import 'package:devtools_shared/devtools_shared.dart';
 import 'package:flutter/material.dart';
diff --git a/packages/devtools_app/lib/src/framework/scaffold.dart b/packages/devtools_app/lib/src/framework/scaffold.dart
index cd866e4..b9b7a27 100644
--- a/packages/devtools_app/lib/src/framework/scaffold.dart
+++ b/packages/devtools_app/lib/src/framework/scaffold.dart
@@ -23,7 +23,6 @@
 import '../shared/routing.dart';
 import '../shared/screen.dart';
 import '../shared/title.dart';
-import '../shared/utils.dart';
 import 'about_dialog.dart';
 import 'app_bar.dart';
 import 'report_feedback_button.dart';
diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart
index a5f1e1c..4d710e5 100644
--- a/packages/devtools_app/lib/src/framework/settings_dialog.dart
+++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart
@@ -5,17 +5,16 @@
 import 'dart:async';
 
 import 'package:devtools_app_shared/ui.dart';
+import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
 import '../shared/analytics/analytics_controller.dart';
 import '../shared/analytics/constants.dart' as gac;
 import '../shared/common_widgets.dart';
-import '../shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart';
 import '../shared/globals.dart';
 import '../shared/log_storage.dart';
 import '../shared/server/server.dart';
-import '../shared/utils.dart';
 
 class OpenSettingsAction extends ScaffoldAction {
   OpenSettingsAction({super.key, super.color})
@@ -113,7 +112,8 @@
                   _minScreenWidthForTextBeforeScaling,
               onPressed: () async => await copyToClipboard(
                 LogStorage.root.toString(),
-                'Successfully copied logs',
+                onSuccess: () =>
+                    notificationService.push('Successfully copied logs'),
               ),
             ),
             const SizedBox(width: denseSpacing),
diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart
index 12e0d3a..1a0a72e 100644
--- a/packages/devtools_app/lib/src/screens/network/network_screen.dart
+++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart
@@ -13,7 +13,6 @@
 import '../../shared/analytics/analytics.dart' as ga;
 import '../../shared/analytics/constants.dart' as gac;
 import '../../shared/common_widgets.dart';
-import '../../shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart';
 import '../../shared/globals.dart';
 import '../../shared/http/curl_command.dart';
 import '../../shared/http/http_request_data.dart';
@@ -437,7 +436,8 @@
             unawaited(
               copyToClipboard(
                 data.uri,
-                'Copied the URL to the clipboard',
+                onSuccess: () =>
+                    notificationService.push('Copied the URL to the clipboard'),
               ),
             );
           },
@@ -448,7 +448,8 @@
             unawaited(
               copyToClipboard(
                 CurlCommand.from(data).toString(),
-                'Copied the cURL command to the clipboard',
+                onSuccess: () => notificationService
+                    .push('Copied the cURL command to the clipboard'),
               ),
             );
           },
diff --git a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart
index 244be3d..4dba24e 100644
--- a/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart
+++ b/packages/devtools_app/lib/src/shared/analytics/_analytics_web.dart
@@ -10,6 +10,7 @@
 import 'dart:async';
 import 'dart:js_interop';
 
+import 'package:devtools_app_shared/ui.dart';
 import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:unified_analytics/unified_analytics.dart' as ua;
diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart
index bd79b49..d2a50d9 100644
--- a/packages/devtools_app/lib/src/shared/common_widgets.dart
+++ b/packages/devtools_app/lib/src/shared/common_widgets.dart
@@ -16,7 +16,6 @@
 import '../screens/debugger/debugger_controller.dart';
 import 'analytics/analytics.dart' as ga;
 import 'analytics/constants.dart' as gac;
-import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart';
 import 'console/widgets/expandable_variable.dart';
 import 'diagnostics/dart_object_node.dart';
 import 'diagnostics/tree_builder.dart';
@@ -1178,7 +1177,8 @@
                             .serviceManager.service!.fakeServiceCache
                             .instanceToJson(copiedVariable.value as Instance),
                       ),
-                      'JSON copied to clipboard',
+                      onSuccess: () =>
+                          notificationService.push('JSON copied to clipboard'),
                     ),
                   );
                 },
@@ -1414,7 +1414,12 @@
               ga.select(gaScreen!, gaItem!);
             }
             unawaited(
-              copyToClipboard(dataProvider!() ?? '', successMessage),
+              copyToClipboard(
+                dataProvider!() ?? '',
+                onSuccess: successMessage != null
+                    ? () => notificationService.push(successMessage!)
+                    : null,
+              ),
             );
           };
     final size = this.size ?? defaultIconSize;
diff --git a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart
index 26fa83f..7e0299e 100644
--- a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart
+++ b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart
@@ -13,6 +13,7 @@
 
 import 'package:devtools_app_shared/service.dart';
 import 'package:devtools_app_shared/service_extensions.dart';
+import 'package:devtools_app_shared/ui.dart';
 import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/foundation.dart';
diff --git a/packages/devtools_app/lib/src/shared/dialogs.dart b/packages/devtools_app/lib/src/shared/dialogs.dart
index c5a0607..169d300 100644
--- a/packages/devtools_app/lib/src/shared/dialogs.dart
+++ b/packages/devtools_app/lib/src/shared/dialogs.dart
@@ -5,9 +5,9 @@
 import 'dart:async';
 
 import 'package:devtools_app_shared/ui.dart';
+import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/material.dart';
 
-import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart';
 import 'globals.dart';
 import 'utils.dart';
 
@@ -36,7 +36,8 @@
           onPressed: () => unawaited(
             copyToClipboard(
               additionalInfo,
-              'Error details copied to clipboard',
+              onSuccess: () =>
+                  notificationService.push('Error details copied to clipboard'),
             ),
           ),
         ),
diff --git a/packages/devtools_app/lib/src/shared/editable_list.dart b/packages/devtools_app/lib/src/shared/editable_list.dart
index 42fc44d..dbd8cc6 100644
--- a/packages/devtools_app/lib/src/shared/editable_list.dart
+++ b/packages/devtools_app/lib/src/shared/editable_list.dart
@@ -10,7 +10,7 @@
 import 'package:flutter/material.dart';
 
 import 'common_widgets.dart';
-import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart';
+import 'globals.dart';
 
 /// A widget that displays the contents of [entries].
 ///
@@ -276,7 +276,12 @@
       icon: Icons.copy_outlined,
       outlined: false,
       onPressed: () {
-        unawaited(copyToClipboard(value, 'Copied to clipboard.'));
+        unawaited(
+          copyToClipboard(
+            value,
+            onSuccess: () => notificationService.push('Copied to clipboard.'),
+          ),
+        );
       },
     );
   }
diff --git a/packages/devtools_app/lib/src/shared/utils.dart b/packages/devtools_app/lib/src/shared/utils.dart
index e833b47..541287f 100644
--- a/packages/devtools_app/lib/src/shared/utils.dart
+++ b/packages/devtools_app/lib/src/shared/utils.dart
@@ -39,10 +39,6 @@
   );
 }
 
-/// Whether DevTools is in embedded mode, as determined by the [ideTheme] parsed
-/// from query parameters.
-bool isEmbedded() => ideTheme.embedded;
-
 /// Whether DevTools is using a dark theme.
 ///
 /// When DevTools is in embedded mode, we first check if the [ideTheme] has
diff --git a/packages/devtools_app_shared/CHANGELOG.md b/packages/devtools_app_shared/CHANGELOG.md
index 2d4a683..5946e3a 100644
--- a/packages/devtools_app_shared/CHANGELOG.md
+++ b/packages/devtools_app_shared/CHANGELOG.md
@@ -6,6 +6,7 @@
 * Add `caseInsensitiveFuzzyMatch` extension method on `String`.
 * Add common widgets `DevToolsClearableTextField`, `InputDecorationSuffixButton`,
 and `RoundedDropDownButton`.
+* Add `copyToClipboard` and `isEmbedded` utility methods.
 * Deprecate `ServiceManager.hasConnection` in favor of
 `ServiceManager.connectedState.value.connected`.
 * Correct the dartdoc for the `ListValueNotifier` class.
diff --git a/packages/devtools_app_shared/lib/src/ui/ui_utils.dart b/packages/devtools_app_shared/lib/src/ui/ui_utils.dart
index cff4a29..2e66d16 100644
--- a/packages/devtools_app_shared/lib/src/ui/ui_utils.dart
+++ b/packages/devtools_app_shared/lib/src/ui/ui_utils.dart
@@ -5,6 +5,10 @@
 import '../utils/globals.dart';
 import 'theme/ide_theme.dart';
 
+/// Whether DevTools is in embedded mode, as determined by the [ideTheme] parsed
+/// from query parameters.
+bool isEmbedded() => ideTheme.embedded;
+
 IdeTheme get ideTheme {
   final theme = globals[IdeTheme];
   if (theme == null) {
diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_desktop.dart b/packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/_copy_to_clipboard_desktop.dart
similarity index 100%
rename from packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_desktop.dart
rename to packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/_copy_to_clipboard_desktop.dart
diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_web.dart b/packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/_copy_to_clipboard_web.dart
similarity index 100%
rename from packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_web.dart
rename to packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/_copy_to_clipboard_web.dart
diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart b/packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/copy_to_clipboard.dart
similarity index 79%
rename from packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart
rename to packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/copy_to_clipboard.dart
index 1b2a343..fc1835f 100644
--- a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart
+++ b/packages/devtools_app_shared/lib/src/utils/copy_to_clipboard/copy_to_clipboard.dart
@@ -5,8 +5,7 @@
 import 'package:flutter/services.dart';
 import 'package:logging/logging.dart';
 
-import '../../globals.dart';
-import '../../utils.dart';
+import '../../ui/ui_utils.dart';
 import '_copy_to_clipboard_desktop.dart'
     if (dart.library.js_interop) '_copy_to_clipboard_web.dart';
 
@@ -19,24 +18,23 @@
 /// attempts to post the [data] to the parent frame where the parent frame will
 /// try to complete the copy (this fallback will only work in VSCode).
 Future<void> copyToClipboard(
-  String data,
-  String? successMessage,
-) async {
+  String data, {
+  void Function()? onSuccess,
+}) async {
   try {
     await Clipboard.setData(
       ClipboardData(
         text: data,
       ),
     );
-
-    if (successMessage != null) notificationService.push(successMessage);
+    onSuccess?.call();
   } catch (e) {
     if (isEmbedded()) {
       _log.warning(
-        'DevTools copy failed. This may be as a result of a known bug in VSCode. '
+        'Copy failed. This may be as a result of a known bug in VS Code. '
         'See https://github.com/Dart-Code/Dart-Code/issues/4540 for more '
-        'information. DevTools will now attempt to use a fallback method of '
-        'copying the contents.',
+        'information. Now attempting to use a fallback method of copying the '
+        'that is a workaround for VS Code only.',
       );
       // Trying to use Clipboard.setData to copy in vscode will not work as a
       // result of a bug. So we should fallback to `copyToClipboardVSCode` which
diff --git a/packages/devtools_app_shared/lib/utils.dart b/packages/devtools_app_shared/lib/utils.dart
index f44804b..6568b0e 100644
--- a/packages/devtools_app_shared/lib/utils.dart
+++ b/packages/devtools_app_shared/lib/utils.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 export 'src/utils/auto_dispose.dart';
+export 'src/utils/copy_to_clipboard/copy_to_clipboard.dart';
 export 'src/utils/globals.dart';
 export 'src/utils/list.dart';
 export 'src/utils/url/url.dart';