class DictationTextAccumulator { String _baseText = ''; String _recognizedText = ''; String get text => [_baseText, _recognizedText].where((part) => part.isNotEmpty).join(' '); void begin(String existingText) { _baseText = existingText.trimRight(); _recognizedText = ''; } String addResult(String words) { final nextText = words.trim().replaceAll(RegExp(r'\s+'), ' '); if (nextText.isEmpty) return text; _recognizedText = _mergeWithoutDeleting(_recognizedText, nextText); return text; } String _mergeWithoutDeleting(String previousText, String nextText) { if (previousText.isEmpty) return nextText; final previousWords = _words(previousText); final nextWords = _words(nextText); if (_startsWith(nextWords, previousWords)) return nextText; if (_startsWith(previousWords, nextWords)) return previousText; final commonPrefixLength = _commonPrefixLength(previousWords, nextWords); if (commonPrefixLength > 0) { if (nextWords.length > previousWords.length) return nextText; if (nextWords.length < previousWords.length) return previousText; return nextText.length >= previousText.length ? nextText : previousText; } final overlapLength = _suffixPrefixOverlap(previousWords, nextWords); if (overlapLength > 0) { final originalNextWords = nextText.split(' '); final remainingWords = originalNextWords.skip(overlapLength).join(' '); return remainingWords.isEmpty ? previousText : '$previousText $remainingWords'; } return '$previousText $nextText'; } List _words(String value) { return value .toLowerCase() .replaceAll(RegExp(r'[.,;:!?¿¡]'), '') .split(RegExp(r'\s+')) .where((word) => word.isNotEmpty) .toList(); } bool _startsWith(List words, List prefix) { if (prefix.length > words.length) return false; for (var index = 0; index < prefix.length; index++) { if (words[index] != prefix[index]) return false; } return true; } int _commonPrefixLength(List first, List second) { final limit = first.length < second.length ? first.length : second.length; var commonWords = 0; while (commonWords < limit && first[commonWords] == second[commonWords]) { commonWords++; } return commonWords; } int _suffixPrefixOverlap(List previous, List next) { final limit = previous.length < next.length ? previous.length : next.length; for (var overlap = limit; overlap > 0; overlap--) { var matches = true; for (var index = 0; index < overlap; index++) { if (previous[previous.length - overlap + index] != next[index]) { matches = false; break; } } if (matches) return overlap; } return 0; } }