messenger-android

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

commit f9b609a1cbad55c071894f755eb228a92851089b
parent 7dfcf601a5803aaeeec6dff82a2e1f6e0687ca29
Author: t3sserakt <t3ss@posteo.de>
Date:   Fri, 22 May 2026 13:18:18 +0200

fix the text msg side left and right sided

Diffstat:
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/MainActivity.kt | 68+++++++++++++++++++++++++++++++++++---------------------------------
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt | 6++++++
2 files changed, 41 insertions(+), 33 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 @@ -391,7 +391,16 @@ class MainActivity : AppCompatActivity() { } fun getChatViewModel(chatContext: ChatContext): ChatViewModel? { - val key = stableChatKey(chatContext) ?: return null + val base = stableChatKey(chatContext) ?: return null + // Key the per-chat ViewModel by the *foreground* account too. OWN/OTHER + // is viewer-relative: the same group message is "mine" for the account + // that sent it and "theirs" for everyone else. A shared (account-blind) + // ViewModel bakes one account's perspective into the stored message and + // shows it wrong after a switch — the cause of "messages on the wrong + // side for both accounts". One ViewModel per (account, chat) keeps each + // account's perspective independent. + val acct = currentAccount?.name?.lowercase() ?: "?" + val key = "$acct|$base" val isNew = key !in chatViewModels val vm = chatViewModels.getOrPut(key) { ChatViewModel(chatContext) } Log.d(TAG, "getChatViewModel key=$key isNew=$isNew msgCount=${vm.messages.value?.size ?: 0}") @@ -539,10 +548,10 @@ class MainActivity : AppCompatActivity() { // Intentionally keep chatViewModels and chatMenuViewModels across // account switches. The per-chat message log is in-memory only, and // the daemon does not replay history on (re)connect. Wiping them - // here makes A's messages vanish the moment we switch to B. The - // stable chat key (group:<name> / contact:<key>) is identity- - // independent, so the same ViewModel is still the correct sink for - // future TEXT callbacks under account B. + // here makes the messages vanish the moment we switch accounts. + // ViewModels are keyed per (account, chat) (see getChatViewModel), + // so each account keeps its own correctly-sided copy of the + // conversation; switching back restores that account's view intact. chats.clear() Log.d(TAG, "clearChatState: chat overview/contacts cleared, message history preserved") } @@ -715,41 +724,31 @@ class MainActivity : AppCompatActivity() { MessageKind.TEXT, MessageKind.FILE -> { val senderKey = chatMessage.sender?.key ?: "" - // Skip if the sender matches ANY of our local sessions' - // profile keys (not just this background session's). In - // multi-handle group chat, when account A (foreground) - // sends a message, the daemon broadcasts it to every - // handle subscribed to the group, including B's. B's - // background handler then sees a TEXT whose sender is A. - // A's foreground has already optimistically added that - // message to the shared "group:<name>" viewmodel via - // ChatFragment's sendButton; if we also add it here, the - // user sees the same text twice. Treat "sender is any of - // our active accounts" as an own-echo and skip. - // - // Edge case left open intentionally: an external peer - // sending to a group where multiple of our local - // accounts are both members would still duplicate - // (foreground handler adds + background handler adds). - // Rare enough that we accept it for now; can be tackled - // with viewmodel-level dedup if it ever matters. - val isOurOwnEcho = senderKey.isNotEmpty() && sessions.values.any { other -> - val k = runCatching { - other.gnunetChat.getProfileKey(other.handle) - }.getOrDefault("") - k.isNotEmpty() && k == senderKey - } - if (isOurOwnEcho) { + // Skip only THIS background session's own echo. ViewModels are + // keyed per (account, chat), so each account holds its own copy + // of the conversation. When account A (foreground) sends a group + // message, B's background handler must record it as OTHER in B's + // own ViewModel — that's how B sees A's message on the left after + // switching. (The earlier "skip if sender is ANY of our sessions" + // logic was for a single shared ViewModel and made B drop A's + // messages entirely; with per-account ViewModels there's no cross- + // account duplication to guard against.) The only echo to skip is + // a message whose sender is this very session's account — B already + // added that optimistically while B was foreground. + val ownKey = runCatching { + session.gnunetChat.getProfileKey(session.handle) + }.getOrDefault("") + if (senderKey.isNotEmpty() && senderKey == ownKey) { Log.d( TAG, "background-session ${session.account.name}: kind=${chatMessage.kind} " + - "(own echo from one of our sessions, skip)" + "(own echo, skip)" ) return } - val key = stableChatKeyFor(session, chatContext) - if (key == null) { + val base = stableChatKeyFor(session, chatContext) + if (base == null) { Log.w( TAG, "background-session ${session.account.name}: kind=${chatMessage.kind} " + @@ -757,6 +756,9 @@ class MainActivity : AppCompatActivity() { ) return } + // Same per-account keying scheme as getChatViewModel(), but using + // this background session's account (not the foreground one). + val key = "${session.account.name.lowercase()}|$base" val vm = chatViewModels.getOrPut(key) { ChatViewModel(chatContext) } chatMessage.type = ChatMessageType.OTHER vm.addMessage(chatMessage) diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt @@ -202,6 +202,12 @@ class ChatFragment : Fragment(R.layout.fragment_chat) { }) chatViewModel.messages.observe(viewLifecycleOwner, Observer { messages -> + Log.d( + "ChatFragment", + "observer: count=${messages.size} " + + "types=${messages.map { it.type }} " + + "texts=${messages.map { it.text.take(8) }}" + ) adapter.submitList(messages) recyclerView.scrollToPosition(messages.size - 1) })