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.
|
||||
///
|
||||
class WordCountService with ChangeNotifier {
|
||||
WordCountService({required this.editorState});
|
||||
WordCountService({
|
||||
required this.editorState,
|
||||
this.debounceDuration = const Duration(milliseconds: 300),
|
||||
});
|
||||
|
||||
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].
|
||||
///
|
||||
Counters get documentCounters => _documentCounters;
|
||||
|
|
@ -111,9 +127,8 @@ class WordCountService with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
_streamSubscription =
|
||||
editorState.transactionStream.listen(_recountOnTransactionUpdate);
|
||||
editorState.selectionNotifier.addListener(_recountOnSelectionUpdate);
|
||||
_streamSubscription = editorState.transactionStream.listen(_onDocUpdate);
|
||||
editorState.selectionNotifier.addListener(_onSelUpdate);
|
||||
}
|
||||
|
||||
/// Stops the Word Counter and resets the counts.
|
||||
|
|
@ -123,6 +138,10 @@ class WordCountService with ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
_documentTimer?.cancel();
|
||||
_documentTimer = null;
|
||||
_selectionTimer?.cancel();
|
||||
_selectionTimer = null;
|
||||
_streamSubscription?.cancel();
|
||||
_documentCounters = const Counters();
|
||||
_selectionCounters = const Counters();
|
||||
|
|
@ -133,11 +152,27 @@ class WordCountService with ChangeNotifier {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
editorState.selectionNotifier.removeListener(_recountOnSelectionUpdate);
|
||||
editorState.selectionNotifier.removeListener(_onSelUpdate);
|
||||
_streamSubscription?.cancel();
|
||||
_documentTimer?.cancel();
|
||||
_selectionTimer?.cancel();
|
||||
_documentTimer = null;
|
||||
_selectionTimer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSelUpdate() {
|
||||
if (debounceDuration.inMilliseconds == 0) {
|
||||
return _recountOnSelectionUpdate();
|
||||
}
|
||||
|
||||
if (_selectionTimer?.isActive ?? false) {
|
||||
_selectionTimer!.cancel();
|
||||
}
|
||||
|
||||
_selectionTimer = Timer(debounceDuration, _recountOnSelectionUpdate);
|
||||
}
|
||||
|
||||
void _recountOnSelectionUpdate() {
|
||||
// If collapsed or null, reset count
|
||||
if (editorState.selection?.isCollapsed ?? true) {
|
||||
|
|
@ -146,7 +181,6 @@ class WordCountService with ChangeNotifier {
|
|||
}
|
||||
|
||||
_selectionCounters = const Counters();
|
||||
|
||||
return notifyListeners();
|
||||
}
|
||||
|
||||
|
|
@ -169,16 +203,26 @@ class WordCountService with ChangeNotifier {
|
|||
charCount += counters.charCount;
|
||||
}
|
||||
|
||||
return Counters(
|
||||
wordCount: wordCount,
|
||||
charCount: charCount,
|
||||
return Counters(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(
|
||||
(TransactionTime time, Transaction t) event,
|
||||
) {
|
||||
if (event.$1 != TransactionTime.after) {
|
||||
void _recountOnTransactionUpdate(TransactionTime time) {
|
||||
if (time != TransactionTime.after) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
import '../../new/infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
|
|
@ -44,7 +45,7 @@ void main() async {
|
|||
|
||||
await editor.editorState.apply(transaction);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||
|
||||
expect(service.documentCounters.wordCount, 3 * 4);
|
||||
expect(service.documentCounters.charCount, text.length * 4);
|
||||
|
|
@ -53,6 +54,8 @@ void main() async {
|
|||
|
||||
service.stop();
|
||||
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||
|
||||
expect(service.documentCounters.wordCount, 0);
|
||||
expect(service.documentCounters.charCount, 0);
|
||||
expect(wordCount, 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue