import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import '../models/message.dart'; import 'code_block.dart'; class MessageBubble extends StatelessWidget { final Message message; const MessageBubble({super.key, required this.message}); @override Widget build(BuildContext context) { final isUser = message.isUser; return Padding( padding: EdgeInsets.only( top: 8, bottom: 8, left: isUser ? 40 : 0, right: isUser ? 0 : 40, ), child: Column( crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ // Role label Padding( padding: const EdgeInsets.only(bottom: 4, left: 4, right: 4), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isUser ? Icons.person : Icons.smart_toy, size: 14, color: Colors.grey.shade500, ), const SizedBox(width: 4), Text( isUser ? 'You' : 'Captain', style: TextStyle( fontSize: 12, color: Colors.grey.shade500, fontWeight: FontWeight.w500, ), ), if (message.isStreaming) ...[ const SizedBox(width: 8), SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, color: Theme.of(context).colorScheme.primary, ), ), ], ], ), ), // Message content GestureDetector( onLongPress: () { Clipboard.setData(ClipboardData(text: message.content)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Copied to clipboard'), duration: Duration(seconds: 1), ), ); }, child: Container( decoration: BoxDecoration( color: isUser ? Theme.of(context).colorScheme.primary.withOpacity(0.2) : const Color(0xFF2D2D2D), borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.all(12), child: isUser ? SelectableText( message.content, style: const TextStyle(fontSize: 15), ) : MarkdownBody( data: message.content, selectable: true, styleSheet: MarkdownStyleSheet( p: const TextStyle(fontSize: 15), code: TextStyle( fontFamily: 'JetBrainsMono', backgroundColor: Colors.black.withOpacity(0.3), fontSize: 13, ), codeblockDecoration: BoxDecoration( color: Colors.black.withOpacity(0.3), borderRadius: BorderRadius.circular(8), ), blockquotePadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), blockquoteDecoration: BoxDecoration( border: Border( left: BorderSide( color: Theme.of(context).colorScheme.primary, width: 3, ), ), ), ), builders: { 'code': CodeBlockBuilder(), }, ), ), ), // Timestamp Padding( padding: const EdgeInsets.only(top: 4, left: 4, right: 4), child: Text( _formatTime(message.timestamp), style: TextStyle( fontSize: 10, color: Colors.grey.shade600, ), ), ), ], ), ); } String _formatTime(DateTime time) { final hour = time.hour.toString().padLeft(2, '0'); final minute = time.minute.toString().padLeft(2, '0'); return '$hour:$minute'; } }