diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 2ac9d51e9c..12cab783a1 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -802,33 +802,41 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat)) } -func receiveFile(user: User, fileId: Int64) async { - if let chatItem = await apiReceiveFile(fileId: fileId) { +func receiveFile(user: User, fileId: Int64, auto: Bool = false) async { + if let chatItem = await apiReceiveFile(fileId: fileId, auto: auto) { DispatchQueue.main.async { chatItemSimpleUpdate(user, chatItem) } } } -func apiReceiveFile(fileId: Int64, inline: Bool? = nil) async -> AChatItem? { +func apiReceiveFile(fileId: Int64, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { let r = await chatSendCmd(.receiveFile(fileId: fileId, inline: inline)) let am = AlertManager.shared if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } if case .rcvFileAcceptedSndCancelled = r { - am.showAlertMsg( - title: "Cannot receive file", - message: "Sender cancelled file transfer." - ) + logger.debug("apiReceiveFile error: sender cancelled file transfer") + if !auto { + am.showAlertMsg( + title: "Cannot receive file", + message: "Sender cancelled file transfer." + ) + } } else if let networkErrorAlert = networkErrorAlert(r) { - am.showAlert(networkErrorAlert) + logger.error("apiReceiveFile network error: \(String(describing: r))") + if !auto { + am.showAlert(networkErrorAlert) + } } else { - logger.error("apiReceiveFile error: \(String(describing: r))") switch r { case .chatCmdError(_, .error(.fileAlreadyReceiving)): logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") default: - am.showAlertMsg( - title: "Error receiving file", - message: "Error: \(String(describing: r))" - ) + logger.error("apiReceiveFile error: \(String(describing: r))") + if !auto { + am.showAlertMsg( + title: "Error receiving file", + message: "Error: \(String(describing: r))" + ) + } } } return nil @@ -1323,7 +1331,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } if let file = cItem.autoReceiveFile() { Task { - await receiveFile(user: user, fileId: file.fileId) + await receiveFile(user: user, fileId: file.fileId, auto: true) } } if cItem.showNotification { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 4387614918..f9db2cf777 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -247,10 +247,10 @@ struct CIVideoView: View { .padding([.trailing, .top], 11) } - private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64) async -> Void) { + private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool) async -> Void) { Task { if let user = ChatModel.shared.currentUser { - await receiveFile(user, file.fileId) + await receiveFile(user, file.fileId, false) } } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index ab916db9ad..3ac908bb78 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -87,7 +87,7 @@ struct MsgContentView: View { func messageText(_ text: String, _ formattedText: [FormattedText]?, _ sender: String?, icon: String? = nil, preview: Bool = false) -> Text { let s = text var res: Text - if let ft = formattedText, ft.count > 0 { + if let ft = formattedText, ft.count > 0 && ft.count <= 200 { res = formatText(ft[0], preview) var i = 1 while i < ft.count { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 37e00863fe..47a2131d30 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -28,6 +28,7 @@ struct GroupMemberInfoView: View { case switchAddressAlert case abortSwitchAddressAlert case syncConnectionForceAlert + case connectViaMemberAddressAlert(contactLink: String) case connRequestSentAlert(type: ConnReqType) case error(title: LocalizedStringKey, error: LocalizedStringKey) case other(alert: Alert) @@ -38,8 +39,9 @@ struct GroupMemberInfoView: View { case let .changeMemberRoleAlert(_, role): return "changeMemberRoleAlert \(role.rawValue)" case .switchAddressAlert: return "switchAddressAlert" case .abortSwitchAddressAlert: return "abortSwitchAddressAlert" - case .connRequestSentAlert: return "connRequestSentAlert" case .syncConnectionForceAlert: return "syncConnectionForceAlert" + case .connectViaMemberAddressAlert: return "connectViaMemberAddressAlert" + case .connRequestSentAlert: return "connRequestSentAlert" case let .error(title, _): return "error \(title)" case let .other(alert): return "other \(alert)" } @@ -201,6 +203,7 @@ struct GroupMemberInfoView: View { case .switchAddressAlert: return switchAddressAlert(switchMemberAddress) case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress) case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) }) + case let .connectViaMemberAddressAlert(contactLink): return connectViaMemberAddressAlert(contactLink) case let .connRequestSentAlert(type): return connReqSentAlert(type) case let .error(title, error): return Alert(title: Text(title), message: Text(error)) case let .other(alert): return alert @@ -210,12 +213,23 @@ struct GroupMemberInfoView: View { func connectViaAddressButton(_ contactLink: String) -> some View { Button { - connectViaAddress(contactLink) + alert = .connectViaMemberAddressAlert(contactLink: contactLink) } label: { Label("Connect", systemImage: "link") } } + func connectViaMemberAddressAlert(_ contactLink: String) -> Alert { + return Alert( + title: Text("Connect directly?"), + message: Text("Сonnection request will be sent to this group member."), + primaryButton: .default(Text("Connect")) { + connectViaAddress(contactLink) + }, + secondaryButton: .cancel() + ) + } + func connectViaAddress(_ contactLink: String) { Task { let (connReqType, connectAlert) = await apiConnect_(connReq: contactLink) diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index a26d7bb41d..2caca119d4 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -206,7 +206,7 @@ public func responseError(_ err: Error) -> String { switch r { case let .chatCmdError(_, chatError): return chatErrorString(chatError) case let .chatError(_, chatError): return chatErrorString(chatError) - default: return String(describing: r) + default: return "\(String(describing: r.responseType)), details: \(String(describing: r.details))" } } else { return String(describing: err) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 5658d7a091..4e37c368ce 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -452,6 +452,7 @@ public enum ChatResponse: Decodable, Error { case sentConfirmation(user: User) case sentInvitation(user: User) case contactAlreadyExists(user: User, contact: Contact) + case contactRequestAlreadyAccepted(user: User, contact: Contact) case contactDeleted(user: User, contact: Contact) case chatCleared(user: User, chatInfo: ChatInfo) case userProfileNoChange(user: User) @@ -481,6 +482,7 @@ public enum ChatResponse: Decodable, Error { case newChatItem(user: User, chatItem: AChatItem) case chatItemStatusUpdated(user: User, chatItem: AChatItem) case chatItemUpdated(user: User, chatItem: AChatItem) + case chatItemNotChanged(user: User, chatItem: AChatItem) case chatItemReaction(user: User, added: Bool, reaction: ACIReaction) case chatItemDeleted(user: User, deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool) case contactsList(user: User, contacts: [Contact]) @@ -583,6 +585,7 @@ public enum ChatResponse: Decodable, Error { case .sentConfirmation: return "sentConfirmation" case .sentInvitation: return "sentInvitation" case .contactAlreadyExists: return "contactAlreadyExists" + case .contactRequestAlreadyAccepted: return "contactRequestAlreadyAccepted" case .contactDeleted: return "contactDeleted" case .chatCleared: return "chatCleared" case .userProfileNoChange: return "userProfileNoChange" @@ -612,6 +615,7 @@ public enum ChatResponse: Decodable, Error { case .newChatItem: return "newChatItem" case .chatItemStatusUpdated: return "chatItemStatusUpdated" case .chatItemUpdated: return "chatItemUpdated" + case .chatItemNotChanged: return "chatItemNotChanged" case .chatItemReaction: return "chatItemReaction" case .chatItemDeleted: return "chatItemDeleted" case .contactsList: return "contactsList" @@ -713,6 +717,7 @@ public enum ChatResponse: Decodable, Error { case .sentConfirmation: return noDetails case .sentInvitation: return noDetails case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact)) + case let .contactRequestAlreadyAccepted(u, contact): return withUser(u, String(describing: contact)) case let .contactDeleted(u, contact): return withUser(u, String(describing: contact)) case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo)) case .userProfileNoChange: return noDetails @@ -742,6 +747,7 @@ public enum ChatResponse: Decodable, Error { case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") case let .chatItemDeleted(u, deletedChatItem, toChatItem, byUser): return withUser(u, "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)") case let .contactsList(u, contacts): return withUser(u, String(describing: contacts)) @@ -1367,14 +1373,32 @@ public enum ChatError: Decodable { public enum ChatErrorType: Decodable { case noActiveUser + case noConnectionUser(agentConnId: String) + case noSndFileUser(agentSndFileId: String) + case noRcvFileUser(agentRcvFileId: String) + case userUnknown case activeUserExists case userExists - case differentActiveUser + case differentActiveUser(commandUserId: Int64, activeUserId: Int64) + case cantDeleteActiveUser(userId: Int64) + case cantDeleteLastUser(userId: Int64) + case cantHideLastUser(userId: Int64) + case hiddenUserAlwaysMuted(userId: Int64) + case emptyUserPassword(userId: Int64) + case userAlreadyHidden(userId: Int64) + case userNotHidden(userId: Int64) case chatNotStarted + case chatNotStopped + case chatStoreChanged case invalidConnReq - case invalidChatMessage(message: String) + case invalidChatMessage(connection: Connection, message: String) case contactNotReady(contact: Contact) - case groupUserRole + case contactDisabled(contact: Contact) + case connectionDisabled(connection: Connection) + case groupUserRole(groupInfo: GroupInfo, requiredRole: GroupMemberRole) + case groupMemberInitialRole(groupInfo: GroupInfo, initialRole: GroupMemberRole) + case contactIncognitoCantInvite + case groupIncognitoCantInvite case groupContactRole(contactName: ContactName) case groupDuplicateMember(contactName: ContactName) case groupDuplicateMemberId @@ -1386,23 +1410,49 @@ public enum ChatErrorType: Decodable { case groupCantResendInvitation(groupInfo: GroupInfo, contactName: ContactName) case groupInternal(message: String) case fileNotFound(message: String) + case fileSize(filePath: String) case fileAlreadyReceiving(message: String) + case fileCancelled(message: String) + case fileCancel(fileId: Int64, message: String) case fileAlreadyExists(filePath: String) case fileRead(filePath: String, message: String) case fileWrite(filePath: String, message: String) case fileSend(fileId: Int64, agentError: String) case fileRcvChunk(message: String) case fileInternal(message: String) + case fileImageType(filePath: String) + case fileImageSize(filePath: String) + case fileNotReceived(fileId: Int64) + // case xFTPRcvFile + // case xFTPSndFile + case fallbackToSMPProhibited(fileId: Int64) + case inlineFileProhibited(fileId: Int64) case invalidQuote case invalidChatItemUpdate case invalidChatItemDelete + case hasCurrentCall + case noCurrentCall + case callContact(contactId: Int64) + case callState + case directMessagesProhibited(contact: Contact) case agentVersion + case agentNoSubResult(agentConnId: String) case commandError(message: String) + case serverProtocol + case agentCommandError(message: String) + case invalidFileDescription(message: String) + case internalError(message: String) case exception(message: String) } public enum StoreError: Decodable { case duplicateName + case userNotFound(userId: Int64) + case userNotFoundByName(contactName: ContactName) + case userNotFoundByContactId(contactId: Int64) + case userNotFoundByGroupId(groupId: Int64) + case userNotFoundByFileId(fileId: Int64) + case userNotFoundByContactRequestId(contactRequestId: Int64) case contactNotFound(contactId: Int64) case contactNotFoundByName(contactName: ContactName) case contactNotReady(contactName: ContactName) @@ -1412,6 +1462,9 @@ public enum StoreError: Decodable { case contactRequestNotFoundByName(contactName: ContactName) case groupNotFound(groupId: Int64) case groupNotFoundByName(groupName: GroupName) + case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName) + case groupMemberNotFound(groupMemberId: Int64) + case groupMemberNotFoundByMemberId(memberId: String) case groupWithoutUser case duplicateGroupMember case groupAlreadyJoined @@ -1419,9 +1472,16 @@ public enum StoreError: Decodable { case sndFileNotFound(fileId: Int64) case sndFileInvalid(fileId: Int64) case rcvFileNotFound(fileId: Int64) + case rcvFileDescrNotFound(fileId: Int64) case fileNotFound(fileId: Int64) case rcvFileInvalid(fileId: Int64) + case rcvFileInvalidDescrPart + case sharedMsgIdNotFoundByFileId(fileId: Int64) + case fileIdNotFoundBySharedMsgId(sharedMsgId: String) + case sndFileNotFoundXFTP(agentSndFileId: String) + case rcvFileNotFoundXFTP(agentRcvFileId: String) case connectionNotFound(agentConnId: String) + case connectionNotFoundById(connId: Int64) case pendingConnectionNotFound(connId: Int64) case introNotFound case uniqueID @@ -1429,11 +1489,16 @@ public enum StoreError: Decodable { case noMsgDelivery(connId: Int64, agentMsgId: String) case badChatItem(itemId: Int64) case chatItemNotFound(itemId: Int64) - case quotedChatItemNotFound + case chatItemNotFoundByText(text: String) case chatItemSharedMsgIdNotFound(sharedMsgId: String) case chatItemNotFoundByFileId(fileId: Int64) + case chatItemNotFoundByGroupId(groupId: Int64) + case profileNotFound(profileId: Int64) case duplicateGroupLink(groupInfo: GroupInfo) case groupLinkNotFound(groupInfo: GroupInfo) + case hostMemberIdNotFound(groupId: Int64) + case contactNotFoundByFileId(fileId: Int64) + case noGroupSndStatus(itemId: Int64, groupMemberId: Int64) } public enum DatabaseError: Decodable { @@ -1453,11 +1518,12 @@ public enum AgentErrorType: Decodable { case CMD(cmdErr: CommandErrorType) case CONN(connErr: ConnectionErrorType) case SMP(smpErr: ProtocolErrorType) - case XFTP(xftpErr: XFTPErrorType) case NTF(ntfErr: ProtocolErrorType) + case XFTP(xftpErr: XFTPErrorType) case BROKER(brokerAddress: String, brokerErr: BrokerErrorType) case AGENT(agentErr: SMPAgentError) case INTERNAL(internalErr: String) + case INACTIVE } public enum CommandErrorType: Decodable { @@ -1477,9 +1543,10 @@ public enum ConnectionErrorType: Decodable { } public enum BrokerErrorType: Decodable { - case RESPONSE(smpErr: ProtocolErrorType) + case RESPONSE(smpErr: String) case UNEXPECTED case NETWORK + case HOST case TRANSPORT(transportErr: ProtocolTransportError) case TIMEOUT } @@ -1513,6 +1580,7 @@ public enum XFTPErrorType: Decodable { public enum ProtocolCommandError: Decodable { case UNKNOWN case SYNTAX + case PROHIBITED case NO_AUTH case HAS_AUTH case NO_ENTITY @@ -1535,7 +1603,9 @@ public enum SMPAgentError: Decodable { case A_MESSAGE case A_PROHIBITED case A_VERSION - case A_ENCRYPTION + case A_CRYPTO + case A_DUPLICATE + case A_QUEUE(queueErr: String) } public enum ArchiveError: Decodable { diff --git a/apps/multiplatform/.gitignore b/apps/multiplatform/.gitignore index d8419431db..81d296183d 100644 --- a/apps/multiplatform/.gitignore +++ b/apps/multiplatform/.gitignore @@ -11,7 +11,7 @@ local.properties common/src/commonMain/cpp/android/libs/ common/src/commonMain/cpp/desktop/libs/ -common/src/commonMain/resources/libs/ +desktop/src/jvmMain/resources/libs/ android/build android/release common/build diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 3e83ef76fe..39740d05fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -49,7 +49,7 @@ object ChatModel { val chatDbStatus = mutableStateOf(null) val chats = mutableStateListOf() // map of connections network statuses, key is agent connection id - val networkStatuses = mutableStateMapOf() + val networkStatuses = mutableStateOf>(mapOf()) // current chat val chatId = mutableStateOf(null) @@ -477,11 +477,13 @@ object ChatModel { } fun setContactNetworkStatus(contact: Contact, status: NetworkStatus) { - networkStatuses[contact.activeConn.agentConnId] = status + val statuses = networkStatuses.value.toMutableMap() + statuses[contact.activeConn.agentConnId] = status + networkStatuses.value = statuses } fun contactNetworkStatus(contact: Contact): NetworkStatus = - networkStatuses[contact.activeConn.agentConnId] ?: NetworkStatus.Unknown() + networkStatuses.value[contact.activeConn.agentConnId] ?: NetworkStatus.Unknown() fun addTerminalItem(item: TerminalItem) { if (terminalItems.value.size >= 500) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index ad2dfe47f4..16f8db0bc6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1678,9 +1678,11 @@ object ChatController { } private fun updateContactsStatus(contactRefs: List, status: NetworkStatus) { + val statuses = chatModel.networkStatuses.value.toMutableMap() for (c in contactRefs) { - chatModel.networkStatuses[c.agentConnId] = status + statuses[c.agentConnId] = status } + chatModel.networkStatuses.value = statuses } private fun processContactSubError(contact: Contact, chatError: ChatError) { @@ -3250,6 +3252,7 @@ sealed class CR { @Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: User): CR() @Serializable @SerialName("sentInvitation") class SentInvitation(val user: User): CR() @Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: User, val contact: Contact): CR() + @Serializable @SerialName("contactRequestAlreadyAccepted") class ContactRequestAlreadyAccepted(val user: User, val contact: Contact): CR() @Serializable @SerialName("contactDeleted") class ContactDeleted(val user: User, val contact: Contact): CR() @Serializable @SerialName("chatCleared") class ChatCleared(val user: User, val chatInfo: ChatInfo): CR() @Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR() @@ -3279,6 +3282,7 @@ sealed class CR { @Serializable @SerialName("newChatItem") class NewChatItem(val user: User, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemStatusUpdated") class ChatItemStatusUpdated(val user: User, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: User, val chatItem: AChatItem): CR() + @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: User, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: User, val added: Boolean, val reaction: ACIReaction): CR() @Serializable @SerialName("chatItemDeleted") class ChatItemDeleted(val user: User, val deletedChatItem: AChatItem, val toChatItem: AChatItem? = null, val byUser: Boolean): CR() @Serializable @SerialName("contactsList") class ContactsList(val user: User, val contacts: List): CR() @@ -3376,6 +3380,7 @@ sealed class CR { is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" is ContactAlreadyExists -> "contactAlreadyExists" + is ContactRequestAlreadyAccepted -> "contactRequestAlreadyAccepted" is ContactDeleted -> "contactDeleted" is ChatCleared -> "chatCleared" is UserProfileNoChange -> "userProfileNoChange" @@ -3405,6 +3410,7 @@ sealed class CR { is NewChatItem -> "newChatItem" is ChatItemStatusUpdated -> "chatItemStatusUpdated" is ChatItemUpdated -> "chatItemUpdated" + is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" is ChatItemDeleted -> "chatItemDeleted" is ContactsList -> "contactsList" @@ -3499,6 +3505,7 @@ sealed class CR { is SentConfirmation -> withUser(user, noDetails()) is SentInvitation -> withUser(user, noDetails()) is ContactAlreadyExists -> withUser(user, json.encodeToString(contact)) + is ContactRequestAlreadyAccepted -> withUser(user, json.encodeToString(contact)) is ContactDeleted -> withUser(user, json.encodeToString(contact)) is ChatCleared -> withUser(user, json.encodeToString(chatInfo)) is UserProfileNoChange -> withUser(user, noDetails()) @@ -3529,6 +3536,7 @@ sealed class CR { is NewChatItem -> withUser(user, json.encodeToString(chatItem)) is ChatItemStatusUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) + is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") is ChatItemDeleted -> withUser(user, "deletedChatItem:\n${json.encodeToString(deletedChatItem)}\ntoChatItem:\n${json.encodeToString(toChatItem)}\nbyUser: $byUser") is ContactsList -> withUser(user, json.encodeToString(contacts)) @@ -3734,34 +3742,266 @@ sealed class ChatError { @Serializable sealed class ChatErrorType { - val string: String get() = when (this) { - is NoActiveUser -> "noActiveUser" - is DifferentActiveUser -> "differentActiveUser" - is UserExists -> "userExists" - is InvalidConnReq -> "invalidConnReq" - is FileAlreadyReceiving -> "fileAlreadyReceiving" - is СommandError -> "commandError $message" - is CEException -> "exception $message" - } - @Serializable @SerialName("noActiveUser") class NoActiveUser: ChatErrorType() - @Serializable @SerialName("differentActiveUser") class DifferentActiveUser: ChatErrorType() + val string: String + get() = when (this) { + is NoActiveUser -> "noActiveUser" + is NoConnectionUser -> "noConnectionUser" + is NoSndFileUser -> "noSndFileUser" + is NoRcvFileUser -> "noRcvFileUser" + is UserUnknown -> "userUnknown" + is ActiveUserExists -> "activeUserExists" + is UserExists -> "userExists" + is DifferentActiveUser -> "differentActiveUser" + is CantDeleteActiveUser -> "cantDeleteActiveUser" + is CantDeleteLastUser -> "cantDeleteLastUser" + is CantHideLastUser -> "cantHideLastUser" + is HiddenUserAlwaysMuted -> "hiddenUserAlwaysMuted" + is EmptyUserPassword -> "emptyUserPassword" + is UserAlreadyHidden -> "userAlreadyHidden" + is UserNotHidden -> "userNotHidden" + is ChatNotStarted -> "chatNotStarted" + is ChatNotStopped -> "chatNotStopped" + is ChatStoreChanged -> "chatStoreChanged" + is InvalidConnReq -> "invalidConnReq" + is InvalidChatMessage -> "invalidChatMessage" + is ContactNotReady -> "contactNotReady" + is ContactDisabled -> "contactDisabled" + is ConnectionDisabled -> "connectionDisabled" + is GroupUserRole -> "groupUserRole" + is GroupMemberInitialRole -> "groupMemberInitialRole" + is ContactIncognitoCantInvite -> "contactIncognitoCantInvite" + is GroupIncognitoCantInvite -> "groupIncognitoCantInvite" + is GroupContactRole -> "groupContactRole" + is GroupDuplicateMember -> "groupDuplicateMember" + is GroupDuplicateMemberId -> "groupDuplicateMemberId" + is GroupNotJoined -> "groupNotJoined" + is GroupMemberNotActive -> "groupMemberNotActive" + is GroupMemberUserRemoved -> "groupMemberUserRemoved" + is GroupMemberNotFound -> "groupMemberNotFound" + is GroupMemberIntroNotFound -> "groupMemberIntroNotFound" + is GroupCantResendInvitation -> "groupCantResendInvitation" + is GroupInternal -> "groupInternal" + is FileNotFound -> "fileNotFound" + is FileSize -> "fileSize" + is FileAlreadyReceiving -> "fileAlreadyReceiving" + is FileCancelled -> "fileCancelled" + is FileCancel -> "fileCancel" + is FileAlreadyExists -> "fileAlreadyExists" + is FileRead -> "fileRead" + is FileWrite -> "fileWrite" + is FileSend -> "fileSend" + is FileRcvChunk -> "fileRcvChunk" + is FileInternal -> "fileInternal" + is FileImageType -> "fileImageType" + is FileImageSize -> "fileImageSize" + is FileNotReceived -> "fileNotReceived" + // is XFTPRcvFile -> "xftpRcvFile" + // is XFTPSndFile -> "xftpSndFile" + is FallbackToSMPProhibited -> "fallbackToSMPProhibited" + is InlineFileProhibited -> "inlineFileProhibited" + is InvalidQuote -> "invalidQuote" + is InvalidChatItemUpdate -> "invalidChatItemUpdate" + is InvalidChatItemDelete -> "invalidChatItemDelete" + is HasCurrentCall -> "hasCurrentCall" + is NoCurrentCall -> "noCurrentCall" + is CallContact -> "callContact" + is CallState -> "callState" + is DirectMessagesProhibited -> "directMessagesProhibited" + is AgentVersion -> "agentVersion" + is AgentNoSubResult -> "agentNoSubResult" + is CommandError -> "commandError $message" + is ServerProtocol -> "serverProtocol" + is AgentCommandError -> "agentCommandError" + is InvalidFileDescription -> "invalidFileDescription" + is InternalError -> "internalError" + is CEException -> "exception $message" + } + + @Serializable @SerialName("noActiveUser") object NoActiveUser: ChatErrorType() + @Serializable @SerialName("noConnectionUser") class NoConnectionUser(val agentConnId: String): ChatErrorType() + @Serializable @SerialName("noSndFileUser") class NoSndFileUser(val agentSndFileId: String): ChatErrorType() + @Serializable @SerialName("noRcvFileUser") class NoRcvFileUser(val agentRcvFileId: String): ChatErrorType() + @Serializable @SerialName("userUnknown") object UserUnknown: ChatErrorType() + @Serializable @SerialName("activeUserExists") object ActiveUserExists: ChatErrorType() @Serializable @SerialName("userExists") class UserExists(val contactName: String): ChatErrorType() - @Serializable @SerialName("invalidConnReq") class InvalidConnReq: ChatErrorType() - @Serializable @SerialName("fileAlreadyReceiving") class FileAlreadyReceiving: ChatErrorType() - @Serializable @SerialName("commandError") class СommandError(val message: String): ChatErrorType() + @Serializable @SerialName("differentActiveUser") class DifferentActiveUser(val commandUserId: Long, val activeUserId: Long): ChatErrorType() + @Serializable @SerialName("cantDeleteActiveUser") class CantDeleteActiveUser(val userId: Long): ChatErrorType() + @Serializable @SerialName("cantDeleteLastUser") class CantDeleteLastUser(val userId: Long): ChatErrorType() + @Serializable @SerialName("cantHideLastUser") class CantHideLastUser(val userId: Long): ChatErrorType() + @Serializable @SerialName("hiddenUserAlwaysMuted") class HiddenUserAlwaysMuted(val userId: Long): ChatErrorType() + @Serializable @SerialName("emptyUserPassword") class EmptyUserPassword(val userId: Long): ChatErrorType() + @Serializable @SerialName("userAlreadyHidden") class UserAlreadyHidden(val userId: Long): ChatErrorType() + @Serializable @SerialName("userNotHidden") class UserNotHidden(val userId: Long): ChatErrorType() + @Serializable @SerialName("chatNotStarted") object ChatNotStarted: ChatErrorType() + @Serializable @SerialName("chatNotStopped") object ChatNotStopped: ChatErrorType() + @Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType() + @Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType() + @Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType() + @Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType() + @Serializable @SerialName("contactDisabled") class ContactDisabled(val contact: Contact): ChatErrorType() + @Serializable @SerialName("connectionDisabled") class ConnectionDisabled(val connection: Connection): ChatErrorType() + @Serializable @SerialName("groupUserRole") class GroupUserRole(val groupInfo: GroupInfo, val requiredRole: GroupMemberRole): ChatErrorType() + @Serializable @SerialName("groupMemberInitialRole") class GroupMemberInitialRole(val groupInfo: GroupInfo, val initialRole: GroupMemberRole): ChatErrorType() + @Serializable @SerialName("contactIncognitoCantInvite") object ContactIncognitoCantInvite: ChatErrorType() + @Serializable @SerialName("groupIncognitoCantInvite") object GroupIncognitoCantInvite: ChatErrorType() + @Serializable @SerialName("groupContactRole") class GroupContactRole(val contactName: String): ChatErrorType() + @Serializable @SerialName("groupDuplicateMember") class GroupDuplicateMember(val contactName: String): ChatErrorType() + @Serializable @SerialName("groupDuplicateMemberId") object GroupDuplicateMemberId: ChatErrorType() + @Serializable @SerialName("groupNotJoined") class GroupNotJoined(val groupInfo: GroupInfo): ChatErrorType() + @Serializable @SerialName("groupMemberNotActive") object GroupMemberNotActive: ChatErrorType() + @Serializable @SerialName("groupMemberUserRemoved") object GroupMemberUserRemoved: ChatErrorType() + @Serializable @SerialName("groupMemberNotFound") object GroupMemberNotFound: ChatErrorType() + @Serializable @SerialName("groupMemberIntroNotFound") class GroupMemberIntroNotFound(val contactName: String): ChatErrorType() + @Serializable @SerialName("groupCantResendInvitation") class GroupCantResendInvitation(val groupInfo: GroupInfo, val contactName: String): ChatErrorType() + @Serializable @SerialName("groupInternal") class GroupInternal(val message: String): ChatErrorType() + @Serializable @SerialName("fileNotFound") class FileNotFound(val message: String): ChatErrorType() + @Serializable @SerialName("fileSize") class FileSize(val filePath: String): ChatErrorType() + @Serializable @SerialName("fileAlreadyReceiving") class FileAlreadyReceiving(val message: String): ChatErrorType() + @Serializable @SerialName("fileCancelled") class FileCancelled(val message: String): ChatErrorType() + @Serializable @SerialName("fileCancel") class FileCancel(val fileId: Long, val message: String): ChatErrorType() + @Serializable @SerialName("fileAlreadyExists") class FileAlreadyExists(val filePath: String): ChatErrorType() + @Serializable @SerialName("fileRead") class FileRead(val filePath: String, val message: String): ChatErrorType() + @Serializable @SerialName("fileWrite") class FileWrite(val filePath: String, val message: String): ChatErrorType() + @Serializable @SerialName("fileSend") class FileSend(val fileId: Long, val agentError: String): ChatErrorType() + @Serializable @SerialName("fileRcvChunk") class FileRcvChunk(val message: String): ChatErrorType() + @Serializable @SerialName("fileInternal") class FileInternal(val message: String): ChatErrorType() + @Serializable @SerialName("fileImageType") class FileImageType(val filePath: String): ChatErrorType() + @Serializable @SerialName("fileImageSize") class FileImageSize(val filePath: String): ChatErrorType() + @Serializable @SerialName("fileNotReceived") class FileNotReceived(val fileId: Long): ChatErrorType() + // @Serializable @SerialName("xFTPRcvFile") object XFTPRcvFile: ChatErrorType() + // @Serializable @SerialName("xFTPSndFile") object XFTPSndFile: ChatErrorType() + @Serializable @SerialName("fallbackToSMPProhibited") class FallbackToSMPProhibited(val fileId: Long): ChatErrorType() + @Serializable @SerialName("inlineFileProhibited") class InlineFileProhibited(val fileId: Long): ChatErrorType() + @Serializable @SerialName("invalidQuote") object InvalidQuote: ChatErrorType() + @Serializable @SerialName("invalidChatItemUpdate") object InvalidChatItemUpdate: ChatErrorType() + @Serializable @SerialName("invalidChatItemDelete") object InvalidChatItemDelete: ChatErrorType() + @Serializable @SerialName("hasCurrentCall") object HasCurrentCall: ChatErrorType() + @Serializable @SerialName("noCurrentCall") object NoCurrentCall: ChatErrorType() + @Serializable @SerialName("callContact") class CallContact(val contactId: Long): ChatErrorType() + @Serializable @SerialName("callState") object CallState: ChatErrorType() + @Serializable @SerialName("directMessagesProhibited") class DirectMessagesProhibited(val contact: Contact): ChatErrorType() + @Serializable @SerialName("agentVersion") object AgentVersion: ChatErrorType() + @Serializable @SerialName("agentNoSubResult") class AgentNoSubResult(val agentConnId: String): ChatErrorType() + @Serializable @SerialName("commandError") class CommandError(val message: String): ChatErrorType() + @Serializable @SerialName("serverProtocol") object ServerProtocol: ChatErrorType() + @Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType() + @Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType() + @Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType() @Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType() } @Serializable sealed class StoreError { - val string: String get() = when (this) { - is UserContactLinkNotFound -> "userContactLinkNotFound" - is GroupNotFound -> "groupNotFound" - is DuplicateName -> "duplicateName" - } - @Serializable @SerialName("userContactLinkNotFound") class UserContactLinkNotFound: StoreError() - @Serializable @SerialName("groupNotFound") class GroupNotFound: StoreError() - @Serializable @SerialName("duplicateName") class DuplicateName: StoreError() + val string: String + get() = when (this) { + is DuplicateName -> "duplicateName" + is UserNotFound -> "userNotFound" + is UserNotFoundByName -> "userNotFoundByName" + is UserNotFoundByContactId -> "userNotFoundByContactId" + is UserNotFoundByGroupId -> "userNotFoundByGroupId" + is UserNotFoundByFileId -> "userNotFoundByFileId" + is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId" + is ContactNotFound -> "contactNotFound" + is ContactNotFoundByName -> "contactNotFoundByName" + is ContactNotReady -> "contactNotReady" + is DuplicateContactLink -> "duplicateContactLink" + is UserContactLinkNotFound -> "userContactLinkNotFound" + is ContactRequestNotFound -> "contactRequestNotFound" + is ContactRequestNotFoundByName -> "contactRequestNotFoundByName" + is GroupNotFound -> "groupNotFound" + is GroupNotFoundByName -> "groupNotFoundByName" + is GroupMemberNameNotFound -> "groupMemberNameNotFound" + is GroupMemberNotFound -> "groupMemberNotFound" + is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId" + is GroupWithoutUser -> "groupWithoutUser" + is DuplicateGroupMember -> "duplicateGroupMember" + is GroupAlreadyJoined -> "groupAlreadyJoined" + is GroupInvitationNotFound -> "groupInvitationNotFound" + is SndFileNotFound -> "sndFileNotFound" + is SndFileInvalid -> "sndFileInvalid" + is RcvFileNotFound -> "rcvFileNotFound" + is RcvFileDescrNotFound -> "rcvFileDescrNotFound" + is FileNotFound -> "fileNotFound" + is RcvFileInvalid -> "rcvFileInvalid" + is RcvFileInvalidDescrPart -> "rcvFileInvalidDescrPart" + is SharedMsgIdNotFoundByFileId -> "sharedMsgIdNotFoundByFileId" + is FileIdNotFoundBySharedMsgId -> "fileIdNotFoundBySharedMsgId" + is SndFileNotFoundXFTP -> "sndFileNotFoundXFTP" + is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP" + is ConnectionNotFound -> "connectionNotFound" + is ConnectionNotFoundById -> "connectionNotFoundById" + is PendingConnectionNotFound -> "pendingConnectionNotFound" + is IntroNotFound -> "introNotFound" + is UniqueID -> "uniqueID" + is InternalError -> "internalError" + is NoMsgDelivery -> "noMsgDelivery" + is BadChatItem -> "badChatItem" + is ChatItemNotFound -> "chatItemNotFound" + is ChatItemNotFoundByText -> "chatItemNotFoundByText" + is ChatItemSharedMsgIdNotFound -> "chatItemSharedMsgIdNotFound" + is ChatItemNotFoundByFileId -> "chatItemNotFoundByFileId" + is ChatItemNotFoundByGroupId -> "chatItemNotFoundByGroupId" + is ProfileNotFound -> "profileNotFound" + is DuplicateGroupLink -> "duplicateGroupLink" + is GroupLinkNotFound -> "groupLinkNotFound" + is HostMemberIdNotFound -> "hostMemberIdNotFound" + is ContactNotFoundByFileId -> "contactNotFoundByFileId" + is NoGroupSndStatus -> "noGroupSndStatus" + } + + @Serializable @SerialName("duplicateName") object DuplicateName: StoreError() + @Serializable @SerialName("userNotFound") class UserNotFound(val userId: Long): StoreError() + @Serializable @SerialName("userNotFoundByName") class UserNotFoundByName(val contactName: String): StoreError() + @Serializable @SerialName("userNotFoundByContactId") class UserNotFoundByContactId(val contactId: Long): StoreError() + @Serializable @SerialName("userNotFoundByGroupId") class UserNotFoundByGroupId(val groupId: Long): StoreError() + @Serializable @SerialName("userNotFoundByFileId") class UserNotFoundByFileId(val fileId: Long): StoreError() + @Serializable @SerialName("userNotFoundByContactRequestId") class UserNotFoundByContactRequestId(val contactRequestId: Long): StoreError() + @Serializable @SerialName("contactNotFound") class ContactNotFound(val contactId: Long): StoreError() + @Serializable @SerialName("contactNotFoundByName") class ContactNotFoundByName(val contactName: String): StoreError() + @Serializable @SerialName("contactNotReady") class ContactNotReady(val contactName: String): StoreError() + @Serializable @SerialName("duplicateContactLink") object DuplicateContactLink: StoreError() + @Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: StoreError() + @Serializable @SerialName("contactRequestNotFound") class ContactRequestNotFound(val contactRequestId: Long): StoreError() + @Serializable @SerialName("contactRequestNotFoundByName") class ContactRequestNotFoundByName(val contactName: String): StoreError() + @Serializable @SerialName("groupNotFound") class GroupNotFound(val groupId: Long): StoreError() + @Serializable @SerialName("groupNotFoundByName") class GroupNotFoundByName(val groupName: String): StoreError() + @Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError() + @Serializable @SerialName("groupMemberNotFound") class GroupMemberNotFound(val groupMemberId: Long): StoreError() + @Serializable @SerialName("groupMemberNotFoundByMemberId") class GroupMemberNotFoundByMemberId(val memberId: String): StoreError() + @Serializable @SerialName("groupWithoutUser") object GroupWithoutUser: StoreError() + @Serializable @SerialName("duplicateGroupMember") object DuplicateGroupMember: StoreError() + @Serializable @SerialName("groupAlreadyJoined") object GroupAlreadyJoined: StoreError() + @Serializable @SerialName("groupInvitationNotFound") object GroupInvitationNotFound: StoreError() + @Serializable @SerialName("sndFileNotFound") class SndFileNotFound(val fileId: Long): StoreError() + @Serializable @SerialName("sndFileInvalid") class SndFileInvalid(val fileId: Long): StoreError() + @Serializable @SerialName("rcvFileNotFound") class RcvFileNotFound(val fileId: Long): StoreError() + @Serializable @SerialName("rcvFileDescrNotFound") class RcvFileDescrNotFound(val fileId: Long): StoreError() + @Serializable @SerialName("fileNotFound") class FileNotFound(val fileId: Long): StoreError() + @Serializable @SerialName("rcvFileInvalid") class RcvFileInvalid(val fileId: Long): StoreError() + @Serializable @SerialName("rcvFileInvalidDescrPart") object RcvFileInvalidDescrPart: StoreError() + @Serializable @SerialName("sharedMsgIdNotFoundByFileId") class SharedMsgIdNotFoundByFileId(val fileId: Long): StoreError() + @Serializable @SerialName("fileIdNotFoundBySharedMsgId") class FileIdNotFoundBySharedMsgId(val sharedMsgId: String): StoreError() + @Serializable @SerialName("sndFileNotFoundXFTP") class SndFileNotFoundXFTP(val agentSndFileId: String): StoreError() + @Serializable @SerialName("rcvFileNotFoundXFTP") class RcvFileNotFoundXFTP(val agentRcvFileId: String): StoreError() + @Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError() + @Serializable @SerialName("connectionNotFoundById") class ConnectionNotFoundById(val connId: Long): StoreError() + @Serializable @SerialName("pendingConnectionNotFound") class PendingConnectionNotFound(val connId: Long): StoreError() + @Serializable @SerialName("introNotFound") object IntroNotFound: StoreError() + @Serializable @SerialName("uniqueID") object UniqueID: StoreError() + @Serializable @SerialName("internalError") class InternalError(val message: String): StoreError() + @Serializable @SerialName("noMsgDelivery") class NoMsgDelivery(val connId: Long, val agentMsgId: String): StoreError() + @Serializable @SerialName("badChatItem") class BadChatItem(val itemId: Long): StoreError() + @Serializable @SerialName("chatItemNotFound") class ChatItemNotFound(val itemId: Long): StoreError() + @Serializable @SerialName("chatItemNotFoundByText") class ChatItemNotFoundByText(val text: String): StoreError() + @Serializable @SerialName("chatItemSharedMsgIdNotFound") class ChatItemSharedMsgIdNotFound(val sharedMsgId: String): StoreError() + @Serializable @SerialName("chatItemNotFoundByFileId") class ChatItemNotFoundByFileId(val fileId: Long): StoreError() + @Serializable @SerialName("chatItemNotFoundByGroupId") class ChatItemNotFoundByGroupId(val groupId: Long): StoreError() + @Serializable @SerialName("profileNotFound") class ProfileNotFound(val profileId: Long): StoreError() + @Serializable @SerialName("duplicateGroupLink") class DuplicateGroupLink(val groupInfo: GroupInfo): StoreError() + @Serializable @SerialName("groupLinkNotFound") class GroupLinkNotFound(val groupInfo: GroupInfo): StoreError() + @Serializable @SerialName("hostMemberIdNotFound") class HostMemberIdNotFound(val groupId: Long): StoreError() + @Serializable @SerialName("contactNotFoundByFileId") class ContactNotFoundByFileId(val fileId: Long): StoreError() + @Serializable @SerialName("noGroupSndStatus") class NoGroupSndStatus(val itemId: Long, val groupMemberId: Long): StoreError() } @Serializable @@ -3792,18 +4032,22 @@ sealed class AgentErrorType { is CMD -> "CMD ${cmdErr.string}" is CONN -> "CONN ${connErr.string}" is SMP -> "SMP ${smpErr.string}" + // is NTF -> "NTF ${ntfErr.string}" is XFTP -> "XFTP ${xftpErr.string}" is BROKER -> "BROKER ${brokerErr.string}" is AGENT -> "AGENT ${agentErr.string}" is INTERNAL -> "INTERNAL $internalErr" + is INACTIVE -> "INACTIVE" } @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType() @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() @Serializable @SerialName("SMP") class SMP(val smpErr: SMPErrorType): AgentErrorType() + // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType() @Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType() @Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType() @Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType() + @Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType() } @Serializable @@ -3841,17 +4085,19 @@ sealed class ConnectionErrorType { @Serializable sealed class BrokerErrorType { val string: String get() = when (this) { - is RESPONSE -> "RESPONSE ${smpErr.string}" + is RESPONSE -> "RESPONSE ${smpErr}" is UNEXPECTED -> "UNEXPECTED" is NETWORK -> "NETWORK" + is HOST -> "HOST" is TRANSPORT -> "TRANSPORT ${transportErr.string}" is TIMEOUT -> "TIMEOUT" } - @Serializable @SerialName("RESPONSE") class RESPONSE(val smpErr: SMPErrorType): BrokerErrorType() - @Serializable @SerialName("UNEXPECTED") class UNEXPECTED: BrokerErrorType() - @Serializable @SerialName("NETWORK") class NETWORK: BrokerErrorType() + @Serializable @SerialName("RESPONSE") class RESPONSE(val smpErr: String): BrokerErrorType() + @Serializable @SerialName("UNEXPECTED") object UNEXPECTED: BrokerErrorType() + @Serializable @SerialName("NETWORK") object NETWORK: BrokerErrorType() + @Serializable @SerialName("HOST") object HOST: BrokerErrorType() @Serializable @SerialName("TRANSPORT") class TRANSPORT(val transportErr: SMPTransportError): BrokerErrorType() - @Serializable @SerialName("TIMEOUT") class TIMEOUT: BrokerErrorType() + @Serializable @SerialName("TIMEOUT") object TIMEOUT: BrokerErrorType() } @Serializable @@ -3881,15 +4127,17 @@ sealed class ProtocolCommandError { val string: String get() = when (this) { is UNKNOWN -> "UNKNOWN" is SYNTAX -> "SYNTAX" + is PROHIBITED -> "PROHIBITED" is NO_AUTH -> "NO_AUTH" is HAS_AUTH -> "HAS_AUTH" is NO_QUEUE -> "NO_QUEUE" } - @Serializable @SerialName("UNKNOWN") class UNKNOWN: ProtocolCommandError() - @Serializable @SerialName("SYNTAX") class SYNTAX: ProtocolCommandError() - @Serializable @SerialName("NO_AUTH") class NO_AUTH: ProtocolCommandError() - @Serializable @SerialName("HAS_AUTH") class HAS_AUTH: ProtocolCommandError() - @Serializable @SerialName("NO_QUEUE") class NO_QUEUE: ProtocolCommandError() + @Serializable @SerialName("UNKNOWN") object UNKNOWN: ProtocolCommandError() + @Serializable @SerialName("SYNTAX") object SYNTAX: ProtocolCommandError() + @Serializable @SerialName("PROHIBITED") object PROHIBITED: ProtocolCommandError() + @Serializable @SerialName("NO_AUTH") object NO_AUTH: ProtocolCommandError() + @Serializable @SerialName("HAS_AUTH") object HAS_AUTH: ProtocolCommandError() + @Serializable @SerialName("NO_QUEUE") object NO_QUEUE: ProtocolCommandError() } @Serializable @@ -3924,12 +4172,16 @@ sealed class SMPAgentError { is A_MESSAGE -> "A_MESSAGE" is A_PROHIBITED -> "A_PROHIBITED" is A_VERSION -> "A_VERSION" - is A_ENCRYPTION -> "A_ENCRYPTION" + is A_CRYPTO -> "A_CRYPTO" + is A_DUPLICATE -> "A_DUPLICATE" + is A_QUEUE -> "A_QUEUE" } - @Serializable @SerialName("A_MESSAGE") class A_MESSAGE: SMPAgentError() - @Serializable @SerialName("A_PROHIBITED") class A_PROHIBITED: SMPAgentError() - @Serializable @SerialName("A_VERSION") class A_VERSION: SMPAgentError() - @Serializable @SerialName("A_ENCRYPTION") class A_ENCRYPTION: SMPAgentError() + @Serializable @SerialName("A_MESSAGE") object A_MESSAGE: SMPAgentError() + @Serializable @SerialName("A_PROHIBITED") object A_PROHIBITED: SMPAgentError() + @Serializable @SerialName("A_VERSION") object A_VERSION: SMPAgentError() + @Serializable @SerialName("A_CRYPTO") object A_CRYPTO: SMPAgentError() + @Serializable @SerialName("A_DUPLICATE") object A_DUPLICATE: SMPAgentError() + @Serializable @SerialName("A_QUEUE") class A_QUEUE(val queueErr: String): SMPAgentError() } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 01e64d54c9..b9f60f9cf0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -46,7 +46,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState - val uri = URI(connReqUri) - withUriAction(uri) { linkType -> - withApi { - Log.d(TAG, "connectViaUri: connecting") - connectViaUri(chatModel, linkType, uri) - } - } + connectViaMemberAddressAlert(connReqUri) }, removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) }, onRoleSelected = { @@ -450,6 +444,23 @@ private fun updateMemberRoleDialog( ) } +fun connectViaMemberAddressAlert(connReqUri: String) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.connect_via_member_address_alert_title), + text = generalGetString(MR.strings.connect_via_member_address_alert_desc), + confirmText = generalGetString(MR.strings.connect_via_link_verb), + onConfirm = { + val uri = URI(connReqUri) + withUriAction(uri) { linkType -> + withApi { + Log.d(TAG, "connectViaUri: connecting") + connectViaUri(chatModel, linkType, uri) + } + } + }, + ) +} + @Preview @Composable fun PreviewGroupMemberInfoLayout() { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index a54a1a9320..753e89a1d4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1212,6 +1212,8 @@ Change group role? The role will be changed to \"%s\". Everyone in the group will be notified. The role will be changed to \"%s\". The member will receive a new invitation. + Connect directly? + Сonnection request will be sent to this group member. Error removing member Error changing role Group diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt index d7da2fa123..94268002a6 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt @@ -1,5 +1,7 @@ package chat.simplex.common.ui.theme +import chat.simplex.common.platform.Log +import chat.simplex.common.platform.TAG import com.jthemedetecor.OsThemeDetector private val detector: OsThemeDetector = OsThemeDetector.getDetector() @@ -7,4 +9,11 @@ private val detector: OsThemeDetector = OsThemeDetector.getDetector() registerListener(::reactOnDarkThemeChanges) } -actual fun isSystemInDarkTheme(): Boolean = detector.isDark +actual fun isSystemInDarkTheme(): Boolean = try { + detector.isDark +} +catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + /* On Mac this code can produce exception */ + false +} diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index a45fb94991..c3c18b1321 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -125,7 +125,7 @@ afterEvaluate { copy { from("${project(":desktop").buildDir}/cmake/main/linux-amd64", "$cppPath/desktop/libs/linux-x86_64", "$cppPath/desktop/libs/linux-x86_64/deps") into("src/jvmMain/resources/libs/linux-x86_64") - include("*.so") + include("*.so*") eachFile { path = name } @@ -135,7 +135,7 @@ afterEvaluate { copy { from("${project(":desktop").buildDir}/cmake/main/linux-aarch64", "$cppPath/desktop/libs/linux-aarch64", "$cppPath/desktop/libs/linux-aarch64/deps") into("src/jvmMain/resources/libs/linux-aarch64") - include("*.so") + include("*.so*") eachFile { path = name } diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 1393831630..d18f28db31 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -17,7 +17,7 @@ import qualified Data.Attoparsec.Text as A import Data.Char (isDigit) import Data.Either (fromRight) import Data.Functor (($>)) -import Data.List (intercalate) +import Data.List (intercalate, foldl') import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe, isNothing) @@ -124,9 +124,15 @@ unmarked :: Text -> Markdown unmarked = Markdown Nothing parseMaybeMarkdownList :: Text -> Maybe MarkdownList -parseMaybeMarkdownList s = - let m = intercalate ["\n"] . map (markdownToList . parseMarkdown) $ T.lines s - in if all (isNothing . format) m then Nothing else Just m +parseMaybeMarkdownList s + | all (isNothing . format) ml = Nothing + | otherwise = Just . reverse $ foldl' acc [] ml + where + ml = intercalate ["\n"] . map (markdownToList . parseMarkdown) $ T.lines s + acc [] m = [m] + acc ms@(FormattedText f t : ms') ft@(FormattedText f' t') + | f == f' = FormattedText f (t <> t') : ms' + | otherwise = ft : ms parseMarkdownList :: Text -> MarkdownList parseMarkdownList = markdownToList . parseMarkdown diff --git a/tests/MarkdownTests.hs b/tests/MarkdownTests.hs index 487fe651ed..837849d7e4 100644 --- a/tests/MarkdownTests.hs +++ b/tests/MarkdownTests.hs @@ -204,5 +204,7 @@ multilineMarkdownList :: Spec multilineMarkdownList = describe "multiline markdown" do it "correct markdown" do parseMaybeMarkdownList "http://simplex.chat\nhttp://app.simplex.chat" `shouldBe` Just [uri' "http://simplex.chat", "\n", uri' "http://app.simplex.chat"] + it "combines the same formats" do + parseMaybeMarkdownList "http://simplex.chat\ntext 1\ntext 2\nhttp://app.simplex.chat" `shouldBe` Just [uri' "http://simplex.chat", "\ntext 1\ntext 2\n", uri' "http://app.simplex.chat"] it "no markdown" do parseMaybeMarkdownList "not a\nmarkdown" `shouldBe` Nothing