From 193287377635fd3ffd2066337d790126174fb8d4 Mon Sep 17 00:00:00 2001 From: IanRDavies Date: Fri, 22 Apr 2022 17:26:17 +0100 Subject: [PATCH] Show pending contacts (#547) * capture contact connecting event * disable navigation to not ready chat * update "pending contact" alert, Russian translations Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../java/chat/simplex/app/model/ChatModel.kt | 2 +- .../java/chat/simplex/app/model/SimpleXAPI.kt | 7 ++- .../app/views/chatlist/ChatListNavLinkView.kt | 24 ++++++++- .../app/views/chatlist/ChatPreviewView.kt | 28 ++++++----- .../app/src/main/res/values-ru/strings.xml | 5 ++ .../app/src/main/res/values/strings.xml | 7 ++- apps/ios/Shared/Model/ChatModel.swift | 2 +- apps/ios/Shared/Model/SimpleXAPI.swift | 5 ++ .../Views/ChatList/ChatListNavLink.swift | 46 ++++++++++++++++- .../en.xcloc/Localized Contents/en.xliff | 49 +++++++++++++++++++ .../ru.xcloc/Localized Contents/ru.xliff | 49 +++++++++++++++++++ apps/ios/ru.lproj/Localizable.strings | 27 ++++++++++ 12 files changed, 233 insertions(+), 18 deletions(-) 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 eec3f1d9ff..6f3376cd29 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 @@ -357,7 +357,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" || activeConn.connStatus == "snd-ready" + override val ready get() = activeConn.connStatus == "ready" override val displayName get() = profile.displayName override val fullName get() = profile.fullName override val image get() = profile.image 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 14ed6cd63e..bc126724b1 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 @@ -11,7 +11,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Bolt import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier -import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.simplex.app.* @@ -339,6 +338,9 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected()) // NtfManager.shared.notifyContactConnected(contact) } + is CR.ContactConnecting -> { + chatModel.updateContact(r.contact) + } is CR.ReceivedContactRequest -> { val contactRequest = r.contactRequest val cInfo = ChatInfo.ContactRequest(contactRequest) @@ -642,6 +644,7 @@ sealed class CR { @Serializable @SerialName("userContactLinkCreated") class UserContactLinkCreated(val connReqContact: String): CR() @Serializable @SerialName("userContactLinkDeleted") class UserContactLinkDeleted: CR() @Serializable @SerialName("contactConnected") class ContactConnected(val contact: Contact): CR() + @Serializable @SerialName("contactConnecting") class ContactConnecting(val contact: Contact): CR() @Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val contactRequest: UserContactRequest): CR() @Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val contact: Contact): CR() @Serializable @SerialName("contactRequestRejected") class ContactRequestRejected: CR() @@ -685,6 +688,7 @@ sealed class CR { is UserContactLinkCreated -> "userContactLinkCreated" is UserContactLinkDeleted -> "userContactLinkDeleted" is ContactConnected -> "contactConnected" + is ContactConnecting -> "contactConnecting" is ReceivedContactRequest -> "receivedContactRequest" is AcceptingContactRequest -> "acceptingContactRequest" is ContactRequestRejected -> "contactRequestRejected" @@ -729,6 +733,7 @@ sealed class CR { is UserContactLinkCreated -> connReqContact is UserContactLinkDeleted -> noDetails() is ContactConnected -> json.encodeToString(contact) + is ContactConnecting -> json.encodeToString(contact) is ReceivedContactRequest -> json.encodeToString(contactRequest) is AcceptingContactRequest -> json.encodeToString(contact) is ContactRequestRejected -> noDetails() 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 f707e62c8b..b764c8d5a4 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 @@ -25,7 +25,11 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { if (chat.chatInfo is ChatInfo.ContactRequest) { contactRequestAlertDialog(chat.chatInfo, chatModel) } else { - withApi { openChat(chatModel, chat.chatInfo) } + if (chat.chatInfo.ready) { + withApi { openChat(chatModel, chat.chatInfo) } + } else { + pendingConnectionAlertDialog(chat.chatInfo, chatModel) + } } } ) @@ -63,6 +67,24 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel ) } +fun pendingConnectionAlertDialog(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), + confirmText = generalGetString(R.string.button_delete_contact), + onConfirm = { + withApi { + val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId) + if (r) { + chatModel.removeChat(chatInfo.id) + chatModel.chatId.value = null + } + } + }, + dismissText = generalGetString(R.string.cancel_verb), + ) +} + @Composable fun ChatListNavLinkLayout(chat: Chat, click: () -> Unit) { Surface( 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 3c9a4158b2..f71a0ff00b 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 @@ -9,6 +9,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -17,8 +18,7 @@ import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.Chat import chat.simplex.app.model.getTimestampText -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimpleXTheme +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.* @@ -36,17 +36,21 @@ fun ChatPreviewView(chat: Chat) { maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h3, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + color = if (chat.chatInfo.ready) Color.Unspecified else HighOrLowlight ) - - val ci = chat.chatItems.lastOrNull() - if (ci != null) { - MarkdownText( - ci.text, ci.formattedText, ci.memberDisplayName, - metaText = ci.timestampText, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) + if (chat.chatInfo.ready) { + val ci = chat.chatItems.lastOrNull() + if (ci != null) { + MarkdownText( + ci.text, ci.formattedText, ci.memberDisplayName, + metaText = ci.timestampText, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } else { + Text(generalGetString(R.string.contact_connection_pending), color = HighOrLowlight) } } val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt) 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 ddabcdea50..f4a36949b7 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -71,6 +71,7 @@ Здравствуйте! Этот текст можно найти в Настройках Ваши чаты + соединяется… Прикрепить @@ -129,6 +130,10 @@ Принять Отклонить + + Соединение еще не установлено! + Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой). + хочет соединиться с вами! diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 30ed858e2c..cbc0bd7183 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ Welcome! This text is available in settings Your chats + connecting… Attach @@ -123,12 +124,16 @@ 💻 desktop: scan displayed QR code from the app, via Scan QR code. 📱 mobile: tap Open in mobile app, then tap Connect in the app. - + Accept connection request? If you choose to reject sender will NOT be notified. Accept Reject + + 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). + wants to connect to you! diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 776692b913..a2846d825a 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -455,7 +455,7 @@ struct Contact: Identifiable, Decodable, NamedChat { var id: ChatId { get { "@\(contactId)" } } var apiId: Int64 { get { contactId } } - var ready: Bool { get { activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready" } } + var ready: Bool { get { activeConn.connStatus == "ready" } } var displayName: String { get { profile.displayName } } var fullName: String { get { profile.fullName } } var image: String? { get { profile.image } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 4bf008600a..e62b7e6d21 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -154,6 +154,7 @@ enum ChatResponse: Decodable, Error { case userContactLinkCreated(connReqContact: String) case userContactLinkDeleted case contactConnected(contact: Contact) + case contactConnecting(contact: Contact) case receivedContactRequest(contactRequest: UserContactRequest) case acceptingContactRequest(contact: Contact) case contactRequestRejected @@ -198,6 +199,7 @@ enum ChatResponse: Decodable, Error { case .userContactLinkCreated: return "userContactLinkCreated" case .userContactLinkDeleted: return "userContactLinkDeleted" case .contactConnected: return "contactConnected" + case .contactConnecting: return "contactConnecting" case .receivedContactRequest: return "receivedContactRequest" case .acceptingContactRequest: return "acceptingContactRequest" case .contactRequestRejected: return "contactRequestRejected" @@ -245,6 +247,7 @@ enum ChatResponse: Decodable, Error { case let .userContactLinkCreated(connReq): return connReq case .userContactLinkDeleted: return noDetails case let .contactConnected(contact): return String(describing: contact) + case let .contactConnecting(contact): return String(describing: contact) case let .receivedContactRequest(contactRequest): return String(describing: contactRequest) case let .acceptingContactRequest(contact): return String(describing: contact) case .contactRequestRejected: return noDetails @@ -702,6 +705,8 @@ func processReceivedMsg(_ res: ChatResponse) { chatModel.updateContact(contact) chatModel.updateNetworkStatus(contact, .connected) NtfManager.shared.notifyContactConnected(contact) + case let .contactConnecting(contact): + chatModel.updateContact(contact) case let .receivedContactRequest(contactRequest): chatModel.addChat(Chat( chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest), diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index d25ffafe53..6689d8f5db 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -53,11 +53,20 @@ struct ChatListNavLink: View { } .swipeActions(edge: .trailing) { Button(role: .destructive) { - AlertManager.shared.showAlert(deleteContactAlert(contact)) + AlertManager.shared.showAlert( + contact.ready + ? deleteContactAlert(contact) + : deletePendingContactAlert(chat, contact) + ) } label: { Label("Delete", systemImage: "trash") } } + .onTapGesture { + if !contact.ready { + AlertManager.shared.showAlert(pendingContactAlert(chat, contact)) + } + } .frame(height: 80) } @@ -150,6 +159,41 @@ struct ChatListNavLink: View { secondaryButton: .cancel() ) } + + private func pendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert { + Alert( + title: Text("Contact is not connected yet!"), + message: Text("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)."), + primaryButton: .cancel(), + secondaryButton: .destructive(Text("Delete Contact")) { + removePendingContact(chat, contact) + } + ) + } + + private func deletePendingContactAlert(_ chat: Chat, _ contact: Contact) -> Alert { + Alert( + title: Text("Delete pending connection"), + message: Text("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)."), + primaryButton: .destructive(Text("Delete")) { + removePendingContact(chat, contact) + }, + secondaryButton: .cancel() + ) + } + + private func removePendingContact(_ chat: Chat, _ contact: Contact) { + Task { + do { + try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId) + DispatchQueue.main.async { + chatModel.removeChat(contact.id) + } + } catch let error { + logger.error("ChatListNavLink.removePendingContact apiDeleteChat error: \(responseError(error))") + } + } + } } struct ChatListNavLink_Previews: PreviewProvider { 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 3c41816622..a76bc6a379 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -145,6 +145,11 @@ Chats back button to return to chats list + + Check messages + Check messages + No comment provided by engineer. + Choose from library Choose from library @@ -230,6 +235,11 @@ Contact is connected notification + + Contact is not connected yet! + Contact is not connected yet! + No comment provided by engineer. + Copy Copy @@ -260,6 +270,11 @@ Delete No comment provided by engineer. + + Delete Contact + Delete Contact + No comment provided by engineer. + Delete address Delete address @@ -300,6 +315,11 @@ Delete message? No comment provided by engineer. + + Delete pending connection + Delete pending connection + No comment provided by engineer. + Develop Develop @@ -315,11 +335,26 @@ Edit No comment provided by engineer. + + Enable notifications? (BETA) + Enable notifications? (BETA) + No comment provided by engineer. + Enter one SMP server per line: Enter one SMP server per line: No comment provided by engineer. + + Error deleting token + Error deleting token + No comment provided by engineer. + + + Error registering token + Error registering token + No comment provided by engineer. + Error saving SMP servers Error saving SMP servers @@ -552,6 +587,13 @@ to scan from the app The app can notify you when you receive messages or contact requests - please open settings to enable. No comment provided by engineer. + + The app can receive background notifications every 20 minutes to check the new messages. +*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server. + The app can receive background notifications every 20 minutes to check the new messages. +*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server. + No comment provided by engineer. + The messaging and application platform 100% private by design! The messaging and application platform 100% private by design! @@ -689,6 +731,13 @@ To connect, please ask your contact to create another connection link and check Your chats No comment provided by engineer. + + Your contact needs to be online for the connection to complete. +You can cancel this connection and remove the contact (and try later with a new link). + Your contact needs to be online for the connection to complete. +You can cancel this connection and remove the contact (and try later with a new link). + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. 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 377d0b05ec..60d98f3542 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -145,6 +145,11 @@ Назад back button to return to chats list + + Check messages + Проверять сообщения + No comment provided by engineer. + Choose from library Выбрать из библиотеки @@ -230,6 +235,11 @@ Соединение с контактом установлено notification + + Contact is not connected yet! + Соединение еще не установлено! + No comment provided by engineer. + Copy Скопировать @@ -260,6 +270,11 @@ Удалить No comment provided by engineer. + + Delete Contact + Удалить контакт + No comment provided by engineer. + Delete address Удалить адрес @@ -300,6 +315,11 @@ Удалить сообщение? No comment provided by engineer. + + Delete pending connection + Удалить соединение + No comment provided by engineer. + Develop Для разработчиков @@ -315,11 +335,26 @@ Редактировать No comment provided by engineer. + + Enable notifications? (BETA) + Включить уведомления? (БЕТА) + No comment provided by engineer. + Enter one SMP server per line: Введите SMP серверы, каждый на отдельной строке: No comment provided by engineer. + + Error deleting token + Ошибка удаления токена + No comment provided by engineer. + + + Error registering token + Ошибка регистрации токена + No comment provided by engineer. + Error saving SMP servers Ошибка при сохранении SMP серверов @@ -551,6 +586,13 @@ to scan from the app Приложение может посылать вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. No comment provided by engineer. + + The app can receive background notifications every 20 minutes to check the new messages. +*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server. + Приложение может получать скрытые уведомления каждые 20 минут чтобы проверить новые сообщения. +*Обратите внимание*: если вы подтвердите, токен вашего устройства будет послан на сервер SimpleX Chat. + No comment provided by engineer. + The messaging and application platform 100% private by design! Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность. @@ -688,6 +730,13 @@ To connect, please ask your contact to create another connection link and check Ваши чаты No comment provided by engineer. + + Your contact needs to be online for the connection to complete. +You can cancel this connection and remove the contact (and try later with a new link). + Ваш контакт должен быть в сети чтобы установить соединение. +Вы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой). + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index e5ec985260..30fdf466bd 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -106,6 +106,9 @@ /* back button to return to chats list */ "Chats" = "Назад"; +/* No comment provided by engineer. */ +"Check messages" = "Проверять сообщения"; + /* No comment provided by engineer. */ "Choose from library" = "Выбрать из библиотеки"; @@ -163,6 +166,9 @@ /* notification */ "Contact is connected" = "Соединение с контактом установлено"; +/* No comment provided by engineer. */ +"Contact is not connected yet!" = "Соединение еще не установлено!"; + /* No comment provided by engineer. */ "Copy" = "Скопировать"; @@ -190,6 +196,9 @@ /* No comment provided by engineer. */ "Delete contact" = "Удалить контакт"; +/* No comment provided by engineer. */ +"Delete Contact" = "Удалить контакт"; + /* No comment provided by engineer. */ "Delete contact?" = "Удалить контакт?"; @@ -205,6 +214,9 @@ /* No comment provided by engineer. */ "Delete message?" = "Удалить сообщение?"; +/* No comment provided by engineer. */ +"Delete pending connection" = "Удалить соединение"; + /* deleted chat item */ "deleted" = "удалено"; @@ -217,9 +229,18 @@ /* No comment provided by engineer. */ "Edit" = "Редактировать"; +/* No comment provided by engineer. */ +"Enable notifications? (BETA)" = "Включить уведомления? (БЕТА)"; + /* No comment provided by engineer. */ "Enter one SMP server per line:" = "Введите SMP серверы, каждый на отдельной строке:"; +/* No comment provided by engineer. */ +"Error deleting token" = "Ошибка удаления токена"; + +/* No comment provided by engineer. */ +"Error registering token" = "Ошибка регистрации токена"; + /* No comment provided by engineer. */ "Error saving SMP servers" = "Ошибка при сохранении SMP серверов"; @@ -367,6 +388,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; +/* 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 messaging and application platform 100% private by design!" = "Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность."; @@ -448,6 +472,9 @@ /* No comment provided by engineer. */ "Your chats" = "Ваши чаты"; +/* No comment provided by engineer. */ +"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)." = "Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой)."; + /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.\nSimpleX серверы не могут получить доступ к вашему профилю.";