diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 53b27943a..1a0fb9f6d 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 21CE25E62650070300ADFF4B /* WkdUrlConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CE25E52650070300ADFF4B /* WkdUrlConstructor.swift */; }; 21EA3B592656611D00691848 /* ClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21EA3B15265647C400691848 /* ClientConfiguration.swift */; }; 21F836B62652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */; }; - 21FEE26626FDD91A00E3783F /* ComposeMessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */; }; 2C08F6BE273FA7B900EE1610 /* Version5SchemaMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C08F6BD273FA7B900EE1610 /* Version5SchemaMigration.swift */; }; 2C124DB42728809100A2EFA6 /* ApiCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C124DB32728809100A2EFA6 /* ApiCall.swift */; }; 2C141B2C274572D50038A3F8 /* Recipient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C141B2B274572D50038A3F8 /* Recipient.swift */; }; @@ -61,6 +60,7 @@ 5133B6702716320F00C95463 /* ContactKeyDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */; }; 5133B6722716321F00C95463 /* ContactKeyDetailDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */; }; 5133B6742716E5EA00C95463 /* LabelCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133B6732716E5EA00C95463 /* LabelCellNode.swift */; }; + 5168FB0B274F94D300131072 /* MessageAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5168FB0A274F94D300131072 /* MessageAttachment.swift */; }; 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */; }; 51775C39270C7D2400D7C944 /* StorageMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51775C38270C7D2400D7C944 /* StorageMethod.swift */; }; 5180CB9127356D48001FC7EF /* MessageSubjectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */; }; @@ -443,7 +443,6 @@ 21F836B52652A26B00B2448C /* DataExntensions+ZBase32Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataExntensions+ZBase32Encoding.swift"; sourceTree = ""; }; 21F836CB2652A38700B2448C /* ZBase32EncodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZBase32EncodingTests.swift; sourceTree = ""; }; 21F836D22652A46E00B2448C /* WKDURLsConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKDURLsConstructorTests.swift; sourceTree = ""; }; - 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMessageAttachment.swift; sourceTree = ""; }; 2C08F6BD273FA7B900EE1610 /* Version5SchemaMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version5SchemaMigration.swift; sourceTree = ""; }; 2C124DB32728809100A2EFA6 /* ApiCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCall.swift; sourceTree = ""; }; 2C141B2B274572D50038A3F8 /* Recipient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recipient.swift; sourceTree = ""; }; @@ -484,6 +483,7 @@ 5133B66F2716320F00C95463 /* ContactKeyDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailViewController.swift; sourceTree = ""; }; 5133B6712716321F00C95463 /* ContactKeyDetailDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactKeyDetailDecorator.swift; sourceTree = ""; }; 5133B6732716E5EA00C95463 /* LabelCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCellNode.swift; sourceTree = ""; }; + 5168FB0A274F94D300131072 /* MessageAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageAttachment.swift; sourceTree = ""; }; 51775C31270B01C200D7C944 /* PrvKeyInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrvKeyInfoTests.swift; sourceTree = ""; }; 51775C38270C7D2400D7C944 /* StorageMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageMethod.swift; sourceTree = ""; }; 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubjectNode.swift; sourceTree = ""; }; @@ -1348,7 +1348,6 @@ children = ( 9F6F3BEC26ADF5DE005BD9C6 /* ComposeMessageService.swift */, 9F6F3BED26ADF5DE005BD9C6 /* ComposeMessageError.swift */, - 21FEE26526FDD91A00E3783F /* ComposeMessageAttachment.swift */, ); path = "Compose Message Service"; sourceTree = ""; @@ -1427,6 +1426,7 @@ 9F93623E2573D16F0009912F /* Gmail+Message.swift */, 9F9362182573D10E0009912F /* Imap+Message.swift */, 9FA9C83B264C2D75005A9670 /* MessageService.swift */, + 5168FB0A274F94D300131072 /* MessageAttachment.swift */, ); path = "Message Provider"; sourceTree = ""; @@ -2539,6 +2539,7 @@ C132B9D91EC30E1D00763715 /* InboxViewController.swift in Sources */, 9F56BD2C23438A8500A7371A /* Imap+messages.swift in Sources */, 9F6F3BEE26ADF5DE005BD9C6 /* ComposeMessageService.swift in Sources */, + 5168FB0B274F94D300131072 /* MessageAttachment.swift in Sources */, C132B9CB1EC2DE6400763715 /* GeneralConstants.swift in Sources */, 5ADEDCBE23A4363700EC495E /* KeyDetailInfoViewController.swift in Sources */, D20D3C752520AB9A00D4AA9A /* BackupService.swift in Sources */, @@ -2690,7 +2691,6 @@ 32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */, 21489B78267CB42400BDE4AC /* LocalClientConfiguration.swift in Sources */, 9FF0671C25520D9D00FCC9E6 /* MailProvider.swift in Sources */, - 21FEE26626FDD91A00E3783F /* ComposeMessageAttachment.swift in Sources */, 9FE743072347AA54005E2DBB /* MainNavigationController.swift in Sources */, 9F5C2A8B257E6C4900DE9B4B /* ImapError.swift in Sources */, 9FAFD7592713870800321FA4 /* InboxViewController+State.swift in Sources */, diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index 576ccb988..91e01df4a 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -103,6 +103,7 @@ final class ComposeViewController: TableNodeViewController { self.passPhraseService = passPhraseService self.router = router self.contextToSend.subject = input.subject + self.contextToSend.attachments = input.attachments super.init(node: TableNode()) } @@ -157,7 +158,7 @@ final class ComposeViewController: TableNodeViewController { } } - func updateWithMessage(message: Message) { + func update(with message: Message) { self.contextToSend.subject = message.subject self.contextToSend.message = message.raw self.contextToSend.recipients = [ComposeMessageRecipient(email: "tom@flowcrypt.com", state: decorator.recipientIdleState)] @@ -558,8 +559,7 @@ extension ComposeViewController { return true } .then { - let subject = input.isQuote ? input.subjectQuoteTitle : contextToSend.subject - $0.attributedText = decorator.styledTitle(with: subject) + $0.attributedText = decorator.styledTitle(with: contextToSend.subject) } } @@ -625,7 +625,8 @@ extension ComposeViewController { private func attachmentNode(for index: Int) -> ASCellNode { AttachmentNode( input: .init( - composeAttachment: contextToSend.attachments[index] + attachment: contextToSend.attachments[index], + index: index ), onDeleteTap: { [weak self] in self?.contextToSend.attachments.safeRemove(at: index) @@ -924,7 +925,7 @@ extension ComposeViewController { extension ComposeViewController: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let fileUrl = urls.first, - let attachment = ComposeMessageAttachment(fileURL: fileUrl) + let attachment = MessageAttachment(fileURL: fileUrl) else { showAlert(message: "files_picking_files_error_message".localized) return @@ -946,7 +947,7 @@ extension ComposeViewController: PHPickerViewControllerDelegate { completionHandler: { [weak self] url, _ in guard let self = self else { return } DispatchQueue.main.async { - if let url = url, let composeMessageAttachment = ComposeMessageAttachment(fileURL: url) { + if let url = url, let composeMessageAttachment = MessageAttachment(fileURL: url) { self.appendAttachmentIfAllowed(composeMessageAttachment) self.node.reloadSections(IndexSet(integer: 2), with: .automatic) } else { @@ -960,7 +961,7 @@ extension ComposeViewController: PHPickerViewControllerDelegate { completionHandler: { [weak self] url, _ in guard let self = self else { return } DispatchQueue.main.async { - if let url = url, let composeMessageAttachment = ComposeMessageAttachment(fileURL: url) { + if let url = url, let composeMessageAttachment = MessageAttachment(fileURL: url) { self.appendAttachmentIfAllowed(composeMessageAttachment) self.node.reloadSections(IndexSet(integer: 2), with: .automatic) } else { @@ -981,10 +982,10 @@ extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationCo ) { picker.dismiss(animated: true, completion: nil) - let composeMessageAttachment: ComposeMessageAttachment? + let composeMessageAttachment: MessageAttachment? switch picker.sourceType { case .camera: - composeMessageAttachment = ComposeMessageAttachment(cameraSourceMediaInfo: info) + composeMessageAttachment = MessageAttachment(cameraSourceMediaInfo: info) default: fatalError("No other image picker's sources should be used") } guard let attachment = composeMessageAttachment else { @@ -995,8 +996,8 @@ extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationCo node.reloadSections(IndexSet(integer: 2), with: .automatic) } - private func appendAttachmentIfAllowed(_ attachment: ComposeMessageAttachment) { - let totalSize = contextToSend.attachments.reduce(0, { $0 + $1.size }) + attachment.size + private func appendAttachmentIfAllowed(_ attachment: MessageAttachment) { + let totalSize = contextToSend.attachments.map(\.size).reduce(0, +) + attachment.size if totalSize > GeneralConstants.Global.attachmentSizeLimit { showToast("files_picking_size_error_message".localized) } else { diff --git a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift index c430191e2..fb496c723 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewControllerInput.swift @@ -19,6 +19,7 @@ struct ComposeMessageInput: Equatable { let sentDate: Date let message: String let threadId: String? + let attachments: [MessageAttachment] } enum InputType: Equatable { @@ -40,11 +41,6 @@ struct ComposeMessageInput: Equatable { return info.recipients } - var subjectQuoteTitle: String? { - guard case let .quote(info) = type else { return nil } - return info.subject - } - var successfullySentToast: String { switch type { case .idle: return "compose_encrypted_sent".localized @@ -71,4 +67,9 @@ struct ComposeMessageInput: Equatable { guard case let .quote(info) = type else { return nil } return info.threadId } + + var attachments: [MessageAttachment] { + guard case let .quote(info) = type else { return [] } + return info.attachments + } } diff --git a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift index a9de78e3d..c72361073 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewDecorator.swift @@ -236,12 +236,13 @@ extension RecipientEmailsCellNode.Input { // MARK: - AttachmentNode.Input extension AttachmentNode.Input { - init(composeAttachment: ComposeMessageAttachment) { + init(attachment: MessageAttachment, index: Int) { self.init( - name: composeAttachment.name + name: attachment.name .attributed(.regular(18), color: .mainTextColor, alignment: .left), - size: composeAttachment.humanReadableSizeString - .attributed(.medium(12), color: .mainTextColor, alignment: .left) + size: attachment.formattedSize + .attributed(.medium(12), color: .mainTextColor, alignment: .left), + index: index ) } } diff --git a/FlowCrypt/Controllers/Inbox/InboxViewController+State.swift b/FlowCrypt/Controllers/Inbox/InboxViewController+State.swift index 274fd16b7..40602231a 100644 --- a/FlowCrypt/Controllers/Inbox/InboxViewController+State.swift +++ b/FlowCrypt/Controllers/Inbox/InboxViewController+State.swift @@ -5,7 +5,7 @@ // Created by Anton Kharchevskyi on 10.10.2021 // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // - + import Foundation extension InboxViewController { diff --git a/FlowCrypt/Controllers/MessageList Extension/MsgListViewController.swift b/FlowCrypt/Controllers/MessageList Extension/MsgListViewController.swift index c768d426c..a8a399ec5 100644 --- a/FlowCrypt/Controllers/MessageList Extension/MsgListViewController.swift +++ b/FlowCrypt/Controllers/MessageList Extension/MsgListViewController.swift @@ -32,7 +32,7 @@ extension MsgListViewController where Self: UIViewController { guard let email = DataService.shared.email else { return } let controller = ComposeViewController(email: email) - controller.updateWithMessage(message: message) + controller.update(with: message) navigationController?.pushViewController(controller, animated: true) } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 3a8e6221b..5291622f0 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -61,12 +61,13 @@ extension ProcessedMessage { } extension AttachmentNode.Input { - init(msgAttachment: MessageAttachment) { + init(msgAttachment: MessageAttachment, index: Int) { self.init( name: msgAttachment.name .attributed(.regular(18), color: .textColor, alignment: .left), - size: msgAttachment.humanReadableSizeString - .attributed(.medium(12), color: .textColor, alignment: .left) + size: msgAttachment.formattedSize + .attributed(.medium(12), color: .textColor, alignment: .left), + index: index ) } } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 540cb7c95..c61a5c27c 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -147,15 +147,16 @@ extension ThreadDetailsViewController { let processedMessage = input.processedMessage else { return } - let recipients: [String] - switch quoteType { - case .reply: - recipients = [input.rawMessage.sender].compactMap { $0 } - case .forward: - recipients = [] - } + let recipients = quoteType == .reply + ? [input.rawMessage.sender].compactMap({ $0 }) + : [] + + let attachments = quoteType == .forward + ? input.processedMessage?.attachments ?? [] + : [] let subject = input.rawMessage.subject ?? "(no subject)" + let threadId = quoteType == .reply ? input.rawMessage.threadId : nil let replyInfo = ComposeMessageInput.MessageQuoteInfo( recipients: recipients, @@ -164,7 +165,8 @@ extension ThreadDetailsViewController { mime: processedMessage.rawMimeData, sentDate: input.rawMessage.date, message: processedMessage.text, - threadId: input.rawMessage.threadId + threadId: threadId, + attachments: attachments ) let composeInput = ComposeMessageInput(type: .quote(replyInfo)) @@ -445,7 +447,8 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { let attachment = message.attachments[indexPath.row - 2] return AttachmentNode( input: .init( - msgAttachment: attachment + msgAttachment: attachment, + index: indexPath.row - 2 ), onDownloadTap: { [weak self] in self?.attachmentManager.open(attachment) } ) diff --git a/FlowCrypt/Functionality/FilesManager/FilesManager.swift b/FlowCrypt/Functionality/FilesManager/FilesManager.swift index 41b794619..daf53265c 100644 --- a/FlowCrypt/Functionality/FilesManager/FilesManager.swift +++ b/FlowCrypt/Functionality/FilesManager/FilesManager.swift @@ -11,10 +11,17 @@ import UIKit protocol FileType { var name: String { get } - var size: Int { get } var data: Data { get } } +extension FileType { + var size: Int { data.count } + var formattedSize: String { + ByteCountFormatter().string(fromByteCount: Int64(size)) + } + var type: String { name.mimeType } +} + protocol FilesManagerPresenter { func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) } diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageAttachment.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageAttachment.swift similarity index 57% rename from FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageAttachment.swift rename to FlowCrypt/Functionality/Mail Provider/Message Provider/MessageAttachment.swift index 2639f661a..0f9fd70a7 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageAttachment.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageAttachment.swift @@ -1,26 +1,20 @@ // -// ComposeMessageAttachment.swift +// MessageAttachment.swift // FlowCrypt // -// Created by Yevhen Kyivskyi on 24.09.2021. +// Created by Roma Sosnovsky on 25/11/21 // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import UIKit import Photos +import UIKit -struct ComposeMessageAttachment: Equatable { +struct MessageAttachment: Equatable, FileType { let name: String - let size: Int let data: Data - let type: String - var humanReadableSizeString: String { - return ByteCountFormatter().string(fromByteCount: Int64(self.size)) - } } -extension ComposeMessageAttachment { - +extension MessageAttachment { init?(cameraSourceMediaInfo: [UIImagePickerController.InfoKey: Any]) { guard let image = cameraSourceMediaInfo[.originalImage] as? UIImage, let data = image.jpegData(compressionQuality: 1) else { @@ -29,8 +23,6 @@ extension ComposeMessageAttachment { self.name = "\(UUID().uuidString).jpg" self.data = data - self.size = data.count - self.type = "image/jpg" } init?(fileURL: URL) { @@ -40,11 +32,11 @@ extension ComposeMessageAttachment { self.name = fileURL.lastPathComponent self.data = data - self.size = data.count - self.type = fileURL.mimeType } +} +extension MessageAttachment { func toSendableMsgAttachment() -> SendableMsg.Attachment { - return SendableMsg.Attachment( name: self.name, type: self.type, base64: self.data.base64EncodedString()) + return SendableMsg.Attachment(name: name, type: type, base64: data.base64EncodedString()) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 323d46916..fb1251f65 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -10,16 +10,6 @@ import Foundation import FlowCryptCommon import UIKit -// MARK: - MessageAttachment -struct MessageAttachment: FileType { - let name: String - let size: Int - let data: Data - var humanReadableSizeString: String { - return ByteCountFormatter().string(fromByteCount: Int64(self.size)) - } -} - // MARK: - MessageFetchState enum MessageFetchState { case fetch, download(Float), decrypt @@ -232,23 +222,25 @@ final class MessageService { keys: [PrvKeyInfo] ) async throws -> [MessageAttachment] { let attachmentBlocks = blocks.filter(\.isAttachmentBlock) - var result: [MessageAttachment] = [] + + var attachments: [MessageAttachment] = [] for block in attachmentBlocks { guard let meta = block.attMeta else { continue } - var name = meta.name - var data = meta.data - var size = meta.length + let attachment: MessageAttachment if block.type == .encryptedAtt { // decrypt - let decrypted = try await core.decryptFile(encrypted: data, keys: keys, msgPwd: nil) - data = decrypted.content - name = decrypted.name - size = decrypted.content.count + let decrypted = try await core.decryptFile(encrypted: meta.data, keys: keys, msgPwd: nil) + attachment = MessageAttachment(name: decrypted.name, + data: decrypted.content) + } else { + attachment = MessageAttachment(name: meta.name, + data: meta.data) } - result.append(MessageAttachment(name: name, size: size, data: data)) + attachments.append(attachment) } - return result + + return attachments } private func hasMsgBlockThatNeedsPassPhrase(_ msg: CoreRes.ParseDecryptMsg) -> Bool { @@ -295,7 +287,6 @@ extension MessageService { private extension MessageAttachment { init(block: MsgBlock) { self.name = block.attMeta?.name ?? "Attachment" - self.size = block.attMeta?.length ?? 0 self.data = block.attMeta?.data ?? Data() } } diff --git a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift index 12caf68e4..c01082c35 100644 --- a/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift +++ b/FlowCrypt/Functionality/Services/Compose Message Service/ComposeMessageService.swift @@ -16,7 +16,7 @@ struct ComposeMessageContext: Equatable { var message: String? var recipients: [ComposeMessageRecipient] = [] var subject: String? - var attachments: [ComposeMessageAttachment] = [] + var attachments: [MessageAttachment] = [] } struct ComposeMessageRecipient: Equatable { @@ -86,9 +86,7 @@ final class ComposeMessageService { throw MessageValidationError.emptyMessage } - let subject = input.subjectQuoteTitle - ?? contextToSend.subject - ?? "(no subject)" + let subject = contextToSend.subject ?? "(no subject)" guard let myPubKey = self.dataService.publicKey() else { throw MessageValidationError.missedPublicKey diff --git a/FlowCryptAppTests/Functionality/FilesManager/FileMock.swift b/FlowCryptAppTests/Functionality/FilesManager/FileMock.swift index db4c186cd..0eeb288e3 100644 --- a/FlowCryptAppTests/Functionality/FilesManager/FileMock.swift +++ b/FlowCryptAppTests/Functionality/FilesManager/FileMock.swift @@ -10,14 +10,12 @@ import Foundation struct FileMock: FileType { let name: String - let size: Int let data: Data } extension FileMock { static let stringedFile = FileMock( name: "mock_file.pdf", - size: 125, data: "mocktext".data(using: .utf8)! ) } diff --git a/FlowCryptUI/Nodes/AttachmentNode.swift b/FlowCryptUI/Nodes/AttachmentNode.swift index 898f8a99d..2b62038ea 100644 --- a/FlowCryptUI/Nodes/AttachmentNode.swift +++ b/FlowCryptUI/Nodes/AttachmentNode.swift @@ -9,11 +9,18 @@ public final class AttachmentNode: CellNode { public struct Input { let name: NSAttributedString let size: NSAttributedString + let index: Int - public init(name: NSAttributedString, size: NSAttributedString) { + public init(name: NSAttributedString, size: NSAttributedString, index: Int) { self.name = name self.size = size + self.index = index } + + var cellIdentifier: String { "attachmentCell\(index)" } + var titleLabelIdentifier: String { "attachmentTitleLabel\(index)" } + var deleteButtonIdentifier: String { "attachmentDeleteButton\(index)" } + var downloadButtonIdentifier: String { "attachmentDownloadButton\(index)" } } private let titleNode = ASTextNode() @@ -34,6 +41,9 @@ public final class AttachmentNode: CellNode { self.onDownloadTap = onDownloadTap self.onDeleteTap = onDeleteTap super.init() + + accessibilityIdentifier = input.cellIdentifier + automaticallyManagesSubnodes = true borderNode.borderWidth = 1.0 borderNode.cornerRadius = 8.0 @@ -42,12 +52,16 @@ public final class AttachmentNode: CellNode { imageNode.tintColor = .gray buttonNode.tintColor = .gray + deleteButtonNode.setImage(UIImage(named: "cancel")?.tinted(.gray), for: .normal) + deleteButtonNode.accessibilityIdentifier = input.deleteButtonIdentifier + imageNode.image = UIImage(named: "paperclip")?.tinted(.gray) buttonNode.setImage(UIImage(named: "download")?.tinted(.gray), for: .normal) - buttonNode.accessibilityIdentifier = "downloadButton" + buttonNode.accessibilityIdentifier = input.downloadButtonIdentifier titleNode.attributedText = input.name + titleNode.accessibilityIdentifier = input.titleLabelIdentifier subtitleNode.attributedText = input.size buttonNode.addTarget(self, action: #selector(onDownloadButtonTap), forControlEvents: .touchUpInside) diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index 44dc622f3..3f83e12f1 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -7,7 +7,7 @@ const SELECTORS = { ENTER_PASS_PHRASE_FIELD: '-ios class chain:**/XCUIElementTypeSecureTextField', OK_BUTTON: '~Ok', WRONG_PASS_PHRASE_MESSAGE: '-ios class chain:**/XCUIElementTypeStaticText[`label == "Wrong pass phrase, please try again"`]', - DOWNLOAD_ATTACHMENT_BUTTON: '~downloadButton', + DOWNLOAD_ATTACHMENT_BUTTON: '~attachmentDownloadButton0', REPLY_BUTTON: '~replyButton', MENU_BUTTON: '~messageMenuButton', FORWARD_BUTTON: '~Forward', diff --git a/appium/tests/screenobjects/new-message.screen.ts b/appium/tests/screenobjects/new-message.screen.ts index f232e6831..29750ca24 100644 --- a/appium/tests/screenobjects/new-message.screen.ts +++ b/appium/tests/screenobjects/new-message.screen.ts @@ -10,6 +10,9 @@ const SELECTORS = { '/XCUIElementTypeOther/XCUIElementTypeOther[1]/XCUIElementTypeOther/XCUIElementTypeTable' + '/XCUIElementTypeCell[1]/XCUIElementTypeOther/XCUIElementTypeCollectionView/XCUIElementTypeCell' + '/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeStaticText', //it works only with this selector + ATTACHMENT_CELL: '~attachmentCell0', + ATTACHMENT_NAME_LABEL: '~attachmentTitleLabel0', + DELETE_ATTACHMENT_BUTTON: '~attachmentDeleteButton0', RETURN_BUTTON: '~Return', BACK_BUTTON: '~arrow left c', SEND_BUTTON: '~android send', @@ -42,6 +45,18 @@ class NewMessageScreen extends BaseScreen { return $(SELECTORS.ADDED_RECIPIENT); } + get attachmentCell() { + return $(SELECTORS.ATTACHMENT_CELL); + } + + get attachmentNameLabel() { + return $(SELECTORS.ATTACHMENT_NAME_LABEL); + } + + get deleteAttachmentButton() { + return $(SELECTORS.DELETE_ATTACHMENT_BUTTON); + } + get backButton() { return $(SELECTORS.BACK_BUTTON); } @@ -88,8 +103,9 @@ class NewMessageScreen extends BaseScreen { await ElementHelper.waitAndClick(await $(`~${email}`)); }; - checkFilledComposeEmailInfo = async (recipient: string, subject: string, message: string) => { + checkFilledComposeEmailInfo = async (recipient: string, subject: string, message: string, attachmentName?: string) => { expect(this.composeSecurityMessage).toHaveText(message); + const element = await this.filledSubject(subject); await element.waitForDisplayed(); @@ -98,6 +114,10 @@ class NewMessageScreen extends BaseScreen { } else { await this.checkAddedRecipient(recipient); } + + if (attachmentName !== undefined) { + await this.checkAddedAttachment(attachmentName); + } }; checkEmptyRecipientsList = async () => { @@ -112,6 +132,18 @@ class NewMessageScreen extends BaseScreen { expect(value).toEqual(` ${recipient} `); }; + checkAddedAttachment = async (name: string) => { + await (await this.deleteAttachmentButton).waitForDisplayed(); + const label = await this.attachmentNameLabel; + const value = await label.getValue(); + expect(value).toEqual(name); + } + + deleteAttachment = async () => { + await ElementHelper.waitAndClick(await this.deleteAttachmentButton); + await ElementHelper.waitElementInvisible(await this.attachmentCell); + } + clickBackButton = async () => { await ElementHelper.waitAndClick(await this.backButton); } diff --git a/appium/tests/specs/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts b/appium/tests/specs/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts index 786b51218..f4329be14 100644 --- a/appium/tests/specs/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts +++ b/appium/tests/specs/inbox/CheckReplyAndForwardForEncryptedEmail.spec.ts @@ -13,12 +13,13 @@ describe('INBOX: ', () => { it('user is able to reply or forward email and check info from composed email', async () => { const senderEmail = CommonData.sender.email; - const emailSubject = CommonData.encryptedEmail.subject; - const emailText = CommonData.encryptedEmail.message; + const emailSubject = CommonData.encryptedEmailWithAttachment.subject; + const emailText = CommonData.encryptedEmailWithAttachment.message; + const attachmentName = CommonData.encryptedEmailWithAttachment.attachmentName; const replySubject = `Re: ${emailSubject}`; const forwardSubject = `Fwd: ${emailSubject}`; - const quoteText = `On 10/26/21 at 2:43 PM ${senderEmail} wrote:\n > ${emailText}`; + const quoteText = `On 11/5/21 at 4:15 AM ${senderEmail} wrote:\n > ${emailText}`; await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); @@ -33,6 +34,7 @@ describe('INBOX: ', () => { await NewMessageScreen.clickBackButton(); await EmailScreen.clickMenuButton(); await EmailScreen.clickForwardButton(); - await NewMessageScreen.checkFilledComposeEmailInfo("", forwardSubject, quoteText); + await NewMessageScreen.checkFilledComposeEmailInfo("", forwardSubject, quoteText, attachmentName); + await NewMessageScreen.deleteAttachment(); }); });