diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 6fe1bef8e..bad15ad52 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -594,7 +594,6 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P
}
func apiConnectPlan(connReq: String) async throws -> ConnectionPlan {
- logger.error("apiConnectPlan connReq: \(connReq)")
let userId = try currentUserId("apiConnectPlan")
let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq))
if case let .connectionPlan(_, connectionPlan) = r { return connectionPlan }
diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
index 249180686..935f09cc1 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
@@ -194,7 +194,7 @@ struct SendAddressMailView: View {
let messageBody = String(format: NSLocalizedString("""
Hi!
Connect to me via SimpleX Chat
- """, comment: "email text"), userAddress.connReqContact)
+ """, comment: "email text"), simplexChatLink(userAddress.connReqContact))
MailView(
isShowing: self.$showMailView,
result: $mailViewResult,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index 132384219..ad033387a 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -136,6 +136,7 @@ object ChatModel {
fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null
fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id }
fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
+ fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id }
fun addChat(chat: Chat) = chats.add(index = 0, chat)
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 6bd269f24..8397d2edb 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
@@ -855,6 +855,14 @@ object ChatController {
return null
}
+ suspend fun apiConnectPlan(connReq: String): ConnectionPlan? {
+ val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
+ val r = sendCmd(CC.APIConnectPlan(userId, connReq))
+ if (r is CR.CRConnectionPlan) return r.connectionPlan
+ Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}")
+ return null
+ }
+
suspend fun apiConnect(incognito: Boolean, connReq: String): Boolean {
val userId = chatModel.currentUser.value?.userId ?: run {
Log.e(TAG, "apiConnect: no current user")
@@ -1914,6 +1922,7 @@ sealed class CC {
class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC()
class APIAddContact(val userId: Long, val incognito: Boolean): 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 ApiClearChat(val type: ChatType, val id: Long): CC()
@@ -2023,6 +2032,7 @@ sealed class CC {
is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else ""
is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}"
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 ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
@@ -2124,6 +2134,7 @@ sealed class CC {
is APIVerifyGroupMember -> "apiVerifyGroupMember"
is APIAddContact -> "apiAddContact"
is ApiSetConnectionIncognito -> "apiSetConnectionIncognito"
+ is APIConnectPlan -> "apiConnectPlan"
is APIConnect -> "apiConnect"
is ApiDeleteChat -> "apiDeleteChat"
is ApiClearChat -> "apiClearChat"
@@ -3337,6 +3348,7 @@ sealed class CR {
@Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR()
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR()
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
+ @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR()
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef): CR()
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef): CR()
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
@@ -3472,6 +3484,7 @@ sealed class CR {
is ConnectionVerified -> "connectionVerified"
is Invitation -> "invitation"
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
+ is CRConnectionPlan -> "connectionPlan"
is SentConfirmation -> "sentConfirmation"
is SentInvitation -> "sentInvitation"
is ContactAlreadyExists -> "contactAlreadyExists"
@@ -3602,6 +3615,7 @@ sealed class CR {
is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode")
is Invitation -> withUser(user, connReqInvitation)
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
+ is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan))
is SentConfirmation -> withUser(user, noDetails())
is SentInvitation -> withUser(user, noDetails())
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
@@ -3715,6 +3729,39 @@ fun chatError(r: CR): ChatErrorType? {
)
}
+@Serializable
+sealed class ConnectionPlan {
+ @Serializable @SerialName("invitationLink") class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan()
+ @Serializable @SerialName("contactAddress") class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan()
+ @Serializable @SerialName("groupLink") class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan()
+}
+
+@Serializable
+sealed class InvitationLinkPlan {
+ @Serializable @SerialName("ok") object Ok: InvitationLinkPlan()
+ @Serializable @SerialName("ownLink") object OwnLink: InvitationLinkPlan()
+ @Serializable @SerialName("connecting") class Connecting(val contact_: Contact? = null): InvitationLinkPlan()
+ @Serializable @SerialName("known") class Known(val contact: Contact): InvitationLinkPlan()
+}
+
+@Serializable
+sealed class ContactAddressPlan {
+ @Serializable @SerialName("ok") object Ok: ContactAddressPlan()
+ @Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan()
+ @Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan()
+ @Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan()
+ @Serializable @SerialName("known") class Known(val contact: Contact): ContactAddressPlan()
+}
+
+@Serializable
+sealed class GroupLinkPlan {
+ @Serializable @SerialName("ok") object Ok: GroupLinkPlan()
+ @Serializable @SerialName("ownLink") class OwnLink(val groupInfo: GroupInfo): GroupLinkPlan()
+ @Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: GroupLinkPlan()
+ @Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val groupInfo_: GroupInfo? = null): GroupLinkPlan()
+ @Serializable @SerialName("known") class Known(val groupInfo: GroupInfo): GroupLinkPlan()
+}
+
abstract class TerminalItem {
abstract val id: Long
val date: Instant = Clock.System.now()
@@ -3874,6 +3921,7 @@ sealed class ChatErrorType {
is ChatNotStarted -> "chatNotStarted"
is ChatNotStopped -> "chatNotStopped"
is ChatStoreChanged -> "chatStoreChanged"
+ is ConnectionPlanChatError -> "connectionPlan"
is InvalidConnReq -> "invalidConnReq"
is InvalidChatMessage -> "invalidChatMessage"
is ContactNotReady -> "contactNotReady"
@@ -3950,6 +3998,7 @@ sealed class ChatErrorType {
@Serializable @SerialName("chatNotStarted") object ChatNotStarted: ChatErrorType()
@Serializable @SerialName("chatNotStopped") object ChatNotStopped: ChatErrorType()
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
+ @Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType()
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 1ba742b03..01173157a 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
@@ -31,10 +31,10 @@ import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
-import chat.simplex.common.views.newchat.QRCode
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.platform.*
import chat.simplex.common.views.chatlist.updateChatSettings
+import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@@ -309,9 +309,9 @@ fun ChatInfoLayout(
if (contact.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
- QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
+ SimpleXLinkQRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
val clipboard = LocalClipboardManager.current
- ShareAddressButton { clipboard.shareText(contact.contactLink) }
+ ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
}
SectionDividerSpaced()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt
index 7c767f9b7..7e1c03130 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt
@@ -19,7 +19,7 @@ import chat.simplex.common.model.*
import chat.simplex.common.platform.shareText
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
-import chat.simplex.common.views.newchat.QRCode
+import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
@Composable
@@ -44,14 +44,12 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
createLink()
}
}
- val clipboard = LocalClipboardManager.current
GroupLinkLayout(
groupLink = groupLink,
groupInfo,
groupLinkMemberRole,
creatingLink,
createLink = ::createLink,
- share = { clipboard.shareText(groupLink ?: return@GroupLinkLayout) },
updateLink = {
val role = groupLinkMemberRole.value
if (role != null) {
@@ -95,7 +93,6 @@ fun GroupLinkLayout(
groupLinkMemberRole: MutableState,
creatingLink: Boolean,
createLink: () -> Unit,
- share: () -> Unit,
updateLink: () -> Unit,
deleteLink: () -> Unit
) {
@@ -125,16 +122,17 @@ fun GroupLinkLayout(
}
initialLaunch = false
}
- QRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
+ SimpleXLinkQRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
) {
+ val clipboard = LocalClipboardManager.current
SimpleButton(
stringResource(MR.strings.share_link),
icon = painterResource(MR.images.ic_share),
- click = share
+ click = { clipboard.shareText(simplexChatLink(groupLink)) }
)
SimpleButton(
stringResource(MR.strings.delete_link),
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
index c76fb73a6..e281c5762 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
@@ -284,9 +284,9 @@ fun GroupMemberInfoLayout(
if (member.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
- QRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
+ SimpleXLinkQRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
val clipboard = LocalClipboardManager.current
- ShareAddressButton { clipboard.shareText(member.contactLink) }
+ ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
if (contactId != null) {
if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on) {
ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) })
@@ -473,43 +473,17 @@ private fun updateMemberRoleDialog(
}
fun connectViaMemberAddressAlert(connReqUri: String) {
- AlertManager.shared.showAlertDialogButtonsColumn(
- title = generalGetString(MR.strings.connect_via_member_address_alert_title),
- text = AnnotatedString(generalGetString(MR.strings.connect_via_member_address_alert_desc)),
- buttons = {
- Column {
- SectionItemView({
- AlertManager.shared.hideAlert()
- val uri = URI(connReqUri)
- withUriAction(uri) { linkType ->
- withApi {
- Log.d(TAG, "connectViaUri: connecting")
- connectViaUri(chatModel, linkType, uri, incognito = false)
- }
- }
- }) {
- Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- SectionItemView({
- AlertManager.shared.hideAlert()
- val uri = URI(connReqUri)
- withUriAction(uri) { linkType ->
- withApi {
- Log.d(TAG, "connectViaUri: connecting incognito")
- connectViaUri(chatModel, linkType, uri, incognito = true)
- }
- }
- }) {
- Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- SectionItemView({
- AlertManager.shared.hideAlert()
- }) {
- Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- }
+ try {
+ val uri = URI(connReqUri)
+ withApi {
+ planAndConnect(chatModel, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
}
- )
+ } catch (e: RuntimeException) {
+ AlertManager.shared.showAlertMsg(
+ title = generalGetString(MR.strings.invalid_connection_link),
+ text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
+ )
+ }
}
@Preview
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
index b45543df4..b16bf4c9b 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
@@ -130,6 +130,13 @@ suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
}
}
+suspend fun openGroupChat(groupId: Long, chatModel: ChatModel) {
+ val chat = chatModel.controller.apiGetChat(ChatType.Group, groupId)
+ if (chat != null) {
+ openChat(chat, chatModel)
+ }
+}
+
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
Log.d(TAG, "TODOCHAT: openChat: opening ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}")
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
index 6d7450a21..66ef1cf9f 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt
@@ -316,46 +316,8 @@ fun connectIfOpenedViaUri(uri: URI, chatModel: ChatModel) {
if (chatModel.currentUser.value == null) {
chatModel.appOpenUrl.value = uri
} else {
- withUriAction(uri) { linkType ->
- val title = when (linkType) {
- ConnectionLinkType.CONTACT -> generalGetString(MR.strings.connect_via_contact_link)
- ConnectionLinkType.INVITATION -> generalGetString(MR.strings.connect_via_invitation_link)
- ConnectionLinkType.GROUP -> generalGetString(MR.strings.connect_via_group_link)
- }
- AlertManager.shared.showAlertDialogButtonsColumn(
- title = title,
- text = if (linkType == ConnectionLinkType.GROUP)
- AnnotatedString(generalGetString(MR.strings.you_will_join_group))
- else
- AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
- buttons = {
- Column {
- SectionItemView({
- AlertManager.shared.hideAlert()
- withApi {
- Log.d(TAG, "connectIfOpenedViaUri: connecting")
- connectViaUri(chatModel, linkType, uri, incognito = false)
- }
- }) {
- Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- SectionItemView({
- AlertManager.shared.hideAlert()
- withApi {
- Log.d(TAG, "connectIfOpenedViaUri: connecting incognito")
- connectViaUri(chatModel, linkType, uri, incognito = true)
- }
- }) {
- Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- SectionItemView({
- AlertManager.shared.hideAlert()
- }) {
- Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- }
- }
- )
+ withApi {
+ planAndConnect(chatModel, uri, incognito = null, close = null)
}
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt
index 8542ea52a..ef3633d1f 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt
@@ -34,7 +34,6 @@ fun AddContactView(
incognitoPref = chatModel.controller.appPrefs.incognito,
connReq = connReqInvitation,
contactConnection = contactConnection,
- share = { clipboard.shareText(connReqInvitation) },
learnMore = {
ModalManager.center.showModal {
Column(
@@ -56,7 +55,6 @@ fun AddContactLayout(
incognitoPref: SharedPreference,
connReq: String,
contactConnection: MutableState,
- share: () -> Unit,
learnMore: () -> Unit
) {
val incognito = remember { mutableStateOf(incognitoPref.get()) }
@@ -82,7 +80,7 @@ fun AddContactLayout(
SectionView(stringResource(MR.strings.one_time_link_short).uppercase()) {
if (connReq.isNotEmpty()) {
- QRCode(
+ SimpleXLinkQRCode(
connReq, Modifier
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
.aspectRatio(1f)
@@ -99,7 +97,7 @@ fun AddContactLayout(
}
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
- ShareLinkButton(share)
+ ShareLinkButton(connReq)
OneTimeLinkLearnMoreButton(learnMore)
}
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
@@ -109,11 +107,12 @@ fun AddContactLayout(
}
@Composable
-fun ShareLinkButton(onClick: () -> Unit) {
+fun ShareLinkButton(connReqInvitation: String) {
+ val clipboard = LocalClipboardManager.current
SettingsActionItem(
painterResource(MR.images.ic_share),
stringResource(MR.strings.share_invitation_link),
- onClick,
+ click = { clipboard.shareText(simplexChatLink(connReqInvitation)) },
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
@@ -177,7 +176,6 @@ fun PreviewAddContactView() {
incognitoPref = SharedPreference({ false }, {}),
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
contactConnection = mutableStateOf(PendingContactConnection.getSampleData()),
- share = {},
learnMore = {},
)
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt
index 934c050d8..d04a85d90 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt
@@ -131,13 +131,13 @@ private fun ContactConnectionInfoLayout(
SectionView {
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
- QRCode(
+ SimpleXLinkQRCode(
connReq, Modifier
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
.aspectRatio(1f)
)
incognitoEnabled()
- ShareLinkButton(share)
+ ShareLinkButton(connReq)
OneTimeLinkLearnMoreButton(learnMore)
} else {
incognitoEnabled()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt
index 0c13bd4f6..f7a5a1e86 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt
@@ -53,32 +53,14 @@ fun PasteToConnectLayout(
fun connectViaLink(connReqUri: String) {
try {
val uri = URI(connReqUri)
- withUriAction(uri) { linkType ->
- val action = suspend {
- Log.d(TAG, "connectViaUri: connecting")
- if (connectViaUri(chatModel, linkType, uri, incognito = incognito.value)) {
- close()
- }
- }
- if (linkType == ConnectionLinkType.GROUP) {
- AlertManager.shared.showAlertDialog(
- title = generalGetString(MR.strings.connect_via_group_link),
- text = generalGetString(MR.strings.you_will_join_group),
- confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
- onConfirm = { withApi { action() } }
- )
- } else action()
+ withApi {
+ planAndConnect(chatModel, uri, incognito = incognito.value, close)
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.invalid_connection_link),
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
)
- } catch (e: URISyntaxException) {
- AlertManager.shared.showAlertMsg(
- title = generalGetString(MR.strings.invalid_connection_link),
- text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
- )
}
}
@@ -115,6 +97,7 @@ fun PasteToConnectLayout(
painterResource(MR.images.ic_link),
stringResource(MR.strings.connect_button),
click = { connectViaLink(connectionLink.value) },
+ disabled = connectionLink.value.isEmpty() || connectionLink.value.trim().contains(" ")
)
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt
index 663292596..763addae6 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt
@@ -19,6 +19,29 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import kotlinx.coroutines.launch
+@Composable
+fun SimpleXLinkQRCode(
+ connReq: String,
+ modifier: Modifier = Modifier,
+ tintColor: Color = Color(0xff062d56),
+ withLogo: Boolean = true
+) {
+ QRCode(
+ simplexChatLink(connReq),
+ modifier,
+ tintColor,
+ withLogo
+ )
+}
+
+fun simplexChatLink(uri: String): String {
+ return if (uri.startsWith("simplex:/")) {
+ uri.replace("simplex:/", "https://simplex.chat/")
+ } else {
+ uri
+ }
+}
+
@Composable
fun QRCode(
connReq: String,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt
index e3fa92275..2f52c2cac 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt
@@ -1,6 +1,7 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
+import SectionItemView
import SectionTextFooter
import androidx.compose.desktop.ui.tooling.preview.Preview
import chat.simplex.common.platform.Log
@@ -10,66 +11,265 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextAlign
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.TAG
import chat.simplex.common.ui.theme.*
+import chat.simplex.common.views.chatlist.openDirectChat
+import chat.simplex.common.views.chatlist.openGroupChat
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
import java.net.URI
@Composable
expect fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit)
enum class ConnectionLinkType {
- CONTACT, INVITATION, GROUP
+ INVITATION, CONTACT, GROUP
}
-@Serializable
-sealed class CReqClientData {
- @Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData()
-}
-
-fun withUriAction(uri: URI, run: suspend (ConnectionLinkType) -> Unit) {
- val action = uri.path?.drop(1)?.replace("/", "")
- val data = URI(uri.toString().replaceFirst("#/", "/")).getQueryParameter("data")
- val type = when {
- data != null -> {
- val parsed = runCatching {
- json.decodeFromString(CReqClientData.serializer(), data)
+suspend fun planAndConnect(
+ chatModel: ChatModel,
+ uri: URI,
+ incognito: Boolean?,
+ close: (() -> Unit)?
+) {
+ val connectionPlan = chatModel.controller.apiConnectPlan(uri.toString())
+ if (connectionPlan != null) {
+ when (connectionPlan) {
+ is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
+ InvitationLinkPlan.Ok -> {
+ Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
+ if (incognito != null) {
+ connectViaUri(chatModel, uri, incognito, connectionPlan, close)
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_via_invitation_link),
+ text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
+ connectDestructive = false
+ )
+ }
+ }
+ InvitationLinkPlan.OwnLink -> {
+ Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito")
+ if (incognito != null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
+ text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link),
+ confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
+ onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
+ destructive = true,
+ )
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
+ text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)),
+ connectDestructive = true
+ )
+ }
+ }
+ is InvitationLinkPlan.Connecting -> {
+ Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito")
+ val contact = connectionPlan.invitationLinkPlan.contact_
+ if (contact != null) {
+ openKnownContact(chatModel, close, contact)
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.contact_already_exists),
+ String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
+ )
+ } else {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.connect_plan_already_connecting),
+ generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link)
+ )
+ }
+ }
+ is InvitationLinkPlan.Known -> {
+ Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
+ val contact = connectionPlan.invitationLinkPlan.contact
+ openKnownContact(chatModel, close, contact)
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.contact_already_exists),
+ String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
+ )
+ }
}
- when {
- parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP
- else -> null
+ is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) {
+ ContactAddressPlan.Ok -> {
+ Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
+ if (incognito != null) {
+ connectViaUri(chatModel, uri, incognito, connectionPlan, close)
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_via_contact_link),
+ text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
+ connectDestructive = false
+ )
+ }
+ }
+ ContactAddressPlan.OwnLink -> {
+ Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito")
+ if (incognito != null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
+ text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address),
+ confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
+ onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
+ destructive = true,
+ )
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
+ text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)),
+ connectDestructive = true
+ )
+ }
+ }
+ ContactAddressPlan.ConnectingConfirmReconnect -> {
+ Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito")
+ if (incognito != null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
+ text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address),
+ confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
+ onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
+ destructive = true,
+ )
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
+ text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)),
+ connectDestructive = true
+ )
+ }
+ }
+ is ContactAddressPlan.ConnectingProhibit -> {
+ Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
+ val contact = connectionPlan.contactAddressPlan.contact
+ openKnownContact(chatModel, close, contact)
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.contact_already_exists),
+ String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
+ )
+ }
+ is ContactAddressPlan.Known -> {
+ Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
+ val contact = connectionPlan.contactAddressPlan.contact
+ openKnownContact(chatModel, close, contact)
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.contact_already_exists),
+ String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
+ )
+ }
+ }
+ is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
+ GroupLinkPlan.Ok -> {
+ Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito")
+ if (incognito != null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.connect_via_group_link),
+ text = generalGetString(MR.strings.you_will_join_group),
+ confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
+ onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }
+ )
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_via_group_link),
+ text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)),
+ connectDestructive = false
+ )
+ }
+ }
+ is GroupLinkPlan.OwnLink -> {
+ Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito")
+ val groupInfo = connectionPlan.groupLinkPlan.groupInfo
+ ownGroupLinkConfirmConnect(chatModel, uri, incognito, connectionPlan, groupInfo, close)
+ }
+ GroupLinkPlan.ConnectingConfirmReconnect -> {
+ Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
+ if (incognito != null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.connect_plan_repeat_join_request),
+ text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
+ confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
+ onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
+ destructive = true,
+ )
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan, close,
+ title = generalGetString(MR.strings.connect_plan_repeat_join_request),
+ text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)),
+ connectDestructive = true
+ )
+ }
+ }
+ is GroupLinkPlan.ConnectingProhibit -> {
+ Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito")
+ val groupInfo = connectionPlan.groupLinkPlan.groupInfo_
+ if (groupInfo != null) {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.connect_plan_group_already_exists),
+ String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName)
+ )
+ } else {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.connect_plan_already_joining_the_group),
+ generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)
+ )
+ }
+ }
+ is GroupLinkPlan.Known -> {
+ Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
+ val groupInfo = connectionPlan.groupLinkPlan.groupInfo
+ openKnownGroup(chatModel, close, groupInfo)
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.connect_plan_group_already_exists),
+ String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName)
+ )
+ }
}
}
-
- action == "contact" -> ConnectionLinkType.CONTACT
- action == "invitation" -> ConnectionLinkType.INVITATION
- else -> null
- }
- if (type != null) {
- withApi { run(type) }
} else {
- AlertManager.shared.showAlertMsg(
- title = generalGetString(MR.strings.invalid_contact_link),
- text = generalGetString(MR.strings.this_link_is_not_a_valid_connection_link)
- )
+ Log.d(TAG, "planAndConnect, plan error")
+ if (incognito != null) {
+ connectViaUri(chatModel, uri, incognito, connectionPlan = null, close)
+ } else {
+ askCurrentOrIncognitoProfileAlert(
+ chatModel, uri, connectionPlan = null, close,
+ title = generalGetString(MR.strings.connect_plan_connect_via_link),
+ connectDestructive = false
+ )
+ }
}
}
-suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: URI, incognito: Boolean): Boolean {
+suspend fun connectViaUri(
+ chatModel: ChatModel,
+ uri: URI,
+ incognito: Boolean,
+ connectionPlan: ConnectionPlan?,
+ close: (() -> Unit)?
+): Boolean {
val r = chatModel.controller.apiConnect(incognito, uri.toString())
+ val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
if (r) {
+ close?.invoke()
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.connection_request_sent),
text =
- when (action) {
+ when (connLinkType) {
ConnectionLinkType.CONTACT -> generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted)
ConnectionLinkType.INVITATION -> generalGetString(MR.strings.you_will_be_connected_when_your_contacts_device_is_online)
ConnectionLinkType.GROUP -> generalGetString(MR.strings.you_will_be_connected_when_group_host_device_is_online)
@@ -79,6 +279,139 @@ suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri:
return r
}
+fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType {
+ return when(connectionPlan) {
+ is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION
+ is ConnectionPlan.ContactAddress -> ConnectionLinkType.CONTACT
+ is ConnectionPlan.GroupLink -> ConnectionLinkType.GROUP
+ }
+}
+
+fun askCurrentOrIncognitoProfileAlert(
+ chatModel: ChatModel,
+ uri: URI,
+ connectionPlan: ConnectionPlan?,
+ close: (() -> Unit)?,
+ title: String,
+ text: AnnotatedString? = null,
+ connectDestructive: Boolean,
+) {
+ AlertManager.shared.showAlertDialogButtonsColumn(
+ title = title,
+ text = text,
+ buttons = {
+ Column {
+ val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
+ }
+ }) {
+ Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
+ }
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
+ }
+ }) {
+ Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
+ }
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ }) {
+ Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
+ }
+ }
+ }
+ )
+}
+
+fun openKnownContact(chatModel: ChatModel, close: (() -> Unit)?, contact: Contact) {
+ withApi {
+ val c = chatModel.getContactChat(contact.contactId)
+ if (c != null) {
+ close?.invoke()
+ openDirectChat(contact.contactId, chatModel)
+ }
+ }
+}
+
+fun ownGroupLinkConfirmConnect(
+ chatModel: ChatModel,
+ uri: URI,
+ incognito: Boolean?,
+ connectionPlan: ConnectionPlan?,
+ groupInfo: GroupInfo,
+ close: (() -> Unit)?,
+) {
+ AlertManager.shared.showAlertDialogButtonsColumn(
+ title = generalGetString(MR.strings.connect_plan_join_your_group),
+ text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)),
+ buttons = {
+ Column {
+ // Open group
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ openKnownGroup(chatModel, close, groupInfo)
+ }) {
+ Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
+ }
+ if (incognito != null) {
+ // Join incognito / Join with current profile
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ connectViaUri(chatModel, uri, incognito, connectionPlan, close)
+ }
+ }) {
+ Text(
+ if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
+ Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error
+ )
+ }
+ } else {
+ // Use current profile
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
+ }
+ }) {
+ Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
+ }
+ // Use new incognito profile
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ withApi {
+ connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
+ }
+ }) {
+ Text(generalGetString(MR.strings.connect_use_new_incognito_profile), 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)
+ }
+ }
+ }
+ )
+}
+
+fun openKnownGroup(chatModel: ChatModel, close: (() -> Unit)?, groupInfo: GroupInfo) {
+ withApi {
+ val g = chatModel.getGroupChat(groupInfo.groupId)
+ if (g != null) {
+ close?.invoke()
+ openGroupChat(groupInfo.groupId, chatModel)
+ }
+ }
+}
+
@Composable
fun ConnectContactLayout(
chatModel: ChatModel,
@@ -92,21 +425,8 @@ fun ConnectContactLayout(
QRCodeScanner { connReqUri ->
try {
val uri = URI(connReqUri)
- withUriAction(uri) { linkType ->
- val action = suspend {
- Log.d(TAG, "connectViaUri: connecting")
- if (connectViaUri(ChatModel, linkType, uri, incognito = incognito.value)) {
- close()
- }
- }
- if (linkType == ConnectionLinkType.GROUP) {
- AlertManager.shared.showAlertDialog(
- title = generalGetString(MR.strings.connect_via_group_link),
- text = generalGetString(MR.strings.you_will_join_group),
- confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
- onConfirm = { withApi { action() } }
- )
- } else action()
+ withApi {
+ planAndConnect(chatModel, uri, incognito = incognito.value, close)
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt
index 72cbc3a62..013222338 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt
@@ -18,7 +18,8 @@ import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
-import chat.simplex.common.views.newchat.QRCode
+import chat.simplex.common.views.newchat.SimpleXLinkQRCode
+import chat.simplex.common.views.newchat.simplexChatLink
import chat.simplex.res.MR
@Composable
@@ -38,7 +39,7 @@ fun CreateSimpleXAddress(m: ChatModel) {
sendEmail = { address ->
uriHandler.sendEmail(
generalGetString(MR.strings.email_invite_subject),
- generalGetString(MR.strings.email_invite_body).format(address.connReqContact)
+ generalGetString(MR.strings.email_invite_body).format(simplexChatLink(address.connReqContact))
)
},
createAddress = {
@@ -91,8 +92,8 @@ private fun CreateSimpleXAddressLayout(
Spacer(Modifier.weight(1f))
if (userAddress != null) {
- QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
- ShareAddressButton { share(userAddress.connReqContact) }
+ SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
+ ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
Spacer(Modifier.weight(1f))
ShareViaEmailButton { sendEmail(userAddress) }
Spacer(Modifier.weight(1f))
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt
index 63f06a2ae..d03b75856 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt
@@ -24,10 +24,10 @@ import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.ShareAddressButton
import chat.simplex.common.views.helpers.*
-import chat.simplex.common.views.newchat.QRCode
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.MsgContent
import chat.simplex.common.platform.*
+import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
@Composable
@@ -100,7 +100,7 @@ fun UserAddressView(
sendEmail = { userAddress ->
uriHandler.sendEmail(
generalGetString(MR.strings.email_invite_subject),
- generalGetString(MR.strings.email_invite_body).format(userAddress.connReqContact)
+ generalGetString(MR.strings.email_invite_body).format(simplexChatLink( userAddress.connReqContact))
)
},
setProfileAddress = ::setProfileAddress,
@@ -201,8 +201,8 @@ private fun UserAddressLayout(
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
- QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
- ShareAddressButton { share(userAddress.connReqContact) }
+ SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
+ ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
ShareViaEmailButton { sendEmail(userAddress) }
ShareWithContactsButton(shareViaProfile, setProfileAddress)
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
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 114fe49e9..bd59d236d 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -4,13 +4,13 @@
k
- Connect via contact link?
- Connect via invitation link?
- Connect via group link?
+ Connect via contact address?
+ Connect via one-time link?
+ Join group?
Use current profile
Use new incognito profile
Your profile will be sent to the contact that you received this link from.
- You will join a group this link refers to and connect to its group members.
+ You will connect to all group members.
Connect
Connect incognito
@@ -1600,4 +1600,25 @@
Coming soon!
This feature is not yet supported. Try the next release.
+
+
+ Connect to yourself?
+ This is your own one-time link!
+ You are already connecting to %1$s.
+ Already connecting!
+ You are already connecting via this one-time link!
+ This is your own SimpleX address!
+ Repeat connection request?
+ You have already requested connection via this address!
+ Join your group?
+ This is your link for group %1$s!
+ Open group
+ Repeat join request?
+ You are already joining the group via this link!
+ Group already exists!
+ You are already joining the group %1$s.
+ Already joining the group!
+ You are already joining the group via this link.
+ You are already in group %1$s.
+ Connect via link?
\ No newline at end of file