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.
This commit is contained in:
@@ -31,9 +31,9 @@ class _HeroSectionState extends ConsumerState<HeroSection> {
|
|||||||
String? _selectedLocation;
|
String? _selectedLocation;
|
||||||
String _selectedLocationName = 'Location';
|
String _selectedLocationName = 'Location';
|
||||||
String? _selectedCategory;
|
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<FilterableField>? _cachedFilterFields;
|
List<FilterableField>? _cachedFilterFields;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -371,22 +371,22 @@ class _HeroSectionState extends ConsumerState<HeroSection> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
// Categories — opens picker
|
// Specialization — opens picker
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => _showFilterPicker(
|
onTap: () => _showFilterPicker(
|
||||||
title: 'Select Category',
|
title: 'Select Specialization',
|
||||||
fieldSlug: 'specialization',
|
fieldSlug: 'specialization',
|
||||||
currentValue: _selectedCategory,
|
currentValue: _selectedCategory,
|
||||||
onSelect: (value, label) {
|
onSelect: (value, label) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedCategory = value;
|
_selectedCategory = value;
|
||||||
_selectedCategoryName = label ?? 'Categories';
|
_selectedCategoryName = label ?? 'Specialization';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onClear: () {
|
onClear: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedCategory = null;
|
_selectedCategory = null;
|
||||||
_selectedCategoryName = 'Categories';
|
_selectedCategoryName = 'Specialization';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -291,7 +291,20 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
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,
|
conversationId,
|
||||||
content: content,
|
content: content,
|
||||||
);
|
);
|
||||||
@@ -311,7 +324,7 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
|||||||
final updatedConversations = state.conversations.map((c) {
|
final updatedConversations = state.conversations.map((c) {
|
||||||
if (c.id == conversationId) {
|
if (c.id == conversationId) {
|
||||||
return c.copyWith(
|
return c.copyWith(
|
||||||
lastMessageText: realMessage.content,
|
lastMessageText: realMessage!.content,
|
||||||
lastMessageAt: realMessage.createdAt,
|
lastMessageAt: realMessage.createdAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@@ -28,6 +30,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
|
|||||||
int _previousMessageCount = 0;
|
int _previousMessageCount = 0;
|
||||||
|
|
||||||
final SocketService _socket = SocketService();
|
final SocketService _socket = SocketService();
|
||||||
|
StreamSubscription<bool>? _connectionSub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -37,17 +40,17 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
|
|||||||
notifier.setActiveConversation(widget.conversationId);
|
notifier.setActiveConversation(widget.conversationId);
|
||||||
notifier.loadMessages(widget.conversationId);
|
notifier.loadMessages(widget.conversationId);
|
||||||
notifier.markAsRead(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) {
|
if (_socket.isConnected) {
|
||||||
_socket.joinConversation(widget.conversationId);
|
_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);
|
_scrollController.addListener(_onScroll);
|
||||||
_messageController.addListener(_onTextChanged);
|
_messageController.addListener(_onTextChanged);
|
||||||
@@ -55,6 +58,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_connectionSub?.cancel();
|
||||||
// Stop typing and leave room before disposing
|
// Stop typing and leave room before disposing
|
||||||
if (_isComposing) {
|
if (_isComposing) {
|
||||||
_socket.stopTyping(widget.conversationId);
|
_socket.stopTyping(widget.conversationId);
|
||||||
|
|||||||
Reference in New Issue
Block a user