Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2574 Added show only encrypted emails filter buttoN #2642

Merged
merged 6 commits into from
Nov 8, 2024
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
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
ioanmo226 marked this conversation as resolved.
Show resolved Hide resolved
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
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);
});
});
});
Loading