import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import '../controllers/chat_controller.dart'; import '../models/conversation_model.dart'; import '../models/message_model.dart'; import '../theme/app_theme.dart'; import '../widgets/message_bubble.dart'; class ChatScreen extends StatelessWidget { final ConversationModel conversation; const ChatScreen({super.key, required this.conversation}); @override Widget build(BuildContext context) { final ctrl = Get.put( ChatController(conversation: conversation), tag: conversation.id, ); return Scaffold( backgroundColor: AppTheme.background, appBar: _buildAppBar(conversation), body: Column( children: [ Expanded(child: _buildMessageList(ctrl)), _buildInputBar(ctrl), ], ), ); } AppBar _buildAppBar(ConversationModel chat) => AppBar( backgroundColor: AppTheme.surface, leadingWidth: 32, title: Row( children: [ _avatar(chat, radius: 18), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( chat.name, style: const TextStyle( color: AppTheme.textPrimary, fontSize: 16, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), if (chat.isGroup) const Text( 'Group', style: TextStyle(color: AppTheme.textSecondary, fontSize: 12), ), ], ), ), ], ), actions: [ IconButton( icon: const Icon(Icons.videocam_outlined, color: AppTheme.iconColor), onPressed: null, ), IconButton( icon: const Icon(Icons.call_outlined, color: AppTheme.iconColor), onPressed: null, ), IconButton( icon: const Icon(Icons.more_vert, color: AppTheme.iconColor), onPressed: null, ), ], ); Widget _buildMessageList(ChatController ctrl) { return Obx(() { if (ctrl.isLoading.value) { return const Center( child: CircularProgressIndicator(color: AppTheme.primary), ); } final items = ctrl.groupedMessages; if (items.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.chat_bubble_outline, color: AppTheme.textSecondary.withOpacity(0.5), size: 48), const SizedBox(height: 12), Text( 'No messages yet', style: TextStyle(color: AppTheme.textSecondary.withOpacity(0.8)), ), ], ), ); } return ListView.builder( controller: ctrl.scrollCtrl, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), itemCount: items.length, itemBuilder: (_, i) { final item = items[i]; if (item is String) return _buildDateSeparator(item); return MessageBubble(message: item as MessageModel); }, ); }); } Widget _buildDateSeparator(String label) => Center( child: Container( margin: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: AppTheme.surfaceLight, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: const TextStyle( color: AppTheme.textSecondary, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ); Widget _buildInputBar(ChatController ctrl) => Container( color: AppTheme.surface, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: SafeArea( child: Obx(() { if (ctrl.isRecording.value) { return Row( children: [ const SizedBox(width: 12), const Icon(Icons.fiber_manual_record, color: Colors.red, size: 16), const SizedBox(width: 8), const Text( 'Recording...', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(width: 12), Text( '${(ctrl.recordDuration.value ~/ 60).toString().padLeft(2, '0')}:${(ctrl.recordDuration.value % 60).toString().padLeft(2, '0')}', style: const TextStyle(color: AppTheme.textPrimary, fontSize: 14, fontFamily: 'monospace'), ), const Spacer(), TextButton.icon( icon: const Icon(Icons.delete, color: Colors.redAccent, size: 18), label: const Text('Cancel', style: TextStyle(color: Colors.redAccent)), onPressed: ctrl.cancelRecording, ), const SizedBox(width: 8), GestureDetector( onTap: ctrl.stopAndSendRecording, child: Container( width: 44, height: 44, decoration: const BoxDecoration( color: AppTheme.primary, shape: BoxShape.circle, ), child: const Icon(Icons.check, color: Colors.white, size: 20), ), ), const SizedBox(width: 8), ], ); } return Row( children: [ // Attachment button IconButton( icon: const Icon(Icons.add, color: AppTheme.primary, size: 28), onPressed: () => _showAttachmentSheet(ctrl), ), // Input Expanded( child: TextField( controller: ctrl.inputCtrl, style: const TextStyle(color: AppTheme.textPrimary), maxLines: 5, minLines: 1, textCapitalization: TextCapitalization.sentences, decoration: InputDecoration( hintText: 'Message', hintStyle: const TextStyle(color: AppTheme.textSecondary), filled: true, fillColor: AppTheme.surfaceLight, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(24), borderSide: BorderSide.none, ), ), onSubmitted: (_) => ctrl.sendMessage(), ), ), const SizedBox(width: 8), // Dynamic Send / Mic Button GestureDetector( onTap: () { if (ctrl.hasText.value) { ctrl.sendMessage(); } else { ctrl.startRecording(); } }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), width: 48, height: 48, decoration: const BoxDecoration( color: AppTheme.primary, shape: BoxShape.circle, ), child: ctrl.isSending.value ? const Padding( padding: EdgeInsets.all(12), child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Icon( ctrl.hasText.value ? Icons.send : Icons.mic, color: Colors.white, size: 20, ), ), ), const SizedBox(width: 4), ], ); }), ), ); void _showAttachmentSheet(ChatController ctrl) { Get.bottomSheet( Container( decoration: const BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ), padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, decoration: BoxDecoration( color: AppTheme.textSecondary.withOpacity(0.3), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 24), const Text( 'Send Media Attachment', style: TextStyle( color: AppTheme.textPrimary, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildAttachmentItem( icon: Icons.camera_alt, color: Colors.green, label: 'Camera', onTap: () { Get.back(); _pickAndSendImage(ctrl, ImageSource.camera); }, ), _buildAttachmentItem( icon: Icons.photo_library, color: Colors.purple, label: 'Gallery', onTap: () { Get.back(); _pickAndSendImage(ctrl, ImageSource.gallery); }, ), _buildAttachmentItem( icon: Icons.mic, color: Colors.orange, label: 'Voice Note', onTap: () { Get.back(); // 100% valid MP3 silent audio base64 snippet to prevent getAudioDuration errors const base64Audio = 'SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAAAwAAAbAAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV////////////////////////////////////////////AAAAAExhdmM1OC4xMwAAAAAAAAAAAAAAACQDkAAAAAAAAAGw9wrNaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAAAAANIAAAAAExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxDsAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/+MYxHYAAANIAAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'; ctrl.sendMediaMessage( base64Audio, 'audio/mp3', 'voice_note.mp3', ); }, ), ], ), const SizedBox(height: 16), ], ), ), barrierColor: Colors.black.withOpacity(0.5), ); } void _pickAndSendImage(ChatController ctrl, ImageSource source) async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage( source: source, imageQuality: 75, ); if (image == null) return; final bytes = await image.readAsBytes(); final base64String = base64Encode(bytes); String mimetype = 'image/jpeg'; if (image.path.toLowerCase().endsWith('.png')) { mimetype = 'image/png'; } else if (image.path.toLowerCase().endsWith('.gif')) { mimetype = 'image/gif'; } await ctrl.sendMediaMessage( base64String, mimetype, image.name, caption: '📸 Photo sent via Mywhatsapp!', ); } catch (e) { Get.snackbar( 'Error picking image', e.toString(), backgroundColor: Colors.redAccent.withOpacity(0.8), colorText: Colors.white, ); } } Widget _buildAttachmentItem({ required IconData icon, required Color color, required String label, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Column( children: [ CircleAvatar( radius: 28, backgroundColor: color.withOpacity(0.15), child: Icon(icon, color: color, size: 28), ), const SizedBox(height: 8), Text( label, style: const TextStyle(color: AppTheme.textPrimary, fontSize: 12), ), ], ), ); } Widget _avatar(ConversationModel chat, {double radius = 24}) { if (chat.avatar != null) { return CircleAvatar( radius: radius, backgroundImage: NetworkImage(chat.avatar!), backgroundColor: AppTheme.surfaceLight, ); } return CircleAvatar( radius: radius, backgroundColor: AppTheme.primaryDark, child: Text( chat.name.isNotEmpty ? chat.name[0].toUpperCase() : '?', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ); } }