From 3cdba73109f74d9376c6abf9334ae5059a049ee2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:04:00 +0400 Subject: [PATCH] wip --- apps/ios/Shared/Views/Chat/ChatView.swift | 54 +++++++++++++++++-- .../Chat/ComposeMessage/ComposeView.swift | 6 ++- .../ContextDirectMemberView.swift | 8 +-- apps/ios/SimpleXChat/ChatTypes.swift | 42 +++++++++++++-- .../chat/simplex/common/model/ChatModel.kt | 2 +- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e0e0c36149..1d487fce3a 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -486,7 +486,7 @@ struct ChatView: View { HStack { if let directMember = directMember { ProfileImage(imageStr: directMember.image) - .frame(width: 30, height: 30) + .frame(width: 20, height: 20) .onTapGesture { selectedMember = directMember } .appSheet(item: $selectedMember) { member in GroupMemberInfoView(groupInfo: groupInfo, member: directMember, navigation: true) @@ -550,6 +550,8 @@ struct ChatView: View { @State private var playbackState: VoiceMessagePlaybackState = .noPlayback @State private var playbackTime: TimeInterval? + @State private var allowMemberMenu: Bool = true + var body: some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading let uiMenu: Binding = Binding( @@ -642,7 +644,15 @@ struct ChatView: View { menu.append(rm) } if ci.meta.itemDeleted == nil && !ci.isLiveDummy && !live { - menu.append(replyUIAction()) + if ci.directMember == nil { + menu.append(replyUIAction()) + } + if case let .group(groupInfo) = chat.chatInfo, + let toDirectMember = ci.memberToReplyDirectlyTo, + let toDirectMemberWithConn = ChatModel.shared.getGroupMember(toDirectMember.groupMemberId), + toDirectMemberWithConn.allowedToSendDirectlyTo(groupInfo: groupInfo) { + menu.append(replyDirectlyUIAction(toDirectMemberWithConn, groupInfo)) + } } menu.append(shareUIAction()) menu.append(copyUIAction()) @@ -697,7 +707,45 @@ struct ChatView: View { if composeState.editing { composeState = ComposeState(contextItem: .quotedItem(chatItem: ci)) } else { - composeState = composeState.copy(contextItem: .quotedItem(chatItem: ci)) + composeState = composeState.copy( + directMember: .noDirectMember, + contextItem: .quotedItem(chatItem: ci) + ) + } + } + } + } + + private func replyDirectlyUIAction(_ toDirectMember: GroupMember, _ groupInfo: GroupInfo) -> UIAction { + UIAction( + title: NSLocalizedString("Reply directly", comment: "chat item action"), + image: UIImage(systemName: "arrowshape.turn.up.left") + ) { _ in + let canSend = toDirectMember.canSendDirectlyTo(groupInfo: groupInfo) + switch canSend { + case .notConnected: + AlertManager.shared.showAlert(Alert( + title: Text("Can't send directly!"), + message: Text("Member is not connected.") + )) + case .notSupported: + AlertManager.shared.showAlert(Alert( + title: Text("Can't send directly!"), + message: Text("Member doesn't support this feature, ask them to update.") + )) + case .canSend: + withAnimation { + if composeState.editing { + composeState = ComposeState( + directMember: .directMember(groupMember: toDirectMember), + contextItem: .quotedItem(chatItem: ci) + ) + } else { + composeState = composeState.copy( + directMember: .directMember(groupMember: toDirectMember), + contextItem: .quotedItem(chatItem: ci) + ) + } } } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 56b785b02b..fa6645f816 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -273,6 +273,7 @@ struct ComposeView: View { var body: some View { VStack(spacing: 0) { + contextDirectMemberView() contextItemView() switch (composeState.editing, composeState.preview) { case (true, .filePreview): EmptyView() @@ -605,7 +606,10 @@ struct ComposeView: View { case let .directMember(groupMember): ContextDirectMemberView( directMember: groupMember, - cancelDirectMemberContext: { composeState = composeState.copy(directMember: .directMemberCancelled) } + cancelDirectMemberContext: { composeState = composeState.copy( + directMember: .directMemberCancelled, + contextItem: .noContextItem + )} ) case .directMemberCancelled: ContextDirectMemberView( diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextDirectMemberView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextDirectMemberView.swift index f523844f09..01659e5e77 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextDirectMemberView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextDirectMemberView.swift @@ -17,9 +17,11 @@ struct ContextDirectMemberView: View { var body: some View { HStack { if let directMember = directMember { - ProfileImage(imageStr: directMember.image) - .frame(width: 30, height: 30) - .padding(.trailing, 2) + if let image = directMember.image { + ProfileImage(imageStr: directMember.image) + .frame(width: 30, height: 30) + .padding(.trailing, 2) + } Text("Directly to \(directMember.chatViewName)") .lineLimit(1) } else { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index cbca88dc49..74d016d92f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1807,6 +1807,30 @@ public struct GroupMember: Identifiable, Decodable { return GroupMemberRole.allCases.filter { $0 <= userRole } } + public func allowedToSendDirectlyTo(groupInfo: GroupInfo) -> Bool { + let userRole = groupInfo.membership.memberRole + return ( + (groupInfo.fullGroupPreferences.directMessages.on && userRole >= .author) + || userRole >= .admin + || memberRole >= .admin + ) + } + + public enum CanSendDirectlyTo { + case canSend + case notConnected + case notSupported + } + + public func canSendDirectlyTo(groupInfo: GroupInfo) -> CanSendDirectlyTo { + // TODO group-direct: check version + if let activeConn = activeConn { + return .canSend + } else { + return .notConnected + } + } + public var memberIncognito: Bool { memberProfile.profileId != memberContactProfileId } @@ -1834,6 +1858,7 @@ public struct GroupMemberRef: Decodable { public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable { case observer = "observer" + case author = "author" case member = "member" case admin = "admin" case owner = "owner" @@ -1843,6 +1868,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec public var text: String { switch self { case .observer: return NSLocalizedString("observer", comment: "member role") + case .author: return NSLocalizedString("author", comment: "member role") case .member: return NSLocalizedString("member", comment: "member role") case .admin: return NSLocalizedString("admin", comment: "member role") case .owner: return NSLocalizedString("owner", comment: "member role") @@ -1852,9 +1878,10 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec private var comparisonValue: Int { switch self { case .observer: return 0 - case .member: return 1 - case .admin: return 2 - case .owner: return 3 + case .author: return 1 + case .member: return 2 + case .admin: return 3 + case .owner: return 4 } } @@ -2141,6 +2168,14 @@ public struct ChatItem: Identifiable, Decodable { } } + public var memberToReplyDirectlyTo: GroupMember? { + switch chatDir { + case let .groupSnd(directMember): return directMember + case let .groupRcv(groupMember, _): return groupMember + default: return nil + } + } + public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember)? { switch (chatInfo, chatDir) { case let (.group(groupInfo), .groupRcv(groupMember, .msGroup)): @@ -2254,7 +2289,6 @@ public struct ChatItem: Identifiable, Decodable { ) } - // TODO group-direct: possibly this has to take sendRef as parameter instead (for directMember) public static func liveDummy(_ chatType: ChatType) -> ChatItem { var item = ChatItem( chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd(directMember: nil), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index d4eda389f4..f0a59c491e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1633,7 +1633,7 @@ data class ChatItem ( @Serializable enum class MessageScope(val messageScope: String) { @SerialName("group") MSGroup("group"), - @SerialName("private") MSDirect("direct"); + @SerialName("direct") MSDirect("direct"); } @Serializable