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: 4 additions & 2 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@
32DCAF683D87EA6221F71335 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA058652FD4616FB04FB6 /* SequenceExtensions.swift */; };
32DCAF95A6A329C3136B1C8E /* Imap+msg.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA55C094E9745AA1FD210 /* Imap+msg.swift */; };
32DCAF9DA9EC47798DF8BB73 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DCA9701B2D5052225A0414 /* SignInViewController.swift */; };
50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50531BE32629B9A80039BAE9 /* AttachmentNode.swift */; };
5A39F42D239EC321001F4607 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A39F42C239EC321001F4607 /* SettingsViewController.swift */; };
5A39F430239EC396001F4607 /* SettingsViewDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A39F42F239EC396001F4607 /* SettingsViewDecorator.swift */; };
5A39F437239ECC23001F4607 /* KeySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A39F436239ECC23001F4607 /* KeySettingsViewController.swift */; };
Expand DownExpand Up@@ -134,7 +135,6 @@
9FE1B3802563F85400D6D086 /* MessagesListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE1B37F2563F85400D6D086 /* MessagesListProvider.swift */; };
9FE1B3942563F98600D6D086 /* Imap+MessagesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE1B3932563F98600D6D086 /* Imap+MessagesList.swift */; };
9FE1B3A02565B0CE00D6D086 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE1B39F2565B0CD00D6D086 /* Message.swift */; };
9FE233D825712E51003D7C7E /* test-ci-secrets.json in Resources */ = {isa = PBXBuildFile; fileRef = 9FE233D725712E51003D7C7E /* test-ci-secrets.json */; };
9FE743072347AA54005E2DBB /* MainNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE743062347AA54005E2DBB /* MainNavigationController.swift */; };
9FEED1D2230DAD1E00700F8E /* InboxViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEED1D1230DAD1E00700F8E /* InboxViewModel.swift */; };
9FF0670825520CF800FCC9E6 /* GmailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF0670725520CF800FCC9E6 /* GmailService.swift */; };
Expand DownExpand Up@@ -370,6 +370,7 @@
411CEC75050F852F172CD687 /* Pods-FlowCryptTests.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptTests.testflight.xcconfig"; path = "Target Support Files/Pods-FlowCryptTests/Pods-FlowCryptTests.testflight.xcconfig"; sourceTree = "<group>"; };
44D0BF0D60EF854CEC17561C /* Pods-FlowCryptUIApplication.testflight.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptUIApplication.testflight.xcconfig"; path = "Target Support Files/Pods-FlowCryptUIApplication/Pods-FlowCryptUIApplication.testflight.xcconfig"; sourceTree = "<group>"; };
4A76C3D4559C9F415D392A62 /* Pods-FlowCryptTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlowCryptTests.debug.xcconfig"; path = "Target Support Files/Pods-FlowCryptTests/Pods-FlowCryptTests.debug.xcconfig"; sourceTree = "<group>"; };
50531BE32629B9A80039BAE9 /* AttachmentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentNode.swift; sourceTree = "<group>"; };
567BA6739257FE0D2924D82C /* Pods_FlowCryptUIApplication.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlowCryptUIApplication.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5A39F42C239EC321001F4607 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
5A39F42F239EC396001F4607 /* SettingsViewDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewDecorator.swift; sourceTree = "<group>"; };
Expand DownExpand Up@@ -1590,6 +1591,7 @@
9F4453BF236B894D005D7D05 /* TableNode.swift */,
9F4453C1236B9273005D7D05 /* TextFieldNode.swift */,
D24FAFAA2520BFAE00BF46C5 /* CheckBoxNode.swift */,
50531BE32629B9A80039BAE9 /* AttachmentNode.swift */,
);
path = Nodes;
sourceTree = "<group>";
Expand DownExpand Up@@ -1866,7 +1868,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9FE233D825712E51003D7C7E /* test-ci-secrets.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand DownExpand Up@@ -2419,6 +2420,7 @@
D28655912423B4580066F52E /* HeaderNode.swift in Sources */,
D271774C242558DA00BDA9A9 /* MessageTextSubjectNode.swift in Sources */,
D27177502425659F00BDA9A9 /* SettingsCellNode.swift in Sources */,
50531BE42629B9A80039BAE9 /* AttachmentNode.swift in Sources */,
D2CDC3D824047066002B045F /* RecipientEmailNode.swift in Sources */,
D2717753242568A600BDA9A9 /* NavigationBarItemsView.swift in Sources */,
D211CE7023FC35AC00D1CE38 /* TableNode.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions FlowCrypt/Assets.xcassets/download.imageset/Contents.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Light-M.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions FlowCrypt/Assets.xcassets/paperclip.imageset/Contents.json
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "paperclip.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "paperclip@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "paperclip@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}
18 changes: 16 additions & 2 deletions FlowCrypt/Controllers/Msg/MessageViewController.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,7 @@ final class MessageViewController: TableNodeViewController {
}

enum Parts: Int, CaseIterable {
case sender, subject, text
case sender, subject, text, attachment
Comment thread
Kharchevskyi marked this conversation as resolved.

var indexPath: IndexPath {
IndexPath(row: rawValue, section: 0)
Expand DownExpand Up@@ -53,6 +53,7 @@ final class MessageViewController: TableNodeViewController {
private let messageProvider: MessageProvider
private let messageOperationsProvider: MessageOperationsProvider
private var message: NSAttributedString
private var attachments: [Attachment]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can initiate it here instead of inside init private var attachments: [Attachment] = []

private let trashFolderProvider: TrashFolderProviderType

init(
Expand All@@ -73,6 +74,7 @@ final class MessageViewController: TableNodeViewController {
self.core = core
self.trashFolderProvider = trashFolderProvider
self.onCompletion = completion
self.attachments = []
self.message = decorator.attributed(
text: "loading_title".localized + "...",
color: .lightGray
Expand DownExpand Up@@ -143,7 +145,7 @@ extension MessageViewController {
self?.message = try awaitPromise(self!.fetchMessage())
}.then(on: .main) { [weak self] in
self?.hideSpinner()
self?.node.reloadRows(at: [Parts.text.indexPath], with: .fade)
self?.node.reloadRows(at: [Parts.text.indexPath, Parts.attachment.indexPath], with: .fade)
self?.asyncMarkAsReadIfNotAlreadyMarked()
}.catch(on: .main) { [weak self] error in
self?.hideSpinner()
Expand All@@ -170,6 +172,10 @@ extension MessageViewController {
isEmail: true
)
let decryptErrBlocks = decrypted.blocks.filter { $0.decryptErr != nil }
let decryptAttBlocks = decrypted.blocks.filter { $0.type == .plainAtt || $0.type == .encryptedAtt || $0.type == .decryptedAtt }

@KharchevskyiKharchevskyiMay 7, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to have some computed property for MsgBlock and cover it with tests(not in scope of this PR)

extension MsgBlock {
var isAttachmentBlock: Bool {
type == .plainAtt || type == .encryptedAtt || type == .decryptedAtt
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be called isAttachmentBlock. But this is rather minor as you say


let attachments = decryptAttBlocks.map { Attachment(name: $0.attMeta?.name ?? "Attachment", size: $0.attMeta?.length ?? 0) }

@KharchevskyiKharchevskyiMay 7, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for Attachment. You can have

extension Attachment {
init(block: MsgBlock) {
self.name = self.size = }
}

and then it can be just

let attachments = decrypted.blocks
.filter(\.isAttachmentBlock)
.map(Attachment.init)

this solution is more flexible and can be tested easily.

self.attachments = attachments

let message: NSAttributedString
if let decryptErrBlock = decryptErrBlocks.first {
Expand DownExpand Up@@ -242,6 +248,10 @@ extension MessageViewController {
showToast("Marking as unread will be implemented soon")
}

@objc private func handleAttachmentTap() {
showToast("Downloading attachments is not implemented yet")
}

@objc private func handleTrashTap() {
showSpinner()

Expand DownExpand Up@@ -377,6 +387,10 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource {
return MessageSubjectNode(subject, time: time)
case .text:
return MessageTextSubjectNode(self.message)
case .attachment:
return AttachmentsNode(attachments: self.attachments) { [weak self] in
self?.handleAttachmentTap()
}
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions FlowCrypt/Core/CoreTypes.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,15 +124,15 @@ struct SendableMsg {

struct MsgBlock: Decodable {
static func blockParseErr(with content: String) -> MsgBlock {
MsgBlock(type: .blockParseErr, content: content, decryptErr: nil, keyDetails: nil)
MsgBlock(type: .blockParseErr, content: content, decryptErr: nil, keyDetails: nil, attMeta: nil)
}

let type: BlockType
let content: String
let decryptErr: DecryptErr? // always present in decryptErr BlockType
let keyDetails: KeyDetails? // always present in publicKey BlockType
let attMeta: AttMeta? // always present in plainAtt, encryptedAtt, decryptedAtt, encryptedAttLink
// let verifyRes: VerifyRes?,
// let attMeta: AttMeta?; // always present in plainAtt, encryptedAtt, decryptedAtt, encryptedAttLink

// let signature: String? // possibly not neded in Swift

Expand DownExpand Up@@ -166,6 +166,12 @@ struct MsgBlock: Decodable {
}
}

struct AttMeta: Decodable {
let name: String
let data: Data
let length: Int
}

enum BlockType: String, Decodable {
case plainHtml // all content blocks, regardless if encrypted or not, formatted as a plainHtml (todo - rename this one day to formattedHtml)
case publicKey
Expand Down
117 changes: 117 additions & 0 deletions FlowCryptUI/Nodes/AttachmentNode.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
//
// AttachmentNode.swift
// FlowCryptUI
//
// Created by QSD BiH on 16. 4. 2021..
// Copyright © 2021 FlowCrypt Limited. All rights reserved.
//

import AsyncDisplayKit

public struct Attachment {
var name, size: String

public init(
name: String,
size: Int
) {
self.name = name
self.size = ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: .file)
}
}

public final class AttachmentsNode: CellNode {
public struct Input {
let name: String
let size: String

public init(
name: String,
size: String
) {
self.name = name
self.size = size
}
}

private var attachmentNodes: [AttachmentNode] = []
private var onTap: (() -> Void)?

public init(attachments: [Attachment], onTap: (() -> Void)?) {
super.init()
self.onTap = onTap
attachmentNodes = attachments.map { AttachmentNode(input: AttachmentNode.Input(name: $0.name, size: $0.size),
onTap: {
self.onTap?()
})
}
Comment on lines +43 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please change indentation here? hard for understanding

attachmentNodes = attachments.map {
input: AttachmentNode(
input:
onTap:
)
)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can xcode be set to indent automatically on save?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a script for that. Will check if it's possible to indent code during build phase.
Will work on it ASAP to prevent all this "code style" comments

}

public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),
child: ASStackLayoutSpec(
direction: .vertical,
spacing: 8,
justifyContent: .start,
alignItems: .stretch,
children: attachmentNodes))
}
}

public final class AttachmentNode: CellNode {
public struct Input {
var name, size: String
}

private let titleNode = ASTextNode()
private let subtitleNode = ASTextNode2()
private let imageNode = ASImageNode()
private let buttonNode = ASButtonNode()

private var onTap: (() -> Void)?

public init(input: Input, onTap: (() -> Void)?) {
super.init()
self.onTap = onTap

self.borderWidth = 1.0
self.cornerRadius = 8.0
self.borderColor = UIColor.lightGray.cgColor

imageNode.tintColor = .gray
buttonNode.tintColor = .gray

imageNode.image = UIImage(named: "paperclip")?.tinted(.gray)
buttonNode.setImage(UIImage(named: "download")?.tinted(.gray), for: .normal)
buttonNode.addTarget(self, action: #selector(tapHandle), forControlEvents: .touchUpInside)
titleNode.attributedText = NSAttributedString.text(from: input.name, style: .regular(18), color: .gray, alignment: .left)
subtitleNode.attributedText = NSAttributedString.text(from: input.size, style: .medium(12), color: .gray, alignment: .left)
}

public override func layoutSpecThatFits(_: ASSizeRange) -> ASLayoutSpec {
let verticalStack = ASStackLayoutSpec.vertical()
verticalStack.spacing = 3
verticalStack.style.flexShrink = 1.0
verticalStack.style.flexGrow = 1.0

verticalStack.children = [titleNode, subtitleNode]

let finalSpec = ASStackLayoutSpec(
direction: .horizontal,
spacing: 10,
justifyContent: .start,
alignItems: .center,
children: [imageNode, verticalStack, buttonNode]
)

return ASInsetLayoutSpec(
insets: UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20),
child: finalSpec
)
}

@objc private func tapHandle() {
onTap?()
}
}