diff --git a/packages/devtools_app/lib/src/screens/dtd/dtd_tools_controller.dart b/packages/devtools_app/lib/src/screens/dtd/dtd_tools_controller.dart index df2764bf6f0..04489088a38 100644 --- a/packages/devtools_app/lib/src/screens/dtd/dtd_tools_controller.dart +++ b/packages/devtools_app/lib/src/screens/dtd/dtd_tools_controller.dart @@ -4,10 +4,11 @@ import 'package:devtools_app_shared/service.dart'; import 'package:devtools_app_shared/utils.dart'; +import 'package:flutter/foundation.dart'; import '../../shared/framework/screen.dart'; import '../../shared/framework/screen_controllers.dart'; -import '../../shared/globals.dart'; +import '../../shared/globals.dart' as globals; /// The [DevToolsScreenController] for the `DTDTools` screen. /// @@ -17,29 +18,45 @@ class DTDToolsController extends DevToolsScreenController @override final screenId = ScreenMetaData.dtdTools.id; + bool get useGlobalDtd => _useGlobalDtd && kDebugMode; + bool _useGlobalDtd = false; + /// The [DTDManager] that manages the DTD connection for this screen. /// - /// This instance of [DTDManager] is intentionally separate from the global - /// [DTDManager] so that we can connect and disconnect from DTD instances - /// to inspect them without affecting other screens. - final localDtdManager = DTDManager(); + /// By default, this instance of [DTDManager] is intentionally separate from + /// the global [DTDManager] so that we can connect and disconnect from DTD + /// instances to inspect them without affecting other screens. + /// + /// However, in debug mode, we can optionally use the global [DTDManager] + /// (if specifically requested) to set the DTD instance that the entire + /// DevTools is connected to. + DTDManager get activeDtdManager => + useGlobalDtd ? globals.dtdManager : _localDtdManager; + DTDManager get localDtdManager => _localDtdManager; + + final _localDtdManager = DTDManager(); @override Future init() async { - if (dtdManager.hasConnection) { - await localDtdManager.connect(dtdManager.uri!); + if (globals.dtdManager.hasConnection) { + await activeDtdManager.connect(globals.dtdManager.uri!); } - addAutoDisposeListener(dtdManager.connection, () async { - if (dtdManager.hasConnection) { - await localDtdManager.connect(dtdManager.uri!); + addAutoDisposeListener(globals.dtdManager.connection, () async { + if (globals.dtdManager.hasConnection && !_useGlobalDtd) { + await activeDtdManager.connect(globals.dtdManager.uri!); } }); } + Future connectDtd(Uri uri, {bool connectToGlobalDtd = false}) async { + _useGlobalDtd = connectToGlobalDtd; + await activeDtdManager.connect(uri); + } + @override Future dispose() async { - await localDtdManager.disconnect(); - await localDtdManager.dispose(); + await activeDtdManager.disconnect(); + await activeDtdManager.dispose(); super.dispose(); } } diff --git a/packages/devtools_app/lib/src/screens/dtd/dtd_tools_screen.dart b/packages/devtools_app/lib/src/screens/dtd/dtd_tools_screen.dart index 774871f46a7..cec12a8f043 100644 --- a/packages/devtools_app/lib/src/screens/dtd/dtd_tools_screen.dart +++ b/packages/devtools_app/lib/src/screens/dtd/dtd_tools_screen.dart @@ -4,14 +4,15 @@ import 'dart:async'; -import 'package:devtools_app_shared/service.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:dtd/dtd.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../shared/development_helpers.dart'; import '../../shared/framework/screen.dart'; import '../../shared/globals.dart'; +import '../../shared/ui/common_widgets.dart'; import 'dtd_tools_controller.dart'; import 'events.dart'; import 'services.dart'; @@ -39,15 +40,22 @@ class DTDToolsScreenBody extends StatelessWidget { @override Widget build(BuildContext context) { final controller = screenControllers.lookup(); - return ValueListenableBuilder( - valueListenable: controller.localDtdManager.connection, - builder: (context, connection, _) => connection != null - ? DtdConnectedView( - dtd: connection, - dtdUri: controller.localDtdManager.uri!.toString(), - onDisconnect: controller.localDtdManager.disconnect, - ) - : DtdNotConnectedView(localDtdManager: controller.localDtdManager), + return MultiValueListenableBuilder( + listenables: [ + controller.localDtdManager.connection, + dtdManager.connection, + ], + builder: (context, values, _) { + final activeDtdManager = controller.activeDtdManager; + final connection = activeDtdManager.connection.value; + return connection != null + ? DtdConnectedView( + dtd: connection, + dtdUri: activeDtdManager.uri!.toString(), + onDisconnect: activeDtdManager.disconnect, + ) + : DtdNotConnectedView(connectDtd: controller.connectDtd); + }, ); } } @@ -111,6 +119,7 @@ class _DtdConnectedViewState extends State { @override Widget build(BuildContext context) { + final isGlobalDtd = widget.dtd == dtdManager.connection.value; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -122,6 +131,14 @@ class _DtdConnectedViewState extends State { children: [ Text('DTD connection:', style: Theme.of(context).boldTextStyle), const SizedBox(width: denseSpacing), + DevToolsTooltip( + message: + 'This DTD URI is being used for ${isGlobalDtd ? 'all of DevTools.' : 'this screen only.'}', + child: RoundedLabel( + labelText: isGlobalDtd ? 'Global' : 'Local', + ), + ), + const SizedBox(width: defaultSpacing), Text(widget.dtdUri), const SizedBox(width: defaultSpacing), DevToolsButton( @@ -156,9 +173,9 @@ class _DtdConnectedViewState extends State { /// Displays a text field for entering a DTD URI to connect the DTD Tools screen /// to. class DtdNotConnectedView extends StatefulWidget { - const DtdNotConnectedView({super.key, required this.localDtdManager}); + const DtdNotConnectedView({super.key, required this.connectDtd}); - final DTDManager localDtdManager; + final Future Function(Uri, {bool connectToGlobalDtd}) connectDtd; @override State createState() => _DtdNotConnectedViewState(); @@ -167,6 +184,10 @@ class DtdNotConnectedView extends StatefulWidget { class _DtdNotConnectedViewState extends State { late final TextEditingController textEditingController; + bool _connectToGlobalDtd = false; + + String? _connectionError; + @override void initState() { super.initState(); @@ -198,6 +219,22 @@ class _DtdNotConnectedViewState extends State { onSubmitted: (_) => _connect(), ), ), + if (kDebugMode) ...[ + const SizedBox(width: defaultSpacing), + DevToolsTooltip( + message: 'Connect all DevTools screens to this DTD URI.', + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: _connectToGlobalDtd, + onChanged: _setConnectToGlobalDtd, + ), + const Text('Connect DevTools'), + ], + ), + ), + ], const SizedBox(width: defaultSpacing), DevToolsButton( label: 'Connect', @@ -206,11 +243,33 @@ class _DtdNotConnectedViewState extends State { ), ], ), + if (_connectionError != null) ...[ + const SizedBox(height: denseSpacing), + Text(_connectionError!, style: Theme.of(context).errorTextStyle), + ], ], ); } + void _setConnectToGlobalDtd(bool? shouldConnect) { + setState(() { + _connectToGlobalDtd = kDebugMode && (shouldConnect ?? false); + }); + } + Future _connect() async { - await widget.localDtdManager.connect(Uri.parse(textEditingController.text)); + setState(() { + _connectionError = null; + }); + try { + await widget.connectDtd( + Uri.parse(textEditingController.text), + connectToGlobalDtd: _connectToGlobalDtd, + ); + } catch (error) { + setState(() { + _connectionError = error.toString(); + }); + } } }