Skip to content

Commit 8abdcc8

Browse files
[Super Editor][Super Text Layout] - Fix lost ticker in BlinkController (Resolves #3050) (#3051)
1 parent 58ae88b commit 8abdcc8

3 files changed

Lines changed: 73 additions & 26 deletions

File tree

super_editor/lib/src/default_editor/document_caret_overlay.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ class CaretDocumentOverlayState extends DocumentLayoutLayerState<CaretDocumentOv
8181
void didUpdateWidget(CaretDocumentOverlay oldWidget) {
8282
super.didUpdateWidget(oldWidget);
8383

84+
if (widget.blinkTimingMode != oldWidget.blinkTimingMode) {
85+
_blinkController.dispose();
86+
87+
switch (widget.blinkTimingMode) {
88+
case BlinkTimingMode.ticker:
89+
_blinkController = BlinkController(tickerProvider: this);
90+
case BlinkTimingMode.timer:
91+
_blinkController = BlinkController.withTimer();
92+
}
93+
}
94+
8495
if (widget.composer != oldWidget.composer) {
8596
oldWidget.composer.selectionNotifier.removeListener(_onSelectionChange);
8697
widget.composer.selectionNotifier.addListener(_onSelectionChange);

super_text_layout/lib/src/infrastructure/blink_controller.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@ class BlinkController with ChangeNotifier {
3737
Ticker? _ticker;
3838
Duration _lastBlinkTime = Duration.zero;
3939

40+
@visibleForTesting
41+
bool get isTicking => _ticker?.isTicking ?? false;
42+
4043
Timer? _timer;
4144

45+
@visibleForTesting
46+
bool get isTimerRunning => _timer?.isActive ?? false;
47+
4248
final Duration _flashPeriod;
4349

4450
/// Duration to switch between visible and invisible.
@@ -93,7 +99,6 @@ class BlinkController with ChangeNotifier {
9399
if (_ticker != null) {
94100
// We're using a Ticker to blink. Stop it.
95101
_ticker?.stop();
96-
_ticker = null;
97102
} else {
98103
// We're using a Timer to blink. Stop it.
99104
_timer?.cancel();

super_text_layout/test/blink_controller_test.dart

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ void main() {
77
bool hasNotifiedItsListener = false;
88

99
final blinkController = BlinkController(tickerProvider: tester);
10-
blinkController.addListener(() {
10+
blinkController.addListener(() {
1111
hasNotifiedItsListener = true;
12-
});
13-
12+
});
13+
1414
blinkController.stopBlinking();
15-
15+
1616
// Ensure that the callback was called
1717
expect(hasNotifiedItsListener, true);
1818
});
@@ -21,23 +21,21 @@ void main() {
2121
// Configure BlinkController to animate, otherwise it won't blink
2222
BlinkController.indeterminateAnimationsEnabled = true;
2323

24-
bool hasNotifiedItsListeners = false;
25-
26-
final blinkController = BlinkController(
27-
tickerProvider: tester
28-
);
24+
bool hasNotifiedItsListeners = false;
25+
26+
final blinkController = BlinkController(tickerProvider: tester);
2927
blinkController.stopBlinking();
3028

31-
blinkController.addListener(() {
29+
blinkController.addListener(() {
3230
hasNotifiedItsListeners = true;
33-
});
34-
31+
});
32+
3533
blinkController.startBlinking();
3634

3735
// Ensure that the callback was called
3836
expect(hasNotifiedItsListeners, true);
3937

40-
// Release the ticker
38+
// Release the ticker
4139
blinkController.stopBlinking();
4240
BlinkController.indeterminateAnimationsEnabled = false;
4341
});
@@ -49,32 +47,65 @@ void main() {
4947
const flashPeriod = Duration(milliseconds: 500);
5048

5149
int notificationCount = 0;
52-
53-
final blinkController = BlinkController(
54-
tickerProvider: tester,
55-
flashPeriod: flashPeriod
56-
);
57-
blinkController.addListener(() {
50+
51+
final blinkController = BlinkController(tickerProvider: tester, flashPeriod: flashPeriod);
52+
blinkController.addListener(() {
5853
notificationCount++;
59-
});
60-
54+
});
55+
6156
blinkController.startBlinking();
6257

63-
// Ensure that the callback was called before the first blink
58+
// Ensure that the callback was called before the first blink
6459
expect(notificationCount, 1);
65-
60+
6661
// Trigger the first frame, otherwise we get a zero elapsedTime in the _onTick method
6762
await tester.pump();
6863
// Trigger a frame with an ellapsed time greater than the flashPeriod,
6964
// so the controller should change its visible state and notify its listeners
7065
await tester.pump(flashPeriod + const Duration(milliseconds: 1));
71-
66+
7267
// Ensure that the callback was called a second time
7368
expect(notificationCount, 2);
7469

75-
// Release the ticker
70+
// Release the ticker
7671
blinkController.stopBlinking();
7772
BlinkController.indeterminateAnimationsEnabled = false;
7873
});
74+
75+
testWidgets("can stop and then restart ticker", (tester) async {
76+
// Configure BlinkController to animate, otherwise it won't blink
77+
BlinkController.indeterminateAnimationsEnabled = true;
78+
79+
int notificationCount = 0;
80+
81+
final blinkController = BlinkController(tickerProvider: tester);
82+
blinkController.addListener(() {
83+
notificationCount += 1;
84+
});
85+
86+
blinkController.startBlinking();
87+
expect(notificationCount, 1);
88+
89+
// Ensure the ticker is ticking.
90+
expect(blinkController.isTicking, isTrue);
91+
92+
// Stop the blinking, which should stop the ticker, but should retain the ticker.
93+
blinkController.stopBlinking();
94+
expect(notificationCount, 2);
95+
96+
// Ensure that the ticker is no longer ticking.
97+
expect(blinkController.isTicking, isFalse);
98+
expect(blinkController.isTimerRunning, isFalse);
99+
100+
blinkController.startBlinking();
101+
expect(notificationCount, 3);
102+
103+
// Ensure that the ticker is running again, and that we're not using a timer.
104+
expect(blinkController.isTicking, isTrue);
105+
expect(blinkController.isTimerRunning, isFalse);
106+
107+
// Dispose the controller so that we don't leak the ticker and cause a test error.
108+
blinkController.dispose();
109+
});
79110
});
80111
}

0 commit comments

Comments
 (0)