* android, desktop, ios: show chat name in delete / leave / clear confirmation dialogs The dialogs confirming delete contact, delete/leave group/channel and clear chat now show the chat's name on its own line above the existing warning, so the user can see which chat the destructive action will affect. Pure code change: no new translation strings, no signature changes, no new helpers. The name is read via existing displayName accessors on GroupInfo / Contact / ChatInfo. clearNoteFolderDialog is intentionally unchanged - the notes folder is a single-instance object and its existing warning already identifies it. * android, desktop, ios: also show chat name when deleting pending connection deleteContactConnectionAlert was missed in the original inventory. Same pattern as the other dispatchers - prepend the connection's displayName on its own line above the existing warning - so a user who set a custom name on a pending connection can see which one they are about to delete. * android: use <br> instead of \n for newline in delete confirmation dialog body On Android, the alert body goes through HtmlCompat.fromHtml which treats the input as HTML and collapses literal \n to a single space - so "Tech Talk\n\nGroup will be deleted..." rendered as "Tech Talk Group will be deleted...". Switch to <br><br>, which both HtmlCompat (Android) and the Desktop parser at Utils.desktop.kt:75 correctly render as a newline. * android, desktop: skip HTML parsing for delete confirmation dialog text Add parseHtml: Boolean = true parameter to showAlertDialog and showAlertDialogButtonsColumn; when false, the body text is wrapped as AnnotatedString and routed through the existing AnnotatedString AlertContent overload, bypassing escapedHtmlToAnnotatedString entirely. The 8 dispatchers that embed the user-controlled chat displayName now opt out (parseHtml = false). This means: - displayName is rendered as literal text - no HTML interpretation, so a contact whose alias is "<b>X</b>" or "<font color=..." no longer renders bold or coloured in the confirmation dialog - the platforms align: both iOS and Kotlin now use plain "\n\n" for the separator (no more <br><br> Kotlin-only workaround) - removes coupling to escapedHtmlToAnnotatedString in this path * android, desktop: shorten parseHtml comment in AlertManager * remove extra text --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
12 KiB
Implementation plan — chat name on its own line in delete/leave/clear dialogs
Follows the product spec in
delete-leave-dialog-with-profile.md.
Pure code change — zero string additions, zero new helpers, zero
signature changes. Each call site edits one argument: the text = /
message: value gains "${displayName}\n\n" prepended to the
existing localized warning (or, where there is no current body, the
chat name becomes the new body).
One commit per platform.
Commit 1 — Kotlin
Files touched:
apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt— addsparseHtml: Boolean = truetoshowAlertDialogandshowAlertDialogButtonsColumn. Whenfalse, the body text is wrapped asAnnotatedStringand routed through the existing AnnotatedStringAlertContentoverload, which does NOT callescapedHtmlToAnnotatedString. Default staystrueso existing callers are unaffected.apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.ktapps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.ktapps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt— adds the previously-misseddeleteContactConnectionAlertdispatcher to the coverage (pending contact connections).
Every Kotlin call site that prepends the chat name sets
parseHtml = false, so displayName is never HTML-interpreted.
1.1 — deleteGroupDialog (GroupChatInfoView.kt:182)
fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
val chatInfo = chat.chatInfo
val titleId = /* unchanged */
val messageId = /* unchanged */
AlertManager.shared.showAlertDialog(
title = generalGetString(titleId),
- text = generalGetString(messageId),
+ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
confirmText = generalGetString(MR.strings.delete_verb),
onConfirm = { /* unchanged */ },
destructive = true,
)
}
1.2 — leaveGroupDialog (GroupChatInfoView.kt:222)
fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
val titleId = /* unchanged */
val messageId = /* unchanged */
AlertManager.shared.showAlertDialog(
title = generalGetString(titleId),
- text = generalGetString(messageId),
+ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
confirmText = generalGetString(MR.strings.leave_group_button),
onConfirm = { /* unchanged */ },
destructive = true,
)
}
Signature unchanged. No caller updates. groupInfo.displayName is
already available on the existing parameter (ChatModel.kt:2142).
1.3 — clearChatDialog (ChatInfoView.kt:492)
fun clearChatDialog(chat: Chat, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.clear_chat_question),
- text = generalGetString(MR.strings.clear_chat_warning),
+ text = "${chat.chatInfo.displayName}\n\n${generalGetString(MR.strings.clear_chat_warning)}",
confirmText = generalGetString(MR.strings.clear_verb),
onConfirm = { controller.clearChat(chat, close) },
destructive = true,
)
}
1.4 — Contact-delete dispatchers (ChatInfoView.kt)
Four functions. deleteContactOrConversationDialog (line 248) has
no existing text =, so the chat name becomes the new body. The
other three already have a text =, so the name is prepended.
All four already have contact: Contact as a parameter, so
contact.displayName is used directly (same value as
chat.chatInfo.displayName for a direct chat, shorter expression).
// deleteContactOrConversationDialog — line 248
private fun deleteContactOrConversationDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)?) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.delete_contact_question),
+ text = contact.displayName,
buttons = { /* unchanged */ }
)
}
// deleteActiveContactDialog — line 304
private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)? = null) {
val contactDeleteMode = mutableStateOf<ContactDeleteMode>(ContactDeleteMode.Full())
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.delete_contact_question),
- text = generalGetString(MR.strings.delete_contact_cannot_undo_warning),
+ text = "${contact.displayName}\n\n${generalGetString(MR.strings.delete_contact_cannot_undo_warning)}",
buttons = { /* unchanged */ }
)
}
Same diff for deleteContactWithoutConversation (line 361) and
deleteNotReadyContact (line 417) — both use
delete_contact_cannot_undo_warning. Neither takes contact as
a parameter, so the name is read via chat.chatInfo.displayName
(which resolves to contact.displayName because these dispatchers
are only reached for ChatInfo.Direct chats). Their titles
(confirm_delete_contact_question) stay unchanged — the
not-ready / no-conversation paths keep their distinct title.
Commit 2 — iOS
Files touched:
apps/ios/Shared/Views/ChatList/ChatListNavLink.swiftapps/ios/Shared/Views/Chat/ChatInfoView.swiftapps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
2.1 — deleteGroupAlert (two locations)
Views/Chat/Group/GroupChatInfoView.swift:835 and
Views/ChatList/ChatListNavLink.swift:567 get the same diff.
deleteGroupAlertMessage(_:) already returns a Text containing
the localized warning — concatenate to it.
private func deleteGroupAlert() -> Alert {
let label: LocalizedStringKey = /* unchanged */
return Alert(
title: Text(label),
- message: deleteGroupAlertMessage(groupInfo),
+ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + deleteGroupAlertMessage(groupInfo),
primaryButton: .destructive(Text("Delete")) { /* unchanged */ },
secondaryButton: .cancel()
)
}
Text(chat.chatInfo.displayName) resolves to Text(_ content: some StringProtocol)
(the runtime-string overload — no localization lookup, matches
codebase convention: ChatView.swift:984, ChatInfoToolbar.swift:49,
SettingsView.swift:540). Text(verbatim: "\n\n") is the literal
separator, matching the codebase convention that reserves
verbatim: for fixed punctuation (ContextItemView.swift:88 is
the textbook example: Text(chatLink.displayName) + Text(verbatim: " - ")).
The third term Text(messageLabel) keeps the existing
LocalizedStringKey lookup.
2.2 — leaveGroupAlert (two locations)
Views/Chat/Group/GroupChatInfoView.swift:872 and
Views/ChatList/ChatListNavLink.swift:622:
private func leaveGroupAlert() -> Alert {
let titleLabel: LocalizedStringKey = /* unchanged */
let messageLabel: LocalizedStringKey = /* unchanged */
return Alert(
title: Text(titleLabel),
- message: Text(messageLabel),
+ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + Text(messageLabel),
primaryButton: .destructive(Text("Leave")) { /* unchanged */ },
secondaryButton: .cancel()
)
}
2.3 — clearChatAlert (three locations)
Views/Chat/ChatInfoView.swift:577,
Views/Chat/Group/GroupChatInfoView.swift:858,
Views/ChatList/ChatListNavLink.swift:600:
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
- message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
+ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
primaryButton: .destructive(Text("Clear")) { /* unchanged */ },
secondaryButton: .cancel()
)
}
2.4 — Contact-delete action sheets
Three functions in Views/Chat/ChatInfoView.swift. None currently
pass message: to ActionSheet; we add it. ActionSheet's
message: is an optional second parameter that SwiftUI already
supports.
All three functions have contact: Contact in scope. Use bare
Text(contact.displayName) (resolves to the StringProtocol
overload, no localization lookup, matches codebase convention).
Add only the name as message: — these ActionSheets had no
message before, so adding any additional warning would be new
behavior beyond the stated goal.
deleteContactOrConversationDialog (line 1177):
private func deleteContactOrConversationDialog(
_ chat: Chat, _ contact: Contact, _ dismissToChatList: Bool,
_ showAlert: @escaping (SomeAlert) -> Void,
_ showActionSheet: @escaping (SomeActionSheet) -> Void,
_ showSheetContent: @escaping (SomeSheet<AnyView>) -> Void
) {
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Delete contact?"),
+ message: Text(contact.displayName),
buttons: [ /* unchanged */ ]
),
id: "deleteContactOrConversationDialog"
))
}
deleteContactWithoutConversation (line 1324):
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
+ message: Text(contact.displayName),
buttons: [ /* unchanged */ ]
),
id: "deleteContactWithoutConversation"
))
deleteNotReadyContact (line 1348) — same:
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
+ message: Text(contact.displayName),
buttons: [ /* unchanged */ ]
),
id: "deleteNotReadyContact"
))
2.5 — DeleteActiveContactDialog sheet (line 1282) unchanged
The secondary multi-option sheet is reached only after the user confirms "Delete contact" in the previous action sheet — which now shows the name. The sheet itself remains as-is.
Verification
For each platform, exercise every entry point and confirm the
body reads <chat name> on its own line followed by the existing
warning:
- Android & Desktop:
- Chat list swipe — direct contact, group, channel, business chat
→ delete / clear / leave. (Note folder's clear dialog is
intentionally unchanged —
clearNoteFolderDialogexcluded.) - Chat info screens — "Delete contact" / "Delete group" / "Delete channel" / "Clear conversation" / "Leave …" rows.
- Contact list (
ContactListNavView.kt:148) — "Delete contact" action shows the name in entry-point dialog and toggle dialog. - Multi-option contact-delete path: entry dialog (now has a name body where it had none) → toggle dialog (name above the warning) → success.
- Chat list swipe — direct contact, group, channel, business chat
→ delete / clear / leave. (Note folder's clear dialog is
intentionally unchanged —
- iOS:
- Same matrix from chat list swipe and chat info screens.
- Action-sheet contact-delete paths show the name as the
message:line on iPhone and iPad.
Edge cases:
- Long chat name — alert containers wrap automatically; the body occupies 3+ lines. Confirm with a chat renamed to ~40 characters.
- Special characters (emoji, RTL, double quotes) — render literally via string interpolation, no format-substitution involved.
- Empty
displayName— does not occur in practice (NamedChatenforces non-empty vialocalAlias.ifEmpty { profile.displayName }).
Diff-level checks:
git diff '*strings.xml' '*Localizable.strings'returns zero hunks. Pure code change.git diff --statshows ~5 files total: two Kotlin dispatcher files, three iOS view files.- Cancel/confirm flows behave exactly as before — same API calls, same model updates, same navigation.
Out of scope
- Profile picture / avatar in dialogs — excluded by product decision.
- Refactoring the iOS duplication between
ChatListNavLinkandGroupChatInfoView/ChatInfoView(pre-existing// TODOatGroupChatInfoView.swift:834). - Pre-existing wording divergence between Kotlin's "Clear chat?" and iOS's "Clear conversation?". Both platforms keep their titles.
- "Delete invitation" at
ChatListNavLink.swift:236— has no confirmation dialog (direct call todeleteChat(chat)); nothing to modify. - Bolding the chat name. SwiftUI
Text + Textsupports.bold()on the first term, but Jetpack ComposeAlertDialogtext is a single unstyled string — keeping both unstyled preserves parity.