android: Connection info (#1169)

* android: Connection info

* UI changes

* Icon in text field

* Alias text field

* Revert "Alias text field"

This reverts commit 2ac694db4d.

* Padding

* Layout changes

* Bigger delay

* UI changes

* UI changes
This commit is contained in:
Stanislav Dmitrenko
2022-10-04 19:25:53 +03:00
committed by GitHub
parent 7a54351f15
commit b263d7e547
13 changed files with 266 additions and 25 deletions

View File

@@ -63,6 +63,9 @@ class ChatModel(val controller: ChatController) {
val showCallView = mutableStateOf(false)
val switchingCall = mutableStateOf(false)
// currently showing QR code
val connReqInv = mutableStateOf(null as String?)
// working with external intents
val sharedContent = mutableStateOf(null as SharedContent?)
@@ -283,6 +286,15 @@ class ChatModel(val controller: ChatController) {
chats.add(index = 0, chat)
}
fun dismissConnReqView(id: String) {
if (connReqInv.value == null) return
val info = getChat(id)?.chatInfo as? ChatInfo.ContactConnection ?: return
if (info.contactConnection.connReqInv == connReqInv.value) {
connReqInv.value = null
ModalManager.shared.closeModals()
}
}
fun removeChat(id: String) {
chats.removeAll { it.id == id }
}
@@ -878,6 +890,8 @@ class PendingContactConnection(
val pccConnStatus: ConnStatus,
val viaContactUri: Boolean,
val customUserProfileId: Long? = null,
val connReqInv: String? = null,
override val localAlias: String,
override val createdAt: Instant,
override val updatedAt: Instant
): SomeChat, NamedChat {
@@ -889,6 +903,7 @@ class PendingContactConnection(
override val ntfsEnabled get() = false
override val localDisplayName get() = String.format(generalGetString(R.string.connection_local_display_name), pccConnId)
override val displayName: String get() {
if (localAlias.isNotEmpty()) return localAlias
val initiated = pccConnStatus.initiated
return if (initiated == null) {
// this should not be in the chat list
@@ -902,7 +917,6 @@ class PendingContactConnection(
}
override val fullName get() = ""
override val image get() = null
override val localAlias get() = ""
val initiated get() = (pccConnStatus.initiated ?: false) && !viaContactUri
@@ -927,6 +941,7 @@ class PendingContactConnection(
pccAgentConnId = "abcd",
pccConnStatus = status,
viaContactUri = viaContactUri,
localAlias = "",
customUserProfileId = null,
createdAt = Clock.System.now(),
updatedAt = Clock.System.now()

View File

@@ -597,6 +597,13 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
return null
}
suspend fun apiSetConnectionAlias(connId: Long, localAlias: String): PendingContactConnection? {
val r = sendCmd(CC.ApiSetConnectionAlias(connId, localAlias))
if (r is CR.ConnectionAliasUpdated) return r.toConnection
Log.e(TAG, "apiSetConnectionAlias bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiCreateUserAddress(): String? {
val r = sendCmd(CC.CreateMyAddress())
return when (r) {
@@ -846,12 +853,14 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
is CR.ContactConnected -> {
chatModel.updateContact(r.contact)
chatModel.dismissConnReqView(r.contact.activeConn.id)
chatModel.removeChat(r.contact.activeConn.id)
chatModel.updateNetworkStatus(r.contact.id, Chat.NetworkStatus.Connected())
ntfManager.notifyContactConnected(r.contact)
}
is CR.ContactConnecting -> {
chatModel.updateContact(r.contact)
chatModel.dismissConnReqView(r.contact.activeConn.id)
chatModel.removeChat(r.contact.activeConn.id)
}
is CR.ReceivedContactRequest -> {
@@ -1340,6 +1349,7 @@ sealed class CC {
class ApiUpdateProfile(val profile: Profile): CC()
class ApiParseMarkdown(val text: String): CC()
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
class CreateMyAddress: CC()
class DeleteMyAddress: CC()
class ShowMyAddress: CC()
@@ -1394,6 +1404,7 @@ sealed class CC {
is ApiUpdateProfile -> "/_profile ${json.encodeToString(profile)}"
is ApiParseMarkdown -> "/_parse $text"
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
is CreateMyAddress -> "/address"
is DeleteMyAddress -> "/delete_address"
is ShowMyAddress -> "/show_address"
@@ -1449,6 +1460,7 @@ sealed class CC {
is ApiUpdateProfile -> "updateProfile"
is ApiParseMarkdown -> "apiParseMarkdown"
is ApiSetContactAlias -> "apiSetContactAlias"
is ApiSetConnectionAlias -> "apiSetConnectionAlias"
is CreateMyAddress -> "createMyAddress"
is DeleteMyAddress -> "deleteMyAddress"
is ShowMyAddress -> "showMyAddress"
@@ -1637,6 +1649,7 @@ sealed class CR {
@Serializable @SerialName("userProfileNoChange") class UserProfileNoChange: CR()
@Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val fromProfile: Profile, val toProfile: Profile): CR()
@Serializable @SerialName("contactAliasUpdated") class ContactAliasUpdated(val toContact: Contact): CR()
@Serializable @SerialName("connectionAliasUpdated") class ConnectionAliasUpdated(val toConnection: PendingContactConnection): CR()
@Serializable @SerialName("apiParsedMarkdown") class ParsedMarkdown(val formattedText: List<FormattedText>? = null): CR()
@Serializable @SerialName("userContactLink") class UserContactLink(val connReqContact: String): CR()
@Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val connReqContact: String): CR()
@@ -1725,6 +1738,7 @@ sealed class CR {
is UserProfileNoChange -> "userProfileNoChange"
is UserProfileUpdated -> "userProfileUpdated"
is ContactAliasUpdated -> "contactAliasUpdated"
is ConnectionAliasUpdated -> "connectionAliasUpdated"
is ParsedMarkdown -> "apiParsedMarkdown"
is UserContactLink -> "userContactLink"
is UserContactLinkCreated -> "userContactLinkCreated"
@@ -1811,6 +1825,7 @@ sealed class CR {
is UserProfileNoChange -> noDetails()
is UserProfileUpdated -> json.encodeToString(toProfile)
is ContactAliasUpdated -> json.encodeToString(toContact)
is ConnectionAliasUpdated -> json.encodeToString(toConnection)
is ParsedMarkdown -> json.encodeToString(formattedText)
is UserContactLink -> connReqContact
is UserContactLinkCreated -> connReqContact

View File

@@ -208,21 +208,33 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) {
}
@Composable
private fun LocalAliasEditor(initialValue: String, updateValue: (String) -> Unit) {
fun LocalAliasEditor(
initialValue: String,
center: Boolean = true,
leadingIcon: Boolean = false,
focus: Boolean = false,
updateValue: (String) -> Unit
) {
var value by rememberSaveable { mutableStateOf(initialValue) }
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
val modifier = if (center)
Modifier.padding(horizontal = if (!leadingIcon) DEFAULT_PADDING else 0.dp).widthIn(min = 100.dp)
else
Modifier.padding(horizontal = if (!leadingIcon) DEFAULT_PADDING else 0.dp).fillMaxWidth()
Row(Modifier.fillMaxWidth(), horizontalArrangement = if (center) Arrangement.Center else Arrangement.Start) {
DefaultBasicTextField(
Modifier.padding(horizontal = 10.dp).widthIn(min = 100.dp),
modifier,
value,
{
Text(
generalGetString(R.string.text_field_set_contact_placeholder),
textAlign = TextAlign.Center,
textAlign = if (center) TextAlign.Center else TextAlign.Start,
color = HighOrLowlight
)
},
leadingIcon = if (leadingIcon) {{ Icon(Icons.Default.Edit, null, Modifier.padding(start = 7.dp)) }} else null,
color = HighOrLowlight,
textStyle = TextStyle.Default.copy(textAlign = if (value.isEmpty()) TextAlign.Start else TextAlign.Center),
focus = focus,
textStyle = TextStyle.Default.copy(textAlign = if (value.isEmpty() || !center) TextAlign.Start else TextAlign.Center),
keyboardActions = KeyboardActions(onDone = { updateValue(value) })
) {
value = it

View File

@@ -22,6 +22,7 @@ import chat.simplex.app.views.chat.group.deleteGroupDialog
import chat.simplex.app.views.chat.group.leaveGroupDialog
import chat.simplex.app.views.chat.item.ItemAction
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.ContactConnectionInfoView
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
@@ -63,7 +64,11 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
is ChatInfo.ContactConnection ->
ChatListNavLinkLayout(
chatLinkPreview = { ContactConnectionView(chat.chatInfo.contactConnection) },
click = { contactConnectionAlertDialog(chat.chatInfo.contactConnection, chatModel) },
click = {
ModalManager.shared.showModalCloseable(true) { close ->
ContactConnectionInfoView(chatModel, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close)
}
},
dropdownMenuItems = { ContactConnectionMenuItems(chat.chatInfo, chatModel, showMenu) },
showMenu,
stopped
@@ -264,11 +269,21 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo
@Composable
fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.set_contact_name),
Icons.Outlined.Edit,
onClick = {
ModalManager.shared.showModalCloseable(true) { close ->
ContactConnectionInfoView(chatModel, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close)
}
showMenu.value = false
},
)
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
onClick = {
deleteContactConnectionAlert(chatInfo.contactConnection, chatModel)
deleteContactConnectionAlert(chatInfo.contactConnection, chatModel) {}
showMenu.value = false
},
color = Color.Red
@@ -337,7 +352,7 @@ fun contactConnectionAlertDialog(connection: PendingContactConnection, chatModel
) {
TextButton(onClick = {
AlertManager.shared.hideAlert()
deleteContactConnectionAlert(connection, chatModel)
deleteContactConnectionAlert(connection, chatModel) {}
}) {
Text(stringResource(R.string.delete_verb))
}
@@ -350,7 +365,7 @@ fun contactConnectionAlertDialog(connection: PendingContactConnection, chatModel
)
}
fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel: ChatModel) {
fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel: ChatModel, onSuccess: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_pending_connection__question),
text = generalGetString(
@@ -363,6 +378,7 @@ fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel
AlertManager.shared.hideAlert()
if (chatModel.controller.apiDeleteChat(ChatType.ContactConnection, connection.apiId)) {
chatModel.removeChat(connection.id)
onSuccess()
}
}
}

View File

@@ -4,16 +4,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddLink
import androidx.compose.material.icons.outlined.Link
import androidx.compose.runtime.Composable
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.model.PendingContactConnection
import chat.simplex.app.model.getTimestampText
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.ProfileImage

View File

@@ -28,6 +28,7 @@ fun DefaultBasicTextField(
modifier: Modifier,
initialValue: String,
placeholder: (@Composable () -> Unit)? = null,
leadingIcon: (@Composable () -> Unit)? = null,
focus: Boolean = false,
color: Color = MaterialTheme.colors.onBackground,
textStyle: TextStyle = TextStyle.Default,
@@ -44,8 +45,8 @@ fun DefaultBasicTextField(
LaunchedEffect(Unit) {
if (!focus) return@LaunchedEffect
delay(300)
focusRequester.requestFocus()
delay(200)
keyboard?.show()
}
val enabled = true
@@ -100,6 +101,7 @@ fun DefaultBasicTextField(
placeholder = placeholder,
singleLine = true,
enabled = enabled,
leadingIcon = leadingIcon,
interactionSource = interactionSource,
contentPadding = TextFieldDefaults.textFieldWithLabelPadding(start = 0.dp, end = 0.dp),
visualTransformation = VisualTransformation.None,

View File

@@ -63,7 +63,7 @@ class ModalManager {
}
fun closeModals() {
while (modalViews.isNotEmpty()) closeModal()
while (modalCount.value > 0) closeModal()
}
@OptIn(ExperimentalAnimationApi::class)

View File

@@ -9,7 +9,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TheaterComedy
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -24,17 +24,25 @@ import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun AddContactView(chatModel: ChatModel, connReqInvitation: String) {
fun AddContactView(chatModel: ChatModel, connReqInvitation: String, connIncognito: Boolean) {
val cxt = LocalContext.current
LaunchedEffect(connReqInvitation) {
if (connReqInvitation.isNotEmpty()) {
chatModel.connReqInv.value = connReqInvitation
}
}
DisposableEffect(Unit) {
onDispose { chatModel.connReqInv.value = null }
}
AddContactLayout(
chatModelIncognito = chatModel.incognito.value,
connReq = connReqInvitation,
connIncognito = connIncognito,
share = { shareText(cxt, connReqInvitation) }
)
}
@Composable
fun AddContactLayout(chatModelIncognito: Boolean, connReq: String, share: () -> Unit) {
fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit) {
BoxWithConstraints {
val screenHeight = maxHeight
Column(
@@ -49,7 +57,7 @@ fun AddContactLayout(chatModelIncognito: Boolean, connReq: String, share: () ->
)
Row {
InfoAboutIncognito(
chatModelIncognito,
connIncognito,
true,
generalGetString(R.string.incognito_random_profile_description),
generalGetString(R.string.your_profile_will_be_sent)
@@ -132,8 +140,8 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean
fun PreviewAddContactView() {
SimpleXTheme {
AddContactLayout(
chatModelIncognito = false,
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
connIncognito = false,
share = {}
)
}

View File

@@ -0,0 +1,163 @@
package chat.simplex.app.views.newchat
import SectionDivider
import SectionView
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.LocalAliasEditor
import chat.simplex.app.views.chatlist.deleteContactConnectionAlert
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.usersettings.SettingsActionItem
@Composable
fun ContactConnectionInfoView(
chatModel: ChatModel,
connReqInvitation: String?,
contactConnection: PendingContactConnection,
focusAlias: Boolean,
close: () -> Unit
) {
/** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv]. It will be managed by [AddContactView] itself
* Otherwise it will be called here AFTER [AddContactView] is launched and will clear the value too soon */
val allowDispose = remember { mutableStateOf(true) }
LaunchedEffect(connReqInvitation) {
allowDispose.value = true
chatModel.connReqInv.value = connReqInvitation
}
DisposableEffect(Unit) {
onDispose {
if (allowDispose.value) {
chatModel.connReqInv.value = null
}
}
}
ContactConnectionInfoLayout(
connReq = connReqInvitation,
contactConnection.localAlias,
contactConnection.initiated,
contactConnection.viaContactUri,
contactConnection.incognito,
focusAlias,
deleteConnection = { deleteContactConnectionAlert(contactConnection, chatModel, close) },
onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) },
showQr = {
allowDispose.value = false
ModalManager.shared.showModal {
Column(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AddContactView(chatModel, connReqInvitation ?: return@showModal, contactConnection.incognito)
}
}
}
)
}
@Composable
private fun ContactConnectionInfoLayout(
connReq: String?,
localAlias: String,
connectionInitiated: Boolean,
connectionViaContactUri: Boolean,
connectionIncognito: Boolean,
focusAlias: Boolean,
deleteConnection: () -> Unit,
onLocalAliasChanged: (String) -> Unit,
showQr: () -> Unit,
) {
Column(
Modifier
.verticalScroll(rememberScrollState()),
) {
AppBarTitle(
stringResource(
if (connectionInitiated) R.string.you_invited_your_contact
else R.string.you_accepted_connection
)
)
Row(Modifier.padding(bottom = DEFAULT_PADDING)) {
LocalAliasEditor(localAlias, center = false, leadingIcon = true, focus = focusAlias, updateValue = onLocalAliasChanged)
}
Text(
stringResource(
if (connectionViaContactUri) R.string.you_will_be_connected_when_your_connection_request_is_accepted
else R.string.you_will_be_connected_when_your_contacts_device_is_online
),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING)
)
SectionView {
if (!connReq.isNullOrEmpty() && connectionInitiated) {
ShowQrButton(connectionIncognito, showQr)
SectionDivider()
}
DeleteButton(deleteConnection)
}
}
}
@Composable
fun ShowQrButton(incognito: Boolean, onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.QrCode,
stringResource(R.string.show_QR_code),
click = onClick,
textColor = if (incognito) Indigo else MaterialTheme.colors.primary,
iconColor = if (incognito) Indigo else MaterialTheme.colors.primary,
)
}
@Composable
fun DeleteButton(onClick: () -> Unit) {
SettingsActionItem(
Icons.Outlined.Delete,
stringResource(R.string.delete_verb),
click = onClick,
textColor = Color.Red,
iconColor = Color.Red,
)
}
private fun setContactAlias(contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withApi {
chatModel.controller.apiSetConnectionAlias(contactConnection.pccConnId, localAlias)?.let {
chatModel.updateContactConnection(it)
}
}
@Preview
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
private fun PreviewContactConnectionInfoView() {
SimpleXTheme {
ContactConnectionInfoLayout(
localAlias = "",
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
connectionInitiated = true,
connectionViaContactUri = true,
connectionIncognito = false,
focusAlias = false,
deleteConnection = {},
onLocalAliasChanged = {},
showQr = {},
)
}
}

View File

@@ -46,7 +46,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) {
Column(Modifier.weight(1f)) {
when (selection.value) {
CreateLinkTab.ONE_TIME -> {
AddContactView(m, connReqInvitation.value)
AddContactView(m, connReqInvitation.value, m.incognito.value)
}
CreateLinkTab.LONG_TERM -> {
UserAddressView(m)

View File

@@ -271,6 +271,7 @@
<string name="delete_contact_menu_action">Kontakt löschen</string>
<string name="delete_group_menu_action">Gruppe löschen</string>
<string name="mark_read">Als gelesen markieren</string>
<string name="set_contact_name">Set contact name</string>
<!-- Actions - ChatListNavLinkView.kt -->
<string name="mute_chat">Stummschalten</string>
@@ -307,6 +308,9 @@
<string name="icon_descr_email">E-Mail</string>
<string name="icon_descr_more_button">Mehr</string>
<!-- Connection info - ContactConnectionInfoView.kt -->
<string name="show_QR_code">Show QR code</string>
<!-- Add Contact - AddContactView.kt -->
<string name="invalid_QR_code">Ungültiger QR-Code</string>
<string name="this_QR_code_is_not_a_link">Dieser QR-Code beschreibt keinen Link!</string>

View File

@@ -271,6 +271,7 @@
<string name="delete_contact_menu_action">Удалить</string>
<string name="delete_group_menu_action">Удалить</string>
<string name="mark_read">Прочитано</string>
<string name="set_contact_name">Имя контакта</string>
<!-- Actions - ChatListNavLinkView.kt -->
<string name="mute_chat">Без звука</string>
@@ -307,6 +308,9 @@
<string name="icon_descr_email">Email</string>
<string name="icon_descr_more_button">Больше</string>
<!-- Connection info - ContactConnectionInfoView.kt -->
<string name="show_QR_code">Показать QR код</string>
<!-- Add Contact - AddContactView.kt -->
<string name="invalid_QR_code">Неверный QR код</string>
<string name="this_QR_code_is_not_a_link">Этот QR код не является ссылкой!</string>

View File

@@ -271,6 +271,7 @@
<string name="delete_contact_menu_action">Delete</string>
<string name="delete_group_menu_action">Delete</string>
<string name="mark_read">Mark read</string>
<string name="set_contact_name">Set contact name</string>
<!-- Actions - ChatListNavLinkView.kt -->
<string name="mute_chat">Mute</string>
@@ -307,6 +308,9 @@
<string name="icon_descr_email">Email</string>
<string name="icon_descr_more_button">More</string>
<!-- Connection info - ContactConnectionInfoView.kt -->
<string name="show_QR_code">Show QR code</string>
<!-- Add Contact - AddContactView.kt -->
<string name="invalid_QR_code">Invalid QR code</string>
<string name="this_QR_code_is_not_a_link">This QR code is not a link!</string>
@@ -315,7 +319,7 @@
<string name="connection_request_sent">Connection request sent!</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">You will be connected when your connection request is accepted, please wait or check later!</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">You will be connected when your contact\'s device is online, please wait or check later!</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Your contact can scan it from the app.</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Your contact can scan QR code from the app.</string>
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">If you can\'t meet in person, <b>show QR code in the video call</b>, or share the link.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Your chat profile will be sent\nto your contact</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">If you cannot meet in person, you can <b>scan QR code in the video call</b>, or your contact can share an invitation link.</string>