Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions FlowCrypt/Controllers/Compose/ComposeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,12 @@ extension ComposeViewController {
}()

guard let recipientIndex = index else { return }

let recipient = contextToSend.recipients[recipientIndex]
let needsReload = recipient.state != state || recipient.keyState != keyState

guard needsReload else { return }

contextToSend.recipients[recipientIndex].state = state
contextToSend.recipients[recipientIndex].keyState = keyState

Expand Down
54 changes: 36 additions & 18 deletions FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,28 @@ extension ThreadDetailsViewController {
message: nil,
preferredStyle: .actionSheet
)

if let view = node.nodeForRow(at: indexPath) as? ThreadMessageInfoCellNode {
alert.popoverPresentation(style: .sourceView(view.menuNode.view))
} else {
alert.popoverPresentation(style: .centred(view))
}

alert.addAction(
UIAlertAction(
title: "forward".localized,
style: .default) { [weak self] _ in
self?.composeNewMessage(at: indexPath, quoteType: .forward)
}
)
alert.addAction(createComposeNewMessageAlertAction(at: indexPath, type: .replyAll))
alert.addAction(createComposeNewMessageAlertAction(at: indexPath, type: .forward))
alert.addAction(UIAlertAction(title: "cancel".localized, style: .cancel))

present(alert, animated: true, completion: nil)
}

private func createComposeNewMessageAlertAction(at indexPath: IndexPath, type: MessageQuoteType) -> UIAlertAction {
UIAlertAction(
title: type.actionLabel,
style: .default) { [weak self] _ in
self?.composeNewMessage(at: indexPath, quoteType: type)
}
}

private func handleAttachmentTap(at indexPath: IndexPath) {
Task {
do {
Expand Down Expand Up @@ -211,16 +216,27 @@ extension ThreadDetailsViewController {
let processedMessage = input.processedMessage
else { return }

let recipients = quoteType == .reply
? [input.rawMessage.sender].compactMap({ $0 })
: []
let sender = [input.rawMessage.sender].compactMap { $0 }
let recipients: [String] = {
switch quoteType {
case .reply:
return sender
case .replyAll:
let recipientEmails = input.rawMessage.recipients.map(\.email)
let allRecipients = (recipientEmails + sender).unique()
let filteredRecipients = allRecipients.filter { $0 != appContext.user.email }
return filteredRecipients.isEmpty ? sender : filteredRecipients
case .forward:
return []
}
}()

let attachments = quoteType == .forward
? input.processedMessage?.attachments ?? []
: []

let subject = input.rawMessage.subject ?? "(no subject)"
let threadId = quoteType == .reply ? input.rawMessage.threadId : nil
let threadId = quoteType == .forward ? nil : input.rawMessage.threadId

let replyInfo = ComposeMessageInput.MessageQuoteInfo(
recipients: recipients,
Expand All @@ -233,13 +249,15 @@ extension ThreadDetailsViewController {
attachments: attachments
)

let composeType: ComposeMessageInput.InputType
switch quoteType {
case .reply:
composeType = .reply(replyInfo)
case .forward:
composeType = .forward(replyInfo)
}
let composeType: ComposeMessageInput.InputType = {
switch quoteType {
case .reply, .replyAll:
return .reply(replyInfo)
case .forward:
return .forward(replyInfo)
}
}()

let composeInput = ComposeMessageInput(type: composeType)
navigationController?.pushViewController(
ComposeViewController(appContext: appContext, input: composeInput),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,27 @@
import Foundation

enum MessageQuoteType {
case reply, forward
case reply, replyAll, forward
}

extension MessageQuoteType {
var subjectPrefix: String {
switch self {
case .reply:
case .reply, .replyAll:
return "Re: "
case .forward:
return "Fwd: "
}
}

var actionLabel: String {
switch self {
case .reply:
return ""
case .replyAll:
return "message_reply_all".localized
case .forward:
return "forward".localized
}
}
}
1 change: 1 addition & 0 deletions FlowCrypt/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"message_signature_fail_reason" = "Failed to verify signature due to: %@";
"message_decrypt_error" = "decrypt error";
"message_mark_read_error" = "Could not mark message as read: %@";
"message_reply_all" = "Reply all";

// ERROR
"error_fetch_folders" = "Could not fetch folders";
Expand Down
10 changes: 6 additions & 4 deletions FlowCryptUI/Cell Nodes/RecipientEmailNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ final class RecipientEmailNode: CellNode {
init(input: Input, index: Int) {
self.input = input
super.init()

if let stateAccessibilityIdentifier = input.recipient.state.accessibilityIdentifier {
accessibilityIdentifier = "aid-to-\(index)-\(stateAccessibilityIdentifier)"
}

titleNode.attributedText = " ".attributed() + input.recipient.email + " ".attributed()
titleNode.backgroundColor = input.recipient.state.backgroundColor
if let accessibilityIdentifier = input.recipient.state.accessibilityIdentifier {
titleNode.accessibilityIdentifier = "aid-to-\(index)-\(accessibilityIdentifier)"
}
titleNode.isAccessibilityElement = true
titleNode.accessibilityIdentifier = "aid-to-\(index)-label"

titleNode.cornerRadius = 8
titleNode.clipsToBounds = true
Expand Down
2 changes: 1 addition & 1 deletion FlowCryptUI/Cell Nodes/RecipientEmailsCellNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final public class RecipientEmailsCellNode: CellNode {

public lazy var collectionNode: ASCollectionNode = {
let node = ASCollectionNode(collectionViewLayout: layout)
node.accessibilityIdentifier = "recipientsList"
node.accessibilityIdentifier = "aid-recipients-list"
node.backgroundColor = .clear
return node
}()
Expand Down
4 changes: 2 additions & 2 deletions FlowCryptUI/Cell Nodes/RecipientEmailsCellNodeInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UIKit
// MARK: Input
extension RecipientEmailsCellNode {
public struct Input {
public struct StateContext {
public struct StateContext: Equatable {
let backgroundColor, borderColor, textColor: UIColor
let image: UIImage?
let accessibilityIdentifier: String?
Expand All @@ -31,7 +31,7 @@ extension RecipientEmailsCellNode {
}
}

public enum State: CustomStringConvertible {
public enum State: CustomStringConvertible, Equatable {
case idle(StateContext)
case selected(StateContext)
case keyFound(StateContext)
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ GEM
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.551.0)
aws-sdk-core (3.125.5)
aws-sdk-core (3.125.6)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
Expand Down Expand Up @@ -116,7 +116,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.203.0)
fastlane (2.204.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
Expand Down
8 changes: 8 additions & 0 deletions appium/tests/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export const CommonData = {
attachmentName: 'image.png',
encryptedAttachmentName: 'image.png.pgp'
},
emailWithMultipleRecipients: {
sender: 'flowcrypt.compatibility@gmail.com',
recipient: 'robot@flowcrypt.com',
subject: 'Message with multiple recipients and attachment',
message: 'This email has multiple recipients and attachment',
attachmentName: 'image.png',
encryptedAttachmentName: 'image.png.pgp',
},
simpleEmail: {
subject: 'Test 1',
message: 'Test email',
Expand Down
4 changes: 2 additions & 2 deletions appium/tests/helpers/DataHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ class DataHelper {
static uniqueValue() {
return Math.random().toString(36).substring(2);
}
static convertDateToMSec = async (date: string ) => {
return await Date.parse(moment(date.replace('at', '')).toISOString())
static convertDateToMSec = (date: string ) => {
return Date.parse(moment(date.replace('at', '')).toISOString())
}
}

Expand Down
9 changes: 9 additions & 0 deletions appium/tests/screenobjects/email.screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const SELECTORS = {
RECIPIENTS_BCC_LABEL: '~bccLabel0',
MENU_BUTTON: '~aid-message-menu-button',
FORWARD_BUTTON: '~Forward',
REPLY_ALL_BUTTON: '~Reply all',
DELETE_BUTTON: '~Delete',
DOWNLOAD_BUTTON: '~Download',
CANCEL_BUTTON: '~Cancel',
Expand Down Expand Up @@ -84,6 +85,10 @@ class EmailScreen extends BaseScreen {
return $(SELECTORS.FORWARD_BUTTON);
}

get replyAllButton() {
return $(SELECTORS.REPLY_ALL_BUTTON);
}

get deleteButton() {
return $(SELECTORS.DELETE_BUTTON)
}
Expand Down Expand Up @@ -181,6 +186,10 @@ class EmailScreen extends BaseScreen {
await ElementHelper.waitAndClick(await this.forwardButton);
}

clickReplyAllButton = async () => {
await ElementHelper.waitAndClick(await this.replyAllButton);
}

clickDeleteButton = async () => {
await ElementHelper.waitAndClick(await this.deleteButton);
}
Expand Down
46 changes: 19 additions & 27 deletions appium/tests/screenobjects/new-message.screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ const SELECTORS = {
ADD_RECIPIENT_FIELD: '~aid-recipient-text-field',
SUBJECT_FIELD: '~subjectTextField',
COMPOSE_SECURITY_MESSAGE: '~messageTextView',
RECIPIENTS_LIST: '~recipientsList',
ADDED_RECIPIENT: '-ios class chain:**/XCUIElementTypeWindow[1]/XCUIElementTypeOther/XCUIElementTypeOther' +
'/XCUIElementTypeOther/XCUIElementTypeOther[1]/XCUIElementTypeOther/XCUIElementTypeTable' +
'/XCUIElementTypeCell[1]/XCUIElementTypeOther/XCUIElementTypeCollectionView/XCUIElementTypeCell' +
'/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeStaticText', //it works only with this selector
RECIPIENTS_LIST: '~aid-recipients-list',
PASSWORD_CELL: '~aid-message-password-cell',
ATTACHMENT_CELL: '~aid-attachment-cell-0',
ATTACHMENT_NAME_LABEL: '~aid-attachment-title-label-0',
Expand Down Expand Up @@ -45,10 +41,6 @@ class NewMessageScreen extends BaseScreen {
return $(SELECTORS.RECIPIENTS_LIST);
}

get addedRecipientEmail() {
return $(SELECTORS.ADDED_RECIPIENT);
}

get attachmentCell() {
return $(SELECTORS.ATTACHMENT_CELL);
}
Expand Down Expand Up @@ -126,17 +118,13 @@ class NewMessageScreen extends BaseScreen {
await ElementHelper.waitAndClick(await $(`~${email}`));
};

checkFilledComposeEmailInfo = async (recipient: string, subject: string, message: string, attachmentName?: string) => {
checkFilledComposeEmailInfo = async (recipients: string[], subject: string, message: string, attachmentName?: string) => {
expect(this.composeSecurityMessage).toHaveTextContaining(message);

const element = await this.filledSubject(subject);
await element.waitForDisplayed();

if (recipient.length === 0) {
await this.checkEmptyRecipientsList();
} else {
await this.checkAddedRecipient(recipient);
}
await this.checkRecipientsList(recipients);

if (attachmentName !== undefined) {
await this.checkAddedAttachment(attachmentName);
Expand All @@ -147,26 +135,30 @@ class NewMessageScreen extends BaseScreen {
await ElementHelper.waitElementInvisible(await this.addRecipientField);
}

checkEmptyRecipientsList = async () => {
const list = await this.recipientsList;
const listText = await list.getText();
expect(listText.length).toEqual(0);
checkRecipientsList = async(recipients: string[]) => {
if (recipients.length === 0) {
await ElementHelper.waitElementInvisible(await $(`~aid-to-0-label`));
} else {
for (const [index, recipient] of recipients.entries()) {
await this.checkAddedRecipient(recipient, index);
}
}
}

checkAddedRecipient = async (recipient: string) => {
const addedRecipientEl = await this.addedRecipientEmail;
const value = await addedRecipientEl.getValue();
expect(value).toEqual(` ${recipient} `);
checkAddedRecipient = async (recipient: string, order = 0) => {
const recipientCell = await $(`~aid-to-${order}-label`);
const name = await recipientCell.getValue();
expect(name).toEqual(` ${recipient} `);
}

checkAddedRecipientColor = async (recipient: string, order: number, color: string) => {
const addedRecipientEl = await $(`~aid-to-${order}-${color}`);
const name = await addedRecipientEl.getValue();
expect(name).toEqual(` ${recipient} `);
await ElementHelper.waitElementVisible(addedRecipientEl);
await this.checkAddedRecipient(recipient, order);
}

deleteAddedRecipient = async (order: number, color: string) => {
const addedRecipientEl = await $(`~aid-to-${order}-${color}`);
deleteAddedRecipient = async (order: number) => {
const addedRecipientEl = await $(`~aid-to-${order}-label`);
await ElementHelper.waitAndClick(addedRecipientEl);
await driver.sendKeys(['\b']); // backspace
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ describe('COMPOSE EMAIL: ', () => {
await MailFolderScreen.checkInboxScreen();
await MailFolderScreen.clickCreateEmail();
await NewMessageScreen.composeEmail(recipientEmail, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo(recipientEmail, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo([recipientEmail], emailSubject, emailText);

await driver.background(3);

await NewMessageScreen.checkFilledComposeEmailInfo(recipientEmail, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo([recipientEmail], emailSubject, emailText);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('COMPOSE EMAIL: ', () => {

await MailFolderScreen.clickCreateEmail();
await NewMessageScreen.composeEmail(expiredPublicKey, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo(expiredPublicKey, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo([expiredPublicKey], emailSubject, emailText);
await NewMessageScreen.clickSendButton();

await BaseScreen.checkModalMessage(expiredPublicKeyError);
Expand All @@ -37,7 +37,7 @@ describe('COMPOSE EMAIL: ', () => {

await MailFolderScreen.clickCreateEmail();
await NewMessageScreen.composeEmail(revokedpublicKey, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo(revokedpublicKey, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo([revokedpublicKey], emailSubject, emailText);
await NewMessageScreen.clickSendButton();

await BaseScreen.checkModalMessage(revokedPublicKeyError);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ describe('COMPOSE EMAIL: ', () => {

await MailFolderScreen.clickCreateEmail();
await NewMessageScreen.composeEmail(recipient, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo(recipient, emailSubject, emailText);
await NewMessageScreen.checkFilledComposeEmailInfo([recipient], emailSubject, emailText);
await NewMessageScreen.clickSendButton();
await BaseScreen.checkModalMessage(passwordModalMessage);
await NewMessageScreen.clickCancelButton();
await NewMessageScreen.checkPasswordCell(emptyPasswordMessage);

await NewMessageScreen.deleteAddedRecipient(0, 'gray');
await NewMessageScreen.deleteAddedRecipient(0);

await NewMessageScreen.setAddRecipient(recipient);
await NewMessageScreen.clickSendButton();
Expand Down
Loading