import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:tugas_akhir_supabase/screens/community/models/message.dart'; import 'package:intl/intl.dart'; import 'package:tugas_akhir_supabase/screens/community/components/image_detail_screen.dart'; class MessageItem extends StatelessWidget { final Message message; final bool isMyMessage; final bool isReadByAll; final Function(Message) onReply; final Function(Message) onLongPress; final Function(LinkableElement) onOpenLink; const MessageItem({ super.key, required this.message, required this.isMyMessage, required this.isReadByAll, required this.onReply, required this.onLongPress, required this.onOpenLink, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), child: GestureDetector( onLongPress: () => onLongPress(message), child: Row( mainAxisAlignment: isMyMessage ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ // Only show avatar for messages from others if (!isMyMessage) _buildAvatar(), // Message bubble with swipe to reply Flexible( child: Dismissible( key: Key('dismissible-${message.id}'), direction: DismissDirection.startToEnd, dismissThresholds: const {DismissDirection.startToEnd: 0.2}, confirmDismiss: (_) async { onReply(message); return false; }, background: Container( alignment: Alignment.centerLeft, padding: const EdgeInsets.only(left: 8), color: Colors.green.shade100, width: 80, child: Icon( Icons.reply, color: Colors.green.shade700, size: 18, ), ), child: _buildMessageBubble(context), ), ), // Space after my messages if (isMyMessage) const SizedBox(width: 12), ], ), ), ); } Widget _buildAvatar() { return Padding( padding: const EdgeInsets.only(right: 8), child: CircleAvatar( radius: 16, backgroundColor: Colors.grey[300], backgroundImage: message.avatarUrl != null && message.avatarUrl!.isNotEmpty ? CachedNetworkImageProvider( message.avatarUrl!, maxHeight: 64, maxWidth: 64, ) as ImageProvider : null, child: (message.avatarUrl == null || message.avatarUrl!.isEmpty) ? Text( message.senderUsername.isNotEmpty ? message.senderUsername[0].toUpperCase() : '?', style: TextStyle( color: Colors.black54, fontWeight: FontWeight.bold, ), ) : null, ), ); } Widget _buildMessageBubble(BuildContext context) { return Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.75, ), decoration: BoxDecoration( color: isMyMessage ? Color(0xFFDCF8C6) : Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(isMyMessage ? 8 : 0), topRight: Radius.circular(isMyMessage ? 0 : 8), bottomLeft: const Radius.circular(8), bottomRight: const Radius.circular(8), ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 3, offset: const Offset(0, 1), ), ], ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), margin: EdgeInsets.only( bottom: 1, left: isMyMessage ? 40 : 0, right: isMyMessage ? 0 : 40, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Sender name (only for messages from others) if (!isMyMessage) Text( message.senderUsername, style: TextStyle( color: Colors.green[700], fontWeight: FontWeight.bold, fontSize: 13, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), // Show reply preview if this is a reply if (message.replyToId != null && message.replyToContent != null) _buildReplyPreview(), // Message content if (message.imageUrl != null && message.imageUrl!.isNotEmpty) _buildImagePreview(context), if (message.content.isNotEmpty) Linkify( onOpen: onOpenLink, text: message.content, style: const TextStyle(color: Colors.black87, fontSize: 15), linkStyle: const TextStyle( color: Colors.blue, decoration: TextDecoration.underline, ), options: LinkifyOptions(humanize: false), ), // Time indicator with read status Align( alignment: Alignment.bottomRight, child: Padding( padding: const EdgeInsets.only(top: 2), child: _buildTimeWithStatus(), ), ), ], ), ); } Widget _buildReplyPreview() { // Extract username from reply String replyUsername = message.replyToSenderEmail ?? 'Unknown'; if (replyUsername.contains('@')) { replyUsername = replyUsername.split('@')[0]; } final replyContent = message.replyToContent ?? 'No content'; return Container( margin: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), decoration: BoxDecoration( color: Colors.grey.shade100, border: Border(left: BorderSide(color: Colors.blue.shade400, width: 2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( replyUsername, style: TextStyle( fontWeight: FontWeight.w500, fontSize: 10, color: Colors.blue.shade700, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( replyContent.length > 40 ? '${replyContent.substring(0, 40)}...' : replyContent, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: Colors.grey.shade800), ), ], ), ); } Widget _buildImagePreview(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 4, top: 2), child: GestureDetector( onTap: () => _openImageDetail(context), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( imageUrl: message.imageUrl!, placeholder: (context, url) => Container( height: 200, color: Colors.grey[200], child: const Center(child: CircularProgressIndicator()), ), errorWidget: (context, url, error) => Container( height: 200, color: Colors.grey[200], child: const Center(child: Icon(Icons.error)), ), fit: BoxFit.cover, ), ), ), ); } void _openImageDetail(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => ImageDetailScreen( imageUrl: message.imageUrl!, senderName: message.senderUsername, timestamp: message.createdAt, heroTag: 'message-image-${message.id}', ), ), ); } Widget _buildTimeWithStatus() { return Row( mainAxisSize: MainAxisSize.min, children: [ Text( _formatTime(message.createdAt), style: TextStyle(color: Colors.grey[600], fontSize: 11), ), if (isMyMessage) Padding( padding: const EdgeInsets.only(left: 3), child: Icon( isReadByAll ? Icons.done_all : Icons.done, size: 14, color: isReadByAll ? Colors.blue[400] : Colors.grey[400], ), ), ], ); } String _formatTime(DateTime dateTime) { final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final yesterday = today.subtract(Duration(days: 1)); final messageDate = DateTime(dateTime.year, dateTime.month, dateTime.day); if (messageDate == today) { return DateFormat('HH:mm').format(dateTime); } else if (messageDate == yesterday) { return 'Kemarin ${DateFormat('HH:mm').format(dateTime)}'; } else { return DateFormat('dd/MM HH:mm').format(dateTime); } } } // Global navigator key for context access final GlobalKey navigatorKey = GlobalKey();