ios: group link role, add observer role (#1978)
* ios: group link role, add observer role * prevent observers from sending in UI, clear compose state on role change
This commit is contained in:
parent
f915eb2a20
commit
be19af62d9
@ -545,6 +545,16 @@ final class Chat: ObservableObject, Identifiable {
|
|||||||
self.chatStats = chatStats
|
self.chatStats = chatStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userCanSend: Bool {
|
||||||
|
switch chatInfo {
|
||||||
|
case .direct: return true
|
||||||
|
case let .group(groupInfo):
|
||||||
|
let m = groupInfo.membership
|
||||||
|
return m.memberActive && m.memberRole >= .member
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var id: ChatId { get { chatInfo.id } }
|
var id: ChatId { get { chatInfo.id } }
|
||||||
|
|
||||||
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
||||||
|
@ -868,9 +868,15 @@ func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws
|
|||||||
throw r
|
throw r
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiCreateGroupLink(_ groupId: Int64) async throws -> String {
|
func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) {
|
||||||
let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId))
|
let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId, memberRole: memberRole))
|
||||||
if case let .groupLinkCreated(_, _, connReq) = r { return connReq }
|
if case let .groupLinkCreated(_, _, connReq, memberRole) = r { return (connReq, memberRole) }
|
||||||
|
throw r
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiGroupLinkMemberRole(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> (String, GroupMemberRole) {
|
||||||
|
let r = await chatSendCmd(.apiGroupLinkMemberRole(groupId: groupId, memberRole: memberRole))
|
||||||
|
if case let .groupLink(_, _, connReq, memberRole) = r { return (connReq, memberRole) }
|
||||||
throw r
|
throw r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -880,11 +886,11 @@ func apiDeleteGroupLink(_ groupId: Int64) async throws {
|
|||||||
throw r
|
throw r
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiGetGroupLink(_ groupId: Int64) throws -> String? {
|
func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? {
|
||||||
let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId))
|
let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId))
|
||||||
switch r {
|
switch r {
|
||||||
case let .groupLink(_, _, connReq):
|
case let .groupLink(_, _, connReq, memberRole):
|
||||||
return connReq
|
return (connReq, memberRole)
|
||||||
case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)):
|
case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)):
|
||||||
return nil
|
return nil
|
||||||
default: throw r
|
default: throw r
|
||||||
@ -1180,6 +1186,10 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
|||||||
if active(user) {
|
if active(user) {
|
||||||
m.updateGroup(toGroup)
|
m.updateGroup(toGroup)
|
||||||
}
|
}
|
||||||
|
case let .memberRole(user, groupInfo, _, _, _, _):
|
||||||
|
if active(user) {
|
||||||
|
m.updateGroup(groupInfo)
|
||||||
|
}
|
||||||
case let .rcvFileStart(user, aChatItem):
|
case let .rcvFileStart(user, aChatItem):
|
||||||
chatItemSimpleUpdate(user, aChatItem)
|
chatItemSimpleUpdate(user, aChatItem)
|
||||||
case let .rcvFileComplete(user, aChatItem):
|
case let .rcvFileComplete(user, aChatItem):
|
||||||
|
@ -258,10 +258,11 @@ struct ComposeView: View {
|
|||||||
Image(systemName: "paperclip")
|
Image(systemName: "paperclip")
|
||||||
.resizable()
|
.resizable()
|
||||||
}
|
}
|
||||||
.disabled(composeState.attachmentDisabled)
|
.disabled(composeState.attachmentDisabled || !chat.userCanSend)
|
||||||
.frame(width: 25, height: 25)
|
.frame(width: 25, height: 25)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
.padding(.leading, 12)
|
.padding(.leading, 12)
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
SendMessageView(
|
SendMessageView(
|
||||||
composeState: $composeState,
|
composeState: $composeState,
|
||||||
sendMessage: {
|
sendMessage: {
|
||||||
@ -288,6 +289,21 @@ struct ComposeView: View {
|
|||||||
)
|
)
|
||||||
.padding(.trailing, 12)
|
.padding(.trailing, 12)
|
||||||
.background(.background)
|
.background(.background)
|
||||||
|
.disabled(!chat.userCanSend)
|
||||||
|
|
||||||
|
if (!chat.userCanSend) {
|
||||||
|
Text("you are observer")
|
||||||
|
.italic()
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.onTapGesture {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title: "You can't send messages!",
|
||||||
|
message: "Please contact group admin."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: composeState.message) { _ in
|
.onChange(of: composeState.message) { _ in
|
||||||
@ -299,6 +315,13 @@ struct ComposeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: chat.userCanSend) { canSend in
|
||||||
|
if !canSend {
|
||||||
|
cancelCurrentVoiceRecording()
|
||||||
|
clearCurrentDraft()
|
||||||
|
clearState()
|
||||||
|
}
|
||||||
|
}
|
||||||
.confirmationDialog("Attach", isPresented: $showChooseSource, titleVisibility: .visible) {
|
.confirmationDialog("Attach", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||||
Button("Take picture") {
|
Button("Take picture") {
|
||||||
showTakePhoto = true
|
showTakePhoto = true
|
||||||
|
@ -17,6 +17,7 @@ struct GroupChatInfoView: View {
|
|||||||
@ObservedObject private var alertManager = AlertManager.shared
|
@ObservedObject private var alertManager = AlertManager.shared
|
||||||
@State private var alert: GroupChatInfoViewAlert? = nil
|
@State private var alert: GroupChatInfoViewAlert? = nil
|
||||||
@State private var groupLink: String?
|
@State private var groupLink: String?
|
||||||
|
@State private var groupLinkMemberRole: GroupMemberRole = .member
|
||||||
@State private var showAddMembersSheet: Bool = false
|
@State private var showAddMembersSheet: Bool = false
|
||||||
@State private var connectionStats: ConnectionStats?
|
@State private var connectionStats: ConnectionStats?
|
||||||
@State private var connectionCode: String?
|
@State private var connectionCode: String?
|
||||||
@ -107,7 +108,9 @@ struct GroupChatInfoView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
do {
|
do {
|
||||||
groupLink = try apiGetGroupLink(groupInfo.groupId)
|
if let link = try apiGetGroupLink(groupInfo.groupId) {
|
||||||
|
(groupLink, groupLinkMemberRole) = link
|
||||||
|
}
|
||||||
} catch let error {
|
} catch let error {
|
||||||
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
|
logger.error("GroupChatInfoView apiGetGroupLink: \(responseError(error))")
|
||||||
}
|
}
|
||||||
@ -187,7 +190,7 @@ struct GroupChatInfoView: View {
|
|||||||
|
|
||||||
private func groupLinkButton() -> some View {
|
private func groupLinkButton() -> some View {
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
GroupLinkView(groupId: groupInfo.groupId, groupLink: $groupLink)
|
GroupLinkView(groupId: groupInfo.groupId, groupLink: $groupLink, groupLinkMemberRole: $groupLinkMemberRole)
|
||||||
.navigationBarTitle("Group link")
|
.navigationBarTitle("Group link")
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -12,6 +12,7 @@ import SimpleXChat
|
|||||||
struct GroupLinkView: View {
|
struct GroupLinkView: View {
|
||||||
var groupId: Int64
|
var groupId: Int64
|
||||||
@Binding var groupLink: String?
|
@Binding var groupLink: String?
|
||||||
|
@Binding var groupLinkMemberRole: GroupMemberRole
|
||||||
@State private var creatingLink = false
|
@State private var creatingLink = false
|
||||||
@State private var alert: GroupLinkAlert?
|
@State private var alert: GroupLinkAlert?
|
||||||
|
|
||||||
@ -33,6 +34,15 @@ struct GroupLinkView: View {
|
|||||||
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
if let groupLink = groupLink {
|
if let groupLink = groupLink {
|
||||||
|
HStack {
|
||||||
|
Text("Initial role")
|
||||||
|
Picker("Initial role", selection: $groupLinkMemberRole) {
|
||||||
|
ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
|
||||||
|
Text(role.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
QRCode(uri: groupLink)
|
QRCode(uri: groupLink)
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
@ -85,6 +95,16 @@ struct GroupLinkView: View {
|
|||||||
return Alert(title: Text(title), message: Text(error))
|
return Alert(title: Text(title), message: Text(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: groupLinkMemberRole) { _ in
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
_ = try await apiGroupLinkMemberRole(groupId, memberRole: groupLinkMemberRole)
|
||||||
|
} catch let error {
|
||||||
|
let a = getErrorAlert(error, "Error updating group link")
|
||||||
|
alert = .error(title: a.title, error: a.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if groupLink == nil && !creatingLink {
|
if groupLink == nil && !creatingLink {
|
||||||
createGroupLink()
|
createGroupLink()
|
||||||
@ -100,7 +120,7 @@ struct GroupLinkView: View {
|
|||||||
let link = try await apiCreateGroupLink(groupId)
|
let link = try await apiCreateGroupLink(groupId)
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
creatingLink = false
|
creatingLink = false
|
||||||
groupLink = link
|
(groupLink, groupLinkMemberRole) = link
|
||||||
}
|
}
|
||||||
} catch let error {
|
} catch let error {
|
||||||
logger.error("GroupLinkView apiCreateGroupLink: \(responseError(error))")
|
logger.error("GroupLinkView apiCreateGroupLink: \(responseError(error))")
|
||||||
@ -120,8 +140,8 @@ struct GroupLinkView_Previews: PreviewProvider {
|
|||||||
@State var noGroupLink: String? = nil
|
@State var noGroupLink: String? = nil
|
||||||
|
|
||||||
return Group {
|
return Group {
|
||||||
GroupLinkView(groupId: 1, groupLink: $groupLink)
|
GroupLinkView(groupId: 1, groupLink: $groupLink, groupLinkMemberRole: Binding.constant(.member))
|
||||||
GroupLinkView(groupId: 1, groupLink: $noGroupLink)
|
GroupLinkView(groupId: 1, groupLink: $noGroupLink, groupLinkMemberRole: Binding.constant(.member))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ public enum ChatCommand {
|
|||||||
case apiLeaveGroup(groupId: Int64)
|
case apiLeaveGroup(groupId: Int64)
|
||||||
case apiListMembers(groupId: Int64)
|
case apiListMembers(groupId: Int64)
|
||||||
case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile)
|
case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile)
|
||||||
case apiCreateGroupLink(groupId: Int64)
|
case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole)
|
||||||
|
case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole)
|
||||||
case apiDeleteGroupLink(groupId: Int64)
|
case apiDeleteGroupLink(groupId: Int64)
|
||||||
case apiGetGroupLink(groupId: Int64)
|
case apiGetGroupLink(groupId: Int64)
|
||||||
case apiGetUserSMPServers(userId: Int64)
|
case apiGetUserSMPServers(userId: Int64)
|
||||||
@ -134,7 +135,8 @@ public enum ChatCommand {
|
|||||||
case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)"
|
case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)"
|
||||||
case let .apiListMembers(groupId): return "/_members #\(groupId)"
|
case let .apiListMembers(groupId): return "/_members #\(groupId)"
|
||||||
case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))"
|
case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))"
|
||||||
case let .apiCreateGroupLink(groupId): return "/_create link #\(groupId)"
|
case let .apiCreateGroupLink(groupId, memberRole): return "/_create link #\(groupId) \(memberRole)"
|
||||||
|
case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)"
|
||||||
case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)"
|
case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)"
|
||||||
case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)"
|
case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)"
|
||||||
case let .apiGetUserSMPServers(userId): return "/_smp \(userId)"
|
case let .apiGetUserSMPServers(userId): return "/_smp \(userId)"
|
||||||
@ -228,6 +230,7 @@ public enum ChatCommand {
|
|||||||
case .apiListMembers: return "apiListMembers"
|
case .apiListMembers: return "apiListMembers"
|
||||||
case .apiUpdateGroupProfile: return "apiUpdateGroupProfile"
|
case .apiUpdateGroupProfile: return "apiUpdateGroupProfile"
|
||||||
case .apiCreateGroupLink: return "apiCreateGroupLink"
|
case .apiCreateGroupLink: return "apiCreateGroupLink"
|
||||||
|
case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole"
|
||||||
case .apiDeleteGroupLink: return "apiDeleteGroupLink"
|
case .apiDeleteGroupLink: return "apiDeleteGroupLink"
|
||||||
case .apiGetGroupLink: return "apiGetGroupLink"
|
case .apiGetGroupLink: return "apiGetGroupLink"
|
||||||
case .apiGetUserSMPServers: return "apiGetUserSMPServers"
|
case .apiGetUserSMPServers: return "apiGetUserSMPServers"
|
||||||
@ -391,8 +394,8 @@ public enum ChatResponse: Decodable, Error {
|
|||||||
case connectedToGroupMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
case connectedToGroupMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||||
case groupRemoved(user: User, groupInfo: GroupInfo) // unused
|
case groupRemoved(user: User, groupInfo: GroupInfo) // unused
|
||||||
case groupUpdated(user: User, toGroup: GroupInfo)
|
case groupUpdated(user: User, toGroup: GroupInfo)
|
||||||
case groupLinkCreated(user: User, groupInfo: GroupInfo, connReqContact: String)
|
case groupLinkCreated(user: User, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole)
|
||||||
case groupLink(user: User, groupInfo: GroupInfo, connReqContact: String)
|
case groupLink(user: User, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole)
|
||||||
case groupLinkDeleted(user: User, groupInfo: GroupInfo)
|
case groupLinkDeleted(user: User, groupInfo: GroupInfo)
|
||||||
// receiving file events
|
// receiving file events
|
||||||
case rcvFileAccepted(user: User, chatItem: AChatItem)
|
case rcvFileAccepted(user: User, chatItem: AChatItem)
|
||||||
@ -606,8 +609,8 @@ public enum ChatResponse: Decodable, Error {
|
|||||||
case let .connectedToGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
case let .connectedToGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||||
case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||||
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
||||||
case let .groupLinkCreated(u, groupInfo, connReqContact): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)")
|
case let .groupLinkCreated(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)")
|
||||||
case let .groupLink(u, groupInfo, connReqContact): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)")
|
case let .groupLink(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)")
|
||||||
case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||||
case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem))
|
case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||||
case .rcvFileAcceptedSndCancelled: return noDetails
|
case .rcvFileAcceptedSndCancelled: return noDetails
|
||||||
|
@ -1546,6 +1546,7 @@ public struct GroupMemberRef: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Decodable {
|
||||||
|
case observer = "observer"
|
||||||
case member = "member"
|
case member = "member"
|
||||||
case admin = "admin"
|
case admin = "admin"
|
||||||
case owner = "owner"
|
case owner = "owner"
|
||||||
@ -1554,6 +1555,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
|
|||||||
|
|
||||||
public var text: String {
|
public var text: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .observer: return NSLocalizedString("observer", comment: "member role")
|
||||||
case .member: return NSLocalizedString("member", comment: "member role")
|
case .member: return NSLocalizedString("member", comment: "member role")
|
||||||
case .admin: return NSLocalizedString("admin", comment: "member role")
|
case .admin: return NSLocalizedString("admin", comment: "member role")
|
||||||
case .owner: return NSLocalizedString("owner", comment: "member role")
|
case .owner: return NSLocalizedString("owner", comment: "member role")
|
||||||
@ -1562,9 +1564,10 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec
|
|||||||
|
|
||||||
private var comparisonValue: Int {
|
private var comparisonValue: Int {
|
||||||
switch self {
|
switch self {
|
||||||
case .member: return 0
|
case .observer: return 0
|
||||||
case .admin: return 1
|
case .member: return 1
|
||||||
case .owner: return 2
|
case .admin: return 2
|
||||||
|
case .owner: return 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user