diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 607200d9c7..32defb1556 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2610,12 +2610,10 @@ class EditableTextState extends State with AutomaticKeepAliveClien final bool newTickerEnabled = TickerMode.of(context); if (_tickersEnabled != newTickerEnabled) { _tickersEnabled = newTickerEnabled; - if (_tickersEnabled && _cursorActive) { + if (_showBlinkingCursor) { _startCursorBlink(); } else if (!_tickersEnabled && _cursorTimer != null) { - // Cannot use _stopCursorBlink because it would reset _cursorActive. - _cursorTimer!.cancel(); - _cursorTimer = null; + _stopCursorBlink(); } } @@ -2707,6 +2705,10 @@ class EditableTextState extends State with AutomaticKeepAliveClien ); } } + + if (widget.showCursor != oldWidget.showCursor) { + _startOrStopCursorTimerIfNeeded(); + } final bool canPaste = widget.selectionControls is TextSelectionHandleControls ? pasteEnabled : widget.selectionControls?.canPaste(this) ?? false; @@ -3655,6 +3657,8 @@ class EditableTextState extends State with AutomaticKeepAliveClien _cursorVisibilityNotifier.value = widget.showCursor && _cursorBlinkOpacityController.value > 0; } + bool get _showBlinkingCursor => _hasFocus && _value.selection.isCollapsed && widget.showCursor && _tickersEnabled; + /// Whether the blinking cursor is actually visible at this precise moment /// (it's hidden half the time, since it blinks). @visibleForTesting @@ -3673,13 +3677,11 @@ class EditableTextState extends State with AutomaticKeepAliveClien int _obscureShowCharTicksPending = 0; int? _obscureLatestCharIndex; - // Indicates whether the cursor should be blinking right now (but it may - // actually not blink because it's disabled via TickerMode.of(context)). - bool _cursorActive = false; - void _startCursorBlink() { assert(!(_cursorTimer?.isActive ?? false) || !(_backingCursorBlinkOpacityController?.isAnimating ?? false)); - _cursorActive = true; + if (!widget.showCursor) { + return; + } if (!_tickersEnabled) { return; } @@ -3719,7 +3721,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien } void _stopCursorBlink({ bool resetCharTicks = true }) { - _cursorActive = false; _cursorBlinkOpacityController.value = 0.0; _cursorTimer?.cancel(); _cursorTimer = null; @@ -3729,11 +3730,10 @@ class EditableTextState extends State with AutomaticKeepAliveClien } void _startOrStopCursorTimerIfNeeded() { - if (_cursorTimer == null && _hasFocus && _value.selection.isCollapsed) { - _startCursorBlink(); - } - else if (_cursorActive && (!_hasFocus || !_value.selection.isCollapsed)) { + if (!_showBlinkingCursor) { _stopCursorBlink(); + } else if (_cursorTimer == null) { + _startCursorBlink(); } } diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index 7b79f183c4..3db3d48b0f 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -771,6 +771,54 @@ void main() { await checkCursorBlinking(); }); + testWidgets('Turning showCursor off stops the cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/108187. + final bool debugDeterministicCursor = EditableText.debugDeterministicCursor; + // This doesn't really matter. + EditableText.debugDeterministicCursor = false; + addTearDown(() { EditableText.debugDeterministicCursor = debugDeterministicCursor; }); + const Key key = Key('EditableText'); + + Widget buildEditableText({ required bool showCursor }) { + return MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: EditableText( + key: key, + backgroundCursorColor: Colors.grey, + // Use animation controller to animate cursor blink for testing. + cursorOpacityAnimates: true, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + showCursor: showCursor, + ), + ), + ); + } + late final EditableTextState editableTextState = tester.state(find.byKey(key)); + await tester.pumpWidget(buildEditableText(showCursor: false)); + await tester.tap(find.byKey(key)); + await tester.pump(); + + // No cursor even when focused. + expect(editableTextState.cursorCurrentlyVisible, false); + + // The EditableText still has focus, so the cursor should starts blinking. + await tester.pumpWidget(buildEditableText(showCursor: true)); + expect(editableTextState.cursorCurrentlyVisible, true); + await tester.pump(); + expect(editableTextState.cursorCurrentlyVisible, true); + + // readOnly disables blinking cursor. + await tester.pumpWidget(buildEditableText(showCursor: false)); + expect(editableTextState.cursorCurrentlyVisible, false); + await tester.pump(); + expect(editableTextState.cursorCurrentlyVisible, false); + }); + // Regression test for https://github.com/flutter/flutter/pull/30475. testWidgets('Trying to select with the floating cursor does not crash', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!';