Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ class EditorClient extends DisposableController

String get gaId => EditorSidebar.id;

bool get isDtdClosed => _dtd.isClosed;

Future<void> _initialize() async {
autoDisposeStreamSubscription(
_dtd.onEvent(CoreDtdServiceConstants.servicesStreamId).listen((data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,13 @@ class PropertyEditorController extends DisposableController
String? get fileUri => _editableWidgetData.value?.fileUri;
EditorRange? get widgetRange => _editableWidgetData.value?.range;

ValueListenable<bool> get shouldReconnect => _shouldReconnect;
final _shouldReconnect = ValueNotifier<bool>(false);

bool get waitingForFirstEvent => _waitingForFirstEvent;
bool _waitingForFirstEvent = true;

late final Debouncer _requestDebouncer;

late final Timer _checkConnectionTimer;

static const _requestDebounceDuration = Duration(milliseconds: 600);

static const _checkConnectionInterval = Duration(minutes: 1);

static const _setPropertiesFilterId = 'set-properties-filter';

@visibleForTesting
Expand All @@ -84,9 +77,6 @@ class PropertyEditorController extends DisposableController
void init() {
super.init();
_requestDebouncer = Debouncer(duration: _requestDebounceDuration);
_checkConnectionTimer = _periodicallyCheckConnection(
_checkConnectionInterval,
);

// Update in response to ActiveLocationChanged events.
autoDisposeStreamSubscription(
Expand Down Expand Up @@ -133,7 +123,6 @@ class PropertyEditorController extends DisposableController
@override
void dispose() {
_requestDebouncer.dispose();
_checkConnectionTimer.cancel();
super.dispose();
}

Expand Down Expand Up @@ -266,16 +255,6 @@ class PropertyEditorController extends DisposableController
List<CodeActionCommand> _extractRefactors(CodeActionResult? result) =>
(result?.actions ?? <CodeActionCommand>[]).toList();

Timer _periodicallyCheckConnection(Duration interval) {
return Timer.periodic(interval, (timer) {
final isClosed = editorClient.isDtdClosed;
if (isClosed) {
_shouldReconnect.value = true;
timer.cancel();
}
});
}

bool _filteredOutBySettings(
EditableProperty property, {
required Filter filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import '../../../shared/primitives/query_parameters.dart';
import '../../../shared/ui/common_widgets.dart';
import 'property_editor_controller.dart';
import 'property_editor_view.dart';
import 'reconnecting_overlay.dart';

/// The side panel for the Property Editor.
class PropertyEditorPanel extends StatefulWidget {
Expand Down Expand Up @@ -106,43 +105,31 @@ class _PropertyEditorConnectedPanelState

@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: widget.controller.shouldReconnect,
builder: (context, shouldReconnect, _) {
return Stack(
children: [
Scrollbar(
return Scrollbar(
controller: scrollController,
thumbVisibility: true,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
controller: scrollController,
thumbVisibility: true,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
controller: scrollController,
child: Padding(
padding: const EdgeInsets.fromLTRB(
denseSpacing,
defaultSpacing,
defaultSpacing, // Additional right padding for scroll bar.
defaultSpacing,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PropertyEditorView(controller: widget.controller),
],
),
),
),
),
const _PropertyEditorFooter(),
],
child: Padding(
padding: const EdgeInsets.fromLTRB(
denseSpacing,
defaultSpacing,
defaultSpacing, // Additional right padding for scroll bar.
defaultSpacing,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [PropertyEditorView(controller: widget.controller)],
),
),
),
if (shouldReconnect) const ReconnectingOverlay(),
],
);
},
),
const _PropertyEditorFooter(),
],
),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,63 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'dart:async';

import 'package:devtools_app_shared/service.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:flutter/material.dart';

import '../../../shared/globals.dart';
import '../../../shared/ui/common_widgets.dart';
import 'utils/utils.dart';

class ReconnectingOverlay extends StatefulWidget {
const ReconnectingOverlay({super.key});
// TODO(dantup): Rename and move this file one level up. Leaving as-is to
// make the review/diff simpler.

@override
State<ReconnectingOverlay> createState() => _ReconnectingOverlayState();
}
/// An overlay to show when we are not connected to DTD based on the
/// [DTDConnectionState] classes.
class NotConnectedOverlay extends StatefulWidget {
const NotConnectedOverlay(this.connectionState, {super.key});

class _ReconnectingOverlayState extends State<ReconnectingOverlay> {
static const _countdownInterval = Duration(seconds: 1);
late final Timer _countdownTimer;
int _secondsUntilReconnection = 3;
final DTDConnectionState connectionState;

@override
void initState() {
super.initState();
_countdownTimer = Timer.periodic(_countdownInterval, _onTick);
}

@override
void dispose() {
_countdownTimer.cancel();
super.dispose();
}
State<NotConnectedOverlay> createState() => _NotConnectedOverlayState();
}

class _NotConnectedOverlayState extends State<NotConnectedOverlay> {
@override
Widget build(BuildContext context) {
final connectionState = widget.connectionState;
final theme = Theme.of(context);

final showSpinner = connectionState is! ConnectionFailedDTDState;
final showReconnectButton = connectionState is ConnectionFailedDTDState;
final stateLabel = switch (connectionState) {
NotConnectedDTDState() => 'Waiting to connect...',
ConnectingDTDState() => 'Connecting...',
WaitingToRetryDTDState(seconds: final seconds) =>
'Reconnecting in $seconds...',
ConnectionFailedDTDState() => 'Connection Failed',
// We should never present this widget when connected, but provide a label
// for debugging if it happens.
ConnectedDTDState() => 'Connected',
};

return DevToolsOverlay(
fullScreen: true,
content: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: defaultSpacing),
Text(
_secondsUntilReconnection > 0
? 'Reconnecting in $_secondsUntilReconnection'
: 'Reconnecting...',
style: theme.textTheme.headlineMedium,
),
if (showSpinner) ...const [
CircularProgressIndicator(),
SizedBox(height: defaultSpacing),
],
Text(stateLabel, style: theme.textTheme.headlineMedium),
if (showReconnectButton)
ElevatedButton(
onPressed: () => dtdManager.reconnect(),
child: const Text('Retry'),
),
],
),
);
}

void _onTick(Timer timer) {
setState(() {
_secondsUntilReconnection--;
if (_secondsUntilReconnection == 0) {
timer.cancel();
_reconnect();
}
});
}

void _reconnect() {
forceReload();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'package:flutter/widgets.dart';
import '_utils_desktop.dart' if (dart.library.js_interop) '_utils_web.dart';

/// Converts a [dartDocText] String into a [Text] widget.
class DartDocConverter {
Expand Down Expand Up @@ -133,10 +132,3 @@ class DartDocConverter {
return result;
}
}

/// Workaround to force reload the Property Editor when it disconnects.
///
/// See https://github.com/flutter/devtools/issues/9028 for details.
void forceReload() {
reloadIframe();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'package:devtools_app_shared/service.dart';
import 'package:dtd/dtd.dart';
import 'package:flutter/material.dart';

import '../shared/globals.dart';
import '../shared/ui/common_widgets.dart';
import 'ide_shared/property_editor/property_editor_panel.dart';
import 'ide_shared/property_editor/reconnecting_overlay.dart';
import 'vs_code/flutter_panel.dart';

/// "Screens" that are intended for standalone use only, likely for embedding
Expand All @@ -33,26 +35,13 @@ enum StandaloneScreenType {
'newer of the Dart VS Code extension',
),
),
StandaloneScreenType.editorSidebar => ValueListenableBuilder(
// TODO(dantup): Add a timeout here so if dtdManager.connection
// doesn't complete after some period we can give some kind of
// useful message.
valueListenable: dtdManager.connection,
builder: (context, data, _) {
return _DtdConnectedScreen(
dtd: data,
screenProvider: (dtd) => EditorSidebarPanel(dtd),
);
},
StandaloneScreenType.editorSidebar => _DtdConnectedScreen(
dtdManager: dtdManager,
builder: (dtd) => EditorSidebarPanel(dtd),
),
StandaloneScreenType.propertyEditor => ValueListenableBuilder(
valueListenable: dtdManager.connection,
builder: (context, data, _) {
return _DtdConnectedScreen(
dtd: data,
screenProvider: (dtd) => PropertyEditorPanel(dtd),
);
},
StandaloneScreenType.propertyEditor => _DtdConnectedScreen(
dtdManager: dtdManager,
builder: (dtd) => PropertyEditorPanel(dtd),
),
};
}
Expand All @@ -61,18 +50,45 @@ enum StandaloneScreenType {
values.any((value) => value.name == screenName);
}

/// Widget that returns a [CenteredCircularProgressIndicator] while it waits for
/// a [DartToolingDaemon] connection.
/// Widget that show progress while connecting to [DartToolingDaemon] and then
/// the result of calling [builder] when a connection is available.
///
/// If the DTD connection is dropped, a reconnecting progress will be shown.
class _DtdConnectedScreen extends StatelessWidget {
const _DtdConnectedScreen({required this.dtd, required this.screenProvider});
const _DtdConnectedScreen({required this.dtdManager, required this.builder});

final DartToolingDaemon? dtd;
final Widget Function(DartToolingDaemon) screenProvider;
final DTDManager dtdManager;
final Widget Function(DartToolingDaemon) builder;

@override
Widget build(BuildContext context) {
return dtd == null
? const CenteredCircularProgressIndicator()
: screenProvider(dtd!);
return ValueListenableBuilder(
valueListenable: dtdManager.connectionState,
builder: (context, connectionState, child) {
return ValueListenableBuilder(
valueListenable: dtdManager.connection,
builder: (context, connection, _) {
return Stack(
children: [
if (connection != null)
// Use a keyed subtree on the connection, so if the connection
// changes (eg. we reconnect), we reset the state because it's
// not safe to assume the existing state is still valid.
//
// This allows us to still keep rendering the old state under
// the overlay (rather than a blank background) until the
// reconnect occurs.
KeyedSubtree(
key: ValueKey(connection),
child: builder(connection),
),
if (connectionState is! ConnectedDTDState)
NotConnectedOverlay(connectionState),
],
);
},
);
},
);
}
}
4 changes: 3 additions & 1 deletion packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ To learn more about DevTools, check out the

## General updates

TODO: Remove this section if there are not any updates.
- Dropped connections to DTD will now automatically be retried to improve the
experience when your machine is resumed from sleep.
[#9587](https://github.com/flutter/devtools/pull/9587)

## Inspector updates

Expand Down
Loading