feat: debounce on word count service (#711)
* feat: debounce on word count service * test: add pump for debounce in test
This commit is contained in:
parent
e09bde476b
commit
66762ef9a0
2 changed files with 62 additions and 15 deletions
|
|
@ -48,10 +48,26 @@ class Counters {
|
||||||
/// the counter stats.
|
/// the counter stats.
|
||||||
///
|
///
|
||||||
class WordCountService with ChangeNotifier {
|
class WordCountService with ChangeNotifier {
|
||||||
WordCountService({required this.editorState});
|
WordCountService({
|
||||||
|
required this.editorState,
|
||||||
|
this.debounceDuration = const Duration(milliseconds: 300),
|
||||||
|
});
|
||||||
|
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
|
|
||||||
|
/// The time to wait before input stops, to recalculate
|
||||||
|
/// word and character count.
|
||||||
|
///
|
||||||
|
/// This duration is used for debouncing both document
|
||||||
|
/// and selection changes.
|
||||||
|
///
|
||||||
|
/// If 0 no debouncing will occur
|
||||||
|
///
|
||||||
|
final Duration debounceDuration;
|
||||||
|
|
||||||
|
Timer? _selectionTimer;
|
||||||
|
Timer? _documentTimer;
|
||||||
|
|
||||||
/// Number of words and characters in the [Document].
|
/// Number of words and characters in the [Document].
|
||||||
///
|
///
|
||||||
Counters get documentCounters => _documentCounters;
|
Counters get documentCounters => _documentCounters;
|
||||||
|
|
@ -111,9 +127,8 @@ class WordCountService with ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
_streamSubscription =
|
_streamSubscription = editorState.transactionStream.listen(_onDocUpdate);
|
||||||
editorState.transactionStream.listen(_recountOnTransactionUpdate);
|
editorState.selectionNotifier.addListener(_onSelUpdate);
|
||||||
editorState.selectionNotifier.addListener(_recountOnSelectionUpdate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the Word Counter and resets the counts.
|
/// Stops the Word Counter and resets the counts.
|
||||||
|
|
@ -123,6 +138,10 @@ class WordCountService with ChangeNotifier {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_documentTimer?.cancel();
|
||||||
|
_documentTimer = null;
|
||||||
|
_selectionTimer?.cancel();
|
||||||
|
_selectionTimer = null;
|
||||||
_streamSubscription?.cancel();
|
_streamSubscription?.cancel();
|
||||||
_documentCounters = const Counters();
|
_documentCounters = const Counters();
|
||||||
_selectionCounters = const Counters();
|
_selectionCounters = const Counters();
|
||||||
|
|
@ -133,11 +152,27 @@ class WordCountService with ChangeNotifier {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
editorState.selectionNotifier.removeListener(_recountOnSelectionUpdate);
|
editorState.selectionNotifier.removeListener(_onSelUpdate);
|
||||||
_streamSubscription?.cancel();
|
_streamSubscription?.cancel();
|
||||||
|
_documentTimer?.cancel();
|
||||||
|
_selectionTimer?.cancel();
|
||||||
|
_documentTimer = null;
|
||||||
|
_selectionTimer = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSelUpdate() {
|
||||||
|
if (debounceDuration.inMilliseconds == 0) {
|
||||||
|
return _recountOnSelectionUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectionTimer?.isActive ?? false) {
|
||||||
|
_selectionTimer!.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectionTimer = Timer(debounceDuration, _recountOnSelectionUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
void _recountOnSelectionUpdate() {
|
void _recountOnSelectionUpdate() {
|
||||||
// If collapsed or null, reset count
|
// If collapsed or null, reset count
|
||||||
if (editorState.selection?.isCollapsed ?? true) {
|
if (editorState.selection?.isCollapsed ?? true) {
|
||||||
|
|
@ -146,7 +181,6 @@ class WordCountService with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectionCounters = const Counters();
|
_selectionCounters = const Counters();
|
||||||
|
|
||||||
return notifyListeners();
|
return notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,16 +203,26 @@ class WordCountService with ChangeNotifier {
|
||||||
charCount += counters.charCount;
|
charCount += counters.charCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Counters(
|
return Counters(wordCount: wordCount, charCount: charCount);
|
||||||
wordCount: wordCount,
|
}
|
||||||
charCount: charCount,
|
|
||||||
|
void _onDocUpdate((TransactionTime time, Transaction t) event) {
|
||||||
|
if (debounceDuration.inMilliseconds == 0) {
|
||||||
|
return _recountOnTransactionUpdate(event.$1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_documentTimer?.isActive ?? false) {
|
||||||
|
_documentTimer!.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_documentTimer = Timer(
|
||||||
|
debounceDuration,
|
||||||
|
() => _recountOnTransactionUpdate(event.$1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _recountOnTransactionUpdate(
|
void _recountOnTransactionUpdate(TransactionTime time) {
|
||||||
(TransactionTime time, Transaction t) event,
|
if (time != TransactionTime.after) {
|
||||||
) {
|
|
||||||
if (event.$1 != TransactionTime.after) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
import '../../new/infra/testable_editor.dart';
|
import '../../new/infra/testable_editor.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
|
@ -44,7 +45,7 @@ void main() async {
|
||||||
|
|
||||||
await editor.editorState.apply(transaction);
|
await editor.editorState.apply(transaction);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(service.documentCounters.wordCount, 3 * 4);
|
expect(service.documentCounters.wordCount, 3 * 4);
|
||||||
expect(service.documentCounters.charCount, text.length * 4);
|
expect(service.documentCounters.charCount, text.length * 4);
|
||||||
|
|
@ -53,6 +54,8 @@ void main() async {
|
||||||
|
|
||||||
service.stop();
|
service.stop();
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(service.documentCounters.wordCount, 0);
|
expect(service.documentCounters.wordCount, 0);
|
||||||
expect(service.documentCounters.charCount, 0);
|
expect(service.documentCounters.charCount, 0);
|
||||||
expect(wordCount, 0);
|
expect(wordCount, 0);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue