Разделение навигации и реактивности
This commit is contained in:
@@ -16,6 +16,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
on<ChatEventChatsLoaded>(_onChatsLoaded);
|
||||
on<ChatEventChatSelected>(_onChatSelected);
|
||||
on<ChatEventMessagesRequested>(_onMessagesRequested);
|
||||
on<ChatEventMessageReceived>(_onMessageReceived);
|
||||
on<ChatEventMessageSent>(_onMessageSent);
|
||||
on<ChatEventFavoritesRequested>(_onFavoritesRequested);
|
||||
on<ChatEventTypingUpdated>(_onTypingUpdated);
|
||||
@@ -23,7 +24,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
on<ChatEventCacheCleared>(_onCacheCleared);
|
||||
|
||||
_messageSubscription = chatRepository.messageStream.listen((message) {
|
||||
add(ChatEvent.messagesRequested(message.chatId));
|
||||
add(ChatEvent.messageReceived(message));
|
||||
});
|
||||
|
||||
_typingSubscription = chatRepository.typingStream.listen((data) {
|
||||
@@ -40,7 +41,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
});
|
||||
// Immediately trigger chats loading
|
||||
add(ChatEvent.chatsLoaded(currentUserId: event.userId));
|
||||
add(const ChatEvent.favoritesRequested());
|
||||
}
|
||||
|
||||
Future<void> _onChatsLoaded(ChatEventChatsLoaded event, Emitter<ChatState> emit) async {
|
||||
@@ -53,42 +53,114 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
}
|
||||
|
||||
Future<void> _onChatSelected(ChatEventChatSelected event, Emitter<ChatState> emit) async {
|
||||
emit(const ChatState.loading());
|
||||
final currentChatId = state.maybeMap(
|
||||
chatSelected: (s) => s.chat.id,
|
||||
messagesLoaded: (s) => s.chat.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (currentChatId == event.chatId) {
|
||||
// Already in this chat, just refresh messages
|
||||
final messagesResult = await chatRepository.getMessages(event.chatId);
|
||||
messagesResult.fold(
|
||||
(error) => null,
|
||||
(messages) {
|
||||
final currentChat = state.maybeMap(
|
||||
chatSelected: (s) => s.chat,
|
||||
messagesLoaded: (s) => s.chat,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (currentChat != null) {
|
||||
emit(ChatState.messagesLoaded(currentChat, messages, typingUsers: state.typingUsers));
|
||||
}
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(ChatState.loading(typingUsers: state.typingUsers));
|
||||
final chatResult = await chatRepository.getChatById(event.chatId);
|
||||
final messagesResult = await chatRepository.getMessages(event.chatId);
|
||||
|
||||
chatResult.fold(
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to load chat')),
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to load chat', typingUsers: state.typingUsers)),
|
||||
(chat) {
|
||||
messagesResult.fold(
|
||||
(error) => emit(ChatState.chatSelected(chat, [])),
|
||||
(messages) => emit(ChatState.chatSelected(chat, messages)),
|
||||
(error) => emit(ChatState.chatSelected(chat, [], typingUsers: state.typingUsers)),
|
||||
(messages) => emit(ChatState.chatSelected(chat, messages, typingUsers: state.typingUsers)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMessagesRequested(ChatEventMessagesRequested event, Emitter<ChatState> emit) async {
|
||||
final currentChat = state.maybeMap(
|
||||
chatSelected: (s) => s.chat,
|
||||
messagesLoaded: (s) => s.chat,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
// Only fetch if it's the current chat or we are in chats list
|
||||
final result = await chatRepository.getMessages(event.chatId);
|
||||
result.fold(
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to load messages')),
|
||||
(messages) => emit(ChatState.messagesLoaded(messages)),
|
||||
(error) => null, // Don't emit error state for background updates to avoid UI flickering
|
||||
(messages) {
|
||||
if (currentChat != null && currentChat.id == event.chatId) {
|
||||
// If we are in the chat, update messages and switch to messagesLoaded (stable state)
|
||||
emit(ChatState.messagesLoaded(currentChat, messages, typingUsers: state.typingUsers));
|
||||
}
|
||||
// If we are in the list, the list will update via local cache anyway or on next fetch
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMessageReceived(ChatEventMessageReceived event, Emitter<ChatState> emit) async {
|
||||
final message = event.message;
|
||||
final currentChat = state.maybeMap(
|
||||
chatSelected: (s) => s.chat,
|
||||
messagesLoaded: (s) => s.chat,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (currentChat != null && currentChat.id == message.chatId) {
|
||||
final List<Message> currentMessages = state.maybeMap(
|
||||
chatSelected: (s) => s.messages,
|
||||
messagesLoaded: (s) => s.messages,
|
||||
orElse: () => [],
|
||||
);
|
||||
|
||||
// Check for duplicates
|
||||
if (currentMessages.any((m) => m.id == message.id)) return;
|
||||
|
||||
final updatedMessages = List<Message>.from(currentMessages)..insert(0, message);
|
||||
// Ensure sorting if needed (usually messages are already in order from stream)
|
||||
|
||||
emit(ChatState.messagesLoaded(currentChat, updatedMessages, typingUsers: state.typingUsers));
|
||||
}
|
||||
|
||||
// Also trigger chats refresh if we are in chatsLoaded state to update last message/unread
|
||||
state.maybeMap(
|
||||
chatsLoaded: (_) => add(ChatEvent.chatsLoaded(currentUserId: currentChat?.participants?.firstWhere((p) => p.id != '').id)), // userId logic might need to be better
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMessageSent(ChatEventMessageSent event, Emitter<ChatState> emit) async {
|
||||
final result = await chatRepository.sendMessage(event.chatId, event.content);
|
||||
result.fold(
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to send message')),
|
||||
(_) => add(ChatEvent.messagesRequested(event.chatId)),
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to send message', typingUsers: state.typingUsers)),
|
||||
(_) {
|
||||
// Optimistically or via refresh
|
||||
add(ChatEvent.messagesRequested(event.chatId));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFavoritesRequested(ChatEventFavoritesRequested event, Emitter<ChatState> emit) async {
|
||||
emit(const ChatState.loading());
|
||||
emit(ChatState.loading(typingUsers: state.typingUsers));
|
||||
final result = await chatRepository.getOrCreateFavorites();
|
||||
result.fold(
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to open Favorites')),
|
||||
(error) => emit(ChatState.error(error.message ?? 'Failed to open Favorites', typingUsers: state.typingUsers)),
|
||||
(chat) => add(ChatEvent.chatSelected(chat.id)),
|
||||
);
|
||||
}
|
||||
@@ -109,7 +181,19 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
newTypingUsers[event.chatId] = chatTypingUsers;
|
||||
}
|
||||
|
||||
emit(state.copyWith(typingUsers: newTypingUsers));
|
||||
state.maybeMap(
|
||||
chatSelected: (s) {
|
||||
// If it's the current chat, transition to stable state
|
||||
if (s.chat.id == event.chatId) {
|
||||
emit(ChatState.messagesLoaded(s.chat, s.messages, typingUsers: newTypingUsers));
|
||||
} else {
|
||||
emit(s.copyWith(typingUsers: newTypingUsers));
|
||||
}
|
||||
},
|
||||
messagesLoaded: (s) => emit(s.copyWith(typingUsers: newTypingUsers)),
|
||||
chatsLoaded: (s) => emit(s.copyWith(typingUsers: newTypingUsers)),
|
||||
orElse: () => emit(state.copyWith(typingUsers: newTypingUsers)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSendTypingStatus(ChatEventSendTypingStatus event, Emitter<ChatState> emit) async {
|
||||
|
||||
Reference in New Issue
Block a user