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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand Up @@ -1590,6 +1591,7 @@
9F4453BF236B894D005D7D05 /* TableNode.swift */,
9F4453C1236B9273005D7D05 /* TextFieldNode.swift */,
D24FAFAA2520BFAE00BF46C5 /* CheckBoxNode.swift */,
50531BE32629B9A80039BAE9 /* AttachmentNode.swift */,
);
path = Nodes;
sourceTree = "<group>";
Expand Down Expand Up @@ -1866,7 +1868,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9FE233D825712E51003D7C7E /* test-ci-secrets.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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

var indexPath: IndexPath {
IndexPath(row: rawValue, section: 0)
Expand Down Expand 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
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 Down Expand 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 }
Copy link
Contributor

@Kharchevskyi Kharchevskyi May 7, 2021

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
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) }
Copy link
Contributor

@Kharchevskyi Kharchevskyi May 7, 2021

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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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
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
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
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?()
}
}