Paste link to connect (#551)

* initial implementation of textbox

* paste to connect box implemented (and tested) in android

* first pass at pastebox in iOS

* clean up iOS implementation

* put paste link page in for groups in android

* initial inclusion in iOS UI

* refactor naming

* lint kotlin

* fix typo

* ios: update "connect via link" ui, refactor connecting via link to use the one function

* android: update paste link UI

* add russian translations

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
IanRDavies
2022-04-25 07:54:07 +01:00
committed by GitHub
parent 48ba6472b6
commit e87660974e
22 changed files with 561 additions and 247 deletions

View File

@@ -1,24 +1,13 @@
package chat.simplex.app.views.chat
import ComposeImageView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddCircleOutline
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.ComposeLinkView
import chat.simplex.app.views.helpers.generalGetString
// TODO ComposeState

View File

@@ -7,9 +7,9 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.CIFile
import chat.simplex.app.views.helpers.*
import chat.simplex.app.R
@Composable
fun CIImageView(

View File

@@ -1,7 +1,7 @@
package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons

View File

@@ -3,6 +3,7 @@ package chat.simplex.app.ui.theme
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
@@ -18,14 +19,19 @@ import androidx.compose.ui.unit.dp
fun SimpleButton(text: String, icon: ImageVector,
color: Color = MaterialTheme.colors.primary,
click: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { click() }
) {
Icon(icon, text, tint = color,
modifier = Modifier.padding(horizontal = 10.dp)
)
Text(text, style = MaterialTheme.typography.caption, color = color)
Surface(shape = RoundedCornerShape(20.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable { click() }
.padding(8.dp)
) {
Icon(
icon, text, tint = color,
modifier = Modifier.padding(end = 8.dp)
)
Text(text, style = MaterialTheme.typography.caption, color = color)
}
}
}

View File

@@ -0,0 +1,58 @@
package chat.simplex.app.views.helpers
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.ui.theme.HighOrLowlight
@Composable
fun TextEditor(modifier: Modifier, text: MutableState<String>) {
BasicTextField(
value = text.value,
onValueChange = { text.value = it },
textStyle = TextStyle(
fontFamily = FontFamily.Monospace, fontSize = 14.sp,
color = MaterialTheme.colors.onBackground
),
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
modifier = modifier,
cursorBrush = SolidColor(HighOrLowlight),
decorationBox = { innerTextField ->
Surface(
shape = RoundedCornerShape(10.dp),
border = BorderStroke(1.dp, MaterialTheme.colors.secondary)
) {
Row(
Modifier.background(MaterialTheme.colors.background),
verticalAlignment = Alignment.Top
) {
Box(
Modifier
.weight(1f)
.padding(vertical = 5.dp, horizontal = 7.dp)
) {
innerTextField()
}
}
}
}
)
}

View File

@@ -63,7 +63,7 @@ fun AddContactLayout(connReq: String, share: () -> Unit) {
lineHeight = 22.sp,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = if(screenHeight > 600.dp) 16.dp else 8.dp)
.padding(bottom = if (screenHeight > 600.dp) 16.dp else 8.dp)
)
SimpleButton(generalGetString(R.string.share_invitation_link), icon = Icons.Outlined.Share, click = share)
Spacer(Modifier.height(10.dp))

View File

@@ -3,6 +3,7 @@ package chat.simplex.app.views.newchat
import android.Manifest
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
@@ -39,52 +40,65 @@ fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController) {
},
scanCode = {
newChatCtrl.collapse()
ModalManager.shared.showCustomModal { close -> ConnectContactView(chatModel, close) }
ModalManager.shared.showCustomModal { close -> ScanToConnectView(chatModel, close) }
cameraPermissionState.launchPermissionRequest()
},
pasteLink = {
newChatCtrl.collapse()
ModalManager.shared.showCustomModal { close -> PasteToConnectView(chatModel, close) }
}
)
}
@Composable
fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 48.dp),
horizontalArrangement = Arrangement.SpaceEvenly
fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit, pasteLink: () -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
Text(
generalGetString(R.string.add_contact_to_start_new_chat),
modifier = Modifier.padding(horizontal = 8.dp).padding(top = 32.dp)
)
Row(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.add_contact),
generalGetString(R.string.create_QR_code_or_link__bracketed__multiline),
Icons.Outlined.PersonAdd,
click = addContact
)
}
Box(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.scan_QR_code),
generalGetString(R.string.in_person_or_in_video_call__bracketed),
Icons.Outlined.QrCode,
click = scanCode
)
}
Box(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.create_group),
generalGetString(R.string.coming_soon__bracketed),
Icons.Outlined.GroupAdd,
disabled = true
)
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(top = 24.dp, bottom = 40.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Box(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.create_one_time_link),
generalGetString(R.string.to_share_with_your_contact),
Icons.Outlined.PersonAdd,
click = addContact
)
}
Box(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.paste_received_link),
generalGetString(R.string.paste_received_link_from_clipboard),
Icons.Outlined.Link,
click = pasteLink
)
}
Box(
Modifier
.weight(1F)
.fillMaxWidth()) {
ActionButton(
generalGetString(R.string.scan_QR_code),
generalGetString(R.string.in_person_or_in_video_call__bracketed),
Icons.Outlined.QrCode,
click = scanCode
)
}
}
}
}
@@ -92,33 +106,35 @@ fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) {
@Composable
fun ActionButton(text: String?, comment: String?, icon: ImageVector, disabled: Boolean = false,
click: () -> Unit = {}) {
Column(
Modifier
.clickable(onClick = click)
.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
Icon(icon, text,
tint = tint,
modifier = Modifier
.size(40.dp)
.padding(bottom = 8.dp))
if (text != null) {
Text(
text,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = tint,
modifier = Modifier.padding(bottom = 4.dp)
)
}
if (comment != null) {
Text(
comment,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.body2
)
Surface(shape = RoundedCornerShape(18.dp)) {
Column(
Modifier
.clickable(onClick = click)
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
Icon(icon, text,
tint = tint,
modifier = Modifier
.size(40.dp)
.padding(bottom = 8.dp))
if (text != null) {
Text(
text,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = tint,
modifier = Modifier.padding(bottom = 4.dp)
)
}
if (comment != null) {
Text(
comment,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.body2
)
}
}
}
}
@@ -129,7 +145,8 @@ fun PreviewNewChatSheet() {
SimpleXTheme {
NewChatSheetLayout(
addContact = {},
scanCode = {}
scanCode = {},
pasteLink = {}
)
}
}

View File

@@ -0,0 +1,126 @@
package chat.simplex.app.views.newchat
import android.content.ClipboardManager
import android.content.res.Configuration
import android.net.Uri
import androidx.activity.compose.BackHandler
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.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
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.model.ChatModel
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@Composable
fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) {
val connectionLink = remember { mutableStateOf("")}
val context = LocalContext.current
val clipboard = getSystemService(context, ClipboardManager::class.java)
BackHandler(onBack = close)
PasteToConnectLayout(
connectionLink = connectionLink,
pasteFromClipboard = {
connectionLink.value = clipboard?.primaryClip?.getItemAt(0)?.coerceToText(context) as String
},
connectViaLink = { connReqUri ->
try {
val uri = Uri.parse(connReqUri)
withUriAction(uri) { action ->
connectViaUri(chatModel, action, uri)
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.invalid_connection_link),
text = generalGetString(R.string.this_string_is_not_a_connection_link)
)
}
close()
},
close = close
)
}
@Composable
fun PasteToConnectLayout(
connectionLink: MutableState<String>,
pasteFromClipboard: () -> Unit,
connectViaLink: (String) -> Unit,
close: () -> Unit
) {
ModalView(close) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween,
) {
Text(
generalGetString(R.string.connect_via_link),
style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
modifier = Modifier.padding(bottom = 16.dp)
)
Text(generalGetString(R.string.paste_connection_link_below_to_connect))
Text(generalGetString(R.string.profile_will_be_sent_to_contact_sending_link))
Box(Modifier.padding(top = 16.dp, bottom = 6.dp)) {
TextEditor(Modifier.height(180.dp), text = connectionLink)
}
Row(
Modifier.fillMaxWidth().padding(bottom = 6.dp),
horizontalArrangement = Arrangement.Start,
) {
if (connectionLink.value == "") {
SimpleButton(text = "Paste", icon = Icons.Outlined.ContentPaste) {
pasteFromClipboard()
}
} else {
SimpleButton(text = "Clear", icon = Icons.Outlined.Clear) {
connectionLink.value = ""
}
}
Spacer(Modifier.weight(1f).fillMaxWidth())
SimpleButton(text = "Connect", icon = Icons.Outlined.Link) {
connectViaLink(connectionLink.value)
}
}
Text(annotatedStringResource(R.string.you_can_also_connect_by_clicking_the_link))
}
}
}
@Preview(showBackground = true)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)
@Composable
fun PreviewPasteToConnectTextbox() {
SimpleXTheme {
PasteToConnectLayout(
connectionLink = remember { mutableStateOf("") },
pasteFromClipboard = {},
connectViaLink = { link ->
try {
println(link)
// withApi { chatModel.controller.apiConnect(link) }
} catch (e: Exception) {
e.printStackTrace()
}
},
close = {}
)
}
}

View File

@@ -19,7 +19,7 @@ import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@Composable
fun ConnectContactView(chatModel: ChatModel, close: () -> Unit) {
fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) {
BackHandler(onBack = close)
ConnectContactLayout(
qrCodeScanner = {

View File

@@ -3,8 +3,6 @@ package chat.simplex.app.views.usersettings
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@@ -12,11 +10,9 @@ import androidx.compose.material.icons.outlined.OpenInNew
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -32,7 +28,7 @@ fun SMPServersView(chatModel: ChatModel) {
if (userSMPServers != null) {
var isUserSMPServers by remember { mutableStateOf(userSMPServers.isNotEmpty()) }
var editSMPServers by remember { mutableStateOf(!isUserSMPServers) }
var userSMPServersStr by remember { mutableStateOf(if (isUserSMPServers) userSMPServers.joinToString(separator = "\n") else "") }
var userSMPServersStr = remember { mutableStateOf(if (isUserSMPServers) userSMPServers.joinToString(separator = "\n") else "") }
fun saveSMPServers(smpServers: List<String>) {
withApi {
val r = chatModel.controller.setUserSMPServers(smpServers = smpServers)
@@ -66,23 +62,22 @@ fun SMPServersView(chatModel: ChatModel) {
onConfirm = {
saveSMPServers(listOf())
isUserSMPServers = false
userSMPServersStr = ""
userSMPServersStr.value = ""
}
)
} else {
isUserSMPServers = false
userSMPServersStr = ""
userSMPServersStr.value = ""
}
}
}
},
editUserSMPServersStr = { userSMPServersStr = it },
cancelEdit = {
val userSMPServers = chatModel.userSMPServers.value
if (userSMPServers != null) {
isUserSMPServers = userSMPServers.isNotEmpty()
editSMPServers = !isUserSMPServers
userSMPServersStr = if (isUserSMPServers) userSMPServers.joinToString(separator = "\n") else ""
userSMPServersStr.value = if (isUserSMPServers) userSMPServers.joinToString(separator = "\n") else ""
}
},
saveSMPServers = { saveSMPServers(it) },
@@ -95,9 +90,8 @@ fun SMPServersView(chatModel: ChatModel) {
fun SMPServersLayout(
isUserSMPServers: Boolean,
editSMPServers: Boolean,
userSMPServersStr: String,
userSMPServersStr: MutableState<String>,
isUserSMPServersOnOff: (Boolean) -> Unit,
editUserSMPServersStr: (String) -> Unit,
cancelEdit: () -> Unit,
saveSMPServers: (List<String>) -> Unit,
editOn: () -> Unit,
@@ -131,39 +125,7 @@ fun SMPServersLayout(
} else {
Text(generalGetString(R.string.enter_one_SMP_server_per_line))
if (editSMPServers) {
BasicTextField(
value = userSMPServersStr,
onValueChange = editUserSMPServersStr,
textStyle = TextStyle(
fontFamily = FontFamily.Monospace, fontSize = 14.sp,
color = MaterialTheme.colors.onBackground
),
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.None,
autoCorrect = false
),
modifier = Modifier.height(160.dp),
cursorBrush = SolidColor(HighOrLowlight),
decorationBox = { innerTextField ->
Surface(
shape = RoundedCornerShape(10.dp),
border = BorderStroke(1.dp, MaterialTheme.colors.secondary)
) {
Row(
Modifier.background(MaterialTheme.colors.background),
verticalAlignment = Alignment.Top
) {
Box(
Modifier
.weight(1f)
.padding(vertical = 5.dp, horizontal = 7.dp)
) {
innerTextField()
}
}
}
}
)
TextEditor(Modifier.height(160.dp), text = userSMPServersStr)
Row(
Modifier.fillMaxWidth(),
@@ -183,7 +145,7 @@ fun SMPServersLayout(
generalGetString(R.string.save_servers_button),
color = MaterialTheme.colors.primary,
modifier = Modifier.clickable(onClick = {
val servers = userSMPServersStr.split("\n")
val servers = userSMPServersStr.value.split("\n")
saveSMPServers(servers)
})
)
@@ -205,7 +167,7 @@ fun SMPServersLayout(
Modifier.verticalScroll(rememberScrollState())
) {
Text(
userSMPServersStr,
userSMPServersStr.value,
Modifier
.padding(vertical = 5.dp, horizontal = 7.dp),
style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 14.sp),
@@ -256,9 +218,8 @@ fun PreviewSMPServersLayoutDefaultServers() {
SMPServersLayout(
isUserSMPServers = false,
editSMPServers = true,
userSMPServersStr = "",
userSMPServersStr = remember { mutableStateOf("") },
isUserSMPServersOnOff = {},
editUserSMPServersStr = {},
cancelEdit = {},
saveSMPServers = {},
editOn = {},
@@ -273,9 +234,8 @@ fun PreviewSMPServersLayoutUserServersEditOn() {
SMPServersLayout(
isUserSMPServers = true,
editSMPServers = true,
userSMPServersStr = "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im\nsmp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im",
userSMPServersStr = remember { mutableStateOf("smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im\nsmp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im") },
isUserSMPServersOnOff = {},
editUserSMPServersStr = {},
cancelEdit = {},
saveSMPServers = {},
editOn = {},
@@ -290,9 +250,8 @@ fun PreviewSMPServersLayoutUserServersEditOff() {
SMPServersLayout(
isUserSMPServers = true,
editSMPServers = false,
userSMPServersStr = "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im\nsmp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im",
userSMPServersStr = remember { mutableStateOf("smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im\nsmp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im") },
isUserSMPServersOnOff = {},
editUserSMPServersStr = {},
cancelEdit = {},
saveSMPServers = {},
editOn = {},

View File

@@ -98,13 +98,14 @@
<string name="ok"></string>
<string name="no_details">нет описания</string>
<string name="add_contact">Добавить контакт</string>
<string name="scan_QR_code">Сканировать QR код</string>
<!-- NewChatSheet -->
<string name="create_QR_code_or_link__bracketed__multiline">(создать QR код или ссылку)</string>
<string name="create_one_time_link">Создать одноразовую ссылку</string>
<string name="paste_received_link">Вставить полученную ссылку</string>
<string name="scan_QR_code">Сканировать QR код</string>
<string name="to_share_with_your_contact">(чтобы отправить вашему контакту)</string>
<string name="in_person_or_in_video_call__bracketed">(при встрече или через видеозвонок)</string>
<string name="create_group">Создать группу</string>
<string name="coming_soon__bracketed">(скоро!)</string>
<string name="paste_received_link_from_clipboard">(вставить ссылку из буфера обмена)</string>
<!-- GetImageView -->
<string name="toast_camera_permission_denied">Разрешение не получено!</string>
@@ -166,6 +167,8 @@
<string name="your_chat_profile_will_be_sent_to_your_contact">Ваш профиль будет отправлен\nвашему контакту</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">Если вы не можете встретиться лично, вы можете <b>сосканировать QR код во время видеозвонка</b>, или ваш контакт может отправить вам ссылку.</string>
<string name="share_invitation_link">Поделиться ссылкой</string>
<string name="paste_connection_link_below_to_connect">Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта..</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Настройки</string>
@@ -227,5 +230,9 @@
<string name="a_plus_b">a + b</string>
<string name="colored">цвет</string>
<string name="secret">секрет</string>
<string name="connect_via_link">Соединиться через ссылку</string>
<string name="this_string_is_not_a_connection_link">Эта строка не является ссылкой-приглашением!</string>
<string name="you_can_also_connect_by_clicking_the_link">Вы также можете соединиться, открыв ссылку там, где вы её получили. Если ссылка откроется в браузере, нажмите кнопку <b>Open in mobile app</b>.</string>
<string name="add_contact_to_start_new_chat">Добавьте контакт, чтобы начать разговор:</string>
</resources>

View File

@@ -98,13 +98,15 @@
<string name="ok">Ok</string>
<string name="no_details">no details</string>
<string name="add_contact">Add contact</string>
<string name="scan_QR_code">Scan QR code</string>
<!-- NewChatSheet -->
<string name="create_QR_code_or_link__bracketed__multiline">(create QR code\nor link)</string>
<string name="add_contact_to_start_new_chat">Add contact to start a new chat:</string>
<string name="create_one_time_link">Create link / QR code</string>
<string name="paste_received_link">Connect via received link</string>
<string name="scan_QR_code">Scan QR code</string>
<string name="to_share_with_your_contact">(to share with your contact)</string>
<string name="in_person_or_in_video_call__bracketed">(in person or in video call)</string>
<string name="create_group">Create Group</string>
<string name="coming_soon__bracketed">(coming soon!)</string>
<string name="paste_received_link_from_clipboard">(paste link from clipboard)</string>
<!-- GetImageView -->
<string name="toast_camera_permission_denied">Permission Denied!</string>
@@ -166,6 +168,12 @@
<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>
<string name="share_invitation_link">Share invitation link</string>
<string name="paste_connection_link_below_to_connect">Paste the link you received into the box below to connect with your contact.</string>
<!-- PasteToConnect.kt -->
<string name="connect_via_link">Connect via link</string>
<string name="this_string_is_not_a_connection_link">This string is not a connection link!</string>
<string name="you_can_also_connect_by_clicking_the_link">You can also connect by clicking the link. If it opens in the browser, click <b>Open in mobile app</b> button.</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Your settings</string>

View File

@@ -496,42 +496,42 @@ func apiAddContact() throws -> String {
throw r
}
func apiConnect(connReq: String) async throws -> Bool {
func apiConnect(connReq: String) async throws -> ConnReqType? {
let r = await chatSendCmd(.connect(connReq: connReq))
let am = AlertManager.shared
switch r {
case .sentConfirmation: return true
case .sentInvitation: return true
case .sentConfirmation: return .invitation
case .sentInvitation: return .contact
case let .contactAlreadyExists(contact):
am.showAlertMsg(
title: "Contact already exists",
message: "You are already connected to \(contact.displayName) via this link."
)
return false
return nil
case .chatCmdError(.error(.invalidConnReq)):
am.showAlertMsg(
title: "Invalid connection link",
message: "Please check that you used the correct link or ask your contact to send you another one."
)
return false
return nil
case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))):
am.showAlertMsg(
title: "Connection timeout",
message: "Please check your network connection and try again."
)
return false
return nil
case .chatCmdError(.errorAgent(.BROKER(.NETWORK))):
am.showAlertMsg(
title: "Connection error",
message: "Please check your network connection and try again."
)
return false
return nil
case .chatCmdError(.errorAgent(.SMP(.AUTH))):
am.showAlertMsg(
title: "Connection error (AUTH)",
message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection."
)
return false
return nil
default: throw r
}
}

View File

@@ -78,23 +78,12 @@ struct ChatListView: View {
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
let title: LocalizedStringKey
if case .contact = action { title = "Connect via contact link?" }
else { title = "Connect via invitation link?" }
else { title = "Connect via one-time link?" }
return Alert(
title: Text(title),
message: Text("Your profile will be sent to the contact that you received this link from"),
primaryButton: .default(Text("Connect")) {
DispatchQueue.main.async {
Task {
do {
let ok = try await apiConnect(connReq: link)
if ok { connectionReqSentAlert(action) }
} catch {
let err = error.localizedDescription
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(err)")
logger.debug("ChatListView.connectViaUrlAlert: apiConnect error: \(err)")
}
}
}
connectViaLink(link)
},
secondaryButton: .cancel()
)

View File

@@ -11,7 +11,6 @@ import CoreImage.CIFilterBuiltins
struct AddContactView: View {
var connReqInvitation: String
var body: some View {
VStack {
Text("Add contact")

View File

@@ -12,26 +12,27 @@ struct NewChatButton: View {
@State private var showAddChat = false
@State private var addContact = false
@State private var connReqInvitation: String = ""
@State private var connectContact = false
@State private var createGroup = false
@State private var scanToConnect = false
@State private var pasteToConnect = false
var body: some View {
Button { showAddChat = true } label: {
Image(systemName: "person.crop.circle.badge.plus")
}
.confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Add contact") { addContactAction() }
Button("Scan QR code") { connectContact = true }
Button("Create group") { createGroup = true }
.disabled(true)
.confirmationDialog("Add contact to start a new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Create link / QR code") { addContactAction() }
Button("Paste received link") { pasteToConnect = true }
Button("Scan QR code") { scanToConnect = true }
}
.sheet(isPresented: $addContact, content: {
AddContactView(connReqInvitation: connReqInvitation)
})
.sheet(isPresented: $connectContact, content: {
connectContactSheet()
.sheet(isPresented: $scanToConnect, content: {
ScanToConnectView(openedSheet: $scanToConnect)
})
.sheet(isPresented: $pasteToConnect, content: {
PasteToConnectView(openedSheet: $pasteToConnect)
})
.sheet(isPresented: $createGroup, content: { CreateGroupView() })
}
func addContactAction() {
@@ -45,28 +46,6 @@ struct NewChatButton: View {
logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)")
}
}
func addContactSheet() -> some View {
AddContactView(connReqInvitation: connReqInvitation)
}
func connectContactSheet() -> some View {
ConnectContactView(completed: { err in
connectContact = false
DispatchQueue.global().async {
switch (err) {
case let .success(ok):
if ok { connectionReqSentAlert(.invitation) }
case let .failure(error):
connectionErrorAlert(error)
}
}
})
}
func connectionErrorAlert(_ error: Error) {
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(error.localizedDescription)")
}
}
enum ConnReqType: Equatable {
@@ -74,6 +53,30 @@ enum ConnReqType: Equatable {
case invitation
}
func connectViaLink(_ connectionLink: String, _ openedSheet: Binding<Bool>? = nil) {
Task {
do {
let res = try await apiConnect(connReq: connectionLink)
DispatchQueue.main.async {
openedSheet?.wrappedValue = false
if let connReqType = res {
connectionReqSentAlert(connReqType)
}
}
} catch {
logger.error("connectViaLink apiConnect error: \(responseError(error))")
DispatchQueue.main.async {
openedSheet?.wrappedValue = false
connectionErrorAlert(error)
}
}
}
}
func connectionErrorAlert(_ error: Error) {
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(error.localizedDescription)")
}
func connectionReqSentAlert(_ type: ConnReqType) {
AlertManager.shared.showAlertMsg(
title: "Connection request sent!",

View File

@@ -0,0 +1,78 @@
//
// PasteToConnectView.swift
// SimpleX (iOS)
//
// Created by Ian Davies on 22/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct PasteToConnectView: View {
@Binding var openedSheet: Bool
@State private var connectionLink: String = ""
var body: some View {
VStack(alignment: .leading) {
Text("Connect via link")
.font(.title)
.padding([.bottom])
.frame(maxWidth: .infinity, alignment: .center)
Text("Paste the link you received into the box below to connect with your contact.")
.multilineTextAlignment(.leading)
Text("Your profile will be sent to the contact that you received this link from")
.multilineTextAlignment(.leading)
.padding(.bottom)
TextEditor(text: $connectionLink)
.onSubmit(connect)
.font(.body)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.allowsTightening(false)
.frame(height: 180)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
)
HStack(spacing: 20) {
if connectionLink == "" {
Button {
connectionLink = UIPasteboard.general.string ?? ""
} label: {
Label("Paste", systemImage: "doc.on.clipboard")
}
} else {
Button {
connectionLink = ""
} label: {
Label("Clear", systemImage: "multiply")
}
}
Spacer()
Button(action: connect, label: {
Label("Connect", systemImage: "link")
})
.disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil)
}
.frame(height: 48)
.padding(.bottom)
Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button")
.multilineTextAlignment(.leading)
}
.padding()
}
private func connect() {
connectViaLink(connectionLink.trimmingCharacters(in: .whitespaces), $openedSheet)
}
}
struct PasteToConnectView_Previews: PreviewProvider {
static var previews: some View {
@State var openedSheet: Bool = true
return PasteToConnectView(openedSheet: $openedSheet)
}
}

View File

@@ -9,8 +9,8 @@
import SwiftUI
import CodeScanner
struct ConnectContactView: View {
var completed: ((Result<Bool, Error>) -> Void)
struct ScanToConnectView: View {
@Binding var openedSheet: Bool
var body: some View {
VStack {
@@ -37,24 +37,17 @@ struct ConnectContactView: View {
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
switch resp {
case let .success(r):
Task {
do {
let ok = try await apiConnect(connReq: r.string)
completed(.success(ok))
} catch {
logger.error("ConnectContactView.processQRCode apiConnect error: \(error.localizedDescription)")
completed(.failure(error))
}
}
Task { connectViaLink(r.string, $openedSheet) }
case let .failure(e):
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
completed(.failure(e))
openedSheet = false
}
}
}
struct ConnectContactView_Previews: PreviewProvider {
static var previews: some View {
return ConnectContactView(completed: {_ in })
@State var openedSheet: Bool = true
return ScanToConnectView(openedSheet: $openedSheet)
}
}

View File

@@ -115,6 +115,11 @@
<target>Add contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add contact to start a new chat" xml:space="preserve">
<source>Add contact to start a new chat</source>
<target>Add contact to start a new chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected" xml:space="preserve">
<source>All your contacts will remain connected</source>
<target>All your contacts will remain connected</target>
@@ -160,6 +165,11 @@
<target>Choose from library</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Clear" xml:space="preserve">
<source>Clear</source>
<target>Clear</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Configure SMP servers" xml:space="preserve">
<source>Configure SMP servers</source>
<target>Configure SMP servers</target>
@@ -180,9 +190,14 @@
<target>Connect via contact link?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via invitation link?" xml:space="preserve">
<source>Connect via invitation link?</source>
<target>Connect via invitation link?</target>
<trans-unit id="Connect via link" xml:space="preserve">
<source>Connect via link</source>
<target>Connect via link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via one-time link?" xml:space="preserve">
<source>Connect via one-time link?</source>
<target>Connect via one-time link?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -260,9 +275,9 @@
<target>Create address</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<target>Create group</target>
<trans-unit id="Create link / QR code" xml:space="preserve">
<source>Create link / QR code</source>
<target>Create link / QR code</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
@@ -470,6 +485,21 @@
<target>Open Settings</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste" xml:space="preserve">
<source>Paste</source>
<target>Paste</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste received link" xml:space="preserve">
<source>Paste received link</source>
<target>Paste received link</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste the link you received into the box below to connect with your contact." xml:space="preserve">
<source>Paste the link you received into the box below to connect with your contact.</source>
<target>Paste the link you received into the box below to connect with your contact.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please check that you used the correct link or ask your contact to send you another one." xml:space="preserve">
<source>Please check that you used the correct link or ask your contact to send you another one.</source>
<target>Please check that you used the correct link or ask your contact to send you another one.</target>
@@ -567,11 +597,6 @@ to scan from the app</source>
to scan from the app</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Start new chat" xml:space="preserve">
<source>Start new chat</source>
<target>Start new chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Take picture" xml:space="preserve">
<source>Take picture</source>
<target>Take picture</target>
@@ -676,6 +701,11 @@ To connect, please ask your contact to create another connection link and check
<target>You are connected to the server used to receive messages from this contact.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button" xml:space="preserve">
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button</source>
<target>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can now send messages to %@" xml:space="preserve">
<source>You can now send messages to %@</source>
<target>You can now send messages to %@</target>

View File

@@ -115,6 +115,11 @@
<target>Добавить контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Add contact to start a new chat" xml:space="preserve">
<source>Add contact to start a new chat</source>
<target>Добавьте контакт, чтобы начать разговор</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="All your contacts will remain connected" xml:space="preserve">
<source>All your contacts will remain connected</source>
<target>Все контакты, которые соединились через этот адрес, сохранятся.</target>
@@ -160,6 +165,11 @@
<target>Выбрать из библиотеки</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Clear" xml:space="preserve">
<source>Clear</source>
<target>Очистить</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Configure SMP servers" xml:space="preserve">
<source>Configure SMP servers</source>
<target>Настройка SMP серверов</target>
@@ -180,9 +190,14 @@
<target>Соединиться через ссылку-контакт?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via invitation link?" xml:space="preserve">
<source>Connect via invitation link?</source>
<target>Соединиться через ссылку-приглашение?</target>
<trans-unit id="Connect via link" xml:space="preserve">
<source>Connect via link</source>
<target>Соединиться через ссылку</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connect via one-time link?" xml:space="preserve">
<source>Connect via one-time link?</source>
<target>Соединиться через одноразовую ссылку?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve">
@@ -260,9 +275,9 @@
<target>Создать адрес</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create group" xml:space="preserve">
<source>Create group</source>
<target>Создать группу</target>
<trans-unit id="Create link / QR code" xml:space="preserve">
<source>Create link / QR code</source>
<target>Создать ссылку / QR код</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Create profile" xml:space="preserve">
@@ -470,6 +485,21 @@
<target>Открыть Настройки</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste" xml:space="preserve">
<source>Paste</source>
<target>Вставить</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste received link" xml:space="preserve">
<source>Paste received link</source>
<target>Вставить полученную ссылку</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Paste the link you received into the box below to connect with your contact." xml:space="preserve">
<source>Paste the link you received into the box below to connect with your contact.</source>
<target>Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Please check that you used the correct link or ask your contact to send you another one." xml:space="preserve">
<source>Please check that you used the correct link or ask your contact to send you another one.</source>
<target>Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку.</target>
@@ -566,11 +596,6 @@ to scan from the app</source>
<target>Покажите QR код вашему контакту для сканирования в приложении</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Start new chat" xml:space="preserve">
<source>Start new chat</source>
<target>Начать новый разговор</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Take picture" xml:space="preserve">
<source>Take picture</source>
<target>Сделать фото</target>
@@ -675,6 +700,11 @@ To connect, please ask your contact to create another connection link and check
<target>Установлено соединение с сервером, через который вы получаете сообщения от этого контакта.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button" xml:space="preserve">
<source>You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button</source>
<target>Вы также можете соединиться, открыв ссылку там, где вы её получили. Если ссылка откроется в браузере, нажмите кнопку **Open in mobile app**.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You can now send messages to %@" xml:space="preserve">
<source>You can now send messages to %@</source>
<target>Вы теперь можете отправлять сообщения %@</target>

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */; };
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; };
@@ -59,7 +60,7 @@
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */; };
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; };
5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; };
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
@@ -84,6 +85,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteToConnectView.swift; sourceTree = "<group>"; };
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = "<group>"; };
@@ -139,7 +141,7 @@
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContactView.swift; sourceTree = "<group>"; };
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = "<group>"; };
5CE4407127ADB1D0007B033A /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; };
@@ -311,7 +313,8 @@
children = (
5C6AD81227A834E300348BD7 /* NewChatButton.swift */,
5CCD403327A5F6DF00368C90 /* AddContactView.swift */,
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */,
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */,
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */,
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */,
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */,
);
@@ -491,6 +494,7 @@
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */,
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */,
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */,
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */,
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
@@ -511,7 +515,7 @@
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */,
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */,
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,

View File

@@ -85,6 +85,9 @@
/* No comment provided by engineer. */
"Add contact" = "Добавить контакт";
/* No comment provided by engineer. */
"Add contact to start a new chat" = "Добавьте контакт, чтобы начать разговор";
/* No comment provided by engineer. */
"All your contacts will remain connected" = "Все контакты, которые соединились через этот адрес, сохранятся.";
@@ -115,6 +118,9 @@
/* No comment provided by engineer. */
"Choose from library" = "Выбрать из библиотеки";
/* No comment provided by engineer. */
"Clear" = "Очистить";
/* No comment provided by engineer. */
"colored" = "цвет";
@@ -134,7 +140,10 @@
"Connect via contact link?" = "Соединиться через ссылку-контакт?";
/* No comment provided by engineer. */
"Connect via invitation link?" = "Соединиться через ссылку-приглашение?";
"Connect via link" = "Соединиться через ссылку";
/* No comment provided by engineer. */
"Connect via one-time link?" = "Соединиться через одноразовую ссылку?";
/* No comment provided by engineer. */
"Connecting server…" = "Устанавливается соединение с сервером…";
@@ -182,7 +191,7 @@
"Create address" = "Создать адрес";
/* No comment provided by engineer. */
"Create group" = "Создать группу";
"Create link / QR code" = "Создать ссылку / QR код";
/* No comment provided by engineer. */
"Create profile" = "Создать профиль";
@@ -313,6 +322,15 @@
/* No comment provided by engineer. */
"Open Settings" = "Открыть Настройки";
/* No comment provided by engineer. */
"Paste" = "Вставить";
/* No comment provided by engineer. */
"Paste received link" = "Вставить полученную ссылку";
/* No comment provided by engineer. */
"Paste the link you received into the box below to connect with your contact." = "Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта.";
/* No comment provided by engineer. */
"Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку.";
@@ -373,9 +391,6 @@
/* No comment provided by engineer. */
"SMP servers" = "SMP серверы";
/* No comment provided by engineer. */
"Start new chat" = "Начать новый разговор";
/* No comment provided by engineer. */
"strike" = "зачеркнуть";
@@ -445,6 +460,9 @@
/* No comment provided by engineer. */
"You are connected to the server used to receive messages from this contact." = "Установлено соединение с сервером, через который вы получаете сообщения от этого контакта.";
/* No comment provided by engineer. */
"You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button" = "Вы также можете соединиться, открыв ссылку там, где вы её получили. Если ссылка откроется в браузере, нажмите кнопку **Open in mobile app**.";
/* notification body */
"You can now send messages to %@" = "Вы теперь можете отправлять сообщения %@";