desktop: after a chat switch, chat info shows the previous chat's security code, link or members (#7926)

* desktop: reload chat info when it is shown for another chat

The info pane keeps the values it last loaded. When the pane is composed
again for another chat - after clicking another chat with a page such as
"Verify security code" open over it, or when switching between a contact
and a group - KeyChangeEffect takes the new chat id as its starting key, so
nothing is reloaded: "Verify security code" opened from the new contact's
info showed the previous contact's code, and a group's info missed its
members.

Remember which chat the loaded values belong to and reload whenever the
shown chat differs from it.

Fixes #5135

* plans: desktop chat info stale after chat switch (#5135)

* desktop: don't show another chat's info while the shown chat loads

When the info pane showed a chat other than the one its values were
loaded for, it reloaded them, but until the reload returned it showed the
previous chat's values: "Verify security code" opened the previous
contact's code, and a group showed the previous group's link, so "Group
link" opened and shared another group's link.

Start the contact info, security code and group link empty when they
belong to another chat. Hide the "Create group link" / "Group link" row
and disable the channel "Link" button until the link is loaded: with no
link yet, both open the link page, which creates a link when it gets
none, and for a group that already has one the core creates a server
queue before failing with a duplicate link error.

* desktop: keep a late group link update from replacing another chat's link

A link created, changed or deleted on the "Group link" page finishes in
the background. If the user switched chats meanwhile and the pane already
loaded the next group, the update replaced that group's stored link, and
the next time the pane was composed for it, it showed the previous group's
link, or no link after a delete.

Store the update only while the stored values belong to the group the
update came from.

* desktop: reload group info when its members were cleared

Switching chats clears the group members, and the preload skips storing
them when another chat is open when they arrive. Clicking info on group A,
then another chat and back to A before the preload finished, opened A's
info with the preloaded values marked as A's, so nothing was reloaded and
the members list showed only the user.

Reload the group info also when its members are not loaded.

* plans: chat info while it reloads, late link update, cleared members (#5135)

* desktop: hide channel owner link rows until the link is loaded

* plans: test of the channel owner rows, which builds the tables are for

* plans: restructure chat info plan into problem, cause, fix and scope

* plans: correct the Android and iOS scope of the chat info plan

* plans: chat info plan wording
This commit is contained in:
Narasimha-sc authored and GitHub committed 2026-10-31 20:18:45 +00:00
1 parent 88ccfaa86c
commit 0735bd2c6e
3 files changed
+69 -12

No files matched your search

@@ -413,6 +413,7 @@ fun ChatView(
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: GroupLink? = null
var preloadedChatId: ChatId? = chatInfo.id
if (chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
@@ -428,27 +429,35 @@ fun ChatView(
ModalView(close, cardScreen = true, appBar = appBar.value) {
val chatInfo = remember { activeChat }.value?.chatInfo
if (chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chatInfo.id) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember(chatInfo.id) { mutableStateOf(if (chatInfo.id == preloadedChatId) preloadedContactInfo else null) }
var code: String? by remember(chatInfo.id) { mutableStateOf(if (chatInfo.id == preloadedChatId) preloadedCode else null) }
LaunchedEffect(chatInfo.id) {
if (chatInfo.id == preloadedChatId) return@LaunchedEffect
preloadedChatId = null
contactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
preloadedCode = code
preloadedChatId = chatInfo.id
}
ChatInfoView(chatsCtx, chatModel, chatInfo.contact, contactInfo?.first, contactInfo?.second, chatInfo.localAlias, code, close) {
showSearch.value = true
}
} else if (chatInfo is ChatInfo.Group) {
var link: GroupLink? by remember(chatInfo.id) { mutableStateOf(preloadedLink) }
KeyChangeEffect(chatInfo.id) {
var link: GroupLink? by remember(chatInfo.id) { mutableStateOf(if (chatInfo.id == preloadedChatId) preloadedLink else null) }
var linkLoaded by remember(chatInfo.id) { mutableStateOf(chatInfo.id == preloadedChatId) }
LaunchedEffect(chatInfo.id) {
if (chatInfo.id == preloadedChatId && chatModel.membersLoaded.value) return@LaunchedEffect
preloadedChatId = null
setGroupMembers(chatRh, chatInfo.groupInfo, chatModel)
link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId)
linkLoaded = true
preloadedLink = link
preloadedChatId = chatInfo.id
}
GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link, selectedItems, appBar, scrollToItemId, {
GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link, linkLoaded, selectedItems, appBar, scrollToItemId, {
link = it
preloadedLink = it
if (chatInfo.id == preloadedChatId) preloadedLink = it
}, close, { showSearch.value = true })
} else {
LaunchedEffect(Unit) {
@@ -63,6 +63,7 @@ fun ModalData.GroupChatInfoView(
rhId: Long?,
chatId: String,
groupLink: GroupLink?,
groupLinkLoaded: Boolean,
selectedItems: MutableState<Set<Long>?>,
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
scrollToItemId: MutableState<Long?>,
@@ -109,6 +110,7 @@ fun ModalData.GroupChatInfoView(
developerTools,
onLocalAliasChanged = { setGroupAlias(chat, it, chatModel) },
groupLink,
groupLinkLoaded,
selectedItems,
appBar,
scrollToItemId,
@@ -462,14 +464,15 @@ fun AddGroupMembersButton(
fun ChannelLinkActionButton(
modifier: Modifier,
groupInfo: GroupInfo,
groupLinkLoaded: Boolean,
manageGroupLink: () -> Unit
) {
InfoViewActionButton(
modifier = modifier,
icon = painterResource(MR.images.ic_link),
title = stringResource(MR.strings.action_button_channel_link),
disabled = !groupInfo.ready,
disabledLook = !groupInfo.ready,
disabled = !groupInfo.ready || !groupLinkLoaded,
disabledLook = !groupInfo.ready || !groupLinkLoaded,
onClick = manageGroupLink
)
}
@@ -521,6 +524,7 @@ fun ModalData.GroupChatInfoLayout(
developerTools: Boolean,
onLocalAliasChanged: (String) -> Unit,
groupLink: GroupLink?,
groupLinkLoaded: Boolean,
selectedItems: MutableState<Set<Long>?>,
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
scrollToItemId: MutableState<Long?>,
@@ -606,7 +610,7 @@ fun ModalData.GroupChatInfoLayout(
) {
if (groupInfo.useRelays && groupInfo.isOwner) {
SearchButton(modifier = Modifier.fillMaxWidth(0.33f), chat, groupInfo, close, onSearchClicked)
ChannelLinkActionButton(modifier = Modifier.fillMaxWidth(0.5f), groupInfo, manageGroupLink)
ChannelLinkActionButton(modifier = Modifier.fillMaxWidth(0.5f), groupInfo, groupLinkLoaded, manageGroupLink)
MuteButton(modifier = Modifier.fillMaxWidth(1f), chat, groupInfo)
} else if (!groupInfo.useRelays && groupInfo.canAddMembers) {
SearchButton(modifier = Modifier.fillMaxWidth(0.33f), chat, groupInfo, close, onSearchClicked)
@@ -642,7 +646,7 @@ fun ModalData.GroupChatInfoLayout(
if (groupInfo.isOwner && groupLink != null) {
anyTopSectionRowShow = true
ChannelLinkButton(manageGroupLink)
} else if (channelLink != null) {
} else if (channelLink != null && (groupLinkLoaded || !groupInfo.isOwner)) {
anyTopSectionRowShow = true
ChannelLinkQRCodeSection(channelLink)
ShareViaChatButton {
@@ -681,7 +685,7 @@ fun ModalData.GroupChatInfoLayout(
}
} else {
SectionView {
if (groupInfo.canAddMembers && groupInfo.businessChat == null) {
if (groupInfo.canAddMembers && groupInfo.businessChat == null && groupLinkLoaded) {
anyTopSectionRowShow = true
if (groupLink == null) {
CreateGroupLinkButton(manageGroupLink)
@@ -1460,6 +1464,7 @@ fun PreviewGroupChatInfoLayout() {
developerTools = false,
onLocalAliasChanged = {},
groupLink = null,
groupLinkLoaded = true,
selectedItems = remember { mutableStateOf(null) },
appBar = remember { mutableStateOf(null) },
scrollToItemId = remember { mutableStateOf(null) },
@@ -0,0 +1,43 @@
# Desktop: chat info shows the previous chat's data after switching chats
Fixes #5135.
## Problem
With chat info open in the right pane on desktop, clicking another chat can leave the pane showing the previous chat's data:
- Contact A's info → "Verify security code" → click contact B → "Verify security code": A's code is shown under "B is not verified", and "Mark verified" fails with "Incorrect security code!".
- Group A's info → "Group preferences" → click group B: B's info shows "1 members" and A's group link.
- Switching between a contact and a group with the info shown: the same, without any page on top.
- Contact A → contact B, or group A → group B, with the info shown: until B's info is reloaded, "Verify security code" opens A's code, and "Group link" opens and shares A's link as B's; "Create group link" for a group that has a link fails with "Error creating group link".
- Info on group A → another chat → back to A before the info has loaded: A's info shows only the user as a member.
## Cause
Before the app opens the pane, `ChatView` loads the contact info, security code, group link and group members into preloaded values, and the pane's state is initialised from them. They are reloaded in `KeyChangeEffect(chatInfo.id)` only when the key changes after the first composition of the pane's branch.
The branch is composed again for another chat in two cases, and then nothing is reloaded:
- On a chat switch the app calls `ModalManager.end.closeModalsExceptFirst()` (`ChatListView.kt`), so a page on top of the pane ("Verify security code", "Group preferences", "Group link") is closed and the pane is composed for the new chat.
- The contact and group cases are separate branches, so after a switch between a contact and a group the app composes the other branch.
When the key does change, the contact branch's state is remembered without a key and the group link is initialised from the previous group's link, so the previous chat's values are shown until the reload returns.
The app clears the group members on every chat switch, and members are stored by `setGroupMembers` only while their group is the open chat. After info on group A → another chat → A, the members list is empty, and the pane opens for A, the chat its values were preloaded for, so nothing is reloaded.
## Fix
`preloadedChatId` stores the id of the chat the preloaded values belong to. The app reloads them in `LaunchedEffect(chatInfo.id)` when the shown chat's id differs from `preloadedChatId`, or when the shown chat is a group whose members are not loaded (`!chatModel.membersLoaded.value`). `preloadedChatId` is cleared while a load runs and set when it completes, so a cancelled load is redone.
Until the reload completes, none of the previous chat's values are shown:
- The contact info, security code and group link are initialised empty for another chat (`remember(chatInfo.id)`); without a code the "Verify security code" row is hidden.
- While `linkLoaded` (passed to `GroupChatInfoView` as `groupLinkLoaded`) is false, the "Create group link" / "Group link" row and a channel owner's channel link rows are hidden, and the channel "Link" button is disabled.
A link created, changed or deleted on the "Group link" page is stored in the preloaded values only while they belong to the group of that page.
The app skips the reload on the first open and on Back for the same chat, as before, unless the group's members were cleared.
## Scope
- `ChatView.kt`: the info pane in `ChatView`.
- `GroupChatInfoView.kt`: the `groupLinkLoaded` parameter of `GroupChatInfoView`, `GroupChatInfoLayout` and `ChannelLinkActionButton`.
- Desktop. Android uses the same code; its info is a full-screen modal over the chat list, and the app closes it on a notification tap.
- iOS is unchanged: the contact info, security code and group link are loaded when its info views appear, and the group members before its `ChatView` shows the group info.