ui: navigation from support chat to member info (#6237)

This commit is contained in:
spaced4ndy
2025-09-03 13:07:28 +00:00
committed by GitHub
parent 6beb6b1efc
commit 9d65b9eb2b
10 changed files with 93 additions and 22 deletions

View File

@@ -60,6 +60,7 @@ struct ChatView: View {
@State private var animatedScrollingInProgress: Bool = false
@State private var showUserSupportChatSheet = false
@State private var showCommandsMenu = false
@State private var supportChatMemberInfoLinkActive = false
@State private var scrollView: EndlessScrollView<MergedItem> = EndlessScrollView(frame: .zero)
@@ -178,6 +179,28 @@ struct ChatView: View {
if im.showLoadingProgress == chat.id {
ProgressView().scaleEffect(2)
}
if case let .group(groupInfo, _) = chat.chatInfo,
case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter,
case let .memberSupport(groupMember_) = groupScopeInfo,
let groupMember = groupMember_ {
NavigationLink(isActive: $supportChatMemberInfoLinkActive) {
GroupMemberInfoView(
groupInfo: groupInfo,
chat: chat,
groupMember: GMember(groupMember),
scrollToItemId: $scrollToItemId,
openedFromSupportChat: true
)
.navigationBarHidden(false)
.modifier(BackButton(disabled: Binding.constant(false)) {
supportChatMemberInfoLinkActive = false
})
} label: {
EmptyView()
}
.frame(width: 1, height: 1)
.hidden()
}
}
.safeAreaInset(edge: .top) {
VStack(spacing: .zero) {
@@ -222,7 +245,9 @@ struct ChatView: View {
}
}
}
.appSheet(item: $selectedMember) { member in
.appSheet(item: $selectedMember, onDismiss: {
chatModel.secondaryIM = nil
}) { member in
if case let .group(groupInfo, _) = chat.chatInfo {
GroupMemberInfoView(
groupInfo: groupInfo,
@@ -459,7 +484,10 @@ struct ChatView: View {
ChatInfoToolbar(chat: chat)
.tint(theme.colors.primary)
}
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
.appSheet(isPresented: $showChatInfoSheet, onDismiss: {
chatModel.secondaryIM = nil
theme = buildTheme()
}) {
GroupChatInfoView(
chat: chat,
groupInfo: Binding(
@@ -562,7 +590,11 @@ struct ChatView: View {
switch groupScopeInfo {
case let .memberSupport(groupMember_):
if let groupMember = groupMember_ {
MemberSupportChatToolbar(groupMember: groupMember)
Button {
supportChatMemberInfoLinkActive = true
} label: {
MemberSupportChatToolbar(groupMember: groupMember)
}
} else {
textChatToolbar("Chat with admins")
}

View File

@@ -18,6 +18,7 @@ struct GroupMemberInfoView: View {
@ObservedObject var groupMember: GMember
@Binding var scrollToItemId: ChatItem.ID?
var navigation: Bool = false
var openedFromSupportChat: Bool = false
@State private var connectionStats: ConnectionStats? = nil
@State private var connectionCode: String? = nil
@State private var connectionLoaded: Bool = false
@@ -101,7 +102,8 @@ struct GroupMemberInfoView: View {
if member.memberActive {
Section {
if groupInfo.membership.memberRole >= .moderator
if !openedFromSupportChat
&& groupInfo.membership.memberRole >= .moderator
&& (member.memberRole < .moderator || member.supportChat != nil) {
MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
}

View File

@@ -10,6 +10,7 @@ import SwiftUI
import SimpleXChat
struct SecondaryChatView: View {
@Environment(\.dismiss) var dismiss
@EnvironmentObject var chatModel: ChatModel
@ObservedObject var chat: Chat
@Binding var scrollToItemId: ChatItem.ID?
@@ -23,9 +24,10 @@ struct SecondaryChatView: View {
floatingButtonModel: FloatingButtonModel(im: im),
scrollToItemId: $scrollToItemId
)
.onDisappear {
.modifier(BackButton(disabled: Binding.constant(false)) {
chatModel.secondaryIM = nil
}
dismiss()
})
}
}
}

View File

@@ -178,8 +178,8 @@
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@@ -545,8 +545,8 @@
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a"; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a"; sourceTree = "<group>"; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
@@ -708,8 +708,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -795,8 +795,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-IWC91cESq2l3JFgl07ryw1.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.4.2-7qDqJsgFG1qLUMejyoXxtN.a */,
);
path = Libraries;
sourceTree = "<group>";

View File

@@ -2634,7 +2634,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
}
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
if !canBeRemoved(groupInfo: groupInfo) { return nil }
if !canBeRemoved(groupInfo: groupInfo) || memberPending { return nil }
let userRole = groupInfo.membership.memberRole
return GroupMemberRole.supportedRoles.filter { $0 <= userRole }
}
@@ -2643,6 +2643,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
let userRole = groupInfo.membership.memberRole
return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .moderator
&& userRole >= .moderator && userRole >= memberRole && groupInfo.membership.memberActive
&& !memberPending
}
public var canReceiveReports: Bool {

View File

@@ -2343,7 +2343,7 @@ data class GroupMember (
}
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
if (!canBeRemoved(groupInfo)) null
if (!canBeRemoved(groupInfo) || memberPending) null
else groupInfo.membership.memberRole.let { userRole ->
GroupMemberRole.selectableRoles.filter { it <= userRole }
}
@@ -2352,6 +2352,7 @@ data class GroupMember (
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft && memberRole < GroupMemberRole.Moderator
&& userRole >= GroupMemberRole.Moderator && userRole >= memberRole && groupInfo.membership.memberActive
&& !memberPending
}
val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange

View File

@@ -453,7 +453,7 @@ fun ChatView(
}
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(chatRh, groupInfo, mem, scrollToItemId, stats, code, chatModel, close, close)
GroupMemberInfoView(chatRh, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = false, close, close)
}
}
}
@@ -1005,7 +1005,9 @@ fun ChatLayout(
Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
Box {
if (selectedChatItems.value == null) {
MemberSupportChatAppBar(chatsCtx, chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_, { ModalManager.end.closeModal() }, onSearchValueChanged)
if (chat != null) {
MemberSupportChatAppBar(chatsCtx, remoteHostId, chat, chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_, scrollToItemId, { ModalManager.end.closeModal() }, onSearchValueChanged)
}
} else {
SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value)
}

View File

@@ -126,7 +126,7 @@ fun ModalData.GroupChatInfoView(
}
ModalManager.end.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, closeCurrent) {
GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = false, closeCurrent) {
closeCurrent()
close()
}

View File

@@ -49,9 +49,13 @@ fun GroupMemberInfoView(
connectionStats: ConnectionStats?,
connectionCode: String?,
chatModel: ChatModel,
openedFromSupportChat: Boolean,
close: () -> Unit,
closeAll: () -> Unit, // Close all open windows up to ChatView
) {
KeyChangeEffect(chat.simplex.common.platform.chatModel.chatId.value) {
ModalManager.end.closeModals()
}
BackHandler(onBack = close)
val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatModel.chatId.value && ch.remoteHostId == rhId }
val connStats = remember { mutableStateOf(connectionStats) }
@@ -225,7 +229,8 @@ fun GroupMemberInfoView(
)
}
}
}
},
openedFromSupportChat = openedFromSupportChat
)
if (progressIndicator) {
@@ -291,6 +296,7 @@ fun GroupMemberInfoLayout(
syncMemberConnection: () -> Unit,
syncMemberConnectionForce: () -> Unit,
verifyClicked: () -> Unit,
openedFromSupportChat: Boolean
) {
val cStats = connStats.value
fun knownDirectChat(contactId: Long): Pair<Chat, Contact>? {
@@ -440,6 +446,7 @@ fun GroupMemberInfoLayout(
if (member.memberActive) {
SectionView {
if (
!openedFromSupportChat &&
groupInfo.membership.memberRole >= GroupMemberRole.Moderator &&
(member.memberRole < GroupMemberRole.Moderator || member.supportChat != null)
) {
@@ -924,6 +931,7 @@ fun PreviewGroupMemberInfoLayout() {
syncMemberConnection = {},
syncMemberConnectionForce = {},
verifyClicked = {},
openedFromSupportChat = false,
)
}
}

View File

@@ -51,7 +51,10 @@ private fun MemberSupportChatView(
@Composable
fun MemberSupportChatAppBar(
chatsCtx: ChatModel.ChatsContext,
rhId: Long?,
chat: Chat,
scopeMember_: GroupMember?,
scrollToItemId: MutableState<Long?>,
close: () -> Unit,
onSearchValueChanged: (String) -> Unit
) {
@@ -67,11 +70,31 @@ fun MemberSupportChatAppBar(
}
}
BackHandler(onBack = onBackClicked)
if (scopeMember_ != null) {
if (chat.chatInfo is ChatInfo.Group && scopeMember_ != null) {
val groupInfo = chat.chatInfo.groupInfo
DefaultAppBar(
navigationButton = { NavigationButtonBack(onBackClicked) },
title = { MemberSupportChatToolbarTitle(scopeMember_) },
onTitleClick = null,
onTitleClick = {
withBGApi {
val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, scopeMember_.groupMemberId)
val stats = r?.second
val code = if (scopeMember_.memberActive) {
val memCode = chatModel.controller.apiGetGroupMemberCode(rhId, groupInfo.apiId, scopeMember_.groupMemberId)
memCode?.second
} else {
null
}
ModalManager.end.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.getGroupMember(scopeMember_.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, openedFromSupportChat = true, close = closeCurrent) {
closeCurrent()
close()
}
}
}
}
},
onTop = !oneHandUI.value || !chatBottomBar.value,
showSearch = showSearch.value,
onSearchValueChanged = onSearchValueChanged,