mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 21:52:28 +00:00
Merge branch 'master' into f/public-groups
This commit is contained in:
commit
e56ee2eefb
15 files changed
+742
-46
No files matched your search
@@ -0,0 +1,323 @@
|
||||
# Implementation plan — chat name on its own line in delete/leave/clear dialogs
|
||||
|
||||
Follows the product spec in
|
||||
[`delete-leave-dialog-with-profile.md`](./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`
|
||||
— adds `parseHtml: Boolean = true` to `showAlertDialog` and
|
||||
`showAlertDialogButtonsColumn`. When `false`, the body text is wrapped
|
||||
as `AnnotatedString` and routed through the existing AnnotatedString
|
||||
`AlertContent` overload, which does NOT call
|
||||
`escapedHtmlToAnnotatedString`. Default stays `true` so existing
|
||||
callers are unaffected.
|
||||
- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt`
|
||||
- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt`
|
||||
- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt`
|
||||
— adds the previously-missed `deleteContactConnectionAlert`
|
||||
dispatcher 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`)
|
||||
|
||||
```diff
|
||||
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`)
|
||||
|
||||
```diff
|
||||
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`)
|
||||
|
||||
```diff
|
||||
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).
|
||||
|
||||
```diff
|
||||
// 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 */ }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```diff
|
||||
// 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.swift`
|
||||
- `apps/ios/Shared/Views/Chat/ChatInfoView.swift`
|
||||
- `apps/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.
|
||||
|
||||
```diff
|
||||
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`:
|
||||
|
||||
```diff
|
||||
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`:
|
||||
|
||||
```diff
|
||||
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):
|
||||
|
||||
```diff
|
||||
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):
|
||||
|
||||
```diff
|
||||
showActionSheet(SomeActionSheet(
|
||||
actionSheet: ActionSheet(
|
||||
title: Text("Confirm contact deletion?"),
|
||||
+ message: Text(contact.displayName),
|
||||
buttons: [ /* unchanged */ ]
|
||||
),
|
||||
id: "deleteContactWithoutConversation"
|
||||
))
|
||||
```
|
||||
|
||||
**`deleteNotReadyContact`** (line 1348) — same:
|
||||
|
||||
```diff
|
||||
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 — `clearNoteFolderDialog` excluded.)
|
||||
- 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.
|
||||
- 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 (`NamedChat`
|
||||
enforces non-empty via `localAlias.ifEmpty { profile.displayName }`).
|
||||
|
||||
Diff-level checks:
|
||||
|
||||
- `git diff '*strings.xml' '*Localizable.strings'` returns zero
|
||||
hunks. Pure code change.
|
||||
- `git diff --stat` shows ~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 `ChatListNavLink` and
|
||||
`GroupChatInfoView` / `ChatInfoView` (pre-existing `// TODO` at
|
||||
`GroupChatInfoView.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 to `deleteChat(chat)`); nothing
|
||||
to modify.
|
||||
- Bolding the chat name. SwiftUI `Text + Text` supports `.bold()`
|
||||
on the first term, but Jetpack Compose `AlertDialog` text is a
|
||||
single unstyled string — keeping both unstyled preserves parity.
|
||||
@@ -0,0 +1,249 @@
|
||||
# Show chat name in delete / leave / clear confirmation dialogs
|
||||
|
||||
## Goal
|
||||
|
||||
The current delete-contact, delete-group, delete-channel, leave-group,
|
||||
leave-channel and clear-chat confirmations are generic. From a long
|
||||
chat list, swiping on a row and triggering one of these actions opens
|
||||
a dialog whose title is "Delete group?", "Leave channel?", "Clear
|
||||
conversation?" — with no indication of *which* chat is the target. A
|
||||
user can easily act on the wrong chat.
|
||||
|
||||
The fix: include the chat's display name in the dialog body, on a line
|
||||
of its own above the existing warning text. Nothing else changes —
|
||||
same title, same warning text, same buttons, same colors, same dialog
|
||||
shape. No profile picture, no layout changes, no new helpers, no new
|
||||
translation strings.
|
||||
|
||||
We deliberately do NOT reuse the open-chat-link alert layout (centered
|
||||
profile image + name + open-chat button). That layout is the *invite*
|
||||
flow's identity; repurposing it for destructive confirmations would
|
||||
confuse the two flows visually. The minimum change that solves the
|
||||
"which chat?" problem is putting the name in the body text.
|
||||
|
||||
## Why body, not title; why no new strings
|
||||
|
||||
The title carries the action ("Delete group?", "Leave channel?"). The
|
||||
body carries the consequences ("Group will be deleted for all
|
||||
members…"). The chat name belongs with the body — it is the subject
|
||||
of the consequence, not part of the question.
|
||||
|
||||
Adding the name to the title would require new format-string variants
|
||||
(`delete_group_named_question` etc.) and per-locale re-translation.
|
||||
Putting the name on its own line in the body is a pure code change —
|
||||
the existing translated warnings are concatenated with the chat name
|
||||
in code:
|
||||
|
||||
```
|
||||
Tech Talk
|
||||
|
||||
Group will be deleted for all members - this cannot be undone!
|
||||
```
|
||||
|
||||
The display name appears first because the user wants to confirm
|
||||
*which* chat before reading *what* will happen. The blank line between
|
||||
the name and the warning makes the name visually distinct.
|
||||
|
||||
## Current state
|
||||
|
||||
### Multiplatform (Kotlin / Android / Desktop)
|
||||
|
||||
All eight dialogs go through `AlertManager.shared.showAlertDialog` or
|
||||
`showAlertDialogButtonsColumn`:
|
||||
|
||||
- `deleteGroupDialog` — `views/chat/group/GroupChatInfoView.kt:182`
|
||||
- `leaveGroupDialog` — `views/chat/group/GroupChatInfoView.kt:222`
|
||||
- `clearChatDialog` — `views/chat/ChatInfoView.kt:492`
|
||||
- `deleteContactOrConversationDialog` — `views/chat/ChatInfoView.kt:248`
|
||||
- `deleteActiveContactDialog` — `views/chat/ChatInfoView.kt:304`
|
||||
- `deleteContactWithoutConversation` — `views/chat/ChatInfoView.kt:361`
|
||||
- `deleteNotReadyContact` — `views/chat/ChatInfoView.kt:417`
|
||||
- `deleteContactConnectionAlert` — `views/chatlist/ChatListNavLinkView.kt:772`
|
||||
(deletes a pending contact connection; takes a `PendingContactConnection`
|
||||
whose `displayName` reflects any custom name the user set)
|
||||
|
||||
Call sites (chat-info screens, chat-list swipe / overflow, contact
|
||||
list) funnel through these dispatcher functions.
|
||||
|
||||
### iOS (Swift)
|
||||
|
||||
Two SwiftUI patterns are used:
|
||||
|
||||
- SwiftUI `Alert` with `primaryButton: .destructive` / `.cancel()`:
|
||||
- `deleteGroupAlert` — `Views/ChatList/ChatListNavLink.swift:567`,
|
||||
`Views/Chat/Group/GroupChatInfoView.swift:835`
|
||||
- `leaveGroupAlert` — `Views/ChatList/ChatListNavLink.swift:622`,
|
||||
`Views/Chat/Group/GroupChatInfoView.swift:872`
|
||||
- `clearChatAlert` — `Views/ChatList/ChatListNavLink.swift:600`,
|
||||
`Views/Chat/ChatInfoView.swift:577`,
|
||||
`Views/Chat/Group/GroupChatInfoView.swift:858`
|
||||
- SwiftUI `ActionSheet`:
|
||||
- `deleteContactOrConversationDialog` —
|
||||
`Views/Chat/ChatInfoView.swift:1177`
|
||||
- `deleteContactWithoutConversation` —
|
||||
`Views/Chat/ChatInfoView.swift:1324`
|
||||
- `deleteNotReadyContact` — `Views/Chat/ChatInfoView.swift:1348`
|
||||
|
||||
`Alert(message:)` accepts `Text`, and `ActionSheet(message:)` (an
|
||||
existing optional parameter not used today) accepts `Text` too — so
|
||||
the name can be added by composing the existing message string with
|
||||
`"\n\n"` and the chat name. No widget changes.
|
||||
|
||||
## Design
|
||||
|
||||
| Dialog | Body today | Body after |
|
||||
|---|---|---|
|
||||
| Delete group | `Group will be deleted for all members – this cannot be undone!` | `Tech Talk` + blank line + existing text |
|
||||
| Delete channel | `Channel will be deleted for all subscribers – this cannot be undone!` | `SimpleX news` + blank line + existing text |
|
||||
| Leave group | `You will stop receiving messages from this group. …` | `Tech Talk` + blank line + existing text |
|
||||
| Clear chat | `All messages will be deleted – this cannot be undone! …` | `Alice` + blank line + existing text |
|
||||
| Delete contact (entry sheet) | *(no body today — title only + buttons)* | `Alice` (becomes the body) |
|
||||
| Delete contact (active variant) | `Contact will be deleted – this cannot be undone!` | `Alice` + blank line + existing text |
|
||||
| Confirm contact deletion (not-ready / no-conversation) | `Contact will be deleted – this cannot be undone!` | `Alice` + blank line + existing text |
|
||||
|
||||
Title text is unchanged in every case. Existing titles
|
||||
(`delete_contact_question`, `confirm_delete_contact_question`, etc.)
|
||||
keep their semantic distinction — the "Confirm contact deletion?"
|
||||
title still appears for the not-ready / no-conversation paths.
|
||||
|
||||
### Which name to use: `displayName`, not `chatViewName`
|
||||
|
||||
The chat list row labels chats with `cInfo.chatViewName`
|
||||
(`ChatPreviewView.kt:87`), defined as:
|
||||
|
||||
```kotlin
|
||||
val chatViewName: String
|
||||
get() = localAlias.ifEmpty { displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName") }
|
||||
```
|
||||
|
||||
The dialog uses `chatInfo.displayName` (and `groupInfo.displayName`
|
||||
for the leave dialog). For most chats these are identical:
|
||||
|
||||
- If `localAlias` is set, both resolve to the alias.
|
||||
- If `displayName == fullName` (or `fullName` is empty), both resolve
|
||||
to `displayName`.
|
||||
|
||||
For a contact with distinct display name and full name (no alias),
|
||||
the row would show `alice / Alice Smith` while the dialog shows
|
||||
`alice`. Acceptable: `displayName` is the recognizable identifier,
|
||||
shorter, and the dialog format (single line above the warning)
|
||||
benefits from concision. Two-part identifiers in the dialog would
|
||||
crowd the layout.
|
||||
|
||||
### `clearNoteFolderDialog` is excluded
|
||||
|
||||
The local notes folder is a single-instance object — there is only
|
||||
one per user — and its existing warning text already names it
|
||||
unambiguously. Adding the display name on its own line would be
|
||||
pure redundancy. Skipped.
|
||||
|
||||
## Changes
|
||||
|
||||
### Multiplatform (Kotlin)
|
||||
|
||||
Each dispatcher function changes one argument: the `text =` parameter
|
||||
passed to `AlertManager.shared.showAlertDialog` /
|
||||
`showAlertDialogButtonsColumn`. The new value is the chat name + two
|
||||
newlines + the existing message text:
|
||||
|
||||
```kotlin
|
||||
text = "${chatInfo.displayName}\n\n${generalGetString(messageId)}",
|
||||
parseHtml = false,
|
||||
```
|
||||
|
||||
`parseHtml = false` is a new boolean parameter added to both alert
|
||||
helpers. It bypasses `escapedHtmlToAnnotatedString` so the
|
||||
user-controlled `displayName` is rendered as literal text, never
|
||||
interpreted as HTML markup (`<b>`, `<font>`, `&`, etc.). The default
|
||||
remains `true`; only our delete-confirmation dispatchers opt out.
|
||||
|
||||
For `leaveGroupDialog` the source is `groupInfo.displayName` (the
|
||||
function already takes `groupInfo` — no signature change needed,
|
||||
no caller updates needed).
|
||||
|
||||
For `deleteGroupDialog`, also `groupInfo.displayName`, for consistency
|
||||
with `leaveGroupDialog` (both have `groupInfo` already in scope).
|
||||
|
||||
For `deleteContactOrConversationDialog`, which has no `text =`
|
||||
parameter today, add `text = chatInfo.displayName` (no concatenation
|
||||
needed — the dialog had no body text before).
|
||||
|
||||
### iOS
|
||||
|
||||
Each of the eight call sites changes one argument: the `message:`
|
||||
parameter passed to `Alert(…)` or `ActionSheet(…)`. The new value
|
||||
composes the chat name with the existing localized message string:
|
||||
|
||||
```swift
|
||||
message: Text("\(chat.chatInfo.displayName)\n\n\(existingMessage)"),
|
||||
```
|
||||
|
||||
For the three `ActionSheet` sites that have no `message:` today, add
|
||||
`message: Text(chat.chatInfo.displayName)`.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Profile picture / avatar in any of these dialogs — excluded by
|
||||
decision: the open-chat-link alert owns that layout, and reusing
|
||||
it for destructive confirmations conflates two semantically
|
||||
different flows.
|
||||
- The pre-existing wording divergence between Kotlin's
|
||||
`clear_chat_question` ("Clear chat?") and iOS's "Clear
|
||||
conversation?". Both platforms keep their existing titles.
|
||||
- Refactoring the iOS duplication between `ChatListNavLink` and
|
||||
`GroupChatInfoView` / `ChatInfoView` (pre-existing `// TODO reuse
|
||||
this and clearChatAlert with ChatInfoView` at
|
||||
`GroupChatInfoView.swift:834`).
|
||||
- "Delete invitation" at `ChatListNavLink.swift:236` — goes through
|
||||
`deleteChat(chat)` directly with no confirmation dialog. No dialog
|
||||
to modify.
|
||||
- Bolding the chat name on its own line. SwiftUI `Text` concatenation
|
||||
supports `.bold()`; Jetpack Compose `AlertDialog` text is a single
|
||||
string. Keep both platforms unstyled for parity.
|
||||
|
||||
## Verification
|
||||
|
||||
Per platform, exercise every entry point and confirm the dialog body
|
||||
reads `<chat name>` on its own line followed by a blank line followed
|
||||
by the existing warning:
|
||||
|
||||
- Android & Desktop:
|
||||
- Chat list swipe — direct contact, group, channel, business chat
|
||||
→ delete / clear / leave actions. (Note folder's clear dialog
|
||||
is intentionally unchanged.)
|
||||
- Chat info screens — "Delete contact" / "Delete group" / "Delete
|
||||
channel" / "Clear conversation" / "Leave …" rows.
|
||||
- Contact list (`ContactListNavView.kt:148`) — "Delete contact"
|
||||
action.
|
||||
- The multi-option contact-delete path: entry dialog (now has a
|
||||
name body where it had none) → toggle dialog (name above the
|
||||
warning) → success.
|
||||
- iOS:
|
||||
- Same matrix from chat list swipe and chat info screens.
|
||||
- Action-sheet contact-delete paths show the name as the
|
||||
`message:` line.
|
||||
|
||||
Edge cases:
|
||||
|
||||
- Long chat name (40+ chars) — alert containers wrap automatically;
|
||||
body now occupies 3+ lines (name on 2, blank line, warning on 1+).
|
||||
Confirm via a chat renamed to a long string.
|
||||
- Special characters in name (emoji, RTL text, double quotes) —
|
||||
render literally because the substitution is string concatenation,
|
||||
not format expansion. A contact named `Bob "the builder"` displays
|
||||
as `Bob "the builder"` on its own line. No quoting/escaping issue.
|
||||
- Empty `displayName` would render an empty first line above the
|
||||
warning. In practice `displayName` is non-empty (the `NamedChat`
|
||||
interface enforces it via `localAlias.ifEmpty { profile.displayName }`);
|
||||
no defensive trimming added.
|
||||
|
||||
Diff-level checks:
|
||||
|
||||
- `git diff strings.xml` and `git diff '*Localizable.strings'` show
|
||||
zero hunks. The change is pure code.
|
||||
- `git diff --stat` shows each platform touched in 2–4 files:
|
||||
the dispatcher file(s) on Kotlin (`ChatInfoView.kt`,
|
||||
`GroupChatInfoView.kt`), and the SwiftUI views holding the
|
||||
alert/sheet builders on iOS.
|
||||
- Behavior is unchanged. Cancel returns to the prior screen;
|
||||
confirm performs the same destructive API call as before.
|
||||
Reference in new issue
Block a user