Skip to content

Commit e7e5480

Browse files
authored
[ Widget Preview ] Update generated scaffold project to include early preview rendering (#162847)
With this change, `flutter widget-preview start` will launch a working widget preview environment that can render previews from a target project. Also fixes an issue where `--offline` wasn't being respected by some pub operations.
1 parent 94cd4b1 commit e7e5480

File tree

12 files changed

+543
-41
lines changed

12 files changed

+543
-41
lines changed

packages/flutter_tools/lib/src/base/signals.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class LocalSignals implements Signals {
124124

125125
// If _handlersList[signal] is empty, then lookup the cached stream
126126
// controller and unsubscribe from the stream.
127-
if (_handlersList.isEmpty) {
127+
if (_handlersList[signal]!.isEmpty) {
128128
await _streamSubscriptions[signal]?.cancel();
129129
}
130130
return true;

packages/flutter_tools/lib/src/commands/widget_preview.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,26 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
491491
await pub.interactively(
492492
<String>[
493493
pubAdd,
494+
if (offline) '--offline',
494495
'--directory',
495496
widgetPreviewScaffoldProject.directory.path,
496-
'${rootProject.manifest.appName}:{"path":${rootProject.directory.path}}',
497+
// Ensure the path using POSIX separators, otherwise the "path_not_posix" check will fail.
498+
'${rootProject.manifest.appName}:{"path":${rootProject.directory.path.replaceAll(r"\", "/")}}',
499+
],
500+
context: PubContext.pubAdd,
501+
command: pubAdd,
502+
touchesPackageConfig: true,
503+
);
504+
505+
// Adds a dependency on flutter_lints, which is referenced by the
506+
// analysis_options.yaml generated by the 'app' template.
507+
await pub.interactively(
508+
<String>[
509+
pubAdd,
510+
if (offline) '--offline',
511+
'--directory',
512+
widgetPreviewScaffoldProject.directory.path,
513+
'flutter_lints',
497514
],
498515
context: PubContext.pubAdd,
499516
command: pubAdd,

packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PreviewCodeGenerator {
1919
/// project.
2020
final FlutterProject widgetPreviewScaffoldProject;
2121

22-
static const String generatedPreviewFilePath = 'lib/generated_preview.dart';
22+
static const String generatedPreviewFilePath = 'lib/src/generated_preview.dart';
2323

2424
/// Generates code used by the widget preview scaffold based on the preview instances listed in
2525
/// [previews].
@@ -43,10 +43,7 @@ class PreviewCodeGenerator {
4343
void populatePreviewsInGeneratedPreviewScaffold(PreviewMapping previews) {
4444
final Library lib = Library(
4545
(LibraryBuilder b) => b.body.addAll(<Spec>[
46-
Directive.import(
47-
// TODO(bkonyi): update with actual location in the framework
48-
'package:widget_preview/widget_preview.dart',
49-
),
46+
Directive.import('package:flutter/widgets.dart'),
5047
Method(
5148
(MethodBuilder b) =>
5249
b

packages/flutter_tools/templates/template_manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@
353353
"templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/PrivacyInfo.xcprivacy",
354354

355355
"templates/widget_preview_scaffold/lib/main.dart.tmpl",
356+
"templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl",
357+
"templates/widget_preview_scaffold/lib/src/controls.dart.tmpl",
358+
"templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl",
359+
"templates/widget_preview_scaffold/lib/src/utils.dart.tmpl",
356360
"templates/widget_preview_scaffold/pubspec.yaml.tmpl",
357361
"templates/widget_preview_scaffold/README.md.tmpl",
358362

packages/flutter_tools/templates/widget_preview_scaffold/lib/main.dart.tmpl

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// TODO(bkonyi): Implement.
5+
import 'src/widget_preview_rendering.dart';
6+
import 'src/generated_preview.dart';
67

7-
import 'package:flutter/material.dart';
8-
9-
void main() {
10-
runApp(const MainApp());
11-
}
12-
13-
class MainApp extends StatelessWidget {
14-
const MainApp({super.key});
15-
16-
@override
17-
Widget build(BuildContext context) {
18-
return const MaterialApp(
19-
home: Scaffold(
20-
body: Center(
21-
child: Text('Hello World!'),
22-
),
23-
),
24-
);
25-
}
8+
Future<void> main() async {
9+
await mainImpl(previewsProvider: previews);
2610
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
class _WidgetPreviewIconButton extends StatelessWidget {
8+
const _WidgetPreviewIconButton({
9+
required this.tooltip,
10+
required this.onPressed,
11+
required this.icon,
12+
});
13+
14+
final String tooltip;
15+
final void Function()? onPressed;
16+
final IconData icon;
17+
18+
@override
19+
Widget build(BuildContext context) {
20+
return Tooltip(
21+
message: tooltip,
22+
child: Ink(
23+
decoration: ShapeDecoration(
24+
shape: const CircleBorder(),
25+
color: onPressed != null ? Colors.lightBlue : Colors.grey,
26+
),
27+
child: IconButton(
28+
onPressed: onPressed,
29+
icon: Icon(
30+
color: Colors.white,
31+
icon,
32+
),
33+
),
34+
),
35+
);
36+
}
37+
}
38+
39+
/// Provides controls to change the zoom level of a [WidgetPreview].
40+
class ZoomControls extends StatelessWidget {
41+
/// Provides controls to change the zoom level of a [WidgetPreview].
42+
const ZoomControls({
43+
super.key,
44+
required TransformationController transformationController,
45+
}) : _transformationController = transformationController;
46+
47+
final TransformationController _transformationController;
48+
49+
@override
50+
Widget build(BuildContext context) {
51+
return Row(
52+
mainAxisSize: MainAxisSize.min,
53+
children: <Widget>[
54+
_WidgetPreviewIconButton(
55+
tooltip: 'Zoom in',
56+
onPressed: _zoomIn,
57+
icon: Icons.zoom_in,
58+
),
59+
const SizedBox(
60+
width: 10,
61+
),
62+
_WidgetPreviewIconButton(
63+
tooltip: 'Zoom out',
64+
onPressed: _zoomOut,
65+
icon: Icons.zoom_out,
66+
),
67+
const SizedBox(
68+
width: 10,
69+
),
70+
_WidgetPreviewIconButton(
71+
tooltip: 'Reset zoom',
72+
onPressed: _reset,
73+
icon: Icons.refresh,
74+
),
75+
],
76+
);
77+
}
78+
79+
void _zoomIn() {
80+
_transformationController.value = Matrix4.copy(
81+
_transformationController.value,
82+
).scaled(1.1);
83+
}
84+
85+
void _zoomOut() {
86+
final Matrix4 updated = Matrix4.copy(
87+
_transformationController.value,
88+
).scaled(0.9);
89+
90+
// Don't allow for zooming out past the original size of the widget.
91+
// Assumes scaling is evenly applied to the entire matrix.
92+
if (updated.entry(0, 0) < 1.0) {
93+
updated.setIdentity();
94+
}
95+
96+
_transformationController.value = updated;
97+
}
98+
99+
void _reset() {
100+
_transformationController.value = Matrix4.identity();
101+
}
102+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/widgets.dart';
6+
7+
List<WidgetPreview> previews() => <WidgetPreview>[];
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// A basic vertical spacer.
8+
class VerticalSpacer extends StatelessWidget {
9+
/// Creates a basic vertical spacer.
10+
const VerticalSpacer({super.key});
11+
12+
@override
13+
Widget build(BuildContext context) {
14+
return const SizedBox(
15+
height: 10,
16+
);
17+
}
18+
}

0 commit comments

Comments
 (0)