Initialize Flutter Beta (flutter-3.32-candidate.0) (#166783)

Closes https://github.com/flutter/flutter/issues/166811.

```sh
$ dev/conductor/bin/conductor start \
  --candidate-branch=flutter-3.32-candidate.0 \
  --release-channel=beta \
  --github-username=matanlurey \
  --dart-revision=0d6811928830b87e36a0f49eb7fe554c308d3699 
```
This commit is contained in:
Matan Lurey
2025-04-09 15:57:49 -07:00
committed by GitHub
parent e2dea95082
commit 72ee26e314
62 changed files with 1258 additions and 631 deletions

View File

@@ -107,6 +107,15 @@ import 'theme.dart';
/// Here is an example to show different carousel layouts that [CarouselView]
/// and [CarouselView.weighted] can build.
///
/// On desktop and web running on desktop platforms, dragging to scroll with a mouse
/// is disabled by default to align with natural behavior.
///
/// To further align expected behavior like this, mouse input can scroll horizontally
/// by pressing the shift key while scrolling with the mouse wheel.
///
/// This key-driven behavior is dictated by the [ScrollBehavior.pointerAxisModifiers],
/// while [ScrollBehavior.dragDevices] manages what devices can drag a scrollable.
///
/// ** See code in examples/api/lib/material/carousel/carousel.0.dart **
/// {@end-tool}
///

View File

@@ -393,7 +393,7 @@ class ListTile extends StatelessWidget {
this.title,
this.subtitle,
this.trailing,
this.isThreeLine = false,
this.isThreeLine,
this.dense,
this.visualDensity,
this.shape,
@@ -425,7 +425,7 @@ class ListTile extends StatelessWidget {
this.minTileHeight,
this.titleAlignment,
this.internalAddSemanticForOnTap = true,
}) : assert(!isThreeLine || subtitle != null);
}) : assert(isThreeLine != true || subtitle != null);
/// A widget to display before the title.
///
@@ -482,7 +482,12 @@ class ListTile extends StatelessWidget {
///
/// When using a [Text] widget for [title] and [subtitle], you can enforce
/// line limits using [Text.maxLines].
final bool isThreeLine;
///
/// See also:
///
/// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s
/// [ListTileThemeData].
final bool? isThreeLine;
/// {@template flutter.material.ListTile.dense}
/// Whether this list tile is part of a vertically dense list.
@@ -987,7 +992,11 @@ class ListTile extends StatelessWidget {
trailing: trailingIcon,
isDense: _isDenseLayout(theme, tileTheme),
visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
isThreeLine: isThreeLine,
isThreeLine:
isThreeLine ??
tileTheme.isThreeLine ??
theme.listTileTheme.isThreeLine ??
false,
textDirection: textDirection,
titleBaselineType:
titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!,
@@ -1021,7 +1030,6 @@ class ListTile extends StatelessWidget {
ifTrue: 'THREE_LINE',
ifFalse: 'TWO_LINE',
showName: true,
defaultValue: false,
),
);
properties.add(

View File

@@ -73,6 +73,7 @@ class ListTileThemeData with Diagnosticable {
this.minTileHeight,
this.titleAlignment,
this.controlAffinity,
this.isThreeLine,
});
/// Overrides the default value of [ListTile.dense].
@@ -139,6 +140,9 @@ class ListTileThemeData with Diagnosticable {
/// or [ExpansionTile.controlAffinity] or [SwitchListTile.controlAffinity] or [RadioListTile.controlAffinity].
final ListTileControlAffinity? controlAffinity;
/// If specified, overrides the default value of [ListTile.isThreeLine].
final bool? isThreeLine;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ListTileThemeData copyWith({
@@ -187,6 +191,7 @@ class ListTileThemeData with Diagnosticable {
visualDensity: visualDensity ?? this.visualDensity,
titleAlignment: titleAlignment ?? this.titleAlignment,
controlAffinity: controlAffinity ?? this.controlAffinity,
isThreeLine: isThreeLine ?? this.isThreeLine,
);
}
@@ -221,6 +226,7 @@ class ListTileThemeData with Diagnosticable {
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
titleAlignment: t < 0.5 ? a?.titleAlignment : b?.titleAlignment,
controlAffinity: t < 0.5 ? a?.controlAffinity : b?.controlAffinity,
isThreeLine: t < 0.5 ? a?.isThreeLine : b?.isThreeLine,
);
}
@@ -247,6 +253,7 @@ class ListTileThemeData with Diagnosticable {
visualDensity,
titleAlignment,
controlAffinity,
isThreeLine,
]);
@override
@@ -278,7 +285,8 @@ class ListTileThemeData with Diagnosticable {
other.mouseCursor == mouseCursor &&
other.visualDensity == visualDensity &&
other.titleAlignment == titleAlignment &&
other.controlAffinity == controlAffinity;
other.controlAffinity == controlAffinity &&
other.isThreeLine == isThreeLine;
}
@override
@@ -337,6 +345,7 @@ class ListTileThemeData with Diagnosticable {
defaultValue: null,
),
);
properties.add(DiagnosticsProperty<bool>('isThreeLine', isThreeLine, defaultValue: null));
}
}
@@ -573,6 +582,7 @@ class ListTileTheme extends InheritedTheme {
MaterialStateProperty<MouseCursor?>? mouseCursor,
VisualDensity? visualDensity,
ListTileControlAffinity? controlAffinity,
bool? isThreeLine,
required Widget child,
}) {
return Builder(
@@ -603,6 +613,7 @@ class ListTileTheme extends InheritedTheme {
mouseCursor: mouseCursor ?? parent.mouseCursor,
visualDensity: visualDensity ?? parent.visualDensity,
controlAffinity: controlAffinity ?? parent.controlAffinity,
isThreeLine: isThreeLine ?? parent.isThreeLine,
),
child: child,
);
@@ -627,6 +638,7 @@ class ListTileTheme extends InheritedTheme {
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
isThreeLine: _data?.isThreeLine,
),
child: child,
);

View File

@@ -42,6 +42,14 @@ abstract class RenderProxySliver extends RenderSliver
this.child = child;
}
@override
Rect get semanticBounds {
if (child != null) {
return child!.semanticBounds;
}
return super.semanticBounds;
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData) {

View File

@@ -5,6 +5,7 @@
/// @docImport 'package:flutter/material.dart';
///
/// @docImport 'proxy_box.dart';
/// @docImport 'proxy_sliver.dart';
/// @docImport 'sliver_fill.dart';
/// @docImport 'sliver_grid.dart';
/// @docImport 'sliver_list.dart';
@@ -1306,6 +1307,28 @@ List<DiagnosticsNode> _debugCompareFloats(
/// than zero, then it should override [childCrossAxisPosition]. For example
/// [RenderSliverGrid] overrides this method.
abstract class RenderSliver extends RenderObject {
/// Whether this sliver should be included in the semantics tree.
///
/// This value is used by [RenderViewportBase] to ensure a sliver is
/// included in the semantics tree regardless of its geometry.
///
/// A [RenderSliver] should override this value to `true` to ensure
/// its child is included in the semantics tree. For example if your
/// sliver is under a [RenderViewport] you may want to wrap it with
/// a [SliverEnsureSemantics] to ensure that:
///
/// 1. It is still visited by [RenderViewportBase.visitChildrenForSemantics]
/// regardless of its geometry. This includes cases where your sliver is outside
/// the current viewport and cache extent.
/// 2. Its semantic information is not clipped out by the [RenderViewport] in
/// [RenderViewportBase.describeSemanticsClip] or [RenderViewportBase.describeApproximatePaintClip].
///
/// If a given [RenderSliver] does not provide a valid [semanticBounds] it will still
/// be dropped from the semantics tree.
///
/// Defaults to `false`.
bool get ensureSemantics => false;
// layout input
@override
SliverConstraints get constraints => super.constraints as SliverConstraints;

View File

@@ -425,6 +425,18 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
// Do not visit children in [_keepAliveBucket].
}
@override
Rect get semanticBounds {
// If we laid out the first child but this sliver is not visible, we report the
// semantic bounds of this sliver as the bounds of the first child. This is necessary
// for accessibility technologies to reach this sliver even when it is outside
// the current viewport and cache extent.
if (geometry != null && !geometry!.visible && firstChild != null && firstChild!.hasSize) {
return firstChild!.paintBounds;
}
return super.semanticBounds;
}
/// Called during layout to create and add the child with the given index and
/// scroll offset.
///

View File

@@ -332,7 +332,8 @@ class RenderTreeSliver extends RenderSliverVariedExtentList {
while (child != null && indexOf(child) <= index) {
final double mainAxisDelta = childMainAxisPosition(child);
final TreeSliverNodeParentData parentData = child.parentData! as TreeSliverNodeParentData;
final Offset childOffset = Offset(parentData.depth * indentation, parentData.layoutOffset!);
final Offset childOffset =
Offset(parentData.depth * indentation, parentData.layoutOffset!) + offset;
// If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child))
// does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden.

View File

@@ -314,7 +314,10 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
childrenInPaintOrder
.where(
(RenderSliver sliver) => sliver.geometry!.visible || sliver.geometry!.cacheExtent > 0.0,
(RenderSliver sliver) =>
sliver.geometry!.visible ||
sliver.geometry!.cacheExtent > 0.0 ||
sliver.ensureSemantics,
)
.forEach(visitor);
}
@@ -671,6 +674,12 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
@override
Rect? describeApproximatePaintClip(RenderSliver child) {
if (child.ensureSemantics && !(child.geometry!.visible || child.geometry!.cacheExtent > 0.0)) {
// Return null here so we don't end up clipping out a semantics node rect
// for a sliver child when we explicitly want it to be included in the semantics tree.
return null;
}
switch (clipBehavior) {
case Clip.none:
return null;
@@ -716,7 +725,14 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
}
@override
Rect describeSemanticsClip(RenderSliver? child) {
Rect? describeSemanticsClip(RenderSliver? child) {
if (child != null &&
child.ensureSemantics &&
!(child.geometry!.visible || child.geometry!.cacheExtent > 0.0)) {
// Return null here so we don't end up clipping out a semantics node rect
// for a sliver child when we explicitly want it to be included in the semantics tree.
return null;
}
if (_calculatedCacheExtent == null) {
return semanticBounds;
}

View File

@@ -1771,3 +1771,48 @@ class _SliverMainAxisGroupElement extends MultiChildRenderObjectElement {
.forEach(visitor);
}
}
/// A sliver that ensures its sliver child is included in the semantics tree.
///
/// This sliver ensures that its child sliver is still visited by the [RenderViewport]
/// when constructing the semantics tree, and is not clipped out of the semantics tree by
/// the [RenderViewport] when it is outside the current viewport and outside the cache extent.
///
/// The child sliver may still be excluded from the semantics tree if its [RenderSliver] does
/// not provide a valid [RenderSliver.semanticBounds]. This sliver does not guarantee its
/// child sliver is laid out.
///
/// Be mindful when positioning [SliverEnsureSemantics] in a [CustomScrollView] after slivers that build
/// their children lazily, like [SliverList]. Lazy slivers might underestimate the total scrollable size (scroll
/// extent) before the [SliverEnsureSemantics] widget. This inaccuracy can cause problems for assistive
/// technologies (e.g., screen readers), which rely on a correct scroll extent to navigate properly; they
/// might fail to scroll accurately to the content wrapped by [SliverEnsureSemantics].
///
/// To avoid this potential issue and ensure the scroll extent is calculated accurately up to this sliver,
/// it's recommended to use slivers that can determine their extent precisely beforehand. Instead of
/// [SliverList], consider using [SliverFixedExtentList], [SliverVariedExtentList], or
/// [SliverPrototypeExtentList]. If using [SliverGrid], ensure it employs a delegate such as
/// [SliverGridDelegateWithFixedCrossAxisCount] or [SliverGridDelegateWithMaxCrossAxisExtent].
/// Using these alternatives guarantees that the scrollable area's size is known accurately, allowing
/// assistive technologies to function correctly with [SliverEnsureSemantics].
///
/// {@tool dartpad}
/// This example shows how to use [SliverEnsureSemantics] to keep certain headers and lists
/// available to assistive technologies while they are outside the current viewport and cache extent.
///
/// ** See code in examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart **
/// {@end-tool}
// TODO(Renzo-Olivares): Investigate potential solutions for revealing off screen items, https://github.com/flutter/flutter/issues/166703.
class SliverEnsureSemantics extends SingleChildRenderObjectWidget {
/// Creates a sliver that ensures its sliver child is included in the semantics tree.
const SliverEnsureSemantics({super.key, required Widget sliver}) : super(child: sliver);
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverEnsureSemantics();
}
/// Ensures its sliver child is included in the semantics tree.
class _RenderSliverEnsureSemantics extends RenderProxySliver {
@override
bool get ensureSemantics => true;
}

View File

@@ -17,8 +17,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../impeller_test_helpers.dart';
// TODO(yjbanov): on the web text rendered with perspective produces flaky goldens: https://github.com/flutter/flutter/issues/110785
final bool skipPerspectiveTextGoldens = isBrowser && isSkwasm;
@@ -1611,7 +1609,7 @@ void main() {
matchesGoldenFile('date_picker_test.datetime.drag.png'),
);
}
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616
});
testWidgets('DatePicker displays the date in correct order', (WidgetTester tester) async {
await tester.pumpWidget(
@@ -1761,7 +1759,7 @@ void main() {
matchesGoldenFile('timer_picker_test.datetime.drag.png'),
);
}
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616
});
testWidgets('TimerPicker only changes hour label after scrolling stops', (
WidgetTester tester,

View File

@@ -4361,6 +4361,138 @@ void main() {
expect(trailingOffset.dy - tileOffset.dy, minVerticalPadding);
});
});
// Regression test for https://github.com/flutter/flutter/issues/165453
testWidgets('ListTile isThreeLine', (WidgetTester tester) async {
const double height = 300;
const double avatarTop = 130.0;
const double placeholderTop = 138.0;
Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) {
return MaterialApp(
key: UniqueKey(),
theme:
themeDataIsThreeLine != null
? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: themeDataIsThreeLine))
: null,
home: Material(
child: ListTileTheme(
data:
themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null,
child: ListView(
children: <Widget>[
ListTile(
isThreeLine: isThreeLine,
leading: const CircleAvatar(),
trailing: const SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: const Text('A'),
subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
isThreeLine: isThreeLine,
leading: const CircleAvatar(),
trailing: const SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: const Text('A'),
subtitle: const Text('A'),
),
],
),
),
),
);
}
void expectTwoLine() {
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 72.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0),
);
}
void expectThreeLine() {
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 88.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0),
);
}
await tester.pumpWidget(buildFrame());
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false),
);
expectTwoLine();
await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true));
expectThreeLine();
await tester.pumpWidget(
buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true),
);
expectThreeLine();
});
}
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {

View File

@@ -77,6 +77,7 @@ void main() {
expect(themeData.mouseCursor, null);
expect(themeData.visualDensity, null);
expect(themeData.titleAlignment, null);
expect(themeData.isThreeLine, null);
});
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
@@ -115,6 +116,7 @@ void main() {
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.comfortable,
titleAlignment: ListTileTitleAlignment.top,
isThreeLine: true,
).debugFillProperties(builder);
final List<String> description =
@@ -146,6 +148,7 @@ void main() {
'mouseCursor: WidgetStateMouseCursor(clickable)',
'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)',
'titleAlignment: ListTileTitleAlignment.top',
'isThreeLine: true',
]),
);
});
@@ -937,6 +940,7 @@ void main() {
minTileHeight: 30,
enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
isThreeLine: true,
);
final ListTileThemeData copy = original.copyWith(
@@ -958,6 +962,7 @@ void main() {
minTileHeight: 80,
enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
isThreeLine: false,
);
expect(copy.dense, false);
@@ -978,6 +983,7 @@ void main() {
expect(copy.minTileHeight, 80);
expect(copy.enableFeedback, false);
expect(copy.titleAlignment, ListTileTitleAlignment.top);
expect(copy.isThreeLine, false);
});
testWidgets('ListTileTheme.titleAlignment is overridden by ListTile.titleAlignment', (
@@ -1040,6 +1046,7 @@ void main() {
titleAlignment: ListTileTitleAlignment.bottom,
mouseCursor: MaterialStateMouseCursor.textable,
visualDensity: VisualDensity.comfortable,
isThreeLine: true,
),
),
home: Material(
@@ -1067,6 +1074,7 @@ void main() {
titleAlignment: ListTileTitleAlignment.top,
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.compact,
isThreeLine: false,
child: const ListTile(),
);
},
@@ -1098,6 +1106,217 @@ void main() {
expect(theme.titleAlignment, ListTileTitleAlignment.top);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
expect(theme.visualDensity, VisualDensity.compact);
expect(theme.isThreeLine, false);
});
// Regression test for https://github.com/flutter/flutter/issues/165453
testWidgets('ListTileThemeData isThreeLine', (WidgetTester tester) async {
const double height = 300;
const double avatarTop = 130.0;
const double placeholderTop = 138.0;
Widget buildFrame({bool? isThreeLine}) {
return MaterialApp(
key: UniqueKey(),
theme:
isThreeLine != null
? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: isThreeLine))
: null,
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
);
}
void expectTwoLine() {
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 72.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0),
);
}
void expectThreeLine() {
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 88.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0),
);
}
await tester.pumpWidget(buildFrame());
expectTwoLine();
await tester.pumpWidget(buildFrame(isThreeLine: false));
expectTwoLine();
await tester.pumpWidget(buildFrame(isThreeLine: true));
expectThreeLine();
});
// Regression test for https://github.com/flutter/flutter/issues/165453
testWidgets('ListTileTheme isThreeLine', (WidgetTester tester) async {
const double height = 300;
const double avatarTop = 130.0;
const double placeholderTop = 138.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(listTileTheme: const ListTileThemeData(isThreeLine: true)),
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(isThreeLine: false),
child: ListView(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
),
);
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 72.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0),
);
// THREE-LINE
await tester.pumpWidget(
MaterialApp(
key: UniqueKey(),
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(isThreeLine: true),
child: ListView(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
),
);
expect(
tester.getRect(find.byType(ListTile).at(0)),
const Rect.fromLTWH(0.0, 0.0, 800.0, height),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(0)),
const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(0)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0),
);
expect(
tester.getRect(find.byType(ListTile).at(1)),
const Rect.fromLTWH(0.0, height, 800.0, 88.0),
);
expect(
tester.getRect(find.byType(CircleAvatar).at(1)),
const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0),
);
expect(
tester.getRect(find.byType(Placeholder).at(1)),
const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0),
);
});
}

View File

@@ -12,8 +12,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../impeller_test_helpers.dart';
void main() {
testWidgets('Color filter - red', (WidgetTester tester) async {
await tester.pumpWidget(
@@ -56,7 +54,7 @@ void main() {
),
);
await expectLater(find.byType(ColorFiltered), matchesGoldenFile('color_filter_sepia.png'));
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616
});
testWidgets('Color filter - reuses its layer', (WidgetTester tester) async {
Future<void> pumpWithColor(Color color) async {

View File

@@ -20,7 +20,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../impeller_test_helpers.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart' show textOffsetToPosition;
import '../widgets/semantics_tester.dart';
@@ -5267,7 +5266,7 @@ void main() {
find.byType(MaterialApp),
matchesGoldenFile('selectable_text_golden.TextSelectionStyle.1.png'),
);
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616
});
testWidgets('text selection style 2', (WidgetTester tester) async {
await tester.pumpWidget(
@@ -5304,7 +5303,7 @@ void main() {
find.byType(MaterialApp),
matchesGoldenFile('selectable_text_golden.TextSelectionStyle.2.png'),
);
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616
});
testWidgets('keeps alive when has focus', (WidgetTester tester) async {
await tester.pumpWidget(

View File

@@ -10,8 +10,6 @@ library;
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../impeller_test_helpers.dart';
Shader createShader(Rect bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
@@ -102,5 +100,5 @@ void main() {
find.byType(RepaintBoundary),
matchesGoldenFile('shader_mask.bounds.matches_top_left.png'),
);
}, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/144555
});
}

View File

@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -861,4 +866,43 @@ void main() {
});
},
);
testWidgets('TreeSliver and PinnedHeaderSliver can render correctly when used together.', (
WidgetTester tester,
) async {
const ValueKey<String> key = ValueKey<String>('sliver_tree_pined_header');
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Align(
alignment: Alignment.topLeft,
child: RepaintBoundary(
key: key,
child: SizedBox(
height: 20,
width: 20,
child: CustomScrollView(
slivers: <Widget>[
const PinnedHeaderSliver(child: SizedBox(height: 10)),
TreeSliver<Object>(
tree: <TreeSliverNode<Object>>[TreeSliverNode<Object>(Object())],
treeRowExtentBuilder: (_, _) => 10,
treeNodeBuilder: (
BuildContext context,
TreeSliverNode<Object?> node,
AnimationStyle animationStyle,
) {
return const ColoredBox(color: Colors.red);
},
),
],
),
),
),
),
),
);
await expectLater(find.byKey(key), matchesGoldenFile('sliver_tree.pined_header.0.png'));
expect(tester.getTopLeft(find.byType(ColoredBox)), const Offset(0, 10));
});
}

View File

@@ -726,7 +726,7 @@ void main() {
},
);
Widget boilerPlate(Widget sliver) {
Widget boilerPlate(List<Widget> slivers) {
return Localizations(
locale: const Locale('en', 'us'),
delegates: const <LocalizationsDelegate<dynamic>>[
@@ -735,10 +735,7 @@ void main() {
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: CustomScrollView(slivers: <Widget>[sliver]),
),
child: MediaQuery(data: const MediaQueryData(), child: CustomScrollView(slivers: slivers)),
),
);
}
@@ -747,7 +744,7 @@ void main() {
testWidgets('offstage true', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
boilerPlate(const SliverOffstage(sliver: SliverToBoxAdapter(child: Text('a')))),
boilerPlate(<Widget>[const SliverOffstage(sliver: SliverToBoxAdapter(child: Text('a')))]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(0));
@@ -762,9 +759,9 @@ void main() {
testWidgets('offstage false', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOffstage(offstage: false, sliver: SliverToBoxAdapter(child: Text('a'))),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -783,12 +780,12 @@ void main() {
// Opacity 1.0: Semantics and painting
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 1.0,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -796,12 +793,12 @@ void main() {
// Opacity 0.0: Nothing
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.0,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(0));
@@ -809,13 +806,13 @@ void main() {
// Opacity 0.0 with semantics: Just semantics
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.0,
alwaysIncludeSemantics: true,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -823,12 +820,12 @@ void main() {
// Opacity 0.0 without semantics: Nothing
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.0,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(0));
@@ -836,12 +833,12 @@ void main() {
// Opacity 0.1: Semantics and painting
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.1,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -849,12 +846,12 @@ void main() {
// Opacity 0.1 without semantics: Still has semantics and painting
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.1,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -862,13 +859,13 @@ void main() {
// Opacity 0.1 with semantics: Semantics and painting
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
const SliverOpacity(
sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)),
opacity: 0.1,
alwaysIncludeSemantics: true,
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
@@ -883,7 +880,7 @@ void main() {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
SliverIgnorePointer(
ignoringSemantics: false,
sliver: SliverToBoxAdapter(
@@ -895,7 +892,7 @@ void main() {
),
),
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
await tester.tap(find.byType(GestureDetector), warnIfMissed: false);
@@ -907,7 +904,7 @@ void main() {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
SliverIgnorePointer(
ignoring: false,
ignoringSemantics: true,
@@ -920,7 +917,7 @@ void main() {
),
),
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(0));
await tester.tap(find.byType(GestureDetector));
@@ -931,13 +928,13 @@ void main() {
testWidgets('ignoring only block semantics actions', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
SliverIgnorePointer(
sliver: SliverToBoxAdapter(
child: GestureDetector(child: const Text('a'), onTap: () {}),
),
),
),
]),
);
expect(semantics, includesNodeWith(label: 'a', actions: <SemanticsAction>[]));
semantics.dispose();
@@ -947,7 +944,7 @@ void main() {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
SliverIgnorePointer(
ignoringSemantics: true,
sliver: SliverToBoxAdapter(
@@ -959,7 +956,7 @@ void main() {
),
),
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(0));
await tester.tap(find.byType(GestureDetector), warnIfMissed: false);
@@ -971,7 +968,7 @@ void main() {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(
boilerPlate(
boilerPlate(<Widget>[
SliverIgnorePointer(
ignoring: false,
ignoringSemantics: false,
@@ -984,7 +981,7 @@ void main() {
),
),
),
),
]),
);
expect(semantics.nodesWith(label: 'a'), hasLength(1));
await tester.tap(find.byType(GestureDetector));
@@ -993,6 +990,40 @@ void main() {
});
});
group('SliverEnsureSemantics - ', () {
testWidgets('ensure semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
boilerPlate(<Widget>[
const SliverEnsureSemantics(sliver: SliverToBoxAdapter(child: Text('a'))),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Lorem Ipsum $index'),
),
);
},
childCount: 50,
semanticIndexOffset: 1,
),
),
const SliverEnsureSemantics(sliver: SliverToBoxAdapter(child: Text('b'))),
]),
);
// Even though 'b' is outside of the Viewport and cacheExtent, since it is
// wrapped with a `SliverEnsureSemantics` it will still be included in the
// semantics tree.
expect(semantics.nodesWith(label: 'b'), hasLength(1));
expect(find.text('b'), findsNothing);
expect(find.byType(SliverEnsureSemantics, skipOffstage: false), findsNWidgets(2));
semantics.dispose();
});
});
testWidgets('SliverList handles 0 scrollOffsetCorrection', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/62198
await tester.pumpWidget(

View File

@@ -258,8 +258,6 @@ List<FlutterCommand> generateCommands({required bool verboseHelp, required bool
platform: globals.platform,
shutdownHooks: globals.shutdownHooks,
os: globals.os,
processManager: globals.processManager,
artifacts: globals.artifacts!,
),
UpgradeCommand(verboseHelp: verboseHelp),
SymbolizeCommand(stdio: globals.stdio, fileSystem: globals.fs),

View File

@@ -5,9 +5,7 @@
import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
@@ -26,8 +24,6 @@ import '../linux/build_linux.dart';
import '../macos/build_macos.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../widget_preview/dtd_services.dart';
import '../widget_preview/preview_code_generator.dart';
import '../widget_preview/preview_detector.dart';
import '../widget_preview/preview_manifest.dart';
@@ -45,8 +41,6 @@ class WidgetPreviewCommand extends FlutterCommand {
required Platform platform,
required ShutdownHooks shutdownHooks,
required OperatingSystemUtils os,
required ProcessManager processManager,
required Artifacts artifacts,
}) {
addSubcommand(
WidgetPreviewStartCommand(
@@ -58,8 +52,6 @@ class WidgetPreviewCommand extends FlutterCommand {
platform: platform,
shutdownHooks: shutdownHooks,
os: os,
processManager: processManager,
artifacts: artifacts,
),
);
addSubcommand(
@@ -126,8 +118,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
required this.platform,
required this.shutdownHooks,
required this.os,
required this.processManager,
required this.artifacts,
}) {
addPubOptions();
argParser
@@ -162,9 +152,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
static const String kHeadlessWeb = 'headless-web';
static const String kWidgetPreviewScaffoldOutputDir = 'scaffold-output-dir';
/// Environment variable used to pass the DTD URI to the widget preview scaffold.
static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI';
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
// Ensure the Flutter Web SDK is installed.
@@ -198,10 +185,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
final OperatingSystemUtils os;
final ProcessManager processManager;
final Artifacts artifacts;
late final FlutterProject rootProject = getRootProject();
late final PreviewDetector _previewDetector = PreviewDetector(
@@ -220,12 +203,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
cache: cache,
);
late final WidgetPreviewDtdServices _dtdService = WidgetPreviewDtdServices(
logger: logger,
shutdownHooks: shutdownHooks,
dtdLauncher: DtdLauncher(logger: logger, artifacts: artifacts, processManager: processManager),
);
/// The currently running instance of the widget preview scaffold.
AppInstance? _widgetPreviewApp;
@@ -307,7 +284,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
shutdownHooks.addShutdownHook(() async {
await _widgetPreviewApp?.stop();
});
await configureDtd();
_widgetPreviewApp = await runPreviewEnvironment(
widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject,
);
@@ -333,31 +309,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
_populatePreviewPubspec(rootProject: rootProject);
}
/// Configures the Dart Tooling Daemon connection.
///
/// If --dtd-uri is provided, the existing DTD instance will be used. If the tool fails to
/// connect to this URI, it will start its own DTD instance.
///
/// If --dtd-uri is not provided, a DTD instance managed by the tool will be started.
Future<void> configureDtd() async {
final String? existingDtdUriStr = stringArg(FlutterGlobalOptions.kDtdUrl, global: true);
Uri? existingDtdUri;
try {
if (existingDtdUriStr != null) {
existingDtdUri = Uri.parse(existingDtdUriStr);
}
} on FormatException {
logger.printWarning('Failed to parse value of --dtd-uri: $existingDtdUriStr.');
}
if (existingDtdUri == null) {
logger.printTrace('Launching a fresh DTD instance...');
await _dtdService.launchAndConnect();
} else {
logger.printTrace('Connecting to existing DTD instance at: $existingDtdUri...');
await _dtdService.connect(dtdWsUri: existingDtdUri);
}
}
/// Builds the application binary for the widget preview scaffold the first
/// time the widget preview command is run.
///
@@ -506,12 +457,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
BuildMode.debug,
null,
treeShakeIcons: false,
// Provide the DTD connection information directly to the preview scaffold.
// This could, in theory, be provided via a follow up call to a service extension
// registered by the preview scaffold, but there's some uncertainty around how service
// extensions will work with Flutter web embedded in VSCode without a Chrome debugger
// connection.
dartDefines: <String>['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'],
extraFrontEndOptions:
isWeb ? <String>['--dartdevc-canary', '--dartdevc-module-format=ddc'] : null,
packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path,
@@ -654,7 +599,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
if (offline) '--offline',
'--directory',
widgetPreviewScaffoldProject.directory.path,
'dtd',
'flutter_lints',
'stack_trace',
],

View File

@@ -18,7 +18,13 @@ Future<Set<String>> computeExclusiveDevDependencies(
required Logger logger,
required FlutterProject project,
}) async {
final Map<String, Object?> jsonResult = await pub.deps(project);
final Map<String, Object?>? jsonResult = await pub.deps(project);
// Avoid crashing if dart pub deps is not ready.
// See https://github.com/flutter/flutter/issues/166648.
if (jsonResult == null) {
return <String>{};
}
Never fail([String? reason]) {
logger.printTrace(const JsonEncoder.withIndent(' ').convert(jsonResult));

View File

@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../application_package.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
@@ -21,6 +22,7 @@ import '../convert.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../features.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../protocol_discovery.dart';
import '../vmservice.dart';
@@ -341,6 +343,7 @@ class CustomDeviceAppSession {
Map<String, Object?> platformArgs = const <String, Object>{},
bool prebuiltApplication = false,
String? userIdentifier,
Map<String, String> additionalReplacementValues = const <String, String>{},
}) async {
final bool traceStartup = platformArgs['trace-startup'] as bool? ?? false;
final String? packageName = _appPackage.name;
@@ -352,7 +355,7 @@ class CustomDeviceAppSession {
'remotePath': '/tmp/',
'appName': packageName,
'engineOptions': _getEngineOptionsForCmdline(debuggingOptions, traceStartup, route),
});
}, additionalReplacementValues: additionalReplacementValues);
final Process process = await _processUtils.start(interpolated);
assert(_process == null);
@@ -700,6 +703,16 @@ class CustomDevice extends Device {
String? userIdentifier,
BundleBuilder? bundleBuilder,
}) async {
final TargetPlatform platform = await targetPlatform;
final Artifacts artifacts = globals.artifacts!;
final Map<String, String> additionalReplacementValues = <String, String>{
'buildMode': debuggingOptions.buildInfo.modeName,
'icuDataPath': artifacts.getArtifactPath(Artifact.icuData, platform: platform),
'engineRevision':
artifacts.usesLocalArtifacts ? 'local' : globals.flutterVersion.engineRevision,
};
if (!prebuiltApplication) {
final String assetBundleDir = getAssetBuildDirectory();
@@ -707,7 +720,7 @@ class CustomDevice extends Device {
// this just builds the asset bundle, it's the same as `flutter build bundle`
await bundleBuilder.build(
platform: await targetPlatform,
platform: platform,
buildInfo: debuggingOptions.buildInfo,
mainPath: mainPath,
depfilePath: defaultDepfilePath,
@@ -720,7 +733,11 @@ class CustomDevice extends Device {
if (packageName == null) {
throwToolExit('Could not start app, name for $package is unknown.');
}
await _tryPostBuild(appName: packageName, localPath: assetBundleDir);
await _tryPostBuild(
appName: packageName,
localPath: assetBundleDir,
additionalReplacementValues: additionalReplacementValues,
);
}
}
@@ -736,6 +753,7 @@ class CustomDevice extends Device {
platformArgs: platformArgs,
prebuiltApplication: prebuiltApplication,
userIdentifier: userIdentifier,
additionalReplacementValues: additionalReplacementValues,
);
}

View File

@@ -158,7 +158,9 @@ abstract class Pub {
/// While it is guaranteed that, if successful, that the result are a valid
/// JSON object, the exact contents returned are _not_ validated, and are left
/// as a responsibility of the caller.
Future<Map<String, Object?>> deps(FlutterProject project);
///
/// If `null` is returned, it should be assumed deps could not be determined.
Future<Map<String, Object?>?> deps(FlutterProject project);
/// Runs pub in 'batch' mode.
///
@@ -354,13 +356,22 @@ class _DefaultPub implements Pub {
}
@override
Future<Map<String, Object?>> deps(FlutterProject project) async {
Future<Map<String, Object?>?> deps(FlutterProject project) async {
final List<String> pubCommand = <String>[..._pubCommand, 'deps', '--json'];
final RunResult runResult;
final RunResult runResult = await _processUtils.run(
pubCommand,
workingDirectory: project.directory.path,
);
// Don't treat this command as terminal if it fails.
// See https://github.com/flutter/flutter/issues/166648
try {
runResult = await _processUtils.run(
pubCommand,
workingDirectory: project.directory.path,
throwOnError: true,
);
} on io.ProcessException catch (e) {
_logger.printWarning('${pubCommand.join(' ')} ${e.message}');
return null;
}
Never fail([String? reason]) {
final String stdout = runResult.stdout;
@@ -374,11 +385,6 @@ class _DefaultPub implements Pub {
);
}
// Guard against dart pub deps crashing.
if (runResult.exitCode != 0) {
fail();
}
// Guard against dart pub deps having explicitly invalid output.
try {
final Object? result = json.decode(runResult.stdout);

View File

@@ -36,7 +36,6 @@ abstract final class FlutterGlobalOptions {
static const String kMachineFlag = 'machine';
static const String kPackagesOption = 'packages';
static const String kPrefixedErrorsFlag = 'prefixed-errors';
static const String kDtdUrl = 'dtd-url';
static const String kPrintDtd = 'print-dtd';
static const String kQuietFlag = 'quiet';
static const String kShowTestDeviceFlag = 'show-test-device';
@@ -152,12 +151,6 @@ class FlutterCommandRunner extends CommandRunner<void> {
hide: !verboseHelp,
help: 'Path to your "package_config.json" file.',
);
argParser.addOption(
FlutterGlobalOptions.kDtdUrl,
help:
'The address of an existing Dart Tooling Daemon instance to be used by the Flutter CLI.',
hide: !verboseHelp,
);
argParser.addFlag(
FlutterGlobalOptions.kPrintDtd,
negatable: false,

View File

@@ -1,101 +0,0 @@
// Copyright 2014 The Flutter 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 'dart:async';
import 'package:dtd/dtd.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../convert.dart';
/// Provides services, streams, and RPC invocations to interact with the Widget Preview Scaffold.
class WidgetPreviewDtdServices {
WidgetPreviewDtdServices({
required this.logger,
required this.shutdownHooks,
required this.dtdLauncher,
}) {
shutdownHooks.addShutdownHook(() async {
await _dtd?.close();
await dtdLauncher.dispose();
});
}
final Logger logger;
final ShutdownHooks shutdownHooks;
final DtdLauncher dtdLauncher;
DartToolingDaemon? _dtd;
/// The [Uri] pointing to the currently connected DTD instance.
///
/// Returns `null` if there is no DTD connection.
Uri? get dtdUri => _dtdUri;
Uri? _dtdUri;
/// Starts DTD in a child process before invoking [connect] with a [Uri] pointing to the new
/// DTD instance.
Future<void> launchAndConnect() async {
// Connect to the new DTD instance.
await connect(dtdWsUri: await dtdLauncher.launch());
}
/// Connects to an existing DTD instance and registers any relevant services.
Future<void> connect({required Uri dtdWsUri}) async {
_dtdUri = dtdWsUri;
_dtd = await DartToolingDaemon.connect(dtdWsUri);
// TODO(bkonyi): register services.
logger.printTrace('Connected to DTD and registered services.');
}
}
/// Manages the lifecycle of a Dart Tooling Daemon (DTD) instance.
class DtdLauncher {
DtdLauncher({required this.logger, required this.artifacts, required this.processManager});
/// Starts a new DTD instance and returns the web socket URI it's available on.
Future<Uri> launch() async {
if (_dtdProcess != null) {
throw StateError('Attempted to launch DTD twice.');
}
// Start DTD.
_dtdProcess = await processManager.start(<Object>[
artifacts.getArtifactPath(Artifact.engineDartBinary),
'tooling-daemon',
'--machine',
]);
// Wait for the DTD connection information.
final Completer<Uri> dtdUri = Completer<Uri>();
late final StreamSubscription<String> sub;
sub = _dtdProcess!.stdout.transform(const Utf8Decoder()).listen((String data) async {
await sub.cancel();
final Map<String, Object?> jsonData = json.decode(data) as Map<String, Object?>;
if (jsonData case {'tooling_daemon_details': {'uri': final String dtdUriString}}) {
dtdUri.complete(Uri.parse(dtdUriString));
} else {
throwToolExit('Unable to start the Dart Tooling Daemon.');
}
});
return dtdUri.future;
}
/// Kills the spawned DTD instance.
Future<void> dispose() async {
_dtdProcess?.kill();
_dtdProcess = null;
}
final Logger logger;
final Artifacts artifacts;
final ProcessManager processManager;
Process? _dtdProcess;
}

View File

@@ -21,7 +21,7 @@ dependencies:
ffi: 2.1.4
file: 7.0.1
flutter_template_images: 5.0.0
html: 0.15.5
html: 0.15.5+1
http: 1.3.0
intl: 0.20.2
meta: 1.16.0
@@ -122,4 +122,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
# PUBSPEC CHECKSUM: fe37
# PUBSPEC CHECKSUM: c093

View File

@@ -59,7 +59,7 @@
"required": false
},
"postBuild": {
"description": "The command to be invoked after the build process is done, to do any additional packaging for example.",
"description": "The command to be invoked after the build process is done, to do any additional packaging for example. The following variables are available via string interpolation:\n- ${appName}\n- ${localPath}\n- ${buildMode}\n- ${icuDataPath}\n- ${engineRevision}",
"type": ["array", "null"],
"items": {
"type": "string"
@@ -91,7 +91,7 @@
]
},
"runDebug": {
"description": "The command to be invoked to run the app in debug mode. The name of the app to be started is available via the ${appName} string interpolation. Make sure the flutter cmdline output is available via this commands stdout/stderr since the SDK needs the \"VM Service is now listening on ...\" message to function. If the forwardPort command is not specified, the VM Service URL will be connected to as-is, without any port forwarding. In that case you need to make sure it is reachable from your host device, possibly via the \"--vm-service-host=<ip>\" engine flag.",
"description": "The command to be invoked to run the app in debug mode. Make sure the flutter cmdline output is available via this commands stdout/stderr since the SDK needs the \"VM Service is now listening on ...\" message to function. If the forwardPort command is not specified, the VM Service URL will be connected to as-is, without any port forwarding. In that case you need to make sure it is reachable from your host device, possibly via the \"--vm-service-host=<ip>\" engine flag. The following variables are available via string interpolation:\n- ${appName}\n- ${engineOptions}\n- ${buildMode}\n- ${icuDataPath}\n- ${engineRevision}",
"type": "array",
"items": {
"type": "string"

View File

@@ -356,7 +356,6 @@
"templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl",
"templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl",
"templates/widget_preview_scaffold/lib/src/controls.dart.tmpl",
"templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl",
"templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl",
"templates/widget_preview_scaffold/lib/src/utils.dart.tmpl",
"templates/widget_preview_scaffold/pubspec.yaml.tmpl",

View File

@@ -1,33 +0,0 @@
// Copyright 2014 The Flutter 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 'dart:async';
import 'package:dtd/dtd.dart';
/// Provides services, streams, and RPC invocations to interact with Flutter developer tooling.
class WidgetPreviewScaffoldDtdServices {
/// Environment variable for the DTD URI.
static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI';
/// Connects to the Dart Tooling Daemon (DTD) specified by the Flutter tool.
///
/// If the connection is successful, the Widget Preview Scaffold will register services and
/// subscribe to various streams to interact directly with other tooling (e.g., IDEs).
Future<void> connect() async {
final Uri dtdWsUri = Uri.parse(
const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar),
);
_dtd = await DartToolingDaemon.connect(dtdWsUri);
unawaited(
_dtd.postEvent(
'WidgetPreviewScaffold',
'Connected',
const <String, Object?>{},
),
);
}
late final DartToolingDaemon _dtd;
}

View File

@@ -13,7 +13,6 @@ import 'package:flutter/services.dart';
import 'package:stack_trace/stack_trace.dart';
import 'controls.dart';
import 'dtd_services.dart';
import 'generated_preview.dart';
import 'utils.dart';
import 'widget_preview.dart';
@@ -411,8 +410,6 @@ class PreviewAssetBundle extends PlatformAssetBundle {
/// the preview scaffold project which prevents us from being able to use hot
/// restart to iterate on this file.
Future<void> mainImpl() async {
// TODO(bkonyi): store somewhere.
await WidgetPreviewScaffoldDtdServices().connect();
runApp(_WidgetPreviewScaffold());
}

View File

@@ -12,7 +12,6 @@ dependencies:
flutter_test:
sdk: flutter
# These will be replaced with proper constraints after the template is hydrated.
dtd: any
flutter_lints: any
stack_trace: any

View File

@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
@@ -48,8 +47,6 @@ void main() {
platform: platform,
processManager: processManager,
),
processManager: FakeProcessManager.any(),
artifacts: Artifacts.test(fileSystem: fileSystem),
);
rootProject = FakeFlutterProject(
projectRoot: 'some_project',

View File

@@ -6,7 +6,6 @@ import 'dart:io' as io show IOOverrides;
import 'package:args/command_runner.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/bot_detector.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
@@ -81,8 +80,6 @@ void main() {
logger: logger,
platform: platform,
),
artifacts: Artifacts.test(),
processManager: FakeProcessManager.any(),
),
);
await runner.run(<String>['widget-preview', ...arguments]);

View File

@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
@@ -356,6 +358,17 @@ void main() {
);
});
test('a pub error is treated as no data available instead of terminal', () async {
final ProcessManager processes = _dartPubDepsCrashes(project: project);
final Set<String> dependencies = await computeExclusiveDevDependencies(
pub(processes),
project: project,
logger: logger,
);
expect(dependencies, isEmpty, reason: 'pub deps crashed, but was not terminal');
});
test('throws and logs on invalid JSON', () async {
final ProcessManager processes = _dartPubDepsReturns('''
{
@@ -420,3 +433,18 @@ ProcessManager _dartPubDepsReturns(String dartPubDepsOutput, {required FlutterPr
),
]);
}
ProcessManager _dartPubDepsCrashes({required FlutterProject project}) {
return FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[_dartBin, 'pub', '--suppress-analytics', 'deps', '--json'],
workingDirectory: project.directory.path,
exception: const io.ProcessException('pub', <String>[
'pub',
'--suppress-analytics',
'deps',
'--json',
]),
),
]);
}

View File

@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
@@ -530,6 +531,100 @@ void main() {
},
);
testUsingContext(
'custom device command string interpolation end-to-end test',
() async {
final Completer<void> runDebugCompleter = Completer<void>();
final CustomDeviceConfig config = testConfig.copyWith(
platform: TargetPlatform.linux_arm64,
postBuildCommand: const <String>[
'testpostbuild',
r'--buildMode=${buildMode}',
r'--icuDataPath=${icuDataPath}',
r'--engineRevision=${engineRevision}',
],
runDebugCommand: const <String>[
'testrundebug',
r'--buildMode=${buildMode}',
r'--icuDataPath=${icuDataPath}',
r'--engineRevision=${engineRevision}',
],
);
final List<Pattern> commandArgumentsPattern = <Pattern>[
RegExp(r'--buildMode=.*'),
RegExp(r'--icuDataPath=.*'),
RegExp(r'--engineRevision=.*'),
];
final String expectedIcuDataPath = globals.artifacts!.getArtifactPath(
Artifact.icuData,
platform: config.platform,
);
final String expectedEngineRevision = globals.flutterVersion.engineRevision;
final List<String> expectedCommandArguments = <String>[
'--buildMode=debug',
'--icuDataPath=$expectedIcuDataPath',
'--engineRevision=$expectedEngineRevision',
];
final List<String> expectedRunDebugCommand = <String>[
'testrundebug',
...expectedCommandArguments,
];
final List<String> expectedPostBuildCommand = <String>[
'testpostbuild',
...expectedCommandArguments,
];
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <Pattern>['testpostbuild', ...commandArgumentsPattern],
onRun: (List<String> command) => expect(command, expectedPostBuildCommand),
),
FakeCommand(command: config.uninstallCommand),
FakeCommand(command: config.installCommand),
FakeCommand(
command: <Pattern>['testrundebug', ...commandArgumentsPattern],
completer: runDebugCompleter,
onRun: (List<String> command) => expect(command, expectedRunDebugCommand),
stdout: 'The Dart VM service is listening on http://127.0.0.1:12345/abcd/\n',
),
FakeCommand(
command: config.forwardPortCommand!,
stdout: testConfigForwardPortSuccessOutput,
),
]);
// CustomDevice.startApp doesn't care whether we pass a prebuilt app or
// buildable app as long as we pass prebuiltApplication as false
final PrebuiltLinuxApp app = PrebuiltLinuxApp(executable: 'testexecutable');
// finally start actually testing things
final CustomDevice device = CustomDevice(
config: config,
logger: BufferLogger.test(),
processManager: processManager,
);
await device.startApp(
app,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
bundleBuilder: FakeBundleBuilder(),
);
expect(runDebugCompleter.isCompleted, false);
expect(await device.stopApp(app), true);
expect(runDebugCompleter.isCompleted, true);
},
overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
},
);
testWithoutContext('CustomDevice screenshotting', () async {
bool screenshotCommandWasExecuted = false;

View File

@@ -43,7 +43,7 @@ void main() {
);
});
testWithoutContext('fails on non-zero exit code', () async {
testWithoutContext('returns null on non-zero exit code', () async {
final BufferLogger logger = BufferLogger.test();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final ProcessManager processManager = _dartPubDepsFails(
@@ -62,14 +62,8 @@ void main() {
);
await expectLater(
() => pub.deps(FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)),
throwsA(
isA<StateError>().having(
(StateError e) => e.message,
'message',
contains('dart pub --suppress-analytics deps --json failed'),
),
),
pub.deps(FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)),
completion(isNull),
);
});

View File

@@ -5,18 +5,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:dtd/dtd.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/commands/widget_preview.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:flutter_tools/src/widget_preview/dtd_services.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import 'test_data/basic_project.dart';
import 'test_utils.dart';
@@ -49,13 +43,10 @@ const List<String> subsequentLaunchMessagesWeb = <String>[
void main() {
late Directory tempDir;
Process? process;
Logger? logger;
DtdLauncher? dtdLauncher;
final BasicProject project = BasicProject();
const ProcessManager processManager = LocalProcessManager();
setUp(() async {
logger = BufferLogger.test();
tempDir = createResolvedTempDirectorySync('widget_preview_test.');
await project.setUpIn(tempDir);
});
@@ -63,15 +54,12 @@ void main() {
tearDown(() async {
process?.kill();
process = null;
await dtdLauncher?.dispose();
dtdLauncher = null;
tryToDelete(tempDir);
});
Future<void> runWidgetPreview({
required List<String> expectedMessages,
bool useWeb = false,
Uri? dtdUri,
}) async {
expect(expectedMessages, isNotEmpty);
int i = 0;
@@ -84,7 +72,6 @@ void main() {
'--${WidgetPreviewStartCommand.kHeadlessWeb}'
else
'--${WidgetPreviewStartCommand.kUseFlutterDesktop}',
if (dtdUri != null) '--${FlutterGlobalOptions.kDtdUrl}=$dtdUri',
], workingDirectory: tempDir.path);
final Completer<void> completer = Completer<void>();
@@ -116,6 +103,8 @@ void main() {
}),
);
await completer.future;
process!.kill();
process = null;
}
group('flutter widget-preview start', () {
@@ -143,38 +132,5 @@ void main() {
// We shouldn't regenerate the scaffold after the initial run.
await runWidgetPreview(expectedMessages: subsequentLaunchMessagesWeb, useWeb: true);
});
testUsingContext('can connect to an existing DTD instance', () async {
dtdLauncher = DtdLauncher(
logger: logger!,
artifacts: globals.artifacts!,
processManager: globals.processManager,
);
// Start a DTD instance.
final Uri dtdUri = await dtdLauncher!.launch();
// Connect to it and listen to the WidgetPreviewScaffold stream.
//
// The preview scaffold will send a 'Connected' event on this stream once it has initialized
// and is ready.
final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri);
const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
final Completer<void> completer = Completer<void>();
dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) {
expect(event.stream, kWidgetPreviewScaffoldStream);
expect(event.kind, 'Connected');
completer.complete();
});
await dtdConnection.streamListen(kWidgetPreviewScaffoldStream);
// Start the widget preview and wait for the 'Connected' event.
await runWidgetPreview(
expectedMessages: firstLaunchMessagesWeb,
useWeb: true,
dtdUri: dtdUri,
);
await completer.future;
});
});
}

View File

@@ -1,33 +0,0 @@
// Copyright 2014 The Flutter 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 'dart:async';
import 'package:dtd/dtd.dart';
/// Provides services, streams, and RPC invocations to interact with Flutter developer tooling.
class WidgetPreviewScaffoldDtdServices {
/// Environment variable for the DTD URI.
static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI';
/// Connects to the Dart Tooling Daemon (DTD) specified by the Flutter tool.
///
/// If the connection is successful, the Widget Preview Scaffold will register services and
/// subscribe to various streams to interact directly with other tooling (e.g., IDEs).
Future<void> connect() async {
final Uri dtdWsUri = Uri.parse(
const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar),
);
_dtd = await DartToolingDaemon.connect(dtdWsUri);
unawaited(
_dtd.postEvent(
'WidgetPreviewScaffold',
'Connected',
const <String, Object?>{},
),
);
}
late final DartToolingDaemon _dtd;
}

View File

@@ -13,7 +13,6 @@ import 'package:flutter/services.dart';
import 'package:stack_trace/stack_trace.dart';
import 'controls.dart';
import 'dtd_services.dart';
import 'generated_preview.dart';
import 'utils.dart';
import 'widget_preview.dart';
@@ -411,8 +410,6 @@ class PreviewAssetBundle extends PlatformAssetBundle {
/// the preview scaffold project which prevents us from being able to use hot
/// restart to iterate on this file.
Future<void> mainImpl() async {
// TODO(bkonyi): store somewhere.
await WidgetPreviewScaffoldDtdServices().connect();
runApp(_WidgetPreviewScaffold());
}

View File

@@ -12,7 +12,6 @@ dependencies:
flutter_test:
sdk: flutter
# These will be replaced with proper constraints after the template is hydrated.
dtd: 2.5.0
flutter_lints: 5.0.0
stack_trace: 1.12.1
@@ -21,13 +20,7 @@ dependencies:
characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
fake_async: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
json_rpc_2: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker: 10.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -41,15 +34,10 @@ dependencies:
string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
unified_analytics: 7.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 15.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
flutter:
uses-material-design: true
# PUBSPEC CHECKSUM: 4b12
# PUBSPEC CHECKSUM: 367e