diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index c7bf7635a9..68893ebef6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -33,8 +33,7 @@ import chat.simplex.app.views.chat.ChatView import chat.simplex.app.views.chatlist.* import chat.simplex.app.views.database.DatabaseErrorView import chat.simplex.app.views.helpers.* -import chat.simplex.app.views.newchat.connectViaUri -import chat.simplex.app.views.newchat.withUriAction +import chat.simplex.app.views.newchat.* import chat.simplex.app.views.onboarding.* import kotlinx.coroutines.delay @@ -426,23 +425,23 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) { // TODO open from chat list view chatModel.appOpenUrl.value = uri } else { - withUriAction(uri) { action -> - val title = when (action) { - "contact" -> generalGetString(R.string.connect_via_contact_link) - "invitation" -> generalGetString(R.string.connect_via_invitation_link) - else -> { - Log.e(TAG, "URI has unexpected action. Alert shown.") - action - } + withUriAction(uri) { linkType -> + val title = when (linkType) { + ConnectionLinkType.CONTACT -> generalGetString(R.string.connect_via_contact_link) + ConnectionLinkType.INVITATION -> generalGetString(R.string.connect_via_invitation_link) + ConnectionLinkType.GROUP -> generalGetString(R.string.connect_via_group_link) } AlertManager.shared.showAlertMsg( title = title, - text = generalGetString(R.string.profile_will_be_sent_to_contact_sending_link), + text = if (linkType == ConnectionLinkType.GROUP) + generalGetString(R.string.you_will_join_group) + else + generalGetString(R.string.profile_will_be_sent_to_contact_sending_link), confirmText = generalGetString(R.string.connect_via_link_verb), onConfirm = { withApi { Log.d(TAG, "connectIfOpenedViaUri: connecting") - connectViaUri(chatModel, action, uri) + connectViaUri(chatModel, linkType, uri) } } ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt index 9554d8a991..f231195b0e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/PasteToConnect.kt @@ -3,6 +3,7 @@ package chat.simplex.app.views.newchat import android.content.ClipboardManager import android.content.res.Configuration import android.net.Uri +import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -17,6 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.getSystemService import chat.simplex.app.R +import chat.simplex.app.TAG import chat.simplex.app.model.ChatModel import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* @@ -35,10 +37,21 @@ fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) { connectViaLink = { connReqUri -> try { val uri = Uri.parse(connReqUri) - withUriAction(uri) { action -> - if (connectViaUri(chatModel, action, uri)) { - close() + withUriAction(uri) { linkType -> + val action = suspend { + Log.d(TAG, "connectViaUri: connecting") + if (connectViaUri(chatModel, linkType, uri)) { + close() + } } + if (linkType == ConnectionLinkType.GROUP) { + AlertManager.shared.showAlertMsg( + title = generalGetString(R.string.connect_via_group_link), + text = generalGetString(R.string.you_will_join_group), + confirmText = generalGetString(R.string.connect_via_link_verb), + onConfirm = { withApi { action() } } + ) + } else action() } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt index 5d22090c27..eb7558dbf5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ScanToConnectView.kt @@ -3,6 +3,7 @@ package chat.simplex.app.views.newchat import android.Manifest import android.content.res.Configuration import android.net.Uri +import android.util.Log import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -14,12 +15,17 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.net.toUri import chat.simplex.app.R +import chat.simplex.app.TAG import chat.simplex.app.model.ChatModel +import chat.simplex.app.model.json import chat.simplex.app.ui.theme.DEFAULT_PADDING import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.* import com.google.accompanist.permissions.rememberPermissionState +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Composable fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { @@ -33,10 +39,21 @@ fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { QRCodeScanner { connReqUri -> try { val uri = Uri.parse(connReqUri) - withUriAction(uri) { action -> - if (connectViaUri(chatModel, action, uri)) { - close() + withUriAction(uri) { linkType -> + val action = suspend { + Log.d(TAG, "connectViaUri: connecting") + if (connectViaUri(chatModel, linkType, uri)) { + close() + } } + if (linkType == ConnectionLinkType.GROUP) { + AlertManager.shared.showAlertMsg( + title = generalGetString(R.string.connect_via_group_link), + text = generalGetString(R.string.you_will_join_group), + confirmText = generalGetString(R.string.connect_via_link_verb), + onConfirm = { withApi { action() } } + ) + } else action() } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( @@ -49,10 +66,34 @@ fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { ) } -fun withUriAction(uri: Uri, run: suspend (String) -> Unit) { +enum class ConnectionLinkType { + CONTACT, INVITATION, 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("/", "") - if (action == "contact" || action == "invitation") { - withApi { run(action) } + val data = uri.toString().replaceFirst("#/", "/").toUri().getQueryParameter("data") + val type = when { + data != null -> { + val parsed = runCatching { + json.decodeFromString(CReqClientData.serializer(), data) + } + when { + parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP + else -> null + } + } + action == "contact" -> ConnectionLinkType.CONTACT + action == "invitation" -> ConnectionLinkType.INVITATION + else -> null + } + if (type != null) { + withApi { run(type) } } else { AlertManager.shared.showAlertMsg( title = generalGetString(R.string.invalid_contact_link), @@ -61,14 +102,17 @@ fun withUriAction(uri: Uri, run: suspend (String) -> Unit) { } } -suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri): Boolean { +suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: Uri): Boolean { val r = chatModel.controller.apiConnect(uri.toString()) if (r) { AlertManager.shared.showAlertMsg( title = generalGetString(R.string.connection_request_sent), text = - if (action == "contact") generalGetString(R.string.you_will_be_connected_when_your_connection_request_is_accepted) - else generalGetString(R.string.you_will_be_connected_when_your_contacts_device_is_online) + when (action) { + ConnectionLinkType.CONTACT -> generalGetString(R.string.you_will_be_connected_when_your_connection_request_is_accepted) + ConnectionLinkType.INVITATION -> generalGetString(R.string.you_will_be_connected_when_your_contacts_device_is_online) + ConnectionLinkType.GROUP -> generalGetString(R.string.you_will_be_connected_when_group_host_device_is_online) + } ) } return r diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index ce89176602..5c2011f2bb 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -6,7 +6,9 @@ Über den Kontakt-Link verbinden? Über den Einladungs-Link verbinden? + Über den Gruppen-Link verbinden? Ihr Profil wird an den Kontakt gesendet von dem Sie diesen Link erhalten haben. + Sie werden der Gruppe beitreten, auf die sich dieser Link bezieht und sich mit deren Gruppenmitgliedern verbinden. Verbinden diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 7c00d2daa7..da0397cc9b 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -6,7 +6,9 @@ Соединиться через ссылку-контакт? Соединиться через ссылку-приглашение? + Соединиться через ссылку группы? Ваш профиль будет отправлен контакту, от которого вы получили эту ссылку. + Вы вступите в группу, на которую ссылается эта ссылка. Соединиться diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 3cdd934816..b1e492ea7d 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -6,7 +6,9 @@ Connect via contact link? Connect via invitation link? + Connect via group link? 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. Connect diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index ff6b94dffd..bdf8f9b02e 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -78,25 +78,25 @@ func connectViaLink(_ connectionLink: String, _ dismiss: DismissAction? = nil) { } } -struct CRData: Decodable { +struct CReqClientData: Decodable { var type: String var groupLinkId: String? } -func parseLinkQueryData(_ connectionLink: String) -> CRData? { +func parseLinkQueryData(_ connectionLink: String) -> CReqClientData? { if let hashIndex = connectionLink.firstIndex(of: "#"), let urlQuery = URL(string: String(connectionLink[connectionLink.index(after: hashIndex)...])), let components = URLComponents(url: urlQuery, resolvingAgainstBaseURL: false), let data = components.queryItems?.first(where: { $0.name == "data" })?.value, let d = data.data(using: .utf8), - let crData = try? getJSONDecoder().decode(CRData.self, from: d) { + let crData = try? getJSONDecoder().decode(CReqClientData.self, from: d) { return crData } else { return nil } } -func checkCRDataGroup(_ crData: CRData) -> Bool { +func checkCRDataGroup(_ crData: CReqClientData) -> Bool { return crData.type == "group" && crData.groupLinkId != nil }