From 0b58078ab1b5f31f0250a95ef5334ec362942aba Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 4 Mar 2024 20:10:07 +0700 Subject: [PATCH 1/4] Fixed UI for long chat title --- .../Modules/Messaging/Chat/ChatView/ChatNavTitleView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatNavTitleView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatNavTitleView.swift index de19e9bdd..1fc012542 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatNavTitleView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatNavTitleView.swift @@ -23,6 +23,7 @@ struct ChatNavTitleView: View { Text(title) .foregroundStyle(Color.foregroundDefault) .font(.currentFont(size: 16, weight: .semibold)) + .lineLimit(1) } .onChange(of: titleType, perform: { newValue in loadIconNonBlocking() From 0d7d3c9c6d742fbec27bf6a34932044d17b10d3d Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 5 Mar 2024 10:48:14 +0700 Subject: [PATCH 2/4] Added unsupported message type --- .../project.pbxproj | 6 ++ .../ChatView/Messages/MessageRowView.swift | 2 + .../MessagingChatMessageDisplayInfo.swift | 2 +- .../MessagingChatMessageDisplayType.swift | 5 +- ...hatMessageUnsupportedTypeDisplayInfo.swift | 12 +++ .../MessagingService/MessagingService.swift | 4 +- .../Push/PushEntitiesTransformer.swift | 75 +++++++++-------- .../Files/MessagingFilesService.swift | 2 +- .../PushMessagingAPIService.swift | 6 +- .../XMTPMessagingAPIService.swift | 28 +++---- .../CoreDataMessagingStorageService.swift | 16 ++++ .../XMTP/XMTPEntitiesTransformer.swift | 82 ++++++++++--------- 12 files changed, 144 insertions(+), 96 deletions(-) create mode 100644 unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageUnsupportedTypeDisplayInfo.swift diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index c1d4bd5e3..226f5174c 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -1200,6 +1200,8 @@ C6AE670D2AA1CA260061D87D /* Debug-Tools-Dev.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302EAF3126332DC200CAFE8B /* Debug-Tools-Dev.swift */; }; C6AF12402A67C72200F89D2E /* MessagingServiceDataRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6AF123F2A67C72200F89D2E /* MessagingServiceDataRefreshManager.swift */; }; C6B0A8CD2A4D7DDA00284022 /* MessagingImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B0A8CC2A4D7DDA00284022 /* MessagingImageLoader.swift */; }; + C6B2E1FF2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B2E1FE2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift */; }; + C6B2E2002B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B2E1FE2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift */; }; C6B366CD2A1BA1A700DE512B /* NavBarItemsTransitionPerformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B366CC2A1BA1A700DE512B /* NavBarItemsTransitionPerformer.swift */; }; C6B366D22A1BA35A00DE512B /* NavBarBackButtonTransitionPerformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B366D12A1BA35A00DE512B /* NavBarBackButtonTransitionPerformer.swift */; }; C6B366D72A1C715000DE512B /* PushRESTAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B366D62A1C715000DE512B /* PushRESTAPIService.swift */; }; @@ -3285,6 +3287,7 @@ C6AD180E28D319BF0008A479 /* CollectionViewShowHideCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectionViewShowHideCell.xib; sourceTree = ""; }; C6AF123F2A67C72200F89D2E /* MessagingServiceDataRefreshManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingServiceDataRefreshManager.swift; sourceTree = ""; }; C6B0A8CC2A4D7DDA00284022 /* MessagingImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingImageLoader.swift; sourceTree = ""; }; + C6B2E1FE2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingChatMessageUnsupportedTypeDisplayInfo.swift; sourceTree = ""; }; C6B366CC2A1BA1A700DE512B /* NavBarItemsTransitionPerformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarItemsTransitionPerformer.swift; sourceTree = ""; }; C6B366D12A1BA35A00DE512B /* NavBarBackButtonTransitionPerformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarBackButtonTransitionPerformer.swift; sourceTree = ""; }; C6B366D62A1C715000DE512B /* PushRESTAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRESTAPIService.swift; sourceTree = ""; }; @@ -5454,6 +5457,7 @@ C603D7992A56B11800CEC696 /* MessagingChatMessageUnknownTypeDisplayInfo.swift */, C6157AFD2A7108B400081574 /* MessagingChatMessageRemoteContentTypeDisplayInfo.swift */, C623967B2A288A8A00363F60 /* MessagingChatMessageDisplayType.swift */, + C6B2E1FE2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift */, C6FAED882B8C717100CC1844 /* MessagingChatMessageReplyTypeDisplayInfo.swift */, C631DFA62B7B530800040221 /* MessagingChatMessageReactionTypeDisplayInfo.swift */, C62396802A288A9400363F60 /* MessagingChatMessageDisplayInfo.swift */, @@ -9005,6 +9009,7 @@ C663A4D62B7D09490099BCE8 /* UpdateToWalletGreetingsView.swift in Sources */, C630BC7D2B18448A00E29318 /* AppContextEnvironmentKeys.swift in Sources */, C6DDF2592B147F5E006D1F0B /* UDTitleText.swift in Sources */, + C6B2E1FF2B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift in Sources */, C6D8FF362B8318310094A21E /* ChatListView.swift in Sources */, C6109E9328E6AA7A0027D5D8 /* AppContextType.swift in Sources */, 295506F02954D14400EDA42D /* WCConnectedAppsStorageV2.swift in Sources */, @@ -9788,6 +9793,7 @@ C6D645F62B1DBF0B00D724AC /* UDConfigurableButton.swift in Sources */, C6D646342B1DC43D00D724AC /* TextTertiaryButton.swift in Sources */, C61808322B19ADF10032E543 /* PreviewCoreAppCoordinator.swift in Sources */, + C6B2E2002B96C51B00CEA1F9 /* MessagingChatMessageUnsupportedTypeDisplayInfo.swift in Sources */, C6D645AA2B1DBB3800D724AC /* UICollectionViewLayout.swift in Sources */, C6C8F9372B2183D100A9834D /* DomainImageDetailsViewPresenter.swift in Sources */, C6D6470D2B1ED7D000D724AC /* MessagingChatMessageDisplayInfo.swift in Sources */, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/Messages/MessageRowView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/Messages/MessageRowView.swift index a54dd0dc2..a3a0a9073 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/Messages/MessageRowView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/Messages/MessageRowView.swift @@ -100,6 +100,8 @@ private extension MessageRowView { referenceMessageId: info.messageId) case .reaction(let info): Text(info.content) + case .unsupported: + Text(String.Constants.messageNotSupported.localized()) } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayInfo.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayInfo.swift index 445bd51ae..dd431b159 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayInfo.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayInfo.swift @@ -25,7 +25,7 @@ struct MessagingChatMessageDisplayInfo: Hashable { time = Date() } switch type { - case .text, .unknown, .remoteContent, .reaction, .reply: + case .text, .unknown, .remoteContent, .reaction, .reply, .unsupported: return case .imageData(var info): if info.image == nil { diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayType.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayType.swift index 9df19f078..d1236eb0f 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayType.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageDisplayType.swift @@ -15,6 +15,7 @@ indirect enum MessagingChatMessageDisplayType: Hashable { case remoteContent(MessagingChatMessageRemoteContentTypeDisplayInfo) case reaction(MessagingChatMessageReactionTypeDisplayInfo) case reply(MessagingChatMessageReplyTypeDisplayInfo) + case unsupported(MessagingChatMessageUnsupportedTypeDisplayInfo) var analyticName: String { switch self { @@ -30,6 +31,8 @@ indirect enum MessagingChatMessageDisplayType: Hashable { return "Reaction" case .reply: return "Reply" + case .unsupported: + return "Unsupported" } } } @@ -42,7 +45,7 @@ extension MessagingChatMessageDisplayType { return description.text case .imageBase64, .imageData: return String.Constants.photo.localized() - case .unknown: + case .unknown, .unsupported: return String.Constants.messageNotSupported.localized() case .remoteContent: return String.Constants.messagingRemoteContent.localized() diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageUnsupportedTypeDisplayInfo.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageUnsupportedTypeDisplayInfo.swift new file mode 100644 index 000000000..8c375550c --- /dev/null +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Entities/Messages/MessagingChatMessageUnsupportedTypeDisplayInfo.swift @@ -0,0 +1,12 @@ +// +// MessagingChatMessageUnsupportedTypeDisplayInfo.swift +// domains-manager-ios +// +// Created by Oleg Kuplin on 05.03.2024. +// + +import Foundation + +struct MessagingChatMessageUnsupportedTypeDisplayInfo: Hashable { + let data: Data +} diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/MessagingService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/MessagingService.swift index b9b8ffbfc..c3add2fd2 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/MessagingService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/MessagingService.swift @@ -328,7 +328,7 @@ extension MessagingService: MessagingServiceProtocol { let profile = try await getUserProfileWith(wallet: chat.thisUserDetails.wallet, serviceIdentifier: serviceIdentifier) switch chatMessage.displayInfo.type { - case .text, .imageData, .imageBase64, .unknown, .reaction: + case .text, .imageData, .imageBase64, .unknown, .reaction, .unsupported: return message case .remoteContent(let info): let loadedType = try await apiService.loadRemoteContentFor(chatMessage, @@ -340,7 +340,7 @@ extension MessagingService: MessagingServiceProtocol { return chatMessage.displayInfo case .reply(var info): switch info.contentType { - case .text, .imageData, .imageBase64, .unknown, .reaction, .reply: + case .text, .imageData, .imageBase64, .unknown, .reaction, .reply, .unsupported: return message case .remoteContent(let remoteInfo): let loadedType = try await apiService.loadRemoteContentFor(chatMessage, diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/Push/PushEntitiesTransformer.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/Push/PushEntitiesTransformer.swift index e2f1a2420..b931f0e92 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/Push/PushEntitiesTransformer.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/Push/PushEntitiesTransformer.swift @@ -224,13 +224,13 @@ struct PushEntitiesTransformer { filesService: MessagingFilesServiceProtocol, env: Push.ENV) async -> MessagingChatMessage? { guard let senderWallet = getWalletAddressFrom(eip155String: pushMessage.fromDID), - let id = pushMessage.cid, - let type = try? await extractPushMessageType(from: pushMessage, - messageId: id, - userId: chat.userId, - pgpKey: pgpKey, - filesService: filesService, - env: env) else { return nil } + let id = pushMessage.cid else { return nil } + let type = await extractPushMessageType(from: pushMessage, + messageId: id, + userId: chat.userId, + pgpKey: pgpKey, + filesService: filesService, + env: env) var time = Date() if let timestamp = pushMessage.timestamp { @@ -379,21 +379,22 @@ private extension PushEntitiesTransformer { userId: String, pgpKey: String, filesService: MessagingFilesServiceProtocol, - env: Push.ENV) async throws -> MessagingChatMessageDisplayType? { - let messageType = PushMessageType(rawValue: pushMessage.messageType) ?? .unknown - - guard let (decryptedContent, messageObj) = try? await decrypt(pushMessage: pushMessage, - pgpKey: pgpKey, - env: env) else { - return nil + env: Push.ENV) async -> MessagingChatMessageDisplayType { + do { + let messageType = PushMessageType(rawValue: pushMessage.messageType) ?? .unknown + let (decryptedContent, messageObj) = try await decrypt(pushMessage: pushMessage, + pgpKey: pgpKey, + env: env) + + return try parseMessageFromPushMessage(decryptedContent: decryptedContent, + messageObj: messageObj ?? pushMessage.messageObj, + messageType: messageType, + messageId: messageId, + userId: userId, + filesService: filesService) + } catch { + return .unsupported(MessagingChatMessageUnsupportedTypeDisplayInfo(data: Data())) } - - return try parseMessageFromPushMessage(decryptedContent: decryptedContent, - messageObj: messageObj ?? pushMessage.messageObj, - messageType: messageType, - messageId: messageId, - userId: userId, - filesService: filesService) } static func parseMessageFromPushMessage(decryptedContent: String, @@ -401,52 +402,50 @@ private extension PushEntitiesTransformer { messageType: PushMessageType, messageId: String, userId: String, - filesService: MessagingFilesServiceProtocol) throws -> MessagingChatMessageDisplayType? { + filesService: MessagingFilesServiceProtocol) throws -> MessagingChatMessageDisplayType { switch messageType { case .text: let textDisplayInfo = MessagingChatMessageTextTypeDisplayInfo(text: decryptedContent) return .text(textDisplayInfo) case .image: - guard let contentInfo = PushEnvironment.PushMessageContentResponse.objectFromJSONString(decryptedContent) else { return nil } + guard let contentInfo = PushEnvironment.PushMessageContentResponse.objectFromJSONString(decryptedContent) else { throw PushEntitiesTransformerError.failedToBuildMessageType } let base64Image = contentInfo.content let imageBase64DisplayInfo = MessagingChatMessageImageBase64TypeDisplayInfo(base64: base64Image) return .imageBase64(imageBase64DisplayInfo) case .reaction: guard let messageObj, - let contentInfo = PushEnvironment.PushMessageReactionContent.objectFromJSONString(messageObj) else { - return nil } + let contentInfo = PushEnvironment.PushMessageReactionContent.objectFromJSONString(messageObj) else { throw PushEntitiesTransformerError.failedToBuildMessageType } let messageId = parseReferenceIdToMessage(from: contentInfo.reference) return .reaction(.init(content: contentInfo.content, messageId: messageId)) case .reply: guard let messageObj, let contentInfo = PushEnvironment.PushMessageReplyContent.objectFromJSONString(messageObj), - let messageType = PushMessageType(rawValue: contentInfo.content.messageType) else { - return nil } - guard let contentType = try parseMessageFromPushMessage(decryptedContent: contentInfo.content.messageObj.content, + let messageType = PushMessageType(rawValue: contentInfo.content.messageType) else { throw PushEntitiesTransformerError.failedToBuildMessageType } + let contentType = try parseMessageFromPushMessage(decryptedContent: contentInfo.content.messageObj.content, messageObj: nil, messageType: messageType, messageId: messageId, userId: userId, - filesService: filesService) else { return nil } + filesService: filesService) let messageId = parseReferenceIdToMessage(from: contentInfo.reference) return .reply(.init(contentType: contentType, messageId: messageId)) case .meta: guard let messageObj, - let contentInfo = PushEnvironment.PushMessageMetaContent.objectFromJSONString(messageObj) else { return nil } + let contentInfo = PushEnvironment.PushMessageMetaContent.objectFromJSONString(messageObj) else { throw PushEntitiesTransformerError.failedToBuildMessageType } - return nil + // Aware but not supporting at the moment + throw PushEntitiesTransformerError.failedToBuildMessageType case .mediaEmbed: guard let messageObj, - let contentInfo = PushEnvironment.PushMessageMediaEmbeddedContent.objectFromJSONString(messageObj), - let serviceData = try? contentInfo.jsonDataThrowing() else { return nil } - + let contentInfo = PushEnvironment.PushMessageMediaEmbeddedContent.objectFromJSONString(messageObj) else { throw PushEntitiesTransformerError.failedToBuildMessageType } + let serviceData = try contentInfo.jsonDataThrowing() let displayInfo = MessagingChatMessageRemoteContentTypeDisplayInfo(serviceData: serviceData) return .remoteContent(displayInfo) default: - guard let contentInfo = PushEnvironment.PushMessageContentResponse.objectFromJSONString(decryptedContent) else { return nil } - guard let data = contentInfo.content.data(using: .utf8) else { return nil } + guard let contentInfo = PushEnvironment.PushMessageContentResponse.objectFromJSONString(decryptedContent) else { throw PushEntitiesTransformerError.failedToBuildMessageType } + guard let data = contentInfo.content.data(using: .utf8) else { throw PushEntitiesTransformerError.failedToBuildMessageType } let fileName = messageId + "_" + String(userId.suffix(4)) + "_" + (contentInfo.name ?? "") try filesService.saveData(data, fileName: fileName) @@ -478,4 +477,8 @@ private extension PushEntitiesTransformer { privateKeyArmored: pgpKey, env: env) } + + enum PushEntitiesTransformerError: Error { + case failedToBuildMessageType + } } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Files/MessagingFilesService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Files/MessagingFilesService.swift index 647c61a00..f6e518f11 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Files/MessagingFilesService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Files/MessagingFilesService.swift @@ -154,7 +154,7 @@ private extension MessagingFilesService { func getContentFilenameFor(messageType: MessagingChatMessageDisplayType) -> String? { switch messageType { - case .text, .imageBase64, .imageData, .remoteContent, .reaction: + case .text, .imageBase64, .imageData, .remoteContent, .reaction, .unsupported: return nil case .unknown(let info): return info.fileName diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift index 673a22472..384f21354 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift @@ -688,7 +688,7 @@ private extension PushMessagingAPIService { by: user) } throw PushMessagingAPIServiceError.canReplyOnlyWithText - case .unknown, .remoteContent: + case .unknown, .remoteContent, .unsupported: throw PushMessagingAPIServiceError.unsupportedType } } @@ -708,7 +708,7 @@ private extension PushMessagingAPIService { return details.messageId case .reply(let info): return info.messageId - case .text, .imageBase64, .imageData, .unknown, .remoteContent: + case .text, .imageBase64, .imageData, .unknown, .remoteContent, .unsupported: return nil } } @@ -723,7 +723,7 @@ private extension PushMessagingAPIService { return .mediaEmbed case .reply: return .reply - case .unknown, .remoteContent: + case .unknown, .remoteContent, .unsupported: throw PushMessagingAPIServiceError.unsupportedType } } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift index 284eace3e..9a46a8d19 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift @@ -329,18 +329,17 @@ private extension XMTPMessagingAPIService { in: conversation, client: client, by: senderWallet) - case .unknown, .remoteContent, .reaction, .reply: + case .unknown, .remoteContent, .reaction, .reply, .unsupported: throw XMTPServiceError.unsupportedAction } let newestMessages = try await conversation.messages(limit: 3, before: Date().addingTimeInterval(100)) // Get latest message - guard let xmtpMessage = newestMessages.first(where: { $0.id == messageID }), - let message = await XMTPEntitiesTransformer.convertXMTPMessageToChatMessage(xmtpMessage, - cachedMessage: nil, - in: chat, - isRead: true, - filesService: filesService) else { throw XMTPServiceError.failedToFindSentMessage } - + guard let xmtpMessage = newestMessages.first(where: { $0.id == messageID }) else { throw XMTPServiceError.failedToFindSentMessage } + let message = await XMTPEntitiesTransformer.convertXMTPMessageToChatMessage(xmtpMessage, + cachedMessage: nil, + in: chat, + isRead: true, + filesService: filesService) return message } @@ -600,13 +599,12 @@ final class DefaultXMTPMessagingAPIServiceDataProvider: XMTPMessagingAPIServiceD var chatMessages = [MessagingChatMessage]() for xmtpMessage in messages { - if let chatMessage = await XMTPEntitiesTransformer.convertXMTPMessageToChatMessage(xmtpMessage, - cachedMessage: cachedMessages.first(where: { $0.displayInfo.id == xmtpMessage.id }), - in: chat, - isRead: isRead, - filesService: filesService) { - chatMessages.append(chatMessage) - } + let chatMessage = await XMTPEntitiesTransformer.convertXMTPMessageToChatMessage(xmtpMessage, + cachedMessage: cachedMessages.first(where: { $0.displayInfo.id == xmtpMessage.id }), + in: chat, + isRead: isRead, + filesService: filesService) + chatMessages.append(chatMessage) } return chatMessages diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift index 588d55ffa..489d15623 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift @@ -662,6 +662,7 @@ private extension CoreDataMessagingStorageService { case reaction = 4 case reply = 5 case unknown = 999 + case unsupported = 1000 static func valueFor(_ messageType: MessagingChatMessageDisplayType) -> CoreDataMessageTypeWrapper { switch messageType { @@ -679,6 +680,8 @@ private extension CoreDataMessagingStorageService { return .reaction case .reply: return .reply + case .unsupported: + return .unsupported } } } @@ -750,6 +753,12 @@ private extension CoreDataMessagingStorageService { let reactionDisplayInfo = MessagingChatMessageReplyTypeDisplayInfo(contentType: displayType, messageId: decryptedData.messageId) return .reply(reactionDisplayInfo) + case .unsupported: + guard let decryptedContent, + let decryptedData = Data(base64Encoded: decryptedContent) else { return nil } + + let unsupportedDisplayInfo = MessagingChatMessageUnsupportedTypeDisplayInfo(data: decryptedData) + return .unsupported(unsupportedDisplayInfo) } } @@ -796,6 +805,13 @@ private extension CoreDataMessagingStorageService { contentMessageType: contentMessageType, messageId: info.messageId).jsonStringThrowing() return try decrypterService.encryptText(content) + case .unsupported(let info): + do { + return try encryptDataContent(info.data) + } catch { + print("Failed") + throw error + } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/XMTP/XMTPEntitiesTransformer.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/XMTP/XMTPEntitiesTransformer.swift index 1a68c3f1f..b2cf942aa 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/XMTP/XMTPEntitiesTransformer.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/XMTP/XMTPEntitiesTransformer.swift @@ -83,7 +83,7 @@ struct XMTPEntitiesTransformer { cachedMessage: MessagingChatMessage?, in chat: MessagingChat, isRead: Bool, - filesService: MessagingFilesServiceProtocol) async -> MessagingChatMessage? { + filesService: MessagingFilesServiceProtocol) async -> MessagingChatMessage { var isRead = isRead if Constants.shouldHideBlockedUsersLocally, XMTPBlockedUsersStorage.shared.isOtherUserBlockedInChat(chat.displayInfo) { @@ -91,19 +91,18 @@ struct XMTPEntitiesTransformer { } if var cachedMessage { - cachedMessage.displayInfo.isRead = isRead + cachedMessage.displayInfo.isRead = isRead return cachedMessage } let id = xmtpMessage.id let userId = chat.userId let metadataModel = XMTPEnvironmentNamespace.MessageServiceMetadata(encodedContent: xmtpMessage.encodedContent) - guard let serviceMetadata = metadataModel.jsonData(), - let type = try? await extractMessageType(from: xmtpMessage, - messageId: id, - userId: userId, - filesService: filesService) else { return nil } - + let serviceMetadata = metadataModel.jsonData() + let type = await extractMessageType(from: xmtpMessage, + messageId: id, + userId: userId, + filesService: filesService) let senderWallet = xmtpMessage.senderAddress let userDisplayInfo = MessagingChatUserDisplayInfo(wallet: senderWallet) let sender: MessagingChatSender @@ -128,42 +127,47 @@ struct XMTPEntitiesTransformer { let chatMessage = MessagingChatMessage(displayInfo: displayInfo, serviceMetadata: serviceMetadata) return chatMessage + } private static func extractMessageType(from xmtpMessage: XMTP.DecodedMessage, messageId: String, userId: String, - filesService: MessagingFilesServiceProtocol) async throws -> MessagingChatMessageDisplayType? { - if let knownType = XMTPMessageKnownTypeFrom(xmtpMessage) { - switch knownType { - case .text: - let decryptedContent: String = try xmtpMessage.content() - let textDisplayInfo = MessagingChatMessageTextTypeDisplayInfo(text: decryptedContent) - return .text(textDisplayInfo) - case .attachment: - let attachment: XMTP.Attachment = try xmtpMessage.content() - return try await getMessageTypeFor(attachment: attachment, - messageId: messageId, - userId: userId, - filesService: filesService) - case .remoteStaticAttachment: - let remoteAttachment: XMTP.RemoteAttachment = try xmtpMessage.content() - let attachmentProperties = RemoteAttachmentProperties(remoteAttachment: remoteAttachment) - let serviceData = try attachmentProperties.jsonDataThrowing() - let displayInfo = MessagingChatMessageRemoteContentTypeDisplayInfo(serviceData: serviceData) - return .remoteContent(displayInfo) + filesService: MessagingFilesServiceProtocol) async -> MessagingChatMessageDisplayType { + do { + if let knownType = XMTPMessageKnownTypeFrom(xmtpMessage) { + switch knownType { + case .text: + let decryptedContent: String = try xmtpMessage.content() + let textDisplayInfo = MessagingChatMessageTextTypeDisplayInfo(text: decryptedContent) + return .text(textDisplayInfo) + case .attachment: + let attachment: XMTP.Attachment = try xmtpMessage.content() + return try await getMessageTypeFor(attachment: attachment, + messageId: messageId, + userId: userId, + filesService: filesService) + case .remoteStaticAttachment: + let remoteAttachment: XMTP.RemoteAttachment = try xmtpMessage.content() + let attachmentProperties = RemoteAttachmentProperties(remoteAttachment: remoteAttachment) + let serviceData = try attachmentProperties.jsonDataThrowing() + let displayInfo = MessagingChatMessageRemoteContentTypeDisplayInfo(serviceData: serviceData) + return .remoteContent(displayInfo) + } + } else { + guard let contentData: Data = try? xmtpMessage.content() else { throw XMTPEntitiesTransformerError.failedToBuildMessageType } + + let fileName = messageId + "_" + String(userId.suffix(4)) + try filesService.saveData(contentData, fileName: fileName) + let unknownDisplayInfo = MessagingChatMessageUnknownTypeDisplayInfo(fileName: fileName, + type: XMTPMessageTypeIDFrom(xmtpMessage), + name: nil, + size: nil) + return .unknown(unknownDisplayInfo) } - } else { - guard let contentData: Data = try? xmtpMessage.content() else { return nil } - - let fileName = messageId + "_" + String(userId.suffix(4)) - try filesService.saveData(contentData, fileName: fileName) - let unknownDisplayInfo = MessagingChatMessageUnknownTypeDisplayInfo(fileName: fileName, - type: XMTPMessageTypeIDFrom(xmtpMessage), - name: nil, - size: nil) - return .unknown(unknownDisplayInfo) + } catch { + return .unsupported(MessagingChatMessageUnsupportedTypeDisplayInfo(data: Data())) /// No data could be extracted from XMTP message } } @@ -287,4 +291,8 @@ struct XMTPEntitiesTransformer { scheme: .init(rawValue: scheme)!) } } + + private enum XMTPEntitiesTransformerError: Error { + case failedToBuildMessageType + } } From 93907235d937470474e0aef0e21ef930d0d8f555 Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 5 Mar 2024 10:55:59 +0700 Subject: [PATCH 3/4] Filter unsupported messages on the chat view --- .../Modules/Messaging/Chat/ChatView/ChatViewModel.swift | 7 +++++-- .../Storage/CoreDataMessagingStorageService.swift | 7 +------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift index 57af4d7a9..1e7f3aed5 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift @@ -530,7 +530,8 @@ private extension ChatViewModel { let messages = serialQueue.sync { messages.filter { message in - if case .reaction(let info) = message.type { + switch message.type { + case .reaction(let info): let counter = MessageReactionDescription(content: info.content, messageId: message.id, referenceMessageId: info.messageId, @@ -541,7 +542,9 @@ private extension ChatViewModel { try? messagingService.markMessage(message, isRead: true, wallet: chat.thisUserDetails.wallet) } return false - } else { + case .unsupported: + return false + default: return true } } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift index 489d15623..f712ed654 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/Storage/CoreDataMessagingStorageService.swift @@ -806,12 +806,7 @@ private extension CoreDataMessagingStorageService { messageId: info.messageId).jsonStringThrowing() return try decrypterService.encryptText(content) case .unsupported(let info): - do { - return try encryptDataContent(info.data) - } catch { - print("Failed") - throw error - } + return try encryptDataContent(info.data) } } From e94f3a097ed0701c66080c23b0bf5160ec19926b Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 5 Mar 2024 11:05:33 +0700 Subject: [PATCH 4/4] Break load more messages loop after 30 sec --- .../Chat/ChatView/ChatViewModel.swift | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift index 1e7f3aed5..3a80a9776 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift @@ -17,7 +17,6 @@ final class ChatViewModel: ObservableObject, ViewAnalyticsLogger { private let messagingService: MessagingServiceProtocol private let featureFlagsService: UDFeatureFlagsServiceProtocol private(set) var conversationState: MessagingChatConversationState - private let fetchLimit: Int = 30 @Published private(set) var isLoadingMessages = false @Published private(set) var blockStatus: MessagingPrivateChatBlockingStatus = .unblocked @Published private(set) var isChannelEncrypted: Bool = true @@ -424,6 +423,7 @@ private extension ChatViewModel { func loadAndShowData() { Task { isLoading = true + let fetchLimit = ChatConstants.fetchLimit do { switch conversationState { case .existingChat(let chat): @@ -455,19 +455,24 @@ private extension ChatViewModel { } } - func loadMoreInitialMessagesIfNeeded(in chat: MessagingChatDisplayInfo) async throws { + func loadMoreInitialMessagesIfNeeded(in chat: MessagingChatDisplayInfo, startTime: Date = Date()) async throws { guard isNeedToLoadMoreInitialMessages(), - let earliestMessage = getEarliestMessageInCache() else { return } + let earliestMessage = getEarliestMessageInCache(), + !shouldBreakLoadMoreLoopDueToTimeout(startTime: startTime) else { return } let messages = try await createTaskAndLoadMoreMessagesIn(chat: chat, beforeMessage: earliestMessage) await addMessages(messages, scrollToBottom: true) - try await loadMoreInitialMessagesIfNeeded(in: chat) // Call recursively until sufficient number of visible messages is loaded + try await loadMoreInitialMessagesIfNeeded(in: chat, startTime: startTime) // Call recursively until sufficient number of visible messages is loaded + } + + func shouldBreakLoadMoreLoopDueToTimeout(startTime: Date) -> Bool { + Date().timeIntervalSince(startTime) > ChatConstants.loadMoreMessagesLimitSec } func isNeedToLoadMoreInitialMessages() -> Bool { // Number of visible messages (not including reactions, system messages, etc.) should be at least 20 - messages.count < 20 && + messages.count < ChatConstants.minRequiredNumberOfVisibleMessagesOnLaunch && !didReachFirstMessageInChat() } @@ -503,7 +508,7 @@ private extension ChatViewModel { try await messagingService.getMessagesForChat(chat, before: beforeMessage, cachedOnly: false, - limit: fetchLimit) + limit: ChatConstants.fetchLimit) } self.loadMoreMessagesTask = task let result = try await task.value @@ -517,7 +522,7 @@ private extension ChatViewModel { let cachedMessages = try await messagingService.getMessagesForChat(chat, before: nil, cachedOnly: true, - limit: fetchLimit) + limit: ChatConstants.fetchLimit) await addMessages(cachedMessages, scrollToBottom: false) } } @@ -1162,6 +1167,15 @@ extension ChatViewModel: UDFeatureFlagsListener { } } +// MARK: - Open methods +extension ChatViewModel { + struct ChatConstants { + static let fetchLimit = 30 + static let loadMoreMessagesLimitSec: TimeInterval = 30 + static let minRequiredNumberOfVisibleMessagesOnLaunch = 20 + } +} + #Preview { NavigationViewWithCustomTitle(content: { ChatView(viewModel: .init(profile: .init(id: "",