import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/chat_service.dart'; import '../services/reverb_socket_service.dart'; import '../services/dio_client.dart'; import '../providers/chat_unread_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'chat_file_viewer.dart'; import '../widgets/chat_file_preview.dart'; class ChatScreen extends StatefulWidget { const ChatScreen({super.key}); @override State createState() => _ChatScreenState(); } class _ChatScreenState extends State { final TextEditingController _messageCtrl = TextEditingController(); final ScrollController _scrollCtrl = ScrollController(); late ChatService _chatService; final ReverbSocketService _socket = ReverbSocketService(); int? ticketId; List> messages = []; bool isLoading = true; // ============================ // INIT STATE // ============================ @override void initState() { super.initState(); _chatService = ChatService(DioClient.getInstance(context)); // 🔔 Mark chat as OPEN (important) WidgetsBinding.instance.addPostFrameCallback((_) { context.read().setChatOpen(true); }); _initChat(); } Future _pickAndSendFile() async { final XFile? file = await openFile(); if (file == null || ticketId == null) return; final dartFile = File(file.path); await _chatService.sendFile(ticketId!, dartFile); } // ============================ // INIT CHAT // ============================ Future _initChat() async { // 1️⃣ Start chat final ticketRes = await _chatService.startChat(); ticketId = ticketRes['ticket']['id']; // 2️⃣ Load messages final msgs = await _chatService.getMessages(ticketId!); messages = List>.from(msgs); // 3️⃣ Realtime socket await _socket.connect( context: context, ticketId: ticketId!, onMessage: (msg) { if (!mounted) return; setState(() => messages.add(msg)); _scrollToBottom(); }, onAdminMessage: () { if (!mounted) { context.read().increment(); } }, ); if (!mounted) return; setState(() => isLoading = false); _scrollToBottom(); } // ============================ // SCROLL // ============================ void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollCtrl.hasClients) { _scrollCtrl.jumpTo(_scrollCtrl.position.maxScrollExtent); } }); } // ============================ // SEND MESSAGE // ============================ Future _sendMessage() async { final text = _messageCtrl.text.trim(); if (text.isEmpty) return; _messageCtrl.clear(); await _chatService.sendMessage( ticketId!, message: text, ); } // ============================ // DISPOSE // ============================ @override void dispose() { // 🔕 Mark chat CLOSED context.read().setChatOpen(false); _socket.disconnect(); _messageCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } // ============================ // UI // ============================ @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Support Chat")), body: isLoading ? const Center(child: CircularProgressIndicator()) : Column( children: [ Expanded(child: _buildMessages()), _buildInput(), ], ), ); } // Future _openUrl(String url) async { // final uri = Uri.parse(url); // // if (await canLaunchUrl(uri)) { // await launchUrl( // uri, // mode: LaunchMode.externalApplication, // ); // } else { // debugPrint("❌ Cannot launch URL: $url"); // } // } Widget _buildMessageContent({ String? message, String? filePath, String? fileType, required bool isUser, }) { final textColor = isUser ? Colors.white : Colors.black; if (filePath == null) { return Text(message ?? '', style: TextStyle(color: textColor)); } final url = "${DioClient.baseUrl}/storage/$filePath"; return GestureDetector( onTap: () { ChatFileViewer.open( context, url: url, fileType: fileType ?? '', ); }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(_iconForFile(fileType), color: textColor), const SizedBox(width: 8), Text( _labelForFile(fileType), style: TextStyle(color: textColor), ), ], ), ); } IconData _iconForFile(String? type) { if (type == null) return Icons.insert_drive_file; if (type.startsWith('image/')) return Icons.image; if (type.startsWith('video/')) return Icons.play_circle_fill; if (type == 'application/pdf') return Icons.picture_as_pdf; return Icons.insert_drive_file; } String _labelForFile(String? type) { if (type == null) return "File"; if (type.startsWith('image/')) return "Image"; if (type.startsWith('video/')) return "Video"; if (type == 'application/pdf') return "PDF"; return "Download file"; } Widget _buildMessages() { return ListView.builder( controller: _scrollCtrl, padding: const EdgeInsets.all(12), itemCount: messages.length, itemBuilder: (_, index) { final msg = messages[index]; final isUser = msg['sender_type'] == 'App\\Models\\User'; final String? filePath = msg['file_path']; final String? fileType = msg['file_type']; final String? message = msg['message']; return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: isUser ? Colors.blue : Colors.grey.shade300, borderRadius: BorderRadius.circular(12), ), // 🔽 ONLY THIS PART CHANGED child: filePath == null ? Text( message ?? '', style: TextStyle( color: isUser ? Colors.white : Colors.black, ), ) : ChatFilePreview( filePath: filePath, fileType: fileType ?? '', isUser: isUser, ), ), ); }, ); } Widget _buildInput() { return SafeArea( child: Row( children: [ IconButton( icon: const Icon(Icons.attach_file), onPressed: _pickAndSendFile, ), Expanded( child: TextField( controller: _messageCtrl, decoration: const InputDecoration( hintText: "Type message", border: InputBorder.none, ), ), ), IconButton( icon: const Icon(Icons.send), onPressed: _sendMessage, ), ], ), ); } }