Unverified Commit b09e64e1 authored by xster's avatar xster Committed by GitHub

Support iOS devices reporting pressure data of 0 (#28478)

parent a49d93b9
...@@ -206,12 +206,10 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -206,12 +206,10 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
@override @override
void addPointer(PointerEvent event) { void addPointer(PointerEvent event) {
assert(event.pressureMax >= 1.0); // If the device has a maximum pressure of less than or equal to 1, it
// If the device has a maximum pressure of less than or equal to 1, // doesn't have touch pressure sensing capabilities. Do not participate
// indicating a faux pressure sensor on this device or a device without a // in the gesture arena.
// pressure sensor (ie. on a non iOS device) we want do not want any if (!(event is PointerUpEvent) && event.pressureMax <= 1.0) {
// callbacks to be called.
if (!(event is PointerUpEvent) && event.pressureMax == 1.0) {
resolve(GestureDisposition.rejected); resolve(GestureDisposition.rejected);
} else { } else {
startTrackingPointer(event.pointer); startTrackingPointer(event.pointer);
......
...@@ -1537,6 +1537,7 @@ void main() { ...@@ -1537,6 +1537,7 @@ void main() {
controller.selection, controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12), const TextSelection(baseOffset: 8, extentOffset: 12),
); );
// Shows toolbar.
expect(find.byType(CupertinoButton), findsNWidgets(3)); expect(find.byType(CupertinoButton), findsNWidgets(3));
}, },
); );
...@@ -1641,11 +1642,52 @@ void main() { ...@@ -1641,11 +1642,52 @@ void main() {
); );
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
// Shows toolbar.
expect(find.byType(CupertinoButton), findsNWidgets(3)); expect(find.byType(CupertinoButton), findsNWidgets(3));
}, });
testWidgets('force press on unsupported devices falls back to tap', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
await tester.pumpWidget(
CupertinoApp(
home: Center(
child: CupertinoTextField(
controller: controller,
),
),
),
); );
final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
const int pointerValue = 1;
final TestGesture gesture = await tester.createGesture();
await gesture.downWithCustomEvent(
textfieldStart + const Offset(150.0, 5.0),
PointerDownEvent(
pointer: pointerValue,
position: textfieldStart + const Offset(150.0, 5.0),
// iPhone 6 and below report 0 across the board.
pressure: 0,
pressureMax: 0,
pressureMin: 0,
),
);
await gesture.up();
// Fall back to a single tap which selects the edge of the word.
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
await tester.pump();
// Falling back to a single tap doesn't trigger a toolbar.
expect(find.byType(CupertinoButton), findsNothing);
});
testWidgets( testWidgets(
'text field respects theme', 'text field respects theme',
(WidgetTester tester) async { (WidgetTester tester) async {
......
...@@ -109,34 +109,23 @@ void main() { ...@@ -109,34 +109,23 @@ void main() {
expect(ended, 1); expect(ended, 1);
}); });
testGesture('Force presses are not recognized on devices with low maxmium pressure', (GestureTester tester) { testGesture('Invalid pressure ranges capabilities are not recognized', (GestureTester tester) {
// Device specific constants that represent those from the iPhone X void testGestureWithMaxPressure(double pressureMax) {
const double pressureMin = 0;
const double pressureMax = 1.0;
// Interpolated Flutter pressure values.
const double startPressure = 0.4; // = Device pressure of 2.66.
const double peakPressure = 0.85; // = Device pressure of 5.66.
int started = 0; int started = 0;
int peaked = 0; int peaked = 0;
int updated = 0; int updated = 0;
int ended = 0; int ended = 0;
void onStart(ForcePressDetails details) { final ForcePressGestureRecognizer force = ForcePressGestureRecognizer();
started += 1;
}
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
force.onStart = onStart; force.onStart = (ForcePressDetails details) => started += 1;
force.onPeak = (ForcePressDetails details) => peaked += 1; force.onPeak = (ForcePressDetails details) => peaked += 1;
force.onUpdate = (ForcePressDetails details) => updated += 1; force.onUpdate = (ForcePressDetails details) => updated += 1;
force.onEnd = (ForcePressDetails details) => ended += 1; force.onEnd = (ForcePressDetails details) => ended += 1;
const int pointerValue = 1; const int pointerValue = 1;
final TestPointer pointer = TestPointer(pointerValue); final TestPointer pointer = TestPointer(pointerValue);
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 0, pressureMin: pressureMin, pressureMax: pressureMax); final PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: const Offset(10.0, 10.0), pressure: 0, pressureMin: 0, pressureMax: pressureMax);
pointer.setDownInfo(down, const Offset(10.0, 10.0)); pointer.setDownInfo(down, const Offset(10.0, 10.0));
force.addPointer(down); force.addPointer(down);
tester.closeArena(pointerValue); tester.closeArena(pointerValue);
...@@ -147,59 +136,28 @@ void main() { ...@@ -147,59 +136,28 @@ void main() {
expect(ended, 0); expect(ended, 0);
// Pressure fed into the test environment simulates the values received directly from the device. // Pressure fed into the test environment simulates the values received directly from the device.
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.5, pressureMin: pressureMin, pressureMax: pressureMax)); tester.route(PointerMoveEvent(pointer: pointerValue, position: const Offset(10.0, 10.0), pressure: 10, pressureMin: 0, pressureMax: pressureMax));
// We have not hit the start pressure, so no events should be true. // Regardless of current pressure, this recognizer shouldn't participate or
expect(started, 0); // trigger any callbacks.
expect(peaked, 0);
expect(updated, 0);
expect(ended, 0);
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 2.8, pressureMin: pressureMin, pressureMax: pressureMax));
// We have just hit the start pressure so just the start event should be triggered and one update call should have occurred.
expect(started, 0); expect(started, 0);
expect(peaked, 0); expect(peaked, 0);
expect(updated, 0); expect(updated, 0);
expect(ended, 0); expect(ended, 0);
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 5.0, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
// We have exceeded the start pressure so update should be greater than 0.
expect(started, 0);
expect(updated, 0);
expect(peaked, 0);
expect(ended, 0);
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 6.0, pressureMin: pressureMin, pressureMax: pressureMax));
// We have exceeded the peak pressure so peak pressure should be true.
expect(started, 0);
expect(updated, 0);
expect(peaked, 0);
expect(ended, 0);
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 3.3, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 4.0, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 5.0, pressureMin: pressureMin, pressureMax: pressureMax));
tester.route(const PointerMoveEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 1.0, pressureMin: pressureMin, pressureMax: pressureMax));
// Update is still called.
expect(started, 0);
expect(updated, 0);
expect(peaked, 0);
expect(ended, 0);
tester.route(pointer.up()); tester.route(pointer.up());
// We have ended the gesture so ended should be true. // There should still be no callbacks.
expect(started, 0); expect(started, 0);
expect(updated, 0); expect(updated, 0);
expect(peaked, 0); expect(peaked, 0);
expect(ended, 0); expect(ended, 0);
}
testGestureWithMaxPressure(0);
testGestureWithMaxPressure(1);
testGestureWithMaxPressure(-1);
testGestureWithMaxPressure(0.5);
}); });
testGesture('If minimum pressure is not reached, start and end callbacks are not called', (GestureTester tester) { testGesture('If minimum pressure is not reached, start and end callbacks are not called', (GestureTester tester) {
......
...@@ -4083,6 +4083,7 @@ void main() { ...@@ -4083,6 +4083,7 @@ void main() {
controller.selection, controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12), const TextSelection(baseOffset: 8, extentOffset: 12),
); );
// The toolbar is still showing.
expect(find.byType(CupertinoButton), findsNWidgets(3)); expect(find.byType(CupertinoButton), findsNWidgets(3));
}, },
); );
...@@ -4272,6 +4273,7 @@ void main() { ...@@ -4272,6 +4273,7 @@ void main() {
controller.selection, controller.selection,
const TextSelection.collapsed(offset: 3, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 3, affinity: TextAffinity.downstream),
); );
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.moveBy(const Offset(50, 0)); await gesture.moveBy(const Offset(50, 0));
...@@ -4282,6 +4284,7 @@ void main() { ...@@ -4282,6 +4284,7 @@ void main() {
controller.selection, controller.selection,
const TextSelection.collapsed(offset: 6, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 6, affinity: TextAffinity.downstream),
); );
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.moveBy(const Offset(50, 0)); await gesture.moveBy(const Offset(50, 0));
...@@ -4292,6 +4295,7 @@ void main() { ...@@ -4292,6 +4295,7 @@ void main() {
controller.selection, controller.selection,
const TextSelection.collapsed(offset: 9, affinity: TextAffinity.downstream), const TextSelection.collapsed(offset: 9, affinity: TextAffinity.downstream),
); );
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing); expect(find.byType(CupertinoButton), findsNothing);
await gesture.up(); await gesture.up();
...@@ -4554,7 +4558,6 @@ void main() { ...@@ -4554,7 +4558,6 @@ void main() {
); );
testWidgets('force press does not select a word on (android)', (WidgetTester tester) async { testWidgets('force press does not select a word on (android)', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
final TextEditingController controller = TextEditingController( final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure', text: 'Atwater Peel Sherbrooke Bonaventure',
); );
...@@ -4588,27 +4591,26 @@ void main() { ...@@ -4588,27 +4591,26 @@ void main() {
expect(controller.selection, const TextSelection.collapsed(offset: -1)); expect(controller.selection, const TextSelection.collapsed(offset: -1));
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
expect(find.byType(FlatButton), findsNothing); expect(find.byType(FlatButton), findsNothing);
debugDefaultTargetPlatformOverride = null;
}); });
testWidgets('force press selects word (iOS)', (WidgetTester tester) async { testWidgets('force press selects word (iOS)', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final TextEditingController controller = TextEditingController( final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure', text: 'Atwater Peel Sherbrooke Bonaventure',
); );
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( MaterialApp(
home: Center( theme: ThemeData(platform: TargetPlatform.iOS),
child: CupertinoTextField( home: Material(
child: TextField(
controller: controller, controller: controller,
), ),
), ),
), ),
); );
final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField)); final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
const int pointerValue = 1; const int pointerValue = 1;
final Offset offset = textfieldStart + const Offset(150.0, 5.0); final Offset offset = textfieldStart + const Offset(150.0, 5.0);
...@@ -4632,9 +4634,54 @@ void main() { ...@@ -4632,9 +4634,54 @@ void main() {
); );
await gesture.up(); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
expect(find.byType(CupertinoButton), findsNWidgets(3)); expect(find.byType(CupertinoButton), findsNWidgets(3));
debugDefaultTargetPlatformOverride = null; });
testWidgets('tap on non-force-press-supported devices work (iOS)', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
home: Material(
child: TextField(
controller: controller,
),
),
),
);
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
const int pointerValue = 1;
final Offset offset = textfieldStart + const Offset(150.0, 5.0);
final TestGesture gesture = await tester.createGesture();
await gesture.downWithCustomEvent(
offset,
PointerDownEvent(
pointer: pointerValue,
position: offset,
// iPhone 6 and below report 0 across the board.
pressure: 0,
pressureMax: 0,
pressureMin: 0,
),
);
await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: textfieldStart + const Offset(150.0, 5.0), pressure: 0.5, pressureMin: 0, pressureMax: 1));
await gesture.up();
// The event should fallback to a normal tap and move the cursor.
// Single taps selects the edge of the word.
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
await tester.pump();
// Single taps shouldn't trigger the toolbar.
expect(find.byType(CupertinoButton), findsNothing);
}); });
testWidgets('default TextField debugFillProperties', (WidgetTester tester) async { testWidgets('default TextField debugFillProperties', (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