Skip to content

Commit

Permalink
Add skeleton for extension screen views and controllers (flutter#6054)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Jul 19, 2023
1 parent f1c92c2 commit 69ec807
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/devtools_app/lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:provider/provider.dart';

import 'example/conditional_screen.dart';
import 'extensions/extension_model.dart';
import 'extensions/ui/extension_screen.dart';
import 'extensions/extension_screen.dart';
import 'framework/framework_core.dart';
import 'framework/home_screen.dart';
import 'framework/initializer.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'controller.dart';

class EmbeddedExtensionControllerImpl extends EmbeddedExtensionController {
// TODO(kenz): implement desktop stubs that match the web implementation.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'controller.dart';

class EmbeddedExtensionControllerImpl extends EmbeddedExtensionController {
// TODO(kenz): implement web controller for embedded extension
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

import 'controller.dart';

class EmbeddedExtension extends StatelessWidget {
const EmbeddedExtension({super.key, required this.controller});

final EmbeddedExtensionController controller;

@override
Widget build(BuildContext context) {
// TODO(kenz): if web view support for desktop is ever added, use that here.
return const Center(
child: Text(
'Cannot display the DevTools plugin.'
' IFrames are not supported on desktop platforms.',
),
);
}
}
20 changes: 20 additions & 0 deletions packages/devtools_app/lib/src/extensions/embedded/_view_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

import 'controller.dart';

class EmbeddedExtension extends StatelessWidget {
const EmbeddedExtension({super.key, required this.controller});

final EmbeddedExtensionController controller;

@override
Widget build(BuildContext context) {
return const Center(
child: Text('TODO implement embedded extension web view'),
);
}
}
12 changes: 12 additions & 0 deletions packages/devtools_app/lib/src/extensions/embedded/controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../shared/primitives/auto_dispose.dart';
import '_controller_desktop.dart' if (dart.library.html) '_controller_web.dart';

EmbeddedExtensionControllerImpl createEmbeddedExtensionController() {
return EmbeddedExtensionControllerImpl();
}

abstract class EmbeddedExtensionController extends DisposableController {}
27 changes: 27 additions & 0 deletions packages/devtools_app/lib/src/extensions/embedded/view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

import '_view_desktop.dart' if (dart.library.html) '_view_web.dart';
import 'controller.dart';

/// A widget that displays a DevTools extension in an embedded iFrame.
///
/// A DevTools extension is provided by a pub package and is served by the
/// DevTools server when present for a connected application.
///
/// When DevTools is run on Desktop for development, this widget displays a
/// placeholder, since Flutter Desktop does not currently support web views.
class EmbeddedExtensionView extends StatelessWidget {
const EmbeddedExtensionView({Key? key, required this.controller})
: super(key: key);

final EmbeddedExtensionController controller;

@override
Widget build(BuildContext context) {
return EmbeddedExtension(controller: controller);
}
}
146 changes: 146 additions & 0 deletions packages/devtools_app/lib/src/extensions/extension_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../shared/analytics/constants.dart' as gac;
import '../shared/common_widgets.dart';
import '../shared/primitives/listenable.dart';
import '../shared/primitives/utils.dart';
import '../shared/screen.dart';
import '../shared/theme.dart';
import 'embedded/controller.dart';
import 'embedded/view.dart';
import 'extension_model.dart';

class ExtensionScreen extends Screen {
ExtensionScreen(this.extensionConfig)
: super.conditional(
// TODO(kenz): we may need to ensure this is a unique id.
id: '${extensionConfig.name}-ext',
title: extensionConfig.name.toSentenceCase(),
icon: extensionConfig.icon,
// TODO(kenz): support static DevTools extensions.
requiresConnection: true,
);

final DevToolsExtensionConfig extensionConfig;

@override
ValueListenable<bool> get showIsolateSelector =>
const FixedValueListenable<bool>(true);

@override
Widget build(BuildContext context) =>
_ExtensionScreenBody(extensionConfig: extensionConfig);
}

class _ExtensionScreenBody extends StatefulWidget {
const _ExtensionScreenBody({required this.extensionConfig});

final DevToolsExtensionConfig extensionConfig;

@override
State<_ExtensionScreenBody> createState() => __ExtensionScreenBodyState();
}

class __ExtensionScreenBodyState extends State<_ExtensionScreenBody> {
late final EmbeddedExtensionController extensionController;

@override
void initState() {
super.initState();
extensionController = createEmbeddedExtensionController();
}

@override
void dispose() {
extensionController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return ExtensionView(
controller: extensionController,
extension: widget.extensionConfig,
);
}
}

class ExtensionView extends StatelessWidget {
const ExtensionView({
super.key,
required this.controller,
required this.extension,
});

final EmbeddedExtensionController controller;

final DevToolsExtensionConfig extension;

@override
Widget build(BuildContext context) {
return RoundedOutlinedBorder(
clip: true,
child: Column(
children: [
EmbeddedExtensionHeader(extension: extension),
Expanded(
child: KeepAliveWrapper(
child: Center(
child: EmbeddedExtensionView(controller: controller),
),
),
),
],
),
);
}
}

// TODO(kenz): add button to deactivate extension once activate / deactivate
// logic is hooked up.
class EmbeddedExtensionHeader extends StatelessWidget {
const EmbeddedExtensionHeader({super.key, required this.extension});

final DevToolsExtensionConfig extension;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final extensionName = extension.name.toLowerCase();
return AreaPaneHeader(
title: RichText(
text: TextSpan(
text: 'package:$extensionName extension',
style: theme.regularTextStyle.copyWith(fontWeight: FontWeight.bold),
children: [
TextSpan(
text: ' (v${extension.version})',
style: theme.subtleTextStyle,
),
],
),
),
includeTopBorder: false,
roundedTopBorder: false,
rightPadding: defaultSpacing,
actions: [
RichText(
text: LinkTextSpan(
link: Link(
display: 'Report an issue',
url: extension.issueTrackerLink,
gaScreenName: gac.extensionScreenId,
gaSelectedItemDescription: gac.extensionFeedback(extensionName),
),
context: context,
),
),
],
);
}
}
32 changes: 0 additions & 32 deletions packages/devtools_app/lib/src/extensions/ui/extension_screen.dart

This file was deleted.

4 changes: 4 additions & 0 deletions packages/devtools_app/lib/src/shared/analytics/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const feedbackButton = 'feedbackButton';
const contributingLink = 'contributing';
const discordLink = 'discord';

// Extension screens UX actions.
const extensionScreenId = 'devtoolsExtension';
String extensionFeedback(String name) => 'extensionFeedback-$name';

// Inspector UX actions:
const refresh = 'refresh';
const refreshEmptyTree = 'refreshEmptyTree';
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_app/lib/src/shared/screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ abstract class Screen {
text: TextSpan(text: title),
textDirection: TextDirection.ltr,
)..layout();
const measurementBuffer = 1.5;
const measurementBuffer = 2.0;
return painter.width +
denseSpacing +
defaultIconSize +
Expand Down
Loading

0 comments on commit 69ec807

Please sign in to comment.