From b88d138fcc09ec38343f7797ac86757d13c55b26 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Mon, 29 Nov 2021 22:31:58 +0200 Subject: [PATCH 01/10] issue #921 show full recipients list --- .../Threads/ThreadDetailsDecorator.swift | 7 +++-- .../Threads/ThreadDetailsViewController.swift | 31 ++++++++++--------- .../Mail Provider/Gmail/GmailService.swift | 1 + .../MessagesList Provider/Model/Message.swift | 16 +++++++++- .../Threads/MessagesThreadProvider.swift | 7 +++-- .../Resources/en.lproj/Localizable.strings | 2 ++ .../ThreadMessageSenderCellNode.swift | 13 ++++++-- appium/tests/screenobjects/email.screen.ts | 2 +- 8 files changed, 55 insertions(+), 24 deletions(-) diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index e6b012622..59b11b4c7 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -12,12 +12,14 @@ import UIKit extension ThreadMessageSenderCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized + let recipientPrefix = "to".localized + let recipientLabel = "\(recipientPrefix) \(threadMessage.rawMessage.recipientsList)" let date = DateFormatter().formatDate(threadMessage.rawMessage.date) let isMessageRead = threadMessage.rawMessage.isMessageRead let style: NSAttributedString.Style = isMessageRead - ? .regular(17) - : .bold(17) + ? .regular(16) + : .bold(16) let dateColor: UIColor = isMessageRead ? .lightGray @@ -31,6 +33,7 @@ extension ThreadMessageSenderCellNode.Input { encryptionBadge: makeEncryptionBadge(threadMessage), signatureBadge: makeSignatureBadge(threadMessage), sender: NSAttributedString.text(from: sender, style: style, color: textColor), + recipient: NSAttributedString.text(from: recipientLabel, style: style, color: textColor), date: NSAttributedString.text(from: date, style: style, color: dateColor), isExpanded: threadMessage.isExpanded, buttonColor: .messageButtonColor, diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 410627dae..1a9ff05d5 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -429,29 +429,30 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { return MessageSubjectNode(subject.attributed(.medium(18))) } - let section = self.input[indexPath.section-1] + let message = self.input[indexPath.section-1] if indexPath.row == 0 { return ThreadMessageSenderCellNode( - input: .init(threadMessage: section), + input: .init(threadMessage: message), onReplyTap: { [weak self] _ in self?.handleReplyTap(at: indexPath) }, onMenuTap: { [weak self] _ in self?.handleMenuTap(at: indexPath) } ) } - if indexPath.row == 1, let message = section.processedMessage { - return MessageTextSubjectNode(message.attributedMessage) - } - - if indexPath.row > 1, let message = section.processedMessage { - let attachment = message.attachments[indexPath.row - 2] - return AttachmentNode( - input: .init( - msgAttachment: attachment, - index: indexPath.row - 2 - ), - onDownloadTap: { [weak self] in self?.attachmentManager.open(attachment) } - ) + if let processedMessage = message.processedMessage { + if indexPath.row == 1 { + return MessageTextSubjectNode(processedMessage.attributedMessage) + } else if indexPath.row > 1 { + let attachmentIndex = indexPath.row - 2 + let attachment = processedMessage.attachments[attachmentIndex] + return AttachmentNode( + input: .init( + msgAttachment: attachment, + index: attachmentIndex + ), + onDownloadTap: { [weak self] in self?.attachmentManager.open(attachment) } + ) + } } return ASCellNode() diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index dc0407985..83d3d3dda 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -53,5 +53,6 @@ extension String { static let subject = "subject" static let date = "date" static let to = "to" + static let cc = "cc" static let identifier = "Message-ID" } diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index 06c7dfb65..fad66dc2d 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -15,6 +15,7 @@ struct Message: Hashable { let date: Date let sender: String? let recipient: String? + let cc: String? let subject: String? let size: Int? let attachmentIds: [String] @@ -47,7 +48,8 @@ struct Message: Hashable { threadId: String? = nil, draftIdentifier: String? = nil, raw: String? = nil, - recipient: String? = nil + recipient: String? = nil, + cc: String? = nil ) { self.identifier = identifier self.date = date @@ -60,6 +62,7 @@ struct Message: Hashable { self.draftIdentifier = draftIdentifier self.raw = raw self.recipient = recipient + self.cc = cc } } @@ -84,6 +87,17 @@ extension Message { } return copy } + + var recipientsList: String { + [recipient, cc] + .compactMap { $0 } + .flatMap { $0.components(separatedBy: ", ") } + .compactMap { + $0.components(separatedBy: " ").first? + .components(separatedBy: "@").first + } + .joined(separator: ", ") + } } struct Identifier: Equatable, Hashable { diff --git a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift index 526d489bb..6a9f50656 100644 --- a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift @@ -148,14 +148,16 @@ extension Message { var sender: String? var subject: String? var recipient: String? + var cc: String? - messageHeaders.compactMap { $0 }.forEach { + messageHeaders.compactMap({ $0 }).forEach { guard let name = $0.name?.lowercased() else { return } let value = $0.value switch name { case .from: sender = value case .subject: subject = value case .to: recipient = value + case .cc: cc = value default: break } } @@ -178,7 +180,8 @@ extension Message { threadId: message.threadId, draftIdentifier: draftIdentifier, raw: message.raw, - recipient: recipient + recipient: recipient, + cc: cc ) } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index e783ad01e..cf3498231 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -15,6 +15,7 @@ "allow" = "Allow"; "later" = "Later"; "forward" = "Forward"; +"to" = "to"; // EMAIL "email_removed" = "Email moved to Trash"; @@ -26,6 +27,7 @@ "message_failed_load" = "Failed to load messages"; "message_compose_secure" = "Compose Secure Message"; "message_unknown_sender" = "(unknown sender)"; +"message_no_recipients" = "(no recipients)"; "message_missed_subject" = "No subject"; "message_attachment_saved_successfully_title" = "Attachment Saved"; "message_attachment_saved_successfully_message" = "Your attachment was saved in Files. Would you like to open it?"; diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift index 9ebf5512a..cda9959cc 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift @@ -14,6 +14,7 @@ public final class ThreadMessageSenderCellNode: CellNode { public let encryptionBadge: BadgeNode.Input public let signatureBadge: BadgeNode.Input? public let sender: NSAttributedString + public let recipient: NSAttributedString public let date: NSAttributedString? public let isExpanded: Bool public let buttonColor: UIColor @@ -22,6 +23,7 @@ public final class ThreadMessageSenderCellNode: CellNode { public init(encryptionBadge: BadgeNode.Input, signatureBadge: BadgeNode.Input?, sender: NSAttributedString, + recipient: NSAttributedString, date: NSAttributedString, isExpanded: Bool, buttonColor: UIColor, @@ -29,6 +31,7 @@ public final class ThreadMessageSenderCellNode: CellNode { self.encryptionBadge = encryptionBadge self.signatureBadge = signatureBadge self.sender = sender + self.recipient = recipient self.date = date self.isExpanded = isExpanded self.buttonColor = buttonColor @@ -54,6 +57,7 @@ public final class ThreadMessageSenderCellNode: CellNode { }() private let senderNode = ASTextNode2() + private let recipientNode = ASTextNode2() private let dateNode = ASTextNode2() public private(set) var replyNode = ASButtonNode() @@ -74,9 +78,12 @@ public final class ThreadMessageSenderCellNode: CellNode { automaticallyManagesSubnodes = true senderNode.attributedText = input.sender - senderNode.accessibilityIdentifier = "senderEmail" - dateNode.attributedText = input.date + senderNode.accessibilityIdentifier = "messageSenderLabel" + + recipientNode.attributedText = input.recipient + recipientNode.accessibilityIdentifier = "messageRecipientLabel" + dateNode.attributedText = input.date setupReplyNode() setupMenuNode() @@ -131,7 +138,7 @@ public final class ThreadMessageSenderCellNode: CellNode { spacing: 4, justifyContent: .spaceBetween, alignItems: .start, - children: [senderNode, dateNode] + children: [senderNode, recipientNode, dateNode] ) infoNode.style.flexGrow = 1 infoNode.style.flexShrink = 1 diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index e6ea1597e..4f861bec5 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -13,7 +13,7 @@ const SELECTORS = { FORWARD_BUTTON: '~Forward', DELETE_BUTTON: '~Delete', CONFIRM_DELETING: '~OK', - SENDER_EMAIL: '~senderEmail', + SENDER_EMAIL: '~messageSenderLabel', ENCRYPTION_BADGE: '~encryptionBadge', SIGNATURE_BADGE: '~signatureBadge' }; From 31e39bc5060e3bbbf8c114510ade02bd0ebaba7f Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 30 Nov 2021 15:51:57 +0200 Subject: [PATCH 02/10] issue #921 show cc and bcc recipients --- .../Threads/ThreadDetailsDecorator.swift | 6 +- .../Threads/ThreadDetailsViewController.swift | 19 ++- .../Mail Provider/Gmail/GmailService.swift | 1 + .../MessagesList Provider/Model/Message.swift | 7 +- .../Threads/MessagesThreadProvider.swift | 5 +- .../Resources/en.lproj/Localizable.strings | 2 + .../ThreadMessageSenderCellNode.swift | 108 ++++++++++++++++-- 7 files changed, 131 insertions(+), 17 deletions(-) diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 59b11b4c7..c8ec99549 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -33,9 +33,13 @@ extension ThreadMessageSenderCellNode.Input { encryptionBadge: makeEncryptionBadge(threadMessage), signatureBadge: makeSignatureBadge(threadMessage), sender: NSAttributedString.text(from: sender, style: style, color: textColor), - recipient: NSAttributedString.text(from: recipientLabel, style: style, color: textColor), + recipientLabel: NSAttributedString.text(from: recipientLabel, style: style, color: textColor), + recipients: threadMessage.rawMessage.recipient?.components(separatedBy: ", ") ?? [], + ccRecipients: threadMessage.rawMessage.cc?.components(separatedBy: ", ") ?? [], + bccRecipients: threadMessage.rawMessage.bcc?.components(separatedBy: ", ") ?? [], date: NSAttributedString.text(from: date, style: style, color: dateColor), isExpanded: threadMessage.isExpanded, + shouldShowRecipientsList: threadMessage.shouldShowRecipientsList, buttonColor: .messageButtonColor, nodeInsets: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 8) ) diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 1a9ff05d5..7ce915320 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -18,11 +18,13 @@ final class ThreadDetailsViewController: TableNodeViewController { class Input { var rawMessage: Message var isExpanded: Bool + var shouldShowRecipientsList: Bool var processedMessage: ProcessedMessage? - init(message: Message, isExpanded: Bool) { + init(message: Message, isExpanded: Bool = false, shouldShowRecipientsList: Bool = false) { self.rawMessage = message self.isExpanded = isExpanded + self.shouldShowRecipientsList = shouldShowRecipientsList } } @@ -66,7 +68,7 @@ final class ThreadDetailsViewController: TableNodeViewController { self.onComplete = completion self.input = thread.messages .sorted(by: >) - .map { Input(message: $0, isExpanded: false) } + .map { Input(message: $0) } super.init(node: TableNode()) } @@ -124,6 +126,14 @@ extension ThreadDetailsViewController { } } + private func handleRecipientsTap(at indexPath: IndexPath) { + input[indexPath.section - 1].shouldShowRecipientsList.toggle() + + UIView.animate(withDuration: 0.3) { + self.node.reloadSections(IndexSet(integer: indexPath.section), with: .automatic) + } + } + private func handleReplyTap(at indexPath: IndexPath) { composeNewMessage(at: indexPath, quoteType: .reply) } @@ -161,7 +171,7 @@ extension ThreadDetailsViewController { let replyInfo = ComposeMessageInput.MessageQuoteInfo( recipients: recipients, sender: input.rawMessage.sender, - subject: "\(quoteType.subjectPrefix)\(subject)", + subject: [quoteType.subjectPrefix, subject].joined(), mime: processedMessage.rawMimeData, sentDate: input.rawMessage.date, message: processedMessage.text, @@ -435,7 +445,8 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { return ThreadMessageSenderCellNode( input: .init(threadMessage: message), onReplyTap: { [weak self] _ in self?.handleReplyTap(at: indexPath) }, - onMenuTap: { [weak self] _ in self?.handleMenuTap(at: indexPath) } + onMenuTap: { [weak self] _ in self?.handleMenuTap(at: indexPath) }, + onRecipientsTap: { [weak self] _ in self?.handleRecipientsTap(at: indexPath) } ) } diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index 83d3d3dda..6ced31c9a 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -54,5 +54,6 @@ extension String { static let date = "date" static let to = "to" static let cc = "cc" + static let bcc = "bcc" static let identifier = "Message-ID" } diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index fad66dc2d..395878037 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -16,6 +16,7 @@ struct Message: Hashable { let sender: String? let recipient: String? let cc: String? + let bcc: String? let subject: String? let size: Int? let attachmentIds: [String] @@ -49,7 +50,8 @@ struct Message: Hashable { draftIdentifier: String? = nil, raw: String? = nil, recipient: String? = nil, - cc: String? = nil + cc: String? = nil, + bcc: String? = nil ) { self.identifier = identifier self.date = date @@ -63,6 +65,7 @@ struct Message: Hashable { self.raw = raw self.recipient = recipient self.cc = cc + self.bcc = bcc } } @@ -89,7 +92,7 @@ extension Message { } var recipientsList: String { - [recipient, cc] + [recipient, cc, bcc] .compactMap { $0 } .flatMap { $0.components(separatedBy: ", ") } .compactMap { diff --git a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift index 6a9f50656..5e3ad8f28 100644 --- a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift @@ -149,6 +149,7 @@ extension Message { var subject: String? var recipient: String? var cc: String? + var bcc: String? messageHeaders.compactMap({ $0 }).forEach { guard let name = $0.name?.lowercased() else { return } @@ -158,6 +159,7 @@ extension Message { case .subject: subject = value case .to: recipient = value case .cc: cc = value + case .bcc: bcc = value default: break } } @@ -181,7 +183,8 @@ extension Message { draftIdentifier: draftIdentifier, raw: message.raw, recipient: recipient, - cc: cc + cc: cc, + bcc: bcc ) } } diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index cf3498231..f6754990a 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -16,6 +16,8 @@ "later" = "Later"; "forward" = "Forward"; "to" = "to"; +"cc" = "cc"; +"bcc" = "bcc"; // EMAIL "email_removed" = "Email moved to Trash"; diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift index cda9959cc..f6bdee0a9 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift @@ -14,26 +14,38 @@ public final class ThreadMessageSenderCellNode: CellNode { public let encryptionBadge: BadgeNode.Input public let signatureBadge: BadgeNode.Input? public let sender: NSAttributedString - public let recipient: NSAttributedString + public let recipientLabel: NSAttributedString + public let recipients: [String] + public let ccRecipients: [String] + public let bccRecipients: [String] public let date: NSAttributedString? public let isExpanded: Bool + public let shouldShowRecipientsList: Bool public let buttonColor: UIColor public let nodeInsets: UIEdgeInsets public init(encryptionBadge: BadgeNode.Input, signatureBadge: BadgeNode.Input?, sender: NSAttributedString, - recipient: NSAttributedString, + recipientLabel: NSAttributedString, + recipients: [String], + ccRecipients: [String], + bccRecipients: [String], date: NSAttributedString, isExpanded: Bool, + shouldShowRecipientsList: Bool, buttonColor: UIColor, nodeInsets: UIEdgeInsets) { self.encryptionBadge = encryptionBadge self.signatureBadge = signatureBadge self.sender = sender - self.recipient = recipient + self.recipientLabel = recipientLabel + self.recipients = recipients + self.ccRecipients = ccRecipients + self.bccRecipients = bccRecipients self.date = date self.isExpanded = isExpanded + self.shouldShowRecipientsList = shouldShowRecipientsList self.buttonColor = buttonColor self.nodeInsets = nodeInsets } @@ -57,7 +69,7 @@ public final class ThreadMessageSenderCellNode: CellNode { }() private let senderNode = ASTextNode2() - private let recipientNode = ASTextNode2() + private let recipientNode = ASButtonNode() private let dateNode = ASTextNode2() public private(set) var replyNode = ASButtonNode() @@ -65,22 +77,32 @@ public final class ThreadMessageSenderCellNode: CellNode { public private(set) var expandNode = ASImageNode() private let input: ThreadMessageSenderCellNode.Input - private var onReplyTap: ((ThreadMessageSenderCellNode) -> Void)? - private var onMenuTap: ((ThreadMessageSenderCellNode) -> Void)? + + private let onReplyTap: ((ThreadMessageSenderCellNode) -> Void)? + private let onMenuTap: ((ThreadMessageSenderCellNode) -> Void)? + private let onRecipientsTap: ((ThreadMessageSenderCellNode) -> Void)? + + private enum RecipientType: String, CaseIterable { + case to, cc, bcc + } public init(input: ThreadMessageSenderCellNode.Input, onReplyTap: ((ThreadMessageSenderCellNode) -> Void)?, - onMenuTap: ((ThreadMessageSenderCellNode) -> Void)?) { + onMenuTap: ((ThreadMessageSenderCellNode) -> Void)?, + onRecipientsTap: ((ThreadMessageSenderCellNode) -> Void)?) { self.input = input self.onReplyTap = onReplyTap self.onMenuTap = onMenuTap + self.onRecipientsTap = onRecipientsTap + super.init() automaticallyManagesSubnodes = true senderNode.attributedText = input.sender senderNode.accessibilityIdentifier = "messageSenderLabel" - recipientNode.attributedText = input.recipient + recipientNode.setAttributedTitle(input.recipientLabel, for: .normal) + recipientNode.addTarget(self, action: #selector(onRecipientsNodeTap), forControlEvents: .touchUpInside) recipientNode.accessibilityIdentifier = "messageRecipientLabel" dateNode.attributedText = input.date @@ -128,17 +150,85 @@ public final class ThreadMessageSenderCellNode: CellNode { onMenuTap?(self) } + @objc private func onRecipientsNodeTap() { + onRecipientsTap?(self) + } + + private func recipientList(label: String, recipients: [String]) -> ASStackLayoutSpec? { + guard recipients.isNotEmpty else { return nil } + + let labelNode = ASTextNode2() + labelNode.attributedText = label.localizedCapitalized.attributed() + labelNode.style.preferredSize = CGSize(width: 40, height: 20) + + let children: [ASDisplayNode] = recipients.map { + let node = ASTextNode2() + node.attributedText = $0.attributed() + return node + } + + let recipientsList = ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .start, + alignItems: .start, + children: children + ) + + return ASStackLayoutSpec( + direction: .horizontal, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: [labelNode, recipientsList] + ) + } + + private var recipientsListNode: ASStackLayoutSpec { + let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in + let recipients: [String] + switch type { + case .to: + recipients = input.recipients + case .cc: + recipients = input.ccRecipients + case .bcc: + recipients = input.bccRecipients + } + return recipientList(label: type.rawValue, recipients: recipients) + } + + return ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: recipientsNodes + ) + } + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { replyNode.style.preferredSize = CGSize(width: 44, height: 44) menuNode.style.preferredSize = CGSize(width: 36, height: 44) expandNode.style.preferredSize = CGSize(width: 36, height: 44) + let infoChildren: [ASLayoutElement] + if input.isExpanded { + if input.shouldShowRecipientsList { + infoChildren = [senderNode, recipientNode, recipientsListNode, dateNode] + } else { + infoChildren = [senderNode, recipientNode, dateNode] + } + } else { + infoChildren = [senderNode, dateNode] + } + let infoNode = ASStackLayoutSpec( direction: .vertical, spacing: 4, justifyContent: .spaceBetween, alignItems: .start, - children: [senderNode, recipientNode, dateNode] + children: infoChildren ) infoNode.style.flexGrow = 1 infoNode.style.flexShrink = 1 From 4c232f9c40931cb79f2505fc54465a536e609ef9 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Tue, 30 Nov 2021 20:22:45 +0200 Subject: [PATCH 03/10] issue #921 add MessageRecipientsNode --- FlowCrypt.xcodeproj/project.pbxproj | 4 + .../Threads/ThreadDetailsDecorator.swift | 4 +- .../ThreadMessageSenderCellNode.swift | 66 ++--------- FlowCryptUI/Nodes/BadgeNode.swift | 1 - FlowCryptUI/Nodes/MessageRecipientsNode.swift | 106 ++++++++++++++++++ 5 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 FlowCryptUI/Nodes/MessageRecipientsNode.swift diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 46879c2e7..e16cea314 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ 51B0C774272AB61000124663 /* StringTestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B0C773272AB61000124663 /* StringTestExtension.swift */; }; 51B4AE51271444580001F33B /* PubKeyRealmObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE50271444580001F33B /* PubKeyRealmObject.swift */; }; 51B4AE5327144E590001F33B /* PubKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B4AE5227144E590001F33B /* PubKey.swift */; }; + 51B9EE6F27567B520080B2D5 /* MessageRecipientsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B9EE6E27567B520080B2D5 /* MessageRecipientsNode.swift */; }; 51C0C1EF271982A1000C9738 /* MailCore in Frameworks */ = {isa = PBXBuildFile; productRef = 51C0C1EE271982A1000C9738 /* MailCore */; }; 51C0C1F2271987DB000C9738 /* Toast in Frameworks */ = {isa = PBXBuildFile; productRef = 51C0C1F1271987DB000C9738 /* Toast */; }; 51DA5BD62721AB07001C4359 /* PubKeyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DA5BD52721AB07001C4359 /* PubKeyState.swift */; }; @@ -498,6 +499,7 @@ 51B0C773272AB61000124663 /* StringTestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringTestExtension.swift; sourceTree = ""; }; 51B4AE50271444580001F33B /* PubKeyRealmObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKeyRealmObject.swift; sourceTree = ""; }; 51B4AE5227144E590001F33B /* PubKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKey.swift; sourceTree = ""; }; + 51B9EE6E27567B520080B2D5 /* MessageRecipientsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRecipientsNode.swift; sourceTree = ""; }; 51DA5BD52721AB07001C4359 /* PubKeyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubKeyState.swift; sourceTree = ""; }; 51DA5BD92722C82E001C4359 /* RecipientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientTests.swift; sourceTree = ""; }; 51DAD9BC273E7DD20076CBA7 /* BadgeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeNode.swift; sourceTree = ""; }; @@ -2026,6 +2028,7 @@ D24FAFAA2520BFAE00BF46C5 /* CheckBoxNode.swift */, 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */, 51DAD9BC273E7DD20076CBA7 /* BadgeNode.swift */, + 51B9EE6E27567B520080B2D5 /* MessageRecipientsNode.swift */, ); path = Nodes; sourceTree = ""; @@ -2783,6 +2786,7 @@ 5109A77C272153B400D2CEB9 /* LeftAlignedCollectionViewFlowLayout.swift in Sources */, D27177462424D59800BDA9A9 /* InboxCellNode.swift in Sources */, D27177472424D59800BDA9A9 /* TextCellNode.swift in Sources */, + 51B9EE6F27567B520080B2D5 /* MessageRecipientsNode.swift in Sources */, D211CE6E23FC354200D1CE38 /* CellNode.swift in Sources */, D24ABA6023FDB26C002EE9DD /* Helpers.swift in Sources */, D211CE7323FC35AC00D1CE38 /* TextViewCellNode.swift in Sources */, diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index c8ec99549..e41b913e3 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -32,8 +32,8 @@ extension ThreadMessageSenderCellNode.Input { self.init( encryptionBadge: makeEncryptionBadge(threadMessage), signatureBadge: makeSignatureBadge(threadMessage), - sender: NSAttributedString.text(from: sender, style: style, color: textColor), - recipientLabel: NSAttributedString.text(from: recipientLabel, style: style, color: textColor), + sender: NSAttributedString.text(from: sender, style: style, color: .label), + recipientLabel: NSAttributedString.text(from: recipientLabel, style: style, color: .secondaryLabel), recipients: threadMessage.rawMessage.recipient?.components(separatedBy: ", ") ?? [], ccRecipients: threadMessage.rawMessage.cc?.components(separatedBy: ", ") ?? [], bccRecipients: threadMessage.rawMessage.bcc?.components(separatedBy: ", ") ?? [], diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift index f6bdee0a9..6c7c2f94c 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift @@ -68,6 +68,13 @@ public final class ThreadMessageSenderCellNode: CellNode { return input.signatureBadge.map(BadgeNode.init) }() + private lazy var recipientsListNode: ASDisplayNode = { + MessageRecipientsNode(input: .init(recipients: input.recipients, + ccRecipients: input.ccRecipients, + bccRecipients: input.bccRecipients) + ) + }() + private let senderNode = ASTextNode2() private let recipientNode = ASButtonNode() private let dateNode = ASTextNode2() @@ -82,10 +89,6 @@ public final class ThreadMessageSenderCellNode: CellNode { private let onMenuTap: ((ThreadMessageSenderCellNode) -> Void)? private let onRecipientsTap: ((ThreadMessageSenderCellNode) -> Void)? - private enum RecipientType: String, CaseIterable { - case to, cc, bcc - } - public init(input: ThreadMessageSenderCellNode.Input, onReplyTap: ((ThreadMessageSenderCellNode) -> Void)?, onMenuTap: ((ThreadMessageSenderCellNode) -> Void)?, @@ -154,59 +157,6 @@ public final class ThreadMessageSenderCellNode: CellNode { onRecipientsTap?(self) } - private func recipientList(label: String, recipients: [String]) -> ASStackLayoutSpec? { - guard recipients.isNotEmpty else { return nil } - - let labelNode = ASTextNode2() - labelNode.attributedText = label.localizedCapitalized.attributed() - labelNode.style.preferredSize = CGSize(width: 40, height: 20) - - let children: [ASDisplayNode] = recipients.map { - let node = ASTextNode2() - node.attributedText = $0.attributed() - return node - } - - let recipientsList = ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .start, - alignItems: .start, - children: children - ) - - return ASStackLayoutSpec( - direction: .horizontal, - spacing: 4, - justifyContent: .spaceBetween, - alignItems: .start, - children: [labelNode, recipientsList] - ) - } - - private var recipientsListNode: ASStackLayoutSpec { - let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in - let recipients: [String] - switch type { - case .to: - recipients = input.recipients - case .cc: - recipients = input.ccRecipients - case .bcc: - recipients = input.bccRecipients - } - return recipientList(label: type.rawValue, recipients: recipients) - } - - return ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .spaceBetween, - alignItems: .start, - children: recipientsNodes - ) - } - public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { replyNode.style.preferredSize = CGSize(width: 44, height: 44) menuNode.style.preferredSize = CGSize(width: 36, height: 44) @@ -225,7 +175,7 @@ public final class ThreadMessageSenderCellNode: CellNode { let infoNode = ASStackLayoutSpec( direction: .vertical, - spacing: 4, + spacing: 6, justifyContent: .spaceBetween, alignItems: .start, children: infoChildren diff --git a/FlowCryptUI/Nodes/BadgeNode.swift b/FlowCryptUI/Nodes/BadgeNode.swift index 63a62f2e6..70996428b 100644 --- a/FlowCryptUI/Nodes/BadgeNode.swift +++ b/FlowCryptUI/Nodes/BadgeNode.swift @@ -5,7 +5,6 @@ // Created by Roma Sosnovsky on 12/11/21 // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // - import AsyncDisplayKit diff --git a/FlowCryptUI/Nodes/MessageRecipientsNode.swift b/FlowCryptUI/Nodes/MessageRecipientsNode.swift new file mode 100644 index 000000000..a2fffdfb5 --- /dev/null +++ b/FlowCryptUI/Nodes/MessageRecipientsNode.swift @@ -0,0 +1,106 @@ +// +// MessageRecipientsNode.swift +// FlowCryptUI +// +// Created by Roma Sosnovsky on 30/11/21 +// Copyright © 2017-present FlowCrypt a. s. All rights reserved. +// + +import AsyncDisplayKit + +public final class MessageRecipientsNode: ASDisplayNode { + public struct Input { + public let recipients: [String] + public let ccRecipients: [String] + public let bccRecipients: [String] + + public init(recipients: [String], + ccRecipients: [String], + bccRecipients: [String]) { + self.recipients = recipients + self.ccRecipients = ccRecipients + self.bccRecipients = bccRecipients + } + } + + private let input: MessageRecipientsNode.Input + + private enum RecipientType: String, CaseIterable { + case to, cc, bcc + } + + public init(input: MessageRecipientsNode.Input) { + self.input = input + } + + private func recipientList(label: String, recipients: [String]) -> ASStackLayoutSpec? { + guard recipients.isNotEmpty else { return nil } + + let labelNode = ASTextNode2() + labelNode.attributedText = label.localizedCapitalized.attributed() + labelNode.style.preferredSize = CGSize(width: 30, height: 20) + + let children: [ASDisplayNode] = recipients.compactMap { recipient in + let node = ASTextNode2() + let parts = recipient.components(separatedBy: " ") + if parts.count > 1 { + guard let email = parts.last else { return nil } + + let attributedEmail = email.attributed(.regular(15), color: .secondaryLabel) + let name = recipient + .replacingOccurrences(of: email, with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + .attributed(.regular(15), color: .label) + let text = name.mutable() + text.append(attributedEmail) + node.attributedText = text + } else { + node.attributedText = recipient.attributed(.regular(15), color: .secondaryLabel) + } + + return node + } + + let recipientsList = ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .start, + alignItems: .start, + children: children + ) + + return ASStackLayoutSpec( + direction: .horizontal, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: [labelNode, recipientsList] + ) + } + + private var recipientsListNode: ASLayoutSpec { + let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in + let recipients: [String] + switch type { + case .to: + recipients = input.recipients + case .cc: + recipients = input.ccRecipients + case .bcc: + recipients = input.bccRecipients + } + return recipientList(label: type.rawValue, recipients: recipients) + } + + return ASInsetLayoutSpec( + insets: UIEdgeInsets(top: 6, left: 0, bottom: 6, right: 0), + child: ASStackLayoutSpec( + direction: .vertical, + spacing: 6, + justifyContent: .spaceBetween, + alignItems: .start, + children: recipientsNodes + ) + ) + } +} From 17986ad63ad46610e8090c91f0c010e3cf1207aa Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 2 Dec 2021 12:00:10 +0200 Subject: [PATCH 04/10] issue #921 show full recipients list --- FlowCrypt.xcodeproj/project.pbxproj | 22 ------ ...eatePassphraseAbstractViewController.swift | 2 +- .../Controllers/Threads/MessageAction.swift | 2 +- .../Threads/ThreadDetailsViewController.swift | 2 +- .../DataManager/DataService.swift | 3 +- .../Encrypted Storage/EncryptedStorage.swift | 2 +- .../Mail Provider/Gmail/GmailService.swift | 2 +- .../Mail Provider/Imap/Imap+msg.swift | 2 +- .../Imap/ImapSessionProvider.swift | 4 +- .../Message Provider/Imap+Message.swift | 2 +- .../EnterpriseServerApi.swift | 2 +- .../ThreadMessageSenderCellNode.swift | 67 ++++++++++++++----- FlowCryptUI/Nodes/MessageRecipientsNode.swift | 16 +++-- 13 files changed, 73 insertions(+), 55 deletions(-) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 8ada20d3f..18c40a549 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -2489,20 +2489,11 @@ 2C2A3B4B2719EE6100B7F27B /* KeyServiceTests.swift in Sources */, 9F976490267E11880058419D /* ImapHelperTest.swift in Sources */, 9F5F501D26F90AE100294FA2 /* OrganisationalRulesServiceMock.swift in Sources */, - 9F976584267E194F0058419D /* TestData.swift in Sources */, - 9F6F3C6A26ADFBEB005BD9C6 /* MessageGatewayMock.swift in Sources */, - 9F7E903926A1AD7A0021C07F /* KeyDetailsTests.swift in Sources */, - 51B0C774272AB61000124663 /* StringTestExtension.swift in Sources */, - 2C2A3B4B2719EE6100B7F27B /* KeyServiceTests.swift in Sources */, - 9F976490267E11880058419D /* ImapHelperTest.swift in Sources */, - 9F5F501D26F90AE100294FA2 /* OrganisationalRulesServiceMock.swift in Sources */, 9F5F503C26FA6C5E00294FA2 /* CurrentUserEmailMock.swift in Sources */, F8A72FA12729F82800E4BCAB /* DraftGatewayMock.swift in Sources */, 9FC413182683C492004C0A69 /* InMemoryPassPhraseStorageTest.swift in Sources */, 9F9764C5267E14AB0058419D /* GeneralConstantsTest.swift in Sources */, 9F976507267E165D0058419D /* ZBase32EncodingTests.swift in Sources */, - 9F9764C5267E14AB0058419D /* GeneralConstantsTest.swift in Sources */, - 9F976507267E165D0058419D /* ZBase32EncodingTests.swift in Sources */, 2C2A3B4D2719EF7300B7F27B /* PassPhraseServiceMock.swift in Sources */, 9F5F504A26FA6C8F00294FA2 /* ClientConfigurationProviderMock.swift in Sources */, 9FC4117D268118AE004C0A69 /* PassPhraseStorageMock.swift in Sources */, @@ -2516,19 +2507,9 @@ 9F9764F4267E15CC0058419D /* ExtensionTests.swift in Sources */, 9F5F503526F90E5F00294FA2 /* ClientConfigurationServiceTests.swift in Sources */, 9F2F206826AEEAA60044E144 /* CombineTestExtension.swift in Sources */, - 9F976585267E194F0058419D /* FlowCryptCoreTests.swift in Sources */, - 9F6F3C7D26ADFC60005BD9C6 /* ContactsServiceMock.swift in Sources */, - 9F6F3C3C26ADFBC7005BD9C6 /* CoreComposeMessageMock.swift in Sources */, - 9FC4116B2681186D004C0A69 /* KeyMethodsTest.swift in Sources */, - 9F97653D267E17C90058419D /* LocalStorageTests.swift in Sources */, - A34D222A27294C67004E0220 /* PubLookupTest.swift in Sources */, - 9F9764F4267E15CC0058419D /* ExtensionTests.swift in Sources */, - 9F5F503526F90E5F00294FA2 /* ClientConfigurationServiceTests.swift in Sources */, - 9F2F206826AEEAA60044E144 /* CombineTestExtension.swift in Sources */, 51775C32270B01C200D7C944 /* PrvKeyInfoTests.swift in Sources */, 9F9500AF26F4BAE300E8C78B /* ClientConfigurationTests.swift in Sources */, 9FC413442683C912004C0A69 /* GmailServiceTest.swift in Sources */, - 9FC413442683C912004C0A69 /* GmailServiceTest.swift in Sources */, 9F976556267E186D0058419D /* RawClientConfigurationTests.swift in Sources */, A36108E9273C7A2E00A90E34 /* MockError.swift in Sources */, 9F7E8EC6269877E70021C07F /* KeyInfoTests.swift in Sources */, @@ -2536,9 +2517,6 @@ 51DA5BDA2722C82E001C4359 /* RecipientTests.swift in Sources */, 9FC41171268118A7004C0A69 /* PassPhraseStorageTests.swift in Sources */, 9F6F3C7626ADFC37005BD9C6 /* KeyStorageMock.swift in Sources */, - 51DA5BDA2722C82E001C4359 /* RecipientTests.swift in Sources */, - 9FC41171268118A7004C0A69 /* PassPhraseStorageTests.swift in Sources */, - 9F6F3C7626ADFC37005BD9C6 /* KeyStorageMock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift index 2fadaff24..f09086b06 100644 --- a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift @@ -16,7 +16,7 @@ import FlowCryptUI */ class SetupCreatePassphraseAbstractViewController: TableNodeViewController, PassPhraseSaveable, NavigationChildController { - + enum Parts: Int, CaseIterable { case title, description, passPhrase, divider, saveLocally, saveInMemory, action, subtitle, fetchedKeys } diff --git a/FlowCrypt/Controllers/Threads/MessageAction.swift b/FlowCrypt/Controllers/Threads/MessageAction.swift index 0506bacd3..aa744370e 100644 --- a/FlowCrypt/Controllers/Threads/MessageAction.swift +++ b/FlowCrypt/Controllers/Threads/MessageAction.swift @@ -5,7 +5,7 @@ // Created by Anton Kharchevskyi on 21.10.2021 // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // - + import Foundation typealias MessageActionCompletion = (MessageAction, InboxRenderable) -> Void diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 4f3d0adf0..685d48fed 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -384,7 +384,7 @@ extension ThreadDetailsViewController { } extension ThreadDetailsViewController: MessageActionsHandler { - + private func handleSuccessfulMessage(action: MessageAction) { hideSpinner() onComplete(action, .init(thread: thread, folderPath: currentFolderPath, activeUserEmail: user.email)) diff --git a/FlowCrypt/Functionality/DataManager/DataService.swift b/FlowCrypt/Functionality/DataManager/DataService.swift index e59e7f148..792c0d484 100644 --- a/FlowCrypt/Functionality/DataManager/DataService.swift +++ b/FlowCrypt/Functionality/DataManager/DataService.swift @@ -9,7 +9,6 @@ import Foundation import RealmSwift - // todo DataServiceType in general is a bit of a confused class // hopefully we can refactor it away or shrink it protocol DataServiceType { @@ -24,7 +23,7 @@ protocol DataServiceType { var users: [User] { get } func validAccounts() -> [User] - + func performMigrationIfNeeded() async throws } diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index fb9881617..8152ad791 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -56,7 +56,7 @@ final class EncryptedStorage: EncryptedStorageType { private let currentSchema: EncryptedStorageSchema = .version5 private let supportedSchemas = EncryptedStorageSchema.allCases - + private let storageEncryptionKey: Data var storage: Realm { diff --git a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift index db5ae08e4..9382109b2 100644 --- a/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Gmail/GmailService.swift @@ -11,7 +11,7 @@ import Foundation import GoogleAPIClientForREST_Gmail class GmailService: MailServiceProvider { - + let mailServiceProviderType = MailServiceProviderType.gmail let userService: GoogleUserServiceType let backupSearchQueryProvider: GmailBackupSearchQueryProviderType diff --git a/FlowCrypt/Functionality/Mail Provider/Imap/Imap+msg.swift b/FlowCrypt/Functionality/Mail Provider/Imap/Imap+msg.swift index a0e18a044..64addcac1 100644 --- a/FlowCrypt/Functionality/Mail Provider/Imap/Imap+msg.swift +++ b/FlowCrypt/Functionality/Mail Provider/Imap/Imap+msg.swift @@ -6,7 +6,7 @@ import Foundation import MailCore extension Imap { - + func fetchMsg(message: MCOIMAPMessage, folder: String) async throws -> Data { return try await execute("fetchMsg", { sess, respond in sess.fetchMessageOperation( diff --git a/FlowCrypt/Functionality/Mail Provider/Imap/ImapSessionProvider.swift b/FlowCrypt/Functionality/Mail Provider/Imap/ImapSessionProvider.swift index c448a6659..45b517726 100644 --- a/FlowCrypt/Functionality/Mail Provider/Imap/ImapSessionProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Imap/ImapSessionProvider.swift @@ -14,9 +14,9 @@ protocol ImapSessionProviderType { } class ImapSessionProvider: ImapSessionProviderType { - + private let user: User - + init(user: User) { self.user = user } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift index e3a353bd6..55409e0fe 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift @@ -9,7 +9,7 @@ import Foundation extension Imap: MessageProvider { - + func fetchMsg( message: Message, folder: String, diff --git a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift index 015d2d6e2..8a4a02422 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/EnterpriseServerApi.swift @@ -32,7 +32,7 @@ extension EnterpriseServerApiError: LocalizedError { class EnterpriseServerApi: EnterpriseServerApiType { static let publicEmailProviderDomains = ["gmail.com", "googlemail.com", "outlook.com"] - + private enum Constants { /// 404 - Not Found static let getToleratedHTTPStatuses = [404] diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift index 6c7c2f94c..fba9e6642 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift @@ -69,14 +69,17 @@ public final class ThreadMessageSenderCellNode: CellNode { }() private lazy var recipientsListNode: ASDisplayNode = { - MessageRecipientsNode(input: .init(recipients: input.recipients, - ccRecipients: input.ccRecipients, - bccRecipients: input.bccRecipients) + MessageRecipientsNode( + input: .init( + recipients: input.recipients, + ccRecipients: input.ccRecipients, + bccRecipients: input.bccRecipients + ) ) }() private let senderNode = ASTextNode2() - private let recipientNode = ASButtonNode() + private let recipientButtonNode = ASButtonNode() private let dateNode = ASTextNode2() public private(set) var replyNode = ASButtonNode() @@ -104,17 +107,28 @@ public final class ThreadMessageSenderCellNode: CellNode { senderNode.attributedText = input.sender senderNode.accessibilityIdentifier = "messageSenderLabel" - recipientNode.setAttributedTitle(input.recipientLabel, for: .normal) - recipientNode.addTarget(self, action: #selector(onRecipientsNodeTap), forControlEvents: .touchUpInside) - recipientNode.accessibilityIdentifier = "messageRecipientLabel" - dateNode.attributedText = input.date + setupRecipientButton() setupReplyNode() setupMenuNode() setupExpandNode() } + private func setupRecipientButton() { + let imageName = input.shouldShowRecipientsList ? "chevron.up" : "chevron.down" + let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 12, weight: .medium)) + recipientButtonNode.setImage(UIImage(systemName: imageName, withConfiguration: configuration), for: .normal) + recipientButtonNode.setAttributedTitle(input.recipientLabel, for: .normal) + recipientButtonNode.titleNode.maximumNumberOfLines = 1 + recipientButtonNode.titleNode.truncationMode = .byTruncatingTail + recipientButtonNode.imageAlignment = .end + recipientButtonNode.imageNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(.secondaryLabel) + recipientButtonNode.contentSpacing = 4 + recipientButtonNode.addTarget(self, action: #selector(onRecipientsNodeTap), forControlEvents: .touchUpInside) + recipientButtonNode.accessibilityIdentifier = "messageRecipientButton" + } + private func setupReplyNode() { setup(buttonNode: replyNode, with: input.replyImage, @@ -165,9 +179,9 @@ public final class ThreadMessageSenderCellNode: CellNode { let infoChildren: [ASLayoutElement] if input.isExpanded { if input.shouldShowRecipientsList { - infoChildren = [senderNode, recipientNode, recipientsListNode, dateNode] + infoChildren = [senderNode, recipientButtonNode] } else { - infoChildren = [senderNode, recipientNode, dateNode] + infoChildren = [senderNode, recipientButtonNode, dateNode] } } else { infoChildren = [senderNode, dateNode] @@ -205,13 +219,32 @@ public final class ThreadMessageSenderCellNode: CellNode { children: [encryptionNode, signatureNode, spacer].compactMap { $0 } ) - contentSpec = ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .spaceBetween, - alignItems: .stretch, - children: [senderSpec, signatureSpec] - ) + if input.shouldShowRecipientsList { + let recipientsSpec = ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: [recipientsListNode, dateNode] + ) + + contentSpec = ASStackLayoutSpec( + direction: .vertical, + spacing: 8, + justifyContent: .spaceBetween, + alignItems: .stretch, + children: [senderSpec, recipientsSpec, signatureSpec] + ) + } else { + contentSpec = ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .stretch, + children: [senderSpec, signatureSpec] + ) + } + } else { contentSpec = ASStackLayoutSpec( direction: .horizontal, diff --git a/FlowCryptUI/Nodes/MessageRecipientsNode.swift b/FlowCryptUI/Nodes/MessageRecipientsNode.swift index a2fffdfb5..e462b29f8 100644 --- a/FlowCryptUI/Nodes/MessageRecipientsNode.swift +++ b/FlowCryptUI/Nodes/MessageRecipientsNode.swift @@ -31,6 +31,14 @@ public final class MessageRecipientsNode: ASDisplayNode { public init(input: MessageRecipientsNode.Input) { self.input = input + + super.init() + + automaticallyManagesSubnodes = true + + borderColor = UIColor.tertiaryLabel.cgColor + borderWidth = 1 + cornerRadius = 6 } private func recipientList(label: String, recipients: [String]) -> ASStackLayoutSpec? { @@ -49,7 +57,6 @@ public final class MessageRecipientsNode: ASDisplayNode { let attributedEmail = email.attributed(.regular(15), color: .secondaryLabel) let name = recipient .replacingOccurrences(of: email, with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) .attributed(.regular(15), color: .label) let text = name.mutable() text.append(attributedEmail) @@ -68,17 +75,18 @@ public final class MessageRecipientsNode: ASDisplayNode { alignItems: .start, children: children ) + recipientsList.style.flexShrink = 1 return ASStackLayoutSpec( direction: .horizontal, spacing: 4, - justifyContent: .spaceBetween, + justifyContent: .start, alignItems: .start, children: [labelNode, recipientsList] ) } - private var recipientsListNode: ASLayoutSpec { + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in let recipients: [String] switch type { @@ -93,7 +101,7 @@ public final class MessageRecipientsNode: ASDisplayNode { } return ASInsetLayoutSpec( - insets: UIEdgeInsets(top: 6, left: 0, bottom: 6, right: 0), + insets: UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6), child: ASStackLayoutSpec( direction: .vertical, spacing: 6, From e21389619b3a5909c9109f1ac4097728758ab88e Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 2 Dec 2021 13:46:41 +0200 Subject: [PATCH 05/10] issue #921 update labels on Sent screen --- .../Controllers/Inbox/InboxRenderable.swift | 16 ++---- .../Threads/ThreadDetailsDecorator.swift | 12 ++-- .../MessagesList Provider/Model/Message.swift | 57 ++++++++++++++----- .../ThreadMessageSenderCellNode.swift | 12 ++-- FlowCryptUI/Nodes/MessageRecipientsNode.swift | 44 +++++++------- 5 files changed, 83 insertions(+), 58 deletions(-) diff --git a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift index 6eeb8f3aa..dd7191bdd 100644 --- a/FlowCrypt/Controllers/Inbox/InboxRenderable.swift +++ b/FlowCrypt/Controllers/Inbox/InboxRenderable.swift @@ -70,17 +70,11 @@ extension InboxRenderable { // for now its not exactly clear how titles on other folders should looks like // so in scope of this PR we are applying this title presentation only for "sent" folder if folderPath == MessageLabelType.sent.value { - var emails = thread.messages.compactMap(\.sender).unique() - // if we have only one email, it means that it could be "me" and we are not - // clearing our own email from that - if emails.count > 1 { - if let i = emails.firstIndex(of: activeUserEmail) { - emails.remove(at: i) - } - } - let recipients = emails - .compactMap { $0.components(separatedBy: "@").first } - .joined(separator: ",") + let recipients = thread.messages + .flatMap(\.allRecipients) + .map(\.displayName) + .unique() + .joined(separator: ", ") return "To: \(recipients)" } else { diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index e41b913e3..653f3030d 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -13,7 +13,11 @@ extension ThreadMessageSenderCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized let recipientPrefix = "to".localized - let recipientLabel = "\(recipientPrefix) \(threadMessage.rawMessage.recipientsList)" + let recipientsList = threadMessage.rawMessage + .allRecipients + .map(\.displayName) + .joined(separator: ", ") + let recipientLabel = [recipientPrefix, recipientsList].joined(separator: " ") let date = DateFormatter().formatDate(threadMessage.rawMessage.date) let isMessageRead = threadMessage.rawMessage.isMessageRead @@ -34,9 +38,9 @@ extension ThreadMessageSenderCellNode.Input { signatureBadge: makeSignatureBadge(threadMessage), sender: NSAttributedString.text(from: sender, style: style, color: .label), recipientLabel: NSAttributedString.text(from: recipientLabel, style: style, color: .secondaryLabel), - recipients: threadMessage.rawMessage.recipient?.components(separatedBy: ", ") ?? [], - ccRecipients: threadMessage.rawMessage.cc?.components(separatedBy: ", ") ?? [], - bccRecipients: threadMessage.rawMessage.bcc?.components(separatedBy: ", ") ?? [], + recipients: threadMessage.rawMessage.recipients.map { ($0.name, $0.email) }, + ccRecipients: threadMessage.rawMessage.cc.map { ($0.name, $0.email) }, + bccRecipients: threadMessage.rawMessage.bcc.map { ($0.name, $0.email) }, date: NSAttributedString.text(from: date, style: style, color: dateColor), isExpanded: threadMessage.isExpanded, shouldShowRecipientsList: threadMessage.shouldShowRecipientsList, diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index 395878037..3c1900197 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -14,9 +14,9 @@ struct Message: Hashable { let identifier: Identifier let date: Date let sender: String? - let recipient: String? - let cc: String? - let bcc: String? + let recipients: [MessageRecipient] + let cc: [MessageRecipient] + let bcc: [MessageRecipient] let subject: String? let size: Int? let attachmentIds: [String] @@ -63,9 +63,9 @@ struct Message: Hashable { self.threadId = threadId self.draftIdentifier = draftIdentifier self.raw = raw - self.recipient = recipient - self.cc = cc - self.bcc = bcc + self.recipients = Message.parseRecipients(recipient) + self.cc = Message.parseRecipients(cc) + self.bcc = Message.parseRecipients(bcc) } } @@ -79,6 +79,12 @@ extension Message: Equatable, Comparable { } } +extension Message { + static func parseRecipients(_ string: String?) -> [MessageRecipient] { + string?.components(separatedBy: ", ").map(MessageRecipient.init) ?? [] + } +} + extension Message { func markAsRead(_ isRead: Bool) -> Message { var copy = self @@ -91,15 +97,8 @@ extension Message { return copy } - var recipientsList: String { - [recipient, cc, bcc] - .compactMap { $0 } - .flatMap { $0.components(separatedBy: ", ") } - .compactMap { - $0.components(separatedBy: " ").first? - .components(separatedBy: "@").first - } - .joined(separator: ", ") + var allRecipients: [MessageRecipient] { + [recipients, cc, bcc].flatMap { $0 } } } @@ -112,3 +111,31 @@ struct Identifier: Equatable, Hashable { self.intId = intId } } + +struct MessageRecipient: Hashable { + let name: String? + let email: String + + init(emailString: String) { + let parts = emailString.components(separatedBy: " ") + + guard parts.count > 1, let email = parts.last else { + self.name = nil + self.email = emailString + return + } + + self.name = emailString + .replacingOccurrences(of: email, with: "") + .trimmingCharacters(in: .whitespaces) + self.email = email.filter { !["<", ">"].contains($0) } + } +} + +extension MessageRecipient { + var displayName: String { + name?.components(separatedBy: " ").first ?? + email.components(separatedBy: "@").first ?? + "unknown" + } +} diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift index fba9e6642..1ac9540da 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift @@ -15,9 +15,9 @@ public final class ThreadMessageSenderCellNode: CellNode { public let signatureBadge: BadgeNode.Input? public let sender: NSAttributedString public let recipientLabel: NSAttributedString - public let recipients: [String] - public let ccRecipients: [String] - public let bccRecipients: [String] + public let recipients: [MessageRecipient] + public let ccRecipients: [MessageRecipient] + public let bccRecipients: [MessageRecipient] public let date: NSAttributedString? public let isExpanded: Bool public let shouldShowRecipientsList: Bool @@ -28,9 +28,9 @@ public final class ThreadMessageSenderCellNode: CellNode { signatureBadge: BadgeNode.Input?, sender: NSAttributedString, recipientLabel: NSAttributedString, - recipients: [String], - ccRecipients: [String], - bccRecipients: [String], + recipients: [(String?, String)], + ccRecipients: [(String?, String)], + bccRecipients: [(String?, String)], date: NSAttributedString, isExpanded: Bool, shouldShowRecipientsList: Bool, diff --git a/FlowCryptUI/Nodes/MessageRecipientsNode.swift b/FlowCryptUI/Nodes/MessageRecipientsNode.swift index e462b29f8..57cd31caa 100644 --- a/FlowCryptUI/Nodes/MessageRecipientsNode.swift +++ b/FlowCryptUI/Nodes/MessageRecipientsNode.swift @@ -8,15 +8,17 @@ import AsyncDisplayKit +public typealias MessageRecipient = (name: String?, email: String) + public final class MessageRecipientsNode: ASDisplayNode { public struct Input { - public let recipients: [String] - public let ccRecipients: [String] - public let bccRecipients: [String] + let recipients: [MessageRecipient] + let ccRecipients: [MessageRecipient] + let bccRecipients: [MessageRecipient] - public init(recipients: [String], - ccRecipients: [String], - bccRecipients: [String]) { + public init(recipients: [MessageRecipient], + ccRecipients: [MessageRecipient], + bccRecipients: [MessageRecipient]) { self.recipients = recipients self.ccRecipients = ccRecipients self.bccRecipients = bccRecipients @@ -41,7 +43,7 @@ public final class MessageRecipientsNode: ASDisplayNode { cornerRadius = 6 } - private func recipientList(label: String, recipients: [String]) -> ASStackLayoutSpec? { + private func recipientList(label: String, recipients: [MessageRecipient]) -> ASStackLayoutSpec? { guard recipients.isNotEmpty else { return nil } let labelNode = ASTextNode2() @@ -50,20 +52,18 @@ public final class MessageRecipientsNode: ASDisplayNode { let children: [ASDisplayNode] = recipients.compactMap { recipient in let node = ASTextNode2() - let parts = recipient.components(separatedBy: " ") - if parts.count > 1 { - guard let email = parts.last else { return nil } - - let attributedEmail = email.attributed(.regular(15), color: .secondaryLabel) - let name = recipient - .replacingOccurrences(of: email, with: "") - .attributed(.regular(15), color: .label) - let text = name.mutable() - text.append(attributedEmail) - node.attributedText = text - } else { - node.attributedText = recipient.attributed(.regular(15), color: .secondaryLabel) - } + + let style: NSAttributedString.Style = .regular(15) + let nameString = recipient.name?.attributed(style, color: .label) + let emailString = recipient.email.attributed(style, color: .secondaryLabel) + let separator = " ".attributed(style) + node.attributedText = [nameString, emailString] + .compactMap { $0 } + .reduce(NSMutableAttributedString(), { + if !$0.string.isEmpty { $0.append(separator) } + $0.append($1); + return $0 + }) return node } @@ -88,7 +88,7 @@ public final class MessageRecipientsNode: ASDisplayNode { public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in - let recipients: [String] + let recipients: [MessageRecipient] switch type { case .to: recipients = input.recipients From 78c3a8a2dba7e8623799157b7c5ecb0f537396b4 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 2 Dec 2021 17:31:40 +0200 Subject: [PATCH 06/10] #921 update ThreadMessageInfoCellNode --- FlowCrypt.xcodeproj/project.pbxproj | 8 +- .../Threads/ThreadDetailsDecorator.swift | 8 +- .../Threads/ThreadDetailsViewController.swift | 36 ++- .../MessagesList Provider/Model/Message.swift | 2 + .../Threads/MessagesThreadProvider.swift | 2 +- ....swift => ThreadMessageInfoCellNode.swift} | 225 ++++++++++-------- 6 files changed, 153 insertions(+), 128 deletions(-) rename FlowCryptUI/Cell Nodes/{ThreadMessageSenderCellNode.swift => ThreadMessageInfoCellNode.swift} (64%) diff --git a/FlowCrypt.xcodeproj/project.pbxproj b/FlowCrypt.xcodeproj/project.pbxproj index 18c40a549..3edbd7e1b 100644 --- a/FlowCrypt.xcodeproj/project.pbxproj +++ b/FlowCrypt.xcodeproj/project.pbxproj @@ -67,7 +67,7 @@ 5180CB9127356D48001FC7EF /* MessageSubjectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */; }; 5180CB9327357B67001FC7EF /* RawClientConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */; }; 5180CB9527357BB0001FC7EF /* WkdApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB9427357BB0001FC7EF /* WkdApi.swift */; }; - 5180CB97273724E9001FC7EF /* ThreadMessageSenderCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */; }; + 5180CB97273724E9001FC7EF /* ThreadMessageInfoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5180CB96273724E9001FC7EF /* ThreadMessageInfoCellNode.swift */; }; 518389C82726D7DD00131B2C /* UIViewController+Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */; }; 518389CA2726D8F700131B2C /* UIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518389C92726D8F700131B2C /* UIApplicationExtension.swift */; }; 51938DC1274CC291007AD57B /* MessageQuoteType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DC0274CC291007AD57B /* MessageQuoteType.swift */; }; @@ -491,7 +491,7 @@ 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubjectNode.swift; sourceTree = ""; }; 5180CB9227357B67001FC7EF /* RawClientConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawClientConfiguration.swift; sourceTree = ""; }; 5180CB9427357BB0001FC7EF /* WkdApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WkdApi.swift; sourceTree = ""; }; - 5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMessageSenderCellNode.swift; sourceTree = ""; }; + 5180CB96273724E9001FC7EF /* ThreadMessageInfoCellNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMessageInfoCellNode.swift; sourceTree = ""; }; 518389C72726D7DD00131B2C /* UIViewController+Spinner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Spinner.swift"; sourceTree = ""; }; 518389C92726D8F700131B2C /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = ""; }; 51938DC0274CC291007AD57B /* MessageQuoteType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageQuoteType.swift; sourceTree = ""; }; @@ -1984,7 +1984,7 @@ 5A39F433239EC61C001F4607 /* TitleCellNode.swift */, 5180CB9027356D48001FC7EF /* MessageSubjectNode.swift */, 9F82779D23737E3800E19C07 /* MessageTextSubjectNode.swift */, - 5180CB96273724E9001FC7EF /* ThreadMessageSenderCellNode.swift */, + 5180CB96273724E9001FC7EF /* ThreadMessageInfoCellNode.swift */, 9F56BD3123438B5B00A7371A /* InboxCellNode.swift */, 9F56BD3523438B9D00A7371A /* TextCellNode.swift */, D24ABA6223FDB4FF002EE9DD /* RecipientEmailsCellNode.swift */, @@ -2757,7 +2757,7 @@ D2A9CA432426210200E1D898 /* SetupTitleNode.swift in Sources */, D2F6D12F24324ACC00DB4065 /* SwitchCellNode.swift in Sources */, 51EBC5702746A06600178DE8 /* TextWithIconNode.swift in Sources */, - 5180CB97273724E9001FC7EF /* ThreadMessageSenderCellNode.swift in Sources */, + 5180CB97273724E9001FC7EF /* ThreadMessageInfoCellNode.swift in Sources */, D2E26F6824F169E300612AF1 /* ContactCellNode.swift in Sources */, D2A9CA38242618DF00E1D898 /* LinkButtonNode.swift in Sources */, D24FAFA42520BF9100BF46C5 /* CheckBoxCircleView.swift in Sources */, diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 653f3030d..a79746b14 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -9,7 +9,7 @@ import FlowCryptUI import UIKit -extension ThreadMessageSenderCellNode.Input { +extension ThreadMessageInfoCellNode.Input { init(threadMessage: ThreadDetailsViewController.Input) { let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized let recipientPrefix = "to".localized @@ -38,9 +38,9 @@ extension ThreadMessageSenderCellNode.Input { signatureBadge: makeSignatureBadge(threadMessage), sender: NSAttributedString.text(from: sender, style: style, color: .label), recipientLabel: NSAttributedString.text(from: recipientLabel, style: style, color: .secondaryLabel), - recipients: threadMessage.rawMessage.recipients.map { ($0.name, $0.email) }, - ccRecipients: threadMessage.rawMessage.cc.map { ($0.name, $0.email) }, - bccRecipients: threadMessage.rawMessage.bcc.map { ($0.name, $0.email) }, + recipients: threadMessage.rawMessage.recipients.map(\.rawString), + ccRecipients: threadMessage.rawMessage.cc.map(\.rawString), + bccRecipients: threadMessage.rawMessage.bcc.map(\.rawString), date: NSAttributedString.text(from: date, style: style, color: dateColor), isExpanded: threadMessage.isExpanded, shouldShowRecipientsList: threadMessage.shouldShowRecipientsList, diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index 685d48fed..c72b59f90 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -121,7 +121,7 @@ extension ThreadDetailsViewController { } private func handleExpandTap(at indexPath: IndexPath) { - guard let threadNode = node.nodeForRow(at: indexPath) as? ThreadMessageSenderCellNode else { + guard let threadNode = node.nodeForRow(at: indexPath) as? ThreadMessageInfoCellNode else { logger.logError("Fail to handle tap at \(indexPath)") return } @@ -467,7 +467,7 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { let message = self.input[indexPath.section-1] if indexPath.row == 0 { - return ThreadMessageSenderCellNode( + return ThreadMessageInfoCellNode( input: .init(threadMessage: message), onReplyTap: { [weak self] _ in self?.handleReplyTap(at: indexPath) }, onMenuTap: { [weak self] _ in self?.handleMenuTap(at: indexPath) }, @@ -475,28 +475,26 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { ) } - if let processedMessage = message.processedMessage { - if indexPath.row == 1 { - return MessageTextSubjectNode(processedMessage.attributedMessage) - } else if indexPath.row > 1 { - let attachmentIndex = indexPath.row - 2 - let attachment = processedMessage.attachments[attachmentIndex] - return AttachmentNode( - input: .init( - msgAttachment: attachment, - index: attachmentIndex - ), - onDownloadTap: { [weak self] in self?.attachmentManager.open(attachment) } - ) - } - } + guard let processedMessage = message.processedMessage + else { return ASCellNode() } + + guard indexPath.row > 1 + else { return MessageTextSubjectNode(processedMessage.attributedMessage) } - return ASCellNode() + let attachmentIndex = indexPath.row - 2 + let attachment = processedMessage.attachments[attachmentIndex] + return AttachmentNode( + input: .init( + msgAttachment: attachment, + index: attachmentIndex + ), + onDownloadTap: { [weak self] in self?.attachmentManager.open(attachment) } + ) } } func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { - guard tableNode.nodeForRow(at: indexPath) is ThreadMessageSenderCellNode else { + guard tableNode.nodeForRow(at: indexPath) is ThreadMessageInfoCellNode else { return } handleExpandTap(at: indexPath) diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index 3c1900197..343e27f3f 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -138,4 +138,6 @@ extension MessageRecipient { email.components(separatedBy: "@").first ?? "unknown" } + + var rawString: (String?, String) { (name, email) } } diff --git a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift index 5e3ad8f28..7ea744222 100644 --- a/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Threads/MessagesThreadProvider.swift @@ -151,7 +151,7 @@ extension Message { var cc: String? var bcc: String? - messageHeaders.compactMap({ $0 }).forEach { + messageHeaders.compactMap { $0 }.forEach { guard let name = $0.name?.lowercased() else { return } let value = $0.value switch name { diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift similarity index 64% rename from FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift rename to FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift index 1ac9540da..77bb48f25 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageSenderCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift @@ -1,5 +1,5 @@ // -// ThreadMessageSenderCellNode.swift +// ThreadMessageInfoCellNode.swift // FlowCryptUI // // Created by Roma Sosnovsky on 06/11/21 @@ -9,7 +9,8 @@ import AsyncDisplayKit import UIKit -public final class ThreadMessageSenderCellNode: CellNode { +public final class ThreadMessageInfoCellNode: CellNode { + // MARK: - Input public struct Input { public let encryptionBadge: BadgeNode.Input public let signatureBadge: BadgeNode.Input? @@ -60,14 +61,84 @@ public final class ThreadMessageSenderCellNode: CellNode { } } - private lazy var encryptionNode: BadgeNode = { - return BadgeNode(input: input.encryptionBadge) + // MARK: - Node State + private enum InfoNodeState { + case collapsed, expanded, expandedWithRecipients + } + + private var nodeState: InfoNodeState { + guard input.isExpanded else { return .collapsed } + guard input.shouldShowRecipientsList else { return .expanded } + return .expandedWithRecipients + } + + // MARK: - Specs + private lazy var headerSpec: ASStackLayoutSpec = { + ASStackLayoutSpec( + direction: .horizontal, + spacing: 2, + justifyContent: .spaceBetween, + alignItems: .start, + children: [infoSpec, replyNode, menuNode] + ) }() - private lazy var signatureNode: BadgeNode? = { - return input.signatureBadge.map(BadgeNode.init) + private lazy var infoSpec: ASStackLayoutSpec = { + let node = ASStackLayoutSpec( + direction: .vertical, + spacing: 6, + justifyContent: .spaceBetween, + alignItems: .start, + children: infoSpecChildren + ) + node.style.flexGrow = 1 + node.style.flexShrink = 1 + return node }() + private var infoSpecChildren: [ASLayoutElement] { + switch nodeState { + case .collapsed: + return [senderNode, dateNode] + case .expanded: + return [senderNode, recipientButtonNode, dateNode] + case .expandedWithRecipients: + return [senderNode, recipientButtonNode] + } + } + + private lazy var recipientsSpec: ASStackLayoutSpec = { + ASStackLayoutSpec( + direction: .vertical, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: [recipientsListNode, dateNode] + ) + }() + + private lazy var encryptionInfoSpec: ASStackLayoutSpec = { + let spacer = ASLayoutSpec() + spacer.style.flexGrow = 1.0 + + return ASStackLayoutSpec( + direction: .horizontal, + spacing: 4, + justifyContent: .spaceBetween, + alignItems: .start, + children: [encryptionNode, signatureNode, spacer].compactMap { $0 } + ) + }() + + // MARK: - Nodes + private let senderNode = ASTextNode2() + private let recipientButtonNode = ASButtonNode() + private let dateNode = ASTextNode2() + + private let replyNode = ASButtonNode() + private let menuNode = ASButtonNode() + public private(set) var expandNode = ASImageNode() + private lazy var recipientsListNode: ASDisplayNode = { MessageRecipientsNode( input: .init( @@ -78,24 +149,32 @@ public final class ThreadMessageSenderCellNode: CellNode { ) }() - private let senderNode = ASTextNode2() - private let recipientButtonNode = ASButtonNode() - private let dateNode = ASTextNode2() + private lazy var encryptionNode: BadgeNode = { + BadgeNode(input: input.encryptionBadge) + }() - public private(set) var replyNode = ASButtonNode() - public private(set) var menuNode = ASButtonNode() - public private(set) var expandNode = ASImageNode() + private lazy var signatureNode: BadgeNode? = { + input.signatureBadge.map(BadgeNode.init) + }() - private let input: ThreadMessageSenderCellNode.Input + // MARK: - Properties + private let input: ThreadMessageInfoCellNode.Input - private let onReplyTap: ((ThreadMessageSenderCellNode) -> Void)? - private let onMenuTap: ((ThreadMessageSenderCellNode) -> Void)? - private let onRecipientsTap: ((ThreadMessageSenderCellNode) -> Void)? + private let onReplyTap: ((ThreadMessageInfoCellNode) -> Void)? + private let onMenuTap: ((ThreadMessageInfoCellNode) -> Void)? + private let onRecipientsTap: ((ThreadMessageInfoCellNode) -> Void)? - public init(input: ThreadMessageSenderCellNode.Input, - onReplyTap: ((ThreadMessageSenderCellNode) -> Void)?, - onMenuTap: ((ThreadMessageSenderCellNode) -> Void)?, - onRecipientsTap: ((ThreadMessageSenderCellNode) -> Void)?) { + private var recipientButtonImage: UIImage? { + let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 12, weight: .medium)) + let imageName = input.shouldShowRecipientsList ? "chevron.up" : "chevron.down" + return UIImage(systemName: imageName, withConfiguration: configuration) + } + + // MARK: - Init + public init(input: ThreadMessageInfoCellNode.Input, + onReplyTap: ((ThreadMessageInfoCellNode) -> Void)?, + onMenuTap: ((ThreadMessageInfoCellNode) -> Void)?, + onRecipientsTap: ((ThreadMessageInfoCellNode) -> Void)?) { self.input = input self.onReplyTap = onReplyTap self.onMenuTap = onMenuTap @@ -115,16 +194,17 @@ public final class ThreadMessageSenderCellNode: CellNode { setupExpandNode() } + // MARK: - Setup private func setupRecipientButton() { - let imageName = input.shouldShowRecipientsList ? "chevron.up" : "chevron.down" - let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 12, weight: .medium)) - recipientButtonNode.setImage(UIImage(systemName: imageName, withConfiguration: configuration), for: .normal) + recipientButtonNode.setImage(recipientButtonImage, for: .normal) + recipientButtonNode.imageAlignment = .end + recipientButtonNode.imageNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(.secondaryLabel) + recipientButtonNode.setAttributedTitle(input.recipientLabel, for: .normal) recipientButtonNode.titleNode.maximumNumberOfLines = 1 recipientButtonNode.titleNode.truncationMode = .byTruncatingTail - recipientButtonNode.imageAlignment = .end - recipientButtonNode.imageNode.imageModificationBlock = ASImageNodeTintColorModificationBlock(.secondaryLabel) recipientButtonNode.contentSpacing = 4 + recipientButtonNode.addTarget(self, action: #selector(onRecipientsNodeTap), forControlEvents: .touchUpInside) recipientButtonNode.accessibilityIdentifier = "messageRecipientButton" } @@ -159,6 +239,7 @@ public final class ThreadMessageSenderCellNode: CellNode { expandNode.contentMode = .center } + // MARK: - Callbacks @objc private func onReplyNodeTap() { onReplyTap?(self) } @@ -171,89 +252,33 @@ public final class ThreadMessageSenderCellNode: CellNode { onRecipientsTap?(self) } - public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { - replyNode.style.preferredSize = CGSize(width: 44, height: 44) - menuNode.style.preferredSize = CGSize(width: 36, height: 44) - expandNode.style.preferredSize = CGSize(width: 36, height: 44) - - let infoChildren: [ASLayoutElement] - if input.isExpanded { - if input.shouldShowRecipientsList { - infoChildren = [senderNode, recipientButtonNode] - } else { - infoChildren = [senderNode, recipientButtonNode, dateNode] - } - } else { - infoChildren = [senderNode, dateNode] - } - - let infoNode = ASStackLayoutSpec( - direction: .vertical, - spacing: 6, - justifyContent: .spaceBetween, - alignItems: .start, - children: infoChildren - ) - infoNode.style.flexGrow = 1 - infoNode.style.flexShrink = 1 - - let contentSpec: ASStackLayoutSpec - - if input.isExpanded { - let senderSpec = ASStackLayoutSpec( - direction: .horizontal, - spacing: 2, - justifyContent: .spaceBetween, - alignItems: .start, - children: [infoNode, replyNode, menuNode] - ) - - let spacer = ASLayoutSpec() - spacer.style.flexGrow = 1.0 - - let signatureSpec = ASStackLayoutSpec( + // MARK: - Layout + private var contentSpec: ASStackLayoutSpec { + switch nodeState { + case .collapsed: + return ASStackLayoutSpec( direction: .horizontal, spacing: 4, justifyContent: .spaceBetween, alignItems: .start, - children: [encryptionNode, signatureNode, spacer].compactMap { $0 } + children: [infoSpec, expandNode] ) - - if input.shouldShowRecipientsList { - let recipientsSpec = ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .spaceBetween, - alignItems: .start, - children: [recipientsListNode, dateNode] - ) - - contentSpec = ASStackLayoutSpec( - direction: .vertical, - spacing: 8, - justifyContent: .spaceBetween, - alignItems: .stretch, - children: [senderSpec, recipientsSpec, signatureSpec] - ) - } else { - contentSpec = ASStackLayoutSpec( - direction: .vertical, - spacing: 4, - justifyContent: .spaceBetween, - alignItems: .stretch, - children: [senderSpec, signatureSpec] - ) - } - - } else { - contentSpec = ASStackLayoutSpec( - direction: .horizontal, - spacing: 4, + case .expanded, .expandedWithRecipients: + let children = nodeState == .expanded ? [headerSpec, encryptionInfoSpec] : [headerSpec, recipientsSpec, encryptionInfoSpec] + return ASStackLayoutSpec( + direction: .vertical, + spacing: 8, justifyContent: .spaceBetween, - alignItems: .start, - children: [infoNode, expandNode] + alignItems: .stretch, + children: children ) } + } + + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { + replyNode.style.preferredSize = CGSize(width: 44, height: 44) + menuNode.style.preferredSize = CGSize(width: 36, height: 44) + expandNode.style.preferredSize = CGSize(width: 36, height: 44) return ASInsetLayoutSpec( insets: input.nodeInsets, From 8d84b891d25d63787b65a1cab914dd9c95cb2c8b Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Thu, 2 Dec 2021 21:53:05 +0200 Subject: [PATCH 07/10] #921 improve recipient name parsing --- .../Controllers/Threads/ThreadDetailsDecorator.swift | 4 ---- .../MessagesList Provider/Model/Message.swift | 12 +++++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index a79746b14..7a16a2335 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -29,10 +29,6 @@ extension ThreadMessageInfoCellNode.Input { ? .lightGray : .main - let textColor: UIColor = isMessageRead - ? .lightGray - : .mainTextUnreadColor - self.init( encryptionBadge: makeEncryptionBadge(threadMessage), signatureBadge: makeSignatureBadge(threadMessage), diff --git a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift index 343e27f3f..ceef163e2 100644 --- a/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/MessagesList Provider/Model/Message.swift @@ -116,19 +116,21 @@ struct MessageRecipient: Hashable { let name: String? let email: String - init(emailString: String) { - let parts = emailString.components(separatedBy: " ") + init(_ string: String) { + let parts = string.components(separatedBy: " ") guard parts.count > 1, let email = parts.last else { self.name = nil - self.email = emailString + self.email = string return } - self.name = emailString + self.email = email.filter { !["<", ">"].contains($0) } + let name = string .replacingOccurrences(of: email, with: "") + .replacingOccurrences(of: "\"", with: "") .trimmingCharacters(in: .whitespaces) - self.email = email.filter { !["<", ">"].contains($0) } + self.name = name == self.email ? nil : name } } From 1614a51b7a70f7bd85fbe099f2e960bed0e94a95 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 3 Dec 2021 15:42:21 +0200 Subject: [PATCH 08/10] #921 add ui test for recipients list --- FlowCryptUI/Nodes/MessageRecipientsNode.swift | 42 ++++++++++-------- appium/tests/data/index.ts | 9 ++++ appium/tests/helpers/ElementHelper.ts | 5 +++ appium/tests/screenobjects/email.screen.ts | 43 ++++++++++++++++--- .../tests/specs/inbox/ReadTextEmail.spec.ts | 15 +++++-- 5 files changed, 87 insertions(+), 27 deletions(-) diff --git a/FlowCryptUI/Nodes/MessageRecipientsNode.swift b/FlowCryptUI/Nodes/MessageRecipientsNode.swift index 57cd31caa..ea90f77c4 100644 --- a/FlowCryptUI/Nodes/MessageRecipientsNode.swift +++ b/FlowCryptUI/Nodes/MessageRecipientsNode.swift @@ -38,6 +38,10 @@ public final class MessageRecipientsNode: ASDisplayNode { automaticallyManagesSubnodes = true + setupBorder() + } + + private func setupBorder() { borderColor = UIColor.tertiaryLabel.cgColor borderWidth = 1 cornerRadius = 6 @@ -50,24 +54,9 @@ public final class MessageRecipientsNode: ASDisplayNode { labelNode.attributedText = label.localizedCapitalized.attributed() labelNode.style.preferredSize = CGSize(width: 30, height: 20) - let children: [ASDisplayNode] = recipients.compactMap { recipient in - let node = ASTextNode2() - - let style: NSAttributedString.Style = .regular(15) - let nameString = recipient.name?.attributed(style, color: .label) - let emailString = recipient.email.attributed(style, color: .secondaryLabel) - let separator = " ".attributed(style) - node.attributedText = [nameString, emailString] - .compactMap { $0 } - .reduce(NSMutableAttributedString(), { - if !$0.string.isEmpty { $0.append(separator) } - $0.append($1); - return $0 - }) - - return node + let children = recipients.enumerated().map { index, recipient in + return recipientNode(for: recipient, identifier: "\(label)Label\(index)") } - let recipientsList = ASStackLayoutSpec( direction: .vertical, spacing: 4, @@ -86,6 +75,25 @@ public final class MessageRecipientsNode: ASDisplayNode { ) } + private func recipientNode(for recipient: MessageRecipient, identifier: String) -> ASTextNode2 { + let style: NSAttributedString.Style = .regular(15) + let nameString = recipient.name?.attributed(style, color: .label) + let emailString = recipient.email.attributed(style, color: .secondaryLabel) + let separator = " ".attributed(style) + + let node = ASTextNode2() + node.accessibilityIdentifier = identifier + node.attributedText = [nameString, emailString] + .compactMap { $0 } + .reduce(NSMutableAttributedString(), { + if !$0.string.isEmpty { $0.append(separator) } + $0.append($1); + return $0 + }) + + return node + } + public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec { let recipientsNodes: [ASStackLayoutSpec] = RecipientType.allCases.compactMap { type in let recipients: [MessageRecipient] diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index 9a76cc20e..752af55ca 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -38,6 +38,15 @@ export const CommonData = { subject: 'Encrypted message with key mismatch', message: 'Could not decrypt:', }, + recipientsListEmail: { + sender: 'flowcrypt.compatibility@gmail.com', + subject: 'CC and BCC test', + message: 'Test message for CC and BCC recipients', + recipients: 'to Robot, robot+cc, e2e.enterprise.test', + to: 'Robot FlowCrypt robot@flowcrypt.com', + cc: 'robot+cc@flowcrypt.com', + bcc: 'e2e.enterprise.test@flowcrypt.com' + }, recipientWithoutPublicKey: { email: 'no.publickey@flowcrypt.com' }, diff --git a/appium/tests/helpers/ElementHelper.ts b/appium/tests/helpers/ElementHelper.ts index 4d546c95b..5f6db0c65 100644 --- a/appium/tests/helpers/ElementHelper.ts +++ b/appium/tests/helpers/ElementHelper.ts @@ -39,6 +39,11 @@ class ElementHelper { await ElementHelper.waitAndClick(await this.staticText(label)); } + static checkStaticText = async (element: WebdriverIO.Element, label: string) => { + await this.waitElementVisible(element); + expect(element).toHaveText(label); + } + static doubleClick = async (element: WebdriverIO.Element) => { await this.waitElementVisible(element); await element.doubleClick(); diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index 4f861bec5..e739557a0 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -9,6 +9,10 @@ const SELECTORS = { WRONG_PASS_PHRASE_MESSAGE: '-ios class chain:**/XCUIElementTypeStaticText[`label == "Wrong pass phrase, please try again"`]', DOWNLOAD_ATTACHMENT_BUTTON: '~attachmentDownloadButton0', REPLY_BUTTON: '~replyButton', + RECIPIENTS_BUTTON: '~messageRecipientButton', + RECIPIENTS_TO_LABEL: '~toLabel0', + RECIPIENTS_CC_LABEL: '~ccLabel0', + RECIPIENTS_BCC_LABEL: '~bcLabel0', MENU_BUTTON: '~messageMenuButton', FORWARD_BUTTON: '~Forward', DELETE_BUTTON: '~Delete', @@ -48,6 +52,22 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.REPLY_BUTTON); } + get recipientsButton() { + return $(SELECTORS.RECIPIENTS_BUTTON); + } + + get recipientsToLabel() { + return $(SELECTORS.RECIPIENTS_TO_LABEL); + } + + get recipientsCcLabel() { + return $(SELECTORS.RECIPIENTS_TO_LABEL); + } + + get recipientsBccLabel() { + return $(SELECTORS.RECIPIENTS_TO_LABEL); + } + get menuButton() { return $(SELECTORS.MENU_BUTTON); } @@ -77,8 +97,7 @@ class EmailScreen extends BaseScreen { } checkEmailAddress = async (email: string) => { - await (await this.senderEmail).waitForDisplayed(); - await expect(await this.senderEmail).toHaveText(email); + await ElementHelper.checkStaticText(await this.senderEmail, email); } checkEmailSubject = async (subject: string) => { @@ -133,6 +152,10 @@ class EmailScreen extends BaseScreen { await ElementHelper.waitAndClick(await this.replyButton); } + clickRecipientsButton =async () => { + await ElementHelper.waitAndClick(await this.recipientsButton); + } + clickMenuButton = async () => { await ElementHelper.waitAndClick(await this.menuButton); } @@ -149,14 +172,22 @@ class EmailScreen extends BaseScreen { await ElementHelper.waitAndClick(await this.confirmDeletingButton); } + checkRecipientsButton = async (value: string) => { + await ElementHelper.checkStaticText(await this.recipientsButton, value); + } + + checkRecipientsList = async (to: string, cc: string, bcc: string) => { + await ElementHelper.checkStaticText(await this.recipientsToLabel, to); + await ElementHelper.checkStaticText(await this.recipientsCcLabel, cc); + await ElementHelper.checkStaticText(await this.recipientsBccLabel, bcc); + } + checkEncryptionBadge = async (value: string) => { - await (await this.encryptionBadge).waitForDisplayed(); - await expect(await this.encryptionBadge).toHaveText(value); + await ElementHelper.checkStaticText(await this.encryptionBadge, value); } checkSignatureBadge = async (value: string) => { - await (await this.signatureBadge).waitForDisplayed(); - await expect(await this.signatureBadge).toHaveText(value); + await ElementHelper.checkStaticText(await this.signatureBadge, value); } } diff --git a/appium/tests/specs/inbox/ReadTextEmail.spec.ts b/appium/tests/specs/inbox/ReadTextEmail.spec.ts index 1e78570e4..aa8309061 100644 --- a/appium/tests/specs/inbox/ReadTextEmail.spec.ts +++ b/appium/tests/specs/inbox/ReadTextEmail.spec.ts @@ -9,11 +9,15 @@ import { CommonData } from '../../data'; describe('INBOX: ', () => { - it('user is able to view text email', async () => { + it('user is able to view text email and recipients list', async () => { - const senderEmail = CommonData.sender.email; - const emailSubject = CommonData.simpleEmail.subject; - const emailText = CommonData.simpleEmail.message; + const senderEmail = CommonData.recipientsListEmail.sender; + const emailSubject = CommonData.recipientsListEmail.subject; + const emailText = CommonData.recipientsListEmail.message; + const recipientsButton = CommonData.recipientsListEmail.recipients; + const toLabel = CommonData.recipientsListEmail.to; + const ccLabel = CommonData.recipientsListEmail.cc; + const bccLabel = CommonData.recipientsListEmail.bcc; await SplashScreen.login(); await SetupKeyScreen.setPassPhrase(); @@ -22,5 +26,8 @@ describe('INBOX: ', () => { await MailFolderScreen.searchEmailBySubject(emailSubject); await MailFolderScreen.clickOnEmailBySubject(emailSubject); await EmailScreen.checkOpenedEmail(senderEmail, emailSubject, emailText); + await EmailScreen.checkRecipientsButton(recipientsButton); + await EmailScreen.clickRecipientsButton(); + await EmailScreen.checkRecipientsList(toLabel, ccLabel, bccLabel); }); }); From b092e72262a9b58a0d18f5603c64034bff1d4de2 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 3 Dec 2021 22:23:07 +0200 Subject: [PATCH 09/10] #921 fix ui test --- appium/tests/helpers/ElementHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appium/tests/helpers/ElementHelper.ts b/appium/tests/helpers/ElementHelper.ts index 5f6db0c65..e4ee38984 100644 --- a/appium/tests/helpers/ElementHelper.ts +++ b/appium/tests/helpers/ElementHelper.ts @@ -41,7 +41,7 @@ class ElementHelper { static checkStaticText = async (element: WebdriverIO.Element, label: string) => { await this.waitElementVisible(element); - expect(element).toHaveText(label); + await expect(element).toHaveText(label); } static doubleClick = async (element: WebdriverIO.Element) => { From 8e9b8b34ff3540a460802632e920573823e900a4 Mon Sep 17 00:00:00 2001 From: Roma Sosnovsky Date: Fri, 3 Dec 2021 23:45:53 +0200 Subject: [PATCH 10/10] #921 fix ui test --- appium/tests/screenobjects/email.screen.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index e739557a0..16f4b74a1 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -12,7 +12,7 @@ const SELECTORS = { RECIPIENTS_BUTTON: '~messageRecipientButton', RECIPIENTS_TO_LABEL: '~toLabel0', RECIPIENTS_CC_LABEL: '~ccLabel0', - RECIPIENTS_BCC_LABEL: '~bcLabel0', + RECIPIENTS_BCC_LABEL: '~bccLabel0', MENU_BUTTON: '~messageMenuButton', FORWARD_BUTTON: '~Forward', DELETE_BUTTON: '~Delete', @@ -61,11 +61,11 @@ class EmailScreen extends BaseScreen { } get recipientsCcLabel() { - return $(SELECTORS.RECIPIENTS_TO_LABEL); + return $(SELECTORS.RECIPIENTS_CC_LABEL); } get recipientsBccLabel() { - return $(SELECTORS.RECIPIENTS_TO_LABEL); + return $(SELECTORS.RECIPIENTS_BCC_LABEL); } get menuButton() {