commit d93a8e24593144c103258608fb824838e8464563
parent f6c510b2aa06e359ed904ac34dc93d276e04c701
Author: t3sserakt <t3ss@posteo.de>
Date: Wed, 8 Apr 2026 13:24:21 +0200
WIP: lobby chat fix, and dynamic time.
Diffstat:
4 files changed, 125 insertions(+), 58 deletions(-)
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt
@@ -43,7 +43,10 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
+import java.util.concurrent.ConcurrentHashMap
import org.gnunet.gnunetmessenger.model.ChatAccount
import org.gnunet.gnunetmessenger.model.ChatContact
import org.gnunet.gnunetmessenger.model.ChatContext
@@ -76,7 +79,8 @@ class MainActivity : AppCompatActivity() {
private val chatMenuViewModels = mutableMapOf<String, ChatMenuViewModel>()
private val chatViewModels = mutableMapOf<String, ChatViewModel>()
- private val chats = mutableMapOf<String, ChatContext>()
+ private val chats = ConcurrentHashMap<String, ChatContext>()
+ private val loadChatsMutex = Mutex()
var currentAccount: ChatAccount? = null
private set
@@ -224,8 +228,16 @@ class MainActivity : AppCompatActivity() {
}
val viewModel = getChatViewModel(chatContext)
- chatMessage.text = lastMessagePreview
- chatMessage.type = ChatMessageType.SYSTEM
+ val displayTimestamp = if (chatMessage.timestamp == 0L) {
+ System.currentTimeMillis()
+ } else {
+ chatMessage.timestamp
+ }
+ val displayMessage = chatMessage.copy(
+ text = lastMessagePreview,
+ type = ChatMessageType.SYSTEM,
+ timestamp = displayTimestamp
+ )
if (uuid == null) {
uuid = gnunetChat.randomUUID()
@@ -248,7 +260,7 @@ class MainActivity : AppCompatActivity() {
ChatSummary(displayContext, displayName, lastMessagePreview)
)
}
- viewModel?.addMessage(chatMessage)
+ viewModel?.addMessage(displayMessage)
}
MessageKind.CONTACT,
@@ -333,68 +345,84 @@ class MainActivity : AppCompatActivity() {
loadChatsSuspend()
}
- private suspend fun loadChatsSuspend() = withContext(Dispatchers.IO) {
- val summaries = mutableListOf<ChatSummary>()
- val contacts = mutableListOf<ChatContact>()
-
- val contactList = gnunetChat.listContacts(handle)
- for (contact in contactList) {
- val chatContext = gnunetChat.getContactContext(contact)
- var uuid = gnunetChat.getUserPointerForContext(chatContext)
- if (uuid == null) {
- uuid = gnunetChat.randomUUID()
- gnunetChat.setUserPointerForContext(chatContext, uuid)
- }
- chatContext.userPointer = uuid
- contacts.add(contact)
-
- chats[uuid] = chatContext
- contact.blocked = gnunetChat.isContactBlocked(contact)
- summaries.add(
- ChatSummary(
- chatContext = chatContext,
- displayName = contact.name,
- contact = contact
- )
- )
- }
+ private suspend fun loadChatsSuspend() = loadChatsMutex.withLock {
+ withContext(Dispatchers.IO) {
+ val summaries = mutableListOf<ChatSummary>()
+ val contacts = mutableListOf<ChatContact>()
- val groupList = gnunetChat.listGroups(handle)
- for (group in groupList) {
- val chatContext = gnunetChat.getGroupContext(group)
- var uuid = gnunetChat.getUserPointerForContext(chatContext)
- if (uuid == null) {
- uuid = gnunetChat.randomUUID()
- gnunetChat.setUserPointerForContext(chatContext, uuid)
- }
- chatContext.userPointer = uuid
-
- chats[uuid] = chatContext
- summaries.add(
- ChatSummary(
- chatContext = chatContext,
- displayName = group.name,
- group = group
+ val contactList = gnunetChat.listContacts(handle)
+ for (contact in contactList) {
+ val chatContext = gnunetChat.getContactContext(contact)
+ var uuid = gnunetChat.getUserPointerForContext(chatContext)
+ if (uuid == null) {
+ uuid = gnunetChat.randomUUID()
+ gnunetChat.setUserPointerForContext(chatContext, uuid)
+ }
+ chatContext.userPointer = uuid
+ contacts.add(contact)
+
+ chats[uuid] = chatContext
+ contact.blocked = gnunetChat.isContactBlocked(contact)
+ summaries.add(
+ ChatSummary(
+ chatContext = chatContext,
+ displayName = contact.name,
+ contact = contact
+ )
)
- )
- }
+ }
- withContext(Dispatchers.Main) {
- contactListViewModel.setContacts(contacts)
- val existing = chatOverviewViewModel.chats.value.orEmpty()
- val merged = summaries.map { summary ->
- val prev = existing.find {
- it.chatContext.userPointer == summary.chatContext.userPointer
+ val groupList = gnunetChat.listGroups(handle)
+ for (group in groupList) {
+ val chatContext = gnunetChat.getGroupContext(group)
+ var uuid = gnunetChat.getUserPointerForContext(chatContext)
+ if (uuid == null) {
+ uuid = gnunetChat.randomUUID()
+ gnunetChat.setUserPointerForContext(chatContext, uuid)
+ }
+ chatContext.userPointer = uuid
+
+ chats[uuid] = chatContext
+
+ val members = try {
+ gnunetChat.listGroupContacts(group)
+ } catch (t: Throwable) {
+ Log.w(TAG, "listGroupContacts failed for ${group.name}", t)
+ emptyList()
}
- if (prev?.lastMessagePreview != null && summary.lastMessagePreview == null) {
- summary.copy(lastMessagePreview = prev.lastMessagePreview)
+ val memberPreview = if (members.isNotEmpty()) {
+ members.joinToString(", ") { it.name } + " joined the chat"
} else {
- summary
+ null
+ }
+
+ summaries.add(
+ ChatSummary(
+ chatContext = chatContext,
+ displayName = group.name,
+ lastMessagePreview = memberPreview,
+ group = group
+ )
+ )
+ }
+
+ withContext(Dispatchers.Main) {
+ contactListViewModel.setContacts(contacts)
+ val existing = chatOverviewViewModel.chats.value.orEmpty()
+ val merged = summaries.map { summary ->
+ val prev = existing.find {
+ it.chatContext.userPointer == summary.chatContext.userPointer
+ }
+ if (prev?.lastMessagePreview != null && summary.lastMessagePreview == null) {
+ summary.copy(lastMessagePreview = prev.lastMessagePreview)
+ } else {
+ summary
+ }
}
+ chatOverviewViewModel.setChats(merged)
}
- chatOverviewViewModel.setChats(merged)
+ Log.d(TAG, "loadChats: loaded ${contacts.size} contacts, ${groupList.size} groups")
}
- Log.d(TAG, "loadChats: loaded ${contacts.size} contacts, ${groupList.size} groups")
}
fun clearChatState() {
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt
@@ -78,6 +78,7 @@ interface GnunetChat {
fun isPlatform(context: ChatContext): Boolean
fun iterateGroupContacts(chatGroup: ChatGroup, callback: (ChatGroup, ChatContact) -> Int)
+ suspend fun listGroupContacts(group: ChatGroup): List<ChatContact>
fun randomUUID(): String
fun getContactAttributes(contact: ChatContact, callback: (String, String) -> Unit)
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt
@@ -900,6 +900,37 @@ class GnunetChatBoundService(
}
}
+ override suspend fun listGroupContacts(group: ChatGroup): List<ChatContact> {
+ return withReadyRemote(lastHandle) { remote, _ ->
+ val result = mutableListOf<ChatContact>()
+ val done = CompletableDeferred<Unit>()
+
+ val bridge = object : IGroupContactCallback.Stub() {
+ override fun onGroupContact(groupDto: ChatGroupDto, contactDto: ChatContactDto) {
+ result.add(contactDto.toLocal())
+ }
+
+ override fun onDone() {
+ if (!done.isCompleted) {
+ done.complete(Unit)
+ }
+ }
+
+ override fun onError(code: Int, message: String?) {
+ if (!done.isCompleted) {
+ done.completeExceptionally(
+ IllegalStateException("iterateGroupContacts failed: $code ${message ?: ""}".trim())
+ )
+ }
+ }
+ }
+
+ remote.iterateGroupContacts(group.toDto(), bridge)
+ done.await()
+ result.toList()
+ }
+ }
+
override fun randomUUID(): String {
return "uuid_${System.currentTimeMillis()}_${uuidCounter++}"
}
diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt
@@ -426,6 +426,13 @@ class GnunetChatMock : GnunetChat {
}
}
+ override suspend fun listGroupContacts(group: ChatGroup): List<ChatContact> {
+ val contextCharlie = ChatContext(ChatContextType.CONTACT, null, true, false)
+ return listOf(
+ ChatContact(contextCharlie, name = "Charlie")
+ )
+ }
+
override fun randomUUID(): String {
return uuidCounter++.toString()
}