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 c75149437c..bf3fec663c 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
@@ -1442,6 +1442,9 @@ enum class FormatColor(val color: String) {
@Serializable
class SndFileTransfer() {}
+@Serializable
+class RcvFileTransfer() {}
+
@Serializable
class FileTransferMeta() {}
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 72aa94529c..8e5ad23e3a 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
@@ -385,9 +385,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiSendMessage(type: ChatType, id: Long, file: String? = null, quotedItemId: Long? = null, mc: MsgContent): AChatItem? {
val cmd = CC.ApiSendMessage(type, id, file, quotedItemId, mc)
val r = sendCmd(cmd)
- if (r is CR.NewChatItem ) return r.chatItem
- Log.e(TAG, "apiSendMessage bad response: ${r.responseType} ${r.details}")
- return null
+ return when (r) {
+ is CR.NewChatItem -> r.chatItem
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiSendMessage", generalGetString(R.string.error_sending_message), r)
+ }
+ null
+ }
+ }
}
suspend fun apiUpdateChatItem(type: ChatType, id: Long, itemId: Long, mc: MsgContent): AChatItem? {
@@ -475,9 +481,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiAddContact(): String? {
val r = sendCmd(CC.AddContact())
- if (r is CR.Invitation) return r.connReqInvitation
- Log.e(TAG, "apiAddContact bad response: ${r.responseType} ${r.details}")
- return null
+ return when (r) {
+ is CR.Invitation -> r.connReqInvitation
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiAddContact", generalGetString(R.string.connection_error), r)
+ }
+ null
+ }
+ }
}
suspend fun apiConnect(connReq: String): Boolean {
@@ -509,7 +521,9 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
return false
}
else -> {
- apiErrorAlert("apiConnect", "Connection error", r)
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiConnect", generalGetString(R.string.connection_error), r)
+ }
return false
}
}
@@ -526,7 +540,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.cannot_delete_contact),
- String.format(generalGetString(R.string.contact_cannot_be_deleted_as_they_are_in_groups), e.errorType.contact.displayName, e.errorType.groupNames)
+ String.format(generalGetString(R.string.contact_cannot_be_deleted_as_they_are_in_groups), e.errorType.contact.displayName, e.errorType.groupNames.joinToString(", "))
)
}
}
@@ -581,9 +595,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiCreateUserAddress(): String? {
val r = sendCmd(CC.CreateMyAddress())
- if (r is CR.UserContactLinkCreated) return r.connReqContact
- Log.e(TAG, "apiCreateUserAddress bad response: ${r.responseType} ${r.details}")
- return null
+ return when (r) {
+ is CR.UserContactLinkCreated -> r.connReqContact
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiCreateUserAddress", generalGetString(R.string.error_creating_address), r)
+ }
+ null
+ }
+ }
}
suspend fun apiDeleteUserAddress(): Boolean {
@@ -606,9 +626,24 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiAcceptContactRequest(contactReqId: Long): Contact? {
val r = sendCmd(CC.ApiAcceptContact(contactReqId))
- if (r is CR.AcceptingContactRequest) return r.contact
- Log.e(TAG, "apiAcceptContactRequest bad response: ${r.responseType} ${r.details}")
- return null
+ return when {
+ r is CR.AcceptingContactRequest -> r.contact
+ r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
+ && r.chatError.agentError is AgentErrorType.SMP
+ && r.chatError.agentError.smpErr is SMPErrorType.AUTH -> {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(R.string.connection_error_auth),
+ generalGetString(R.string.sender_may_have_deleted_the_connection_request)
+ )
+ null
+ }
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiAcceptContactRequest", generalGetString(R.string.error_accepting_contact_request), r)
+ }
+ null
+ }
+ }
}
suspend fun apiRejectContactRequest(contactReqId: Long): Boolean {
@@ -666,9 +701,22 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiReceiveFile(fileId: Long): AChatItem? {
val r = sendCmd(CC.ReceiveFile(fileId))
- if (r is CR.RcvFileAccepted) return r.chatItem
- Log.e(TAG, "apiReceiveFile bad response: ${r.responseType} ${r.details}")
- return null
+ return when (r) {
+ is CR.RcvFileAccepted -> r.chatItem
+ is CR.RcvFileAcceptedSndCancelled -> {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(R.string.cannot_receive_file),
+ generalGetString(R.string.sender_cancelled_file_transfer)
+ )
+ null
+ }
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiReceiveFile", generalGetString(R.string.error_receiving_file), r)
+ }
+ null
+ }
+ }
}
suspend fun apiNewGroup(p: GroupProfile): GroupInfo? {
@@ -680,9 +728,15 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun apiAddMember(groupId: Long, contactId: Long, memberRole: GroupMemberRole): GroupMember? {
val r = sendCmd(CC.ApiAddMember(groupId, contactId, memberRole))
- if (r is CR.SentGroupInvitation) return r.member
- Log.e(TAG, "apiAddMember bad response: ${r.responseType} ${r.details}")
- return null
+ return when (r) {
+ is CR.SentGroupInvitation -> r.member
+ else -> {
+ if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiAddMember", generalGetString(R.string.error_adding_members), r)
+ }
+ null
+ }
+ }
}
suspend fun apiJoinGroup(groupId: Long) {
@@ -699,11 +753,11 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
} else if (e is ChatError.ChatErrorStore && e.storeError is StoreError.GroupNotFound) {
deleteGroup()
AlertManager.shared.showAlertMsg(generalGetString(R.string.alert_title_no_group), generalGetString(R.string.alert_message_no_group))
- } else {
- AlertManager.shared.showAlertMsg(generalGetString(R.string.alert_title_join_group_error), "$e")
+ } else if (!(networkErrorAlert(r))) {
+ apiErrorAlert("apiJoinGroup", generalGetString(R.string.error_joining_group), r)
}
}
- else -> Log.e(TAG, "apiJoinGroup bad response: ${r.responseType} ${r.details}")
+ else -> apiErrorAlert("apiJoinGroup", generalGetString(R.string.error_joining_group), r)
}
}
@@ -746,6 +800,30 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
}
+ private fun networkErrorAlert(r: CR): Boolean {
+ return when {
+ r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
+ && r.chatError.agentError is AgentErrorType.BROKER
+ && r.chatError.agentError.brokerErr is BrokerErrorType.TIMEOUT -> {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(R.string.connection_timeout),
+ generalGetString(R.string.network_error_desc)
+ )
+ true
+ }
+ r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
+ && r.chatError.agentError is AgentErrorType.BROKER
+ && r.chatError.agentError.brokerErr is BrokerErrorType.NETWORK -> {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(R.string.connection_error),
+ generalGetString(R.string.network_error_desc)
+ )
+ true
+ }
+ else -> false
+ }
+ }
+
fun apiErrorAlert(method: String, title: String, r: CR) {
val errMsg = "${r.responseType}: ${r.details}"
Log.e(TAG, "$method bad response: $errMsg")
@@ -1592,6 +1670,7 @@ sealed class CR {
@Serializable @SerialName("groupUpdated") class GroupUpdated(val toGroup: GroupInfo): CR()
// receiving file events
@Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val chatItem: AChatItem): CR()
+ @Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileStart") class RcvFileStart(val chatItem: AChatItem): CR()
@Serializable @SerialName("rcvFileComplete") class RcvFileComplete(val chatItem: AChatItem): CR()
// sending file events
@@ -1676,6 +1755,7 @@ sealed class CR {
is ConnectedToGroupMember -> "connectedToGroupMember"
is GroupRemoved -> "groupRemoved"
is GroupUpdated -> "groupUpdated"
+ is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled"
is RcvFileAccepted -> "rcvFileAccepted"
is RcvFileStart -> "rcvFileStart"
is RcvFileComplete -> "rcvFileComplete"
@@ -1761,6 +1841,7 @@ sealed class CR {
is ConnectedToGroupMember -> "groupInfo: $groupInfo\nmember: $member"
is GroupRemoved -> json.encodeToString(groupInfo)
is GroupUpdated -> json.encodeToString(toGroup)
+ is RcvFileAcceptedSndCancelled -> noDetails()
is RcvFileAccepted -> json.encodeToString(chatItem)
is RcvFileStart -> json.encodeToString(chatItem)
is RcvFileComplete -> json.encodeToString(chatItem)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
index 252543d150..cfe22f96b8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt
@@ -43,10 +43,12 @@ fun AddGroupMembersView(groupInfo: GroupInfo, chatModel: ChatModel, close: () ->
selectedRole = selectedRole,
inviteMembers = {
withApi {
- selectedContacts.forEach {
- val member = chatModel.controller.apiAddMember(groupInfo.groupId, it, selectedRole.value)
+ for (contactId in selectedContacts) {
+ val member = chatModel.controller.apiAddMember(groupInfo.groupId, contactId, selectedRole.value)
if (member != null) {
chatModel.upsertGroupMember(groupInfo, member)
+ } else {
+ break
}
}
close.invoke()
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 1879d9c5b2..f61d65aa29 100644
--- a/apps/android/app/src/main/res/values-ru/strings.xml
+++ b/apps/android/app/src/main/res/values-ru/strings.xml
@@ -43,12 +43,24 @@
Ошибка при сохранении настроек сети
+ Превышено время соединения
+ Ошибка соединения
+ Пожалуйста, проверьте ваше соединение с сетью и попробуйте еще раз.
+ Ошибка при отправке сообщения
+ Ошибка при добавлении членов группы
+ Ошибка при вступлении в группу
+ Невозможно получить файл
+ Отправитель отменил передачу файла.
+ Ошибка при получении файла
+ Ошибка при создании адресаСуществующий контактВы уже соединены с %1$s! через эту ссылку.Ошибка в ссылке контактаПожалуйста, проверьте, что вы использовали правильную ссылку, или попросите ваш контакт отправить вам новую.Ошибка соединения (AUTH)Возможно, ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите ваш контакт создать еще одну ссылку и проверьте ваше соединение с сетью.
+ Ошибка при принятии запроса на соединение
+ Отправитель мог удалить запрос на соединение.Невозможно удалить контакт!Контакт %1$s! не может быть удален, так как является членом групп(ы) %2$s.Ошибка удаления контакта
@@ -645,7 +657,6 @@
Приглашение в группу больше не действительно, оно было удалено отправителем.Группа не найдена!Эта группа больше не существует.
- Ошибка приглашенияНельзя пригласить контакты!Вы используете инкогнито профиль для этой группы - чтобы предотвратить раскрытие вашего основного профиля, приглашать контакты не разрешено
diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml
index b415efd011..999d7cc274 100644
--- a/apps/android/app/src/main/res/values/strings.xml
+++ b/apps/android/app/src/main/res/values/strings.xml
@@ -43,12 +43,24 @@
Error updating network configuration
+ Connection timeout
+ Connection error
+ Please check your network connection and try again.
+ Error sending message
+ Error adding member(s)
+ Error joining group
+ Cannot receive file
+ Sender cancelled file transfer.Error receiving file
+ Error receiving file
+ Error creating addressContact already existsYou are already connected to %1$s! via this link.Invalid connection linkPlease check that you used the correct link or ask your contact to send you another one.Connection error (AUTH)Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection.
+ Error accepting contact request
+ Sender may have deleted the connection request.Can\'t delete contact!Contact %1$s! cannot be deleted, they are a member of the group(s) %2$s.Error deleting contact
@@ -646,7 +658,6 @@
Group invitation is no longer valid, it was removed by sender.Group not found!This group no longer exists.
- Error joining groupCan\'t invite contacts!You\'re using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed
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 a00d7e7301..4043d0d87a 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -988,7 +988,7 @@
Error joining group
- Ошибка приглашения
+ Ошибка при вступлении в группуNo comment provided by engineer.
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 7ab0a64c99..5535d5647c 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -696,7 +696,7 @@
"Error importing chat database" = "Ошибка при импорте архива чата";
/* No comment provided by engineer. */
-"Error joining group" = "Ошибка приглашения";
+"Error joining group" = "Ошибка при вступлении в группу";
/* No comment provided by engineer. */
"Error receiving file" = "Ошибка при получении файла";