Skip to content

Commit

Permalink
#2574 Added show only encrypted emails filter buttoN (#2642)
Browse files Browse the repository at this point in the history
* feat: added show only encrypted emails filter buttoN

* feat: added ui test

* fix: pr reviews

* fix: toggle crop issue
  • Loading branch information
ioanmo226 authored Nov 8, 2024
1 parent 31c7e8d commit 5be5b9a
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 11 deletions.
8 changes: 8 additions & 0 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@
D2FD0F692453245E00259FF0 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FD0F682453245E00259FF0 /* Either.swift */; };
D2FF6966243115EC007182F0 /* SetupImapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6965243115EC007182F0 /* SetupImapViewController.swift */; };
D2FF6968243115F9007182F0 /* SetupImapViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FF6967243115F9007182F0 /* SetupImapViewDecorator.swift */; };
D73F7D9D2CD46AE900955806 /* PgpOnlySwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73F7D9C2CD46AE700955806 /* PgpOnlySwitchNode.swift */; };
D73F7D9F2CD4922500955806 /* NotificationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73F7D9E2CD4922100955806 /* NotificationExtension.swift */; };
D741F9B22CA5661C00E1CAFF /* SecurityWarningNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D741F9B12CA5661400E1CAFF /* SecurityWarningNode.swift */; };
F191F621272511790053833E /* BlurViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F191F620272511790053833E /* BlurViewController.swift */; };
F8678DCC2722143300BB1710 /* GmailService+draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8678DCB2722143300BB1710 /* GmailService+draft.swift */; };
Expand Down Expand Up @@ -887,6 +889,8 @@
D2FD0F682453245E00259FF0 /* Either.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = "<group>"; };
D2FF6965243115EC007182F0 /* SetupImapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImapViewController.swift; sourceTree = "<group>"; };
D2FF6967243115F9007182F0 /* SetupImapViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImapViewDecorator.swift; sourceTree = "<group>"; };
D73F7D9C2CD46AE700955806 /* PgpOnlySwitchNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PgpOnlySwitchNode.swift; sourceTree = "<group>"; };
D73F7D9E2CD4922100955806 /* NotificationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExtension.swift; sourceTree = "<group>"; };
D741F9B12CA5661400E1CAFF /* SecurityWarningNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityWarningNode.swift; sourceTree = "<group>"; };
E26D5E20275AA417007B8802 /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = "<group>"; };
F191F620272511790053833E /* BlurViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2090,6 +2094,7 @@
D254AA5F24092A9E0041CAE0 /* Extensions */ = {
isa = PBXGroup;
children = (
D73F7D9E2CD4922100955806 /* NotificationExtension.swift */,
21F836B42652A25D00B2448C /* Data */,
E26D5E20275AA417007B8802 /* BundleExtensions.swift */,
21C7DEFB26669A3700C44800 /* CalendarExtensions.swift */,
Expand Down Expand Up @@ -2256,6 +2261,7 @@
D2A1D3D223FD9AE600D626D6 /* Nodes */ = {
isa = PBXGroup;
children = (
D73F7D9C2CD46AE700955806 /* PgpOnlySwitchNode.swift */,
9F7ECCA6272C3FB4008A1770 /* TextImageNode.swift */,
51EBC56F2746A06600178DE8 /* TextWithIconNode.swift */,
511D07E2276A2DF80050417B /* ButtonWithPaddingNode.swift */,
Expand Down Expand Up @@ -2891,6 +2897,7 @@
D2A9CA3A2426198600E1D898 /* SignInDescriptionNode.swift in Sources */,
5133B6742716E5EA00C95463 /* LabelCellNode.swift in Sources */,
D211CE7123FC35AC00D1CE38 /* TextFieldCellNode.swift in Sources */,
D73F7D9D2CD46AE900955806 /* PgpOnlySwitchNode.swift in Sources */,
9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */,
51DAD9BD273E7DD20076CBA7 /* BadgeNode.swift in Sources */,
51DE2FEE2714DA0400916222 /* ContactKeyCellNode.swift in Sources */,
Expand Down Expand Up @@ -2960,6 +2967,7 @@
9F44971626430710003A9FE9 /* Trace.swift in Sources */,
9F67998D277B3E4D00AFE5BE /* (null) in Sources */,
9F67998C277B3E4000AFE5BE /* BundleExtensions.swift in Sources */,
D73F7D9F2CD4922500955806 /* NotificationExtension.swift in Sources */,
9FD5052B278B2C8600FAA82F /* UIPopoverPresentationControllerExtensions.swift in Sources */,
D2CDC3CD2402CCD7002B045F /* UIImageExtensions.swift in Sources */,
95473C1B297E61DE006C8957 /* SequenceExtension.swift in Sources */,
Expand Down
33 changes: 29 additions & 4 deletions FlowCrypt/Controllers/Inbox/InboxViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ class InboxViewController: ViewController {
setupUI()
setupNavigationBar()
}
NotificationCenter.default.addObserver(
self,
selector: #selector(reloadThreadList),
name: .reloadThreadList,
object: nil
)
}

deinit {
NotificationCenter.default.removeObserver(self)
}

override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -125,6 +135,11 @@ class InboxViewController: ViewController {

// MARK: - UI
extension InboxViewController {
@objc func reloadThreadList() {
showSpinner()
refresh()
}

private func setupUI() {
title = inboxTitle

Expand Down Expand Up @@ -243,11 +258,16 @@ extension InboxViewController {
// MARK: - Functionality
extension InboxViewController {
private func getSearchQuery() -> String? {
guard searchedExpression.isNotEmpty else { return nil }

guard !searchedExpression.hasPrefix("subject:") else { return searchedExpression }
let showOnlyPgp = UserDefaults.standard.bool(forKey: "SHOW_PGP_ONLY_FLAG")
let pgpPattern = """
("-----BEGIN PGP MESSAGE-----" AND "-----END PGP MESSAGE-----") OR \
("-----BEGIN PGP SIGNED MESSAGE-----") OR \
filename:({asc pgp gpg key})
"""
let baseQuery = showOnlyPgp ? "\(pgpPattern) AND \(searchedExpression)" : searchedExpression
guard baseQuery.isNotEmpty else { return nil }

return "\(searchedExpression) OR subject:\(searchedExpression)"
return baseQuery.hasPrefix("subject:") ? baseQuery : "\(baseQuery) OR subject:\(searchedExpression)"
}

func fetchAndRenderEmails(_ batchContext: ASBatchContext?) {
Expand All @@ -270,6 +290,10 @@ extension InboxViewController {
)
state = .refresh
handleEndFetching(with: context, context: batchContext)
// Hide spinner after 0.5 seconds as it takes a while to reload view
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.hideSpinner()
}
} catch {
handle(error: error)
}
Expand All @@ -291,6 +315,7 @@ extension InboxViewController {
using: FetchMessageContext(
folderPath: viewModel.path,
count: messagesToLoad(),
searchQuery: getSearchQuery(),
pagination: pagination
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class MyMenuViewController: ViewController {
}

private enum Sections: Int, CaseIterable {
case header = 0, main, additional
case header = 0, pgpOnlySwitch = 1, main, additional
}

private enum State {
Expand Down Expand Up @@ -117,6 +117,7 @@ extension MyMenuViewController: ASTableDataSource, ASTableDelegate {
guard let sections = Sections(rawValue: section) else { return 0 }

switch (sections, state) {
case (.pgpOnlySwitch, _): return 1
case (.header, _): return 1
case (.main, .accountAdding): return accounts.count
case (.main, .folders): return folders.count
Expand All @@ -138,6 +139,8 @@ extension MyMenuViewController: ASTableDataSource, ASTableDelegate {
guard let sections = Sections(rawValue: indexPath.section) else { return }

switch (sections, state) {
case (.pgpOnlySwitch, _):
return
case (.header, _):
guard let header = tableNode.nodeForRow(at: indexPath) as? TextImageNode else {
return
Expand Down Expand Up @@ -258,6 +261,8 @@ extension MyMenuViewController {

private func node(for section: Sections, row: Int) -> ASCellNode {
switch (section, state) {
case (.pgpOnlySwitch, _):
return PgpOnlySwitchNode()
case (.header, _):
let headerInput = decorator.header(for: appContext.user, image: state.arrowImage)
return TextImageNode(input: headerInput) { [weak self] node in
Expand Down
2 changes: 2 additions & 0 deletions FlowCrypt/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,5 @@ Be careful - avoid clicking links and downloading attachments, or sharing person
"load_from_file" = "Load from a file";
"no_pubkeys_found" = "No public keys found";
"load_from_clipboard" = "Load from clipboard";

"show_only_pgp_messages" = "Show only PGP messages";
13 changes: 13 additions & 0 deletions FlowCryptCommon/Extensions/NotificationExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NotificationExtension.swift
// FlowCrypt
//
// Created by Ioan Moldovan on 10/31/24
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//

public extension Notification.Name {
static var reloadThreadList: Notification.Name {
return .init(rawValue: "ThreadList.Reload")
}
}
12 changes: 6 additions & 6 deletions FlowCryptUI/Cell Nodes/SwitchCellNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public final class SwitchCellNode: CellNode {

super.init()
self.textNode.attributedText = input?.attributedText
self.textNode.truncationMode = .byWordWrapping
self.automaticallyManagesSubnodes = true

if let backgroundColor = input?.backgroundColor {
Expand All @@ -68,17 +69,16 @@ public final class SwitchCellNode: CellNode {
}

override public func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
switchNode.style.preferredSize = CGSize(width: 100, height: 30)
switchNode.style.preferredSize = CGSize(width: 55, height: 30)
textNode.style.flexGrow = 1.0
textNode.style.flexShrink = 1.0
return ASStackLayoutSpec(
direction: .horizontal,
spacing: 8,
justifyContent: input?.switchJustifyContent ?? .start,
justifyContent: input?.switchJustifyContent ?? .spaceBetween,
alignItems: .center,
children: [
ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 24),
child: textNode
),
textNode,
switchNode
]
)
Expand Down
56 changes: 56 additions & 0 deletions FlowCryptUI/Nodes/PgpOnlySwitchNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// PgpOnlySwitchNode.swift
// FlowCrypt
//
// Created by Ioan Moldovan on 10/31/24
// Copyright © 2017-present FlowCrypt a. s. All rights reserved.
//

import AsyncDisplayKit
import FlowCryptCommon

public final class PgpOnlySwitchNode: CellNode {

let SHOW_PGP_ONLY_KEY = "SHOW_PGP_ONLY_FLAG"

private lazy var imageNode: ASImageNode = {
let node = ASImageNode()
let imageConfiguration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 24, weight: .light))
node.image = UIImage(systemName: "lock.shield", withConfiguration: imageConfiguration)?.tinted(.main)
return node
}()

private lazy var toggleNode: SwitchCellNode = {
let input = SwitchCellNode.Input(
isOn: UserDefaults.standard.bool(forKey: SHOW_PGP_ONLY_KEY),
attributedText: "show_only_pgp_messages"
.localized
.attributed(.medium(16), color: .textColor),
accessibilityIdentifier: "aid-toggle-pgp-only-node",
backgroundColor: .clear,
switchJustifyContent: .center
)
return SwitchCellNode(input: input) { isOn in
UserDefaults.standard.setValue(isOn, forKey: self.SHOW_PGP_ONLY_KEY)
NotificationCenter.default.post(name: .reloadThreadList, object: nil)
}
}()

override public init() {
super.init()
}

override public func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
toggleNode.style.flexGrow = 1
toggleNode.style.flexShrink = 1
return ASInsetLayoutSpec(
insets: .deviceSpecificTextInsets(top: 16, bottom: 16),
child: ASStackLayoutSpec.horizontal().then {
$0.spacing = 6
$0.alignItems = .center
$0.justifyContent = .spaceBetween
$0.children = [imageNode, toggleNode]
}
)
}
}
11 changes: 11 additions & 0 deletions appium/api-mocks/apis/google/google-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class GmailMsg {
this.draftId = msg.draftId;
this.labelIds = msg.labelIds;
this.raw = msg.raw;
this.snippet = msg.mimeMsg.text;
this.sizeEstimate = Buffer.byteLength(msg.raw, 'utf-8');

const dateHeader = msg.mimeMsg.headers.get('date')! as Date;
Expand Down Expand Up @@ -488,6 +489,7 @@ export class GoogleData {

public getThreads = (labelIds: string[] = [], query?: string) => {
const subject = (query?.match(/subject: '([^"]+)'/) || [])[1]?.trim().toLowerCase();
const pgpFlag = query?.includes('-----BEGIN PGP MESSAGE-----');
const threads: GmailThread[] = [];

const filteredThreads = this.getMessagesAndDrafts()
Expand All @@ -504,6 +506,15 @@ export class GoogleData {
}
})
.filter(m => (subject ? GoogleData.msgSubject(m).toLowerCase().includes(subject) : true))
.filter(m => {
if (pgpFlag) {
return (
m.snippet?.includes('-----BEGIN PGP MESSAGE-----') ||
m.snippet?.includes('-----BEGIN PGP SIGNED MESSAGE-----')
);
}
return true;
})
.map(m => ({ historyId: m.historyId, id: m.threadId!, snippet: `MOCK SNIPPET: ${GoogleData.msgSubject(m)}` }));

for (const thread of filteredThreads) {
Expand Down
9 changes: 9 additions & 0 deletions appium/tests/screenobjects/menu-bar.screen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const SELECTORS = {
TRASH_BTN: '~aid-menu-bar-item-trash',
DRAFTS_BTN: '~aid-menu-bar-item-drafts',
ALL_MAIL_BTN: '~aid-menu-bar-item-all-mail',
SHOW_ONLY_ENCRYPTED_EMAILS_TOGGLE: '~aid-toggle-pgp-only-node',
ADD_ACCOUNT_BUTTON: '~aid-add-account-btn',
};

Expand Down Expand Up @@ -51,6 +52,10 @@ class MenuBarScreen extends BaseScreen {
return $(SELECTORS.ALL_MAIL_BTN);
}

get showOnlyEncryptedEmailsToggle() {
return $(SELECTORS.SHOW_ONLY_ENCRYPTED_EMAILS_TOGGLE);
}

get addAccountButton() {
return $(SELECTORS.ADD_ACCOUNT_BUTTON);
}
Expand Down Expand Up @@ -110,6 +115,10 @@ class MenuBarScreen extends BaseScreen {
await ElementHelper.waitAndClick(await this.allMailButton);
};

clickShowOnlyEncryptedEmailsToggle = async () => {
await ElementHelper.waitAndClick(await this.showOnlyEncryptedEmailsToggle);
};

checkMenuBarItem = async (menuItem: string) => {
const menuBarItem = await $(`~aid-menu-item-${menuItem}`);
await menuBarItem.waitForDisplayed();
Expand Down
35 changes: 35 additions & 0 deletions appium/tests/specs/mock/inbox/CheckShowOnlyEncryptedEmails.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MockApi } from 'api-mocks/mock';
import { MockApiConfig } from 'api-mocks/mock-config';
import { MailFolderScreen, MenuBarScreen, SetupKeyScreen, SplashScreen } from '../../../screenobjects/all-screens';

describe('INBOX: ', () => {
it('user is able to see only encrypted emails when he clicks show only encrypted emails button', async () => {
const mockApi = new MockApi();

mockApi.fesConfig = MockApiConfig.defaultEnterpriseFesConfiguration;
mockApi.ekmConfig = MockApiConfig.defaultEnterpriseEkmConfiguration;
const plainEmailSubject = 'Honor reply-to address - plain';
const encryptedEmailSubject = 'Encrypted email with public key attached';
mockApi.addGoogleAccount('[email protected]', {
messages: [plainEmailSubject, encryptedEmailSubject],
});

await mockApi.withMockedApis(async () => {
await SplashScreen.mockLogin();
await SetupKeyScreen.setPassPhrase();
await MailFolderScreen.checkInboxScreen();

// Check if both encrypted & plain emails are present
await MailFolderScreen.checkEmailIsDisplayed(plainEmailSubject);
await MailFolderScreen.checkEmailIsDisplayed(encryptedEmailSubject);

await MenuBarScreen.clickMenuBtn();
await MenuBarScreen.clickShowOnlyEncryptedEmailsToggle();
await browser.pause(1000);
await MenuBarScreen.clickInboxButton();

// Now check if plain email is not present
await MailFolderScreen.checkEmailIsNotDisplayed(plainEmailSubject);
});
});
});

0 comments on commit 5be5b9a

Please sign in to comment.