revert ui

This commit is contained in:
Evgeny @ SimpleX Chat committed 2026-10-21 13:57:12 +00:00
1 parent f39002a146
commit b7fd247998
23 files changed
+258 -597

No files matched your search

+5 -16
View File
@@ -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)
+13 -21
View File
@@ -1079,7 +1079,7 @@ func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqTyp
return nil
}
private func apiConnectResponseAlert<R>(_ r: APIResult<R>, _ noticeTarget: LinkNoticeTarget = .link) async {
private func apiConnectResponseAlert<R>(_ r: APIResult<R>) async {
await MainActor.run {
switch r.unexpected {
case .error(.invalidConnReq):
@@ -1092,8 +1092,6 @@ private func apiConnectResponseAlert<R>(_ r: APIResult<R>, _ 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<ChatResponse1>? = 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<ChatResponse1>? = 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
}
+4 -12
View File
@@ -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")
}
@@ -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
}
@@ -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
)
}
@@ -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<LinkBan>, _ reason: Binding<ReportReason>) -> 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
@@ -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)
@@ -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
}
+1 -4
View File
@@ -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<Content: View>(detents: [UISheetPresentationController.Detent]? = nil, @ViewBuilder content: () -> Content) -> UIViewController? {
func showAppSheet<Content: View>(@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
}
@@ -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))")
-1
View File
@@ -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)
+13 -18
View File
@@ -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
@@ -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<ReportReason> = listOf(Spam, Content, Community, Profile, Other)
val supportedReasons: List<ReportReason> = 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<ReportReason> {
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<ReportReason> {
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"
@@ -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<GroupInfo, List<RelayConnectionResult>>? {
suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean, msg: MsgContent?): Pair<GroupInfo, List<RelayConnectionResult>>? {
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<Long>, withMessages: Boolean, notice: LinkNotice? = null): Pair<GroupInfo, List<GroupMember>>? {
val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages, notice))
suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List<Long>, withMessages: Boolean): Pair<GroupInfo, List<GroupMember>>? {
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<Long>, val memberRole: GroupMemberRole): CC()
class ApiBlockMembersForAll(val groupId: Long, val memberIds: List<Long>, val blocked: Boolean): CC()
class ApiRemoveMembers(val groupId: Long, val memberIds: List<Long>, val withMessages: Boolean, val notice: LinkNotice?): CC()
class ApiRemoveMembers(val groupId: Long, val memberIds: List<Long>, 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()
@@ -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>(ContactDeleteMode.Full())
val ban = mutableStateOf(LinkBan.No)
val reason = mutableStateOf<ReportReason>(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) {
@@ -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,
)
}
@@ -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)
@@ -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<LinkBan>, reason: MutableState<ReportReason>) {
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<LinkBanAction>) {
val banState = mutableStateOf(ban)
val reason = mutableStateOf<ReportReason>(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<LinkBanAction>) {
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<Long>, withMessages: Boolean, notice: LinkNotice? = null, onSuccess: () -> Unit = {}) {
fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List<Long>, 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) {
@@ -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) {
@@ -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
@@ -2039,25 +2039,6 @@
<string name="remove_member_delete_messages_confirmation">Remove and delete messages</string>
<string name="delete_member_messages_confirmation">Delete messages</string>
<string name="remove_member_button">Remove member</string>
<string name="link_ban_from_joining">Ban from joining</string>
<string name="link_ban_from_connecting">Ban from connecting</string>
<string name="link_ban_reason">Reason</string>
<string name="link_ban_no">No</string>
<string name="link_ban_1_hour">1 hour</string>
<string name="link_ban_1_day">1 day</string>
<string name="link_ban_1_week">1 week</string>
<string name="link_ban_1_month">1 month</string>
<string name="link_ban_permanently">Permanently</string>
<string name="link_ban_alert_title">Banned</string>
<string name="link_ban_alert_group">You were banned from this group.</string>
<string name="link_ban_alert_channel">You were banned from this channel.</string>
<string name="link_ban_alert_address">You were banned from connecting via this address.</string>
<string name="link_ban_alert_link">You were banned from connecting via this link.</string>
<string name="link_ban_alert_join_after">You can join again after %s.</string>
<string name="link_ban_alert_connect_after">You can connect again after %s.</string>
<string name="link_ban_alert_cant_join">You can\'t join it again.</string>
<string name="link_ban_alert_cant_connect">You can\'t connect again.</string>
<string name="link_ban_alert_reason">Reason: %s.</string>
<string name="block_member_question">Block member?</string>
<string name="block_member_button">Block member</string>
<string name="block_member_confirmation">Block</string>
@@ -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<void> {
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<void> {
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<T.GroupMember[]> {
const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages, notice}))
async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise<T.GroupMember[]> {
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)
}
+4 -4
View File
@@ -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<T.GroupMember[]> {
const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages, notice}))
async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise<T.GroupMember[]> {
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<void> {
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<void> {
const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode}))
switch (chatType) {
case T.ChatType.Direct:
if (r.type === "contactDeleted") return