From 21e677bf275d412a2288e69efe5012d5b66b1339 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Thu, 19 Mar 2026 03:58:03 +0530 Subject: [PATCH] feat: Enhance socket reconnection and chat screen lifecycle management, and refine agent detail and home screen UI. --- .../screens/agent_detail_screen.dart | 10 +++++-- .../screens/agent_home_screen.dart | 8 +++-- .../presentation/screens/home_screen.dart | 28 +++++++++++------- .../messaging/data/socket_service.dart | 22 ++++++++++++-- .../presentation/screens/chat_screen.dart | 29 +++++++++++++++---- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/lib/features/agents/presentation/screens/agent_detail_screen.dart b/lib/features/agents/presentation/screens/agent_detail_screen.dart index 2ab8127..96c8fca 100644 --- a/lib/features/agents/presentation/screens/agent_detail_screen.dart +++ b/lib/features/agents/presentation/screens/agent_detail_screen.dart @@ -656,7 +656,11 @@ class _AgentDetailScreenState extends ConsumerState { const SizedBox(height: 8), // Location (wrapping) + Member Since (separate line) Builder(builder: (_) { - final parts = state.locationParts; + var parts = state.locationParts; + // Fallback: use agent model location if fieldValues-based parts are empty + if (parts.isEmpty && agent.location.isNotEmpty) { + parts = agent.location.split(', ').map((s) => s.trim()).where((s) => s.isNotEmpty).toList(); + } const maxVisible = 4; final visibleParts = parts.take(maxVisible).join(', '); final remaining = parts.length - maxVisible; @@ -1520,8 +1524,9 @@ class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> { return Container( width: 298, - constraints: const BoxConstraints(minHeight: 236), + height: _expanded ? null : 236, padding: const EdgeInsets.symmetric(vertical: 16), + clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), @@ -1531,6 +1536,7 @@ class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> { ), ), child: Column( + mainAxisSize: _expanded ? MainAxisSize.min : MainAxisSize.max, children: [ Icon(_icon, size: 32, color: AppColors.accentOrange), const SizedBox(height: 8), diff --git a/lib/features/agents/presentation/screens/agent_home_screen.dart b/lib/features/agents/presentation/screens/agent_home_screen.dart index 26e0e3d..391017e 100644 --- a/lib/features/agents/presentation/screens/agent_home_screen.dart +++ b/lib/features/agents/presentation/screens/agent_home_screen.dart @@ -1083,20 +1083,22 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> { final displayName = AgentDetailState.titleCase(card.name); final sectionKey = 'spec_${card.slug}'; final isExpanded = _expandedTagSections.contains(sectionKey); - final displayValues = isExpanded ? card.values : card.values.take(5).toList(); - final hasMore = card.values.length > 5; + final displayValues = isExpanded ? card.values : card.values.take(3).toList(); + final hasMore = card.values.length > 3; return Container( margin: const EdgeInsets.symmetric(horizontal: 66, vertical: 8), width: 298, - constraints: const BoxConstraints(minHeight: 236), + height: isExpanded ? null : 236, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.primaryDark, width: 0.7), ), child: Column( + mainAxisSize: isExpanded ? MainAxisSize.min : MainAxisSize.max, children: [ const Icon(Icons.category_outlined, size: 28, color: AppColors.accentOrange), diff --git a/lib/features/home/presentation/screens/home_screen.dart b/lib/features/home/presentation/screens/home_screen.dart index 729133e..324e06d 100644 --- a/lib/features/home/presentation/screens/home_screen.dart +++ b/lib/features/home/presentation/screens/home_screen.dart @@ -44,9 +44,6 @@ class HomeScreen extends ConsumerWidget { // Testimonials Section const TestimonialsSection(), const SizedBox(height: 40), - - // Footer - _buildFooter(context), ], ), ); @@ -59,7 +56,7 @@ class HomeScreen extends ConsumerWidget { 'FAQ': '/faq', }; - Widget _buildFooter(BuildContext context) { + Widget _buildFooter(BuildContext context, WidgetRef ref) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), @@ -76,13 +73,13 @@ class HomeScreen extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildFooterLink(context, 'Home'), + _buildFooterLink(context, 'Home', ref), _buildFooterDot(), - _buildFooterLink(context, 'About'), + _buildFooterLink(context, 'About', ref), _buildFooterDot(), - _buildFooterLink(context, 'Contact'), + _buildFooterLink(context, 'Contact', ref), _buildFooterDot(), - _buildFooterLink(context, 'FAQ'), + _buildFooterLink(context, 'FAQ', ref), ], ), const SizedBox(height: 16), @@ -100,11 +97,22 @@ class HomeScreen extends ConsumerWidget { ); } - Widget _buildFooterLink(BuildContext context, String text) { + Widget _buildFooterLink(BuildContext context, String text, WidgetRef ref) { return GestureDetector( onTap: () { final route = _footerRoutes[text]; - if (route != null) context.go(route); + if (route == null) return; + final currentLocation = GoRouterState.of(context).uri.toString(); + if (route == '/home' && currentLocation == '/home') { + // Already on home — scroll to top + ref.read(homeScrollControllerProvider).animateTo( + 0, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + } else { + context.go(route); + } }, child: Text( text, diff --git a/lib/features/messaging/data/socket_service.dart b/lib/features/messaging/data/socket_service.dart index 9afd1bc..ec9bec0 100644 --- a/lib/features/messaging/data/socket_service.dart +++ b/lib/features/messaging/data/socket_service.dart @@ -68,9 +68,9 @@ class SocketService { .setAuth({'token': token}) .disableAutoConnect() .enableReconnection() - .setReconnectionAttempts(5) + .setReconnectionAttempts(double.maxFinite.toInt()) .setReconnectionDelay(1000) - .setReconnectionDelayMax(5000) + .setReconnectionDelayMax(10000) .build(), ); @@ -97,6 +97,18 @@ class SocketService { _connectionController.add(false); }); + _socket!.onReconnect((_) { + _log.i('Socket reconnected successfully'); + }); + + _socket!.onReconnectAttempt((attempt) { + _log.w('Socket reconnection attempt #$attempt'); + }); + + _socket!.onReconnectError((error) { + _log.e('Socket reconnection error: $error'); + }); + // Listen for events _socket!.on('new_message', (data) { try { @@ -172,6 +184,12 @@ class SocketService { _socket?.connect(); } + /// Ensure socket is connected — reconnect if needed. + Future ensureConnected() async { + if (_socket?.connected == true) return; + await connect(); + } + void disconnect() { _intentionalDisconnect = true; _socket?.disconnect(); diff --git a/lib/features/messaging/presentation/screens/chat_screen.dart b/lib/features/messaging/presentation/screens/chat_screen.dart index 68aeaba..a44a5c5 100644 --- a/lib/features/messaging/presentation/screens/chat_screen.dart +++ b/lib/features/messaging/presentation/screens/chat_screen.dart @@ -23,7 +23,8 @@ class ChatScreen extends ConsumerStatefulWidget { ConsumerState createState() => _ChatScreenState(); } -class _ChatScreenState extends ConsumerState { +class _ChatScreenState extends ConsumerState + with WidgetsBindingObserver { final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); final FocusNode _messageFocusNode = FocusNode(); @@ -36,6 +37,7 @@ class _ChatScreenState extends ConsumerState { @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { final notifier = ref.read(messagingProvider.notifier); notifier.setActiveConversation(widget.conversationId); @@ -43,10 +45,12 @@ class _ChatScreenState extends ConsumerState { notifier.markAsRead(widget.conversationId); // Suppress push notifications for this conversation while viewing PushNotificationService().activeConversationId = widget.conversationId; - // Join socket room for real-time updates - if (_socket.isConnected) { - _socket.joinConversation(widget.conversationId); - } + // Ensure socket is connected and join room for real-time updates + _socket.ensureConnected().then((_) { + if (_socket.isConnected) { + _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) { @@ -59,8 +63,23 @@ class _ChatScreenState extends ConsumerState { _messageController.addListener(_onTextChanged); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + // App came back to foreground — reconnect socket and refresh messages + _socket.ensureConnected().then((_) { + if (_socket.isConnected && mounted) { + _socket.joinConversation(widget.conversationId); + } + }); + // Refresh messages to catch anything missed while in background + ref.read(messagingProvider.notifier).loadMessages(widget.conversationId); + } + } + @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _connectionSub?.cancel(); // Stop typing and leave room before disposing if (_isComposing) {