chat search now lists local and server-side hits like the file search, and jumping to old messages pages in history with a progress banner and keeps the hit marked
This commit is contained in:
1 parent
fcfd21bb4d
commit
b9cb6a8af1
13 files changed
+926
-322
No files matched your search
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../../api/demo/demo_mode.dart';
|
||||
import '../../../../api/marianumcloud/search/search_chat_messages.dart';
|
||||
import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
|
||||
import '../../../../utils/debouncer.dart';
|
||||
import '../data/chat_message_matcher.dart';
|
||||
|
||||
/// Live state of a search inside one chat, mirroring `FilesSearchController`:
|
||||
/// hits from the loaded history appear immediately, the server-side search
|
||||
/// over the full history follows debounced and can be paged further.
|
||||
class ChatSearchController extends ChangeNotifier {
|
||||
ChatSearchController({
|
||||
required this.chatToken,
|
||||
required GetChatResponse? Function() loadedMessages,
|
||||
}) : _loadedMessages = loadedMessages;
|
||||
|
||||
final String chatToken;
|
||||
final GetChatResponse? Function() _loadedMessages;
|
||||
|
||||
static const Duration _serverDebounce = Duration(seconds: 1);
|
||||
final String _debounceTag =
|
||||
'chat-search-${DateTime.now().microsecondsSinceEpoch}';
|
||||
final SearchChatMessages _api = SearchChatMessages();
|
||||
|
||||
String _query = '';
|
||||
List<ChatSearchHit> _localResults = const [];
|
||||
List<ChatSearchHit> _serverResults = const [];
|
||||
Object? _serverCursor;
|
||||
bool _serverLoading = false;
|
||||
bool _loadingMore = false;
|
||||
Object? _serverError;
|
||||
int _serverEpoch = 0;
|
||||
bool _disposed = false;
|
||||
|
||||
void _safeNotify() {
|
||||
if (_disposed) return;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String get query => _query;
|
||||
bool get serverLoading => _serverLoading;
|
||||
bool get loadingMore => _loadingMore;
|
||||
bool get canLoadMore => _serverCursor != null;
|
||||
Object? get serverError => _serverError;
|
||||
|
||||
List<ChatSearchHit> get combinedResults =>
|
||||
ChatMessageMatcher.merge(_localResults, _serverResults);
|
||||
|
||||
void setQuery(String value) {
|
||||
if (value == _query) return;
|
||||
_query = value;
|
||||
// Invalidates in-flight server calls of the previous query.
|
||||
++_serverEpoch;
|
||||
_serverResults = const [];
|
||||
_serverCursor = null;
|
||||
_loadingMore = false;
|
||||
_serverError = null;
|
||||
if (_query.trim().isEmpty) {
|
||||
Debouncer.cancel(_debounceTag);
|
||||
_localResults = const [];
|
||||
_serverLoading = false;
|
||||
_safeNotify();
|
||||
return;
|
||||
}
|
||||
final loaded = _loadedMessages();
|
||||
_localResults = loaded == null
|
||||
? const []
|
||||
: ChatMessageMatcher.findMatches(loaded, _query);
|
||||
// Demo sessions must not hit the real Nextcloud (see DemoHttpClient).
|
||||
_serverLoading = !DemoMode.active;
|
||||
_safeNotify();
|
||||
if (_serverLoading) {
|
||||
Debouncer.debounce(_debounceTag, _serverDebounce, _runServerCall);
|
||||
}
|
||||
}
|
||||
|
||||
void retry() {
|
||||
if (_query.trim().isEmpty || DemoMode.active) return;
|
||||
++_serverEpoch;
|
||||
Debouncer.cancel(_debounceTag);
|
||||
_serverResults = const [];
|
||||
_serverCursor = null;
|
||||
_loadingMore = false;
|
||||
_serverLoading = true;
|
||||
_serverError = null;
|
||||
_safeNotify();
|
||||
_runServerCall();
|
||||
}
|
||||
|
||||
void loadMore() {
|
||||
if (_serverCursor == null || _serverLoading || _loadingMore) return;
|
||||
_loadingMore = true;
|
||||
_safeNotify();
|
||||
_runServerCall(cursor: _serverCursor);
|
||||
}
|
||||
|
||||
Future<void> _runServerCall({Object? cursor}) async {
|
||||
final epoch = _serverEpoch;
|
||||
try {
|
||||
final response = await _api.run(
|
||||
chatToken: chatToken,
|
||||
term: _query.trim(),
|
||||
cursor: cursor,
|
||||
);
|
||||
if (epoch != _serverEpoch) return;
|
||||
_serverResults = [
|
||||
..._serverResults,
|
||||
...response.entries.map(ChatSearchHit.fromServer),
|
||||
];
|
||||
_serverCursor = response.cursor;
|
||||
_serverError = null;
|
||||
} on Object catch (e) {
|
||||
if (epoch != _serverEpoch) return;
|
||||
_serverCursor = null;
|
||||
_serverError = e;
|
||||
}
|
||||
_serverLoading = false;
|
||||
_loadingMore = false;
|
||||
_safeNotify();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
Debouncer.cancel(_debounceTag);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user