import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:intl/intl.dart'; import '../models/conversation_model.dart'; import '../theme/app_theme.dart'; class ConversationTile extends StatelessWidget { final ConversationModel conversation; final VoidCallback onTap; const ConversationTile({ super.key, required this.conversation, required this.onTap, }); @override Widget build(BuildContext context) { final lastMsg = conversation.lastMessage; final hasUnread = conversation.unreadCount > 0; final isDark = AppTheme.isDark(context); return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ // ── Avatar ────────────────────────────────────────────────────── _buildAvatar(context, conversation), const SizedBox(width: 12), // ── Content ───────────────────────────────────────────────────── Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name row + time Row( children: [ Expanded( child: Text( conversation.name, style: TextStyle( color: AppTheme.textPrimary(context), fontSize: 16.5, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 6), Text( _formatTime(conversation.timestamp), style: TextStyle( color: hasUnread ? AppTheme.primary : AppTheme.textSecondary(context), fontSize: 12, fontWeight: hasUnread ? FontWeight.w600 : FontWeight.normal, ), ), ], ), const SizedBox(height: 4), // Subtitle row: ack icon + preview + badges Row( children: [ // ── ACK icon for sent messages ─────────────────────── if (lastMsg != null && lastMsg.fromMe) ...[ _buildAckIcon(context, lastMsg.ack), const SizedBox(width: 3), ], // ── Message preview ────────────────────────────────── Expanded( child: Text( _getSubtitleText(context, lastMsg), style: TextStyle( color: AppTheme.textSecondary(context), fontSize: 14, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), // ── Trailing badges ────────────────────────────────── if (conversation.isMuted) ...[ const SizedBox(width: 4), Icon(Icons.volume_off, size: 15, color: AppTheme.textSecondary(context)), ], if (conversation.pinned) ...[ const SizedBox(width: 4), Icon(Icons.push_pin, size: 15, color: AppTheme.textSecondary(context)), ], if (hasUnread) ...[ const SizedBox(width: 6), _buildUnreadBadge(conversation.unreadCount), ], ], ), ], ), ), ], ), ), ); } // ── Avatar builder (cached network image + fallback initials) ───────────── Widget _buildAvatar(BuildContext context, ConversationModel c) { final isDark = AppTheme.isDark(context); final fallbackBg = isDark ? const Color(0xff2a3942) : const Color(0xff6b7c85); if (c.avatar != null && c.avatar!.isNotEmpty) { return CircleAvatar( radius: 28, backgroundColor: fallbackBg, child: ClipOval( child: CachedNetworkImage( imageUrl: c.avatar!, width: 56, height: 56, fit: BoxFit.cover, placeholder: (_, __) => _initialsAvatar(c.name, fallbackBg), errorWidget: (_, __, ___) => _initialsAvatar(c.name, fallbackBg), ), ), ); } // Group icon or person icon if (c.isGroup) { return CircleAvatar( radius: 28, backgroundColor: fallbackBg, child: const Icon(Icons.group, color: Colors.white, size: 30), ); } return CircleAvatar( radius: 28, backgroundColor: fallbackBg, child: _initialsAvatar(c.name, fallbackBg), ); } Widget _initialsAvatar(String name, Color bg) { return Container( width: 56, height: 56, color: bg, alignment: Alignment.center, child: Icon( Icons.person, color: Colors.white, size: 30, ), ); } // ── Unread badge ────────────────────────────────────────────────────────── Widget _buildUnreadBadge(int count) { return Container( constraints: const BoxConstraints(minWidth: 20, minHeight: 20), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: const BoxDecoration( color: AppTheme.primary, shape: BoxShape.circle, ), child: Text( count > 99 ? '99+' : count.toString(), style: const TextStyle( color: Colors.white, fontSize: 11.5, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ); } // ── ACK (delivery status) icon ──────────────────────────────────────────── // Real WhatsApp ACK levels from whatsapp-web.js: // -1 = error → clock (pending/error) // 0 = pending → clock // 1 = sent → single grey tick // 2 = received → double grey tick // 3 = read/played→ double blue tick Widget _buildAckIcon(BuildContext context, int ack) { switch (ack) { case -1: case 0: // Pending / clock return Icon(Icons.access_time_rounded, size: 14, color: AppTheme.textSecondary(context)); case 1: // Sent — single grey tick return Icon(Icons.check_rounded, size: 15, color: AppTheme.textSecondary(context)); case 2: // Delivered — double grey tick return Icon(Icons.done_all_rounded, size: 15, color: AppTheme.textSecondary(context)); case 3: // Read — double blue tick return const Icon(Icons.done_all_rounded, size: 15, color: AppTheme.blueTick); default: return const SizedBox.shrink(); } } // ── Subtitle text ───────────────────────────────────────────────────────── String _getSubtitleText(BuildContext context, LastMessageModel? lastMsg) { if (lastMsg == null) return ''; if (lastMsg.hasMedia) { return '📷 Photo'; } return lastMsg.body; } // ── Time formatter ──────────────────────────────────────────────────────── String _formatTime(int timestamp) { if (timestamp == 0) return ''; final dt = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final yesterday = today.subtract(const Duration(days: 1)); final msgDate = DateTime(dt.year, dt.month, dt.day); if (msgDate == today) { return DateFormat('h:mm a').format(dt); } else if (msgDate == yesterday) { return 'Yesterday'; } else if (now.difference(dt).inDays < 7) { return DateFormat('EEEE').format(dt); } else { return DateFormat('dd/MM/yy').format(dt); } } }