1+ import 'dart:async' ;
2+ import 'package:flutter/material.dart' ;
3+ import 'package:hushnet_frontend/data/node/sessions/create_session.dart' ;
4+ import 'package:hushnet_frontend/models/chat_view.dart' ;
5+ import 'package:hushnet_frontend/models/message_view.dart' ;
6+ import 'package:hushnet_frontend/services/key_provider.dart' ;
7+ import 'package:hushnet_frontend/services/message_service.dart' ;
8+ import 'package:hushnet_frontend/services/node_service.dart' ;
9+
10+ class ChatViewScreen extends StatefulWidget {
11+ final String chatId;
12+ final String displayName;
13+ final bool embedded; // 👈 si affiché à droite (desktop)
14+ final ChatView chatView;
15+
16+ const ChatViewScreen ({
17+ super .key,
18+ required this .chatId,
19+ required this .displayName,
20+ this .embedded = false , required this .chatView,
21+ });
22+
23+ @override
24+ State <ChatViewScreen > createState () => _ChatViewScreenState ();
25+ }
26+
27+ class _ChatViewScreenState extends State <ChatViewScreen > {
28+ final MessageService messageService = MessageService ();
29+ final TextEditingController _controller = TextEditingController ();
30+ List <MessageView > _messages = [];
31+ bool _loading = true ;
32+ late Timer _refreshTimer;
33+ final NodeService _nodeService = NodeService ();
34+ String ? _currentUserId;
35+
36+ @override
37+ void initState () {
38+ super .initState ();
39+ _nodeService.getCurrentUserId ().then ((id) {
40+ setState (() {
41+ _currentUserId = id;
42+ });
43+ });
44+ _loadMessages ();
45+ // auto refresh toutes les 10 secondes
46+ _refreshTimer = Timer .periodic (const Duration (seconds: 10 ), (_) {
47+ _loadMessages ();
48+ });
49+ }
50+
51+ @override
52+ void dispose () {
53+ _refreshTimer.cancel ();
54+ _controller.dispose ();
55+ super .dispose ();
56+ }
57+
58+ Future <void > _loadMessages () async {
59+ try {
60+ setState (() => _loading = true );
61+ final all = await messageService.getAllMessagesForChat (widget.chatId);
62+ // 🕒 tri croissant (vieux → récents)
63+ DateTime normalize (DateTime d) {
64+ final s = d.toIso8601String ();
65+ print ("normalizing date string: $s " );
66+ // Si la date n'a pas de "Z" ni d'offset, on la traite comme locale et on force en UTC
67+ if (! s.endsWith ('Z' ) && ! s.contains ('+' )) {
68+ print ("manque zone info, normalizing to UTC for $s " );
69+ final res = DateTime .utc (
70+ d.year,
71+ d.month,
72+ d.day,
73+ d.hour,
74+ d.minute,
75+ d.second,
76+ d.millisecond,
77+ d.microsecond,
78+ ).toUtc ();
79+ print ("normalized date: ${res .toIso8601String ()}" );
80+ return res;
81+ }
82+ return d.toUtc ();
83+ }
84+ for (final msg in all) {
85+ msg.createdAt = normalize (msg.createdAt);
86+ }
87+ all.sort ((a, b) => a.createdAt.compareTo (b.createdAt));
88+ for (final msg in all) {
89+ print ('Message from ${msg .fromUserId }: ${msg .ciphertext } at ${msg .createdAt }' );
90+ }
91+
92+ setState (() {
93+ _messages = all;
94+ _loading = false ;
95+ });
96+ } catch (e) {
97+ debugPrint ("Error loading messages: $e " );
98+ setState (() => _loading = false );
99+ }
100+ }
101+
102+ Future <void > _sendMessage () async {
103+ final text = _controller.text.trim ();
104+ if (text.isEmpty || _currentUserId == null ) return ;
105+
106+ try {
107+ final keyProvider = KeyProvider ();
108+
109+ // 1️⃣ Identifier le destinataire
110+ final recipientUserId = widget.chatView.partnerUserId! ;
111+
112+ // 2️⃣ Récupérer les devices actifs du destinataire
113+ final devices = await keyProvider.getUserDevicesKeys (recipientUserId);
114+ if (devices.isEmpty) {
115+ debugPrint ('No devices for recipient' );
116+ return ;
117+ }
118+
119+ // 5️⃣ Envoi du message
120+ await messageService.sendMessage (
121+ chatId: widget.chatId,
122+ plaintext: text,
123+ recipientUserId: recipientUserId,
124+ recipientDeviceIds: devices.map ((d) => d.deviceId).toList (),
125+ );
126+
127+ // 6️⃣ Reset input et refresh UI
128+ _controller.clear ();
129+ await _loadMessages ();
130+
131+ } catch (e) {
132+ debugPrint ("❌ Error sending message: $e " );
133+ }
134+ }
135+
136+ @override
137+ Widget build (BuildContext context) {
138+ final isDesktop = MediaQuery .of (context).size.width > 800 ;
139+
140+ final chatBody = Column (
141+ children: [
142+ if (! widget.embedded)
143+ AppBar (
144+ backgroundColor: const Color (0xFF1C1C1C ),
145+ title: Text (widget.displayName),
146+ actions: [
147+ IconButton (
148+ icon: const Icon (Icons .refresh, color: Colors .greenAccent),
149+ onPressed: _loadMessages,
150+ ),
151+ ],
152+ ),
153+ Expanded (
154+ child: _loading
155+ ? const Center (
156+ child:
157+ CircularProgressIndicator (color: Colors .greenAccent))
158+ : _messages.isEmpty
159+ ? const Center (
160+ child: Text ("No messages yet 💬" ,
161+ style: TextStyle (color: Colors .grey)),
162+ )
163+ : ListView .builder (
164+ reverse: false ,
165+ padding: const EdgeInsets .all (12 ),
166+ itemCount: _messages.length,
167+ itemBuilder: (context, index) {
168+ final msg = _messages[index];
169+ final isMe = msg.fromUserId == _currentUserId;
170+ return Align (
171+ alignment: isMe
172+ ? Alignment .centerRight
173+ : Alignment .centerLeft,
174+ child: Container (
175+ margin: const EdgeInsets .symmetric (vertical: 4 ),
176+ padding: const EdgeInsets .symmetric (
177+ horizontal: 14 , vertical: 10 ),
178+ decoration: BoxDecoration (
179+ color: isMe
180+ ? Colors .greenAccent
181+ : const Color (0xFF2A2A2A ),
182+ borderRadius: BorderRadius .circular (16 ),
183+ ),
184+ child: Text (
185+ msg.ciphertext,
186+ style: TextStyle (
187+ color: isMe ? Colors .black : Colors .white,
188+ ),
189+ ),
190+ ),
191+ );
192+ },
193+ ),
194+ ),
195+ Container (
196+ padding:
197+ const EdgeInsets .symmetric (horizontal: 16 , vertical: 10 ),
198+ decoration: const BoxDecoration (
199+ color: Color (0xFF1C1C1C ),
200+ border: Border (
201+ top: BorderSide (color: Color (0xFF2F2F2F ), width: 0.5 )),
202+ ),
203+ child: Row (
204+ children: [
205+ Expanded (
206+ child: TextField (
207+ controller: _controller,
208+ style: const TextStyle (color: Colors .white),
209+ decoration: InputDecoration (
210+ hintText: "Type a message..." ,
211+ hintStyle: TextStyle (color: Colors .grey[500 ]),
212+ border: InputBorder .none,
213+ ),
214+ ),
215+ ),
216+ IconButton (
217+ icon: const Icon (Icons .send, color: Colors .greenAccent),
218+ onPressed: _sendMessage,
219+ ),
220+ ],
221+ ),
222+ ),
223+ ],
224+ );
225+
226+ if (widget.embedded) {
227+ return Container (
228+ color: const Color (0xFF101010 ),
229+ child: chatBody,
230+ );
231+ }
232+
233+ return Scaffold (
234+ backgroundColor: const Color (0xFF101010 ),
235+ body: SafeArea (child: chatBody),
236+ );
237+ }
238+ }
0 commit comments