ios: alerts to open known/prepared contact and groups (#6020)

* ios: alerts to open known/prepared contact and groups

* android, desktop: alerts for knonw chats; all ui: fix prepared contacts misinterpreted as contact cards
This commit is contained in:
Evgeny
2025-06-30 22:02:45 +01:00
committed by GitHub
parent bf305964bc
commit 0d875c72a3
32 changed files with 2296 additions and 620 deletions
@@ -1621,7 +1621,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
val contactCard: Boolean
get() = when (this) {
is Direct -> contact.activeConn == null && contact.profile.contactLink != null && contact.active
is Direct -> contact.isContactCard
else -> false
}
@@ -1721,7 +1721,7 @@ data class Contact(
}
val isContactCard: Boolean =
activeConn == null && profile.contactLink != null && active
activeConn == null && profile.contactLink != null && active && preparedContact == null && contactRequestId == null
val contactConnIncognito =
activeConn?.customUserProfileId != null
@@ -552,7 +552,7 @@ fun ComposeView(
// TODO [short links] different messages for business
fun showSendConnectPreparedContactAlert(sendConnect: () -> Unit) {
val empty = composeState.value.whitespaceOnly
AlertManager.shared.showAlertDialog(
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.compose_view_send_contact_request_alert_question),
text = generalGetString(MR.strings.compose_view_send_contact_request_alert_text),
confirmText = (
@@ -188,7 +188,7 @@ fun ErrorChatListItem() {
suspend fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) {
when {
contact.activeConn == null && contact.profile.contactLink != null && contact.active -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true)
contact.isContactCard -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true)
else -> openDirectChat(rhId, contact.contactId)
}
}
@@ -1211,7 +1211,7 @@ fun presetTagMatchesChat(tag: PresetTagKind, chatInfo: ChatInfo, chatStats: Chat
PresetTagKind.GROUP_REPORTS -> chatStats.reportsCount > 0
PresetTagKind.FAVORITES -> chatInfo.chatSettings?.favorite == true
PresetTagKind.CONTACTS -> when (chatInfo) {
is ChatInfo.Direct -> !(chatInfo.contact.activeConn == null && chatInfo.contact.profile.contactLink != null && chatInfo.contact.active) && !chatInfo.contact.chatDeleted
is ChatInfo.Direct -> !chatInfo.contact.isContactCard && !chatInfo.contact.chatDeleted
is ChatInfo.ContactRequest -> true
is ChatInfo.ContactConnection -> true
is ChatInfo.Group -> chatInfo.groupInfo.businessChat?.chatType == BusinessChatType.Customer
@@ -172,7 +172,7 @@ fun ChatPreviewView(
fun chatPreviewInfoText(): Pair<String, Color>? {
return when (cInfo) {
is ChatInfo.Direct ->
if (cInfo.contact.activeConn == null && cInfo.contact.profile.contactLink != null && cInfo.contact.active) {
if (cInfo.contact.isContactCard) {
stringResource(MR.strings.contact_tap_to_connect) to MaterialTheme.colors.primary
} else if (cInfo.contact.sendMsgToConnect) {
stringResource(MR.strings.open_to_connect) to Color.Unspecified
@@ -254,7 +254,8 @@ class AlertManager {
CircularProgressIndicator(Modifier.size(36.dp).padding(4.dp), color = MaterialTheme.colors.secondary, strokeWidth = 3.dp)
}
}
}
},
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
}
@@ -269,6 +270,7 @@ class AlertManager {
fun showOpenChatAlert(
profileName: String,
profileFullName: String,
profileImage: @Composable () -> Unit,
confirmText: String = generalGetString(MR.strings.connect_plan_open_chat),
onConfirm: () -> Unit,
@@ -285,30 +287,41 @@ class AlertManager {
AlertContent(text = null as String?, null) {
Column(
Modifier
.width(360.dp)
.padding(top = DEFAULT_PADDING),
.padding(top = DEFAULT_PADDING_HALF)
.width(360.dp),
verticalArrangement = Arrangement.SpaceEvenly
) {
Row(
Column(
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
horizontalAlignment = Alignment.CenterHorizontally
) {
profileImage()
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Text(
profileName,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h4,
lineHeight = 20.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 2
maxLines = 2,
modifier = Modifier.fillMaxWidth()
)
if (profileFullName.isNotEmpty() && profileFullName != profileName) {
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Text(
profileFullName,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.body2,
maxLines = 2,
modifier = Modifier.fillMaxWidth()
)
}
}
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
Column(
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING_HALF).padding(top = DEFAULT_PADDING, bottom = 2.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
@@ -316,25 +329,23 @@ class AlertManager {
delay(200)
focusRequester.requestFocus()
}
TextButton(onClick = {
onDismiss?.invoke()
hideAlert()
}) {
Text(dismissText)
}
Spacer(Modifier.width(0.dp))
TextButton(onClick = {
onConfirm.invoke()
hideAlert()
}, Modifier.focusRequester(focusRequester)) {
Text(confirmText)
}
TextButton(onClick = {
onDismiss?.invoke()
hideAlert()
}) {
Text(dismissText)
}
}
}
}
}
},
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
}
@@ -83,12 +83,7 @@ suspend fun planAndConnect(
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
} else {
@@ -106,12 +101,7 @@ suspend fun planAndConnect(
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
}
@@ -163,12 +153,7 @@ suspend fun planAndConnect(
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
}
@@ -178,12 +163,7 @@ suspend fun planAndConnect(
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
}
@@ -264,20 +244,7 @@ suspend fun planAndConnect(
if (filterKnownGroup != null) {
filterKnownGroup(groupInfo)
} else {
openKnownGroup(chatModel, rhId, close, groupInfo)
if (groupInfo.businessChat == null) {
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
} else {
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_chat_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connected_with_vName), groupInfo.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
}
showOpenKnownGroupAlert(chatModel, rhId, close, groupInfo)
cleanup()
}
}
@@ -389,6 +356,27 @@ fun openChat_(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, chat: Cha
}
}
val alertProfileImageSize = 138.dp
private fun showOpenKnownContactAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = contact.profile.displayName,
profileFullName = contact.profile.fullName,
profileImage = {
ProfileImage(
size = alertProfileImageSize,
image = contact.profile.image,
icon = MR.images.ic_account_circle_filled
)
},
confirmText = generalGetString(if (contact.nextConnectPrepared) MR.strings.connect_plan_open_new_chat else MR.strings.connect_plan_open_chat),
onConfirm = {
openKnownContact(chatModel, rhId, close, contact)
},
onDismiss = null
)
}
fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) {
withBGApi {
val c = chatModel.getContactChat(contact.contactId)
@@ -454,6 +442,31 @@ fun ownGroupLinkConfirmConnect(
)
}
private fun showOpenKnownGroupAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = groupInfo.groupProfile.displayName,
profileFullName = groupInfo.groupProfile.fullName,
profileImage = {
ProfileImage(
size = alertProfileImageSize,
image = groupInfo.groupProfile.image,
icon = if (groupInfo.businessChat == null) MR.images.ic_supervised_user_circle_filled else MR.images.ic_work_filled_padded
)
},
confirmText = generalGetString(
if (groupInfo.businessChat == null) {
if (groupInfo.nextConnectPrepared) MR.strings.connect_plan_open_new_group else MR.strings.connect_plan_open_group
} else {
if (groupInfo.nextConnectPrepared) MR.strings.connect_plan_open_new_chat else MR.strings.connect_plan_open_chat
}
),
onConfirm = {
openKnownGroup(chatModel, rhId, close, groupInfo)
},
onDismiss = null
)
}
fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) {
withBGApi {
val g = chatModel.getGroupChat(groupInfo.groupId)
@@ -473,14 +486,15 @@ fun showPrepareContactAlert(
) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = contactShortLinkData.profile.displayName,
profileFullName = contactShortLinkData.profile.fullName,
profileImage = {
ProfileImage(
size = 72.dp,
size = alertProfileImageSize,
image = contactShortLinkData.profile.image,
icon = if (contactShortLinkData.business) MR.images.ic_work_filled_padded else MR.images.ic_account_circle_filled
)
},
confirmText = generalGetString(MR.strings.connect_plan_open_chat),
confirmText = generalGetString(MR.strings.connect_plan_open_new_chat),
onConfirm = {
AlertManager.privacySensitive.hideAlert()
withBGApi {
@@ -509,8 +523,9 @@ fun showPrepareGroupAlert(
) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = groupShortLinkData.groupProfile.displayName,
profileImage = { ProfileImage(size = 72.dp, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) },
confirmText = generalGetString(MR.strings.connect_plan_open_group),
profileFullName = groupShortLinkData.groupProfile.fullName,
profileImage = { ProfileImage(size = alertProfileImageSize, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) },
confirmText = generalGetString(MR.strings.connect_plan_open_new_group),
onConfirm = {
AlertManager.privacySensitive.hideAlert()
withBGApi {
@@ -83,7 +83,7 @@ fun chatContactType(chat: Chat): ContactType {
val contact = cInfo.contact
when {
contact.nextAcceptContactRequest -> ContactType.CONTACT_WITH_REQUEST
contact.activeConn == null && contact.profile.contactLink != null && contact.active -> ContactType.CARD
contact.isContactCard -> ContactType.CARD
contact.chatDeleted -> ContactType.CHAT_DELETED
contact.contactStatus == ContactStatus.Active -> ContactType.RECENT
else -> ContactType.UNLISTED
@@ -97,7 +97,7 @@ fun UserAddressView(
}
fun showAddShortLinkAlert() {
AlertManager.shared.showAlertDialog(
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.share_profile_via_link),
text = generalGetString(MR.strings.share_profile_via_link_alert_text),
confirmText = generalGetString(MR.strings.share_profile_via_link_alert_confirm),
@@ -14,7 +14,9 @@
<string name="connect_via_link_verb">Connect</string>
<string name="connect_via_link_incognito">Connect incognito</string>
<string name="connect_plan_open_chat">Open chat</string>
<string name="connect_plan_open_new_chat">Open new chat</string>
<string name="connect_plan_open_group">Open group</string>
<string name="connect_plan_open_new_group">Open new group</string>
<string name="error_parsing_uri_title">Invalid link</string>
<string name="error_parsing_uri_desc">Please check that SimpleX link is correct.</string>
@@ -1086,8 +1088,8 @@
<string name="add_your_team_members_to_conversations">Add your team members to the conversations.</string>
<string name="add_short_link">Add short link</string>
<string name="share_profile_via_link">Share profile via link</string>
<string name="share_profile_via_link_alert_text">Profile will be shared via the address short link. This change to the address cannot be reversed, other than fully deleting it. Do you wish to update the address?</string>
<string name="share_profile_via_link_alert_confirm">Update (and share profile)</string>
<string name="share_profile_via_link_alert_text">Profile will be shared via the address link.</string>
<string name="share_profile_via_link_alert_confirm">Share profile</string>
<string name="share_group_profile_via_link">Share group profile via link</string>
<!-- CreateSimpleXAddress.kt -->