IOS-1110 Project reorder, some refactor. IOS-1110
authorMiguel Berrocal Go?mez <miguel@helm.cat>
Wed, 04 Jul 2018 11:24:51 +0200
branchIOS-1110
changeset 5234aab2cb36b01b
parent 5219 f4acd5eca72d
child 5235 aeb0b325c8d2
IOS-1110 Project reorder, some refactor.
pEpForiOS.xcodeproj/project.pbxproj
pEpForiOS/UI/EmailDisplay/Threading/CellDetailTransition.swift
pEpForiOS/UI/EmailDisplay/Threading/DetailCellSegue.swift
pEpForiOS/UI/EmailDisplay/Threading/EmailViewModelDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/FullMessageCell+SecureWebViewControllerDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/FullMessageCell.swift
pEpForiOS/UI/EmailDisplay/Threading/MessageViewModelConfigurable.swift
pEpForiOS/UI/EmailDisplay/Threading/NeedsRefreshDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/ReplyAlertCreator.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadNavigationDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+EmailViewDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+SegueHandlerType.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+SwipeCell.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+TableView.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadViewcontroller+SizeClasses.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel+MoveToFolderDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel+UpdateThreadDelegate.swift
pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel.swift
pEpForiOS/UI/EmailDisplay/Threading/TreadedEmailViewModel+DisplayedMessage.swift
pEpForiOS/UI/EmailDisplayList/MessageViewModelConfigurable.swift
pEpForiOS/UI/Thread/Cells/FullMessageCell+SecureWebViewControllerDelegate.swift
pEpForiOS/UI/Thread/Cells/FullMessageCell.swift
pEpForiOS/UI/Thread/ThreadViewController+EmailViewDelegate.swift
pEpForiOS/UI/Thread/ThreadViewController+SegueHandlerType.swift
pEpForiOS/UI/Thread/ThreadViewController+SwipeCell.swift
pEpForiOS/UI/Thread/ThreadViewController+TableView.swift
pEpForiOS/UI/Thread/ThreadViewController.swift
pEpForiOS/UI/Thread/ThreadViewcontroller+SizeClasses.swift
pEpForiOS/UI/Thread/Transitions/CellDetailTransition.swift
pEpForiOS/UI/Thread/Transitions/DetailCellSegue.swift
pEpForiOS/UI/Thread/Util/NeedsRefreshDelegate.swift
pEpForiOS/UI/Thread/Util/ReplyAlertCreator.swift
pEpForiOS/UI/Thread/Util/ThreadNavigationDelegate.swift
pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel+MoveToFolderDelegate.swift
pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel+UpdateThreadDelegate.swift
pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel.swift
pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModelDelegate.swift
pEpForiOS/UI/Thread/ViewModel/TreadedEmailViewModel+DisplayedMessage.swift
     1.1 --- a/pEpForiOS.xcodeproj/project.pbxproj	Tue Jul 03 14:44:28 2018 +0200
     1.2 +++ b/pEpForiOS.xcodeproj/project.pbxproj	Wed Jul 04 11:24:51 2018 +0200
     1.3 @@ -16,7 +16,7 @@
     1.4  		002375D620DD185100663961 /* TreadedEmailViewModel+DisplayedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002375D520DD185100663961 /* TreadedEmailViewModel+DisplayedMessage.swift */; };
     1.5  		0033C07F20D7EBD500224E61 /* ThreadedEmailViewModel+UpdateThreadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033C07E20D7EBD500224E61 /* ThreadedEmailViewModel+UpdateThreadDelegate.swift */; };
     1.6  		0033C08120D7F3C600224E61 /* ThreadViewController+EmailViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033C08020D7F3C600224E61 /* ThreadViewController+EmailViewDelegate.swift */; };
     1.7 -		0033C08320D7F41600224E61 /* EmailViewModelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033C08220D7F41600224E61 /* EmailViewModelDelegate.swift */; };
     1.8 +		0033C08320D7F41600224E61 /* ThreadedEmailViewModelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0033C08220D7F41600224E61 /* ThreadedEmailViewModelDelegate.swift */; };
     1.9  		0038494A20D25576008000EA /* ProfilePictureComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0038494920D25576008000EA /* ProfilePictureComposer.swift */; };
    1.10  		0038494C20D2587F008000EA /* PepPictureComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0038494B20D2587F008000EA /* PepPictureComposer.swift */; };
    1.11  		003C0FA720B5581A0093A987 /* SecretTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003C0FA620B5581A0093A987 /* SecretTestData.swift */; };
    1.12 @@ -534,7 +534,7 @@
    1.13  		002375D520DD185100663961 /* TreadedEmailViewModel+DisplayedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TreadedEmailViewModel+DisplayedMessage.swift"; sourceTree = "<group>"; };
    1.14  		0033C07E20D7EBD500224E61 /* ThreadedEmailViewModel+UpdateThreadDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadedEmailViewModel+UpdateThreadDelegate.swift"; sourceTree = "<group>"; };
    1.15  		0033C08020D7F3C600224E61 /* ThreadViewController+EmailViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewController+EmailViewDelegate.swift"; sourceTree = "<group>"; };
    1.16 -		0033C08220D7F41600224E61 /* EmailViewModelDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailViewModelDelegate.swift; sourceTree = "<group>"; };
    1.17 +		0033C08220D7F41600224E61 /* ThreadedEmailViewModelDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadedEmailViewModelDelegate.swift; sourceTree = "<group>"; };
    1.18  		0038494920D25576008000EA /* ProfilePictureComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureComposer.swift; sourceTree = "<group>"; };
    1.19  		0038494B20D2587F008000EA /* PepPictureComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PepPictureComposer.swift; sourceTree = "<group>"; };
    1.20  		003C0FA620B5581A0093A987 /* SecretTestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretTestData.swift; sourceTree = "<group>"; };
    1.21 @@ -1904,6 +1904,7 @@
    1.22  				15BA537A20A1F5CA0090F126 /* MoveToFolder */,
    1.23  				B70D32B0205BCFBD0094A92A /* EmailDisplay */,
    1.24  				43ED53621CC77F95006AB156 /* EmailDisplayList */,
    1.25 +				492EF92B20C69547004EAE14 /* Thread */,
    1.26  				B70D32AA205BCCC70094A92A /* Login */,
    1.27  				15FE1F741FE122B200CC2D97 /* Credits */,
    1.28  				B706C0EF1EA8C378006B2F6C /* StoryboardFiles */,
    1.29 @@ -1976,30 +1977,31 @@
    1.30  			path = Models;
    1.31  			sourceTree = "<group>";
    1.32  		};
    1.33 -		492EF92B20C69547004EAE14 /* Threading */ = {
    1.34 +		492EF92B20C69547004EAE14 /* Thread */ = {
    1.35  			isa = PBXGroup;
    1.36  			children = (
    1.37 -				005A21FA20CAA5F50082D19F /* ThreadedEmailViewModel.swift */,
    1.38 -				00EB89AC20E3D3C200CDFA0D /* ThreadedEmailViewModel+MoveToFolderDelegate.swift */,
    1.39 -				002375D520DD185100663961 /* TreadedEmailViewModel+DisplayedMessage.swift */,
    1.40 -				0033C07E20D7EBD500224E61 /* ThreadedEmailViewModel+UpdateThreadDelegate.swift */,
    1.41 +				49B2ED2920EBBEC9005A41A3 /* ViewModel */,
    1.42 +				49B2ED2A20EBBEEE005A41A3 /* Cells */,
    1.43  				492EF92C20C6957D004EAE14 /* ThreadViewController.swift */,
    1.44  				492EF92E20C699D0004EAE14 /* ThreadViewController+TableView.swift */,
    1.45  				495D69BE20DBAEE900986007 /* ThreadViewController+SwipeCell.swift */,
    1.46  				49228A5220D3D29900A51E9D /* ThreadViewController+SegueHandlerType.swift */,
    1.47  				0033C08020D7F3C600224E61 /* ThreadViewController+EmailViewDelegate.swift */,
    1.48 -				00A12CAD20D3D9AC00B82BE3 /* FullMessageCell.swift */,
    1.49 -				002375D220DCDC7300663961 /* FullMessageCell+SecureWebViewControllerDelegate.swift */,
    1.50 -				49228A5420D4035100A51E9D /* DetailCellSegue.swift */,
    1.51 -				49C34AF520E4F649009D11CC /* CellDetailTransition.swift */,
    1.52 +				00EB89AA20E3A27C00CDFA0D /* ThreadViewcontroller+SizeClasses.swift */,
    1.53 +				49D0A03B20ECC65500462858 /* Transitions */,
    1.54 +				494AFB0620ECC93C00BCB963 /* Util */,
    1.55 +			);
    1.56 +			path = Thread;
    1.57 +			sourceTree = "<group>";
    1.58 +		};
    1.59 +		494AFB0620ECC93C00BCB963 /* Util */ = {
    1.60 +			isa = PBXGroup;
    1.61 +			children = (
    1.62  				4902244E20E50488000E8D7C /* ThreadNavigationDelegate.swift */,
    1.63 -				0033C08220D7F41600224E61 /* EmailViewModelDelegate.swift */,
    1.64 -				49691B1420D7FD0200CA9367 /* MessageViewModelConfigurable.swift */,
    1.65  				00AEB2F520DBA7DA00DA185A /* NeedsRefreshDelegate.swift */,
    1.66 -				00EB89AA20E3A27C00CDFA0D /* ThreadViewcontroller+SizeClasses.swift */,
    1.67  				00EB89AE20E3E4A000CDFA0D /* ReplyAlertCreator.swift */,
    1.68  			);
    1.69 -			path = Threading;
    1.70 +			path = Util;
    1.71  			sourceTree = "<group>";
    1.72  		};
    1.73  		495560841E7150ED0016579E /* Frameworks */ = {
    1.74 @@ -2027,6 +2029,36 @@
    1.75  			path = Delegation;
    1.76  			sourceTree = "<group>";
    1.77  		};
    1.78 +		49B2ED2920EBBEC9005A41A3 /* ViewModel */ = {
    1.79 +			isa = PBXGroup;
    1.80 +			children = (
    1.81 +				005A21FA20CAA5F50082D19F /* ThreadedEmailViewModel.swift */,
    1.82 +				00EB89AC20E3D3C200CDFA0D /* ThreadedEmailViewModel+MoveToFolderDelegate.swift */,
    1.83 +				002375D520DD185100663961 /* TreadedEmailViewModel+DisplayedMessage.swift */,
    1.84 +				0033C07E20D7EBD500224E61 /* ThreadedEmailViewModel+UpdateThreadDelegate.swift */,
    1.85 +				0033C08220D7F41600224E61 /* ThreadedEmailViewModelDelegate.swift */,
    1.86 +			);
    1.87 +			path = ViewModel;
    1.88 +			sourceTree = "<group>";
    1.89 +		};
    1.90 +		49B2ED2A20EBBEEE005A41A3 /* Cells */ = {
    1.91 +			isa = PBXGroup;
    1.92 +			children = (
    1.93 +				00A12CAD20D3D9AC00B82BE3 /* FullMessageCell.swift */,
    1.94 +				002375D220DCDC7300663961 /* FullMessageCell+SecureWebViewControllerDelegate.swift */,
    1.95 +			);
    1.96 +			path = Cells;
    1.97 +			sourceTree = "<group>";
    1.98 +		};
    1.99 +		49D0A03B20ECC65500462858 /* Transitions */ = {
   1.100 +			isa = PBXGroup;
   1.101 +			children = (
   1.102 +				49228A5420D4035100A51E9D /* DetailCellSegue.swift */,
   1.103 +				49C34AF520E4F649009D11CC /* CellDetailTransition.swift */,
   1.104 +			);
   1.105 +			path = Transitions;
   1.106 +			sourceTree = "<group>";
   1.107 +		};
   1.108  		B706C0EF1EA8C378006B2F6C /* StoryboardFiles */ = {
   1.109  			isa = PBXGroup;
   1.110  			children = (
   1.111 @@ -2143,7 +2175,6 @@
   1.112  		B70D32B0205BCFBD0094A92A /* EmailDisplay */ = {
   1.113  			isa = PBXGroup;
   1.114  			children = (
   1.115 -				492EF92B20C69547004EAE14 /* Threading */,
   1.116  				496C0EE520BC2A7A0009B5B9 /* Delegation */,
   1.117  				B70D32B1205BD3E80094A92A /* CellAndSections */,
   1.118  				430D73601E9CBD0600EA6FA9 /* Background */,
   1.119 @@ -2247,6 +2278,7 @@
   1.120  				00EA5F6D20CFD46F00900097 /* Row */,
   1.121  				001D365F20C56524002434EB /* EmailListViewModel+MoveToFolderDelegate.swift */,
   1.122  				B75FF00A1EFD420F00C57289 /* EmailListViewModel.swift */,
   1.123 +				49691B1420D7FD0200CA9367 /* MessageViewModelConfigurable.swift */,
   1.124  				434E5A1A20DB9C4600D7F88A /* EmailListViewModel+MessageFolderDelegate.swift */,
   1.125  				15C5F2431F822560007DE086 /* PreviewMessage.swift */,
   1.126  				43E1619020D7B2D6003F1514 /* UpdateThreadListDelegate.swift */,
   1.127 @@ -2759,7 +2791,7 @@
   1.128  				B75CF47C20DBEB7200184F33 /* UnecryptedSubjectViewModel.swift in Sources */,
   1.129  				43F8D80620C538F00038ABD5 /* UnthreadedFolder.swift in Sources */,
   1.130  				43122B191DF5B48B00610253 /* EmailService.swift in Sources */,
   1.131 -				0033C08320D7F41600224E61 /* EmailViewModelDelegate.swift in Sources */,
   1.132 +				0033C08320D7F41600224E61 /* ThreadedEmailViewModelDelegate.swift in Sources */,
   1.133  				43498CDD200D0790006DC947 /* LoginViewModelOAuth2ErrorDelegate.swift in Sources */,
   1.134  				437E492820E506B600BF959C /* ThreadedFolderWithTop.swift in Sources */,
   1.135  				B74F81021EB0E20000519FCC /* LoginViewModel.swift in Sources */,
     2.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/CellDetailTransition.swift	Tue Jul 03 14:44:28 2018 +0200
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,113 +0,0 @@
     2.4 -//
     2.5 -//  CellDetailTransition.swift
     2.6 -//  pEp
     2.7 -//
     2.8 -//  Created by Miguel Berrocal Gómez on 28/06/2018.
     2.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    2.10 -//
    2.11 -
    2.12 -import Foundation
    2.13 -
    2.14 -class CellDetailTransition: NSObject, UIViewControllerAnimatedTransitioning {
    2.15 -
    2.16 -    let duration: TimeInterval
    2.17 -    let isDismissing: Bool
    2.18 -
    2.19 -    init(duration: TimeInterval, isDismissing: Bool = false) {
    2.20 -        self.duration = duration
    2.21 -        self.isDismissing = isDismissing
    2.22 -    }
    2.23 -
    2.24 -
    2.25 -    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    2.26 -        return duration
    2.27 -    }
    2.28 -
    2.29 -    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    2.30 -        if !isDismissing {
    2.31 -            animatePush(using: transitionContext)
    2.32 -        } else {
    2.33 -            animatePop(using: transitionContext)
    2.34 -        }
    2.35 -
    2.36 -    }
    2.37 -
    2.38 -    private func animatePush(using transitionContext: UIViewControllerContextTransitioning) {
    2.39 -        let containerView = transitionContext.containerView
    2.40 -
    2.41 -        guard let fromView = transitionContext.view(forKey: .from),
    2.42 -            let toView = transitionContext.view(forKey: .to),
    2.43 -            let fromViewController = transitionContext.viewController(forKey: .from),
    2.44 -            let tableView = (fromViewController as? ThreadViewController)?.tableView ,
    2.45 -            let selectedIndexPath = tableView.indexPathForSelectedRow,
    2.46 -            let cell = tableView.cellForRow(at: selectedIndexPath),
    2.47 -            let fullCell = cell as? FullMessageCell else {
    2.48 -                return
    2.49 -        }
    2.50 -
    2.51 -        tableView.bringSubview(toFront: cell)
    2.52 -
    2.53 -        let fromCellView:UIView = fullCell.roundedView
    2.54 -        let originalFrame = fromCellView.frame
    2.55 -
    2.56 -        var fromFrame: CGRect!
    2.57 -        let toFrame = toView.frame
    2.58 -        fromFrame = containerView.convert(fromCellView.frame, from: cell)
    2.59 -        toView.frame = fromFrame
    2.60 -        toView.alpha = 0
    2.61 -
    2.62 -        containerView.addSubview(toView)
    2.63 -
    2.64 -        UIView.animate(withDuration: 0.4, animations: {
    2.65 -            fromCellView.frame = containerView.convert(toFrame, to: fromCellView)
    2.66 -            fromCellView.frame.origin.x = 0
    2.67 -            toView.frame = toFrame
    2.68 -            toView.alpha = 1
    2.69 -            fromView.alpha = 0
    2.70 -        }) { (completed) in
    2.71 -            fromCellView.frame = originalFrame
    2.72 -            fromView.alpha = 1
    2.73 -            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    2.74 -        }
    2.75 -    }
    2.76 -
    2.77 -
    2.78 -    private func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
    2.79 -        let containerView = transitionContext.containerView
    2.80 -
    2.81 -        guard let fromView = transitionContext.view(forKey: .from),
    2.82 -            let toView = transitionContext.view(forKey: .to),
    2.83 -            let toViewController = transitionContext.viewController(forKey: .to),
    2.84 -            let tableView = (toViewController as? ThreadViewController)?.tableView,
    2.85 -            let selectedIndexPath = tableView.indexPathForSelectedRow,
    2.86 -            let cell = tableView.cellForRow(at: selectedIndexPath),
    2.87 -            let fullCell = cell as? FullMessageCell else {
    2.88 -                return
    2.89 -        }
    2.90 -
    2.91 -        tableView.bringSubview(toFront: cell)
    2.92 -
    2.93 -        let toCellView:UIView = fullCell.roundedView
    2.94 -        let originalFrame = toCellView.frame
    2.95 -
    2.96 -        var toFrame: CGRect!
    2.97 -
    2.98 -        toCellView.alpha = 0
    2.99 -        toView.alpha = 0
   2.100 -
   2.101 -        containerView.insertSubview(toView, belowSubview: fromView)
   2.102 -        toFrame = containerView.convert(toCellView.frame, from: cell)
   2.103 -        toCellView.frame = containerView.convert(fromView.frame, to: toCellView)
   2.104 -        toCellView.frame.origin.x = 0
   2.105 -
   2.106 -        UIView.animate(withDuration: 0.4, animations: {
   2.107 -            fromView.frame = toFrame
   2.108 -            toCellView.frame = originalFrame
   2.109 -            fromView.alpha = 0
   2.110 -            toCellView.alpha = 1
   2.111 -            toView.alpha = 1
   2.112 -        }) { (completed) in
   2.113 -            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
   2.114 -        }
   2.115 -    }
   2.116 -}
     3.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/DetailCellSegue.swift	Tue Jul 03 14:44:28 2018 +0200
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,45 +0,0 @@
     3.4 -//
     3.5 -//  DetailCellSegue.swift
     3.6 -//  pEp
     3.7 -//
     3.8 -//  Created by Miguel Berrocal Gómez on 15/06/2018.
     3.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    3.10 -//
    3.11 -
    3.12 -import UIKit
    3.13 -
    3.14 -class DetailCellSegue: UIStoryboardSegue {
    3.15 -
    3.16 -    override func perform() {
    3.17 -        guard let threadViewController = source as? ThreadViewController,
    3.18 -        let navigationController = destination as? UINavigationController,
    3.19 -        let destinationVC = navigationController.rootViewController,
    3.20 -        let destinationView = destinationVC.view.snapshotView(afterScreenUpdates: true),
    3.21 -        let tableView = threadViewController.tableView,
    3.22 -        let selectedIndexPath = tableView.indexPathForSelectedRow,
    3.23 -        let cell = tableView.cellForRow(at: selectedIndexPath)
    3.24 -        else {
    3.25 -            return
    3.26 -        }
    3.27 -
    3.28 -
    3.29 -        // create an NSData object from myView
    3.30 -        let archive = NSKeyedArchiver.archivedData(withRootObject: cell.contentView)
    3.31 -
    3.32 -        // create a clone by unarchiving the NSData
    3.33 -        let view = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView
    3.34 -
    3.35 -        let originalFrame = destinationView.frame
    3.36 -
    3.37 -        view.frame = threadViewController.view.convert(cell.frame, from: tableView)
    3.38 -        threadViewController.view.insertSubview(view, aboveSubview: tableView)
    3.39 -
    3.40 -        UIView.animate(withDuration: 0.4, animations: {
    3.41 -            view.frame = originalFrame
    3.42 -
    3.43 -        }) { (completed) in
    3.44 -            threadViewController.showDetailViewController(self.destination, sender: threadViewController)
    3.45 -            view.removeFromSuperview()
    3.46 -        }
    3.47 -    }
    3.48 -}
     4.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/EmailViewModelDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,14 +0,0 @@
     4.4 -//
     4.5 -//  EmailViewModelDelegate.swift
     4.6 -//  pEp
     4.7 -//
     4.8 -//  Created by Borja González de Pablo on 18/06/2018.
     4.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    4.10 -//
    4.11 -
    4.12 -import Foundation
    4.13 -protocol EmailViewModelDelegate: class, TableViewUpdate {
    4.14 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didInsertDataAt index: Int)
    4.15 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didUpdateDataAt index: Int)
    4.16 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didRemoveDataAt index: Int)
    4.17 -}
     5.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/FullMessageCell+SecureWebViewControllerDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,17 +0,0 @@
     5.4 -//
     5.5 -//  FullMessageCell+SecureWebViewControllerDelegate.swift
     5.6 -//  pEp
     5.7 -//
     5.8 -//  Created by Borja González de Pablo on 22/06/2018.
     5.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    5.10 -//
    5.11 -
    5.12 -import Foundation
    5.13 -
    5.14 -extension FullMessageCell: SecureWebViewControllerDelegate {
    5.15 -    func secureWebViewController(_ webViewController: SecureWebViewController, sizeChangedTo size: CGSize) {
    5.16 -        self.contentHeightConstraint.constant = size.height
    5.17 -        self.contentHeightConstraint.isActive = true
    5.18 -        requestsReload?()
    5.19 -    }
    5.20 -}
     6.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/FullMessageCell.swift	Tue Jul 03 14:44:28 2018 +0200
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,185 +0,0 @@
     6.4 -//
     6.5 -//  FullMessageCell.swift
     6.6 -//  pEp
     6.7 -//
     6.8 -//  Created by Miguel Berrocal Gómez on 14/06/2018.
     6.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    6.10 -//
    6.11 -
    6.12 -import UIKit
    6.13 -import MessageModel
    6.14 -import SwipeCellKit
    6.15 -
    6.16 -class FullMessageCell: SwipeTableViewCell,
    6.17 -    MessageViewModelConfigurable,
    6.18 -    NeedsRefreshDelegate {
    6.19 -
    6.20 -    static var flaggedImage: UIImage? = nil
    6.21 -
    6.22 -    var requestsReload: (() -> Void)?
    6.23 -
    6.24 -    @IBOutlet weak var contentHeightConstraint: NSLayoutConstraint!
    6.25 -    @IBOutlet weak var roundedView: UIView!
    6.26 -    @IBOutlet weak var addressLabel: UILabel!
    6.27 -    @IBOutlet weak var summaryLabel: UILabel!
    6.28 -    @IBOutlet weak var dateLabel: UILabel!
    6.29 -    @IBOutlet weak var subjectLabel: UILabel!
    6.30 -    @IBOutlet weak var bodyText: UITextView!
    6.31 -    @IBOutlet weak var body: UIView!
    6.32 -    @IBOutlet weak var stackView: UIStackView!
    6.33 -    @IBOutlet weak var profilePicture: UIImageView!
    6.34 -    @IBOutlet weak var badgePicture: UIImageView!
    6.35 -    @IBOutlet weak var attachmentIcon: UIImageView!
    6.36 -    @IBOutlet weak var flaggedIcon: UIImageView!
    6.37 -
    6.38 -    var tableView: UITableView!
    6.39 -
    6.40 -    var isFlagged:Bool = false {
    6.41 -        didSet {
    6.42 -            if isFlagged {
    6.43 -                setFlagged()
    6.44 -            } else {
    6.45 -                unsetFlagged()
    6.46 -            }
    6.47 -        }
    6.48 -    }
    6.49 -
    6.50 -
    6.51 -    @IBOutlet weak var view: UIView!
    6.52 -    func configure(for viewModel:MessageViewModel) {
    6.53 -        isFlagged = viewModel.isFlagged
    6.54 -        addressLabel.text = viewModel.from
    6.55 -        subjectLabel.text = viewModel.subject
    6.56 -        backgroundColor = UIColor.clear
    6.57 -        dateLabel.text = viewModel.dateText
    6.58 -        viewModel.getProfilePicture { image in
    6.59 -            self.profilePicture.image = image
    6.60 -        }
    6.61 -        viewModel.getSecurityBadge { image in
    6.62 -            self.badgePicture.image = image
    6.63 -        }
    6.64 -        if let htmlBody = htmlBody(message: viewModel.message) {
    6.65 -            // Its fine to use a webview (iOS>=11) and we do have HTML content.
    6.66 -            bodyText.isHidden = true
    6.67 -            view.addSubview(htmlViewerViewController.view)
    6.68 -            view.isUserInteractionEnabled = false
    6.69 -
    6.70 -            htmlViewerViewController.view.fullSizeInSuperView()
    6.71 -
    6.72 -            let displayHtml = appendInlinedPlainText(fromAttachmentsIn: viewModel.message, to: htmlBody)
    6.73 -            htmlViewerViewController.display(htmlString: displayHtml)
    6.74 -        } else {
    6.75 -            bodyText.attributedText = viewModel.body
    6.76 -            bodyText.isHidden = false
    6.77 -            bodyText.tintColor = UIColor.pEpGreen
    6.78 -            // We are not allowed to use a webview (iOS<11) or do not have HTML content.
    6.79 -            // Remove the HTML view if we just stepped from an HTML mail to one without
    6.80 -            if htmlViewerViewControllerExists &&
    6.81 -                htmlViewerViewController.view.superview == self.contentView {
    6.82 -                htmlViewerViewController.view.removeFromSuperview()
    6.83 -            }
    6.84 -        }
    6.85 -
    6.86 -    }
    6.87 -
    6.88 -    override func awakeFromNib() {
    6.89 -        super.awakeFromNib()
    6.90 -        // Initialization code
    6.91 -    }
    6.92 -
    6.93 -    override func setSelected(_ selected: Bool, animated: Bool) {
    6.94 -        super.setSelected(selected, animated: animated)
    6.95 -
    6.96 -        // Configure the view for the selected state
    6.97 -    }
    6.98 -
    6.99 -    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
   6.100 -        super.setHighlighted(highlighted, animated: animated)
   6.101 -        
   6.102 -    }
   6.103 -
   6.104 -    /**
   6.105 -     Indicate that the htmlViewerViewController already exists, to avoid
   6.106 -     instantiation just to check if it has been instantiated.
   6.107 -     */
   6.108 -    var htmlViewerViewControllerExists = false
   6.109 -
   6.110 -    lazy private var htmlViewerViewController: SecureWebViewController = {
   6.111 -        let storyboard = UIStoryboard(name: "Reusable", bundle: nil)
   6.112 -        guard let vc =
   6.113 -            storyboard.instantiateViewController(withIdentifier: SecureWebViewController.storyboardId)
   6.114 -                as? SecureWebViewController
   6.115 -            else {
   6.116 -                Log.shared.errorAndCrash(component: #function, errorString: "Cast error")
   6.117 -                return SecureWebViewController()
   6.118 -        }
   6.119 -        vc.scrollingEnabled = false
   6.120 -        vc.delegate = self
   6.121 -
   6.122 -        htmlViewerViewControllerExists = true
   6.123 -
   6.124 -        return vc
   6.125 -    }()
   6.126 -
   6.127 -    /**
   6.128 -     Yields the HTML message body if:
   6.129 -     * we can show it in a secure way
   6.130 -     * we have non-empty HTML content at all
   6.131 -     - Returns: The HTML message body or nil
   6.132 -     */
   6.133 -    private func htmlBody(message: Message?) ->  String? {
   6.134 -        guard
   6.135 -            SecureWebViewController.isSaveToUseWebView,
   6.136 -            let m = message,
   6.137 -            let htmlBody = m.longMessageFormatted,
   6.138 -            !htmlBody.isEmpty else {
   6.139 -                return nil
   6.140 -        }
   6.141 -
   6.142 -        return htmlBody
   6.143 -    }
   6.144 -
   6.145 -    private func appendInlinedPlainText(fromAttachmentsIn message: Message, to text: String) -> String {
   6.146 -        var result = text
   6.147 -        let inlinedText = message.inlinedTextAttachments()
   6.148 -        for inlinedTextAttachment in inlinedText {
   6.149 -            guard
   6.150 -                let data = inlinedTextAttachment.data,
   6.151 -                let inlinedText = String(data: data, encoding: .utf8) else {
   6.152 -                    continue
   6.153 -            }
   6.154 -            result = append(appendText: inlinedText, to: result)
   6.155 -        }
   6.156 -        return result
   6.157 -    }
   6.158 -
   6.159 -    private func append(appendText: String, to body: String) -> String {
   6.160 -        var result = body
   6.161 -        let replacee = result.contains(find: "</body>") ? "</body>" : "</html>"
   6.162 -        if result.contains(find: replacee) {
   6.163 -            result = result.replacingOccurrences(of: replacee, with: appendText + replacee)
   6.164 -        } else {
   6.165 -            result += "\n" + appendText
   6.166 -        }
   6.167 -        return result
   6.168 -    }
   6.169 -
   6.170 -    private func setFlagged() {
   6.171 -        if FullMessageCell.flaggedImage == nil {
   6.172 -            FullMessageCell.flaggedImage =
   6.173 -                FlagImages.create(imageSize: flaggedIcon.frame.size).flaggedImage
   6.174 -        }
   6.175 -        guard let saveImg = FullMessageCell.flaggedImage else {
   6.176 -            return
   6.177 -        }
   6.178 -        self.flaggedIcon.isHidden = false
   6.179 -        self.flaggedIcon.image = saveImg
   6.180 -    }
   6.181 -
   6.182 -    private func unsetFlagged() {
   6.183 -        self.flaggedIcon.isHidden = true
   6.184 -        self.flaggedIcon.image = nil
   6.185 -    }
   6.186 -
   6.187 -
   6.188 -}
     7.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/MessageViewModelConfigurable.swift	Tue Jul 03 14:44:28 2018 +0200
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,13 +0,0 @@
     7.4 -//
     7.5 -//  MessageViewModelConfigurable.swift
     7.6 -//  pEp
     7.7 -//
     7.8 -//  Created by Miguel Berrocal Gómez on 18/06/2018.
     7.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    7.10 -//
    7.11 -
    7.12 -import Foundation
    7.13 -
    7.14 -protocol MessageViewModelConfigurable {
    7.15 -    func configure(for viewModel:MessageViewModel)
    7.16 -}
     8.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/NeedsRefreshDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,13 +0,0 @@
     8.4 -//
     8.5 -//  NeedsRefreshDelegate.swift
     8.6 -//  pEp
     8.7 -//
     8.8 -//  Created by Borja González de Pablo on 21/06/2018.
     8.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    8.10 -//
    8.11 -
    8.12 -import Foundation
    8.13 -
    8.14 -protocol NeedsRefreshDelegate {
    8.15 -    var requestsReload: (() -> Void)?  {get set}
    8.16 -}
     9.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ReplyAlertCreator.swift	Tue Jul 03 14:44:28 2018 +0200
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,52 +0,0 @@
     9.4 -//
     9.5 -//  ReplyAlertCreator.swift
     9.6 -//  pEp
     9.7 -//
     9.8 -//  Created by Borja González de Pablo on 27/06/2018.
     9.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
    9.10 -//
    9.11 -
    9.12 -import Foundation
    9.13 -class ReplyAlertCreator {
    9.14 -
    9.15 -    public let alert: UIAlertController
    9.16 -    public init(){
    9.17 -        alert = UIAlertController.pEpAlertController()
    9.18 -    }
    9.19 -
    9.20 -    public func withReplyOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
    9.21 -        let alertActionReply = UIAlertAction(
    9.22 -            title: NSLocalizedString("Reply", comment: "Message actions"),
    9.23 -            style: .default, handler: handler)
    9.24 -        alert.addAction(alertActionReply)
    9.25 -        return self
    9.26 -    }
    9.27 -
    9.28 -    public func withReplyAllOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
    9.29 -        let alertActionReplyAll = UIAlertAction(
    9.30 -            title: NSLocalizedString("Reply All", comment: "Message actions"),
    9.31 -            style: .default, handler: handler)
    9.32 -        alert.addAction(alertActionReplyAll)
    9.33 -        return self
    9.34 -    }
    9.35 -
    9.36 -    public func withFordwardOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
    9.37 -        let alertActionForward = UIAlertAction(
    9.38 -            title: NSLocalizedString("Forward", comment: "Message actions"),
    9.39 -            style: .default, handler: handler)
    9.40 -        alert.addAction(alertActionForward)
    9.41 -        return self
    9.42 -    }
    9.43 -
    9.44 -
    9.45 -    public func withCancelOption() -> ReplyAlertCreator {
    9.46 -        let cancelAction = UIAlertAction(
    9.47 -            title: NSLocalizedString("Cancel", comment: "Message actions"),
    9.48 -            style: .cancel) { (action) in }
    9.49 -        alert.addAction(cancelAction)
    9.50 -        return self
    9.51 -    }
    9.52 -    public func build()-> UIAlertController{
    9.53 -        return alert
    9.54 -    }
    9.55 -}
    10.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadNavigationDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
    10.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.3 @@ -1,32 +0,0 @@
    10.4 -//
    10.5 -//  ThreadNaviationDelegate
    10.6 -//  pEp
    10.7 -//
    10.8 -//  Created by Miguel Berrocal Gómez on 28/06/2018.
    10.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   10.10 -//
   10.11 -
   10.12 -class ThreadNavigationDelegate: NSObject,
   10.13 -UINavigationControllerDelegate {
   10.14 -
   10.15 -    func navigationController(
   10.16 -        _ navigationController: UINavigationController,
   10.17 -        animationControllerFor operation:
   10.18 -        UINavigationControllerOperation,
   10.19 -        from fromVC: UIViewController,
   10.20 -        to toVC: UIViewController
   10.21 -        ) -> UIViewControllerAnimatedTransitioning? {
   10.22 -
   10.23 -        guard let splitView = navigationController.splitViewController,
   10.24 -        !splitView.isCollapsed else {
   10.25 -            return nil
   10.26 -        }
   10.27 -        if toVC is EmailViewController {
   10.28 -            return CellDetailTransition(duration: 0.5)
   10.29 -        }
   10.30 -        if fromVC is EmailViewController && operation == .pop {
   10.31 -            return CellDetailTransition(duration: 0.5, isDismissing: true)
   10.32 -        }
   10.33 -        return nil
   10.34 -    }
   10.35 -}
    11.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+EmailViewDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,42 +0,0 @@
    11.4 -//
    11.5 -//  ThreadViewController+EmailViewControllerDelegate.swift
    11.6 -//  pEp
    11.7 -//
    11.8 -//  Created by Borja González de Pablo on 18/06/2018.
    11.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   11.10 -//
   11.11 -
   11.12 -import Foundation
   11.13 -extension ThreadViewController: EmailViewModelDelegate {
   11.14 -
   11.15 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didInsertDataAt index: Int) {
   11.16 -        updateTableView {
   11.17 -            tableView.insertSections(IndexSet([index]), with: .automatic)
   11.18 -        }
   11.19 -    }
   11.20 -
   11.21 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didUpdateDataAt index: Int) {
   11.22 -        updateTableView {
   11.23 -            tableView.reloadSections(IndexSet([index]), with: .none)
   11.24 -        }
   11.25 -    }
   11.26 -
   11.27 -    func emailViewModel(viewModel: ThreadedEmailViewModel, didRemoveDataAt index: Int) {
   11.28 -        updateTableView {
   11.29 -            tableView.deleteSections(IndexSet([index]), with: .none)
   11.30 -        }
   11.31 -    }
   11.32 -
   11.33 -    func updateView() {
   11.34 -        tableView.dataSource = self
   11.35 -        tableView.reloadData()
   11.36 -    }
   11.37 -
   11.38 -    private func updateTableView(updates: ()->()) {
   11.39 -        tableView.beginUpdates()
   11.40 -        updates()
   11.41 -        tableView.endUpdates()
   11.42 -    }
   11.43 -    
   11.44 -
   11.45 -}
    12.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+SegueHandlerType.swift	Tue Jul 03 14:44:28 2018 +0200
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,79 +0,0 @@
    12.4 -//
    12.5 -//  ThreadViewController+SegueHandlerType.swift
    12.6 -//  pEp
    12.7 -//
    12.8 -//  Created by Miguel Berrocal Gómez on 15/06/2018.
    12.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   12.10 -//
   12.11 -
   12.12 -import Foundation
   12.13 -extension ThreadViewController: SegueHandlerType {
   12.14 -
   12.15 -    enum SegueIdentifier: String {
   12.16 -        case segueShowMoveToFolder
   12.17 -        case segueReplyFrom
   12.18 -        case segueReplyAllForm
   12.19 -        case segueForward
   12.20 -        case segueShowEmail
   12.21 -        case segueShowEmailExpanding
   12.22 -    }
   12.23 -
   12.24 -    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   12.25 -        let segueId = segueIdentifier(for: segue)
   12.26 -        switch segueId {
   12.27 -        case .segueShowEmail, .segueShowEmailExpanding:
   12.28 -//            guard let nav = segue.destination as? UINavigationController,
   12.29 -                guard let vc = segue.destination as? EmailViewController,
   12.30 -                let appConfig = self.appConfig,
   12.31 -                let indexPath = tableView.indexPathForSelectedRow,
   12.32 -                let message = model?.message(at: indexPath.section) else {
   12.33 -                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   12.34 -                    return
   12.35 -            }
   12.36 -            vc.appConfig = appConfig
   12.37 -            vc.shouldShowOKButton = !isSplitViewControllerCollapsed()
   12.38 -            vc.message = message
   12.39 -            vc.folderShow = model?.displayFolder
   12.40 -            vc.messageId = indexPath.row
   12.41 -            break
   12.42 -        case .segueShowMoveToFolder:
   12.43 -            guard  let nav = segue.destination as? UINavigationController,
   12.44 -                let destination = nav.topViewController as? MoveToAccountViewController else {
   12.45 -                    Log.shared.errorAndCrash(component: #function, errorString: "No DVC?")
   12.46 -                    break
   12.47 -            }
   12.48 -            destination.appConfig = appConfig
   12.49 -            if let messages = model?.messages {
   12.50 -                destination.viewModel = MoveToAccountViewModel(messages: messages)
   12.51 -            }
   12.52 -            destination.delegate = model
   12.53 -            break
   12.54 -        case .segueReplyFrom, .segueReplyAllForm, .segueForward:
   12.55 -            guard  let nav = segue.destination as? UINavigationController,
   12.56 -                let destination = nav.topViewController as? ComposeTableViewController,
   12.57 -                let appConfig = appConfig else {
   12.58 -                    Log.shared.errorAndCrash(component: #function, errorString: "No DVC?")
   12.59 -                    break
   12.60 -            }
   12.61 -
   12.62 -
   12.63 -            destination.appConfig = appConfig
   12.64 -
   12.65 -            if segueId == .segueReplyFrom {
   12.66 -                destination.composeMode = .replyFrom
   12.67 -                destination.originalMessage = model.getMessageToReply()
   12.68 -            } else if segueId == .segueReplyAllForm {
   12.69 -                destination.composeMode = .replyAll
   12.70 -                destination.originalMessage =  model.getMessageToReply()
   12.71 -            } else if segueId == .segueForward {
   12.72 -                destination.composeMode = .forward
   12.73 -                destination.originalMessage =  model.getMessageToReply()
   12.74 -            }
   12.75 -            break
   12.76 -        default:
   12.77 -            Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
   12.78 -            break
   12.79 -        }
   12.80 -    }
   12.81 -
   12.82 -}
    13.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+SwipeCell.swift	Tue Jul 03 14:44:28 2018 +0200
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,92 +0,0 @@
    13.4 -//
    13.5 -//  ThreadViewController+SwipeCell.swift
    13.6 -//  pEp
    13.7 -//
    13.8 -//  Created by Miguel Berrocal Gómez on 21/06/2018.
    13.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   13.10 -//
   13.11 -
   13.12 -import Foundation
   13.13 -import SwipeCellKit
   13.14 -
   13.15 -extension ThreadViewController: SwipeTableViewCellDelegate {
   13.16 -
   13.17 -    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
   13.18 -        var swipeActions = [SwipeAction]()
   13.19 -
   13.20 -        // Delete or Archive
   13.21 -        swipeActions.append(deleteAction())
   13.22 -
   13.23 -        //Get from model
   13.24 -        let draftFolder = false
   13.25 -        if !draftFolder {
   13.26 -            swipeActions.append(flagAction())
   13.27 -            swipeActions.append(replyAction())
   13.28 -        }
   13.29 -
   13.30 -        return (orientation == .right ?   swipeActions : nil)
   13.31 -    }
   13.32 -
   13.33 -    func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
   13.34 -        var options = SwipeOptions()
   13.35 -        options.expansionStyle = .destructive(automaticallyDelete: false)
   13.36 -        options.buttonSpacing = 4
   13.37 -        options.transitionStyle = .border
   13.38 -        options.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0)
   13.39 -
   13.40 -        return options
   13.41 -    }
   13.42 -
   13.43 -    private func configure(action: SwipeAction, with descriptor: SwipeActionDescriptor) {
   13.44 -        let buttonStyle: ButtonStyle = .circular
   13.45 -        let buttonDisplayMode: ButtonDisplayMode = .imageOnly
   13.46 -        action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
   13.47 -        action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
   13.48 -        action.backgroundColor = .clear
   13.49 -        action.textColor = descriptor.color
   13.50 -        action.font = .systemFont(ofSize: 13)
   13.51 -        action.transitionDelegate = ScaleTransition.default
   13.52 -    }
   13.53 -
   13.54 -    private func deleteAction() -> SwipeAction {
   13.55 -        let defaultIsArchive = false
   13.56 -        let titleDestructive = defaultIsArchive ? "Archive" : "Delete"
   13.57 -        let descriptorDestructive: SwipeActionDescriptor = defaultIsArchive ? .archive : .trash
   13.58 -        let archiveAction =
   13.59 -            SwipeAction(style: .destructive, title: titleDestructive) {action, indexPath in
   13.60 -                self.tableView.beginUpdates()
   13.61 -                self.model?.deleteMessage(at: indexPath.section)
   13.62 -                action.fulfill(with: .delete)
   13.63 -                self.tableView.endUpdates()
   13.64 -        }
   13.65 -        configure(action: archiveAction, with: descriptorDestructive)
   13.66 -        return archiveAction
   13.67 -    }
   13.68 -
   13.69 -    private func flagAction() -> SwipeAction {
   13.70 -        // Do not add "Flag" action to drafted mails.
   13.71 -        let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
   13.72 -            self.model.switchFlag(forMessageAt: indexPath.section)
   13.73 -            guard let cell = self.tableView.cellForRow(at: indexPath) as? FullMessageCell else {
   13.74 -                return
   13.75 -            }
   13.76 -            cell.isFlagged = !cell.isFlagged
   13.77 -        }
   13.78 -        flagAction.hidesWhenSelected = true
   13.79 -        configure(action: flagAction, with: .flag)
   13.80 -        return flagAction
   13.81 -    }
   13.82 -
   13.83 -    private func replyAction() -> SwipeAction {
   13.84 -        // Do not add reply action to drafted mails.
   13.85 -        let moreAction = SwipeAction(style: .default, title: "Reply") { action, indexPath in
   13.86 -            self.model.replyToMessage(at: indexPath.section)
   13.87 -            self.performSegue(withIdentifier: .segueReplyAllForm , sender: self)
   13.88 -        }
   13.89 -        moreAction.hidesWhenSelected = true
   13.90 -        configure(action: moreAction, with: .reply)
   13.91 -        return moreAction
   13.92 -    }
   13.93 -
   13.94 -
   13.95 -}
    14.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController+TableView.swift	Tue Jul 03 14:44:28 2018 +0200
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,89 +0,0 @@
    14.4 -//
    14.5 -//  ThreadViewController+TableView.swift
    14.6 -//  pEp
    14.7 -//
    14.8 -//  Created by Miguel Berrocal Gómez on 05/06/2018.
    14.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   14.10 -//
   14.11 -
   14.12 -import Foundation
   14.13 -import SwipeCellKit
   14.14 -import UIKit
   14.15 -
   14.16 -extension ThreadViewController: UITableViewDelegate, UITableViewDataSource {
   14.17 -
   14.18 -
   14.19 -    func numberOfSections(in tableView: UITableView) -> Int {
   14.20 -        return model?.rowCount() ?? 0
   14.21 -    }
   14.22 -
   14.23 -    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   14.24 -        return 1
   14.25 -    }
   14.26 -
   14.27 -    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
   14.28 -        if model?.messageisExpanded(at: indexPath.section) == true {
   14.29 -            return UITableViewAutomaticDimension
   14.30 -        }
   14.31 -        else {
   14.32 -            return 100
   14.33 -        }
   14.34 -    }
   14.35 -
   14.36 -    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   14.37 -        if model?.messageisExpanded(at: indexPath.section) == true {
   14.38 -            return configureCell(identifier: "expandedCell", at: indexPath)
   14.39 -        }
   14.40 -        else {
   14.41 -            return configureCell(identifier: "unexpandedCell", at: indexPath)
   14.42 -        }
   14.43 -    }
   14.44 -
   14.45 -    func configureCell(identifier:String, at indexPath:IndexPath) -> UITableViewCell {
   14.46 -        guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier)
   14.47 -                as? UITableViewCell & MessageViewModelConfigurable,
   14.48 -            let viewModel = model?.viewModel(for: indexPath.section)  else {
   14.49 -                return UITableViewCell()
   14.50 -        }
   14.51 -        if var refreshableCell = cell as? NeedsRefreshDelegate {
   14.52 -            refreshableCell.requestsReload = { self.tableView.updateSize() }
   14.53 -        }
   14.54 -
   14.55 -        cell.configure(for: viewModel)
   14.56 -        configureSwipeCell(cell: cell)
   14.57 -        return cell
   14.58 -    }
   14.59 -
   14.60 -    func configureSwipeCell(cell: UITableViewCell) {
   14.61 -        guard let cell = cell as? SwipeTableViewCell else {
   14.62 -            return
   14.63 -        }
   14.64 -
   14.65 -        cell.delegate = self
   14.66 -    }
   14.67 -
   14.68 -
   14.69 -    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   14.70 -        if model?.messageisExpanded(at: indexPath.section) == false {
   14.71 -            model?.messageDidExpand(at: indexPath.section)
   14.72 -            let indexSet = IndexSet(integer: indexPath.section)
   14.73 -            tableView.reloadSections(indexSet, with: .automatic)
   14.74 -        }
   14.75 -        else {
   14.76 -            showEmail()
   14.77 -        }
   14.78 -    }
   14.79 -
   14.80 -    private func showEmail() {
   14.81 -//        guard let splitViewController = self.splitViewController else {
   14.82 -//            Log.shared.errorAndCrash(component: #function,
   14.83 -//                                     errorString: "We must have a splitViewController here")
   14.84 -//            return
   14.85 -//        }
   14.86 -//        if splitViewController.isCollapsed {
   14.87 -            performSegue(withIdentifier: .segueShowEmail, sender: self)
   14.88 -//        } else {
   14.89 -//            performSegue(withIdentifier: .SegueShowEmailExpanding, sender: self)
   14.90 -//        }
   14.91 -    }
   14.92 -}
    15.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewController.swift	Tue Jul 03 14:44:28 2018 +0200
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,100 +0,0 @@
    15.4 -//
    15.5 -//  ThreadViewController.swift
    15.6 -//  pEp
    15.7 -//
    15.8 -//  Created by Miguel Berrocal Gómez on 05/06/2018.
    15.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   15.10 -//
   15.11 -
   15.12 -import UIKit
   15.13 -
   15.14 -class ThreadViewController: BaseViewController {
   15.15 -    var barItems: [UIBarButtonItem]?
   15.16 -
   15.17 -    @IBOutlet weak var flagButton: UIBarButtonItem!
   15.18 -    @IBOutlet weak var tableView: UITableView!
   15.19 -    var model: ThreadedEmailViewModel!
   15.20 -
   15.21 -    var messages = ["hola", "que", "tal"]
   15.22 -    override func viewDidLoad() {
   15.23 -        super.viewDidLoad()
   15.24 -        configureSplitViewBackButton()
   15.25 -        guard let model = model else {
   15.26 -            return
   15.27 -        }
   15.28 -        model.delegate = self
   15.29 -        self.navigationItem.title = String(model.rowCount())  + " messages"
   15.30 -        setUpFlaggedStatus()
   15.31 -    }
   15.32 -
   15.33 -    override func didReceiveMemoryWarning() {
   15.34 -        super.didReceiveMemoryWarning()
   15.35 -        // Dispose of any resources that can be recreated.
   15.36 -    }
   15.37 -
   15.38 -    @IBAction func segueUnwindEmailDisplayDone(segue:UIStoryboardSegue) {
   15.39 -        //do nothing
   15.40 -    }
   15.41 -
   15.42 -    func setUpFlaggedStatus(){
   15.43 -        let allFlagged =  model?.allMessagesFlagged() ?? false
   15.44 -
   15.45 -        if allFlagged {
   15.46 -            flagButton.image = UIImage(named: "icon-flagged")
   15.47 -        } else {
   15.48 -            flagButton.image = UIImage(named: "icon-unflagged")
   15.49 -        }
   15.50 -    }
   15.51 -    
   15.52 -    private func configureSplitViewBackButton() {
   15.53 -        self.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
   15.54 -        self.navigationItem.leftItemsSupplementBackButton = true
   15.55 -    }
   15.56 -
   15.57 -    internal func isSplitViewControllerCollapsed() -> Bool! {
   15.58 -        guard let splitViewController = self.splitViewController else {
   15.59 -            Log.shared.errorAndCrash(component: #function, errorString: "We need a splitViewController here")
   15.60 -            return nil
   15.61 -        }
   15.62 -        return splitViewController.isCollapsed
   15.63 -    }
   15.64 -
   15.65 -    // MARK: Actions
   15.66 -    
   15.67 -    @IBAction func flagButtonTapped(_ sender: Any) {
   15.68 -        guard let model = model else {
   15.69 -            return
   15.70 -        }
   15.71 -        model.setFlag(to: !model.allMessagesFlagged())
   15.72 -        setUpFlaggedStatus()
   15.73 -        tableView.reloadData()
   15.74 -    }
   15.75 -
   15.76 -    @IBAction func moveToFolderTapped(_ sender: Any) {
   15.77 -        performSegue(withIdentifier: .segueShowMoveToFolder, sender: self)
   15.78 -    }
   15.79 -
   15.80 -    @IBAction func destructiveButtonTapped(_ sender: Any) {
   15.81 -        model?.deleteAllMessages()
   15.82 -    }
   15.83 -    
   15.84 -    @IBAction func replyButtonTapped(_ sender: UIBarButtonItem) {
   15.85 -
   15.86 -        let alert = ReplyAlertCreator()
   15.87 -            .withReplyOption { action in
   15.88 -                self.performSegue(withIdentifier: .segueReplyFrom , sender: self)
   15.89 -            }.withReplyAllOption { action in
   15.90 -                self.performSegue(withIdentifier: .segueReplyAllForm , sender: self)
   15.91 -            }.withFordwardOption { action in
   15.92 -                 self.performSegue(withIdentifier: .segueForward , sender: self)
   15.93 -            }.withCancelOption()
   15.94 -            .build()
   15.95 -
   15.96 -        if let popoverPresentationController = alert.popoverPresentationController {
   15.97 -            popoverPresentationController.barButtonItem = sender
   15.98 -        }
   15.99 -
  15.100 -        present(alert, animated: true, completion: nil)
  15.101 -    }
  15.102 -
  15.103 -}
    16.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadViewcontroller+SizeClasses.swift	Tue Jul 03 14:44:28 2018 +0200
    16.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3 @@ -1,31 +0,0 @@
    16.4 -//
    16.5 -//  ThreadedViewcontroller+SizeClasses.swift
    16.6 -//  pEp
    16.7 -//
    16.8 -//  Created by Borja González de Pablo on 27/06/2018.
    16.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   16.10 -//
   16.11 -
   16.12 -import Foundation
   16.13 -
   16.14 -extension ThreadViewController {
   16.15 -    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
   16.16 -        super.traitCollectionDidChange(previousTraitCollection)
   16.17 -
   16.18 -        if (traitCollection.horizontalSizeClass == .regular &&
   16.19 -            traitCollection.verticalSizeClass == .regular) {
   16.20 -            adaptBarButtonItemsForRegularSize()
   16.21 -        }
   16.22 -    }
   16.23 -    private func adaptBarButtonItemsForRegularSize() {
   16.24 -        guard let items = toolbarItems else {
   16.25 -            return
   16.26 -        }
   16.27 -
   16.28 -        barItems = items
   16.29 -
   16.30 -        navigationItem.rightBarButtonItems = items
   16.31 -        self.navigationController?.setToolbarHidden(true, animated: true)
   16.32 -        toolbarItems = nil
   16.33 -    }
   16.34 -}
    17.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel+MoveToFolderDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
    17.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3 @@ -1,18 +0,0 @@
    17.4 -//
    17.5 -//  ThreadedEmailViewModel+MoveToFolderDelegate.swift
    17.6 -//  pEp
    17.7 -//
    17.8 -//  Created by Borja González de Pablo on 27/06/2018.
    17.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   17.10 -//
   17.11 -
   17.12 -import Foundation
   17.13 -
   17.14 -extension ThreadedEmailViewModel: MoveToFolderDelegate{
   17.15 -    func didMove() {
   17.16 -        guard let lastMessage = messages.last else {
   17.17 -            return
   17.18 -        }
   17.19 -        emailDisplayDelegate.emailDisplayDidDelete(message: lastMessage)
   17.20 -    }
   17.21 -}
    18.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel+UpdateThreadDelegate.swift	Tue Jul 03 14:44:28 2018 +0200
    18.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.3 @@ -1,43 +0,0 @@
    18.4 -//
    18.5 -//  ThreadedEmailViewModel+UpdateThreadDelegate.swift
    18.6 -//  pEp
    18.7 -//
    18.8 -//  Created by Borja González de Pablo on 18/06/2018.
    18.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   18.10 -//
   18.11 -
   18.12 -import Foundation
   18.13 -import MessageModel
   18.14 -
   18.15 -extension ThreadedEmailViewModel: UpdateThreadListDelegate {
   18.16 -
   18.17 -    func deleted(message: Message){
   18.18 -        deleted(topMessage: message)
   18.19 -    }
   18.20 -
   18.21 -    func deleted(topMessage message: Message) {
   18.22 -        guard let index = indexOfMessage(message: message) else {
   18.23 -            return
   18.24 -        }
   18.25 -        messages.remove(at: index)
   18.26 -        delegate?.emailViewModel(viewModel: self, didRemoveDataAt: index)
   18.27 -    }
   18.28 -
   18.29 -    func updated(message: Message) {
   18.30 -        updateInternal(message: message)
   18.31 -    }
   18.32 -
   18.33 -    func added(message: Message) {
   18.34 -        let index = addMessage(message: message)
   18.35 -        delegate?.emailViewModel(viewModel: self, didInsertDataAt: index)
   18.36 -    }
   18.37 -
   18.38 -    internal func indexOfMessage(message: Message)-> Int? {
   18.39 -        for  i in 0...messages.count {
   18.40 -            if messages[i] == message{
   18.41 -                return i
   18.42 -            }
   18.43 -        }
   18.44 -        return nil
   18.45 -    }
   18.46 -}
    19.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/ThreadedEmailViewModel.swift	Tue Jul 03 14:44:28 2018 +0200
    19.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.3 @@ -1,158 +0,0 @@
    19.4 -//
    19.5 -//  ThreadedEmailViewModel.swift
    19.6 -//  pEp
    19.7 -//
    19.8 -//  Created by Borja González de Pablo on 08/06/2018.
    19.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   19.10 -//
   19.11 -
   19.12 -import Foundation
   19.13 -import MessageModel
   19.14 -
   19.15 -
   19.16 -class ThreadedEmailViewModel {
   19.17 -
   19.18 -
   19.19 -    internal var messages: [Message]
   19.20 -    internal var tip: Message
   19.21 -    weak var emailDisplayDelegate: EmailDisplayDelegate!
   19.22 -    weak var delegate: EmailViewModelDelegate!
   19.23 -    private let folder: ThreadedFolderWithTop
   19.24 -    private var expandedMessages: [Bool]
   19.25 -    private var messageToReply: Message?
   19.26 -
   19.27 -    //Needed for segue
   19.28 -    public let displayFolder: Folder
   19.29 -
   19.30 -    init(tip: Message, folder: Folder) {
   19.31 -        self.folder = ThreadedFolderWithTop(folder: folder)
   19.32 -        messages = tip.messagesInThread()
   19.33 -        self.tip = tip
   19.34 -        displayFolder = folder
   19.35 -        expandedMessages = Array(repeating: false, count: messages.count)
   19.36 -        expandedMessages.removeLast()
   19.37 -        expandedMessages.append(true)
   19.38 -    }
   19.39 -
   19.40 -    func deleteMessage(at index: Int){
   19.41 -        guard index < messages.count && index >= 0 else {
   19.42 -            return
   19.43 -        }
   19.44 -        emailDisplayDelegate.emailDisplayDidDelete(message: messages[index])
   19.45 -        folder.deleteSingle(message: messages[index])
   19.46 -        messages.remove(at: index)
   19.47 -        expandedMessages.remove(at: index)
   19.48 -        delegate.emailViewModel(viewModel: self, didRemoveDataAt: index)
   19.49 -
   19.50 -    }
   19.51 -
   19.52 -    func deleteAllMessages(){
   19.53 -        folder.deleteThread(message: tip)
   19.54 -        emailDisplayDelegate.emailDisplayDidDelete(message: tip)
   19.55 -    }
   19.56 -
   19.57 -    func addMessage(message: Message) -> Int{
   19.58 -        messages.append(message)
   19.59 -        expandedMessages.append(true)
   19.60 -        return messages.count - 1
   19.61 -    }
   19.62 -
   19.63 -    func updateInternal(message: Message) {
   19.64 -        guard let index = indexOfMessage(message: message) else {
   19.65 -            return
   19.66 -        }
   19.67 -        messages[index] = message
   19.68 -        delegate?.emailViewModel(viewModel: self, didUpdateDataAt: index)
   19.69 -    }
   19.70 -
   19.71 -    fileprivate func notifyFlag(_ status: Bool) {
   19.72 -        if status {
   19.73 -            emailDisplayDelegate.emailDisplayDidFlag(message: tip)
   19.74 -        } else {
   19.75 -            emailDisplayDelegate.emailDisplayDidUnflag(message: tip)
   19.76 -        }
   19.77 -    }
   19.78 -
   19.79 -    //Should only be called from view as it will handle its own update
   19.80 -    func switchFlag(forMessageAt index:Int) {
   19.81 -        guard index < messages.count && index >= 0 else {
   19.82 -            return
   19.83 -        }
   19.84 -
   19.85 -        let flagStatus = (messages[index].imapFlags?.flagged ?? false)
   19.86 -
   19.87 -        messages[index].imapFlags?.flagged = !flagStatus
   19.88 -        if messages[index] == tip {
   19.89 -            notifyFlag(!flagStatus)
   19.90 -        }
   19.91 -    }
   19.92 -
   19.93 -    func setFlag(forMessageAt index: Int, to status: Bool){
   19.94 -        guard index < messages.count && index >= 0 else {
   19.95 -                return
   19.96 -        }
   19.97 -        messages[index].imapFlags?.flagged = status
   19.98 -        messages[index].save()
   19.99 -        updated(message: messages[index])
  19.100 -        if messages[index] == tip {
  19.101 -            notifyFlag(status)
  19.102 -        }
  19.103 -    }
  19.104 -
  19.105 -    func setFlag(to status: Bool){
  19.106 -        for message in messages {
  19.107 -            message.imapFlags?.flagged = status
  19.108 -            message.save()
  19.109 -        }
  19.110 -        notifyFlag(status)
  19.111 -    }
  19.112 -
  19.113 -    func allMessagesFlagged() -> Bool {
  19.114 -        for message in messages {
  19.115 -            if message.imapFlags?.flagged == false {
  19.116 -                return false
  19.117 -            }
  19.118 -        }
  19.119 -        return true
  19.120 -    }
  19.121 -
  19.122 -    func viewModel(for index: Int) -> MessageViewModel? {
  19.123 -        guard index < messages.count && index >= 0 else {
  19.124 -            return nil
  19.125 -        }
  19.126 -        return  MessageViewModel(with: messages[index])
  19.127 -    }
  19.128 -
  19.129 -    func rowCount() -> Int {
  19.130 -        return messages.count
  19.131 -    }
  19.132 -
  19.133 -    internal func message(at index: Int) -> Message? {
  19.134 -        guard index < messages.count && index >= 0 else {
  19.135 -            return nil
  19.136 -        }
  19.137 -        return messages[index]
  19.138 -    }
  19.139 -
  19.140 -    func messageDidExpand(at index: Int){
  19.141 -        expandedMessages[index] = true
  19.142 -    }
  19.143 -
  19.144 -    func messageisExpanded(at index: Int) -> Bool{
  19.145 -        return expandedMessages[index]
  19.146 -    }
  19.147 -
  19.148 -    func replyToMessage(at index: Int){
  19.149 -        guard index < messages.count && index >= 0 else {
  19.150 -            return
  19.151 -        }
  19.152 -        self.messageToReply = messages[index]
  19.153 -    }
  19.154 -
  19.155 -    func getMessageToReply() -> Message? {
  19.156 -        guard let message = messageToReply else {
  19.157 -            return messages.last
  19.158 -        }
  19.159 -        return message
  19.160 -    }
  19.161 -}
    20.1 --- a/pEpForiOS/UI/EmailDisplay/Threading/TreadedEmailViewModel+DisplayedMessage.swift	Tue Jul 03 14:44:28 2018 +0200
    20.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.3 @@ -1,22 +0,0 @@
    20.4 -//
    20.5 -//  TreadedEmailViewModel+DisplayedMessage.swift
    20.6 -//  pEp
    20.7 -//
    20.8 -//  Created by Borja González de Pablo on 22/06/2018.
    20.9 -//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   20.10 -//
   20.11 -
   20.12 -import Foundation
   20.13 -import MessageModel
   20.14 -
   20.15 -extension ThreadedEmailViewModel: DisplayedMessage {
   20.16 -
   20.17 -    var messageModel: Message? {
   20.18 -       return messages.last
   20.19 -    }
   20.20 -
   20.21 -    func update(forMessage message: Message) {
   20.22 -        updateInternal(message: message)
   20.23 -    }
   20.24 -
   20.25 -}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/pEpForiOS/UI/EmailDisplayList/MessageViewModelConfigurable.swift	Wed Jul 04 11:24:51 2018 +0200
    21.3 @@ -0,0 +1,13 @@
    21.4 +//
    21.5 +//  MessageViewModelConfigurable.swift
    21.6 +//  pEp
    21.7 +//
    21.8 +//  Created by Miguel Berrocal Gómez on 18/06/2018.
    21.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   21.10 +//
   21.11 +
   21.12 +import Foundation
   21.13 +
   21.14 +protocol MessageViewModelConfigurable {
   21.15 +    func configure(for viewModel:MessageViewModel)
   21.16 +}
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/pEpForiOS/UI/Thread/Cells/FullMessageCell+SecureWebViewControllerDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    22.3 @@ -0,0 +1,17 @@
    22.4 +//
    22.5 +//  FullMessageCell+SecureWebViewControllerDelegate.swift
    22.6 +//  pEp
    22.7 +//
    22.8 +//  Created by Borja González de Pablo on 22/06/2018.
    22.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   22.10 +//
   22.11 +
   22.12 +import Foundation
   22.13 +
   22.14 +extension FullMessageCell: SecureWebViewControllerDelegate {
   22.15 +    func secureWebViewController(_ webViewController: SecureWebViewController, sizeChangedTo size: CGSize) {
   22.16 +        self.contentHeightConstraint.constant = size.height
   22.17 +        self.contentHeightConstraint.isActive = true
   22.18 +        requestsReload?()
   22.19 +    }
   22.20 +}
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/pEpForiOS/UI/Thread/Cells/FullMessageCell.swift	Wed Jul 04 11:24:51 2018 +0200
    23.3 @@ -0,0 +1,185 @@
    23.4 +//
    23.5 +//  FullMessageCell.swift
    23.6 +//  pEp
    23.7 +//
    23.8 +//  Created by Miguel Berrocal Gómez on 14/06/2018.
    23.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   23.10 +//
   23.11 +
   23.12 +import UIKit
   23.13 +import MessageModel
   23.14 +import SwipeCellKit
   23.15 +
   23.16 +class FullMessageCell: SwipeTableViewCell,
   23.17 +    MessageViewModelConfigurable,
   23.18 +    NeedsRefreshDelegate {
   23.19 +
   23.20 +    static var flaggedImage: UIImage? = nil
   23.21 +
   23.22 +    var requestsReload: (() -> Void)?
   23.23 +
   23.24 +    @IBOutlet weak var contentHeightConstraint: NSLayoutConstraint!
   23.25 +    @IBOutlet weak var roundedView: UIView!
   23.26 +    @IBOutlet weak var addressLabel: UILabel!
   23.27 +    @IBOutlet weak var summaryLabel: UILabel!
   23.28 +    @IBOutlet weak var dateLabel: UILabel!
   23.29 +    @IBOutlet weak var subjectLabel: UILabel!
   23.30 +    @IBOutlet weak var bodyText: UITextView!
   23.31 +    @IBOutlet weak var body: UIView!
   23.32 +    @IBOutlet weak var stackView: UIStackView!
   23.33 +    @IBOutlet weak var profilePicture: UIImageView!
   23.34 +    @IBOutlet weak var badgePicture: UIImageView!
   23.35 +    @IBOutlet weak var attachmentIcon: UIImageView!
   23.36 +    @IBOutlet weak var flaggedIcon: UIImageView!
   23.37 +
   23.38 +    var tableView: UITableView!
   23.39 +
   23.40 +    var isFlagged:Bool = false {
   23.41 +        didSet {
   23.42 +            if isFlagged {
   23.43 +                setFlagged()
   23.44 +            } else {
   23.45 +                unsetFlagged()
   23.46 +            }
   23.47 +        }
   23.48 +    }
   23.49 +
   23.50 +
   23.51 +    @IBOutlet weak var view: UIView!
   23.52 +    func configure(for viewModel:MessageViewModel) {
   23.53 +        isFlagged = viewModel.isFlagged
   23.54 +        addressLabel.text = viewModel.from
   23.55 +        subjectLabel.text = viewModel.subject
   23.56 +        backgroundColor = UIColor.clear
   23.57 +        dateLabel.text = viewModel.dateText
   23.58 +        viewModel.getProfilePicture { image in
   23.59 +            self.profilePicture.image = image
   23.60 +        }
   23.61 +        viewModel.getSecurityBadge { image in
   23.62 +            self.badgePicture.image = image
   23.63 +        }
   23.64 +        if let htmlBody = htmlBody(message: viewModel.message) {
   23.65 +            // Its fine to use a webview (iOS>=11) and we do have HTML content.
   23.66 +            bodyText.isHidden = true
   23.67 +            view.addSubview(htmlViewerViewController.view)
   23.68 +            view.isUserInteractionEnabled = false
   23.69 +
   23.70 +            htmlViewerViewController.view.fullSizeInSuperView()
   23.71 +
   23.72 +            let displayHtml = appendInlinedPlainText(fromAttachmentsIn: viewModel.message, to: htmlBody)
   23.73 +            htmlViewerViewController.display(htmlString: displayHtml)
   23.74 +        } else {
   23.75 +            bodyText.attributedText = viewModel.body
   23.76 +            bodyText.isHidden = false
   23.77 +            bodyText.tintColor = UIColor.pEpGreen
   23.78 +            // We are not allowed to use a webview (iOS<11) or do not have HTML content.
   23.79 +            // Remove the HTML view if we just stepped from an HTML mail to one without
   23.80 +            if htmlViewerViewControllerExists &&
   23.81 +                htmlViewerViewController.view.superview == self.contentView {
   23.82 +                htmlViewerViewController.view.removeFromSuperview()
   23.83 +            }
   23.84 +        }
   23.85 +
   23.86 +    }
   23.87 +
   23.88 +    override func awakeFromNib() {
   23.89 +        super.awakeFromNib()
   23.90 +        // Initialization code
   23.91 +    }
   23.92 +
   23.93 +    override func setSelected(_ selected: Bool, animated: Bool) {
   23.94 +        super.setSelected(selected, animated: animated)
   23.95 +
   23.96 +        // Configure the view for the selected state
   23.97 +    }
   23.98 +
   23.99 +    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
  23.100 +        super.setHighlighted(highlighted, animated: animated)
  23.101 +        
  23.102 +    }
  23.103 +
  23.104 +    /**
  23.105 +     Indicate that the htmlViewerViewController already exists, to avoid
  23.106 +     instantiation just to check if it has been instantiated.
  23.107 +     */
  23.108 +    var htmlViewerViewControllerExists = false
  23.109 +
  23.110 +    lazy private var htmlViewerViewController: SecureWebViewController = {
  23.111 +        let storyboard = UIStoryboard(name: "Reusable", bundle: nil)
  23.112 +        guard let vc =
  23.113 +            storyboard.instantiateViewController(withIdentifier: SecureWebViewController.storyboardId)
  23.114 +                as? SecureWebViewController
  23.115 +            else {
  23.116 +                Log.shared.errorAndCrash(component: #function, errorString: "Cast error")
  23.117 +                return SecureWebViewController()
  23.118 +        }
  23.119 +        vc.scrollingEnabled = false
  23.120 +        vc.delegate = self
  23.121 +
  23.122 +        htmlViewerViewControllerExists = true
  23.123 +
  23.124 +        return vc
  23.125 +    }()
  23.126 +
  23.127 +    /**
  23.128 +     Yields the HTML message body if:
  23.129 +     * we can show it in a secure way
  23.130 +     * we have non-empty HTML content at all
  23.131 +     - Returns: The HTML message body or nil
  23.132 +     */
  23.133 +    private func htmlBody(message: Message?) ->  String? {
  23.134 +        guard
  23.135 +            SecureWebViewController.isSaveToUseWebView,
  23.136 +            let m = message,
  23.137 +            let htmlBody = m.longMessageFormatted,
  23.138 +            !htmlBody.isEmpty else {
  23.139 +                return nil
  23.140 +        }
  23.141 +
  23.142 +        return htmlBody
  23.143 +    }
  23.144 +
  23.145 +    private func appendInlinedPlainText(fromAttachmentsIn message: Message, to text: String) -> String {
  23.146 +        var result = text
  23.147 +        let inlinedText = message.inlinedTextAttachments()
  23.148 +        for inlinedTextAttachment in inlinedText {
  23.149 +            guard
  23.150 +                let data = inlinedTextAttachment.data,
  23.151 +                let inlinedText = String(data: data, encoding: .utf8) else {
  23.152 +                    continue
  23.153 +            }
  23.154 +            result = append(appendText: inlinedText, to: result)
  23.155 +        }
  23.156 +        return result
  23.157 +    }
  23.158 +
  23.159 +    private func append(appendText: String, to body: String) -> String {
  23.160 +        var result = body
  23.161 +        let replacee = result.contains(find: "</body>") ? "</body>" : "</html>"
  23.162 +        if result.contains(find: replacee) {
  23.163 +            result = result.replacingOccurrences(of: replacee, with: appendText + replacee)
  23.164 +        } else {
  23.165 +            result += "\n" + appendText
  23.166 +        }
  23.167 +        return result
  23.168 +    }
  23.169 +
  23.170 +    private func setFlagged() {
  23.171 +        if FullMessageCell.flaggedImage == nil {
  23.172 +            FullMessageCell.flaggedImage =
  23.173 +                FlagImages.create(imageSize: flaggedIcon.frame.size).flaggedImage
  23.174 +        }
  23.175 +        guard let saveImg = FullMessageCell.flaggedImage else {
  23.176 +            return
  23.177 +        }
  23.178 +        self.flaggedIcon.isHidden = false
  23.179 +        self.flaggedIcon.image = saveImg
  23.180 +    }
  23.181 +
  23.182 +    private func unsetFlagged() {
  23.183 +        self.flaggedIcon.isHidden = true
  23.184 +        self.flaggedIcon.image = nil
  23.185 +    }
  23.186 +
  23.187 +
  23.188 +}
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/pEpForiOS/UI/Thread/ThreadViewController+EmailViewDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    24.3 @@ -0,0 +1,42 @@
    24.4 +//
    24.5 +//  ThreadViewController+EmailViewControllerDelegate.swift
    24.6 +//  pEp
    24.7 +//
    24.8 +//  Created by Borja González de Pablo on 18/06/2018.
    24.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   24.10 +//
   24.11 +
   24.12 +import Foundation
   24.13 +extension ThreadViewController: ThreadedEmailViewModelDelegate {
   24.14 +
   24.15 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didInsertDataAt index: Int) {
   24.16 +        updateTableView {
   24.17 +            tableView.insertSections(IndexSet([index]), with: .automatic)
   24.18 +        }
   24.19 +    }
   24.20 +
   24.21 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didUpdateDataAt index: Int) {
   24.22 +        updateTableView {
   24.23 +            tableView.reloadSections(IndexSet([index]), with: .none)
   24.24 +        }
   24.25 +    }
   24.26 +
   24.27 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didRemoveDataAt index: Int) {
   24.28 +        updateTableView {
   24.29 +            tableView.deleteSections(IndexSet([index]), with: .none)
   24.30 +        }
   24.31 +    }
   24.32 +
   24.33 +    func updateView() {
   24.34 +        tableView.dataSource = self
   24.35 +        tableView.reloadData()
   24.36 +    }
   24.37 +
   24.38 +    private func updateTableView(updates: ()->()) {
   24.39 +        tableView.beginUpdates()
   24.40 +        updates()
   24.41 +        tableView.endUpdates()
   24.42 +    }
   24.43 +    
   24.44 +
   24.45 +}
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/pEpForiOS/UI/Thread/ThreadViewController+SegueHandlerType.swift	Wed Jul 04 11:24:51 2018 +0200
    25.3 @@ -0,0 +1,79 @@
    25.4 +//
    25.5 +//  ThreadViewController+SegueHandlerType.swift
    25.6 +//  pEp
    25.7 +//
    25.8 +//  Created by Miguel Berrocal Gómez on 15/06/2018.
    25.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   25.10 +//
   25.11 +
   25.12 +import Foundation
   25.13 +extension ThreadViewController: SegueHandlerType {
   25.14 +
   25.15 +    enum SegueIdentifier: String {
   25.16 +        case segueShowMoveToFolder
   25.17 +        case segueReplyFrom
   25.18 +        case segueReplyAllForm
   25.19 +        case segueForward
   25.20 +        case segueShowEmail
   25.21 +        case segueShowEmailExpanding
   25.22 +    }
   25.23 +
   25.24 +    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   25.25 +        let segueId = segueIdentifier(for: segue)
   25.26 +        switch segueId {
   25.27 +        case .segueShowEmail, .segueShowEmailExpanding:
   25.28 +//            guard let nav = segue.destination as? UINavigationController,
   25.29 +                guard let vc = segue.destination as? EmailViewController,
   25.30 +                let appConfig = self.appConfig,
   25.31 +                let indexPath = tableView.indexPathForSelectedRow,
   25.32 +                let message = model?.message(at: indexPath.section) else {
   25.33 +                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   25.34 +                    return
   25.35 +            }
   25.36 +            vc.appConfig = appConfig
   25.37 +            vc.shouldShowOKButton = !isSplitViewControllerCollapsed()
   25.38 +            vc.message = message
   25.39 +            vc.folderShow = model?.displayFolder
   25.40 +            vc.messageId = indexPath.row
   25.41 +            break
   25.42 +        case .segueShowMoveToFolder:
   25.43 +            guard  let nav = segue.destination as? UINavigationController,
   25.44 +                let destination = nav.topViewController as? MoveToAccountViewController else {
   25.45 +                    Log.shared.errorAndCrash(component: #function, errorString: "No DVC?")
   25.46 +                    break
   25.47 +            }
   25.48 +            destination.appConfig = appConfig
   25.49 +            if let messages = model?.messages {
   25.50 +                destination.viewModel = MoveToAccountViewModel(messages: messages)
   25.51 +            }
   25.52 +            destination.delegate = model
   25.53 +            break
   25.54 +        case .segueReplyFrom, .segueReplyAllForm, .segueForward:
   25.55 +            guard  let nav = segue.destination as? UINavigationController,
   25.56 +                let destination = nav.topViewController as? ComposeTableViewController,
   25.57 +                let appConfig = appConfig else {
   25.58 +                    Log.shared.errorAndCrash(component: #function, errorString: "No DVC?")
   25.59 +                    break
   25.60 +            }
   25.61 +
   25.62 +
   25.63 +            destination.appConfig = appConfig
   25.64 +
   25.65 +            if segueId == .segueReplyFrom {
   25.66 +                destination.composeMode = .replyFrom
   25.67 +                destination.originalMessage = model.getMessageToReply()
   25.68 +            } else if segueId == .segueReplyAllForm {
   25.69 +                destination.composeMode = .replyAll
   25.70 +                destination.originalMessage =  model.getMessageToReply()
   25.71 +            } else if segueId == .segueForward {
   25.72 +                destination.composeMode = .forward
   25.73 +                destination.originalMessage =  model.getMessageToReply()
   25.74 +            }
   25.75 +            break
   25.76 +        default:
   25.77 +            Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
   25.78 +            break
   25.79 +        }
   25.80 +    }
   25.81 +
   25.82 +}
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/pEpForiOS/UI/Thread/ThreadViewController+SwipeCell.swift	Wed Jul 04 11:24:51 2018 +0200
    26.3 @@ -0,0 +1,92 @@
    26.4 +//
    26.5 +//  ThreadViewController+SwipeCell.swift
    26.6 +//  pEp
    26.7 +//
    26.8 +//  Created by Miguel Berrocal Gómez on 21/06/2018.
    26.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   26.10 +//
   26.11 +
   26.12 +import Foundation
   26.13 +import SwipeCellKit
   26.14 +
   26.15 +extension ThreadViewController: SwipeTableViewCellDelegate {
   26.16 +
   26.17 +    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
   26.18 +        var swipeActions = [SwipeAction]()
   26.19 +
   26.20 +        // Delete or Archive
   26.21 +        swipeActions.append(deleteAction())
   26.22 +
   26.23 +        //Get from model
   26.24 +        let draftFolder = false
   26.25 +        if !draftFolder {
   26.26 +            swipeActions.append(flagAction())
   26.27 +            swipeActions.append(replyAction())
   26.28 +        }
   26.29 +
   26.30 +        return (orientation == .right ?   swipeActions : nil)
   26.31 +    }
   26.32 +
   26.33 +    func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
   26.34 +        var options = SwipeOptions()
   26.35 +        options.expansionStyle = .destructive(automaticallyDelete: false)
   26.36 +        options.buttonSpacing = 4
   26.37 +        options.transitionStyle = .border
   26.38 +        options.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0)
   26.39 +
   26.40 +        return options
   26.41 +    }
   26.42 +
   26.43 +    private func configure(action: SwipeAction, with descriptor: SwipeActionDescriptor) {
   26.44 +        let buttonStyle: ButtonStyle = .circular
   26.45 +        let buttonDisplayMode: ButtonDisplayMode = .imageOnly
   26.46 +        action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
   26.47 +        action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
   26.48 +        action.backgroundColor = .clear
   26.49 +        action.textColor = descriptor.color
   26.50 +        action.font = .systemFont(ofSize: 13)
   26.51 +        action.transitionDelegate = ScaleTransition.default
   26.52 +    }
   26.53 +
   26.54 +    private func deleteAction() -> SwipeAction {
   26.55 +        let defaultIsArchive = false
   26.56 +        let titleDestructive = defaultIsArchive ? "Archive" : "Delete"
   26.57 +        let descriptorDestructive: SwipeActionDescriptor = defaultIsArchive ? .archive : .trash
   26.58 +        let archiveAction =
   26.59 +            SwipeAction(style: .destructive, title: titleDestructive) {action, indexPath in
   26.60 +                self.tableView.beginUpdates()
   26.61 +                self.model?.deleteMessage(at: indexPath.section)
   26.62 +                action.fulfill(with: .delete)
   26.63 +                self.tableView.endUpdates()
   26.64 +        }
   26.65 +        configure(action: archiveAction, with: descriptorDestructive)
   26.66 +        return archiveAction
   26.67 +    }
   26.68 +
   26.69 +    private func flagAction() -> SwipeAction {
   26.70 +        // Do not add "Flag" action to drafted mails.
   26.71 +        let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
   26.72 +            self.model.switchFlag(forMessageAt: indexPath.section)
   26.73 +            guard let cell = self.tableView.cellForRow(at: indexPath) as? FullMessageCell else {
   26.74 +                return
   26.75 +            }
   26.76 +            cell.isFlagged = !cell.isFlagged
   26.77 +        }
   26.78 +        flagAction.hidesWhenSelected = true
   26.79 +        configure(action: flagAction, with: .flag)
   26.80 +        return flagAction
   26.81 +    }
   26.82 +
   26.83 +    private func replyAction() -> SwipeAction {
   26.84 +        // Do not add reply action to drafted mails.
   26.85 +        let moreAction = SwipeAction(style: .default, title: "Reply") { action, indexPath in
   26.86 +            self.model.replyToMessage(at: indexPath.section)
   26.87 +            self.performSegue(withIdentifier: .segueReplyAllForm , sender: self)
   26.88 +        }
   26.89 +        moreAction.hidesWhenSelected = true
   26.90 +        configure(action: moreAction, with: .reply)
   26.91 +        return moreAction
   26.92 +    }
   26.93 +
   26.94 +
   26.95 +}
    27.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.2 +++ b/pEpForiOS/UI/Thread/ThreadViewController+TableView.swift	Wed Jul 04 11:24:51 2018 +0200
    27.3 @@ -0,0 +1,89 @@
    27.4 +//
    27.5 +//  ThreadViewController+TableView.swift
    27.6 +//  pEp
    27.7 +//
    27.8 +//  Created by Miguel Berrocal Gómez on 05/06/2018.
    27.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   27.10 +//
   27.11 +
   27.12 +import Foundation
   27.13 +import SwipeCellKit
   27.14 +import UIKit
   27.15 +
   27.16 +extension ThreadViewController: UITableViewDelegate, UITableViewDataSource {
   27.17 +
   27.18 +
   27.19 +    func numberOfSections(in tableView: UITableView) -> Int {
   27.20 +        return model?.rowCount() ?? 0
   27.21 +    }
   27.22 +
   27.23 +    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   27.24 +        return 1
   27.25 +    }
   27.26 +
   27.27 +    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
   27.28 +        if model?.messageisExpanded(at: indexPath.section) == true {
   27.29 +            return UITableViewAutomaticDimension
   27.30 +        }
   27.31 +        else {
   27.32 +            return 100
   27.33 +        }
   27.34 +    }
   27.35 +
   27.36 +    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   27.37 +        if model?.messageisExpanded(at: indexPath.section) == true {
   27.38 +            return configureCell(identifier: "expandedCell", at: indexPath)
   27.39 +        }
   27.40 +        else {
   27.41 +            return configureCell(identifier: "unexpandedCell", at: indexPath)
   27.42 +        }
   27.43 +    }
   27.44 +
   27.45 +    func configureCell(identifier:String, at indexPath:IndexPath) -> UITableViewCell {
   27.46 +        guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier)
   27.47 +                as? UITableViewCell & MessageViewModelConfigurable,
   27.48 +            let viewModel = model?.viewModel(for: indexPath.section)  else {
   27.49 +                return UITableViewCell()
   27.50 +        }
   27.51 +        if var refreshableCell = cell as? NeedsRefreshDelegate {
   27.52 +            refreshableCell.requestsReload = { self.tableView.updateSize() }
   27.53 +        }
   27.54 +
   27.55 +        cell.configure(for: viewModel)
   27.56 +        configureSwipeCell(cell: cell)
   27.57 +        return cell
   27.58 +    }
   27.59 +
   27.60 +    func configureSwipeCell(cell: UITableViewCell) {
   27.61 +        guard let cell = cell as? SwipeTableViewCell else {
   27.62 +            return
   27.63 +        }
   27.64 +
   27.65 +        cell.delegate = self
   27.66 +    }
   27.67 +
   27.68 +
   27.69 +    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   27.70 +        if model?.messageisExpanded(at: indexPath.section) == false {
   27.71 +            model?.messageDidExpand(at: indexPath.section)
   27.72 +            let indexSet = IndexSet(integer: indexPath.section)
   27.73 +            tableView.reloadSections(indexSet, with: .automatic)
   27.74 +        }
   27.75 +        else {
   27.76 +            showEmail()
   27.77 +        }
   27.78 +    }
   27.79 +
   27.80 +    private func showEmail() {
   27.81 +//        guard let splitViewController = self.splitViewController else {
   27.82 +//            Log.shared.errorAndCrash(component: #function,
   27.83 +//                                     errorString: "We must have a splitViewController here")
   27.84 +//            return
   27.85 +//        }
   27.86 +//        if splitViewController.isCollapsed {
   27.87 +            performSegue(withIdentifier: .segueShowEmail, sender: self)
   27.88 +//        } else {
   27.89 +//            performSegue(withIdentifier: .SegueShowEmailExpanding, sender: self)
   27.90 +//        }
   27.91 +    }
   27.92 +}
    28.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.2 +++ b/pEpForiOS/UI/Thread/ThreadViewController.swift	Wed Jul 04 11:24:51 2018 +0200
    28.3 @@ -0,0 +1,100 @@
    28.4 +//
    28.5 +//  ThreadViewController.swift
    28.6 +//  pEp
    28.7 +//
    28.8 +//  Created by Miguel Berrocal Gómez on 05/06/2018.
    28.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   28.10 +//
   28.11 +
   28.12 +import UIKit
   28.13 +
   28.14 +class ThreadViewController: BaseViewController {
   28.15 +    var barItems: [UIBarButtonItem]?
   28.16 +
   28.17 +    @IBOutlet weak var flagButton: UIBarButtonItem!
   28.18 +    @IBOutlet weak var tableView: UITableView!
   28.19 +    var model: ThreadedEmailViewModel!
   28.20 +
   28.21 +    var messages = ["hola", "que", "tal"]
   28.22 +    override func viewDidLoad() {
   28.23 +        super.viewDidLoad()
   28.24 +        configureSplitViewBackButton()
   28.25 +        guard let model = model else {
   28.26 +            return
   28.27 +        }
   28.28 +        model.delegate = self
   28.29 +        self.navigationItem.title = String(model.rowCount())  + " messages"
   28.30 +        setUpFlaggedStatus()
   28.31 +    }
   28.32 +
   28.33 +    override func didReceiveMemoryWarning() {
   28.34 +        super.didReceiveMemoryWarning()
   28.35 +        // Dispose of any resources that can be recreated.
   28.36 +    }
   28.37 +
   28.38 +    @IBAction func segueUnwindEmailDisplayDone(segue:UIStoryboardSegue) {
   28.39 +        //do nothing
   28.40 +    }
   28.41 +
   28.42 +    func setUpFlaggedStatus(){
   28.43 +        let allFlagged =  model?.allMessagesFlagged() ?? false
   28.44 +
   28.45 +        if allFlagged {
   28.46 +            flagButton.image = UIImage(named: "icon-flagged")
   28.47 +        } else {
   28.48 +            flagButton.image = UIImage(named: "icon-unflagged")
   28.49 +        }
   28.50 +    }
   28.51 +    
   28.52 +    private func configureSplitViewBackButton() {
   28.53 +        self.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
   28.54 +        self.navigationItem.leftItemsSupplementBackButton = true
   28.55 +    }
   28.56 +
   28.57 +    internal func isSplitViewControllerCollapsed() -> Bool! {
   28.58 +        guard let splitViewController = self.splitViewController else {
   28.59 +            Log.shared.errorAndCrash(component: #function, errorString: "We need a splitViewController here")
   28.60 +            return nil
   28.61 +        }
   28.62 +        return splitViewController.isCollapsed
   28.63 +    }
   28.64 +
   28.65 +    // MARK: Actions
   28.66 +    
   28.67 +    @IBAction func flagButtonTapped(_ sender: Any) {
   28.68 +        guard let model = model else {
   28.69 +            return
   28.70 +        }
   28.71 +        model.setFlag(to: !model.allMessagesFlagged())
   28.72 +        setUpFlaggedStatus()
   28.73 +        tableView.reloadData()
   28.74 +    }
   28.75 +
   28.76 +    @IBAction func moveToFolderTapped(_ sender: Any) {
   28.77 +        performSegue(withIdentifier: .segueShowMoveToFolder, sender: self)
   28.78 +    }
   28.79 +
   28.80 +    @IBAction func destructiveButtonTapped(_ sender: Any) {
   28.81 +        model?.deleteAllMessages()
   28.82 +    }
   28.83 +    
   28.84 +    @IBAction func replyButtonTapped(_ sender: UIBarButtonItem) {
   28.85 +
   28.86 +        let alert = ReplyAlertCreator()
   28.87 +            .withReplyOption { action in
   28.88 +                self.performSegue(withIdentifier: .segueReplyFrom , sender: self)
   28.89 +            }.withReplyAllOption { action in
   28.90 +                self.performSegue(withIdentifier: .segueReplyAllForm , sender: self)
   28.91 +            }.withFordwardOption { action in
   28.92 +                 self.performSegue(withIdentifier: .segueForward , sender: self)
   28.93 +            }.withCancelOption()
   28.94 +            .build()
   28.95 +
   28.96 +        if let popoverPresentationController = alert.popoverPresentationController {
   28.97 +            popoverPresentationController.barButtonItem = sender
   28.98 +        }
   28.99 +
  28.100 +        present(alert, animated: true, completion: nil)
  28.101 +    }
  28.102 +
  28.103 +}
    29.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.2 +++ b/pEpForiOS/UI/Thread/ThreadViewcontroller+SizeClasses.swift	Wed Jul 04 11:24:51 2018 +0200
    29.3 @@ -0,0 +1,31 @@
    29.4 +//
    29.5 +//  ThreadedViewcontroller+SizeClasses.swift
    29.6 +//  pEp
    29.7 +//
    29.8 +//  Created by Borja González de Pablo on 27/06/2018.
    29.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   29.10 +//
   29.11 +
   29.12 +import Foundation
   29.13 +
   29.14 +extension ThreadViewController {
   29.15 +    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
   29.16 +        super.traitCollectionDidChange(previousTraitCollection)
   29.17 +
   29.18 +        if (traitCollection.horizontalSizeClass == .regular &&
   29.19 +            traitCollection.verticalSizeClass == .regular) {
   29.20 +            adaptBarButtonItemsForRegularSize()
   29.21 +        }
   29.22 +    }
   29.23 +    private func adaptBarButtonItemsForRegularSize() {
   29.24 +        guard let items = toolbarItems else {
   29.25 +            return
   29.26 +        }
   29.27 +
   29.28 +        barItems = items
   29.29 +
   29.30 +        navigationItem.rightBarButtonItems = items
   29.31 +        self.navigationController?.setToolbarHidden(true, animated: true)
   29.32 +        toolbarItems = nil
   29.33 +    }
   29.34 +}
    30.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.2 +++ b/pEpForiOS/UI/Thread/Transitions/CellDetailTransition.swift	Wed Jul 04 11:24:51 2018 +0200
    30.3 @@ -0,0 +1,113 @@
    30.4 +//
    30.5 +//  CellDetailTransition.swift
    30.6 +//  pEp
    30.7 +//
    30.8 +//  Created by Miguel Berrocal Gómez on 28/06/2018.
    30.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   30.10 +//
   30.11 +
   30.12 +import Foundation
   30.13 +
   30.14 +class CellDetailTransition: NSObject, UIViewControllerAnimatedTransitioning {
   30.15 +
   30.16 +    let duration: TimeInterval
   30.17 +    let isDismissing: Bool
   30.18 +
   30.19 +    init(duration: TimeInterval, isDismissing: Bool = false) {
   30.20 +        self.duration = duration
   30.21 +        self.isDismissing = isDismissing
   30.22 +    }
   30.23 +
   30.24 +
   30.25 +    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
   30.26 +        return duration
   30.27 +    }
   30.28 +
   30.29 +    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
   30.30 +        if !isDismissing {
   30.31 +            animatePush(using: transitionContext)
   30.32 +        } else {
   30.33 +            animatePop(using: transitionContext)
   30.34 +        }
   30.35 +
   30.36 +    }
   30.37 +
   30.38 +    private func animatePush(using transitionContext: UIViewControllerContextTransitioning) {
   30.39 +        let containerView = transitionContext.containerView
   30.40 +
   30.41 +        guard let fromView = transitionContext.view(forKey: .from),
   30.42 +            let toView = transitionContext.view(forKey: .to),
   30.43 +            let fromViewController = transitionContext.viewController(forKey: .from),
   30.44 +            let tableView = (fromViewController as? ThreadViewController)?.tableView ,
   30.45 +            let selectedIndexPath = tableView.indexPathForSelectedRow,
   30.46 +            let cell = tableView.cellForRow(at: selectedIndexPath),
   30.47 +            let fullCell = cell as? FullMessageCell else {
   30.48 +                return
   30.49 +        }
   30.50 +
   30.51 +        tableView.bringSubview(toFront: cell)
   30.52 +
   30.53 +        let fromCellView:UIView = fullCell.roundedView
   30.54 +        let originalFrame = fromCellView.frame
   30.55 +
   30.56 +        var fromFrame: CGRect!
   30.57 +        let toFrame = toView.frame
   30.58 +        fromFrame = containerView.convert(fromCellView.frame, from: cell)
   30.59 +        toView.frame = fromFrame
   30.60 +        toView.alpha = 0
   30.61 +
   30.62 +        containerView.addSubview(toView)
   30.63 +
   30.64 +        UIView.animate(withDuration: 0.4, animations: {
   30.65 +            fromCellView.frame = containerView.convert(toFrame, to: fromCellView)
   30.66 +            fromCellView.frame.origin.x = 0
   30.67 +            toView.frame = toFrame
   30.68 +            toView.alpha = 1
   30.69 +            fromView.alpha = 0
   30.70 +        }) { (completed) in
   30.71 +            fromCellView.frame = originalFrame
   30.72 +            fromView.alpha = 1
   30.73 +            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
   30.74 +        }
   30.75 +    }
   30.76 +
   30.77 +
   30.78 +    private func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
   30.79 +        let containerView = transitionContext.containerView
   30.80 +
   30.81 +        guard let fromView = transitionContext.view(forKey: .from),
   30.82 +            let toView = transitionContext.view(forKey: .to),
   30.83 +            let toViewController = transitionContext.viewController(forKey: .to),
   30.84 +            let tableView = (toViewController as? ThreadViewController)?.tableView,
   30.85 +            let selectedIndexPath = tableView.indexPathForSelectedRow,
   30.86 +            let cell = tableView.cellForRow(at: selectedIndexPath),
   30.87 +            let fullCell = cell as? FullMessageCell else {
   30.88 +                return
   30.89 +        }
   30.90 +
   30.91 +        tableView.bringSubview(toFront: cell)
   30.92 +
   30.93 +        let toCellView:UIView = fullCell.roundedView
   30.94 +        let originalFrame = toCellView.frame
   30.95 +
   30.96 +        var toFrame: CGRect!
   30.97 +
   30.98 +        toCellView.alpha = 0
   30.99 +        toView.alpha = 0
  30.100 +
  30.101 +        containerView.insertSubview(toView, belowSubview: fromView)
  30.102 +        toFrame = containerView.convert(toCellView.frame, from: cell)
  30.103 +        toCellView.frame = containerView.convert(fromView.frame, to: toCellView)
  30.104 +        toCellView.frame.origin.x = 0
  30.105 +
  30.106 +        UIView.animate(withDuration: 0.4, animations: {
  30.107 +            fromView.frame = toFrame
  30.108 +            toCellView.frame = originalFrame
  30.109 +            fromView.alpha = 0
  30.110 +            toCellView.alpha = 1
  30.111 +            toView.alpha = 1
  30.112 +        }) { (completed) in
  30.113 +            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
  30.114 +        }
  30.115 +    }
  30.116 +}
    31.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.2 +++ b/pEpForiOS/UI/Thread/Transitions/DetailCellSegue.swift	Wed Jul 04 11:24:51 2018 +0200
    31.3 @@ -0,0 +1,45 @@
    31.4 +//
    31.5 +//  DetailCellSegue.swift
    31.6 +//  pEp
    31.7 +//
    31.8 +//  Created by Miguel Berrocal Gómez on 15/06/2018.
    31.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   31.10 +//
   31.11 +
   31.12 +import UIKit
   31.13 +
   31.14 +class DetailCellSegue: UIStoryboardSegue {
   31.15 +
   31.16 +    override func perform() {
   31.17 +        guard let threadViewController = source as? ThreadViewController,
   31.18 +        let navigationController = destination as? UINavigationController,
   31.19 +        let destinationVC = navigationController.rootViewController,
   31.20 +        let destinationView = destinationVC.view.snapshotView(afterScreenUpdates: true),
   31.21 +        let tableView = threadViewController.tableView,
   31.22 +        let selectedIndexPath = tableView.indexPathForSelectedRow,
   31.23 +        let cell = tableView.cellForRow(at: selectedIndexPath)
   31.24 +        else {
   31.25 +            return
   31.26 +        }
   31.27 +
   31.28 +
   31.29 +        // create an NSData object from myView
   31.30 +        let archive = NSKeyedArchiver.archivedData(withRootObject: cell.contentView)
   31.31 +
   31.32 +        // create a clone by unarchiving the NSData
   31.33 +        let view = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIView
   31.34 +
   31.35 +        let originalFrame = destinationView.frame
   31.36 +
   31.37 +        view.frame = threadViewController.view.convert(cell.frame, from: tableView)
   31.38 +        threadViewController.view.insertSubview(view, aboveSubview: tableView)
   31.39 +
   31.40 +        UIView.animate(withDuration: 0.4, animations: {
   31.41 +            view.frame = originalFrame
   31.42 +
   31.43 +        }) { (completed) in
   31.44 +            threadViewController.showDetailViewController(self.destination, sender: threadViewController)
   31.45 +            view.removeFromSuperview()
   31.46 +        }
   31.47 +    }
   31.48 +}
    32.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.2 +++ b/pEpForiOS/UI/Thread/Util/NeedsRefreshDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    32.3 @@ -0,0 +1,13 @@
    32.4 +//
    32.5 +//  NeedsRefreshDelegate.swift
    32.6 +//  pEp
    32.7 +//
    32.8 +//  Created by Borja González de Pablo on 21/06/2018.
    32.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   32.10 +//
   32.11 +
   32.12 +import Foundation
   32.13 +
   32.14 +protocol NeedsRefreshDelegate {
   32.15 +    var requestsReload: (() -> Void)?  {get set}
   32.16 +}
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/pEpForiOS/UI/Thread/Util/ReplyAlertCreator.swift	Wed Jul 04 11:24:51 2018 +0200
    33.3 @@ -0,0 +1,52 @@
    33.4 +//
    33.5 +//  ReplyAlertCreator.swift
    33.6 +//  pEp
    33.7 +//
    33.8 +//  Created by Borja González de Pablo on 27/06/2018.
    33.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   33.10 +//
   33.11 +
   33.12 +import Foundation
   33.13 +class ReplyAlertCreator {
   33.14 +
   33.15 +    public let alert: UIAlertController
   33.16 +    public init(){
   33.17 +        alert = UIAlertController.pEpAlertController()
   33.18 +    }
   33.19 +
   33.20 +    public func withReplyOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
   33.21 +        let alertActionReply = UIAlertAction(
   33.22 +            title: NSLocalizedString("Reply", comment: "Message actions"),
   33.23 +            style: .default, handler: handler)
   33.24 +        alert.addAction(alertActionReply)
   33.25 +        return self
   33.26 +    }
   33.27 +
   33.28 +    public func withReplyAllOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
   33.29 +        let alertActionReplyAll = UIAlertAction(
   33.30 +            title: NSLocalizedString("Reply All", comment: "Message actions"),
   33.31 +            style: .default, handler: handler)
   33.32 +        alert.addAction(alertActionReplyAll)
   33.33 +        return self
   33.34 +    }
   33.35 +
   33.36 +    public func withFordwardOption(handler: @escaping(UIAlertAction) -> Swift.Void)-> ReplyAlertCreator {
   33.37 +        let alertActionForward = UIAlertAction(
   33.38 +            title: NSLocalizedString("Forward", comment: "Message actions"),
   33.39 +            style: .default, handler: handler)
   33.40 +        alert.addAction(alertActionForward)
   33.41 +        return self
   33.42 +    }
   33.43 +
   33.44 +
   33.45 +    public func withCancelOption() -> ReplyAlertCreator {
   33.46 +        let cancelAction = UIAlertAction(
   33.47 +            title: NSLocalizedString("Cancel", comment: "Message actions"),
   33.48 +            style: .cancel) { (action) in }
   33.49 +        alert.addAction(cancelAction)
   33.50 +        return self
   33.51 +    }
   33.52 +    public func build()-> UIAlertController{
   33.53 +        return alert
   33.54 +    }
   33.55 +}
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/pEpForiOS/UI/Thread/Util/ThreadNavigationDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    34.3 @@ -0,0 +1,32 @@
    34.4 +//
    34.5 +//  ThreadNaviationDelegate
    34.6 +//  pEp
    34.7 +//
    34.8 +//  Created by Miguel Berrocal Gómez on 28/06/2018.
    34.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   34.10 +//
   34.11 +
   34.12 +class ThreadNavigationDelegate: NSObject,
   34.13 +UINavigationControllerDelegate {
   34.14 +
   34.15 +    func navigationController(
   34.16 +        _ navigationController: UINavigationController,
   34.17 +        animationControllerFor operation:
   34.18 +        UINavigationControllerOperation,
   34.19 +        from fromVC: UIViewController,
   34.20 +        to toVC: UIViewController
   34.21 +        ) -> UIViewControllerAnimatedTransitioning? {
   34.22 +
   34.23 +        guard let splitView = navigationController.splitViewController,
   34.24 +        !splitView.isCollapsed else {
   34.25 +            return nil
   34.26 +        }
   34.27 +        if toVC is EmailViewController {
   34.28 +            return CellDetailTransition(duration: 0.5)
   34.29 +        }
   34.30 +        if fromVC is EmailViewController && operation == .pop {
   34.31 +            return CellDetailTransition(duration: 0.5, isDismissing: true)
   34.32 +        }
   34.33 +        return nil
   34.34 +    }
   34.35 +}
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel+MoveToFolderDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    35.3 @@ -0,0 +1,18 @@
    35.4 +//
    35.5 +//  ThreadedEmailViewModel+MoveToFolderDelegate.swift
    35.6 +//  pEp
    35.7 +//
    35.8 +//  Created by Borja González de Pablo on 27/06/2018.
    35.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   35.10 +//
   35.11 +
   35.12 +import Foundation
   35.13 +
   35.14 +extension ThreadedEmailViewModel: MoveToFolderDelegate{
   35.15 +    func didMove() {
   35.16 +        guard let lastMessage = messages.last else {
   35.17 +            return
   35.18 +        }
   35.19 +        emailDisplayDelegate.emailDisplayDidDelete(message: lastMessage)
   35.20 +    }
   35.21 +}
    36.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.2 +++ b/pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel+UpdateThreadDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    36.3 @@ -0,0 +1,43 @@
    36.4 +//
    36.5 +//  ThreadedEmailViewModel+UpdateThreadDelegate.swift
    36.6 +//  pEp
    36.7 +//
    36.8 +//  Created by Borja González de Pablo on 18/06/2018.
    36.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   36.10 +//
   36.11 +
   36.12 +import Foundation
   36.13 +import MessageModel
   36.14 +
   36.15 +extension ThreadedEmailViewModel: UpdateThreadListDelegate {
   36.16 +
   36.17 +    func deleted(message: Message){
   36.18 +        deleted(topMessage: message)
   36.19 +    }
   36.20 +
   36.21 +    func deleted(topMessage message: Message) {
   36.22 +        guard let index = indexOfMessage(message: message) else {
   36.23 +            return
   36.24 +        }
   36.25 +        messages.remove(at: index)
   36.26 +        delegate?.emailViewModel(viewModel: self, didRemoveDataAt: index)
   36.27 +    }
   36.28 +
   36.29 +    func updated(message: Message) {
   36.30 +        updateInternal(message: message)
   36.31 +    }
   36.32 +
   36.33 +    func added(message: Message) {
   36.34 +        let index = addMessage(message: message)
   36.35 +        delegate?.emailViewModel(viewModel: self, didInsertDataAt: index)
   36.36 +    }
   36.37 +
   36.38 +    internal func indexOfMessage(message: Message)-> Int? {
   36.39 +        for  i in 0...messages.count {
   36.40 +            if messages[i] == message{
   36.41 +                return i
   36.42 +            }
   36.43 +        }
   36.44 +        return nil
   36.45 +    }
   36.46 +}
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModel.swift	Wed Jul 04 11:24:51 2018 +0200
    37.3 @@ -0,0 +1,158 @@
    37.4 +//
    37.5 +//  ThreadedEmailViewModel.swift
    37.6 +//  pEp
    37.7 +//
    37.8 +//  Created by Borja González de Pablo on 08/06/2018.
    37.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   37.10 +//
   37.11 +
   37.12 +import Foundation
   37.13 +import MessageModel
   37.14 +
   37.15 +
   37.16 +class ThreadedEmailViewModel {
   37.17 +
   37.18 +
   37.19 +    internal var messages: [Message]
   37.20 +    internal var tip: Message
   37.21 +    weak var emailDisplayDelegate: EmailDisplayDelegate!
   37.22 +    weak var delegate: ThreadedEmailViewModelDelegate!
   37.23 +    private let folder: ThreadedFolderWithTop
   37.24 +    private var expandedMessages: [Bool]
   37.25 +    private var messageToReply: Message?
   37.26 +
   37.27 +    //Needed for segue
   37.28 +    public let displayFolder: Folder
   37.29 +
   37.30 +    init(tip: Message, folder: Folder) {
   37.31 +        self.folder = ThreadedFolderWithTop(folder: folder)
   37.32 +        messages = tip.messagesInThread()
   37.33 +        self.tip = tip
   37.34 +        displayFolder = folder
   37.35 +        expandedMessages = Array(repeating: false, count: messages.count)
   37.36 +        expandedMessages.removeLast()
   37.37 +        expandedMessages.append(true)
   37.38 +    }
   37.39 +
   37.40 +    func deleteMessage(at index: Int){
   37.41 +        guard index < messages.count && index >= 0 else {
   37.42 +            return
   37.43 +        }
   37.44 +        emailDisplayDelegate.emailDisplayDidDelete(message: messages[index])
   37.45 +        folder.deleteSingle(message: messages[index])
   37.46 +        messages.remove(at: index)
   37.47 +        expandedMessages.remove(at: index)
   37.48 +        delegate.emailViewModel(viewModel: self, didRemoveDataAt: index)
   37.49 +
   37.50 +    }
   37.51 +
   37.52 +    func deleteAllMessages(){
   37.53 +        folder.deleteThread(message: tip)
   37.54 +        emailDisplayDelegate.emailDisplayDidDelete(message: tip)
   37.55 +    }
   37.56 +
   37.57 +    func addMessage(message: Message) -> Int{
   37.58 +        messages.append(message)
   37.59 +        expandedMessages.append(true)
   37.60 +        return messages.count - 1
   37.61 +    }
   37.62 +
   37.63 +    func updateInternal(message: Message) {
   37.64 +        guard let index = indexOfMessage(message: message) else {
   37.65 +            return
   37.66 +        }
   37.67 +        messages[index] = message
   37.68 +        delegate?.emailViewModel(viewModel: self, didUpdateDataAt: index)
   37.69 +    }
   37.70 +
   37.71 +    fileprivate func notifyFlag(_ status: Bool) {
   37.72 +        if status {
   37.73 +            emailDisplayDelegate.emailDisplayDidFlag(message: tip)
   37.74 +        } else {
   37.75 +            emailDisplayDelegate.emailDisplayDidUnflag(message: tip)
   37.76 +        }
   37.77 +    }
   37.78 +
   37.79 +    //Should only be called from view as it will handle its own update
   37.80 +    func switchFlag(forMessageAt index:Int) {
   37.81 +        guard index < messages.count && index >= 0 else {
   37.82 +            return
   37.83 +        }
   37.84 +
   37.85 +        let flagStatus = (messages[index].imapFlags?.flagged ?? false)
   37.86 +
   37.87 +        messages[index].imapFlags?.flagged = !flagStatus
   37.88 +        if messages[index] == tip {
   37.89 +            notifyFlag(!flagStatus)
   37.90 +        }
   37.91 +    }
   37.92 +
   37.93 +    func setFlag(forMessageAt index: Int, to status: Bool){
   37.94 +        guard index < messages.count && index >= 0 else {
   37.95 +                return
   37.96 +        }
   37.97 +        messages[index].imapFlags?.flagged = status
   37.98 +        messages[index].save()
   37.99 +        updated(message: messages[index])
  37.100 +        if messages[index] == tip {
  37.101 +            notifyFlag(status)
  37.102 +        }
  37.103 +    }
  37.104 +
  37.105 +    func setFlag(to status: Bool){
  37.106 +        for message in messages {
  37.107 +            message.imapFlags?.flagged = status
  37.108 +            message.save()
  37.109 +        }
  37.110 +        notifyFlag(status)
  37.111 +    }
  37.112 +
  37.113 +    func allMessagesFlagged() -> Bool {
  37.114 +        for message in messages {
  37.115 +            if message.imapFlags?.flagged == false {
  37.116 +                return false
  37.117 +            }
  37.118 +        }
  37.119 +        return true
  37.120 +    }
  37.121 +
  37.122 +    func viewModel(for index: Int) -> MessageViewModel? {
  37.123 +        guard index < messages.count && index >= 0 else {
  37.124 +            return nil
  37.125 +        }
  37.126 +        return  MessageViewModel(with: messages[index])
  37.127 +    }
  37.128 +
  37.129 +    func rowCount() -> Int {
  37.130 +        return messages.count
  37.131 +    }
  37.132 +
  37.133 +    internal func message(at index: Int) -> Message? {
  37.134 +        guard index < messages.count && index >= 0 else {
  37.135 +            return nil
  37.136 +        }
  37.137 +        return messages[index]
  37.138 +    }
  37.139 +
  37.140 +    func messageDidExpand(at index: Int){
  37.141 +        expandedMessages[index] = true
  37.142 +    }
  37.143 +
  37.144 +    func messageisExpanded(at index: Int) -> Bool{
  37.145 +        return expandedMessages[index]
  37.146 +    }
  37.147 +
  37.148 +    func replyToMessage(at index: Int){
  37.149 +        guard index < messages.count && index >= 0 else {
  37.150 +            return
  37.151 +        }
  37.152 +        self.messageToReply = messages[index]
  37.153 +    }
  37.154 +
  37.155 +    func getMessageToReply() -> Message? {
  37.156 +        guard let message = messageToReply else {
  37.157 +            return messages.last
  37.158 +        }
  37.159 +        return message
  37.160 +    }
  37.161 +}
    38.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.2 +++ b/pEpForiOS/UI/Thread/ViewModel/ThreadedEmailViewModelDelegate.swift	Wed Jul 04 11:24:51 2018 +0200
    38.3 @@ -0,0 +1,14 @@
    38.4 +//
    38.5 +//  EmailViewModelDelegate.swift
    38.6 +//  pEp
    38.7 +//
    38.8 +//  Created by Borja González de Pablo on 18/06/2018.
    38.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   38.10 +//
   38.11 +
   38.12 +import Foundation
   38.13 +protocol ThreadedEmailViewModelDelegate: class, TableViewUpdate {
   38.14 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didInsertDataAt index: Int)
   38.15 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didUpdateDataAt index: Int)
   38.16 +    func emailViewModel(viewModel: ThreadedEmailViewModel, didRemoveDataAt index: Int)
   38.17 +}
    39.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.2 +++ b/pEpForiOS/UI/Thread/ViewModel/TreadedEmailViewModel+DisplayedMessage.swift	Wed Jul 04 11:24:51 2018 +0200
    39.3 @@ -0,0 +1,22 @@
    39.4 +//
    39.5 +//  TreadedEmailViewModel+DisplayedMessage.swift
    39.6 +//  pEp
    39.7 +//
    39.8 +//  Created by Borja González de Pablo on 22/06/2018.
    39.9 +//  Copyright © 2018 p≡p Security S.A. All rights reserved.
   39.10 +//
   39.11 +
   39.12 +import Foundation
   39.13 +import MessageModel
   39.14 +
   39.15 +extension ThreadedEmailViewModel: DisplayedMessage {
   39.16 +
   39.17 +    var messageModel: Message? {
   39.18 +       return messages.last
   39.19 +    }
   39.20 +
   39.21 +    func update(forMessage message: Message) {
   39.22 +        updateInternal(message: message)
   39.23 +    }
   39.24 +
   39.25 +}