mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
feat: update ChatRepositoryInterface with methods to manage pagination of messages in a chat
This commit is contained in:
parent
77d6f7257e
commit
d475cf7298
7 changed files with 107 additions and 65 deletions
|
@ -11,6 +11,7 @@
|
|||
- Added FlutterChatDetailNavigatorUserstory that can be used to start the userstory from the chat detail screen without having the chat overview screen
|
||||
- Changed the ChatDetailScreen to use the chatId instead of the ChatModel, the screen will now fetch the chat from the ChatService
|
||||
- Changed baseScreenBuilder to include a chatTitle that can be used to show provide the title logic to apps that use the baseScreenBuilder
|
||||
- Added loadNewMessagesAfter, loadOldMessagesBefore and removed pagination from getMessages in the ChatRepositoryInterface to change pagination behavior to rely on the stream and two methods indicating that more messages should be added to the stream
|
||||
|
||||
## 4.0.0
|
||||
- Move to the new user story architecture
|
||||
|
|
|
@ -43,16 +43,12 @@ abstract class ChatRepositoryInterface {
|
|||
|
||||
/// Get the messages for the given [chatId].
|
||||
/// Returns a list of [MessageModel] stream.
|
||||
/// [pageSize] is the number of messages to be fetched.
|
||||
/// [page] is the page number.
|
||||
/// [userId] is the user id.
|
||||
/// [chatId] is the chat id.
|
||||
/// Returns a list of [MessageModel] stream.
|
||||
Stream<List<MessageModel>?> getMessages({
|
||||
required String chatId,
|
||||
required String userId,
|
||||
required int pageSize,
|
||||
required int page,
|
||||
});
|
||||
|
||||
/// Get the message with the given [messageId].
|
||||
|
@ -63,6 +59,20 @@ abstract class ChatRepositoryInterface {
|
|||
required String messageId,
|
||||
});
|
||||
|
||||
/// Signals that new messages should be loaded after the given message.
|
||||
/// The stream should emit the new messages.
|
||||
Future<void> loadNewMessagesAfter({
|
||||
required String userId,
|
||||
required MessageModel lastMessage,
|
||||
});
|
||||
|
||||
/// Signals that old messages should be loaded before the given message.
|
||||
/// The stream should emit the new messages.
|
||||
Future<void> loadOldMessagesBefore({
|
||||
required String userId,
|
||||
required MessageModel firstMessage,
|
||||
});
|
||||
|
||||
/// Send a message with the given parameters.
|
||||
/// [chatId] is the chat id.
|
||||
/// [senderId] is the sender id.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import "dart:async";
|
||||
import "dart:math" as math;
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
|
@ -20,6 +21,10 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
|||
final StreamController<List<MessageModel>> _messageController =
|
||||
BehaviorSubject<List<MessageModel>>();
|
||||
|
||||
final Map<String, int> _startIndexMap = {};
|
||||
final Map<String, int> _endIndexMap = {};
|
||||
static const int _chunkSize = 30;
|
||||
|
||||
@override
|
||||
Future<void> createChat({
|
||||
required List<String> users,
|
||||
|
@ -110,60 +115,88 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
|||
Stream<List<MessageModel>?> getMessages({
|
||||
required String chatId,
|
||||
required String userId,
|
||||
required int pageSize,
|
||||
required int page,
|
||||
}) {
|
||||
ChatModel? chat;
|
||||
var foundChat =
|
||||
chats.firstWhereOrNull((chatModel) => chatModel.id == chatId);
|
||||
|
||||
chat = chats.firstWhereOrNull((e) => e.id == chatId);
|
||||
|
||||
if (chat != null) {
|
||||
var messages = List<MessageModel>.from(chatMessages[chatId] ?? []);
|
||||
|
||||
messages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
unawaited(
|
||||
_messageController.stream.first
|
||||
.timeout(
|
||||
const Duration(seconds: 1),
|
||||
)
|
||||
.then((oldMessages) {
|
||||
var newMessages = messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList();
|
||||
|
||||
if (newMessages.isEmpty) return;
|
||||
|
||||
var allMessages = [...oldMessages, ...newMessages];
|
||||
|
||||
allMessages = allMessages
|
||||
.toSet()
|
||||
.toList()
|
||||
.cast<MessageModel>()
|
||||
.toList(growable: false);
|
||||
|
||||
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
_messageController.add(allMessages);
|
||||
}).onError((error, stackTrace) {
|
||||
_messageController.add(
|
||||
messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
if (foundChat == null) {
|
||||
_messageController.add([]);
|
||||
} else {
|
||||
var allMessages = List<MessageModel>.from(
|
||||
chatMessages[chatId] ?? [],
|
||||
);
|
||||
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
_startIndexMap[chatId] ??= math.max(0, allMessages.length - _chunkSize);
|
||||
_endIndexMap[chatId] ??= allMessages.length;
|
||||
|
||||
var displayedMessages = allMessages.sublist(
|
||||
_startIndexMap[chatId]!,
|
||||
_endIndexMap[chatId],
|
||||
);
|
||||
_messageController.add(displayedMessages);
|
||||
}
|
||||
|
||||
return _messageController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadNewMessagesAfter({
|
||||
required String userId,
|
||||
required MessageModel lastMessage,
|
||||
}) async {
|
||||
var allMessages = List<MessageModel>.from(
|
||||
chatMessages[lastMessage.chatId] ?? [],
|
||||
)..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
var lastMessageIndex = allMessages
|
||||
.indexWhere((messageModel) => messageModel.id == lastMessage.id);
|
||||
if (lastMessageIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentEndIndex =
|
||||
_endIndexMap[lastMessage.chatId] ?? allMessages.length;
|
||||
_endIndexMap[lastMessage.chatId] = math.min(
|
||||
allMessages.length,
|
||||
currentEndIndex + _chunkSize,
|
||||
);
|
||||
|
||||
var displayedMessages = allMessages.sublist(
|
||||
_startIndexMap[lastMessage.chatId] ?? 0,
|
||||
_endIndexMap[lastMessage.chatId],
|
||||
);
|
||||
_messageController.add(displayedMessages);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadOldMessagesBefore({
|
||||
required String userId,
|
||||
required MessageModel firstMessage,
|
||||
}) async {
|
||||
var allMessages = List<MessageModel>.from(
|
||||
chatMessages[firstMessage.chatId] ?? [],
|
||||
)..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
var firstMessageIndex = allMessages
|
||||
.indexWhere((messageModel) => messageModel.id == firstMessage.id);
|
||||
if (firstMessageIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStartIndex = _startIndexMap[firstMessage.chatId] ?? 0;
|
||||
_startIndexMap[firstMessage.chatId] = math.max(
|
||||
0,
|
||||
currentStartIndex - _chunkSize,
|
||||
);
|
||||
|
||||
var displayedMessages = allMessages.sublist(
|
||||
_startIndexMap[firstMessage.chatId]!,
|
||||
_endIndexMap[firstMessage.chatId] ?? allMessages.length,
|
||||
);
|
||||
_messageController.add(displayedMessages);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<MessageModel?> getMessage({
|
||||
required String chatId,
|
||||
|
|
|
@ -135,14 +135,10 @@ class ChatService {
|
|||
/// Returns a list of [MessageModel] stream.
|
||||
Stream<List<MessageModel>?> getMessages({
|
||||
required String chatId,
|
||||
required int pageSize,
|
||||
required int page,
|
||||
}) =>
|
||||
chatRepository.getMessages(
|
||||
userId: userId,
|
||||
chatId: chatId,
|
||||
pageSize: pageSize,
|
||||
page: page,
|
||||
);
|
||||
|
||||
/// Send a message with the given parameters.
|
||||
|
|
|
@ -94,15 +94,12 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
|||
Stream<List<MessageModel>?> getMessages({
|
||||
required String chatId,
|
||||
required String userId,
|
||||
required int pageSize,
|
||||
required int page,
|
||||
}) =>
|
||||
_firestore
|
||||
.collection(_chatCollection)
|
||||
.doc(chatId)
|
||||
.collection(_messageCollection)
|
||||
.orderBy("timestamp")
|
||||
.limit(pageSize)
|
||||
.snapshots()
|
||||
.map(
|
||||
(query) => query.docs
|
||||
|
@ -199,4 +196,16 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
|||
var snapshot = await uploadTask.whenComplete(() => {});
|
||||
return snapshot.ref.getDownloadURL();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> loadNewMessagesAfter({
|
||||
required String userId,
|
||||
required MessageModel lastMessage,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> loadOldMessagesBefore({
|
||||
required String userId,
|
||||
required MessageModel firstMessage,
|
||||
}) async {}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ class ChatOptions {
|
|||
this.iconDisabledColor,
|
||||
this.chatAlignment,
|
||||
this.onNoChats,
|
||||
this.pageSize = 20,
|
||||
ChatRepositoryInterface? chatRepository,
|
||||
UserRepositoryInterface? userRepository,
|
||||
}) : chatRepository = chatRepository ?? LocalChatRepository(),
|
||||
|
@ -81,9 +80,6 @@ class ChatOptions {
|
|||
|
||||
/// [onNoChats] is a function that is triggered when there are no chats.
|
||||
final Function? onNoChats;
|
||||
|
||||
/// [pageSize] is the number of chats to load at a time.
|
||||
final int pageSize;
|
||||
}
|
||||
|
||||
/// Typedef for the chatTitleResolver function that is used to get a title for
|
||||
|
|
|
@ -226,7 +226,6 @@ class _Body extends HookWidget {
|
|||
var options = chatScope.options;
|
||||
var service = chatScope.service;
|
||||
|
||||
var pageSize = useState(chatScope.options.pageSize);
|
||||
var page = useState(0);
|
||||
var showIndicator = useState(false);
|
||||
var controller = useScrollController();
|
||||
|
@ -253,10 +252,8 @@ class _Body extends HookWidget {
|
|||
var messagesStream = useMemoized(
|
||||
() => service.getMessages(
|
||||
chatId: chat!.id,
|
||||
pageSize: pageSize.value,
|
||||
page: page.value,
|
||||
),
|
||||
[chat!.id, pageSize.value, page.value],
|
||||
[chat!.id, page.value],
|
||||
);
|
||||
var messagesSnapshot = useStream(messagesStream);
|
||||
var messages = messagesSnapshot.data?.reversed.toList() ?? [];
|
||||
|
|
Loading…
Reference in a new issue