diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index bad15ad52..99e8c0284 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -671,18 +671,18 @@ private func connectionErrorAlert(_ r: ChatResponse) -> Alert {
}
}
-func apiDeleteChat(type: ChatType, id: Int64) async throws {
- let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false)
+func apiDeleteChat(type: ChatType, id: Int64, notify: Bool? = nil) async throws {
+ let r = await chatSendCmd(.apiDeleteChat(type: type, id: id, notify: notify), 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
}
-func deleteChat(_ chat: Chat) async {
+func deleteChat(_ chat: Chat, notify: Bool? = nil) async {
do {
let cInfo = chat.chatInfo
- try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId)
+ try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId, notify: notify)
DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) }
} catch let error {
logger.error("deleteChat apiDeleteChat error: \(responseError(error))")
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index 5438eb13b..ec4cc0fc4 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -99,12 +99,12 @@ struct ChatInfoView: View {
@Binding var connectionCode: String?
@FocusState private var aliasTextFieldFocused: Bool
@State private var alert: ChatInfoViewAlert? = nil
+ @State private var showDeleteContactActionSheet = false
@State private var sendReceipts = SendReceipts.userDefault(true)
@State private var sendReceiptsUserDefault = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum ChatInfoViewAlert: Identifiable {
- case deleteContactAlert
case clearChatAlert
case networkStatusAlert
case switchAddressAlert
@@ -114,7 +114,6 @@ struct ChatInfoView: View {
var id: String {
switch self {
- case .deleteContactAlert: return "deleteContactAlert"
case .clearChatAlert: return "clearChatAlert"
case .networkStatusAlert: return "networkStatusAlert"
case .switchAddressAlert: return "switchAddressAlert"
@@ -233,7 +232,6 @@ struct ChatInfoView: View {
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
- case .deleteContactAlert: return deleteContactAlert()
case .clearChatAlert: return clearChatAlert()
case .networkStatusAlert: return networkStatusAlert()
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
@@ -242,6 +240,26 @@ struct ChatInfoView: View {
case let .error(title, error): return mkAlert(title: title, message: error)
}
}
+ .actionSheet(isPresented: $showDeleteContactActionSheet) {
+ if contact.ready && contact.active {
+ ActionSheet(
+ title: Text("Delete contact?\nThis cannot be undone!"),
+ buttons: [
+ .destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
+ .destructive(Text("Delete")) { deleteContact(notify: false) },
+ .cancel()
+ ]
+ )
+ } else {
+ ActionSheet(
+ title: Text("Delete contact?\nThis cannot be undone!"),
+ buttons: [
+ .destructive(Text("Delete")) { deleteContact() },
+ .cancel()
+ ]
+ )
+ }
+ }
}
private func contactInfoHeader() -> some View {
@@ -414,7 +432,7 @@ struct ChatInfoView: View {
private func deleteContactButton() -> some View {
Button(role: .destructive) {
- alert = .deleteContactAlert
+ showDeleteContactActionSheet = true
} label: {
Label("Delete contact", systemImage: "trash")
.foregroundColor(Color.red)
@@ -430,30 +448,23 @@ struct ChatInfoView: View {
}
}
- private func deleteContactAlert() -> Alert {
- Alert(
- title: Text("Delete contact?"),
- message: Text("Contact and all messages will be deleted - this cannot be undone!"),
- primaryButton: .destructive(Text("Delete")) {
- Task {
- do {
- try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId)
- await MainActor.run {
- dismiss()
- chatModel.chatId = nil
- chatModel.removeChat(chat.chatInfo.id)
- }
- } catch let error {
- logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
- let a = getErrorAlert(error, "Error deleting contact")
- await MainActor.run {
- alert = .error(title: a.title, error: a.message)
- }
- }
+ private func deleteContact(notify: Bool? = nil) {
+ Task {
+ do {
+ try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
+ await MainActor.run {
+ dismiss()
+ chatModel.chatId = nil
+ chatModel.removeChat(chat.chatInfo.id)
}
- },
- secondaryButton: .cancel()
- )
+ } catch let error {
+ logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
+ let a = getErrorAlert(error, "Error deleting contact")
+ await MainActor.run {
+ alert = .error(title: a.title, error: a.message)
+ }
+ }
+ }
}
private func clearChatAlert() -> Alert {
diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
index f445ae4b5..be912d666 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
@@ -32,6 +32,7 @@ struct ChatListNavLink: View {
@State private var showJoinGroupDialog = false
@State private var showContactConnectionInfo = false
@State private var showInvalidJSON = false
+ @State private var showDeleteContactActionSheet = false
var body: some View {
switch chat.chatInfo {
@@ -64,17 +65,37 @@ struct ChatListNavLink: View {
clearChatButton()
}
Button {
- AlertManager.shared.showAlert(
- contact.ready || !contact.active
- ? deleteContactAlert(chat.chatInfo)
- : deletePendingContactAlert(chat, contact)
- )
+ if contact.ready || !contact.active {
+ showDeleteContactActionSheet = true
+ } else {
+ AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact))
+ }
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
}
.frame(height: rowHeights[dynamicTypeSize])
+ .actionSheet(isPresented: $showDeleteContactActionSheet) {
+ if contact.ready && contact.active {
+ ActionSheet(
+ title: Text("Delete contact?\nThis cannot be undone!"),
+ buttons: [
+ .destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
+ .destructive(Text("Delete")) { Task { await deleteChat(chat, notify: false) } },
+ .cancel()
+ ]
+ )
+ } else {
+ ActionSheet(
+ title: Text("Delete contact?\nThis cannot be undone!"),
+ buttons: [
+ .destructive(Text("Delete")) { Task { await deleteChat(chat) } },
+ .cancel()
+ ]
+ )
+ }
+ }
}
@ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View {
@@ -269,17 +290,6 @@ struct ChatListNavLink: View {
}
}
- private func deleteContactAlert(_ chatInfo: ChatInfo) -> Alert {
- Alert(
- title: Text("Delete contact?"),
- message: Text("Contact and all messages will be deleted - this cannot be undone!"),
- primaryButton: .destructive(Text("Delete")) {
- Task { await deleteChat(chat) }
- },
- secondaryButton: .cancel()
- )
- }
-
private func deleteGroupAlert(_ groupInfo: GroupInfo) -> Alert {
Alert(
title: Text("Delete group?"),
diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift
index 53da91b04..5c7220f37 100644
--- a/apps/ios/SimpleXChat/APITypes.swift
+++ b/apps/ios/SimpleXChat/APITypes.swift
@@ -89,7 +89,7 @@ public enum ChatCommand {
case apiSetConnectionIncognito(connId: Int64, incognito: Bool)
case apiConnectPlan(userId: Int64, connReq: String)
case apiConnect(userId: Int64, incognito: Bool, connReq: String)
- case apiDeleteChat(type: ChatType, id: Int64)
+ case apiDeleteChat(type: ChatType, id: Int64, notify: Bool?)
case apiClearChat(type: ChatType, id: Int64)
case apiListContacts(userId: Int64)
case apiUpdateProfile(userId: Int64, profile: Profile)
@@ -224,7 +224,11 @@ public enum ChatCommand {
case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))"
case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)"
case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)"
- case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))"
+ case let .apiDeleteChat(type, id, notify): if let notify = notify {
+ return "/_delete \(ref(type, id)) notify=\(onOff(notify))"
+ } else {
+ return "/_delete \(ref(type, id))"
+ }
case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
case let .apiListContacts(userId): return "/_contacts \(userId)"
case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))"
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 8397d2edb..da09ea132 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
@@ -904,8 +904,8 @@ object ChatController {
}
}
- suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean {
- val r = sendCmd(CC.ApiDeleteChat(type, id))
+ suspend fun apiDeleteChat(type: ChatType, id: Long, notify: Boolean? = null): Boolean {
+ val r = sendCmd(CC.ApiDeleteChat(type, id, notify))
when {
r is CR.ContactDeleted && type == ChatType.Direct -> return true
r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> return true
@@ -1924,7 +1924,7 @@ sealed class CC {
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
class APIConnectPlan(val userId: Long, val connReq: String): CC()
class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC()
- class ApiDeleteChat(val type: ChatType, val id: Long): CC()
+ class ApiDeleteChat(val type: ChatType, val id: Long, val notify: Boolean?): 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()
@@ -2034,7 +2034,11 @@ sealed class CC {
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
is APIConnectPlan -> "/_connect plan $userId $connReq"
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq"
- is ApiDeleteChat -> "/_delete ${chatRef(type, id)}"
+ is ApiDeleteChat -> if (notify != null) {
+ "/_delete ${chatRef(type, id)} notify=${onOff(notify)}"
+ } else {
+ "/_delete ${chatRef(type, id)}"
+ }
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
is ApiListContacts -> "/_contacts $userId"
is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}"
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 01173157a..4564a4a6e 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
@@ -196,28 +196,67 @@ sealed class SendReceipts {
}
fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
- AlertManager.shared.showAlertDialog(
+ AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.delete_contact_question),
- text = generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning),
- confirmText = generalGetString(MR.strings.delete_verb),
- onConfirm = {
- withApi {
- val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId)
- if (r) {
- chatModel.removeChat(chatInfo.id)
- if (chatModel.chatId.value == chatInfo.id) {
- chatModel.chatId.value = null
- ModalManager.end.closeModals()
+ text = AnnotatedString(generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning)),
+ buttons = {
+ Column {
+ if (chatInfo is ChatInfo.Direct && chatInfo.contact.ready && chatInfo.contact.active) {
+ // Delete and notify contact
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ deleteContact(chatInfo, chatModel, close, notify = true)
+ }
+ }) {
+ Text(generalGetString(MR.strings.delete_and_notify_contact), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
}
- ntfManager.cancelNotificationsForChat(chatInfo.id)
- close?.invoke()
+ // Delete
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ deleteContact(chatInfo, chatModel, close, notify = false)
+ }
+ }) {
+ Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
+ }
+ } else {
+ // Delete
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ deleteContact(chatInfo, chatModel, close)
+ }
+ }) {
+ Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
+ }
+ }
+ // Cancel
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ }) {
+ Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
- },
- destructive = true,
+ }
)
}
+fun deleteContact(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) {
+ withApi {
+ val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId, notify)
+ if (r) {
+ chatModel.removeChat(chatInfo.id)
+ if (chatModel.chatId.value == chatInfo.id) {
+ chatModel.chatId.value = null
+ ModalManager.end.closeModals()
+ }
+ ntfManager.cancelNotificationsForChat(chatInfo.id)
+ close?.invoke()
+ }
+ }
+}
+
fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.clear_chat_question),
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index bd59d236d..1ae85cd05 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -350,6 +350,7 @@
Delete contact?
Contact and all messages will be deleted - this cannot be undone!
+ Delete and notify contact
Delete contact
Set contact nameā¦
Connected