import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../services/whatsapp_service.dart'; import '../models/conversation_model.dart'; import '../models/message_model.dart'; class ChatController extends GetxController { final ConversationModel conversation; final WhatsAppService _svc = Get.find(); final messages = [].obs; final isLoading = false.obs; final isSending = false.obs; final inputCtrl = TextEditingController(); final scrollCtrl = ScrollController(); StreamSubscription? _eventSub; ChatController({required this.conversation}); @override void onInit() { super.onInit(); _svc.activeChatId.value = conversation.id; loadMessages(); markAsRead(); // Listen to push events for new messages and message delivery updates _eventSub = _svc.events.listen(_onPushEvent); } @override void onClose() { if (_svc.activeChatId.value == conversation.id) { _svc.activeChatId.value = null; } _eventSub?.cancel(); inputCtrl.dispose(); scrollCtrl.dispose(); super.onClose(); } // ── Load Messages ──────────────────────────────────────────────────────── Future loadMessages() async { isLoading.value = true; try { final res = await _svc.getMessages(conversation.id); if (res['type'] == 'messages') { final List data = res['data'] ?? []; final fetched = data.map((m) => MessageModel.fromJson(m as Map)).toList(); // Sort chronologically (oldest to newest) fetched.sort((a, b) => a.timestamp.compareTo(b.timestamp)); messages.assignAll(fetched); // Scroll to bottom after list is rendered _scrollToBottom(); } } catch (e) { print('[LOAD MESSAGES ERROR] $e'); } finally { isLoading.value = false; } } // ── Send Message ───────────────────────────────────────────────────────── Future sendMessage() async { final text = inputCtrl.text.trim(); if (text.isEmpty || isSending.value) return; isSending.value = true; inputCtrl.clear(); try { final res = await _svc.sendMessage(conversation.id, text); if (res['type'] == 'message_sent') { final sentMsg = MessageModel.fromJson(res['data'] as Map); messages.add(sentMsg); _scrollToBottom(); } } catch (e) { print('[SEND MESSAGE ERROR] $e'); Get.snackbar('Error', 'Failed to send message: $e', backgroundColor: Colors.redAccent.withOpacity(0.8), colorText: Colors.white, ); } finally { isSending.value = false; } } // ── Mark Chat as Read ──────────────────────────────────────────────────── Future markAsRead() async { try { await _svc.markRead(conversation.id); } catch (e) { print('[MARK READ ERROR] $e'); } } // ── Push Event Handler ─────────────────────────────────────────────────── void _onPushEvent(Map event) { final type = event['type'] as String?; if (type == null) return; switch (type) { case 'new_message': final chatId = event['chatId'] as String?; final msgData = event['data'] as Map?; if (chatId == null || msgData == null) return; // If the new message is for this chat if (chatId == conversation.id) { final newMsg = MessageModel.fromJson(msgData); // Prevent duplicates just in case if (!messages.any((m) => m.id == newMsg.id)) { messages.add(newMsg); _scrollToBottom(); markAsRead(); // Mark as read since user is actively viewing } } break; case 'message_ack': final messageId = event['messageId'] as String?; final chatId = event['chatId'] as String?; final ack = event['ack'] as int?; if (chatId == null || messageId == null || ack == null) return; if (chatId == conversation.id) { final index = messages.indexWhere((m) => m.id == messageId); if (index != -1) { messages[index] = messages[index].copyWith(ack: ack); } } break; } } // ── Helper: Scroll to Bottom ───────────────────────────────────────────── void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (scrollCtrl.hasClients) { scrollCtrl.animateTo( scrollCtrl.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } // ── Date Separator Logic ───────────────────────────────────────────────── List get groupedMessages { final list = []; if (messages.isEmpty) return list; String? lastDate; for (final msg in messages) { final date = _formatDateSeparator(msg.timestamp); if (date != lastDate) { list.add(date); lastDate = date; } list.add(msg); } return list; } String _formatDateSeparator(int timestamp) { final dt = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final yesterday = today.subtract(const Duration(days: 1)); final msgDate = DateTime(dt.year, dt.month, dt.day); if (msgDate == today) { return 'Today'; } else if (msgDate == yesterday) { return 'Yesterday'; } else { return DateFormat('MMMM d, yyyy').format(dt); } } }