Skip to content

Commit 9fcdd93

Browse files
committed
feat: Enhance session management by adding device check and updating chat view with message info
1 parent 5e45db9 commit 9fcdd93

4 files changed

Lines changed: 227 additions & 100 deletions

File tree

lib/data/node/sessions/create_session.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import 'package:cryptography/cryptography.dart';
44
import 'package:dio/dio.dart';
55
import 'package:flutter/material.dart';
66
import 'package:hushnet_frontend/services/key_provider.dart';
7+
import 'package:hushnet_frontend/services/node_service.dart';
78

89
/// Full X3DH + initial AES-GCM encrypt for each recipient device, then POST /sessions
910
Future<bool> createSession(String nodeUrl, String recipientUserId) async {
1011
final keyProvider = KeyProvider();
1112
final dio = Dio();
13+
final NodeService nodeService = NodeService();
14+
final String currentDeviceId = await nodeService.getCurrentDeviceId() ?? '';
1215

1316
try {
1417
// 1) load identity (Ed25519) and X25519 preKey (we use preKey as IK for DH)
@@ -33,6 +36,10 @@ Future<bool> createSession(String nodeUrl, String recipientUserId) async {
3336

3437
// Loop on each device
3538
for (final device in devices) {
39+
if (device.deviceId == currentDeviceId) {
40+
// skip our own device
41+
continue;
42+
}
3643
// Parse recipient pubs from base64 -> bytes
3744
final recipientIdentityPub = base64Decode(device.prekeyPubkey); // must be X25519 bytes
3845
final recipientSpkPub = base64Decode(device.signedPrekeyPub);

lib/screens/chat_view_screen.dart

Lines changed: 199 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ class ChatViewScreen extends StatefulWidget {
1717
super.key,
1818
required this.chatId,
1919
required this.displayName,
20-
this.embedded = false, required this.chatView,
20+
this.embedded = false,
21+
required this.chatView,
2122
});
2223

2324
@override
@@ -55,86 +56,160 @@ class _ChatViewScreenState extends State<ChatViewScreen> {
5556
super.dispose();
5657
}
5758

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-
}
59+
Future<void> _loadMessages() async {
60+
try {
61+
setState(() => _loading = true);
62+
final all = await messageService.getAllMessagesForChat(widget.chatId);
63+
// 🕒 tri croissant (vieux → récents)
64+
DateTime normalize(DateTime d) {
65+
final s = d.toIso8601String();
66+
print("normalizing date string: $s");
67+
// Si la date n'a pas de "Z" ni d'offset, on la traite comme locale et on force en UTC
68+
if (!s.endsWith('Z') && !s.contains('+')) {
69+
print("manque zone info, normalizing to UTC for $s");
70+
final res = DateTime.utc(
71+
d.year,
72+
d.month,
73+
d.day,
74+
d.hour,
75+
d.minute,
76+
d.second,
77+
d.millisecond,
78+
d.microsecond,
79+
).toUtc();
80+
print("normalized date: ${res.toIso8601String()}");
81+
return res;
82+
}
83+
return d.toUtc();
84+
}
9185

92-
setState(() {
93-
_messages = all;
94-
_loading = false;
95-
});
96-
} catch (e) {
97-
debugPrint("Error loading messages: $e");
98-
setState(() => _loading = false);
99-
}
100-
}
86+
for (final msg in all) {
87+
msg.createdAt = normalize(msg.createdAt);
88+
}
89+
all.sort((a, b) => a.createdAt.compareTo(b.createdAt));
90+
for (final msg in all) {
91+
print(
92+
'Message from ${msg.fromUserId}: ${msg.ciphertext} at ${msg.createdAt}',
93+
);
94+
}
10195

102-
Future<void> _sendMessage() async {
103-
final text = _controller.text.trim();
104-
if (text.isEmpty || _currentUserId == null) return;
96+
setState(() {
97+
_messages = all;
98+
_loading = false;
99+
});
100+
} catch (e) {
101+
debugPrint("Error loading messages: $e");
102+
setState(() => _loading = false);
103+
}
104+
}
105105

106-
try {
107-
final keyProvider = KeyProvider();
106+
Future<void> _sendMessage() async {
107+
final text = _controller.text.trim();
108+
if (text.isEmpty || _currentUserId == null) return;
108109

109-
// 1️⃣ Identifier le destinataire
110-
final recipientUserId = widget.chatView.partnerUserId!;
110+
try {
111+
final keyProvider = KeyProvider();
111112

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-
}
113+
// 1️⃣ Identifier le destinataire
114+
final recipientUserId = widget.chatView.partnerUserId!;
118115

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-
);
116+
// 2️⃣ Récupérer les devices actifs du destinataire
117+
final devices = await keyProvider.getUserDevicesKeys(recipientUserId);
118+
if (devices.isEmpty) {
119+
debugPrint('No devices for recipient');
120+
return;
121+
}
126122

127-
// 6️⃣ Reset input et refresh UI
128-
_controller.clear();
129-
await _loadMessages();
123+
// 5️⃣ Envoi du message
124+
await messageService.sendMessage(
125+
chatId: widget.chatId,
126+
plaintext: text,
127+
recipientUserId: recipientUserId,
128+
recipientDeviceIds: devices.map((d) => d.deviceId).toList(),
129+
);
130130

131-
} catch (e) {
132-
debugPrint("❌ Error sending message: $e");
131+
// 6️⃣ Reset input et refresh UI
132+
_controller.clear();
133+
await _loadMessages();
134+
} catch (e) {
135+
debugPrint("❌ Error sending message: $e");
136+
}
133137
}
134-
}
135138

136139
@override
137140
Widget build(BuildContext context) {
141+
Widget _infoRow(String title, String value) {
142+
return Padding(
143+
padding: const EdgeInsets.symmetric(vertical: 4),
144+
child: Row(
145+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
146+
children: [
147+
Text(
148+
title,
149+
style: const TextStyle(color: Colors.grey, fontSize: 13),
150+
),
151+
Flexible(
152+
child: Text(
153+
value,
154+
textAlign: TextAlign.right,
155+
style: const TextStyle(color: Colors.white, fontSize: 13),
156+
),
157+
),
158+
],
159+
),
160+
);
161+
}
162+
163+
void _showMessageInfo(BuildContext context, MessageView msg) {
164+
showModalBottomSheet(
165+
context: context,
166+
backgroundColor: const Color(0xFF1C1C1C),
167+
shape: const RoundedRectangleBorder(
168+
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
169+
),
170+
builder: (context) {
171+
return Padding(
172+
padding: const EdgeInsets.all(16),
173+
child: Column(
174+
mainAxisSize: MainAxisSize.min,
175+
crossAxisAlignment: CrossAxisAlignment.start,
176+
children: [
177+
const Text(
178+
"🔒 Message Encryption Info",
179+
style: TextStyle(
180+
fontSize: 16,
181+
fontWeight: FontWeight.bold,
182+
color: Colors.greenAccent,
183+
),
184+
),
185+
const SizedBox(height: 12),
186+
_infoRow("Message ID", msg.id ?? "unknown"),
187+
_infoRow("From", msg.fromUserId ?? "unknown"),
188+
_infoRow("Created at", msg.createdAt.toIso8601String()),
189+
const Divider(color: Colors.grey),
190+
const SizedBox(height: 6),
191+
_infoRow("Algorithm", "AES-256-GCM"),
192+
_infoRow("Key Exchange", "X3DH + Double Ratchet"),
193+
_infoRow("Ciphertext Length", "${msg.ciphertext.length} bytes"),
194+
_infoRow("Session ID", widget.chatId),
195+
const SizedBox(height: 12),
196+
Center(
197+
child: TextButton.icon(
198+
onPressed: () => Navigator.pop(context),
199+
icon: const Icon(Icons.close, color: Colors.grey),
200+
label: const Text(
201+
"Close",
202+
style: TextStyle(color: Colors.grey),
203+
),
204+
),
205+
),
206+
],
207+
),
208+
);
209+
},
210+
);
211+
}
212+
138213
final isDesktop = MediaQuery.of(context).size.width > 800;
139214

140215
final chatBody = Column(
@@ -153,28 +228,43 @@ Future<void> _sendMessage() async {
153228
Expanded(
154229
child: _loading
155230
? const Center(
156-
child:
157-
CircularProgressIndicator(color: Colors.greenAccent))
231+
child: CircularProgressIndicator(color: Colors.greenAccent),
232+
)
158233
: _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),
234+
? const Center(
235+
child: Text(
236+
"No messages yet 💬",
237+
style: TextStyle(color: Colors.grey),
238+
),
239+
)
240+
: ListView.builder(
241+
reverse: false,
242+
padding: const EdgeInsets.all(12),
243+
itemCount: _messages.length,
244+
itemBuilder: (context, index) {
245+
final msg = _messages[index];
246+
final isMe = msg.fromUserId == _currentUserId;
247+
248+
return Align(
249+
alignment: isMe
250+
? Alignment.centerRight
251+
: Alignment.centerLeft,
252+
child: Row(
253+
mainAxisSize: MainAxisSize.min,
254+
crossAxisAlignment: CrossAxisAlignment.end,
255+
textDirection: isMe
256+
? TextDirection.rtl
257+
: TextDirection.ltr, // 🔁 pour aligner correctement
258+
children: [
259+
Container(
260+
margin: const EdgeInsets.symmetric(
261+
vertical: 4,
262+
horizontal: 4,
263+
),
176264
padding: const EdgeInsets.symmetric(
177-
horizontal: 14, vertical: 10),
265+
horizontal: 14,
266+
vertical: 10,
267+
),
178268
decoration: BoxDecoration(
179269
color: isMe
180270
? Colors.greenAccent
@@ -188,17 +278,29 @@ Future<void> _sendMessage() async {
188278
),
189279
),
190280
),
191-
);
192-
},
193-
),
281+
IconButton(
282+
icon: Icon(
283+
Icons.info_outline,
284+
color: isMe
285+
? Colors.greenAccent
286+
: Colors.grey[400],
287+
size: 18,
288+
),
289+
onPressed: () => _showMessageInfo(context, msg),
290+
),
291+
],
292+
),
293+
);
294+
},
295+
),
194296
),
195297
Container(
196-
padding:
197-
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
298+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
198299
decoration: const BoxDecoration(
199300
color: Color(0xFF1C1C1C),
200301
border: Border(
201-
top: BorderSide(color: Color(0xFF2F2F2F), width: 0.5)),
302+
top: BorderSide(color: Color(0xFF2F2F2F), width: 0.5),
303+
),
202304
),
203305
child: Row(
204306
children: [
@@ -224,15 +326,12 @@ Future<void> _sendMessage() async {
224326
);
225327

226328
if (widget.embedded) {
227-
return Container(
228-
color: const Color(0xFF101010),
229-
child: chatBody,
230-
);
329+
return Container(color: const Color(0xFF101010), child: chatBody);
231330
}
232331

233332
return Scaffold(
234333
backgroundColor: const Color(0xFF101010),
235334
body: SafeArea(child: chatBody),
236335
);
237336
}
238-
}
337+
}

0 commit comments

Comments
 (0)