import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import '../models/message.dart'; import '../services/chat_service.dart'; import '../services/chat_config_loader.dart'; /// Page simple de chat class ChatPage extends StatefulWidget { final String roomId; final String roomTitle; const ChatPage({ super.key, required this.roomId, required this.roomTitle, }); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { final _service = ChatService.instance; final _messageController = TextEditingController(); final _scrollController = ScrollController(); bool _isLoading = true; bool _isLoadingMore = false; bool _hasMore = true; List _messages = []; String? _oldestMessageId; @override void initState() { super.initState(); _loadInitialMessages(); _service.markAsRead(widget.roomId); } Future _loadInitialMessages() async { setState(() => _isLoading = true); final result = await _service.getMessages(widget.roomId); final messages = result['messages'] as List; setState(() { _messages = messages; _hasMore = result['has_more'] as bool; if (messages.isNotEmpty) { _oldestMessageId = messages.first.id; } _isLoading = false; }); // Attendre un peu avant de scroller pour laisser le temps au ListView de se construire Future.delayed(const Duration(milliseconds: 100), _scrollToBottom); } Future _loadMoreMessages() async { if (_isLoadingMore || !_hasMore || _oldestMessageId == null) return; setState(() => _isLoadingMore = true); final result = await _service.getMessages(widget.roomId, beforeMessageId: _oldestMessageId); final newMessages = result['messages'] as List; setState(() { // Insérer les messages plus anciens au début _messages = [...newMessages, ..._messages]; _hasMore = result['has_more'] as bool; if (newMessages.isNotEmpty) { _oldestMessageId = newMessages.first.id; } _isLoadingMore = false; }); } void _scrollToBottom() { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); } } Future _sendMessage() async { final text = _messageController.text.trim(); if (text.isEmpty) return; _messageController.clear(); await _service.sendMessage(widget.roomId, text); _scrollToBottom(); } @override Widget build(BuildContext context) { // Obtenir le rôle de l'utilisateur pour la colorisation final userRole = _service.getUserRole(); // Déterminer la couleur du badge selon le rôle Color badgeColor; switch (userRole) { case 1: badgeColor = Colors.green; // Vert pour les membres break; case 2: badgeColor = Colors.blue; // Bleu pour les admins amicale break; case 9: badgeColor = Colors.red; // Rouge pour les super admins break; default: badgeColor = Colors.grey; // Gris par défaut } // Obtenir la version du module final moduleVersion = ChatConfigLoader.instance.getModuleVersion(); return Scaffold( backgroundColor: const Color(0xFFFAFAFA), appBar: AppBar( title: Text(widget.roomTitle), backgroundColor: Colors.white, foregroundColor: const Color(0xFF1E293B), elevation: 0, ), body: Stack( children: [ Column( children: [ // Messages Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : ValueListenableBuilder>( valueListenable: _service.messagesBox.listenable(), builder: (context, box, _) { // Mettre à jour la liste avec les nouveaux messages envoyés final recentMessages = box.values .where((m) => m.roomId == widget.roomId && !_messages.any((msg) => msg.id == m.id)) .toList(); // Combiner les messages chargés et les nouveaux final allMessages = [..._messages, ...recentMessages] ..sort((a, b) => a.sentAt.compareTo(b.sentAt)); if (allMessages.isEmpty) { return Center( child: Text( 'Aucun message', style: TextStyle( color: Colors.grey[600], fontSize: 16, ), ), ); } return Column( children: [ // Bouton "Charger plus" en haut if (_hasMore) Container( padding: const EdgeInsets.symmetric(vertical: 8), child: _isLoadingMore ? const SizedBox( height: 40, child: Center( child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ), ) : TextButton.icon( onPressed: _loadMoreMessages, icon: const Icon(Icons.refresh, size: 18), label: const Text('Charger plus de messages'), style: TextButton.styleFrom( foregroundColor: const Color(0xFF2563EB), ), ), ), // Liste des messages avec pull-to-refresh Expanded( child: RefreshIndicator( onRefresh: _loadInitialMessages, child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: allMessages.length, itemBuilder: (context, index) { final message = allMessages[index]; return _MessageBubble(message: message); }, ), ), ), ], ); }, ), ), // Input Container( decoration: BoxDecoration( color: Colors.white, border: Border( top: BorderSide(color: Colors.grey[200]!), ), ), padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( child: TextField( controller: _messageController, decoration: InputDecoration( hintText: 'Message...', hintStyle: TextStyle(color: Colors.grey[400]), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), maxLines: null, textInputAction: TextInputAction.send, onSubmitted: (_) => _sendMessage(), ), ), IconButton( icon: const Icon(Icons.send), color: const Color(0xFF2563EB), onPressed: _sendMessage, ), ], ), ), ], ), // Badge de version en bas à droite Positioned( bottom: 16, right: 16, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: badgeColor.withOpacity(0.9), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: badgeColor.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.chat_bubble_outline, size: 14, color: Colors.white.withOpacity(0.9), ), const SizedBox(width: 4), Text( 'v$moduleVersion', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ), ); } @override void dispose() { _messageController.dispose(); _scrollController.dispose(); super.dispose(); } } /// Widget simple pour une bulle de message class _MessageBubble extends StatelessWidget { final Message message; const _MessageBubble({required this.message}); @override Widget build(BuildContext context) { final isMe = message.isMe; return Align( alignment: isMe ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.75, ), decoration: BoxDecoration( color: isMe ? const Color(0xFFEFF6FF) : Colors.white, borderRadius: BorderRadius.circular(8), border: !isMe ? Border.all(color: Colors.grey[300]!) : null, ), child: Column( crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ if (!isMe) Text( message.senderName, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF2563EB), ), ), const SizedBox(height: 2), Text( message.content, style: const TextStyle(fontSize: 14), ), const SizedBox(height: 2), Text( _formatTime(message.sentAt), style: TextStyle( fontSize: 11, color: Colors.grey[500], ), ), ], ), ), ); } String _formatTime(DateTime date) { return '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; } }