diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index a8f6887019..6af17e3fb8 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -82,7 +82,7 @@ enum ChatCommand: ChatCmdProtocol { case apiDeleteMemberSupportChat(groupId: Int64, groupMemberId: Int64) case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole) case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool) - case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool, notice: LinkNotice?) + case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool) case apiLeaveGroup(groupId: Int64) case apiListMembers(groupId: Int64) case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile) @@ -142,7 +142,7 @@ enum ChatCommand: ChatCmdProtocol { case apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) case apiConnect(userId: Int64, incognito: Bool, connLink: CreatedConnLink) case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) - case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode, notice: LinkNotice?) + case apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode) case apiClearChat(type: ChatType, id: Int64) case apiListContacts(userId: Int64) case apiUpdateProfile(userId: Int64, profile: Profile) @@ -265,7 +265,7 @@ enum ChatCommand: ChatCmdProtocol { let msgs = encodeJSON(composedMessages) return "/_create *\(noteFolderId) json \(msgs)" case let .apiReportMessage(groupId, chatItemId, reportReason, reportText): - return "/_report #\(groupId) \(chatItemId) reason=\(reportReason.rawValue) \(reportText)" + return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)" case let .apiUpdateChatItem(type, id, scope, itemId, um, live): return "/_update item \(ref(type, id, scope: scope)) \(itemId) live=\(onOff(live)) \(um.cmdString)" case let .apiDeleteChatItem(type, id, scope, itemIds, mode): return "/_delete item \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" @@ -301,7 +301,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiDeleteMemberSupportChat(groupId, groupMemberId): return "/_delete member chat #\(groupId) \(groupMemberId)" case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)" case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))" - case let .apiRemoveMembers(groupId, memberIds, withMessages, notice): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))\(noticeCmdString(notice))" + case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))" case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" case let .apiListMembers(groupId): return "/_members #\(groupId)" case let .apiUpdateGroupProfile(groupId, groupProfile): return "/_group_profile #\(groupId) \(encodeJSON(groupProfile))" @@ -373,7 +373,7 @@ enum ChatCommand: ChatCmdProtocol { case let .apiConnectPreparedGroup(groupId, incognito, mc): return "/_connect group #\(groupId) incognito=\(onOff(incognito))\(maybeContent(mc))" case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")" case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" - case let .apiDeleteChat(type, id, chatDeleteMode, notice): return "/_delete \(ref(type, id, scope: nil)) \(chatDeleteMode.cmdString)\(noticeCmdString(notice))" + case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id, scope: nil)) \(chatDeleteMode.cmdString)" case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id, scope: nil))" case let .apiListContacts(userId): return "/_contacts \(userId)" case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))" @@ -1522,7 +1522,6 @@ enum ContactAddressPlan: Decodable, Hashable { case connectingProhibit(contact: Contact) case known(contact: Contact) case contactViaAddress(contact: Contact) - case linkNotice(expiresAt: Date?, reason: ReportReason?) } public struct GroupShortLinkInfo: Decodable, Hashable { @@ -1539,7 +1538,6 @@ enum GroupLinkPlan: Decodable, Hashable { case known(groupInfo: GroupInfo) case noRelays(groupSLinkData_: GroupShortLinkData?) case updateRequired(groupSLinkData_: GroupShortLinkData?) - case linkNotice(expiresAt: Date?, reason: ReportReason?) } struct ChatTagData: Encodable { @@ -1556,15 +1554,6 @@ struct UpdatedMessage: Encodable { } } -struct LinkNotice: Encodable { - var ttl: Int64? - var reason: ReportReason? -} - -func noticeCmdString(_ notice: LinkNotice?) -> String { - notice.map { " notice=\(encodeJSON($0))" } ?? "" -} - enum ChatDeleteMode: Codable { case full(notify: Bool) case entity(notify: Bool) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9ffeb15076..fb1d78f650 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1079,7 +1079,7 @@ func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqTyp return nil } -private func apiConnectResponseAlert(_ r: APIResult, _ noticeTarget: LinkNoticeTarget = .link) async { +private func apiConnectResponseAlert(_ r: APIResult) async { await MainActor.run { switch r.unexpected { case .error(.invalidConnReq): @@ -1092,8 +1092,6 @@ private func apiConnectResponseAlert(_ r: APIResult, _ noticeTarget: LinkN NSLocalizedString("Unsupported connection link", comment: ""), message: NSLocalizedString("This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.", comment: "") ) - case let .error(.linkNotice(expiresAt, reason)): - showLinkNoticeAlert(noticeTarget, expiresAt: expiresAt, reason: reason) case let .error(.simplexDomainNotReady(domain, err)): switch err { case .noValidLink: @@ -1233,14 +1231,14 @@ func apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64) async throws - func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async -> Contact? { let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg)) if case let .result(.startedConnectionToContact(_, contact)) = r { return contact } - if let r { await apiConnectResponseAlert(r, .address) } + if let r { await apiConnectResponseAlert(r) } return nil } -func apiConnectPreparedGroup(groupId: Int64, isChannel: Bool, incognito: Bool, msg: MsgContent?) async -> (GroupInfo, [RelayConnectionResult])? { +func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async -> (GroupInfo, [RelayConnectionResult])? { let r: APIResult? = await chatApiSendCmdWithRetry(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg)) if case let .result(.startedConnectionToGroup(_, groupInfo, relayResults)) = r { return (groupInfo, relayResults) } - if let r { await apiConnectResponseAlert(r, isChannel ? .channel : .group) } + if let r { await apiConnectResponseAlert(r) } return nil } @@ -1253,29 +1251,23 @@ func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> Con if case let .result(.sentInvitationToContact(_, contact, _)) = r { return contact } if let r { logger.error("apiConnectContactViaAddress error: \(responseError(r.unexpected))") - await MainActor.run { - if case let .error(.linkNotice(expiresAt, reason)) = r.unexpected { - showLinkNoticeAlert(.address, expiresAt: expiresAt, reason: reason) - } else { - showAlert(connectionErrorAlert(r)) - } - } + await MainActor.run { showAlert(connectionErrorAlert(r)) } } return nil } -func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true), notice: LinkNotice? = nil) async throws { +func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws { let chatId = type.rawValue + id.description DispatchQueue.main.async { ChatModel.shared.deletedChats.insert(chatId) } defer { DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } - let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode, notice: notice), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case .direct = type, case .contactDeleted = r { return } if case .contactConnection = type, case .contactConnectionDeleted = r { return } if case .group = type, case .groupDeletedUser = r { return } throw r.unexpected } -func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true), notice: LinkNotice? = nil) async throws -> Contact { +func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws -> Contact { let type: ChatType = .direct let chatId = type.rawValue + id.description if case .full = chatDeleteMode { @@ -1286,7 +1278,7 @@ func apiDeleteContact(id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: DispatchQueue.main.async { ChatModel.shared.deletedChats.remove(chatId) } } } - let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode, notice: notice), bgTask: false) + let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChat(type: type, id: id, chatDeleteMode: chatDeleteMode), bgTask: false) if case let .contactDeleted(_, contact) = r { return contact } throw r.unexpected } @@ -1305,10 +1297,10 @@ func deleteChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: tru } } -func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true), notice: LinkNotice? = nil) async -> Alert? { +func deleteContactChat(_ chat: Chat, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async -> Alert? { do { let cInfo = chat.chatInfo - let ct = try await apiDeleteContact(id: cInfo.apiId, chatDeleteMode: chatDeleteMode, notice: notice) + let ct = try await apiDeleteContact(id: cInfo.apiId, chatDeleteMode: chatDeleteMode) await MainActor.run { switch chatDeleteMode { case .full: @@ -2034,8 +2026,8 @@ func apiDeleteMemberSupportChat(_ groupId: Int64, _ groupMemberId: Int64) async throw r.unexpected } -func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool, notice: LinkNotice? = nil) async throws -> (GroupInfo, [GroupMember]) { - let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages, notice: notice), bgTask: false) +func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool) async throws -> (GroupInfo, [GroupMember]) { + let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false) if case let .userDeletedMembers(_, updatedGroupInfo, members, _withMessages) = r { return (updatedGroupInfo, members) } throw r.unexpected } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index f8db4978ca..3d1e7ef477 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -1179,8 +1179,7 @@ private func deleteContactOrConversationDialog( showAlert: showAlert ) ) }, - id: "DeleteActiveContactDialog", - fraction: contact.activeConn?.viaUserContactLink == nil ? 0.4 : 0.55 + id: "DeleteActiveContactDialog" )) }, .cancel() @@ -1194,12 +1193,11 @@ private func deleteContactMaybeErrorAlert( _ chat: Chat, _ contact: Contact, chatDeleteMode: ChatDeleteMode, - notice: LinkNotice? = nil, _ dismissToChatList: Bool, _ showAlert: @escaping (SomeAlert) -> Void ) { Task { - let alert_ = await deleteContactChat(chat, chatDeleteMode: chatDeleteMode, notice: notice) + let alert_ = await deleteContactChat(chat, chatDeleteMode: chatDeleteMode) if let alert = alert_ { showAlert(SomeAlert(alert: alert, id: "deleteContactMaybeErrorAlert, error")) } else { @@ -1269,8 +1267,6 @@ struct DeleteActiveContactDialog: View { var dismissToChatList: Bool var showAlert: (SomeAlert) -> Void @State private var keepConversation = false - @State private var ban: LinkBan = .no - @State private var reason: ReportReason = .spam var body: some View { NavigationView { @@ -1278,20 +1274,16 @@ struct DeleteActiveContactDialog: View { Section { Toggle("Keep conversation", isOn: $keepConversation) - if contact.activeConn?.viaUserContactLink != nil { - linkBanRows("Ban from connecting", $ban, $reason) - } - Button(role: .destructive) { dismiss() - deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: false), notice: ban.notice(reason), dismissToChatList, showAlert) + deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: false), dismissToChatList, showAlert) } label: { Text("Delete without notification") } Button(role: .destructive) { dismiss() - deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: true), notice: ban.notice(reason), dismissToChatList, showAlert) + deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: contactDeleteMode.toChatDeleteMode(notify: true), dismissToChatList, showAlert) } label: { Text("Delete and notify contact") } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 3a787cc4d7..fbac41cf40 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -1209,7 +1209,7 @@ struct ComposeView: View { await sending() let mc = connectCheckLinkPreview() let incognito = chat.chatInfo.profileChangeProhibited ? chat.chatInfo.incognito : incognitoDefault - if let (groupInfo, relayResults) = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, isChannel: chat.chatInfo.isChannel, incognito: incognito, msg: mc) { + if let (groupInfo, relayResults) = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognito, msg: mc) { await MainActor.run { self.chatModel.updateGroup(groupInfo) self.chatModel.channelRelayHostnames.removeValue(forKey: groupInfo.groupId) @@ -1422,7 +1422,7 @@ struct ComposeView: View { case .spam: NSLocalizedString("Report spam: only group moderators will see it.", comment: "report reason") case .profile: NSLocalizedString("Report member profile: only group moderators will see it.", comment: "report reason") case .community: NSLocalizedString("Report violation: only group moderators will see it.", comment: "report reason") - case .content: NSLocalizedString("Report content: only group moderators will see it.", comment: "report reason") + case .illegal: NSLocalizedString("Report content: only group moderators will see it.", comment: "report reason") case .other: NSLocalizedString("Report other: only group moderators will see it.", comment: "report reason") case .unknown: "" // Should never happen } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift index 51ed07b23a..e9913053ea 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift @@ -45,16 +45,11 @@ struct ContextPendingMemberActionsView: View { } func showRejectMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) { - showMemberBanAlert( - groupInfo, - member, - NSLocalizedString("Reject member?", comment: "alert title"), - message: nil, - actions: [ - LinkBanAction(title: NSLocalizedString("Reject", comment: "alert action")) { notice in - removeMember(groupInfo, member, withMessages: false, notice: notice, dismiss: dismiss) - } - ] + showAlert( + title: NSLocalizedString("Reject member?", comment: "alert title"), + buttonTitle: "Reject", + buttonAction: { removeMember(groupInfo, member, withMessages: false, dismiss: dismiss) }, + cancelButton: true ) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 2d8382cc35..1be8070259 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -874,14 +874,7 @@ struct GroupChatInfoView: View { @ViewBuilder private func deleteGroupButton() -> some View { let label: LocalizedStringKey = groupInfo.useRelays ? "Delete channel" : groupInfo.businessChat == nil ? "Delete group" : "Delete chat" Button(role: .destructive) { - if canBanOnChatDeletion(groupInfo) { - showDeleteBusinessChatAlert(chat) { - dismiss() - chatModel.chatId = nil - } - } else { - alert = .deleteGroupAlert - } + alert = .deleteGroupAlert } label: { Label(label, systemImage: "trash") .foregroundColor(Color.red) @@ -1017,54 +1010,43 @@ func showRemoveMemberAlert(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: cancelAlertAction ]} ) + } else if groupInfo.useRelays { + showAlert( + NSLocalizedString("Remove subscriber?", comment: "alert title"), + message: NSLocalizedString("Subscriber will be removed from channel - this cannot be undone!", comment: "alert message"), + actions: {[ + UIAlertAction(title: NSLocalizedString("Remove", comment: "alert action"), style: .destructive) { _ in + removeMember(groupInfo, mem, withMessages: false, dismiss: dismiss) + }, + UIAlertAction(title: NSLocalizedString("Remove and delete messages", comment: "alert action"), style: .destructive) { _ in + removeMember(groupInfo, mem, withMessages: true, dismiss: dismiss) + }, + cancelAlertAction + ]} + ) } else { - showMemberBanAlert( - groupInfo, - mem, - groupInfo.useRelays - ? NSLocalizedString("Remove subscriber?", comment: "alert title") - : NSLocalizedString("Remove member?", comment: "alert title"), - message: groupInfo.useRelays - ? NSLocalizedString("Subscriber will be removed from channel - this cannot be undone!", comment: "alert message") - : groupInfo.businessChat == nil + showAlert( + NSLocalizedString("Remove member?", comment: "alert title"), + message: groupInfo.businessChat == nil ? NSLocalizedString("Member will be removed from group - this cannot be undone!", comment: "alert message") : NSLocalizedString("Member will be removed from chat - this cannot be undone!", comment: "alert message"), - actions: [ - LinkBanAction(title: NSLocalizedString("Remove", comment: "alert action")) { notice in - removeMember(groupInfo, mem, withMessages: false, notice: notice, dismiss: dismiss) + actions: {[ + UIAlertAction(title: NSLocalizedString("Remove", comment: "alert action"), style: .destructive) { _ in + removeMember(groupInfo, mem, withMessages: false, dismiss: dismiss) }, - LinkBanAction(title: NSLocalizedString("Remove and delete messages", comment: "alert action")) { notice in - removeMember(groupInfo, mem, withMessages: true, notice: notice, dismiss: dismiss) - } - ] + UIAlertAction(title: NSLocalizedString("Remove and delete messages", comment: "alert action"), style: .destructive) { _ in + removeMember(groupInfo, mem, withMessages: true, dismiss: dismiss) + }, + cancelAlertAction + ]} ) } } -func showMemberBanAlert(_ groupInfo: GroupInfo, _ mem: GroupMember, _ title: String, message: String?, actions: [LinkBanAction]) { - if mem.memberStatus == .memInvited { - showAlert(title, message: message) { - actions.map { a in - UIAlertAction(title: a.title, style: .destructive) { _ in a.action(nil) } - } + [cancelAlertAction] - } - } else { - showAppSheet(detents: [.medium(), .large()]) { - LinkBanDialog( - title: title, - message: message, - banLabel: groupInfo.businessChat == nil ? "Ban from joining" : "Ban from connecting", - ban: .day, - actions: actions - ) - } - } -} - -func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, withMessages: Bool, notice: LinkNotice? = nil, dismiss: DismissAction?) { +func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, withMessages: Bool, dismiss: DismissAction?) { Task { do { - let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId], withMessages, notice: notice) + let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId], withMessages) await MainActor.run { ChatModel.shared.updateGroup(updatedGroupInfo) updatedMembers.forEach { updatedMember in @@ -1087,128 +1069,6 @@ func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, withMessages: Bool } } -enum LinkBan: Identifiable, Hashable { - case no - case hour - case day - case week - case month - case permanent - - static let values: [LinkBan] = [.no, .hour, .day, .week, .month, .permanent] - - var id: Self { self } - - var text: String { - switch self { - case .no: NSLocalizedString("No", comment: "ban duration") - case .hour: NSLocalizedString("1 hour", comment: "ban duration") - case .day: NSLocalizedString("1 day", comment: "ban duration") - case .week: NSLocalizedString("1 week", comment: "ban duration") - case .month: NSLocalizedString("1 month", comment: "ban duration") - case .permanent: NSLocalizedString("Permanently", comment: "ban duration") - } - } - - func notice(_ reason: ReportReason) -> LinkNotice? { - switch self { - case .no: nil - case .hour: LinkNotice(ttl: 3600, reason: reason) - case .day: LinkNotice(ttl: 86400, reason: reason) - case .week: LinkNotice(ttl: 604800, reason: reason) - case .month: LinkNotice(ttl: 2592000, reason: reason) - case .permanent: LinkNotice(ttl: nil, reason: reason) - } - } -} - -struct LinkBanAction { - var title: String - var action: (LinkNotice?) -> Void -} - -@ViewBuilder func linkBanRows(_ label: LocalizedStringKey, _ ban: Binding, _ reason: Binding) -> some View { - WrappedPicker(label, selection: ban) { - ForEach(LinkBan.values) { Text($0.text) } - } - if ban.wrappedValue != .no { - WrappedPicker("Reason", selection: reason) { - ForEach(ReportReason.supportedReasons, id: \.self) { Text($0.text) } - } - } -} - -struct LinkBanDialog: View { - @Environment(\.dismiss) var dismiss - @EnvironmentObject var theme: AppTheme - var title: String - var message: String? - var banLabel: LocalizedStringKey - @State var ban: LinkBan - var actions: [LinkBanAction] - @State private var reason: ReportReason = .spam - - var body: some View { - NavigationView { - List { - Section { - linkBanRows(banLabel, $ban, $reason) - ForEach(actions.indices, id: \.self) { i in - Button(actions[i].title, role: .destructive) { - dismiss() - actions[i].action(ban.notice(reason)) - } - } - Button("Cancel", role: .cancel) { dismiss() } - } footer: { - if let message { - Text(message).foregroundColor(theme.colors.secondary) - } - } - } - .navigationTitle(title) - .navigationBarTitleDisplayMode(.inline) - .modifier(ThemedBackground(grouped: true)) - } - } -} - -func showDeleteBusinessChatAlert(_ chat: Chat, deleted: (() -> Void)? = nil) { - showAppSheet(detents: [.medium(), .large()]) { - LinkBanDialog( - title: NSLocalizedString("Delete chat?", comment: "alert title"), - message: chat.chatInfo.displayName + "\n\n" + NSLocalizedString("Chat will be deleted for all members - this cannot be undone!", comment: "alert message"), - banLabel: "Ban from connecting", - ban: .no, - actions: [ - LinkBanAction(title: NSLocalizedString("Delete", comment: "alert action")) { notice in - Task { - do { - try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notice: notice) - await MainActor.run { - deleted?() - ChatModel.shared.removeChat(chat.chatInfo.id) - } - } catch let error { - logger.error("showDeleteBusinessChatAlert apiDeleteChat error: \(responseError(error))") - await MainActor.run { - showAlert( - NSLocalizedString("Error deleting chat!", comment: "alert title"), - message: responseError(error) - ) - } - } - } - } - ] - ) - } -} - -func canBanOnChatDeletion(_ groupInfo: GroupInfo) -> Bool { - groupInfo.businessChat?.chatType == .customer && groupInfo.membership.memberCurrent -} - func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text { groupInfo.useRelays ? ( groupInfo.membership.memberCurrent diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 4478ba78cb..0ed78401b0 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -446,7 +446,7 @@ struct ChatListNavLink: View { if canShowGroupDelete, let gi = groupInfo, gi.canDelete { buttons.append(.destructive(Text("Delete")) { - showDeleteGroupAlert(gi) + AlertManager.shared.showAlert(deleteGroupAlert(gi)) }) } @@ -484,23 +484,13 @@ struct ChatListNavLink: View { private func deleteGroupChatButton(_ groupInfo: GroupInfo) -> some View { Button { - showDeleteGroupAlert(groupInfo) + AlertManager.shared.showAlert(deleteGroupAlert(groupInfo)) } label: { deleteLabel } .tint(.red) } - private func showDeleteGroupAlert(_ groupInfo: GroupInfo) { - if canBanOnChatDeletion(groupInfo) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - showDeleteBusinessChatAlert(chat) - } - } else { - AlertManager.shared.showAlert(deleteGroupAlert(groupInfo)) - } - } - // Spec: spec/client/chat-list.md#contactRequestNavLink private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View { ContactRequestView(contactRequest: contactRequest, chat: chat) diff --git a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift index bbb74bd9a2..9214e3ecde 100644 --- a/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift +++ b/apps/ios/Shared/Views/Contacts/ContactListNavLink.swift @@ -43,7 +43,7 @@ struct ContactListNavLink: View { .sheet(item: $sheet) { if #available(iOS 16.0, *) { $0.content - .presentationDetents([.fraction($0.fraction)]) + .presentationDetents([.fraction(0.4)]) } else { $0.content } diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift index 6f5ed979ae..d505cdead7 100644 --- a/apps/ios/Shared/Views/Helpers/AppSheet.swift +++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift @@ -32,16 +32,13 @@ private struct PrivacySensitive: ViewModifier { // Presented from the top view controller instead of a .sheet on a parent view, so an alert button or // a view that is itself in a sheet can open it. @discardableResult -func showAppSheet(detents: [UISheetPresentationController.Detent]? = nil, @ViewBuilder content: () -> Content) -> UIViewController? { +func showAppSheet(@ViewBuilder content: () -> Content) -> UIViewController? { if let topController = getTopViewController() { let v = content() .modifier(PrivacySensitive()) .environmentObject(ChatModel.shared) .environmentObject(AppTheme.shared) let sheet = UIHostingController(rootView: v) - if let detents, let presentation = sheet.sheetPresentationController { - presentation.detents = detents - } topController.present(sheet, animated: true) return sheet } diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 954f2fc81e..b558802d36 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -920,46 +920,6 @@ private func showInvitationLinkConnectingAlert(cleanup: (() -> Void)?) { ) } -enum LinkNoticeTarget { - case group - case channel - case address - case link -} - -func showLinkNoticeAlert(_ target: LinkNoticeTarget, expiresAt: Date?, reason: ReportReason?, cleanup: (() -> Void)? = nil) { - let join = target == .group || target == .channel - let banned = switch target { - case .group: NSLocalizedString("You were banned from this group.", comment: "alert message") - case .channel: NSLocalizedString("You were banned from this channel.", comment: "alert message") - case .address: NSLocalizedString("You were banned from connecting via this address.", comment: "alert message") - case .link: NSLocalizedString("You were banned from connecting via this link.", comment: "alert message") - } - let until = - if let expiresAt { - String.localizedStringWithFormat( - join - ? NSLocalizedString("You can join again after %@.", comment: "alert message") - : NSLocalizedString("You can connect again after %@.", comment: "alert message"), - expiresAt.formatted(date: .abbreviated, time: .shortened) - ) - } else { - join - ? NSLocalizedString("You can't join it again.", comment: "alert message") - : NSLocalizedString("You can't connect again.", comment: "alert message") - } - let reasonLine: String? = - if case .some(.unknown) = reason { nil } - else { reason.map { String.localizedStringWithFormat(NSLocalizedString("Reason: %@.", comment: "alert message"), $0.text) } } - showAlert( - NSLocalizedString("Banned", comment: "alert title"), - message: [banned, until, reasonLine].compactMap { $0 }.joined(separator: "\n"), - actions: {[ - okCleanupAlertAction(cleanup: cleanup) - ]} - ) -} - private func showGroupLinkConnectingAlert(groupInfo: GroupInfo?, cleanup: (() -> Void)?) { if let groupInfo = groupInfo { if groupInfo.businessChat == nil { @@ -1557,11 +1517,6 @@ func planAndConnect( cleanup: cleanup ) } - case let .linkNotice(expiresAt, reason): - logger.debug("planAndConnect, .contactAddress, .linkNotice") - await MainActor.run { - showLinkNoticeAlert(.address, expiresAt: expiresAt, reason: reason, cleanup: cleanup) - } } case let .groupLink(glp): switch glp { @@ -1692,14 +1647,6 @@ func planAndConnect( cleanup?() } } - case let .linkNotice(expiresAt, reason): - logger.debug("planAndConnect, .groupLink, .linkNotice") - let isChannel = - if case .link(_, .channel, _) = strConnectTarget(shortOrFullLink) { true } - else { planSimplexName?.nameType == .publicGroup } - await MainActor.run { - showLinkNoticeAlert(isChannel ? .channel : .group, expiresAt: expiresAt, reason: reason, cleanup: cleanup) - } } case let .error(chatError): logger.debug("planAndConnect, .error \(chatErrorString(chatError))") diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f6016e0afb..6f5f62daf3 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -768,7 +768,6 @@ public enum ChatErrorType: Decodable, Hashable { case unsupportedConnReq case invalidChatMessage(connection: Connection, message: String) case connReqMessageProhibited - case linkNotice(expiresAt: Date?, reason: ReportReason?) case contactNotReady(contact: Contact) case contactNotActive(contact: Contact) case contactDisabled(contact: Contact) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a040e8b1f9..8bdd952372 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2638,7 +2638,6 @@ public struct Connection: Decodable, Hashable { public var peerChatVRange: VersionRange public var connStatus: ConnStatus public var connLevel: Int - public var viaUserContactLink: Int64? public var viaGroupLink: Bool public var customUserProfileId: Int64? public var connectionCode: SecurityCode? @@ -2652,7 +2651,7 @@ public struct Connection: Decodable, Hashable { public var connectionStats: ConnectionStats? = nil private enum CodingKeys: String, CodingKey { - case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaUserContactLink, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter + case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter } public var id: ChatId { get { ":\(connId)" } } @@ -5928,29 +5927,18 @@ public enum FormatColor: String, Decodable, Hashable { public enum ReportReason: Hashable { case spam - case content + case illegal case community case profile case other case unknown(type: String) - public static var supportedReasons: [ReportReason] = [.spam, .content, .community, .profile, .other] - - public var rawValue: String { - switch self { - case .spam: "spam" - case .content: "content" - case .community: "community" - case .profile: "profile" - case .other: "other" - case let .unknown(type): type - } - } + public static var supportedReasons: [ReportReason] = [.spam, .illegal, .community, .profile, .other] public var text: String { switch self { case .spam: return NSLocalizedString("Spam", comment: "report reason") - case .content: return NSLocalizedString("Inappropriate content", comment: "report reason") + case .illegal: return NSLocalizedString("Inappropriate content", comment: "report reason") case .community: return NSLocalizedString("Community guidelines violation", comment: "report reason") case .profile: return NSLocalizedString("Inappropriate profile", comment: "report reason") case .other: return NSLocalizedString("Another reason", comment: "report reason") @@ -5970,7 +5958,14 @@ public enum ReportReason: Hashable { extension ReportReason: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() - try container.encode(rawValue) + switch self { + case .spam: try container.encode("spam") + case .illegal: try container.encode("illegal") + case .community: try container.encode("community") + case .profile: try container.encode("profile") + case .other: try container.encode("other") + case let .unknown(type): try container.encode(type) + } } } @@ -5980,7 +5975,7 @@ extension ReportReason: Decodable { let type = try container.decode(String.self) switch type { case "spam": self = .spam - case "content", "illegal": self = .content + case "illegal": self = .illegal case "community": self = .community case "profile": self = .profile case "other": self = .other 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 842be9b5ab..09c494dc76 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 @@ -2053,7 +2053,6 @@ data class Connection( val peerChatVRange: VersionRange, val connStatus: ConnStatus, val connLevel: Int, - val viaUserContactLink: Long? = null, val viaGroupLink: Boolean, val customUserProfileId: Long? = null, val connectionCode: SecurityCode? = null, @@ -5415,19 +5414,19 @@ enum class FormatColor(val color: String) { @Serializable(with = ReportReasonSerializer::class) sealed class ReportReason { @Serializable @SerialName("spam") object Spam: ReportReason() - @Serializable @SerialName("content") object Content: ReportReason() + @Serializable @SerialName("illegal") object Illegal: ReportReason() @Serializable @SerialName("community") object Community: ReportReason() @Serializable @SerialName("profile") object Profile: ReportReason() @Serializable @SerialName("other") object Other: ReportReason() @Serializable @SerialName("unknown") data class Unknown(val type: String): ReportReason() companion object { - val supportedReasons: List = listOf(Spam, Content, Community, Profile, Other) + val supportedReasons: List = listOf(Spam, Illegal, Community, Profile, Other) } val text: String get() = when (this) { Spam -> generalGetString(MR.strings.report_reason_spam) - Content -> generalGetString(MR.strings.report_reason_illegal) + Illegal -> generalGetString(MR.strings.report_reason_illegal) Community -> generalGetString(MR.strings.report_reason_community) Profile -> generalGetString(MR.strings.report_reason_profile) Other -> generalGetString(MR.strings.report_reason_other) @@ -5442,7 +5441,7 @@ object ReportReasonSerializer : KSerializer { override fun deserialize(decoder: Decoder): ReportReason { return when (val value = decoder.decodeString()) { "spam" -> ReportReason.Spam - "content", "illegal" -> ReportReason.Content + "illegal" -> ReportReason.Illegal "community" -> ReportReason.Community "profile" -> ReportReason.Profile "other" -> ReportReason.Other @@ -5453,7 +5452,7 @@ object ReportReasonSerializer : KSerializer { override fun serialize(encoder: Encoder, value: ReportReason) { val stringValue = when (value) { is ReportReason.Spam -> "spam" - is ReportReason.Content -> "content" + is ReportReason.Illegal -> "illegal" is ReportReason.Community -> "community" is ReportReason.Profile -> "profile" is ReportReason.Other -> "other" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 2d1db57b0e..3508221858 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -36,8 +36,6 @@ import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert import chat.simplex.common.views.chatlist.openGroupChat import chat.simplex.common.views.database.deleteDatabaseBackups import chat.simplex.common.views.migration.MigrationFileLinkData -import chat.simplex.common.views.newchat.LinkNoticeTarget -import chat.simplex.common.views.newchat.showLinkNoticeAlert import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.networkAndServers.defaultConditionsLink @@ -1699,12 +1697,8 @@ object ChatController { return null } - private fun apiConnectResponseAlert(r: API, noticeTarget: LinkNoticeTarget = LinkNoticeTarget.Link) { + private fun apiConnectResponseAlert(r: API) { when { - r is API.Error && r.err is ChatError.ChatErrorChat - && r.err.errorType is ChatErrorType.LinkNotice -> { - showLinkNoticeAlert(noticeTarget, r.err.errorType.expiresAt, r.err.errorType.reason) - } r is API.Error && r.err is ChatError.ChatErrorChat && r.err.errorType is ChatErrorType.InvalidConnReq -> { AlertManager.shared.showAlertMsg( @@ -1865,17 +1859,17 @@ object ChatController { if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact if (r != null) { Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}") - apiConnectResponseAlert(r, LinkNoticeTarget.Address) + apiConnectResponseAlert(r) } return null } - suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, isChannel: Boolean, incognito: Boolean, msg: MsgContent?): Pair>? { + suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean, msg: MsgContent?): Pair>? { val r = sendCmdWithRetry(rh, CC.APIConnectPreparedGroup(groupId, incognito, msg)) if (r is API.Result && r.res is CR.StartedConnectionToGroup) return Pair(r.res.groupInfo, r.res.relayResults) if (r != null) { Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}") - apiConnectResponseAlert(r, if (isChannel) LinkNoticeTarget.Channel else LinkNoticeTarget.Group) + apiConnectResponseAlert(r) } return null } @@ -1885,26 +1879,24 @@ object ChatController { val r = sendCmdWithRetry(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId)) if (r is API.Result && r.res is CR.SentInvitationToContact) return r.res.contact if (r == null) return null - if (r is API.Error && r.err is ChatError.ChatErrorChat && r.err.errorType is ChatErrorType.LinkNotice) { - showLinkNoticeAlert(LinkNoticeTarget.Address, r.err.errorType.expiresAt, r.err.errorType.reason) - } else if (!(networkErrorAlert(r))) { + if (!(networkErrorAlert(r))) { apiErrorAlert("apiConnectContactViaAddress", generalGetString(MR.strings.connection_error), r) } return null } - suspend fun deleteChat(chat: Chat, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true), notice: LinkNotice? = null) { + suspend fun deleteChat(chat: Chat, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)) { val cInfo = chat.chatInfo - if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, chatDeleteMode = chatDeleteMode, notice = notice)) { + if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, chatDeleteMode = chatDeleteMode)) { withContext(Dispatchers.Main) { chatModel.chatsContext.removeChat(chat.remoteHostId, cInfo.id) } } } - suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true), notice: LinkNotice? = null): Boolean { + suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)): Boolean { chatModel.deletedChats.value += rh to type.type + id - val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode, notice)) + val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode)) val res = r.result val success = when { res is CR.ContactDeleted && type == ChatType.Direct -> true @@ -1926,10 +1918,10 @@ object ChatController { return success } - suspend fun apiDeleteContact(rh: Long?, id: Long, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true), notice: LinkNotice? = null): Contact? { + suspend fun apiDeleteContact(rh: Long?, id: Long, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)): Contact? { val type = ChatType.Direct chatModel.deletedChats.value += rh to type.type + id - val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode, notice)) + val r = sendCmd(rh, CC.ApiDeleteChat(type, id, chatDeleteMode)) val contact = if (r is API.Result && r.res is CR.ContactDeleted) { r.res.contact } else { @@ -2505,8 +2497,8 @@ object ChatController { return null } - suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean, notice: LinkNotice? = null): Pair>? { - val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages, notice)) + suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean): Pair>? { + val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages)) if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.groupInfo to r.res.members if (!(networkErrorAlert(r))) { apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r) @@ -4052,7 +4044,7 @@ sealed class CC { class ApiDeleteMemberSupportChat(val groupId: Long, val groupMemberId: Long): CC() class ApiMembersRole(val groupId: Long, val memberIds: List, val memberRole: GroupMemberRole): CC() class ApiBlockMembersForAll(val groupId: Long, val memberIds: List, val blocked: Boolean): CC() - class ApiRemoveMembers(val groupId: Long, val memberIds: List, val withMessages: Boolean, val notice: LinkNotice?): CC() + class ApiRemoveMembers(val groupId: Long, val memberIds: List, val withMessages: Boolean): CC() class ApiLeaveGroup(val groupId: Long): CC() class ApiListMembers(val groupId: Long): CC() class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC() @@ -4112,7 +4104,7 @@ sealed class CC { class APIConnectPreparedGroup(val groupId: Long, val incognito: Boolean, val msg: MsgContent?): CC() class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): CC() class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC() - class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode, val notice: LinkNotice?): CC() + class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC() class ApiClearChat(val type: ChatType, val id: Long): CC() class ApiListContacts(val userId: Long): CC() class ApiUpdateProfile(val userId: Long, val profile: Profile): CC() @@ -4270,7 +4262,7 @@ sealed class CC { is ApiDeleteMemberSupportChat -> "/_delete member chat #$groupId $groupMemberId" is ApiMembersRole -> "/_member role #$groupId ${memberIds.joinToString(",")} ${memberRole.memberRole}" is ApiBlockMembersForAll -> "/_block #$groupId ${memberIds.joinToString(",")} blocked=${onOff(blocked)}" - is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}${maybeNotice(notice)}" + is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}" is ApiLeaveGroup -> "/_leave #$groupId" is ApiListMembers -> "/_members #$groupId" is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}" @@ -4333,7 +4325,7 @@ sealed class CC { is APIConnectPreparedGroup -> "/_connect group #$groupId incognito=${onOff(incognito)}${maybeContent(msg)}" is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}" is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId" - is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}${maybeNotice(notice)}" + is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}" is ApiClearChat -> "/_clear chat ${chatRef(type, id, scope = null)}" is ApiListContacts -> "/_contacts $userId" is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}" @@ -4633,8 +4625,6 @@ sealed class CC { } } - private fun maybeNotice(notice: LinkNotice?): String = if (notice == null) "" else " notice=" + json.encodeToString(notice) - companion object { fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) { null -> "${chatType.type}${id}" @@ -7430,9 +7420,6 @@ sealed class ChatDeleteMode { } } -@Serializable -data class LinkNotice(val ttl: Long? = null, val reason: ReportReason? = null) - @Serializable data class CreatedConnLink(val connFullLink: String, val connShortLink: String?) { fun simplexChatUri(short: Boolean): String = @@ -7616,7 +7603,6 @@ sealed class ContactAddressPlan { @Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan() @Serializable @SerialName("known") class Known(val contact: Contact): ContactAddressPlan() @Serializable @SerialName("contactViaAddress") class ContactViaAddress(val contact: Contact): ContactAddressPlan() - @Serializable @SerialName("linkNotice") class LinkNotice(val expiresAt: Instant? = null, val reason: ReportReason? = null): ContactAddressPlan() } @Serializable @@ -7628,7 +7614,6 @@ sealed class GroupLinkPlan { @Serializable @SerialName("known") class Known(val groupInfo: GroupInfo): GroupLinkPlan() @Serializable @SerialName("noRelays") class NoRelays(val groupSLinkData_: GroupShortLinkData? = null): GroupLinkPlan() @Serializable @SerialName("updateRequired") class UpdateRequired(val groupSLinkData_: GroupShortLinkData? = null): GroupLinkPlan() - @Serializable @SerialName("linkNotice") class LinkNotice(val expiresAt: Instant? = null, val reason: ReportReason? = null): GroupLinkPlan() } abstract class TerminalItem { @@ -7937,7 +7922,6 @@ sealed class ChatErrorType { is UnsupportedConnReq -> "unsupportedConnReq" is InvalidChatMessage -> "invalidChatMessage" is ConnReqMessageProhibited -> "connReqMessageProhibited" - is LinkNotice -> "linkNotice" is ContactNotReady -> "contactNotReady" is ContactNotActive -> "contactNotActive" is ContactDisabled -> "contactDisabled" @@ -8023,7 +8007,6 @@ sealed class ChatErrorType { @Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType() @Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType() @Serializable @SerialName("connReqMessageProhibited") object ConnReqMessageProhibited: ChatErrorType() - @Serializable @SerialName("linkNotice") class LinkNotice(val expiresAt: Instant? = null, val reason: ReportReason? = null): ChatErrorType() @Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType() @Serializable @SerialName("contactNotActive") class ContactNotActive(val contact: Contact): ChatErrorType() @Serializable @SerialName("contactDisabled") class ContactDisabled(val contact: Contact): ChatErrorType() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 7ed2c0db74..82575984f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -40,8 +40,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.common.views.chat.group.ChatTTLOption -import chat.simplex.common.views.chat.group.LinkBan -import chat.simplex.common.views.chat.group.LinkBanRows import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.chatlist.updateChatSettings import chat.simplex.common.views.newchat.* @@ -307,8 +305,6 @@ sealed class ContactDeleteMode { private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)? = null) { val contactDeleteMode = mutableStateOf(ContactDeleteMode.Full()) - val ban = mutableStateOf(LinkBan.No) - val reason = mutableStateOf(ReportReason.Spam) AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.delete_contact_question), @@ -334,13 +330,10 @@ private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: C ) } } - if (contact.activeConn?.viaUserContactLink != null) { - LinkBanRows(stringResource(MR.strings.link_ban_from_connecting), ban, reason) - } // Delete without notification SectionItemView({ AlertManager.shared.hideAlert() - deleteContact(chat, chatModel, close, chatDeleteMode = contactDeleteMode.value.toChatDeleteMode(notify = false), notice = ban.value.notice(reason.value)) + deleteContact(chat, chatModel, close, chatDeleteMode = contactDeleteMode.value.toChatDeleteMode(notify = false)) if (contactDeleteMode.value is ContactDeleteMode.Entity && chatModel.controller.appPrefs.showDeleteContactNotice.get()) { showDeleteContactNotice(contact) } @@ -350,7 +343,7 @@ private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: C // Delete contact and notify SectionItemView({ AlertManager.shared.hideAlert() - deleteContact(chat, chatModel, close, chatDeleteMode = contactDeleteMode.value.toChatDeleteMode(notify = true), notice = ban.value.notice(reason.value)) + deleteContact(chat, chatModel, close, chatDeleteMode = contactDeleteMode.value.toChatDeleteMode(notify = true)) if (contactDeleteMode.value is ContactDeleteMode.Entity && chatModel.controller.appPrefs.showDeleteContactNotice.get()) { showDeleteContactNotice(contact) } @@ -475,11 +468,11 @@ private fun showDeleteContactNotice(contact: Contact) { ) } -fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true), notice: LinkNotice? = null) { +fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)) { val chatInfo = chat.chatInfo withBGApi { val chatRh = chat.remoteHostId - val ct = chatModel.controller.apiDeleteContact(chatRh, chatInfo.apiId, chatDeleteMode, notice) + val ct = chatModel.controller.apiDeleteContact(chatRh, chatInfo.apiId, chatDeleteMode) if (ct != null) { withContext(Dispatchers.Main) { when (chatDeleteMode) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt index c1ae1c6a3a..8cda1226fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt @@ -13,10 +13,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import chat.simplex.common.model.* import chat.simplex.common.platform.chatModel -import chat.simplex.common.views.chat.group.LinkBanAction import chat.simplex.common.views.chat.group.removeMember import chat.simplex.common.views.chat.group.removeMemberDialog -import chat.simplex.common.views.chat.group.showMemberBanAlert import chat.simplex.common.views.helpers.* import chat.simplex.res.* import dev.icerock.moko.resources.compose.stringResource @@ -73,16 +71,13 @@ fun ComposeContextPendingMemberActionsView( } fun rejectMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { - showMemberBanAlert( - groupInfo, - showBan = true, + AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.reject_pending_member_alert_title), - text = null, - actions = listOf( - LinkBanAction(generalGetString(MR.strings.reject_pending_member_button)) { notice -> - removeMember(rhId, groupInfo, member, withMessages = false, chatModel, close, notice) - } - ) + confirmText = generalGetString(MR.strings.reject_pending_member_button), + onConfirm = { + removeMember(rhId, groupInfo, member, withMessages = false, chatModel, close) + }, + destructive = true, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index a44af1df40..357a010d2c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -698,7 +698,6 @@ fun ComposeView( val result = chatModel.controller.apiConnectPreparedGroup( rh = chat.remoteHostId, groupId = chat.chatInfo.apiId, - isChannel = chat.chatInfo.isChannel, incognito = incognito, msg = mc ) @@ -1240,7 +1239,7 @@ fun ComposeView( fun ReportReasonView(reason: ReportReason) { val reportText = when (reason) { is ReportReason.Spam -> generalGetString(MR.strings.report_compose_reason_header_spam) - is ReportReason.Content -> generalGetString(MR.strings.report_compose_reason_header_illegal) + is ReportReason.Illegal -> generalGetString(MR.strings.report_compose_reason_header_illegal) is ReportReason.Profile -> generalGetString(MR.strings.report_compose_reason_header_profile) is ReportReason.Community -> generalGetString(MR.strings.report_compose_reason_header_community) is ReportReason.Other -> generalGetString(MR.strings.report_compose_reason_header_other) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index f9954f31d8..973b684d38 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -225,41 +225,29 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl if (groupInfo.membership.memberCurrent) MR.strings.delete_chat_for_all_members_cannot_undo_warning else MR.strings.delete_chat_for_self_cannot_undo_warning } - val text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}" - val deleteGroup: (LinkNotice?) -> Unit = { notice -> - withBGApi { - val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId, notice = notice) - if (r) { - withContext(Dispatchers.Main) { - chatModel.chatsContext.removeChat(chat.remoteHostId, chatInfo.id) - if (chatModel.chatId.value == chatInfo.id) { - chatModel.chatId.value = null - ModalManager.end.closeModals() + AlertManager.shared.showAlertDialog( + title = generalGetString(titleId), + text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}", + parseHtml = false, + confirmText = generalGetString(MR.strings.delete_verb), + onConfirm = { + withBGApi { + val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId) + if (r) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.removeChat(chat.remoteHostId, chatInfo.id) + if (chatModel.chatId.value == chatInfo.id) { + chatModel.chatId.value = null + ModalManager.end.closeModals() + } + ntfManager.cancelNotificationsForChat(chatInfo.id) + close?.invoke() } - ntfManager.cancelNotificationsForChat(chatInfo.id) - close?.invoke() } } - } - } - if (canBanOnChatDeletion(groupInfo)) { - showLinkBanAlert( - generalGetString(titleId), - text, - generalGetString(MR.strings.link_ban_from_connecting), - LinkBan.No, - listOf(LinkBanAction(generalGetString(MR.strings.delete_verb), deleteGroup)) - ) - } else { - AlertManager.shared.showAlertDialog( - title = generalGetString(titleId), - text = text, - parseHtml = false, - confirmText = generalGetString(MR.strings.delete_verb), - onConfirm = { deleteGroup(null) }, - destructive = true, - ) - } + }, + destructive = true, + ) } fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { @@ -313,25 +301,61 @@ fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMember) { } } }) - } else { - showMemberBanAlert( - groupInfo, - showBan = mem.memberStatus != GroupMemberStatus.MemInvited, - title = generalGetString(if (groupInfo.useRelays) MR.strings.button_remove_subscriber_question else MR.strings.button_remove_member_question), - text = generalGetString( - if (groupInfo.useRelays) MR.strings.subscriber_will_be_removed_from_channel_cannot_be_undone - else if (groupInfo.businessChat == null) MR.strings.member_will_be_removed_from_group_cannot_be_undone - else MR.strings.member_will_be_removed_from_chat_cannot_be_undone - ), - actions = listOf( - LinkBanAction(generalGetString(MR.strings.remove_member_confirmation)) { notice -> - removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = false, notice = notice) - }, - LinkBanAction(generalGetString(MR.strings.remove_member_delete_messages_confirmation)) { notice -> - removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = true, notice = notice) + } else if (groupInfo.useRelays) { + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.button_remove_subscriber_question), + generalGetString(MR.strings.subscriber_will_be_removed_from_channel_cannot_be_undone), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = false) + }) { + Text(generalGetString(MR.strings.remove_member_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = true) + }) { + Text(generalGetString(MR.strings.remove_member_delete_messages_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } } - ) - ) + }) + } else { + val titleId = MR.strings.button_remove_member_question + val messageId = if (groupInfo.businessChat == null) + MR.strings.member_will_be_removed_from_group_cannot_be_undone + else + MR.strings.member_will_be_removed_from_chat_cannot_be_undone + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(titleId), + generalGetString(messageId), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = false) + }) { + Text(generalGetString(MR.strings.remove_member_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, listOf(mem.groupMemberId), withMessages = true) + }) { + Text(generalGetString(MR.strings.remove_member_delete_messages_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + }) } } @@ -340,75 +364,22 @@ private fun removeMembersAlert(rhId: Long?, groupInfo: GroupInfo, memberIds: Lis MR.strings.members_will_be_removed_from_group_cannot_be_undone else MR.strings.members_will_be_removed_from_chat_cannot_be_undone - showMemberBanAlert( - groupInfo, - showBan = true, - title = generalGetString(MR.strings.button_remove_members_question), - text = generalGetString(messageId), - actions = listOf( - LinkBanAction(generalGetString(MR.strings.remove_member_confirmation)) { notice -> - removeMembers(rhId, groupInfo, memberIds, withMessages = false, notice = notice, onSuccess = onSuccess) - }, - LinkBanAction(generalGetString(MR.strings.remove_member_delete_messages_confirmation)) { notice -> - removeMembers(rhId, groupInfo, memberIds, withMessages = true, notice = notice, onSuccess = onSuccess) - } - ) - ) -} - -enum class LinkBan { - No, Hour, Day, Week, Month, Permanent; - - val text: String get() = generalGetString( - when (this) { - No -> MR.strings.link_ban_no - Hour -> MR.strings.link_ban_1_hour - Day -> MR.strings.link_ban_1_day - Week -> MR.strings.link_ban_1_week - Month -> MR.strings.link_ban_1_month - Permanent -> MR.strings.link_ban_permanently - } - ) - - fun notice(reason: ReportReason): LinkNotice? = when (this) { - No -> null - Hour -> LinkNotice(ttl = 3600, reason = reason) - Day -> LinkNotice(ttl = 86400, reason = reason) - Week -> LinkNotice(ttl = 604800, reason = reason) - Month -> LinkNotice(ttl = 2592000, reason = reason) - Permanent -> LinkNotice(ttl = null, reason = reason) - } -} - -class LinkBanAction(val title: String, val action: (LinkNotice?) -> Unit) - -@Composable -fun LinkBanRows(label: String, ban: MutableState, reason: MutableState) { - ExposedDropDownSettingRow(label, LinkBan.entries.map { it to it.text }, ban, onSelected = { ban.value = it }) - if (ban.value != LinkBan.No) { - ExposedDropDownSettingRow(generalGetString(MR.strings.link_ban_reason), ReportReason.supportedReasons.map { it to it.text }, reason, onSelected = { reason.value = it }) - } -} - -fun showLinkBanAlert(title: String, text: String?, banLabel: String?, ban: LinkBan, actions: List) { - val banState = mutableStateOf(ban) - val reason = mutableStateOf(ReportReason.Spam) AlertManager.shared.showAlertDialogButtonsColumn( - title, - text, - parseHtml = false, + generalGetString(MR.strings.button_remove_members_question), + generalGetString(messageId), buttons = { Column { - if (banLabel != null) { - LinkBanRows(banLabel, banState, reason) + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, memberIds, withMessages = false, onSuccess = onSuccess) + }) { + Text(generalGetString(MR.strings.remove_member_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) } - actions.forEach { a -> - SectionItemView({ - AlertManager.shared.hideAlert() - a.action(banState.value.notice(reason.value)) - }) { - Text(a.title, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) - } + SectionItemView({ + AlertManager.shared.hideAlert() + removeMembers(rhId, groupInfo, memberIds, withMessages = true, onSuccess = onSuccess) + }) { + Text(generalGetString(MR.strings.remove_member_delete_messages_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) } SectionItemView({ AlertManager.shared.hideAlert() @@ -419,18 +390,6 @@ fun showLinkBanAlert(title: String, text: String?, banLabel: String?, ban: LinkB }) } -fun showMemberBanAlert(groupInfo: GroupInfo, showBan: Boolean, title: String, text: String?, actions: List) { - if (showBan) { - val banLabel = generalGetString(if (groupInfo.businessChat == null) MR.strings.link_ban_from_joining else MR.strings.link_ban_from_connecting) - showLinkBanAlert(title, text, banLabel, LinkBan.Day, actions) - } else { - showLinkBanAlert(title, text, null, LinkBan.No, actions) - } -} - -fun canBanOnChatDeletion(groupInfo: GroupInfo): Boolean = - groupInfo.businessChat?.chatType == BusinessChatType.Customer && groupInfo.membership.memberCurrent - @Composable fun SearchButton( modifier: Modifier, @@ -1442,9 +1401,9 @@ private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel) } } -fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, withMessages: Boolean, notice: LinkNotice? = null, onSuccess: () -> Unit = {}) { +fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, withMessages: Boolean, onSuccess: () -> Unit = {}) { withBGApi { - val r = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds, withMessages = withMessages, notice = notice) + val r = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds, withMessages = withMessages) if (r != null) { val (updatedGroupInfo, updatedMembers) = r withContext(Dispatchers.Main) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 6361ee45ba..ac1c6915b4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -276,25 +276,60 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c } } }) - } else { - showMemberBanAlert( - groupInfo, - showBan = member.memberStatus != GroupMemberStatus.MemInvited, - title = generalGetString(if (groupInfo.useRelays) MR.strings.button_remove_subscriber_question else MR.strings.button_remove_member_question), - text = generalGetString( - if (groupInfo.useRelays) MR.strings.subscriber_will_be_removed_from_channel_cannot_be_undone - else if (groupInfo.businessChat == null) MR.strings.member_will_be_removed_from_group_cannot_be_undone - else MR.strings.member_will_be_removed_from_chat_cannot_be_undone - ), - actions = listOf( - LinkBanAction(generalGetString(MR.strings.remove_member_confirmation)) { notice -> - removeMember(rhId, groupInfo, member, withMessages = false, chatModel, close, notice) - }, - LinkBanAction(generalGetString(MR.strings.remove_member_delete_messages_confirmation)) { notice -> - removeMember(rhId, groupInfo, member, withMessages = true, chatModel, close, notice) + } else if (groupInfo.useRelays) { + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.button_remove_subscriber_question), + generalGetString(MR.strings.subscriber_will_be_removed_from_channel_cannot_be_undone), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + removeMember(rhId, groupInfo, member, withMessages = false, chatModel, close) + }) { + Text(generalGetString(MR.strings.remove_member_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + removeMember(rhId, groupInfo, member, withMessages = true, chatModel, close) + }) { + Text(generalGetString(MR.strings.remove_member_delete_messages_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } } - ) - ) + }) + } else { + val messageId = if (groupInfo.businessChat == null) + MR.strings.member_will_be_removed_from_group_cannot_be_undone + else + MR.strings.member_will_be_removed_from_chat_cannot_be_undone + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.button_remove_member_question), + generalGetString(messageId), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + removeMember(rhId, groupInfo, member, withMessages = false, chatModel, close) + }) { + Text(generalGetString(MR.strings.remove_member_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + removeMember(rhId, groupInfo, member, withMessages = true, chatModel, close) + }) { + Text(generalGetString(MR.strings.remove_member_delete_messages_confirmation), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + }) } } @@ -310,9 +345,9 @@ fun deleteMemberMessagesDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupM ) } -fun removeMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, withMessages: Boolean, chatModel: ChatModel, close: (() -> Unit)? = null, notice: LinkNotice? = null) { +fun removeMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, withMessages: Boolean, chatModel: ChatModel, close: (() -> Unit)? = null) { withBGApi { - val r = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId), withMessages = withMessages, notice = notice) + val r = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId), withMessages = withMessages) if (r != null) { val (updatedGroupInfo, removedMembers) = r withContext(Dispatchers.Main) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index 81701f1816..8b145ef206 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -16,7 +16,6 @@ import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* import chat.simplex.res.* import kotlinx.coroutines.* -import kotlinx.datetime.Instant enum class ConnectionLinkType { INVITATION, CONTACT, GROUP @@ -245,11 +244,6 @@ private suspend fun planAndConnectTask( cleanup() } } - is ContactAddressPlan.LinkNotice -> { - Log.d(TAG, "planAndConnect, .ContactAddress, .LinkNotice") - showLinkNoticeAlert(LinkNoticeTarget.Address, connectionPlan.contactAddressPlan.expiresAt, connectionPlan.contactAddressPlan.reason) - cleanup() - } } is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) { is GroupLinkPlan.Ok -> @@ -394,12 +388,6 @@ private suspend fun planAndConnectTask( cleanup() } } - is GroupLinkPlan.LinkNotice -> { - Log.d(TAG, "planAndConnect, .GroupLink, .LinkNotice") - val isChannel = (target as? ConnectTarget.Link)?.linkType == SimplexLinkType.channel || planSimplexName?.nameType == SimplexNameType.publicGroup - showLinkNoticeAlert(if (isChannel) LinkNoticeTarget.Channel else LinkNoticeTarget.Group, connectionPlan.groupLinkPlan.expiresAt, connectionPlan.groupLinkPlan.reason) - cleanup() - } } is ConnectionPlan.Error -> { Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}") @@ -448,33 +436,6 @@ suspend fun connectViaUri( return pcc != null } -enum class LinkNoticeTarget { - Group, Channel, Address, Link -} - -fun showLinkNoticeAlert(target: LinkNoticeTarget, expiresAt: Instant?, reason: ReportReason?) { - val join = target == LinkNoticeTarget.Group || target == LinkNoticeTarget.Channel - val banned = generalGetString( - when (target) { - LinkNoticeTarget.Group -> MR.strings.link_ban_alert_group - LinkNoticeTarget.Channel -> MR.strings.link_ban_alert_channel - LinkNoticeTarget.Address -> MR.strings.link_ban_alert_address - LinkNoticeTarget.Link -> MR.strings.link_ban_alert_link - } - ) - val until = - if (expiresAt != null) { - String.format(generalGetString(if (join) MR.strings.link_ban_alert_join_after else MR.strings.link_ban_alert_connect_after), localTimestamp(expiresAt)) - } else { - generalGetString(if (join) MR.strings.link_ban_alert_cant_join else MR.strings.link_ban_alert_cant_connect) - } - val reasonLine = if (reason == null || reason is ReportReason.Unknown) null else String.format(generalGetString(MR.strings.link_ban_alert_reason), reason.text) - AlertManager.privacySensitive.showAlertMsg( - generalGetString(MR.strings.link_ban_alert_title), - listOfNotNull(banned, until, reasonLine).joinToString("\n") - ) -} - fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType? { return when(connectionPlan) { is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION diff --git a/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml b/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml index f8ebd88f56..cd80492607 100644 --- a/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/moko-resources/base/strings.xml @@ -2039,25 +2039,6 @@ Remove and delete messages Delete messages Remove member - Ban from joining - Ban from connecting - Reason - No - 1 hour - 1 day - 1 week - 1 month - Permanently - Banned - You were banned from this group. - You were banned from this channel. - You were banned from connecting via this address. - You were banned from connecting via this link. - You can join again after %s. - You can connect again after %s. - You can\'t join it again. - You can\'t connect again. - Reason: %s. Block member? Block member Block diff --git a/packages/simplex-chat-client/typescript/src/client.ts b/packages/simplex-chat-client/typescript/src/client.ts index cd96241530..67e470e52d 100644 --- a/packages/simplex-chat-client/typescript/src/client.ts +++ b/packages/simplex-chat-client/typescript/src/client.ts @@ -176,8 +176,8 @@ export class ChatClient { } } - async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}, notice?: T.LinkNotice): Promise { - const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode, notice})) + async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}): Promise { + const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode})) switch (chatType) { case T.ChatType.Direct: if (r.type === "contactDeleted") return @@ -273,8 +273,8 @@ export class ChatClient { throw new ChatCommandError("error joining group", r) } - async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false, notice?: T.LinkNotice): Promise { - const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages, notice})) + async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise { + const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages})) if (r.type === "userDeletedMembers") return r.members throw new ChatCommandError("error removing member", r) } diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 38389e6e3f..fc090d037c 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -620,8 +620,8 @@ export class ChatApi { * Remove members. Requires Admin role. * Network usage: background. */ - async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false, notice?: T.LinkNotice): Promise { - const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages, notice})) + async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise { + const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages})) if (r.type === "userDeletedMembers") return r.members throw new ChatCommandError("error removing member", r) } @@ -829,8 +829,8 @@ export class ChatApi { * Delete chat. * Network usage: background. */ - async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}, notice?: T.LinkNotice): Promise { - const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode, notice})) + async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}): Promise { + const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode})) switch (chatType) { case T.ChatType.Direct: if (r.type === "contactDeleted") return