Unverified Commit 50f9b883 authored by Amir Hardon's avatar Amir Hardon Committed by GitHub

Actively reject UiKitView gestures. (#25792)

flutter/engine#7307 changes the engine side of embedded UIView to only
reject gestures when the framework sends a `rejectGesture` message, so
that gesture resolution can done after a touch sequence has ended (see
PR description for flutter/engine#7307 for more details).

This change makes the framework send a `rejectGesture` message to the
engine when a UiKitView rejects a gesture.

I'm planning to land this PR before the engine side change, so right now
it swallows the exception thrown if there is no engine implementation
for `rejectGesture` (which keeps us with the current behavior). After
this change lands I'll land the engine PR, and then clean up the part
that swallows the exception.
parent c24923c7
...@@ -412,11 +412,17 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -412,11 +412,17 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
@override @override
void rejectGesture(int pointer) { void rejectGesture(int pointer) {
// Currently the engine rejects the gesture when the sequence is done. controller.rejectGesture().catchError((dynamic e) {
// This doesn't work well with gesture recognizers that recognize after the sequence if (e is MissingPluginException) {
// has ended. // We land the framework part of active gesture rejection before the engine part.
// TODO(amirh): trigger an engine gesture reject here. // There will be some commit range where we call rejectGesture from the framework and it
// https://github.com/flutter/flutter/issues/24076 // isn't implemented in the engine, if that is the case we swallow the MissingPluginException.
// Once the engine support lands we will remove the enclosing catchError call.
// TODO(amirh): remove this once the engine supports active gesture rejection.
return;
}
throw e;
});
} }
void reset() { void reset() {
......
...@@ -630,6 +630,18 @@ class UiKitViewController { ...@@ -630,6 +630,18 @@ class UiKitViewController {
return SystemChannels.platform_views.invokeMethod('acceptGesture', args); return SystemChannels.platform_views.invokeMethod('acceptGesture', args);
} }
/// Rejects an active gesture.
///
/// When a touch sequence is happening on the embedded UIView all touch events are delayed.
/// Calling this method drops the buffered touch events and prevents any future touch events for
/// the pointers that are part of the active touch sequence from arriving to the embedded view.
Future<void> rejectGesture() {
final Map<String, dynamic> args = <String, dynamic> {
'id': id,
};
return SystemChannels.platform_views.invokeMethod('rejectGesture', args);
}
/// Disposes the view. /// Disposes the view.
/// ///
/// The [UiKitViewController] object is unusable after calling this. /// The [UiKitViewController] object is unusable after calling this.
......
...@@ -159,9 +159,12 @@ class FakeIosPlatformViewsController { ...@@ -159,9 +159,12 @@ class FakeIosPlatformViewsController {
// delayed until it completes. // delayed until it completes.
Completer<void> creationDelay; Completer<void> creationDelay;
// Maps a view id to the number of gestures it accepted so fat. // Maps a view id to the number of gestures it accepted so far.
final Map<int, int> gesturesAccepted = <int, int>{}; final Map<int, int> gesturesAccepted = <int, int>{};
// Maps a view id to the number of gestures it rejected so far.
final Map<int, int> gesturesRejected = <int, int>{};
void registerViewType(String viewType) { void registerViewType(String viewType) {
_registeredViewTypes.add(viewType); _registeredViewTypes.add(viewType);
} }
...@@ -174,6 +177,8 @@ class FakeIosPlatformViewsController { ...@@ -174,6 +177,8 @@ class FakeIosPlatformViewsController {
return _dispose(call); return _dispose(call);
case 'acceptGesture': case 'acceptGesture':
return _acceptGesture(call); return _acceptGesture(call);
case 'rejectGesture':
return _rejectGesture(call);
} }
return Future<dynamic>.sync(() => null); return Future<dynamic>.sync(() => null);
} }
...@@ -202,6 +207,7 @@ class FakeIosPlatformViewsController { ...@@ -202,6 +207,7 @@ class FakeIosPlatformViewsController {
_views[id] = FakeUiKitView(id, viewType, creationParams); _views[id] = FakeUiKitView(id, viewType, creationParams);
gesturesAccepted[id] = 0; gesturesAccepted[id] = 0;
gesturesRejected[id] = 0;
return Future<int>.sync(() => null); return Future<int>.sync(() => null);
} }
...@@ -212,6 +218,13 @@ class FakeIosPlatformViewsController { ...@@ -212,6 +218,13 @@ class FakeIosPlatformViewsController {
return Future<int>.sync(() => null); return Future<int>.sync(() => null);
} }
Future<dynamic> _rejectGesture(MethodCall call) async {
final Map<dynamic, dynamic> args = call.arguments;
final int id = args['id'];
gesturesRejected[id] += 1;
return Future<int>.sync(() => null);
}
Future<dynamic> _dispose(MethodCall call) { Future<dynamic> _dispose(MethodCall call) {
final int id = call.arguments; final int id = call.arguments;
......
...@@ -1196,6 +1196,7 @@ void main() { ...@@ -1196,6 +1196,7 @@ void main() {
expect(verticalDragAcceptedByParent, true); expect(verticalDragAcceptedByParent, true);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0); expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
}); });
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async { testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
...@@ -1237,6 +1238,7 @@ void main() { ...@@ -1237,6 +1238,7 @@ void main() {
expect(verticalDragAcceptedByParent, false); expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1); expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
}); });
testWidgets('UiKitView can claim gesture after all pointers are up', (WidgetTester tester) async { testWidgets('UiKitView can claim gesture after all pointers are up', (WidgetTester tester) async {
...@@ -1276,6 +1278,7 @@ void main() { ...@@ -1276,6 +1278,7 @@ void main() {
expect(verticalDragAcceptedByParent, false); expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1); expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
}); });
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async { testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
...@@ -1320,6 +1323,7 @@ void main() { ...@@ -1320,6 +1323,7 @@ void main() {
await gesture.up(); await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1); expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
}); });
testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async { testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async {
...@@ -1359,6 +1363,7 @@ void main() { ...@@ -1359,6 +1363,7 @@ void main() {
// the Android view). Here we assert that with the eager recognizer in the gesture team the // the Android view). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched. // pointer down event is immediately dispatched.
expect(viewsController.gesturesAccepted[currentViewId + 1], 1); expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
}); });
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async { testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment