Support iOS devices reporting pressure data of 0 (#28478)
This commit is contained in:
parent
a49d93b93a
commit
b09e64e142
@ -206,12 +206,10 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
|
||||
|
||||
@override
|
||||
void addPointer(PointerEvent event) {
|
||||
assert(event.pressureMax >= 1.0);
|
||||
// If the device has a maximum pressure of less than or equal to 1,
|
||||
// indicating a faux pressure sensor on this device or a device without a
|
||||
// pressure sensor (ie. on a non iOS device) we want do not want any
|
||||
// callbacks to be called.
|
||||
if (!(event is PointerUpEvent) && event.pressureMax == 1.0) {
|
||||
// If the device has a maximum pressure of less than or equal to 1, it
|
||||
// doesn't have touch pressure sensing capabilities. Do not participate
|
||||
// in the gesture arena.
|
||||
if (!(event is PointerUpEvent) && event.pressureMax <= 1.0) {
|
||||
resolve(GestureDisposition.rejected);
|
||||
} else {
|
||||
startTrackingPointer(event.pointer);
|
||||
|
@ -1537,6 +1537,7 @@ void main() {
|
||||
controller.selection,
|
||||
const TextSelection(baseOffset: 8, extentOffset: 12),
|
||||
);
|
||||
// Shows toolbar.
|
||||
expect(find.byType(CupertinoButton), findsNWidgets(3));
|
||||
},
|
||||
);
|
||||
@ -1607,44 +1608,85 @@ void main() {
|
||||
);
|
||||
|
||||
testWidgets('force press selects word', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
controller: controller,
|
||||
),
|
||||
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));
|
||||
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),
|
||||
pressure: 3.0,
|
||||
pressureMax: 6.0,
|
||||
pressureMin: 0.0
|
||||
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),
|
||||
pressure: 3.0,
|
||||
pressureMax: 6.0,
|
||||
pressureMin: 0.0
|
||||
),
|
||||
);
|
||||
// We expect the force press to select a word at the given location.
|
||||
expect(
|
||||
controller.selection,
|
||||
const TextSelection(baseOffset: 8, extentOffset: 12),
|
||||
);
|
||||
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
// Shows toolbar.
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
// We expect the force press to select a word at the given location.
|
||||
expect(
|
||||
controller.selection,
|
||||
const TextSelection(baseOffset: 8, extentOffset: 12),
|
||||
);
|
||||
),
|
||||
);
|
||||
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(CupertinoButton), findsNWidgets(3));
|
||||
},
|
||||
);
|
||||
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(
|
||||
'text field respects theme',
|
||||
|
@ -109,97 +109,55 @@ void main() {
|
||||
expect(ended, 1);
|
||||
});
|
||||
|
||||
testGesture('Force presses are not recognized on devices with low maxmium pressure', (GestureTester tester) {
|
||||
// Device specific constants that represent those from the iPhone X
|
||||
const double pressureMin = 0;
|
||||
const double pressureMax = 1.0;
|
||||
testGesture('Invalid pressure ranges capabilities are not recognized', (GestureTester tester) {
|
||||
void testGestureWithMaxPressure(double pressureMax) {
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 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.
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer();
|
||||
|
||||
int started = 0;
|
||||
int peaked = 0;
|
||||
int updated = 0;
|
||||
int ended = 0;
|
||||
force.onStart = (ForcePressDetails details) => started += 1;
|
||||
force.onPeak = (ForcePressDetails details) => peaked += 1;
|
||||
force.onUpdate = (ForcePressDetails details) => updated += 1;
|
||||
force.onEnd = (ForcePressDetails details) => ended += 1;
|
||||
|
||||
void onStart(ForcePressDetails details) {
|
||||
started += 1;
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
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));
|
||||
force.addPointer(down);
|
||||
tester.closeArena(pointerValue);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
// Pressure fed into the test environment simulates the values received directly from the device.
|
||||
tester.route(PointerMoveEvent(pointer: pointerValue, position: const Offset(10.0, 10.0), pressure: 10, pressureMin: 0, pressureMax: pressureMax));
|
||||
|
||||
// Regardless of current pressure, this recognizer shouldn't participate or
|
||||
// trigger any callbacks.
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
tester.route(pointer.up());
|
||||
|
||||
// There should still be no callbacks.
|
||||
expect(started, 0);
|
||||
expect(updated, 0);
|
||||
expect(peaked, 0);
|
||||
expect(ended, 0);
|
||||
}
|
||||
|
||||
final ForcePressGestureRecognizer force = ForcePressGestureRecognizer(startPressure: startPressure, peakPressure: peakPressure);
|
||||
|
||||
force.onStart = onStart;
|
||||
force.onPeak = (ForcePressDetails details) => peaked += 1;
|
||||
force.onUpdate = (ForcePressDetails details) => updated += 1;
|
||||
force.onEnd = (ForcePressDetails details) => ended += 1;
|
||||
|
||||
const int pointerValue = 1;
|
||||
final TestPointer pointer = TestPointer(pointerValue);
|
||||
const PointerDownEvent down = PointerDownEvent(pointer: pointerValue, position: Offset(10.0, 10.0), pressure: 0, pressureMin: pressureMin, pressureMax: pressureMax);
|
||||
pointer.setDownInfo(down, const Offset(10.0, 10.0));
|
||||
force.addPointer(down);
|
||||
tester.closeArena(pointerValue);
|
||||
|
||||
expect(started, 0);
|
||||
expect(peaked, 0);
|
||||
expect(updated, 0);
|
||||
expect(ended, 0);
|
||||
|
||||
// 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));
|
||||
|
||||
// We have not hit the start pressure, so no events should be true.
|
||||
expect(started, 0);
|
||||
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(peaked, 0);
|
||||
expect(updated, 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());
|
||||
|
||||
// We have ended the gesture so ended should be true.
|
||||
expect(started, 0);
|
||||
expect(updated, 0);
|
||||
expect(peaked, 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) {
|
||||
|
@ -4083,6 +4083,7 @@ void main() {
|
||||
controller.selection,
|
||||
const TextSelection(baseOffset: 8, extentOffset: 12),
|
||||
);
|
||||
// The toolbar is still showing.
|
||||
expect(find.byType(CupertinoButton), findsNWidgets(3));
|
||||
},
|
||||
);
|
||||
@ -4272,6 +4273,7 @@ void main() {
|
||||
controller.selection,
|
||||
const TextSelection.collapsed(offset: 3, affinity: TextAffinity.downstream),
|
||||
);
|
||||
// Cursor move doesn't trigger a toolbar initially.
|
||||
expect(find.byType(CupertinoButton), findsNothing);
|
||||
|
||||
await gesture.moveBy(const Offset(50, 0));
|
||||
@ -4282,6 +4284,7 @@ void main() {
|
||||
controller.selection,
|
||||
const TextSelection.collapsed(offset: 6, affinity: TextAffinity.downstream),
|
||||
);
|
||||
// Still no toolbar.
|
||||
expect(find.byType(CupertinoButton), findsNothing);
|
||||
|
||||
await gesture.moveBy(const Offset(50, 0));
|
||||
@ -4292,6 +4295,7 @@ void main() {
|
||||
controller.selection,
|
||||
const TextSelection.collapsed(offset: 9, affinity: TextAffinity.downstream),
|
||||
);
|
||||
// Still no toolbar.
|
||||
expect(find.byType(CupertinoButton), findsNothing);
|
||||
|
||||
await gesture.up();
|
||||
@ -4554,7 +4558,6 @@ void main() {
|
||||
);
|
||||
|
||||
testWidgets('force press does not select a word on (android)', (WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
@ -4588,27 +4591,26 @@ void main() {
|
||||
expect(controller.selection, const TextSelection.collapsed(offset: -1));
|
||||
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump();
|
||||
expect(find.byType(FlatButton), findsNothing);
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets('force press selects word (iOS)', (WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: TargetPlatform.iOS),
|
||||
home: Material(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
|
||||
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
|
||||
|
||||
const int pointerValue = 1;
|
||||
final Offset offset = textfieldStart + const Offset(150.0, 5.0);
|
||||
@ -4632,9 +4634,54 @@ void main() {
|
||||
);
|
||||
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump();
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user