messenger-android

Android graphical user interfaces for GNUnet Messenger
Log | Files | Refs | README | LICENSE

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:
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt | 144+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/GnunetChat.kt | 1+
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt | 31+++++++++++++++++++++++++++++++
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/mock/GnunetChatMock.kt | 7+++++++
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() }