feat: Enhance socket reconnection and chat screen lifecycle management, and refine agent detail and home screen UI.

This commit is contained in:
pradeepkumar
2026-03-19 03:58:03 +05:30
parent 6936c29c13
commit 21e677bf27
5 changed files with 75 additions and 22 deletions

View File

@@ -656,7 +656,11 @@ class _AgentDetailScreenState extends ConsumerState<AgentDetailScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
// Location (wrapping) + Member Since (separate line) // Location (wrapping) + Member Since (separate line)
Builder(builder: (_) { 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; const maxVisible = 4;
final visibleParts = parts.take(maxVisible).join(', '); final visibleParts = parts.take(maxVisible).join(', ');
final remaining = parts.length - maxVisible; final remaining = parts.length - maxVisible;
@@ -1520,8 +1524,9 @@ class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> {
return Container( return Container(
width: 298, width: 298,
constraints: const BoxConstraints(minHeight: 236), height: _expanded ? null : 236,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
@@ -1531,6 +1536,7 @@ class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> {
), ),
), ),
child: Column( child: Column(
mainAxisSize: _expanded ? MainAxisSize.min : MainAxisSize.max,
children: [ children: [
Icon(_icon, size: 32, color: AppColors.accentOrange), Icon(_icon, size: 32, color: AppColors.accentOrange),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@@ -1083,20 +1083,22 @@ class _AgentHomeContentState extends ConsumerState<_AgentHomeContent> {
final displayName = AgentDetailState.titleCase(card.name); final displayName = AgentDetailState.titleCase(card.name);
final sectionKey = 'spec_${card.slug}'; final sectionKey = 'spec_${card.slug}';
final isExpanded = _expandedTagSections.contains(sectionKey); final isExpanded = _expandedTagSections.contains(sectionKey);
final displayValues = isExpanded ? card.values : card.values.take(5).toList(); final displayValues = isExpanded ? card.values : card.values.take(3).toList();
final hasMore = card.values.length > 5; final hasMore = card.values.length > 3;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 66, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 66, vertical: 8),
width: 298, width: 298,
constraints: const BoxConstraints(minHeight: 236), height: isExpanded ? null : 236,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
border: Border.all(color: AppColors.primaryDark, width: 0.7), border: Border.all(color: AppColors.primaryDark, width: 0.7),
), ),
child: Column( child: Column(
mainAxisSize: isExpanded ? MainAxisSize.min : MainAxisSize.max,
children: [ children: [
const Icon(Icons.category_outlined, const Icon(Icons.category_outlined,
size: 28, color: AppColors.accentOrange), size: 28, color: AppColors.accentOrange),

View File

@@ -44,9 +44,6 @@ class HomeScreen extends ConsumerWidget {
// Testimonials Section // Testimonials Section
const TestimonialsSection(), const TestimonialsSection(),
const SizedBox(height: 40), const SizedBox(height: 40),
// Footer
_buildFooter(context),
], ],
), ),
); );
@@ -59,7 +56,7 @@ class HomeScreen extends ConsumerWidget {
'FAQ': '/faq', 'FAQ': '/faq',
}; };
Widget _buildFooter(BuildContext context) { Widget _buildFooter(BuildContext context, WidgetRef ref) {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
@@ -76,13 +73,13 @@ class HomeScreen extends ConsumerWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildFooterLink(context, 'Home'), _buildFooterLink(context, 'Home', ref),
_buildFooterDot(), _buildFooterDot(),
_buildFooterLink(context, 'About'), _buildFooterLink(context, 'About', ref),
_buildFooterDot(), _buildFooterDot(),
_buildFooterLink(context, 'Contact'), _buildFooterLink(context, 'Contact', ref),
_buildFooterDot(), _buildFooterDot(),
_buildFooterLink(context, 'FAQ'), _buildFooterLink(context, 'FAQ', ref),
], ],
), ),
const SizedBox(height: 16), 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( return GestureDetector(
onTap: () { onTap: () {
final route = _footerRoutes[text]; 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( child: Text(
text, text,

View File

@@ -68,9 +68,9 @@ class SocketService {
.setAuth({'token': token}) .setAuth({'token': token})
.disableAutoConnect() .disableAutoConnect()
.enableReconnection() .enableReconnection()
.setReconnectionAttempts(5) .setReconnectionAttempts(double.maxFinite.toInt())
.setReconnectionDelay(1000) .setReconnectionDelay(1000)
.setReconnectionDelayMax(5000) .setReconnectionDelayMax(10000)
.build(), .build(),
); );
@@ -97,6 +97,18 @@ class SocketService {
_connectionController.add(false); _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 // Listen for events
_socket!.on('new_message', (data) { _socket!.on('new_message', (data) {
try { try {
@@ -172,6 +184,12 @@ class SocketService {
_socket?.connect(); _socket?.connect();
} }
/// Ensure socket is connected — reconnect if needed.
Future<void> ensureConnected() async {
if (_socket?.connected == true) return;
await connect();
}
void disconnect() { void disconnect() {
_intentionalDisconnect = true; _intentionalDisconnect = true;
_socket?.disconnect(); _socket?.disconnect();

View File

@@ -23,7 +23,8 @@ class ChatScreen extends ConsumerStatefulWidget {
ConsumerState<ChatScreen> createState() => _ChatScreenState(); ConsumerState<ChatScreen> createState() => _ChatScreenState();
} }
class _ChatScreenState extends ConsumerState<ChatScreen> { class _ChatScreenState extends ConsumerState<ChatScreen>
with WidgetsBindingObserver {
final TextEditingController _messageController = TextEditingController(); final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final FocusNode _messageFocusNode = FocusNode(); final FocusNode _messageFocusNode = FocusNode();
@@ -36,6 +37,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final notifier = ref.read(messagingProvider.notifier); final notifier = ref.read(messagingProvider.notifier);
notifier.setActiveConversation(widget.conversationId); notifier.setActiveConversation(widget.conversationId);
@@ -43,10 +45,12 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
notifier.markAsRead(widget.conversationId); notifier.markAsRead(widget.conversationId);
// Suppress push notifications for this conversation while viewing // Suppress push notifications for this conversation while viewing
PushNotificationService().activeConversationId = widget.conversationId; PushNotificationService().activeConversationId = widget.conversationId;
// Join socket room for real-time updates // Ensure socket is connected and join room for real-time updates
if (_socket.isConnected) { _socket.ensureConnected().then((_) {
_socket.joinConversation(widget.conversationId); if (_socket.isConnected) {
} _socket.joinConversation(widget.conversationId);
}
});
// Listen for ALL connection changes (initial connect + reconnects) // Listen for ALL connection changes (initial connect + reconnects)
// so we re-join the room after any reconnection // so we re-join the room after any reconnection
_connectionSub = _socket.onConnectionChange.listen((connected) { _connectionSub = _socket.onConnectionChange.listen((connected) {
@@ -59,8 +63,23 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
_messageController.addListener(_onTextChanged); _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 @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
_connectionSub?.cancel(); _connectionSub?.cancel();
// Stop typing and leave room before disposing // Stop typing and leave room before disposing
if (_isComposing) { if (_isComposing) {