From 26d9197548e5082721500439bfb31796eff5fbb2 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Mon, 16 Mar 2026 17:49:25 +0530 Subject: [PATCH] feat: Implement socket-first message sending with REST fallback, enhance chat room re-joining on socket reconnect, and update 'Categories' to 'Specialization' in the hero section. --- .../presentation/widgets/hero_section.dart | 12 +++++------ .../providers/messaging_provider.dart | 17 ++++++++++++++-- .../presentation/screens/chat_screen.dart | 20 +++++++++++-------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/features/home/presentation/widgets/hero_section.dart b/lib/features/home/presentation/widgets/hero_section.dart index f955206..e9ca9b0 100644 --- a/lib/features/home/presentation/widgets/hero_section.dart +++ b/lib/features/home/presentation/widgets/hero_section.dart @@ -31,9 +31,9 @@ class _HeroSectionState extends ConsumerState { String? _selectedLocation; String _selectedLocationName = 'Location'; String? _selectedCategory; - String _selectedCategoryName = 'Categories'; + String _selectedCategoryName = 'Specialization'; - /// Cached filter fields fetched on-demand for Location/Categories pickers. + /// Cached filter fields fetched on-demand for Location/Specialization pickers. List? _cachedFilterFields; @override @@ -371,22 +371,22 @@ class _HeroSectionState extends ConsumerState { ), ), const SizedBox(height: 18), - // Categories — opens picker + // Specialization — opens picker GestureDetector( onTap: () => _showFilterPicker( - title: 'Select Category', + title: 'Select Specialization', fieldSlug: 'specialization', currentValue: _selectedCategory, onSelect: (value, label) { setState(() { _selectedCategory = value; - _selectedCategoryName = label ?? 'Categories'; + _selectedCategoryName = label ?? 'Specialization'; }); }, onClear: () { setState(() { _selectedCategory = null; - _selectedCategoryName = 'Categories'; + _selectedCategoryName = 'Specialization'; }); }, ), diff --git a/lib/features/messaging/presentation/providers/messaging_provider.dart b/lib/features/messaging/presentation/providers/messaging_provider.dart index d884e25..b7b0f19 100644 --- a/lib/features/messaging/presentation/providers/messaging_provider.dart +++ b/lib/features/messaging/presentation/providers/messaging_provider.dart @@ -291,7 +291,20 @@ class MessagingNotifier extends StateNotifier { ); try { - final realMessage = await _repository.sendMessage( + // Send via socket so the backend broadcasts new_message to the other user. + // The REST endpoint does NOT emit socket events, so using REST here + // would prevent real-time delivery to the other participant. + ChatMessage? realMessage; + + if (_socket.isConnected) { + realMessage = await _socket.sendMessage(conversationId, { + 'content': content, + 'messageType': 'TEXT', + }); + } + + // Fallback to REST if socket send failed or not connected + realMessage ??= await _repository.sendMessage( conversationId, content: content, ); @@ -311,7 +324,7 @@ class MessagingNotifier extends StateNotifier { final updatedConversations = state.conversations.map((c) { if (c.id == conversationId) { return c.copyWith( - lastMessageText: realMessage.content, + lastMessageText: realMessage!.content, lastMessageAt: realMessage.createdAt, ); } diff --git a/lib/features/messaging/presentation/screens/chat_screen.dart b/lib/features/messaging/presentation/screens/chat_screen.dart index f582478..1d43f28 100644 --- a/lib/features/messaging/presentation/screens/chat_screen.dart +++ b/lib/features/messaging/presentation/screens/chat_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -28,6 +30,7 @@ class _ChatScreenState extends ConsumerState { int _previousMessageCount = 0; final SocketService _socket = SocketService(); + StreamSubscription? _connectionSub; @override void initState() { @@ -37,17 +40,17 @@ class _ChatScreenState extends ConsumerState { notifier.setActiveConversation(widget.conversationId); notifier.loadMessages(widget.conversationId); notifier.markAsRead(widget.conversationId); - // Join socket room for real-time updates (socket is connected at auth time) + // Join socket room for real-time updates if (_socket.isConnected) { _socket.joinConversation(widget.conversationId); - } else { - // Wait for socket connection then join - _socket.onConnectionChange.first.then((connected) { - if (connected && mounted) { - _socket.joinConversation(widget.conversationId); - } - }); } + // Listen for ALL connection changes (initial connect + reconnects) + // so we re-join the room after any reconnection + _connectionSub = _socket.onConnectionChange.listen((connected) { + if (connected && mounted) { + _socket.joinConversation(widget.conversationId); + } + }); }); _scrollController.addListener(_onScroll); _messageController.addListener(_onTextChanged); @@ -55,6 +58,7 @@ class _ChatScreenState extends ConsumerState { @override void dispose() { + _connectionSub?.cancel(); // Stop typing and leave room before disposing if (_isComposing) { _socket.stopTyping(widget.conversationId);