1+ import 'dart:async' ;
12import 'dart:convert' ;
3+ import 'dart:isolate' ;
24import 'package:flutter/material.dart' ;
35import '../models/message.dart' ;
46import '../models/conversation.dart' ;
57import '../services/api.dart' ;
68import '../services/ws.dart' ;
79
8- import 'dart:async' ;
9- import 'dart:isolate' ;
10-
1110class ChatController extends ChangeNotifier {
1211 final ApiService _api = ApiService ();
1312 final WebSocketService _ws = WebSocketService ();
1413
1514 List <Conversation > inbox = [];
1615 List <Message > activeChat = [];
1716 List <dynamic > contactSearchResults = [];
17+
1818 String ? currentChatUserId;
1919 bool isPeerTyping = false ;
2020 bool isPeerOnline = false ;
2121 bool isSearchLoading = false ;
2222
2323 Future <void > initSession (String token) async {
24- _ws.disconnect ();
25-
26- inbox.clear ();
27- activeChat.clear ();
28- contactSearchResults.clear ();
29- currentChatUserId = null ;
30- isPeerTyping = false ;
31- isPeerOnline = false ;
32- notifyListeners ();
33-
3424 await _ws.connect (token);
3525 _ws.stream? .listen (
3626 (rawFrame) {
3727 try {
38- _handleIncomingWebSocketEvent (jsonDecode (rawFrame));
28+ final decoded = jsonDecode (rawFrame);
29+ if (decoded is Map <String , dynamic >) {
30+ _handleIncomingWebSocketEvent (decoded);
31+ }
3932 } catch (e) {
40- debugPrint ("WebSocket stream payload error: $e " );
33+ debugPrint ("WebSocket payload error: $e " );
4134 }
4235 },
4336 onError: (err) => debugPrint ("WS Pipeline Error: $err " ),
@@ -46,32 +39,13 @@ class ChatController extends ChangeNotifier {
4639 await loadInbox ();
4740 }
4841
49- void clearSessionData () {
50- _ws.disconnect ();
51- inbox.clear ();
52- activeChat.clear ();
53- contactSearchResults.clear ();
54- currentChatUserId = null ;
55- isPeerTyping = false ;
56- isPeerOnline = false ;
57- notifyListeners ();
58- }
59-
6042 Future <void > loadInbox () async {
6143 try {
6244 final res = await _api.getConversations ();
63- dynamic targetData;
64-
65- if (res.data is List ) {
66- targetData = res.data;
67- } else if (res.data is Map ) {
68- targetData = res.data['data' ] ?? res.data['conversations' ] ?? res.data;
69- }
45+ final targetList = _extractDataList (res.data, ['conversations' ]);
7046
71- if (targetData is List ) {
72- inbox = targetData.map ((json) => Conversation .fromJson (json)).toList ();
73- notifyListeners ();
74- }
47+ inbox = targetList.map ((json) => Conversation .fromJson (json)).toList ();
48+ notifyListeners ();
7549 } catch (e) {
7650 debugPrint ("Inbox read error: $e " );
7751 }
@@ -86,7 +60,7 @@ class ChatController extends ChangeNotifier {
8660
8761 try {
8862 final res = await _api.getChatHistory (targetUid);
89- dynamic targetData ;
63+ final targetList = _extractDataList (res.data, [ 'messages' ]) ;
9064
9165 activeChat = await Isolate .run (() {
9266 return targetList.reversed
@@ -102,12 +76,19 @@ class ChatController extends ChangeNotifier {
10276 unawaited (loadInbox ());
10377 } catch (e) {
10478 debugPrint ("Timeline tracking fail: $e " );
79+ notifyListeners ();
10580 }
106- notifyListeners ();
10781 }
10882
10983 Future <void > queryUsers (String term) async {
11084 final String cleanTerm = term.trim ();
85+
86+ if (cleanTerm.isEmpty) {
87+ contactSearchResults.clear ();
88+ notifyListeners ();
89+ return ;
90+ }
91+
11192 if (cleanTerm.length < 3 ) {
11293 contactSearchResults.clear ();
11394 notifyListeners ();
@@ -118,22 +99,10 @@ class ChatController extends ChangeNotifier {
11899 notifyListeners ();
119100
120101 try {
121- final res = await _api.searchUsers (cleanTerm);
122- if (res.data == null ) {
123- contactSearchResults = [];
124- } else if (res.data is List ) {
125- contactSearchResults = res.data;
126- } else if (res.data is Map ) {
127- if (res.data['data' ] != null && res.data['data' ] is List ) {
128- contactSearchResults = res.data['data' ];
129- } else if (res.data['users' ] != null && res.data['users' ] is List ) {
130- contactSearchResults = res.data['users' ];
131- } else {
132- contactSearchResults = [];
133- }
134- }
102+ final res = await _api.searchUsers (term);
103+ contactSearchResults = _extractDataList (res.data, ['users' ]);
135104 } catch (e) {
136- debugPrint ("User query pipeline failure: $e " );
105+ debugPrint ("User query failure: $e " );
137106 contactSearchResults.clear ();
138107 } finally {
139108 isSearchLoading = false ;
@@ -150,16 +119,15 @@ class ChatController extends ChangeNotifier {
150119 }
151120
152121 void sendTextMessage (String text) {
153- if (currentChatUserId == null || text.trim ().isEmpty) return ;
122+ final cleanContent = text.trim ();
123+ if (currentChatUserId == null || cleanContent.isEmpty) return ;
154124
155- final String clientMessageId =
156- "cli_${DateTime .now ().millisecondsSinceEpoch }" ;
157- final String targetId = currentChatUserId! ;
158- final String cleanContent = text.trim ();
125+ final targetId = currentChatUserId! ;
126+ final clientMessageId = "cli_${DateTime .now ().millisecondsSinceEpoch }" ;
159127
160128 _ws.sendChat (messageId: "" , targetId: targetId, content: cleanContent);
161129
162- final Message optimisticMsg = Message (
130+ final optimisticMsg = Message (
163131 id: clientMessageId,
164132 senderId: "me" ,
165133 receiverId: targetId,
@@ -170,6 +138,7 @@ class ChatController extends ChangeNotifier {
170138
171139 activeChat.add (optimisticMsg);
172140 notifyListeners ();
141+
173142 loadInbox ();
174143 }
175144
@@ -181,13 +150,14 @@ class ChatController extends ChangeNotifier {
181150
182151 void _handleIncomingWebSocketEvent (Map <String , dynamic > data) {
183152 final String ? type = data['type' ];
184- final String ? senderId =
185- data['sender_id' ] ?? data['sender' ] ?? data['receiver_id' ];
186-
187153 if (type == null ) return ;
188154
155+ final String ? senderId =
156+ data['sender_id' ] ?? data['sender' ] ?? data['receiver_id' ];
189157 final String ? cleanSender = senderId? .trim ().toLowerCase ();
190158 final String ? cleanCurrentChat = currentChatUserId? .trim ().toLowerCase ();
159+ final bool isCurrentChat =
160+ cleanSender != null && cleanSender == cleanCurrentChat;
191161
192162 switch (type) {
193163 case 'user_status' :
@@ -201,35 +171,54 @@ class ChatController extends ChangeNotifier {
201171 notifyListeners ();
202172 }
203173 break ;
174+
204175 case 'chat' :
205176 case 'message' :
206- if (cleanSender != null && cleanSender == cleanCurrentChat ) {
177+ if (isCurrentChat ) {
207178 activeChat.add (Message .fromJson (data));
208179 _ws.sendReadReceipt (targetId: currentChatUserId! );
209180 notifyListeners ();
210- } else {
211- loadInbox ();
212181 }
182+ loadInbox ();
213183 break ;
184+
214185 case 'typing' :
215- if (cleanSender != null && cleanSender == cleanCurrentChat ) {
186+ if (isCurrentChat ) {
216187 isPeerTyping = data['content' ] == 'true' ;
217188 notifyListeners ();
218189 }
219190 break ;
191+
220192 case 'read_receipt' :
221- if (cleanSender != null && cleanSender == cleanCurrentChat) {
193+ if (isCurrentChat) {
194+ bool updated = false ;
222195 activeChat = activeChat.map ((msg) {
223196 final String sId = msg.senderId.trim ().toLowerCase ();
224- if (sId == 'me' || sId != cleanCurrentChat) {
197+ if (! msg.isRead && (sId == 'me' || sId != cleanCurrentChat)) {
198+ updated = true ;
225199 return msg.copyWith (isRead: true );
226200 }
227201 return msg;
228202 }).toList ();
229- notifyListeners ();
230- loadInbox ();
203+
204+ if (updated) {
205+ notifyListeners ();
206+ loadInbox ();
207+ }
231208 }
232209 break ;
233210 }
234211 }
212+
213+ List <dynamic > _extractDataList (dynamic data, List <String > fallbackKeys) {
214+ if (data == null ) return [];
215+ if (data is List ) return data;
216+ if (data is Map ) {
217+ if (data['data' ] is List ) return data['data' ];
218+ for (final key in fallbackKeys) {
219+ if (data[key] is List ) return data[key];
220+ }
221+ }
222+ return [];
223+ }
235224}
0 commit comments