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:
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)
})