Unverified Commit c58dca2a authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Reland "Disable cursor opacity animation on macOS, make iOS cursor animation...

Reland  "Disable cursor opacity animation on macOS, make iOS cursor animation discrete (#104335)" (#106893)
parent 1704d4f5
...@@ -1168,7 +1168,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements ...@@ -1168,7 +1168,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
forcePressEnabled = false; forcePressEnabled = false;
textSelectionControls ??= cupertinoDesktopTextSelectionControls; textSelectionControls ??= cupertinoDesktopTextSelectionControls;
paintCursorAboveText = true; paintCursorAboveText = true;
cursorOpacityAnimates = true; cursorOpacityAnimates = false;
cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor; cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor;
selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
cursorRadius ??= const Radius.circular(2.0); cursorRadius ??= const Radius.circular(2.0);
......
...@@ -702,40 +702,6 @@ void main() { ...@@ -702,40 +702,6 @@ void main() {
expect(editableText.cursorOffset, const Offset(-2.0 / 3.0, 0)); expect(editableText.cursorOffset, const Offset(-2.0 / 3.0, 0));
}); });
testWidgets('Cursor animates', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
home: CupertinoTextField(),
),
);
final Finder textFinder = find.byType(CupertinoTextField);
await tester.tap(textFinder);
await tester.pump();
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
final RenderEditable renderEditable = editableTextState.renderEditable;
expect(renderEditable.cursorColor!.alpha, 255);
await tester.pump(const Duration(milliseconds: 100));
await tester.pump(const Duration(milliseconds: 400));
expect(renderEditable.cursorColor!.alpha, 255);
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable.cursorColor!.alpha, 110);
await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable.cursorColor!.alpha, 16);
await tester.pump(const Duration(milliseconds: 50));
expect(renderEditable.cursorColor!.alpha, 0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Cursor radius is 2.0', (WidgetTester tester) async { testWidgets('Cursor radius is 2.0', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const CupertinoApp( const CupertinoApp(
...@@ -2998,7 +2964,7 @@ void main() { ...@@ -2998,7 +2964,7 @@ void main() {
// Selection should stay the same since it is set on tap up for mobile platforms. // Selection should stay the same since it is set on tap up for mobile platforms.
await touchGesture.down(gPos); await touchGesture.down(gPos);
await tester.pumpAndSettle(); await tester.pump();
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5); expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5); expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
......
...@@ -618,42 +618,6 @@ void main() { ...@@ -618,42 +618,6 @@ void main() {
await checkCursorToggle(); await checkCursorToggle();
}); });
testWidgets('Cursor animates', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: TextField(),
),
),
);
final Finder textFinder = find.byType(TextField);
await tester.tap(textFinder);
await tester.pump();
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
final RenderEditable renderEditable = editableTextState.renderEditable;
expect(renderEditable.cursorColor!.alpha, 255);
await tester.pump(const Duration(milliseconds: 100));
await tester.pump(const Duration(milliseconds: 400));
expect(renderEditable.cursorColor!.alpha, 255);
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable.cursorColor!.alpha, 110);
await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable.cursorColor!.alpha, 16);
await tester.pump(const Duration(milliseconds: 50));
expect(renderEditable.cursorColor!.alpha, 0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
// Regression test for https://github.com/flutter/flutter/issues/78918. // Regression test for https://github.com/flutter/flutter/issues/78918.
testWidgets('RenderEditable sets correct text editing value', (WidgetTester tester) async { testWidgets('RenderEditable sets correct text editing value', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: 'how are you'); final TextEditingController controller = TextEditingController(text: 'how are you');
...@@ -1340,7 +1304,7 @@ void main() { ...@@ -1340,7 +1304,7 @@ void main() {
editText = (findRenderEditable(tester).text! as TextSpan).text!; editText = (findRenderEditable(tester).text! as TextSpan).text!;
expect(editText.substring(editText.length - 1), '\u2022'); expect(editText.substring(editText.length - 1), '\u2022');
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android }));
testWidgets('desktop obscureText control test', (WidgetTester tester) async { testWidgets('desktop obscureText control test', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1994,7 +1958,7 @@ void main() { ...@@ -1994,7 +1958,7 @@ void main() {
// Selection should stay the same since it is set on tap up for mobile platforms. // Selection should stay the same since it is set on tap up for mobile platforms.
await touchGesture.down(gPos); await touchGesture.down(gPos);
await tester.pumpAndSettle(); await tester.pump();
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5); expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5); expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
......
...@@ -166,61 +166,75 @@ void main() { ...@@ -166,61 +166,75 @@ void main() {
); );
}); });
testWidgets('Cursor animates', (WidgetTester tester) async { testWidgets('Cursor animates on iOS', (WidgetTester tester) async {
const Widget widget = MaterialApp( await tester.pumpWidget(
const MaterialApp(
home: Material( home: Material(
child: TextField( child: TextField(),
maxLines: 3,
), ),
), ),
); );
await tester.pumpWidget(widget);
await tester.tap(find.byType(TextField)); final Finder textFinder = find.byType(TextField);
await tester.tap(textFinder);
await tester.pump(); await tester.pump();
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText)); final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
final RenderEditable renderEditable = editableTextState.renderEditable; final RenderEditable renderEditable = editableTextState.renderEditable;
expect(renderEditable.cursorColor!.alpha, 255); expect(renderEditable.cursorColor!.opacity, 1.0);
// Trigger initial timer. When focusing the first time, the cursor shows int walltimeMicrosecond = 0;
// for slightly longer than the average on time. double lastVerifiedOpacity = 1.0;
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
// Start timing standard cursor show period.
expect(renderEditable.cursorColor!.alpha, 255);
expect(renderEditable, paints..rrect(color: const Color(0xff2196f3)));
await tester.pump(const Duration(milliseconds: 500)); Future<void> verifyKeyFrame({ required double opacity, required int at }) async {
// Start to animate the cursor away. const int delta = 1;
expect(renderEditable.cursorColor!.alpha, 255); assert(at - delta > walltimeMicrosecond);
expect(renderEditable, paints..rrect(color: const Color(0xff2196f3))); await tester.pump(Duration(microseconds: at - delta - walltimeMicrosecond));
await tester.pump(const Duration(milliseconds: 100)); // Instead of verifying the opacity at each key frame, this function
expect(renderEditable.cursorColor!.alpha, 110); // verifies the opacity immediately *before* each key frame to avoid
expect(renderEditable, paints..rrect(color: const Color(0x6e2196f3))); // fp precision issues.
expect(
renderEditable.cursorColor!.opacity,
closeTo(lastVerifiedOpacity, 0.01),
reason: 'opacity at ${at-delta} microseconds',
);
await tester.pump(const Duration(milliseconds: 100)); walltimeMicrosecond = at - delta;
expect(renderEditable.cursorColor!.alpha, 16); lastVerifiedOpacity = opacity;
expect(renderEditable, paints..rrect(color: const Color(0x102196f3))); }
await tester.pump(const Duration(milliseconds: 100)); await verifyKeyFrame(opacity: 1.0, at: 500000);
expect(renderEditable.cursorColor!.alpha, 0); await verifyKeyFrame(opacity: 0.75, at: 537500);
// Don't try to draw the cursor. await verifyKeyFrame(opacity: 0.5, at: 575000);
expect(renderEditable, paintsExactlyCountTimes(#drawRRect, 0)); await verifyKeyFrame(opacity: 0.25, at: 612500);
await verifyKeyFrame(opacity: 0.0, at: 650000);
await verifyKeyFrame(opacity: 0.0, at: 850000);
await verifyKeyFrame(opacity: 0.25, at: 887500);
await verifyKeyFrame(opacity: 0.5, at: 925000);
await verifyKeyFrame(opacity: 0.75, at: 962500);
await verifyKeyFrame(opacity: 1.0, at: 1000000);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('Cursor does not animate on non-iOS platforms', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(child: TextField(maxLines: 3)),
),
);
// Wait some more while the cursor is gone. It'll trigger the cursor to await tester.tap(find.byType(TextField));
// start animating in again. await tester.pump();
await tester.pump(const Duration(milliseconds: 300)); // Wait for the current animation to finish. If the cursor never stops its
expect(renderEditable.cursorColor!.alpha, 0); // blinking animation the test will timeout.
expect(renderEditable, paintsExactlyCountTimes(#drawRRect, 0)); await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 50)); for (int i = 0; i < 40; i += 1) {
// Cursor starts coming back. await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable.cursorColor!.alpha, 79); expect(tester.hasRunningAnimations, false);
expect(renderEditable, paints..rrect(color: const Color(0x4f2196f3))); }
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('Cursor does not animate on Android', (WidgetTester tester) async { testWidgets('Cursor does not animate on Android', (WidgetTester tester) async {
final Color defaultCursorColor = Color(ThemeData.fallback().colorScheme.primary.value); final Color defaultCursorColor = Color(ThemeData.fallback().colorScheme.primary.value);
...@@ -444,6 +458,37 @@ void main() { ...@@ -444,6 +458,37 @@ void main() {
expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0));
}); });
testWidgets('Cursor does not show when not focused', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/106512 .
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: TextField(focusNode: focusNode, autofocus: true),
),
),
);
assert(focusNode.hasFocus);
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
final RenderEditable renderEditable = editableTextState.renderEditable;
focusNode.unfocus();
await tester.pump();
for (int i = 0; i < 10; i += 10) {
// Make sure it does not paint for a period of time.
expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0));
expect(tester.hasRunningAnimations, isFalse);
await tester.pump(const Duration(milliseconds: 29));
}
// Refocus and it should paint the caret.
focusNode.requestFocus();
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(renderEditable, isNot(paintsExactlyCountTimes(#drawRect, 0)));
});
testWidgets('Cursor radius is 2.0', (WidgetTester tester) async { testWidgets('Cursor radius is 2.0', (WidgetTester tester) async {
const Widget widget = MaterialApp( const Widget widget = MaterialApp(
home: Material( home: Material(
...@@ -956,4 +1001,40 @@ void main() { ...@@ -956,4 +1001,40 @@ void main() {
); );
EditableText.debugDeterministicCursor = false; EditableText.debugDeterministicCursor = false;
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('password briefly does not show last character when disabled by system', (WidgetTester tester) async {
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
EditableText.debugDeterministicCursor = false;
addTearDown(() {
EditableText.debugDeterministicCursor = debugDeterministicCursor;
});
await tester.pumpWidget(MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
obscureText: true,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
));
await tester.enterText(find.byType(EditableText), 'AA');
await tester.pump();
await tester.enterText(find.byType(EditableText), 'AAA');
await tester.pump();
tester.binding.platformDispatcher.brieflyShowPasswordTestValue = false;
addTearDown(() {
tester.binding.platformDispatcher.brieflyShowPasswordTestValue = true;
});
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
await tester.pump(const Duration(milliseconds: 500));
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
await tester.pump(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500));
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
});
} }
...@@ -3943,42 +3943,6 @@ void main() { ...@@ -3943,42 +3943,6 @@ void main() {
expect((findRenderEditable(tester).text! as TextSpan).text, '•••'); expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
}); });
testWidgets('password briefly does not show last character on Android if turned off', (WidgetTester tester) async {
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
EditableText.debugDeterministicCursor = false;
addTearDown(() {
EditableText.debugDeterministicCursor = debugDeterministicCursor;
});
await tester.pumpWidget(MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
obscureText: true,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
));
await tester.enterText(find.byType(EditableText), 'AA');
await tester.pump();
await tester.enterText(find.byType(EditableText), 'AAA');
await tester.pump();
tester.binding.platformDispatcher.brieflyShowPasswordTestValue = false;
addTearDown(() {
tester.binding.platformDispatcher.brieflyShowPasswordTestValue = true;
});
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
await tester.pump(const Duration(milliseconds: 500));
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
await tester.pump(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500));
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
});
group('a11y copy/cut/paste', () { group('a11y copy/cut/paste', () {
Future<void> buildApp(MockTextSelectionControls controls, WidgetTester tester) { Future<void> buildApp(MockTextSelectionControls controls, WidgetTester tester) {
return tester.pumpWidget(MaterialApp( return tester.pumpWidget(MaterialApp(
......
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