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 package chat.simplex.app.views.chat
import ComposeImageView import ComposeImageView
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* 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.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.* import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.ComposeLinkView import chat.simplex.app.views.helpers.ComposeLinkView
import chat.simplex.app.views.helpers.generalGetString
// TODO ComposeState // TODO ComposeState

View File

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

View File

@@ -1,7 +1,7 @@
package chat.simplex.app.views.chatlist package chat.simplex.app.views.chatlist
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.* import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons 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.clickable
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Share
@@ -18,14 +19,19 @@ import androidx.compose.ui.unit.dp
fun SimpleButton(text: String, icon: ImageVector, fun SimpleButton(text: String, icon: ImageVector,
color: Color = MaterialTheme.colors.primary, color: Color = MaterialTheme.colors.primary,
click: () -> Unit) { click: () -> Unit) {
Row( Surface(shape = RoundedCornerShape(20.dp)) {
verticalAlignment = Alignment.CenterVertically, Row(
modifier = Modifier.clickable { click() } verticalAlignment = Alignment.CenterVertically,
) { modifier = Modifier
Icon(icon, text, tint = color, .clickable { click() }
modifier = Modifier.padding(horizontal = 10.dp) .padding(8.dp)
) ) {
Text(text, style = MaterialTheme.typography.caption, color = color) 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, lineHeight = 22.sp,
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .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) SimpleButton(generalGetString(R.string.share_invitation_link), icon = Icons.Outlined.Share, click = share)
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))

View File

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

View File

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

View File

@@ -98,13 +98,14 @@
<string name="ok"></string> <string name="ok"></string>
<string name="no_details">нет описания</string> <string name="no_details">нет описания</string>
<string name="add_contact">Добавить контакт</string> <string name="add_contact">Добавить контакт</string>
<string name="scan_QR_code">Сканировать QR код</string>
<!-- NewChatSheet --> <!-- 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="in_person_or_in_video_call__bracketed">(при встрече или через видеозвонок)</string>
<string name="create_group">Создать группу</string> <string name="paste_received_link_from_clipboard">(вставить ссылку из буфера обмена)</string>
<string name="coming_soon__bracketed">(скоро!)</string>
<!-- GetImageView --> <!-- GetImageView -->
<string name="toast_camera_permission_denied">Разрешение не получено!</string> <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="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="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="share_invitation_link">Поделиться ссылкой</string>
<string name="paste_connection_link_below_to_connect">Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта..</string>
<!-- settings - SettingsView.kt --> <!-- settings - SettingsView.kt -->
<string name="your_settings">Настройки</string> <string name="your_settings">Настройки</string>
@@ -227,5 +230,9 @@
<string name="a_plus_b">a + b</string> <string name="a_plus_b">a + b</string>
<string name="colored">цвет</string> <string name="colored">цвет</string>
<string name="secret">секрет</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> </resources>

View File

@@ -98,13 +98,15 @@
<string name="ok">Ok</string> <string name="ok">Ok</string>
<string name="no_details">no details</string> <string name="no_details">no details</string>
<string name="add_contact">Add contact</string> <string name="add_contact">Add contact</string>
<string name="scan_QR_code">Scan QR code</string>
<!-- NewChatSheet --> <!-- 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="in_person_or_in_video_call__bracketed">(in person or in video call)</string>
<string name="create_group">Create Group</string> <string name="paste_received_link_from_clipboard">(paste link from clipboard)</string>
<string name="coming_soon__bracketed">(coming soon!)</string>
<!-- GetImageView --> <!-- GetImageView -->
<string name="toast_camera_permission_denied">Permission Denied!</string> <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="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="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="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 --> <!-- settings - SettingsView.kt -->
<string name="your_settings">Your settings</string> <string name="your_settings">Your settings</string>

View File

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

View File

@@ -78,23 +78,12 @@ struct ChatListView: View {
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
let title: LocalizedStringKey let title: LocalizedStringKey
if case .contact = action { title = "Connect via contact link?" } if case .contact = action { title = "Connect via contact link?" }
else { title = "Connect via invitation link?" } else { title = "Connect via one-time link?" }
return Alert( return Alert(
title: Text(title), title: Text(title),
message: Text("Your profile will be sent to the contact that you received this link from"), message: Text("Your profile will be sent to the contact that you received this link from"),
primaryButton: .default(Text("Connect")) { primaryButton: .default(Text("Connect")) {
DispatchQueue.main.async { connectViaLink(link)
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)")
}
}
}
}, },
secondaryButton: .cancel() secondaryButton: .cancel()
) )

View File

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

View File

@@ -12,26 +12,27 @@ struct NewChatButton: View {
@State private var showAddChat = false @State private var showAddChat = false
@State private var addContact = false @State private var addContact = false
@State private var connReqInvitation: String = "" @State private var connReqInvitation: String = ""
@State private var connectContact = false @State private var scanToConnect = false
@State private var createGroup = false @State private var pasteToConnect = false
var body: some View { var body: some View {
Button { showAddChat = true } label: { Button { showAddChat = true } label: {
Image(systemName: "person.crop.circle.badge.plus") Image(systemName: "person.crop.circle.badge.plus")
} }
.confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) { .confirmationDialog("Add contact to start a new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Add contact") { addContactAction() } Button("Create link / QR code") { addContactAction() }
Button("Scan QR code") { connectContact = true } Button("Paste received link") { pasteToConnect = true }
Button("Create group") { createGroup = true } Button("Scan QR code") { scanToConnect = true }
.disabled(true)
} }
.sheet(isPresented: $addContact, content: { .sheet(isPresented: $addContact, content: {
AddContactView(connReqInvitation: connReqInvitation) AddContactView(connReqInvitation: connReqInvitation)
}) })
.sheet(isPresented: $connectContact, content: { .sheet(isPresented: $scanToConnect, content: {
connectContactSheet() ScanToConnectView(openedSheet: $scanToConnect)
})
.sheet(isPresented: $pasteToConnect, content: {
PasteToConnectView(openedSheet: $pasteToConnect)
}) })
.sheet(isPresented: $createGroup, content: { CreateGroupView() })
} }
func addContactAction() { func addContactAction() {
@@ -45,28 +46,6 @@ struct NewChatButton: View {
logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") 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 { enum ConnReqType: Equatable {
@@ -74,6 +53,30 @@ enum ConnReqType: Equatable {
case invitation 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) { func connectionReqSentAlert(_ type: ConnReqType) {
AlertManager.shared.showAlertMsg( AlertManager.shared.showAlertMsg(
title: "Connection request sent!", 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 SwiftUI
import CodeScanner import CodeScanner
struct ConnectContactView: View { struct ScanToConnectView: View {
var completed: ((Result<Bool, Error>) -> Void) @Binding var openedSheet: Bool
var body: some View { var body: some View {
VStack { VStack {
@@ -37,24 +37,17 @@ struct ConnectContactView: View {
func processQRCode(_ resp: Result<ScanResult, ScanError>) { func processQRCode(_ resp: Result<ScanResult, ScanError>) {
switch resp { switch resp {
case let .success(r): case let .success(r):
Task { Task { connectViaLink(r.string, $openedSheet) }
do {
let ok = try await apiConnect(connReq: r.string)
completed(.success(ok))
} catch {
logger.error("ConnectContactView.processQRCode apiConnect error: \(error.localizedDescription)")
completed(.failure(error))
}
}
case let .failure(e): case let .failure(e):
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)") logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
completed(.failure(e)) openedSheet = false
} }
} }
} }
struct ConnectContactView_Previews: PreviewProvider { struct ConnectContactView_Previews: PreviewProvider {
static var previews: some View { 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> <target>Add contact</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="All your contacts will remain connected" xml:space="preserve">
<source>All your contacts will remain connected</source> <source>All your contacts will remain connected</source>
<target>All your contacts will remain connected</target> <target>All your contacts will remain connected</target>
@@ -160,6 +165,11 @@
<target>Choose from library</target> <target>Choose from library</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="Configure SMP servers" xml:space="preserve">
<source>Configure SMP servers</source> <source>Configure SMP servers</source>
<target>Configure SMP servers</target> <target>Configure SMP servers</target>
@@ -180,9 +190,14 @@
<target>Connect via contact link?</target> <target>Connect via contact link?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect via invitation link?" xml:space="preserve"> <trans-unit id="Connect via link" xml:space="preserve">
<source>Connect via invitation link?</source> <source>Connect via link</source>
<target>Connect via invitation link?</target> <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> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve"> <trans-unit id="Connecting server…" xml:space="preserve">
@@ -260,9 +275,9 @@
<target>Create address</target> <target>Create address</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Create group" xml:space="preserve"> <trans-unit id="Create link / QR code" xml:space="preserve">
<source>Create group</source> <source>Create link / QR code</source>
<target>Create group</target> <target>Create link / QR code</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Create profile" xml:space="preserve"> <trans-unit id="Create profile" xml:space="preserve">
@@ -470,6 +485,21 @@
<target>Open Settings</target> <target>Open Settings</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <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> <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> <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> to scan from the app</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="Take picture" xml:space="preserve">
<source>Take picture</source> <source>Take picture</source>
<target>Take picture</target> <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> <target>You are connected to the server used to receive messages from this contact.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="You can now send messages to %@" xml:space="preserve">
<source>You can now send messages to %@</source> <source>You can now send messages to %@</source>
<target>You can now send messages to %@</target> <target>You can now send messages to %@</target>

View File

@@ -115,6 +115,11 @@
<target>Добавить контакт</target> <target>Добавить контакт</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="All your contacts will remain connected" xml:space="preserve">
<source>All your contacts will remain connected</source> <source>All your contacts will remain connected</source>
<target>Все контакты, которые соединились через этот адрес, сохранятся.</target> <target>Все контакты, которые соединились через этот адрес, сохранятся.</target>
@@ -160,6 +165,11 @@
<target>Выбрать из библиотеки</target> <target>Выбрать из библиотеки</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="Configure SMP servers" xml:space="preserve">
<source>Configure SMP servers</source> <source>Configure SMP servers</source>
<target>Настройка SMP серверов</target> <target>Настройка SMP серверов</target>
@@ -180,9 +190,14 @@
<target>Соединиться через ссылку-контакт?</target> <target>Соединиться через ссылку-контакт?</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connect via invitation link?" xml:space="preserve"> <trans-unit id="Connect via link" xml:space="preserve">
<source>Connect via invitation link?</source> <source>Connect via link</source>
<target>Соединиться через ссылку-приглашение?</target> <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> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Connecting server…" xml:space="preserve"> <trans-unit id="Connecting server…" xml:space="preserve">
@@ -260,9 +275,9 @@
<target>Создать адрес</target> <target>Создать адрес</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Create group" xml:space="preserve"> <trans-unit id="Create link / QR code" xml:space="preserve">
<source>Create group</source> <source>Create link / QR code</source>
<target>Создать группу</target> <target>Создать ссылку / QR код</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </trans-unit>
<trans-unit id="Create profile" xml:space="preserve"> <trans-unit id="Create profile" xml:space="preserve">
@@ -470,6 +485,21 @@
<target>Открыть Настройки</target> <target>Открыть Настройки</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <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> <source>Please check that you used the correct link or ask your contact to send you another one.</source>
<target>Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку.</target> <target>Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку.</target>
@@ -566,11 +596,6 @@ to scan from the app</source>
<target>Покажите QR код вашему контакту для сканирования в приложении</target> <target>Покажите QR код вашему контакту для сканирования в приложении</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="Take picture" xml:space="preserve">
<source>Take picture</source> <source>Take picture</source>
<target>Сделать фото</target> <target>Сделать фото</target>
@@ -675,6 +700,11 @@ To connect, please ask your contact to create another connection link and check
<target>Установлено соединение с сервером, через который вы получаете сообщения от этого контакта.</target> <target>Установлено соединение с сервером, через который вы получаете сообщения от этого контакта.</target>
<note>No comment provided by engineer.</note> <note>No comment provided by engineer.</note>
</trans-unit> </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"> <trans-unit id="You can now send messages to %@" xml:space="preserve">
<source>You can now send messages to %@</source> <source>You can now send messages to %@</source>
<target>Вы теперь можете отправлять сообщения %@</target> <target>Вы теперь можете отправлять сообщения %@</target>

View File

@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; }; 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.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 */; }; 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 */; }; 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 */; }; 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 */; }; 5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; };
5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; }; 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407127ADB1D0007B033A /* Emoji.swift */; };
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; }; 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE4407827ADB701007B033A /* EmojiItemView.swift */; };
@@ -84,6 +85,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 5CE4407827ADB701007B033A /* EmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemView.swift; sourceTree = "<group>"; };
@@ -311,7 +313,8 @@
children = ( children = (
5C6AD81227A834E300348BD7 /* NewChatButton.swift */, 5C6AD81227A834E300348BD7 /* NewChatButton.swift */,
5CCD403327A5F6DF00368C90 /* AddContactView.swift */, 5CCD403327A5F6DF00368C90 /* AddContactView.swift */,
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */, 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */,
3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */,
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */, 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */,
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */, 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */,
); );
@@ -491,6 +494,7 @@
640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */, 640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */,
5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */,
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */, 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */,
3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */,
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */, 5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */,
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */, 5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
@@ -511,7 +515,7 @@
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */, 5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */,
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */, 5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */, 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */,
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */, 5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */,

View File

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