175 lines
5.5 KiB
Dart
175 lines
5.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../models/message_model.dart';
|
|
import '../theme/app_theme.dart';
|
|
|
|
class MessageBubble extends StatelessWidget {
|
|
final MessageModel message;
|
|
|
|
const MessageBubble({super.key, required this.message});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isMe = message.fromMe;
|
|
final align = isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start;
|
|
final bg = isMe ? AppTheme.outgoingMsg : AppTheme.incomingMsg;
|
|
final radius = isMe
|
|
? const BorderRadius.only(
|
|
topLeft: Radius.circular(12),
|
|
topRight: Radius.circular(0),
|
|
bottomLeft: Radius.circular(12),
|
|
bottomRight: Radius.circular(12),
|
|
)
|
|
: const BorderRadius.only(
|
|
topLeft: Radius.circular(0),
|
|
topRight: Radius.circular(12),
|
|
bottomLeft: Radius.circular(12),
|
|
bottomRight: Radius.circular(12),
|
|
);
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
|
child: Column(
|
|
crossAxisAlignment: align,
|
|
children: [
|
|
Container(
|
|
constraints: BoxConstraints(
|
|
maxWidth: MediaQuery.of(context).size.width * 0.75,
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: bg,
|
|
borderRadius: radius,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Show sender name in group chats if not from me
|
|
if (!isMe && message.author != null) ...[
|
|
Text(
|
|
message.author!,
|
|
style: const TextStyle(
|
|
color: AppTheme.primary,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
],
|
|
|
|
// Media placeholder if it is media
|
|
if (message.hasMedia) ...[
|
|
_buildMediaPlaceholder(),
|
|
const SizedBox(height: 6),
|
|
],
|
|
|
|
// Message text & time row
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
message.body,
|
|
style: const TextStyle(
|
|
color: AppTheme.textPrimary,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 4),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
_formatTime(message.timestamp),
|
|
style: const TextStyle(
|
|
color: AppTheme.textSecondary,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
if (isMe) ...[
|
|
const SizedBox(width: 4),
|
|
_buildAckIcon(message.ack),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMediaPlaceholder() {
|
|
IconData iconData = Icons.insert_drive_file;
|
|
String label = "File Attachment";
|
|
|
|
switch (message.type) {
|
|
case "image":
|
|
iconData = Icons.photo;
|
|
label = "Image";
|
|
break;
|
|
case "video":
|
|
iconData = Icons.videocam;
|
|
label = "Video";
|
|
break;
|
|
case "audio":
|
|
iconData = Icons.audiotrack;
|
|
label = "Audio File";
|
|
break;
|
|
case "sticker":
|
|
iconData = Icons.emoji_emotions;
|
|
label = "Sticker";
|
|
break;
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(iconData, color: AppTheme.textSecondary, size: 32),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
label,
|
|
style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w500),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAckIcon(int ack) {
|
|
switch (ack) {
|
|
case 1: // Pending
|
|
return const Icon(Icons.access_time, size: 13, color: AppTheme.textSecondary);
|
|
case 2: // Sent
|
|
return const Icon(Icons.done, size: 15, color: AppTheme.textSecondary);
|
|
case 3: // Delivered
|
|
return const Icon(Icons.done_all, size: 15, color: AppTheme.textSecondary);
|
|
case 4: // Read
|
|
return const Icon(Icons.done_all, size: 15, color: Colors.blue);
|
|
default:
|
|
return const SizedBox.shrink();
|
|
}
|
|
}
|
|
|
|
String _formatTime(int timestamp) {
|
|
if (timestamp == 0) return '';
|
|
final dt = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
|
return DateFormat('h:mm a').format(dt);
|
|
}
|
|
}
|