feat: update app icons and refactor home top professionals section to display dynamic agent profiles.

This commit is contained in:
pradeepkumar
2026-03-18 11:42:46 +05:30
parent b52e356a04
commit a90f039b4b
30 changed files with 303 additions and 200 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:logger/logger.dart';
import 'package:real_estate_mobile/config/app_config.dart';
import 'package:real_estate_mobile/core/network/api_client.dart';
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
import 'package:real_estate_mobile/features/messaging/data/models/message.dart';
import 'package:real_estate_mobile/features/support_chat/data/models/support_chat_models.dart';
@@ -144,12 +145,31 @@ class SocketService {
}
Future<void> _reconnectWithFreshToken() async {
// Wait briefly for any in-flight REST token refresh to complete
await Future.delayed(const Duration(milliseconds: 500));
final token = await SecureStorage.getAccessToken();
if (token != null && token != _currentToken) {
if (token == null) return;
// If token hasn't changed, try triggering a refresh via a lightweight API call
if (token == _currentToken) {
try {
// Make a lightweight call that triggers the API client's 401 interceptor
// which auto-refreshes the token
final dio = ApiClient.instance.dio;
await dio.get('/auth/me');
final refreshedToken = await SecureStorage.getAccessToken();
if (refreshedToken == null || refreshedToken == _currentToken) return;
_currentToken = refreshedToken;
} catch (_) {
return; // Token refresh failed — user will need to re-login
}
} else {
_currentToken = token;
_socket?.io.options?['auth'] = {'token': token};
_socket?.connect();
}
_socket?.io.options?['auth'] = {'token': _currentToken};
_socket?.connect();
}
void disconnect() {

View File

@@ -97,7 +97,7 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
final currentMessages =
List<ChatMessage>.from(state.messages[convId] ?? []);
// Avoid duplicates
// Avoid duplicates (broadcast may arrive after ack for sender's own message)
if (currentMessages.any((m) => m.id == message.id)) return;
currentMessages.insert(0, message);
@@ -105,12 +105,22 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
Map<String, List<ChatMessage>>.from(state.messages)
..[convId] = currentMessages;
// Show friendly preview for non-text messages (matching web)
String previewText = message.content;
if (message.messageType == MessageType.image) {
previewText = 'Sent an image';
} else if (message.messageType == MessageType.file) {
previewText = 'Sent a file';
} else if (_isGifUrl(message.content)) {
previewText = 'GIF';
}
// Update conversation's last message and unread count
final updatedConversations = state.conversations.map((c) {
if (c.id == convId) {
final isActive = state.activeConversationId == convId;
return c.copyWith(
lastMessageText: message.content,
lastMessageText: previewText,
lastMessageAt: message.createdAt,
unreadCount: isActive ? c.unreadCount : c.unreadCount + 1,
);
@@ -155,6 +165,59 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
}).toList();
state = state.copyWith(conversations: updatedConversations);
}));
// Reconnection: re-join active conversation room and catch up missed messages
_subscriptions.add(_socket.onConnectionChange.listen((connected) {
if (!mounted || !connected) return;
final activeConvId = state.activeConversationId;
if (activeConvId != null) {
_rejoinAndCatchUp(activeConvId);
}
// Refresh conversation list to catch any missed updates
loadConversations();
}));
}
/// Re-join socket room and fetch missed messages after reconnection (matching web)
Future<void> _rejoinAndCatchUp(String conversationId, {int attempt = 1}) async {
try {
await _socket.joinConversation(conversationId);
} catch (_) {
if (attempt < 3 && mounted && state.activeConversationId == conversationId) {
await Future.delayed(Duration(seconds: attempt));
return _rejoinAndCatchUp(conversationId, attempt: attempt + 1);
}
return;
}
// Fetch recent messages to catch any missed during disconnect
if (!mounted || state.activeConversationId != conversationId) return;
try {
final result = await _repository.getMessages(conversationId, page: 1);
final recentMessages = result.messages.reversed.toList();
final currentMessages = List<ChatMessage>.from(state.messages[conversationId] ?? []);
final existingIds = currentMessages.map((m) => m.id).toSet();
final newMessages = recentMessages.where((m) => !existingIds.contains(m.id)).toList();
if (newMessages.isNotEmpty && mounted) {
currentMessages.insertAll(0, newMessages);
// Sort newest first
currentMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
final updatedMessages = Map<String, List<ChatMessage>>.from(state.messages)
..[conversationId] = currentMessages;
state = state.copyWith(messages: updatedMessages);
}
} catch (e) {
_log.e('Failed to catch up messages after reconnect', error: e);
}
}
static bool _isGifUrl(String text) {
final lower = text.trim().toLowerCase();
return lower.endsWith('.gif') ||
lower.contains('giphy.com/') ||
lower.contains('giphy.gif') ||
lower.contains('tenor.com/');
}
/// Update online status for a user across all conversations (matching web)
@@ -320,12 +383,22 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
Map<String, List<ChatMessage>>.from(state.messages)
..[conversationId] = messagesAfterSend;
// Update conversation's last message
// Update conversation's last message with friendly preview
final sent = realMessage;
String sentPreview = sent.content;
if (sent.messageType == MessageType.image) {
sentPreview = 'Sent an image';
} else if (sent.messageType == MessageType.file) {
sentPreview = 'Sent a file';
} else if (_isGifUrl(sent.content)) {
sentPreview = 'GIF';
}
final updatedConversations = state.conversations.map((c) {
if (c.id == conversationId) {
return c.copyWith(
lastMessageText: realMessage!.content,
lastMessageAt: realMessage.createdAt,
lastMessageText: sentPreview,
lastMessageAt: sent.createdAt,
);
}
return c;