android: UI for pending contact connections, ios: translations, show profile picture in contact requests (#571)

* android: UI for pending contact connections, ios: translations, show profile picture in contact requests

* update translations

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

* update translation

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

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2022-04-26 07:41:08 +01:00
committed by GitHub
parent cd2eb9c88e
commit 44de6297ee
20 changed files with 505 additions and 67 deletions

View File

@@ -47,6 +47,7 @@ android {
freeCompilerArgs += "-opt-in=com.google.accompanist.insets.ExperimentalAnimatedInsets"
freeCompilerArgs += "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi"
freeCompilerArgs += "-opt-in=kotlinx.serialization.InternalSerializationApi"
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
}
externalNativeBuild {
cmake {

View File

@@ -4,6 +4,7 @@ import android.net.Uri
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.TextDecoration
@@ -54,9 +55,12 @@ class ChatModel(val controller: ChatController) {
if (i >= 0) chats[i] = chats[i].copy(chatInfo = cInfo)
}
fun updateContact(contact: Contact) {
val cInfo = ChatInfo.Direct(contact)
if (hasChat(contact.id)) {
fun updateContactConnection(contactConnection: PendingContactConnection) = updateChat(ChatInfo.ContactConnection(contactConnection))
fun updateContact(contact: Contact) = updateChat(ChatInfo.Direct(contact))
private fun updateChat(cInfo: ChatInfo) {
if (hasChat(cInfo.id)) {
updateChatInfo(cInfo)
} else {
addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf()))
@@ -200,14 +204,8 @@ class ChatModel(val controller: ChatController) {
enum class ChatType(val type: String) {
Direct("@"),
Group("#"),
ContactRequest("<@");
val chatTypeName: String get () =
when (this) {
Direct -> "contact"
Group -> "group"
ContactRequest -> "contact request"
}
ContactRequest("<@"),
ContactConnection(":");
}
@Serializable
@@ -343,6 +341,24 @@ sealed class ChatInfo: SomeChat, NamedChat {
val sampleData = ContactRequest(UserContactRequest.sampleData)
}
}
@Serializable @SerialName("contactConnection")
class ContactConnection(val contactConnection: PendingContactConnection): ChatInfo() {
override val chatType get() = ChatType.ContactConnection
override val localDisplayName get() = contactConnection.localDisplayName
override val id get() = contactConnection.id
override val apiId get() = contactConnection.apiId
override val ready get() = contactConnection.ready
override val createdAt get() = contactConnection.createdAt
override val displayName get() = contactConnection.displayName
override val fullName get() = contactConnection.fullName
override val image get() = contactConnection.image
companion object {
fun getSampleData(status: ConnStatus = ConnStatus.New, viaContactUri: Boolean = false): ContactConnection =
ContactConnection(PendingContactConnection.getSampleData(status, viaContactUri))
}
}
}
@Serializable
@@ -357,7 +373,7 @@ class Contact(
override val chatType get() = ChatType.Direct
override val id get() = "@$contactId"
override val apiId get() = contactId
override val ready get() = activeConn.connStatus == "ready"
override val ready get() = activeConn.connStatus == ConnStatus.Ready
override val displayName get() = profile.displayName
override val fullName get() = profile.fullName
override val image get() = profile.image
@@ -380,9 +396,10 @@ class ContactSubStatus(
)
@Serializable
class Connection(val connStatus: String) {
class Connection(val connId: Long, val connStatus: ConnStatus) {
val id: ChatId get() = ":$connId"
companion object {
val sampleData = Connection(connStatus = "ready")
val sampleData = Connection(connId = 1, connStatus = ConnStatus.Ready)
}
}
@@ -491,7 +508,8 @@ class UserContactRequest (
val contactRequestId: Long,
override val localDisplayName: String,
val profile: Profile,
override val createdAt: Instant
override val createdAt: Instant,
val updatedAt: Instant
): SomeChat, NamedChat {
override val chatType get() = ChatType.ContactRequest
override val id get() = "<@$contactRequestId"
@@ -506,11 +524,85 @@ class UserContactRequest (
contactRequestId = 1,
localDisplayName = "alice",
profile = Profile.sampleData,
createdAt = Clock.System.now()
createdAt = Clock.System.now(),
updatedAt = Clock.System.now()
)
}
}
@Serializable
class PendingContactConnection(
val pccConnId: Long,
val pccAgentConnId: String,
val pccConnStatus: ConnStatus,
val viaContactUri: Boolean,
override val createdAt: Instant,
val updatedAt: Instant
): SomeChat, NamedChat {
override val chatType get() = ChatType.ContactConnection
override val id get () = ":$pccConnId"
override val apiId get() = pccConnId
override val ready get() = false
override val localDisplayName get() = String.format(generalGetString(R.string.connection_local_display_name), pccConnId)
override val displayName: String get() {
val initiated = pccConnStatus.initiated
return if (initiated == null) {
// this should not be in the chat list
generalGetString(R.string.display_name_connection_established)
} else {
generalGetString(
if (initiated && !viaContactUri) R.string.display_name_invited_to_connect
else R.string.display_name_connecting
)
}
}
override val fullName get() = ""
override val image get() = null
val initiated get() = (pccConnStatus.initiated ?: false) && !viaContactUri
val description: String get() {
val initiated = pccConnStatus.initiated
return if (initiated == null) "" else generalGetString(
if (initiated && !viaContactUri) R.string.description_you_shared_one_time_link
else if (viaContactUri ) R.string.description_via_contact_address_link
else R.string.description_via_one_time_link
)
}
companion object {
fun getSampleData(status: ConnStatus = ConnStatus.New, viaContactUri: Boolean = false): PendingContactConnection =
PendingContactConnection(
pccConnId = 1,
pccAgentConnId = "abcd",
pccConnStatus = status,
viaContactUri = viaContactUri,
createdAt = Clock.System.now(),
updatedAt = Clock.System.now()
)
}
}
@Serializable
enum class ConnStatus {
@SerialName("new") New,
@SerialName("joined") Joined,
@SerialName("requested") Requested,
@SerialName("accepted") Accepted,
@SerialName("snd-ready") SndReady,
@SerialName("ready") Ready,
@SerialName("deleted") Deleted;
val initiated: Boolean? get() = when (this) {
New -> true
Joined -> false
Requested -> true
Accepted -> true
SndReady -> false
Ready -> null
Deleted -> null
}
}
@Serializable
class AChatItem (
val chatInfo: ChatInfo,
@@ -800,7 +892,6 @@ sealed class MsgContent {
}
object MsgContentSerializer : KSerializer<MsgContent> {
@OptIn(InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) {
element("MCText", buildClassSerialDescriptor("MCText") {
element<String>("text")

View File

@@ -241,9 +241,10 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean {
val r = sendCmd(CC.ApiDeleteChat(type, id))
when (r) {
is CR.ContactDeleted -> return true // TODO groups
is CR.ChatCmdError -> {
when {
r is CR.ContactDeleted && type == ChatType.Direct -> return true
r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> return true
r is CR.ChatCmdError -> {
val e = r.chatError
if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) {
AlertManager.shared.showAlertMsg(
@@ -252,7 +253,15 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
)
}
}
else -> apiErrorAlert("apiDeleteChat", "Error deleting ${type.chatTypeName}", r)
else -> {
val titleId = when (type) {
ChatType.Direct -> R.string.error_deleting_contact
ChatType.Group -> R.string.error_deleting_group
ChatType.ContactRequest -> R.string.error_deleting_contact_request
ChatType.ContactConnection -> R.string.error_deleting_pending_contact_connection
}
apiErrorAlert("apiDeleteChat", generalGetString(titleId), r)
}
}
return false
}
@@ -334,13 +343,21 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
fun processReceivedMsg(r: CR) {
chatModel.terminalItems.add(TerminalItem.resp(r))
when (r) {
is CR.NewContactConnection -> {
chatModel.updateContactConnection(r.connection)
}
is CR.ContactConnectionDeleted -> {
chatModel.removeChat(r.connection.id)
}
is CR.ContactConnected -> {
chatModel.updateContact(r.contact)
chatModel.removeChat(r.contact.activeConn.id)
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected())
// NtfManager.shared.notifyContactConnected(contact)
}
is CR.ContactConnecting -> {
chatModel.updateContact(r.contact)
chatModel.removeChat(r.contact.activeConn.id)
}
is CR.ReceivedContactRequest -> {
val contactRequest = r.contactRequest
@@ -534,7 +551,7 @@ sealed class CC {
is CreateActiveUser -> "/u ${profile.displayName} ${profile.fullName}"
is StartChat -> "/_start"
is SetFilesFolder -> "/_files_folder $filesFolder"
is ApiGetChats -> "/_get chats"
is ApiGetChats -> "/_get chats pcc=on"
is ApiGetChat -> "/_get chat ${chatRef(type, id)} count=100"
is ApiSendMessage -> when {
file == null && quotedItemId == null -> "/_send ${chatRef(type, id)} ${mc.cmdString}"
@@ -664,6 +681,8 @@ sealed class CR {
@Serializable @SerialName("chatItemDeleted") class ChatItemDeleted(val deletedChatItem: AChatItem, val toChatItem: AChatItem): CR()
@Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted: CR()
@Serializable @SerialName("rcvFileComplete") class RcvFileComplete(val chatItem: AChatItem): CR()
@Serializable @SerialName("newContactConnection") class NewContactConnection(val connection: PendingContactConnection): CR()
@Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val connection: PendingContactConnection): CR()
@Serializable @SerialName("cmdOk") class CmdOk: CR()
@Serializable @SerialName("chatCmdError") class ChatCmdError(val chatError: ChatError): CR()
@Serializable @SerialName("chatError") class ChatRespError(val chatError: ChatError): CR()
@@ -708,6 +727,8 @@ sealed class CR {
is ChatItemDeleted -> "chatItemDeleted"
is RcvFileAccepted -> "rcvFileAccepted"
is RcvFileComplete -> "rcvFileComplete"
is NewContactConnection -> "newContactConnection"
is ContactConnectionDeleted -> "contactConnectionDeleted"
is CmdOk -> "cmdOk"
is ChatCmdError -> "chatCmdError"
is ChatRespError -> "chatError"
@@ -753,6 +774,8 @@ sealed class CR {
is ChatItemDeleted -> "deletedChatItem:\n${json.encodeToString(deletedChatItem)}\ntoChatItem:\n${json.encodeToString(toChatItem)}"
is RcvFileAccepted -> noDetails()
is RcvFileComplete -> json.encodeToString(chatItem)
is NewContactConnection -> json.encodeToString(connection)
is ContactConnectionDeleted -> json.encodeToString(connection)
is CmdOk -> noDetails()
is ChatCmdError -> chatError.string
is ChatRespError -> chatError.string

View File

@@ -61,8 +61,8 @@ fun ChatInfoLayout(chat: Chat, close: () -> Unit, deleteContact: () -> Unit) {
) {
CloseSheetBar(close)
Spacer(Modifier.size(48.dp))
ChatInfoImage(chat, size = 192.dp)
val cInfo = chat.chatInfo
ChatInfoImage(cInfo, size = 192.dp)
Text(
cInfo.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
color = MaterialTheme.colors.onBackground,

View File

@@ -261,7 +261,7 @@ fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) {
verticalAlignment = Alignment.CenterVertically
) {
val cInfo = chat.chatInfo
ChatInfoImage(chat, size = 40.dp)
ChatInfoImage(cInfo, size = 40.dp)
Column(
Modifier.padding(start = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally

View File

@@ -3,12 +3,12 @@ package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
@@ -22,14 +22,15 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
ChatListNavLinkLayout(
chat = chat,
click = {
if (chat.chatInfo is ChatInfo.ContactRequest) {
contactRequestAlertDialog(chat.chatInfo, chatModel)
} else {
if (chat.chatInfo.ready) {
withApi { openChat(chatModel, chat.chatInfo) }
} else {
pendingConnectionAlertDialog(chat.chatInfo, chatModel)
}
when (chat.chatInfo) {
is ChatInfo.ContactRequest -> contactRequestAlertDialog(chat.chatInfo, chatModel)
is ChatInfo.ContactConnection -> contactConnectionAlertDialog(chat.chatInfo.contactConnection, chatModel)
else ->
if (chat.chatInfo.ready) {
withApi { openChat(chatModel, chat.chatInfo) }
} else {
pendingContactAlertDialog(chat.chatInfo, chatModel)
}
}
}
)
@@ -67,7 +68,58 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel
)
}
fun pendingConnectionAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) {
fun contactConnectionAlertDialog(connection: PendingContactConnection, chatModel: ChatModel) {
AlertManager.shared.showAlertDialogButtons(
title = generalGetString(
if (connection.initiated) R.string.you_invited_your_contact
else R.string.you_accepted_connection
),
text = generalGetString(
if (connection.viaContactUri) R.string.you_will_be_connected_when_your_connection_request_is_accepted
else R.string.you_will_be_connected_when_your_contacts_device_is_online
),
buttons = {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 2.dp),
horizontalArrangement = Arrangement.End,
) {
Button(onClick = {
AlertManager.shared.hideAlert()
deleteContactConnectionAlert(connection, chatModel)
}) {
Text(stringResource(R.string.delete_verb))
}
Spacer(Modifier.padding(horizontal = 4.dp))
Button(onClick = { AlertManager.shared.hideAlert() }) {
Text(stringResource(R.string.ok))
}
}
}
)
}
fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.delete_pending_connection__question),
text = generalGetString(
if (connection.initiated) R.string.contact_you_shared_link_with_wont_be_able_to_connect
else R.string.connection_you_accepted_will_be_cancelled
),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
AlertManager.shared.hideAlert()
if (chatModel.controller.apiDeleteChat(ChatType.ContactConnection, connection.apiId)) {
chatModel.removeChat(connection.id)
}
}
}
)
}
fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.alert_title_contact_connection_pending),
text = generalGetString(R.string.alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry),
@@ -101,10 +153,11 @@ fun ChatListNavLinkLayout(chat: Chat, click: () -> Unit) {
.padding(end = 12.dp),
verticalAlignment = Alignment.Top
) {
if (chat.chatInfo is ChatInfo.ContactRequest) {
ContactRequestView(chat)
} else {
ChatPreviewView(chat)
when (chat.chatInfo) {
is ChatInfo.Direct -> ChatPreviewView(chat)
is ChatInfo.Group -> ChatPreviewView(chat)
is ChatInfo.ContactRequest -> ContactRequestView(chat.chatInfo)
is ChatInfo.ContactConnection -> ContactConnectionView(chat.chatInfo.contactConnection)
}
}
}

View File

@@ -28,21 +28,22 @@ import chat.simplex.app.views.helpers.badgeLayout
@Composable
fun ChatPreviewView(chat: Chat) {
Row {
ChatInfoImage(chat, size = 72.dp)
val cInfo = chat.chatInfo
ChatInfoImage(cInfo, size = 72.dp)
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
chat.chatInfo.chatViewName,
cInfo.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = if (chat.chatInfo.ready) Color.Unspecified else HighOrLowlight
color = if (cInfo.ready) Color.Unspecified else HighOrLowlight
)
if (chat.chatInfo.ready) {
if (cInfo.ready) {
val ci = chat.chatItems.lastOrNull()
if (ci != null) {
MarkdownText(

View File

@@ -0,0 +1,56 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddLink
import androidx.compose.material.icons.outlined.Link
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.helpers.ChatInfoImage
import chat.simplex.app.views.helpers.ProfileImage
@Composable
fun ContactConnectionView(contactConnection: PendingContactConnection) {
Row {
Box(Modifier.size(72.dp), contentAlignment = Alignment.Center) {
ProfileImage(size = 54.dp, null, if (contactConnection.initiated) Icons.Outlined.AddLink else Icons.Outlined.Link)
}
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
contactConnection.displayName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = HighOrLowlight
)
Text(contactConnection.description, maxLines = 2, color = HighOrLowlight)
}
val ts = getTimestampText(contactConnection.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top
) {
Text(
ts,
color = HighOrLowlight,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(bottom = 5.dp)
)
}
}
}

View File

@@ -10,35 +10,30 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.Chat
import chat.simplex.app.model.getTimestampText
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.helpers.ChatInfoImage
@Composable
fun ContactRequestView(chat: Chat) {
fun ContactRequestView(contactRequest: ChatInfo.ContactRequest) {
Row {
ChatInfoImage(chat, size = 72.dp)
ChatInfoImage(contactRequest, size = 72.dp)
Column(
modifier = Modifier
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
chat.chatInfo.chatViewName,
contactRequest.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary
)
Text(
stringResource(R.string.contact_wants_to_connect_with_you),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(stringResource(R.string.contact_wants_to_connect_with_you), maxLines = 2)
}
val ts = getTimestampText(chat.chatInfo.createdAt)
val ts = getTimestampText(contactRequest.contactRequest.updatedAt)
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.Top

View File

@@ -24,11 +24,11 @@ import chat.simplex.app.model.ChatInfo
import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
fun ChatInfoImage(chat: Chat, size: Dp) {
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp) {
val icon =
if (chat.chatInfo is ChatInfo.Group) Icons.Filled.SupervisedUserCircle
if (chatInfo is ChatInfo.Group) Icons.Filled.SupervisedUserCircle
else Icons.Filled.AccountCircle
ProfileImage(size, chat.chatInfo.image, icon)
ProfileImage(size, chatInfo.image, icon)
}
@Composable
@@ -62,7 +62,7 @@ fun ProfileImage(
fun PreviewChatInfoImage() {
SimpleXTheme {
ChatInfoImage(
chat = Chat(chatInfo = ChatInfo.Direct.sampleData, chatItems = arrayListOf()),
chatInfo = ChatInfo.Direct.sampleData,
size = 55.dp
)
}

View File

@@ -24,6 +24,15 @@
<string name="unknown_message_format">неизвестный формат сообщения</string>
<string name="invalid_message_format">неверный формат сообщения</string>
<!-- PendingContactConnection - ChatModel.kt -->
<string name="connection_local_display_name">connection <xliff:g id="connection ID" example="1">%1$d</xliff:g></string>
<string name="display_name_connection_established">соединение установлено</string>
<string name="display_name_invited_to_connect">приглашение соединиться</string>
<string name="display_name_connecting">соединяется…</string>
<string name="description_you_shared_one_time_link">вы создали одноразовую ссылку</string>
<string name="description_via_contact_address_link">через ссылку-контакт</string>
<string name="description_via_one_time_link">через одноразовую ссылку</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Ошибка при сохранении SMP серверов</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется.</string>
@@ -38,6 +47,10 @@
<string name="cannot_delete_contact">Невозможно удалить контакт!</string>
<string name="contact_cannot_be_deleted_as_they_are_in_groups">Контакт <xliff:g id="contactName" example="Jane Doe">%1$s!</xliff:g> не может быть удален, так как является членом групп(ы) <xliff:g id="groups" example="[team, chess club]">%2$s</xliff:g>.</string>
<string name="icon_descr_instant_notifications">Мгновенные уведомления</string>
<string name="error_deleting_contact">Ошибка удаления контакта</string>
<string name="error_deleting_group">Ошибка удаления группы</string>
<string name="error_deleting_contact_request">Ошибка удаления запроса</string>
<string name="error_deleting_pending_contact_connection">Ошибка удаления ожидаемого соединения</string>
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Приватные мгновенные уведомления!</string>
@@ -132,6 +145,13 @@
<string name="accept_contact_button">Принять</string>
<string name="reject_contact_button">Отклонить</string>
<!-- Pending contact connection alert dialogues -->
<string name="you_invited_your_contact">Вы пригласили ваш контакт</string>
<string name="you_accepted_connection">Вы приняли приглашение соединиться</string>
<string name="delete_pending_connection__question">Удалить ожидаемое соединение?</string>
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Контакт, которому вы отправили эту ссылку, не сможет соединиться!</string>
<string name="connection_you_accepted_will_be_cancelled">Подтвержденное соединение будет отменено!</string>
<!-- Connection Pending Alert Dialogue - ChatListNavLinkView.kt -->
<string name="alert_title_contact_connection_pending">Соединение еще не установлено!</string>
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой).</string>

View File

@@ -24,6 +24,15 @@
<string name="unknown_message_format">unknown message format</string>
<string name="invalid_message_format">invalid message format</string>
<!-- PendingContactConnection - ChatModel.kt -->
<string name="connection_local_display_name">connection <xliff:g id="connection ID" example="1">%1$d</xliff:g></string>
<string name="display_name_connection_established">connection established</string>
<string name="display_name_invited_to_connect">invited to connect</string>
<string name="display_name_connecting">connecting…</string>
<string name="description_you_shared_one_time_link">you shared one-time link</string>
<string name="description_via_contact_address_link">via contact address link</string>
<string name="description_via_one_time_link">via one-time link</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Error saving SMP servers</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
@@ -38,6 +47,10 @@
<string name="cannot_delete_contact">Can\'t delete contact!</string>
<string name="contact_cannot_be_deleted_as_they_are_in_groups">Contact <xliff:g id="contactName" example="Jane Doe">%1$s!</xliff:g> cannot be deleted, they are a member of the group(s) <xliff:g id="groups" example="[team, chess club]">%2$s</xliff:g>.</string>
<string name="icon_descr_instant_notifications">Instant notifications</string>
<string name="error_deleting_contact">Error deleting contact</string>
<string name="error_deleting_group">Error deleting group</string>
<string name="error_deleting_contact_request">Error deleting contact request</string>
<string name="error_deleting_pending_contact_connection">Error deleting pending contact connection</string>
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Private instant notifications!</string>
@@ -133,7 +146,14 @@
<string name="accept_contact_button">Accept</string>
<string name="reject_contact_button">Reject</string>
<!-- Connection Pending Alert Dialogue - ChatListNavLinkView.kt -->
<!-- Pending contact connection alert dialogues -->
<string name="you_invited_your_contact">You invited your contact</string>
<string name="you_accepted_connection">You accepted connection</string>
<string name="delete_pending_connection__question">Delete pending connection?</string>
<string name="contact_you_shared_link_with_wont_be_able_to_connect">The contact you shared this link with will NOT be able to connect!</string>
<string name="connection_you_accepted_will_be_cancelled">The connection you accepted will be cancelled!</string>
<!-- Contact Pending Alert Dialogue - ChatListNavLinkView.kt -->
<string name="alert_title_contact_connection_pending">Contact is not connected yet!</string>
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link).</string>

View File

@@ -713,8 +713,10 @@ func processReceivedMsg(_ res: ChatResponse) {
m.terminalItems.append(.resp(.now, res))
logger.debug("processReceivedMsg: \(res.responseType)")
switch res {
case let .newContactConnection(contactConnection):
m.updateContactConnection(contactConnection)
case let .newContactConnection(connection):
m.updateContactConnection(connection)
case let .contactConnectionDeleted(connection):
m.removeChat(connection.id)
case let .contactConnected(contact):
m.updateContact(contact)
m.removeChat(contact.activeConn.id)
@@ -961,6 +963,7 @@ enum StoreError: Decodable {
case fileNotFound(fileId: Int64)
case rcvFileInvalid(fileId: Int64)
case connectionNotFound(agentConnId: String)
case pendingConnectionNotFound(connId: Int64)
case introNotFound
case uniqueID
case internalError(message: String)
@@ -969,6 +972,7 @@ enum StoreError: Decodable {
case chatItemNotFound(itemId: Int64)
case quotedChatItemNotFound
case chatItemSharedMsgIdNotFound(sharedMsgId: String)
case chatItemNotFoundByFileId(fileId: Int64)
}
enum AgentErrorType: Decodable {

View File

@@ -108,7 +108,7 @@ struct ChatListNavLink: View {
}
private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View {
ContactRequestView(contactRequest: contactRequest)
ContactRequestView(contactRequest: contactRequest, chat: chat)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button { Task { await acceptContactRequest(contactRequest) } }
label: { Label("Accept", systemImage: "checkmark") }

View File

@@ -10,14 +10,12 @@ import SwiftUI
struct ContactRequestView: View {
var contactRequest: UserContactRequest
@ObservedObject var chat: Chat
var body: some View {
return HStack(spacing: 8) {
Image(systemName: "person.crop.circle.fill")
.resizable()
.foregroundColor(Color(uiColor: .secondarySystemBackground))
ChatInfoImage(chat: chat)
.frame(width: 63, height: 63)
.padding(.leading, 4)
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .top) {
Text(contactRequest.chatViewName)
@@ -47,7 +45,7 @@ struct ContactRequestView: View {
struct ContactRequestView_Previews: PreviewProvider {
static var previews: some View {
ContactRequestView(contactRequest: UserContactRequest.sampleData)
ContactRequestView(contactRequest: UserContactRequest.sampleData, chat: Chat(chatInfo: ChatInfo.sampleData.contactRequest , chatItems: []))
.previewLayout(.fixed(width: 360, height: 80))
}
}

View File

@@ -17,6 +17,7 @@ struct ChatInfoImage: View {
switch chat.chatInfo {
case .direct: iconName = "person.crop.circle.fill"
case .group: iconName = "person.2.circle.fill"
case .contactRequest: iconName = "person.crop.circle.fill"
default: iconName = "circle.fill"
}
return ProfileImage(

View File

@@ -340,6 +340,11 @@
<target>Delete pending connection</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
<source>Delete pending connection?</source>
<target>Delete pending connection?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
<source>Develop</source>
<target>Develop</target>
@@ -597,6 +602,11 @@ to scan from the app</source>
to scan from the app</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show pending connections" xml:space="preserve">
<source>Show pending connections</source>
<target>Show pending connections</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>
@@ -624,6 +634,16 @@ to scan from the app</target>
*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
<source>The connection you accepted will be cancelled!</source>
<target>The connection you accepted will be cancelled!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The contact you shared this link with will NOT be able to connect!" xml:space="preserve">
<source>The contact you shared this link with will NOT be able to connect!</source>
<target>The contact you shared this link with will NOT be able to connect!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messaging and application platform 100% private by design!" xml:space="preserve">
<source>The messaging and application platform 100% private by design!</source>
<target>The messaging and application platform 100% private by design!</target>
@@ -691,6 +711,11 @@ To connect, please ask your contact to create another connection link and check
<target>You</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You accepted connection" xml:space="preserve">
<source>You accepted connection</source>
<target>You accepted connection</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connected to %@ via this link." xml:space="preserve">
<source>You are already connected to %@ via this link.</source>
<target>You are already connected to %@ via this link.</target>
@@ -726,6 +751,11 @@ To connect, please ask your contact to create another connection link and check
<target>You control your chat!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You invited your contact" xml:space="preserve">
<source>You invited your contact</source>
<target>You invited your contact</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>You will be connected when your connection request is accepted, please wait or check later!</target>
@@ -830,11 +860,31 @@ SimpleX servers cannot see your profile.</target>
<target>connect to SimpleX Chat developers.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connecting…" xml:space="preserve">
<source>connecting…</source>
<target>connecting…</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="connection established" xml:space="preserve">
<source>connection established</source>
<target>connection established</target>
<note>chat list item title (it should not be shown</note>
</trans-unit>
<trans-unit id="connection:%@" xml:space="preserve">
<source>connection:%@</source>
<target>connection:%@</target>
<note>connection information</note>
</trans-unit>
<trans-unit id="deleted" xml:space="preserve">
<source>deleted</source>
<target>deleted</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="invited to connect" xml:space="preserve">
<source>invited to connect</source>
<target>invited to connect</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="italic" xml:space="preserve">
<source>italic</source>
<target>italic</target>
@@ -855,11 +905,26 @@ SimpleX servers cannot see your profile.</target>
<target>v%@ (%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="via contact address link" xml:space="preserve">
<source>via contact address link</source>
<target>via contact address link</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="via one-time link" xml:space="preserve">
<source>via one-time link</source>
<target>via one-time link</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="wants to connect to you!" xml:space="preserve">
<source>wants to connect to you!</source>
<target>wants to connect to you!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you shared one-time link" xml:space="preserve">
<source>you shared one-time link</source>
<target>you shared one-time link</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="~strike~" xml:space="preserve">
<source>~strike~</source>
<target>~strike~</target>

View File

@@ -340,6 +340,11 @@
<target>Удалить соединение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Delete pending connection?" xml:space="preserve">
<source>Delete pending connection?</source>
<target>Удалить ожидаемое соединение?</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Develop" xml:space="preserve">
<source>Develop</source>
<target>Для разработчиков</target>
@@ -596,6 +601,11 @@ to scan from the app</source>
<target>Покажите QR код вашему контакту для сканирования в приложении</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Show pending connections" xml:space="preserve">
<source>Show pending connections</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>
@@ -623,6 +633,16 @@ to scan from the app</source>
*Обратите внимание*: если вы подтвердите, токен вашего устройства будет послан на сервер SimpleX Chat.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The connection you accepted will be cancelled!" xml:space="preserve">
<source>The connection you accepted will be cancelled!</source>
<target>Подтвержденное соединение будет отменено!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The contact you shared this link with will NOT be able to connect!" xml:space="preserve">
<source>The contact you shared this link with will NOT be able to connect!</source>
<target>Контакт, которому вы отправили эту ссылку, не сможет соединиться!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="The messaging and application platform 100% private by design!" xml:space="preserve">
<source>The messaging and application platform 100% private by design!</source>
<target>Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность.</target>
@@ -690,6 +710,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 accepted connection" xml:space="preserve">
<source>You accepted connection</source>
<target>Вы приняли приглашение соединиться</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You are already connected to %@ via this link." xml:space="preserve">
<source>You are already connected to %@ via this link.</source>
<target>Вы уже соединены с %@ через эту ссылку.</target>
@@ -725,6 +750,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 invited your contact" xml:space="preserve">
<source>You invited your contact</source>
<target>Вы пригласили ваш контакт</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="You will be connected when your connection request is accepted, please wait or check later!" xml:space="preserve">
<source>You will be connected when your connection request is accepted, please wait or check later!</source>
<target>Соединение будет установлено, когда ваш запрос будет принят. Пожалуйста, подождите или проверьте позже!</target>
@@ -829,11 +859,31 @@ SimpleX серверы не могут получить доступ к ваше
<target>соединитесь с разработчиками.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="connecting…" xml:space="preserve">
<source>connecting…</source>
<target>соединяется…</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="connection established" xml:space="preserve">
<source>connection established</source>
<target>соединение установлено</target>
<note>chat list item title (it should not be shown</note>
</trans-unit>
<trans-unit id="connection:%@" xml:space="preserve">
<source>connection:%@</source>
<target>connection:%@</target>
<note>connection information</note>
</trans-unit>
<trans-unit id="deleted" xml:space="preserve">
<source>deleted</source>
<target>удалено</target>
<note>deleted chat item</note>
</trans-unit>
<trans-unit id="invited to connect" xml:space="preserve">
<source>invited to connect</source>
<target>приглашение соединиться</target>
<note>chat list item title</note>
</trans-unit>
<trans-unit id="italic" xml:space="preserve">
<source>italic</source>
<target>курсив</target>
@@ -854,11 +904,26 @@ SimpleX серверы не могут получить доступ к ваше
<target>v%@ (%@)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="via contact address link" xml:space="preserve">
<source>via contact address link</source>
<target>через ссылку-контакт</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="via one-time link" xml:space="preserve">
<source>via one-time link</source>
<target>через одноразовую ссылку</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="wants to connect to you!" xml:space="preserve">
<source>wants to connect to you!</source>
<target>хочет соединиться с вами!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="you shared one-time link" xml:space="preserve">
<source>you shared one-time link</source>
<target>вы создали одноразовую ссылку</target>
<note>chat list item description</note>
</trans-unit>
<trans-unit id="~strike~" xml:space="preserve">
<source>~strike~</source>
<target>\~зачеркнуть~</target>

View File

@@ -151,6 +151,9 @@
/* No comment provided by engineer. */
"Connecting server… (error: %@)" = "Устанавливается соединение с сервером… (ошибка: %@)";
/* chat list item title */
"connecting…" = "соединяется…";
/* No comment provided by engineer. */
"Connecting..." = "Устанавливается соединение…";
@@ -160,6 +163,9 @@
/* No comment provided by engineer. */
"Connection error (AUTH)" = "Ошибка соединения (AUTH)";
/* chat list item title (it should not be shown */
"connection established" = "соединение установлено";
/* No comment provided by engineer. */
"Connection request" = "Запрос на соединение";
@@ -169,6 +175,9 @@
/* No comment provided by engineer. */
"Connection timeout" = "Превышено время соединения";
/* connection information */
"connection:%@" = "connection:%@";
/* No comment provided by engineer. */
"Contact already exists" = "Существующий контакт";
@@ -229,6 +238,9 @@
/* No comment provided by engineer. */
"Delete pending connection" = "Удалить соединение";
/* No comment provided by engineer. */
"Delete pending connection?" = "Удалить ожидаемое соединение?";
/* deleted chat item */
"deleted" = "удалено";
@@ -295,6 +307,9 @@
/* No comment provided by engineer. */
"Invalid connection link" = "Ошибка в ссылке контакта";
/* chat list item title */
"invited to connect" = "приглашение соединиться";
/* No comment provided by engineer. */
"italic" = "курсив";
@@ -385,6 +400,9 @@
/* No comment provided by engineer. */
"Share link" = "Поделиться ссылкой";
/* No comment provided by engineer. */
"Show pending connections" = "Показать ожидаемые соединения";
/* No comment provided by engineer. */
"Show QR code to your contact\nto scan from the app" = "Покажите QR код вашему контакту для сканирования в приложении";
@@ -409,6 +427,12 @@
/* No comment provided by engineer. */
"The app can receive background notifications every 20 minutes to check the new messages.\n*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server." = "Приложение может получать скрытые уведомления каждые 20 минут чтобы проверить новые сообщения.\n*Обратите внимание*: если вы подтвердите, токен вашего устройства будет послан на сервер SimpleX Chat.";
/* No comment provided by engineer. */
"The connection you accepted will be cancelled!" = "Подтвержденное соединение будет отменено!";
/* No comment provided by engineer. */
"The contact you shared this link with will NOT be able to connect!" = "Контакт, которому вы отправили эту ссылку, не сможет соединиться!";
/* No comment provided by engineer. */
"The messaging and application platform 100% private by design!" = "Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность.";
@@ -445,6 +469,12 @@
/* No comment provided by engineer. */
"v%@ (%@)" = "v%@ (%@)";
/* chat list item description */
"via contact address link" = "через ссылку-контакт";
/* chat list item description */
"via one-time link" = "через одноразовую ссылку";
/* No comment provided by engineer. */
"wants to connect to you!" = "хочет соединиться с вами!";
@@ -454,6 +484,9 @@
/* No comment provided by engineer. */
"You" = "Вы";
/* No comment provided by engineer. */
"You accepted connection" = "Вы приняли приглашение соединиться";
/* No comment provided by engineer. */
"You are already connected to %@ via this link." = "Вы уже соединены с %@ через эту ссылку.";
@@ -475,6 +508,12 @@
/* No comment provided by engineer. */
"You control your chat!" = "Вы котролируете Ваш чат!";
/* No comment provided by engineer. */
"You invited your contact" = "Вы пригласили ваш контакт";
/* chat list item description */
"you shared one-time link" = "вы создали одноразовую ссылку";
/* No comment provided by engineer. */
"You will be connected when your connection request is accepted, please wait or check later!" = "Соединение будет установлено, когда ваш запрос будет принят. Пожалуйста, подождите или проверьте позже!";

6
scripts/android/prepare.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
# libsimplex.so and libsupport.so binaries should be in ~/Downloads folder
rm ./apps/android/app/src/main/cpp/libs/arm64-v8a/*
cp ~/Downloads/libsimplex.so ./apps/android/app/src/main/cpp/libs/arm64-v8a/
cp ~/Downloads/libsupport.so ./apps/android/app/src/main/cpp/libs/arm64-v8a/