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
4 changes: 4 additions & 0 deletions FlowCrypt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
9F9AAFFD2383E216000A00F1 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9AAFFC2383E216000A00F1 /* Document.swift */; };
9F9ABC8723AC1EAA00D560E3 /* MessageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */; };
9FA19890253C841F008C9CF2 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA1988F253C841F008C9CF2 /* TableViewController.swift */; };
9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA9C83B264C2D75005A9670 /* MessageService.swift */; };
9FB22CD625715CA10026EE64 /* BackupServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */; };
9FB22CDD25715CF50026EE64 /* GmailServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */; };
9FB22CE425715D3E0026EE64 /* GmailServiceErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB22CE325715D3E0026EE64 /* GmailServiceErrorHandler.swift */; };
Expand Down Expand Up @@ -474,6 +475,7 @@
9F9AAFFC2383E216000A00F1 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
9F9ABC8623AC1EAA00D560E3 /* MessageContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContext.swift; sourceTree = "<group>"; };
9FA1988F253C841F008C9CF2 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
9FA9C83B264C2D75005A9670 /* MessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageService.swift; sourceTree = "<group>"; };
9FB22CD525715CA10026EE64 /* BackupServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupServiceErrorHandler.swift; sourceTree = "<group>"; };
9FB22CDC25715CF50026EE64 /* GmailServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceError.swift; sourceTree = "<group>"; };
9FB22CE325715D3E0026EE64 /* GmailServiceErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GmailServiceErrorHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1034,6 +1036,7 @@
9F9361A42573CE260009912F /* MessageProvider.swift */,
9F93623E2573D16F0009912F /* Gmail+Message.swift */,
9F9362182573D10E0009912F /* Imap+Message.swift */,
9FA9C83B264C2D75005A9670 /* MessageService.swift */,
);
path = "Message Provider";
sourceTree = "<group>";
Expand Down Expand Up @@ -2333,6 +2336,7 @@
9F0C3C102316DD5B00299985 /* GoogleUserService.swift in Sources */,
D24F4C2223E2359B00C5EEE4 /* BootstrapViewController.swift in Sources */,
D211CE7623FC36BC00D1CE38 /* UIColorExtension.swift in Sources */,
9FA9C83C264C2D75005A9670 /* MessageService.swift in Sources */,
D2FF6966243115EC007182F0 /* EmailProviderViewController.swift in Sources */,
D2CDC3D22402D4DA002B045F /* UIViewControllerExtensions.swift in Sources */,
D297990D2444A76D004A3E31 /* UserObject+Empty.swift in Sources */,
Expand Down
152 changes: 69 additions & 83 deletions FlowCrypt/Controllers/Msg/MessageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ final class MessageViewController: TableNodeViewController {
var path = ""
}

enum Sections: Int, CaseIterable {
case main, attributes
}

enum Parts: Int, CaseIterable {
case sender, subject, text, attachment
case sender, subject, text

var indexPath: IndexPath {
IndexPath(row: rawValue, section: 0)
Expand Down Expand Up @@ -47,38 +51,27 @@ final class MessageViewController: TableNodeViewController {
private let onCompletion: MsgViewControllerCompletion?

private var input: MessageViewController.Input?
private let decorator: MessageViewDecoratorType
private var dataService: DataServiceType & KeyDataServiceType
private let core: Core
private let messageProvider: MessageProvider
private let decorator: MessageViewDecorator
private let messageService: MessageService
private let messageOperationsProvider: MessageOperationsProvider
private var message: NSAttributedString
private var attachments: [Attachment]
private let trashFolderProvider: TrashFolderProviderType
private var fetchedMessage: FetchedMessage = .empty

init(
messageProvider: MessageProvider = MailProvider.shared.messageProvider,
messageService: MessageService = MessageService(),
messageOperationsProvider: MessageOperationsProvider = MailProvider.shared.messageOperationsProvider,
decorator: MessageViewDecoratorType = MessageViewDecorator(dateFormatter: DateFormatter()),
decorator: MessageViewDecorator = MessageViewDecorator(dateFormatter: DateFormatter()),
storage: DataServiceType & KeyDataServiceType = DataService.shared,
core: Core = Core.shared,
trashFolderProvider: TrashFolderProviderType = TrashFolderProvider(),
input: MessageViewController.Input,
completion: MsgViewControllerCompletion?
) {
self.messageProvider = messageProvider
self.messageService = messageService
self.messageOperationsProvider = messageOperationsProvider
self.input = input
self.decorator = decorator
self.dataService = storage
self.core = core
self.trashFolderProvider = trashFolderProvider
self.onCompletion = completion
self.attachments = []
self.message = decorator.attributed(
text: "loading_title".localized + "...",
color: .lightGray
)

super.init(node: TableNode())
}
Expand Down Expand Up @@ -142,60 +135,24 @@ extension MessageViewController {
private func fetchDecryptAndRenderMsg() {
guard let input = input else { return }
showSpinner("loading_title".localized, isUserInteractionEnabled: true)

Promise { [weak self] in
self?.message = try awaitPromise(self!.fetchMessage())
}.then(on: .main) { [weak self] in
guard let self = self else { return }
let promise = self.messageService.getMessage(with: input.objMessage, folder: input.path)
let message = try awaitPromise(promise)
self.fetchedMessage = message
}
.then(on: .main) { [weak self] in
self?.hideSpinner()
self?.node.reloadRows(at: [Parts.text.indexPath, Parts.attachment.indexPath], with: .fade)
self?.node.reloadData()
self?.asyncMarkAsReadIfNotAlreadyMarked()
}.catch(on: .main) { [weak self] error in
}
.catch(on: .main) { [weak self] error in
self?.hideSpinner()
self?.handleError(error, path: input.path)
}
}

private func fetchMessage() -> Promise<NSAttributedString> {
Promise { [weak self] resolve, reject in
guard let self = self, let input = self.input else { return }

let rawMimeData: Data = try awaitPromise(self.messageProvider.fetchMsg(message: input.objMessage, folder: input.path))
self.input?.bodyMessage = rawMimeData

guard let keys = self.dataService.keys else {
reject(CoreError.notReady("Could not fetch keys"))
return
}

let decrypted = try self.core.parseDecryptMsg(
encrypted: rawMimeData,
keys: keys,
msgPwd: nil,
isEmail: true
)
let decryptErrBlocks = decrypted.blocks.filter { $0.decryptErr != nil }
let decryptAttBlocks = decrypted.blocks.filter { $0.type == .plainAtt || $0.type == .encryptedAtt || $0.type == .decryptedAtt }

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

let message: NSAttributedString
if let decryptErrBlock = decryptErrBlocks.first {
let rawMsg = decryptErrBlock.content
let err = decryptErrBlock.decryptErr?.error
message = self.decorator.attributed(
text: "Could not decrypt:\n\(err?.type.rawValue ?? "UNKNOWN"): \(err?.message ?? "??")\n\n\n\(rawMsg)",
color: .red
)
} else {
message = self.decorator.attributed(
text: decrypted.text,
color: decrypted.replyType == CoreRes.ReplyType.encrypted ? .main : UIColor.mainTextColor
)
}
resolve(message)
}
}

private func handleError(_ error: Error, path: String) {
if let someError = error as NSError?, someError.code == Imap.Err.fetch.rawValue {
// todo - the missing msg should be removed from the list in inbox view
Expand Down Expand Up @@ -333,9 +290,9 @@ extension MessageViewController {
let replyInfo = ComposeViewController.Input.ReplyInfo(
recipient: input.objMessage.sender,
subject: input.objMessage.subject,
mime: input.bodyMessage,
mime: fetchedMessage.rawMimeData,
sentDate: input.objMessage.date,
message: message.string
message: fetchedMessage.text
)

navigationController?.pushViewController(
Expand All @@ -362,11 +319,38 @@ extension MessageViewController: NavigationChildController {
// MARK: - ASTableDelegate, ASTableDataSource

extension MessageViewController: ASTableDelegate, ASTableDataSource {
func tableNode(_: ASTableNode, numberOfRowsInSection _: Int) -> Int {
Parts.allCases.count
func numberOfSections(in tableNode: ASTableNode) -> Int {
Sections.allCases.count
}

func tableNode(_: ASTableNode, numberOfRowsInSection section: Int) -> Int {
guard let section = Sections(rawValue: section) else {
return 0
}
switch section {
case .main:
return Parts.allCases.count
case .attributes:
return fetchedMessage.attachments.count
}
}

func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
{ [weak self] in
guard let self = self, let section = Sections(rawValue: indexPath.section) else { return ASCellNode() }

switch section {
case .main:
return self.mainSectionNode(for: indexPath.row)
case .attributes:
return self.attachmentNode(for: indexPath.row)
}
}
}

private func mainSectionNode(for index: Int) -> ASCellNode {
guard let part = Parts(rawValue: index) else { return ASCellNode() }

let senderTitle = decorator.attributed(
title: input?.objMessage.sender ?? "(unknown sender)"
)
Expand All @@ -377,22 +361,24 @@ extension MessageViewController: ASTableDelegate, ASTableDataSource {
date: input?.objMessage.date
)

return { [weak self] in
guard let self = self, let part = Parts(rawValue: indexPath.row) else { return ASCellNode() }
switch part {
case .sender:
return MessageSenderNode(senderTitle) { [weak self] in
self?.handleReplyTap()
}
case .subject:
return MessageSubjectNode(subject, time: time)
case .text:
return MessageTextSubjectNode(self.message)
case .attachment:
return AttachmentsNode(attachments: self.attachments) { [weak self] in
self?.handleAttachmentTap()
}
switch part {
case .sender:
return MessageSenderNode(senderTitle) { [weak self] in
self?.handleReplyTap()
}
case .subject:
return MessageSubjectNode(subject, time: time)
case .text:
let messageInput = self.decorator.attributedMessage(from: self.fetchedMessage)
return MessageTextSubjectNode(messageInput)
}
}

private func attachmentNode(for index: Int) -> ASCellNode {
AttachmentNode(
input: .init(
msgAttachment: fetchedMessage.attachments[index]
)
)
}
}
25 changes: 17 additions & 8 deletions FlowCrypt/Controllers/Msg/MessageViewDecorator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@
// Copyright © 2019 FlowCrypt Limited. All rights reserved.
//

import FlowCryptUI
import UIKit

protocol MessageViewDecoratorType {
func attributed(title: String) -> NSAttributedString
func attributed(subject: String) -> NSAttributedString
func attributed(date: Date?) -> NSAttributedString
func attributed(text: String?, color: UIColor) -> NSAttributedString
}

struct MessageViewDecorator: MessageViewDecoratorType {
struct MessageViewDecorator {
let dateFormatter: DateFormatter

func attributed(title: String) -> NSAttributedString {
Expand All @@ -36,4 +30,19 @@ struct MessageViewDecorator: MessageViewDecoratorType {
func attributed(text: String?, color: UIColor) -> NSAttributedString {
(text ?? "").attributed(.regular(17), color: color)
}

func attributedMessage(from fetchedMessage: FetchedMessage) -> NSAttributedString {
fetchedMessage.text.attributed()
}
}

extension AttachmentNode.Input {
init(msgAttachment: MessageAttachment) {
self.init(
name: msgAttachment.name
.attributed(.regular(18), color: .textColor, alignment: .left),
size: "\(msgAttachment.size)"
.attributed(.medium(12), color: .textColor, alignment: .left)
)
}
}
Loading