diff --git a/Rosetta/Core/Services/SessionManager.swift b/Rosetta/Core/Services/SessionManager.swift index 995d206..568471a 100644 --- a/Rosetta/Core/Services/SessionManager.swift +++ b/Rosetta/Core/Services/SessionManager.swift @@ -1413,11 +1413,11 @@ final class SessionManager { if context.fromMe { // Android parity: read sync from another own device means // incoming messages in this dialog should become read locally. - DialogRepository.shared.markAsRead(opponentKey: opponentKey) MessageRepository.shared.markIncomingAsRead( opponentKey: opponentKey, myPublicKey: ownKey ) + DialogRepository.shared.markAsRead(opponentKey: opponentKey) // Desktop-active suppression: prevent in-app banner for 30s. self.desktopActiveDialogs[opponentKey] = Date() @@ -2043,11 +2043,11 @@ final class SessionManager { let shouldMarkRead = dialogIsActive && dialogIsReadEligible && fg && !isSystem && !idle if shouldMarkRead { - DialogRepository.shared.markAsRead(opponentKey: opponentKey) MessageRepository.shared.markIncomingAsRead( opponentKey: opponentKey, myPublicKey: myKey ) + DialogRepository.shared.markAsRead(opponentKey: opponentKey) if !fromMe && !wasKnownBefore { // Android/Desktop parity: send read receipt immediately, // even during sync. 400ms debounce prevents flooding. diff --git a/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift b/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift index 028faf2..e9855ca 100644 --- a/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift +++ b/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift @@ -1573,8 +1573,8 @@ private extension ChatDetailView { func markDialogAsRead() { guard MessageRepository.shared.isDialogReadEligible(route.publicKey) else { return } - DialogRepository.shared.markAsRead(opponentKey: route.publicKey) MessageRepository.shared.markIncomingAsRead(opponentKey: route.publicKey, myPublicKey: currentPublicKey) + DialogRepository.shared.markAsRead(opponentKey: route.publicKey) // Desktop parity: don't send read receipts for system accounts if !route.isSystemAccount { SessionManager.shared.sendReadReceipt(toPublicKey: route.publicKey) diff --git a/Rosetta/Features/Chats/ChatDetail/OpponentProfileViewController.swift b/Rosetta/Features/Chats/ChatDetail/OpponentProfileViewController.swift index 87c92c6..4aaeaf5 100644 --- a/Rosetta/Features/Chats/ChatDetail/OpponentProfileViewController.swift +++ b/Rosetta/Features/Chats/ChatDetail/OpponentProfileViewController.swift @@ -397,12 +397,13 @@ final class OpponentProfileViewController: UIViewController, UIGestureRecognizer y = expandedH + 16 } else { - // ── Collapsed: small avatar (SwiftUI: .padding(.top, 16)) ── + // ── Collapsed: small avatar ── headerImageView.alpha = 0 scrimView.alpha = 0 avatarContainer.alpha = 1 - y = safeTop + 16 + // Avatar starts at same Y as back button — side by side, no gap + y = safeTop avatarContainer.frame = CGRect(x: (w - avatarSize) / 2, y: y, width: avatarSize, height: avatarSize) avatarHosting?.view.frame = avatarContainer.bounds y += avatarSize + 12 diff --git a/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift b/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift index 1b15169..cd040de 100644 --- a/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift +++ b/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift @@ -127,6 +127,10 @@ final class ChatListViewModel: ObservableObject { } func markAsRead(_ dialog: Dialog) { + MessageRepository.shared.markIncomingAsRead( + opponentKey: dialog.opponentKey, + myPublicKey: SessionManager.shared.currentPublicKey + ) DialogRepository.shared.markAsRead(opponentKey: dialog.opponentKey) }