import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'dart:io'; import 'package:file_selector/file_selector.dart'; import '../services/chat_service.dart'; import '../services/reverb_socket_service.dart'; import '../services/dio_client.dart'; import '../providers/chat_unread_provider.dart'; import '../widgets/chat_file_preview.dart'; import 'chat_file_viewer.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(); Map? uploadingMessage; late ChatService _chatService; final ReverbSocketService _socket = ReverbSocketService(); int? ticketId; List> messages = []; bool isLoading = true; @override void initState() { super.initState(); _chatService = ChatService(DioClient.getInstance(context)); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().setChatOpen(true); }); _initChat(); } String _guessMimeType(String path) { final lower = path.toLowerCase(); if (lower.endsWith('.jpg') || lower.endsWith('.png')) return 'image/*'; if (lower.endsWith('.mp4')) return 'video/*'; if (lower.endsWith('.pdf')) return 'application/pdf'; return 'application/octet-stream'; } Future _pickAndSendFile() async { final XFile? picked = await openFile(); if (picked == null || ticketId == null) return; final file = File(picked.path); setState(() { uploadingMessage = { 'local_file': file, 'file_type': _guessMimeType(file.path), 'progress': 0.0, }; }); await _chatService.sendFile( ticketId!, file, onProgress: (progress) { if (!mounted) return; setState(() => uploadingMessage!['progress'] = progress); }, ); if (!mounted) return; setState(() => uploadingMessage = null); } Future _initChat() async { final ticketRes = await _chatService.startChat(); ticketId = ticketRes['ticket']['id']; final msgs = await _chatService.getMessages(ticketId!); messages = List>.from(msgs); await _socket.connect( context: context, ticketId: ticketId!, onMessage: (msg) { final incomingClientId = msg['client_id']; setState(() { messages.removeWhere((m) => m['client_id'] == incomingClientId); messages.add(msg); }); _scrollToBottom(); }, onAdminMessage: () { if (!mounted) { context.read().increment(); } }, ); if (!mounted) return; setState(() => isLoading = false); _scrollToBottom(); } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollCtrl.hasClients) { _scrollCtrl.animateTo( _scrollCtrl.position.maxScrollExtent, duration: const Duration(milliseconds: 250), curve: Curves.easeOut, ); } }); } Future _sendMessage() async { final text = _messageCtrl.text.trim(); if (text.isEmpty || ticketId == null) return; _messageCtrl.clear(); final clientId = DateTime.now().millisecondsSinceEpoch.toString(); setState(() { messages.add({ 'client_id': clientId, 'sender_type': 'App\\Models\\User', 'message': text, 'file_path': null, 'file_type': null, 'sending': true, }); }); _scrollToBottom(); await _chatService.sendMessage( ticketId!, message: text, clientId: clientId, ); } @override void dispose() { context.read().setChatOpen(false); _socket.disconnect(); _messageCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(56), child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, decoration: const BoxDecoration( gradient: LinearGradient( colors: [ Color(0xFF2196F3), Color(0xFF1565C0), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 10, offset: Offset(0, 3), ), ], ), child: AppBar( backgroundColor: Colors.transparent, elevation: 0, leading: Padding( padding: const EdgeInsets.only(left: 12), child: CircleAvatar( radius: 18, backgroundColor: Colors.white, child: Icon( Icons.support_agent, color: Colors.blue, size: 20, ), ), ), title: const Text( "Support Chat", style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 18, ), ), centerTitle: false, iconTheme: const IconThemeData(color: Colors.white), ), ), ), body: isLoading ? const Center(child: CircularProgressIndicator()) : Column( children: [ Expanded(child: _buildMessages()), _buildInput(), ], ), ); } Widget _buildMessages() { return ListView( controller: _scrollCtrl, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), children: [ ...messages.map((msg) { final isUser = msg['sender_type'] == 'App\\Models\\User'; return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( constraints: const BoxConstraints(maxWidth: 280), margin: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: isUser ? null : Colors.white, gradient: isUser ? LinearGradient( colors: [ Colors.lightBlueAccent, Colors.blue.shade700, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, borderRadius: BorderRadius.only( topLeft: const Radius.circular(16), topRight: const Radius.circular(16), bottomLeft: isUser ? const Radius.circular(16) : Radius.zero, bottomRight: isUser ? Radius.zero : const Radius.circular(16), ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, ), ], ), child: msg['file_path'] == null ? Text( msg['message'] ?? '', style: TextStyle( color: isUser ? Colors.white : Colors.blue, fontSize: 15, ), ) : ChatFilePreview( filePath: msg['file_path'], fileType: msg['file_type'] ?? '', isUser: isUser, ), ), ); }), ], ); } Widget _buildInput() { return SafeArea( child: Padding( padding: const EdgeInsets.all(8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, ), ], ), child: Row( children: [ IconButton( icon: const Icon(Icons.attach_file), onPressed: _pickAndSendFile, ), Expanded( child: TextField( controller: _messageCtrl, decoration: const InputDecoration( hintText: "Type a message…", border: InputBorder.none, ), ), ), IconButton( icon: const Icon(Icons.send, color: Colors.blue), onPressed: _sendMessage, ), ], ), ), ), ); } }