diff --git a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt index 8fa4884..07546a6 100644 --- a/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt +++ b/data/src/main/java/com/devusercode/data/firebase/FirebaseChatRepository.kt @@ -1,5 +1,6 @@ package com.devusercode.data.firebase +import android.util.Log import com.devusercode.core.domain.chat.model.UserPair import com.devusercode.core.domain.chat.repo.ChatRepository import com.devusercode.core.domain.user.model.User @@ -9,6 +10,8 @@ import com.devusercode.data.local.mapper.toEntity import com.google.firebase.database.DataSnapshot import com.google.firebase.database.FirebaseDatabase import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext @@ -16,6 +19,10 @@ class FirebaseChatRepository( private val db: FirebaseDatabase, private val conversationDao: ConversationDao, ) : ChatRepository { + companion object { + private const val TAG = "FirebaseChatRepository" + } + override suspend fun listOpenConversations(currentUid: String): List = withContext(Dispatchers.IO) { // 1) Try cache first @@ -36,48 +43,63 @@ class FirebaseChatRepository( return@withContext fresh } - private suspend fun fetchFromNetwork(currentUid: String): List { - val convSnap = - db - .getReference("users") - .child(currentUid) - .child("conversations") - .get() - .await() - val cids = convSnap.children.mapNotNull { it.key } - val res = mutableListOf() - - for (cid in cids) { - val parts = - db - .getReference("conversations") - .child(cid) - .child("participants") - .get() - .await() - val otherUid = parts.children.mapNotNull { it.key }.firstOrNull { it != currentUid } ?: continue - val otherSnap = + private suspend fun fetchFromNetwork(currentUid: String): List = + withContext(Dispatchers.IO) { + val convSnap = db .getReference("users") - .child(otherUid) - .get() - .await() - val user = otherSnap.toUser(otherUid) - val last = - db - .getReference("conversations") - .child(cid) - .child("lastMessage") + .child(currentUid) + .child("conversations") .get() .await() - val text = last.child("text").getValue(String::class.java) - val time = last.child("time").getValue(Long::class.java) + val cids = convSnap.children.mapNotNull { it.key } - res += UserPair(user, cid, text, time) - } + // Batch all Firebase calls concurrently instead of sequentially + cids.map { cid -> + async { + runCatching { + val parts = + db + .getReference("conversations") + .child(cid) + .child("participants") + .get() + .await() + val otherUid = parts.children.mapNotNull { it.key }.firstOrNull { it != currentUid } ?: return@async null - return res - } + // Fetch user data and last message in parallel + val otherSnapDeferred = + async { + db + .getReference("users") + .child(otherUid) + .get() + .await() + } + val lastDeferred = + async { + db + .getReference("conversations") + .child(cid) + .child("lastMessage") + .get() + .await() + } + + val otherSnap = otherSnapDeferred.await() + val last = lastDeferred.await() + + val user = otherSnap.toUser(otherUid) + val text = last.child("text").getValue(String::class.java) + val time = last.child("time").getValue(Long::class.java) + + UserPair(user, cid, text, time) + }.onFailure { e -> + Log.e(TAG, "Failed to fetch conversation $cid", e) + }.getOrNull() + } + }.awaitAll().filterNotNull() + } private fun DataSnapshot.toUser(uid: String): User { val name = diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt b/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt index b1fe924..093199c 100644 --- a/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt +++ b/ui/src/main/java/com/devusercode/ui/screens/home/HomeScreen.kt @@ -68,10 +68,17 @@ fun HomeScreen( title = { Text("UpChat") }, actions = { IconButton(onClick = { menu = true }) { Icon(Icons.Default.MoreVert, contentDescription = null) } - DropdownMenu(expanded = menu, onDismissRequest = { }) { - DropdownMenuItem(text = { Text("Profile") }, onClick = { onProfile() }) - DropdownMenuItem(text = { Text("Settings") }, onClick = { onSettings() }) + DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) { + DropdownMenuItem(text = { Text("Profile") }, onClick = { + menu = false + onProfile() + }) + DropdownMenuItem(text = { Text("Settings") }, onClick = { + menu = false + onSettings() + }) DropdownMenuItem(text = { Text("Logout") }, onClick = { + menu = false vm.doLogout { // If NavHost passed a navigator, use it; else no-op onLoggedOutNavigateToAuth?.invoke(Routes.AUTH) diff --git a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt index b33fbd6..ddda191 100644 --- a/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt +++ b/ui/src/main/java/com/devusercode/ui/screens/home/HomeViewModel.kt @@ -70,9 +70,14 @@ class HomeViewModel viewModelScope.launch { observePresence(id).collect { (online, lastSeen) -> _state.update { st -> + // Optimize: Only update the specific user at the given index val updated = - st.conversations.map { p -> - if (p.user.uid == id) p.copy(user = p.user.copy(online = online, lastSeenEpochMs = lastSeen)) else p + st.conversations.toMutableList().apply { + val targetIndex = indexOfFirst { it.user.uid == id } + if (targetIndex >= 0) { + val pair = get(targetIndex) + set(targetIndex, pair.copy(user = pair.user.copy(online = online, lastSeenEpochMs = lastSeen))) + } } st.copy(conversations = updated) }