[flutter_releases] Flutter 2.0.2 Stable Framework Cherrypicks (#77850)
* Reject unaccepted pointers in Drag recognizer (#75943)
* Delete unzipped FlutterMacOS.framework before replacing artifact (#77316)
* Fix missing root Scaffold check in ScaffoldMessenger (#77410)
* Roll engine cherrypicks
Co-authored-by: Michael Goderbauer <goderbauer@google.com>
Co-authored-by: Jenn Magder <magder@google.com>
Co-authored-by: Kate Lovett <katelovett@google.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 1daead4..ccb5e92 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-40441def692f444660a11e20fac37af9050245ab
+5d8bf811b3072390933d69f3e289a4bb673636c4
diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart
index 82ab77d..15eaf30 100644
--- a/packages/flutter/lib/src/gestures/monodrag.dart
+++ b/packages/flutter/lib/src/gestures/monodrag.dart
@@ -309,15 +309,16 @@
}
}
if (event is PointerUpEvent || event is PointerCancelEvent) {
- _giveUpPointer(
- event.pointer,
- reject: event is PointerCancelEvent || _state ==_DragState.possible,
- );
+ _giveUpPointer(event.pointer);
}
}
+ final Set<int> _acceptedActivePointers = <int>{};
+
@override
void acceptGesture(int pointer) {
+ assert(!_acceptedActivePointers.contains(pointer));
+ _acceptedActivePointers.add(pointer);
if (_state != _DragState.accepted) {
_state = _DragState.accepted;
final OffsetPair delta = _pendingDragOffset;
@@ -384,32 +385,36 @@
_state = _DragState.ready;
}
- void _giveUpPointer(int pointer, {bool reject = true}) {
+ void _giveUpPointer(int pointer) {
stopTrackingPointer(pointer);
- if (reject)
+ // If we never accepted the pointer, we reject it since we are no longer
+ // interested in winning the gesture arena for it.
+ if (!_acceptedActivePointers.remove(pointer))
resolvePointer(pointer, GestureDisposition.rejected);
}
void _checkDown() {
assert(_initialButtons == kPrimaryButton);
- final DragDownDetails details = DragDownDetails(
- globalPosition: _initialPosition.global,
- localPosition: _initialPosition.local,
- );
- if (onDown != null)
+ if (onDown != null) {
+ final DragDownDetails details = DragDownDetails(
+ globalPosition: _initialPosition.global,
+ localPosition: _initialPosition.local,
+ );
invokeCallback<void>('onDown', () => onDown!(details));
+ }
}
void _checkStart(Duration timestamp, int pointer) {
assert(_initialButtons == kPrimaryButton);
- final DragStartDetails details = DragStartDetails(
- sourceTimeStamp: timestamp,
- globalPosition: _initialPosition.global,
- localPosition: _initialPosition.local,
- kind: getKindForPointer(pointer),
- );
- if (onStart != null)
+ if (onStart != null) {
+ final DragStartDetails details = DragStartDetails(
+ sourceTimeStamp: timestamp,
+ globalPosition: _initialPosition.global,
+ localPosition: _initialPosition.local,
+ kind: getKindForPointer(pointer),
+ );
invokeCallback<void>('onStart', () => onStart!(details));
+ }
}
void _checkUpdate({
@@ -420,15 +425,16 @@
Offset? localPosition,
}) {
assert(_initialButtons == kPrimaryButton);
- final DragUpdateDetails details = DragUpdateDetails(
- sourceTimeStamp: sourceTimeStamp,
- delta: delta,
- primaryDelta: primaryDelta,
- globalPosition: globalPosition,
- localPosition: localPosition,
- );
- if (onUpdate != null)
+ if (onUpdate != null) {
+ final DragUpdateDetails details = DragUpdateDetails(
+ sourceTimeStamp: sourceTimeStamp,
+ delta: delta,
+ primaryDelta: primaryDelta,
+ globalPosition: globalPosition,
+ localPosition: localPosition,
+ );
invokeCallback<void>('onUpdate', () => onUpdate!(details));
+ }
}
void _checkEnd(int pointer) {
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 12046bc..957c37c 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -281,7 +281,7 @@
void _register(ScaffoldState scaffold) {
_scaffolds.add(scaffold);
- if (_snackBars.isNotEmpty) {
+ if (_snackBars.isNotEmpty && _isRoot(scaffold)) {
scaffold._updateSnackBar();
}
}
diff --git a/packages/flutter/test/gestures/monodrag_test.dart b/packages/flutter/test/gestures/monodrag_test.dart
new file mode 100644
index 0000000..3ca3b07
--- /dev/null
+++ b/packages/flutter/test/gestures/monodrag_test.dart
@@ -0,0 +1,56 @@
+// 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 'package:flutter/gestures.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'gesture_tester.dart';
+
+void main() {
+ setUp(ensureGestureBinding);
+
+ testGesture('do not crash on up event for a pending pointer after winning arena for another pointer', (GestureTester tester) {
+ // Regression test for https://github.com/flutter/flutter/issues/75061.
+
+ final VerticalDragGestureRecognizer v = VerticalDragGestureRecognizer()
+ ..onStart = (_) { };
+ final HorizontalDragGestureRecognizer h = HorizontalDragGestureRecognizer()
+ ..onStart = (_) { };
+
+ const PointerDownEvent down90 = PointerDownEvent(
+ pointer: 90,
+ position: Offset(10.0, 10.0),
+ );
+
+ const PointerUpEvent up90 = PointerUpEvent(
+ pointer: 90,
+ position: Offset(10.0, 10.0),
+ );
+
+ const PointerDownEvent down91 = PointerDownEvent(
+ pointer: 91,
+ position: Offset(20.0, 20.0),
+ );
+
+ const PointerUpEvent up91 = PointerUpEvent(
+ pointer: 91,
+ position: Offset(20.0, 20.0),
+ );
+
+ v.addPointer(down90);
+ GestureBinding.instance!.gestureArena.close(90);
+ h.addPointer(down91);
+ v.addPointer(down91);
+ GestureBinding.instance!.gestureArena.close(91);
+ tester.async.flushMicrotasks();
+
+ GestureBinding.instance!.handleEvent(up90, HitTestEntry(MockHitTestTarget()));
+ GestureBinding.instance!.handleEvent(up91, HitTestEntry(MockHitTestTarget()));
+ });
+}
+
+class MockHitTestTarget implements HitTestTarget {
+ @override
+ void handleEvent(PointerEvent event, HitTestEntry entry) { }
+}
diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart
index 2c612d7..ab0852f 100644
--- a/packages/flutter/test/material/scaffold_test.dart
+++ b/packages/flutter/test/material/scaffold_test.dart
@@ -2197,6 +2197,77 @@
' MaterialApp at the top of your application widget tree.\n'
));
});
+
+ testWidgets('ScaffoldMessenger checks for nesting when a new Scaffold is registered', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/77251
+ const String snackBarContent = 'SnackBar Content';
+ await tester.pumpWidget(MaterialApp(
+ home: Builder(
+ builder: (BuildContext context) => Scaffold(
+ body: Scaffold(
+ body: TextButton(
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute<void>(
+ builder: (BuildContext context) {
+ return Scaffold(
+ body: Column(
+ children: <Widget>[
+ TextButton(
+ onPressed: () {
+ const SnackBar snackBar = SnackBar(
+ content: Text(snackBarContent),
+ behavior: SnackBarBehavior.floating,
+ );
+ ScaffoldMessenger.of(context).showSnackBar(snackBar);
+ },
+ child: const Text('Show SnackBar'),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context, null);
+ },
+ child: const Text('Pop route'),
+ )
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ child: const Text('Push route'),
+ ),
+ ),
+ ),
+ )
+ ));
+
+ expect(find.text(snackBarContent), findsNothing);
+ await tester.tap(find.text('Push route'));
+ await tester.pumpAndSettle();
+ expect(find.text(snackBarContent), findsNothing);
+ expect(find.text('Pop route'), findsOneWidget);
+
+ // Show SnackBar on second page
+ await tester.tap(find.text('Show SnackBar'));
+ await tester.pump();
+ expect(find.text(snackBarContent), findsOneWidget);
+ // Pop the second page, the SnackBar completes a hero animation to the next route.
+ // If we have not handled the nested Scaffolds properly, this will throw an
+ // exception as duplicate SnackBars on the first route would have a common hero tag.
+ await tester.tap(find.text('Pop route'));
+ await tester.pump();
+ // There are SnackBars two during the execution of the hero animation.
+ expect(find.text(snackBarContent), findsNWidgets(2));
+ await tester.pumpAndSettle();
+ expect(find.text(snackBarContent), findsOneWidget);
+ // Allow the SnackBar to animate out
+ await tester.pump(const Duration(seconds: 4));
+ await tester.pumpAndSettle();
+ expect(find.text(snackBarContent), findsNothing);
+ });
}
class _GeometryListener extends StatefulWidget {
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 197bf54..11e3bf3 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -927,6 +927,7 @@
final File frameworkZip = fileSystem.file(fileSystem.path.join(dir.path, 'FlutterMacOS.framework.zip'));
if (frameworkZip.existsSync()) {
final Directory framework = fileSystem.directory(fileSystem.path.join(dir.path, 'FlutterMacOS.framework'));
+ ErrorHandlingFileSystem.deleteIfExists(framework, recursive: true);
framework.createSync();
operatingSystemUtils.unzip(frameworkZip, framework);
}
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index d61cf94..7422d83 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -278,6 +278,33 @@
verify(operatingSystemUtils.chmod(argThat(hasPath(dir.path)), 'a+r,a+x'));
});
+ testWithoutContext('EngineCachedArtifact removes unzipped FlutterMacOS.framework before replacing', () async {
+ final OperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils();
+ final MockCache cache = MockCache();
+ final FileSystem fileSystem = MemoryFileSystem.test();
+ final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
+ final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
+
+ when(cache.getArtifactDirectory(any)).thenReturn(artifactDir);
+ when(cache.getDownloadDir()).thenReturn(downloadDir);
+ final Directory binDir = artifactDir.childDirectory('bin_dir')..createSync();
+ binDir.childFile('FlutterMacOS.framework.zip').createSync();
+ final Directory unzippedFramework = binDir.childDirectory('FlutterMacOS.framework');
+ final File staleFile = unzippedFramework.childFile('stale_file')..createSync(recursive: true);
+ artifactDir.childFile('unused_url_path').createSync();
+
+ final FakeCachedArtifact artifact = FakeCachedArtifact(
+ cache: cache,
+ binaryDirs: <List<String>>[
+ <String>['bin_dir', 'unused_url_path'],
+ ],
+ requiredArtifacts: DevelopmentArtifact.universal,
+ );
+ await artifact.updateInner(MockArtifactUpdater(), fileSystem, operatingSystemUtils);
+ expect(unzippedFramework, exists);
+ expect(staleFile, isNot(exists));
+ });
+
testWithoutContext('IosUsbArtifacts verifies executables for libimobiledevice in isUpToDateInner', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());