Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 59 additions & 60 deletions mobile/lib/controllers/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ChatController extends ChangeNotifier {
List<Conversation> inbox = [];
List<Message> activeChat = [];
List<dynamic> contactSearchResults = [];

String? currentChatUserId;
bool isPeerTyping = false;
bool isPeerOnline = false;
Expand All @@ -22,9 +23,12 @@ class ChatController extends ChangeNotifier {
_ws.stream?.listen(
(rawFrame) {
try {
_handleIncomingWebSocketEvent(jsonDecode(rawFrame));
final decoded = jsonDecode(rawFrame);
if (decoded is Map<String, dynamic>) {
_handleIncomingWebSocketEvent(decoded);
}
} catch (e) {
debugPrint("WebSocket stream payload error: $e");
debugPrint("WebSocket payload error: $e");
}
},
onError: (err) => debugPrint("WS Pipeline Error: $err"),
Expand All @@ -36,18 +40,10 @@ class ChatController extends ChangeNotifier {
Future<void> loadInbox() async {
try {
final res = await _api.getConversations();
dynamic targetData;

if (res.data is List) {
targetData = res.data;
} else if (res.data is Map) {
targetData = res.data['data'] ?? res.data['conversations'] ?? res.data;
}
final targetList = _extractDataList(res.data, ['conversations']);

if (targetData is List) {
inbox = targetData.map((json) => Conversation.fromJson(json)).toList();
notifyListeners();
}
inbox = targetList.map((json) => Conversation.fromJson(json)).toList();
notifyListeners();
} catch (e) {
debugPrint("Inbox read error: $e");
}
Expand All @@ -62,29 +58,23 @@ class ChatController extends ChangeNotifier {

try {
final res = await _api.getChatHistory(targetUid);
dynamic targetData;

if (res.data is List) {
targetData = res.data;
} else if (res.data is Map) {
targetData = res.data['data'] ?? res.data['messages'] ?? res.data;
}
final targetList = _extractDataList(res.data, ['messages']);

if (targetData is List) {
activeChat = targetData
.map((json) => Message.fromJson(json))
.toList()
.reversed
.toList();
}
activeChat = targetList
.map((json) => Message.fromJson(json))
.toList()
.reversed
.toList();

_ws.sendReadReceipt(targetId: targetUid);
_ws.sendRequestStatus(targetId: targetUid);

notifyListeners();
await loadInbox();
} catch (e) {
debugPrint("Timeline tracking fail: $e");
notifyListeners();
}
notifyListeners();
}

Future<void> queryUsers(String term) async {
Expand All @@ -101,26 +91,15 @@ class ChatController extends ChangeNotifier {
notifyListeners();
return;
}

isSearchLoading = true;
notifyListeners();

try {
final res = await _api.searchUsers(cleanTerm);
if (res.data == null) {
contactSearchResults = [];
} else if (res.data is List) {
contactSearchResults = res.data;
} else if (res.data is Map) {
if (res.data['data'] != null && res.data['data'] is List) {
contactSearchResults = res.data['data'];
} else if (res.data['users'] != null && res.data['users'] is List) {
contactSearchResults = res.data['users'];
} else {
contactSearchResults = [];
}
}
final res = await _api.searchUsers(term);
contactSearchResults = _extractDataList(res.data, ['users']);
} catch (e) {
debugPrint("User query pipeline failure: $e");
debugPrint("User query failure: $e");
contactSearchResults.clear();
} finally {
isSearchLoading = false;
Expand All @@ -137,16 +116,15 @@ class ChatController extends ChangeNotifier {
}

void sendTextMessage(String text) {
if (currentChatUserId == null || text.trim().isEmpty) return;
final cleanContent = text.trim();
if (currentChatUserId == null || cleanContent.isEmpty) return;

final String clientMessageId =
"cli_${DateTime.now().millisecondsSinceEpoch}";
final String targetId = currentChatUserId!;
final String cleanContent = text.trim();
final targetId = currentChatUserId!;
final clientMessageId = "cli_${DateTime.now().millisecondsSinceEpoch}";

_ws.sendChat(messageId: "", targetId: targetId, content: cleanContent);

final Message optimisticMsg = Message(
final optimisticMsg = Message(
id: clientMessageId,
senderId: "me",
receiverId: targetId,
Expand All @@ -157,6 +135,7 @@ class ChatController extends ChangeNotifier {

activeChat.add(optimisticMsg);
notifyListeners();

loadInbox();
}

Expand All @@ -168,13 +147,14 @@ class ChatController extends ChangeNotifier {

void _handleIncomingWebSocketEvent(Map<String, dynamic> data) {
final String? type = data['type'];
final String? senderId =
data['sender_id'] ?? data['sender'] ?? data['receiver_id'];

if (type == null) return;

final String? senderId =
data['sender_id'] ?? data['sender'] ?? data['receiver_id'];
final String? cleanSender = senderId?.trim().toLowerCase();
final String? cleanCurrentChat = currentChatUserId?.trim().toLowerCase();
final bool isCurrentChat =
cleanSender != null && cleanSender == cleanCurrentChat;

switch (type) {
case 'user_status':
Expand All @@ -188,35 +168,54 @@ class ChatController extends ChangeNotifier {
notifyListeners();
}
break;

case 'chat':
case 'message':
if (cleanSender != null && cleanSender == cleanCurrentChat) {
if (isCurrentChat) {
activeChat.add(Message.fromJson(data));
_ws.sendReadReceipt(targetId: currentChatUserId!);
notifyListeners();
} else {
loadInbox();
}
loadInbox();
break;

case 'typing':
if (cleanSender != null && cleanSender == cleanCurrentChat) {
if (isCurrentChat) {
isPeerTyping = data['content'] == 'true';
notifyListeners();
}
break;

case 'read_receipt':
if (cleanSender != null && cleanSender == cleanCurrentChat) {
if (isCurrentChat) {
bool updated = false;
activeChat = activeChat.map((msg) {
final String sId = msg.senderId.trim().toLowerCase();
if (sId == 'me' || sId != cleanCurrentChat) {
if (!msg.isRead && (sId == 'me' || sId != cleanCurrentChat)) {
updated = true;
return msg.copyWith(isRead: true);
}
return msg;
}).toList();
notifyListeners();
loadInbox();

if (updated) {
notifyListeners();
loadInbox();
}
}
break;
}
}

List<dynamic> _extractDataList(dynamic data, List<String> fallbackKeys) {
if (data == null) return [];
if (data is List) return data;
if (data is Map) {
if (data['data'] is List) return data['data'];
for (final key in fallbackKeys) {
if (data[key] is List) return data[key];
}
}
return [];
}
}
Loading
Loading