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 {
@override
void rejectGesture(int pointer) {
// Currently the engine rejects the gesture when the sequence is done.
// This doesn't work well with gesture recognizers that recognize after the sequence
// has ended.
// TODO(amirh): trigger an engine gesture reject here.
// https://github.com/flutter/flutter/issues/24076
controller.rejectGesture().catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of active gesture rejection before the engine part.
// There will be some commit range where we call rejectGesture from the framework and it
// 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() {
......
......@@ -630,6 +630,18 @@ class UiKitViewController {
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.
///
/// The [UiKitViewController] object is unusable after calling this.
......
......@@ -159,9 +159,12 @@ class FakeIosPlatformViewsController {
// delayed until it completes.
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>{};
// Maps a view id to the number of gestures it rejected so far.
final Map<int, int> gesturesRejected = <int, int>{};
void registerViewType(String viewType) {
_registeredViewTypes.add(viewType);
}
......@@ -174,6 +177,8 @@ class FakeIosPlatformViewsController {
return _dispose(call);
case 'acceptGesture':
return _acceptGesture(call);
case 'rejectGesture':
return _rejectGesture(call);
}
return Future<dynamic>.sync(() => null);
}
......@@ -202,6 +207,7 @@ class FakeIosPlatformViewsController {
_views[id] = FakeUiKitView(id, viewType, creationParams);
gesturesAccepted[id] = 0;
gesturesRejected[id] = 0;
return Future<int>.sync(() => null);
}
......@@ -212,6 +218,13 @@ class FakeIosPlatformViewsController {
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) {
final int id = call.arguments;
......
......@@ -1196,6 +1196,7 @@ void main() {
expect(verticalDragAcceptedByParent, true);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
});
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
......@@ -1237,6 +1238,7 @@ void main() {
expect(verticalDragAcceptedByParent, false);
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 {
......@@ -1276,6 +1278,7 @@ void main() {
expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
......@@ -1320,6 +1323,7 @@ void main() {
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async {
......@@ -1359,6 +1363,7 @@ void main() {
// the Android view). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
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