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: 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), // Send button Obx(() => GestureDetector( onTap: ctrl.sendMessage, 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, ), ) : const Icon(Icons.send, color: Colors.white, size: 20), ), )), ], ), ), ); 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(); // Real WhatsApp voice note Ogg/Opus snippet base64 const base64Audio = 'T2dnUwACAAAAAAAAAABkAAAAAAAAADI5MFABE09wdXNIZWFkAQE4AYA+AAAAAABPZ2dTAAAAAAAAAAAAAGQAAAABAAAAWxHrFgEYT3B1c1RhZ3MIAAAAV2hhdHNBcHAAAAAAT2dnUwAAuFIBAAAAAABkAAAAAgAAAMW1RVAcs/8S/xf/C/8W/1X/K/9E/xn/HNH/Dv8P/z3/PEuGBwgTMC0L5ME27MWAB8lyJ+FE6lCAAoCJwmN8nmEoWpnN+vTMmxKRivTjVzyKgC8kq+xU2t9BmYsnP6PiOVb9FSBIclbkE+UQqmpijsWqPKSgqfrb/axQjKz+XqwPUt2yyxIoWNB7gp/NUv8QB8AEzwy9Jb9ZFBPoQ8UljPRzhbjRp8YCjZxOxxP5eLIUrPxlftPv1tu98HUPVsf7zjtZczAbrMtZ7S8RP/BBveWrUZRAS4YvLiwpK45K82R2giPnAouP77D0aXkd3aEek/leJE7lRwH4oHyI0kPXsUbT9kNKi6g7c3SAjqK2HFw8qYXpIaL'; ctrl.sendMediaMessage( base64Audio, 'audio/ogg', 'voice_note.ogg', ); }, ), ], ), 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, ), ), ); } }