diff --git a/apps/android/app/build.gradle b/apps/android/app/build.gradle index 62bf84673..9d9b1154b 100644 --- a/apps/android/app/build.gradle +++ b/apps/android/app/build.gradle @@ -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 { diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 6f3376cd2..a379e1a00 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -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 { - @OptIn(InternalSerializationApi::class) override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { element("MCText", buildClassSerialDescriptor("MCText") { element("text") diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index b38fcbe01..db708aaf0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -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 diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 0faae49c6..5f380545c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -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, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 6b23da0b2..73be70f10 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -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 diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt index b764c8d5a..02bc964b6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -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) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index 80e3713c3..3144f9271 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -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( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt new file mode 100644 index 000000000..b5636a354 --- /dev/null +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactConnectionView.kt @@ -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) + ) + } + } +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt index 3866fc012..1140273b0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ContactRequestView.kt @@ -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 diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt index 8f57b8f15..93ca20c5d 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ChatInfoImage.kt @@ -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 ) } diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 313b9f39f..5d42f5fb0 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -24,6 +24,15 @@ неизвестный формат сообщения неверный формат сообщения + + connection %1$d + соединение установлено + приглашение соединиться + соединяется… + вы создали одноразовую ссылку + через ссылку-контакт + через одноразовую ссылку + Ошибка при сохранении SMP серверов Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. @@ -38,6 +47,10 @@ Невозможно удалить контакт! Контакт %1$s! не может быть удален, так как является членом групп(ы) %2$s. Мгновенные уведомления + Ошибка удаления контакта + Ошибка удаления группы + Ошибка удаления запроса + Ошибка удаления ожидаемого соединения Приватные мгновенные уведомления! @@ -132,6 +145,13 @@ Принять Отклонить + + Вы пригласили ваш контакт + Вы приняли приглашение соединиться + Удалить ожидаемое соединение? + Контакт, которому вы отправили эту ссылку, не сможет соединиться! + Подтвержденное соединение будет отменено! + Соединение еще не установлено! Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой). diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 1d840899f..73219eff5 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -24,6 +24,15 @@ unknown message format invalid message format + + connection %1$d + connection established + invited to connect + connecting… + you shared one-time link + via contact address link + via one-time link + Error saving SMP servers Make sure SMP server addresses are in correct format, line separated and are not duplicated. @@ -38,6 +47,10 @@ Can\'t delete contact! Contact %1$s! cannot be deleted, they are a member of the group(s) %2$s. Instant notifications + Error deleting contact + Error deleting group + Error deleting contact request + Error deleting pending contact connection Private instant notifications! @@ -133,7 +146,14 @@ Accept Reject - + + You invited your contact + You accepted connection + Delete pending connection? + The contact you shared this link with will NOT be able to connect! + The connection you accepted will be cancelled! + + Contact is not connected yet! 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). diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index af6803b79..674d04737 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -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 { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 78b6777ea..8d9636147 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -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") } diff --git a/apps/ios/Shared/Views/ChatList/ContactRequestView.swift b/apps/ios/Shared/Views/ChatList/ContactRequestView.swift index 31be17edf..d13955066 100644 --- a/apps/ios/Shared/Views/ChatList/ContactRequestView.swift +++ b/apps/ios/Shared/Views/ChatList/ContactRequestView.swift @@ -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)) } } diff --git a/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift b/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift index c1b9abc2f..c9a2bdf16 100644 --- a/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift +++ b/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift @@ -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( diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 2744ebdc2..57ca5f9cb 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -340,6 +340,11 @@ Delete pending connection No comment provided by engineer. + + Delete pending connection? + Delete pending connection? + No comment provided by engineer. + Develop Develop @@ -597,6 +602,11 @@ to scan from the app to scan from the app No comment provided by engineer. + + Show pending connections + Show pending connections + No comment provided by engineer. + Take picture Take picture @@ -624,6 +634,16 @@ to scan from the app *Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server. No comment provided by engineer. + + The connection you accepted will be cancelled! + 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! + 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! The messaging and application platform 100% private by design! @@ -691,6 +711,11 @@ To connect, please ask your contact to create another connection link and check You No comment provided by engineer. + + You accepted connection + You accepted connection + No comment provided by engineer. + You are already connected to %@ via this link. You are already connected to %@ via this link. @@ -726,6 +751,11 @@ To connect, please ask your contact to create another connection link and check You control your chat! No comment provided by engineer. + + You invited your contact + You invited your contact + No comment provided by engineer. + You will be connected when your connection request is accepted, please wait or check later! You will be connected when your connection request is accepted, please wait or check later! @@ -830,11 +860,31 @@ SimpleX servers cannot see your profile. connect to SimpleX Chat developers. No comment provided by engineer. + + connecting… + connecting… + chat list item title + + + connection established + connection established + chat list item title (it should not be shown + + + connection:%@ + connection:%@ + connection information + deleted deleted deleted chat item + + invited to connect + invited to connect + chat list item title + italic italic @@ -855,11 +905,26 @@ SimpleX servers cannot see your profile. v%@ (%@) No comment provided by engineer. + + via contact address link + via contact address link + chat list item description + + + via one-time link + via one-time link + chat list item description + wants to connect to you! wants to connect to you! No comment provided by engineer. + + you shared one-time link + you shared one-time link + chat list item description + ~strike~ ~strike~ diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 643507ec7..db6e5fbd4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -340,6 +340,11 @@ Удалить соединение No comment provided by engineer. + + Delete pending connection? + Удалить ожидаемое соединение? + No comment provided by engineer. + Develop Для разработчиков @@ -596,6 +601,11 @@ to scan from the app Покажите QR код вашему контакту для сканирования в приложении No comment provided by engineer. + + Show pending connections + Показать ожидаемые соединения + No comment provided by engineer. + Take picture Сделать фото @@ -623,6 +633,16 @@ to scan from the app *Обратите внимание*: если вы подтвердите, токен вашего устройства будет послан на сервер 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! Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность. @@ -690,6 +710,11 @@ To connect, please ask your contact to create another connection link and check Вы No comment provided by engineer. + + You accepted connection + Вы приняли приглашение соединиться + No comment provided by engineer. + You are already connected to %@ via this link. Вы уже соединены с %@ через эту ссылку. @@ -725,6 +750,11 @@ To connect, please ask your contact to create another connection link and check Вы котролируете Ваш чат! No comment provided by engineer. + + You invited your contact + Вы пригласили ваш контакт + No comment provided by engineer. + You will be connected when your connection request is accepted, please wait or check later! Соединение будет установлено, когда ваш запрос будет принят. Пожалуйста, подождите или проверьте позже! @@ -829,11 +859,31 @@ SimpleX серверы не могут получить доступ к ваше соединитесь с разработчиками. No comment provided by engineer. + + connecting… + соединяется… + chat list item title + + + connection established + соединение установлено + chat list item title (it should not be shown + + + connection:%@ + connection:%@ + connection information + deleted удалено deleted chat item + + invited to connect + приглашение соединиться + chat list item title + italic курсив @@ -854,11 +904,26 @@ SimpleX серверы не могут получить доступ к ваше v%@ (%@) No comment provided by engineer. + + via contact address link + через ссылку-контакт + chat list item description + + + via one-time link + через одноразовую ссылку + chat list item description + wants to connect to you! хочет соединиться с вами! No comment provided by engineer. + + you shared one-time link + вы создали одноразовую ссылку + chat list item description + ~strike~ \~зачеркнуть~ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 1752f798e..0df64ae7b 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -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!" = "Соединение будет установлено, когда ваш запрос будет принят. Пожалуйста, подождите или проверьте позже!"; diff --git a/scripts/android/prepare.sh b/scripts/android/prepare.sh new file mode 100755 index 000000000..363d7f7ed --- /dev/null +++ b/scripts/android/prepare.sh @@ -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/