Unverified Commit 60f30e5d authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Disable cursor opacity animation on macOS, make iOS cursor animation discrete (#104335)

parent 18575321
...@@ -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(
......
...@@ -609,42 +609,6 @@ void main() { ...@@ -609,42 +609,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');
...@@ -1328,7 +1292,7 @@ void main() { ...@@ -1328,7 +1292,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(
......
...@@ -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(TargetPlatform.values.toSet()..remove(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);
...@@ -956,4 +970,40 @@ void main() { ...@@ -956,4 +970,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