pEpForiOS/UI/EmailDisplayList/EmailListViewController.swift
author Xavier Algarra <xavier@pep-project.org>
Mon, 07 May 2018 11:11:24 +0200
branchIOS-1064
changeset 4550 c04fe6d5c76b
parent 4496 0c419fcb3d7f
permissions -rw-r--r--
IOS-1064 some changes
     1 //
     2 //  EmailListViewController.swift
     3 //  pEpForiOS
     4 //
     5 //  Created by Dirk Zimmermann on 16/04/16.
     6 //  Copyright © 2016 p≡p Security S.A. All rights reserved.
     7 //
     8 
     9 import UIKit
    10 import MessageModel
    11 import SwipeCellKit
    12 
    13 class EmailListViewController: BaseTableViewController, SwipeTableViewCellDelegate {
    14     var folderToShow: Folder? //-> se ha de mover al vm
    15     private var model: EmailListViewModel?
    16 
    17     func updateLastLookAt() {
    18         guard let saveFolder = folderToShow else {
    19             return
    20         }
    21         saveFolder.updateLastLookAt()
    22     }
    23     
    24     private let queue: OperationQueue = {
    25         let createe = OperationQueue()
    26         createe.qualityOfService = .userInteractive
    27         createe.maxConcurrentOperationCount = 5
    28         return createe
    29     }()
    30     private var operations = [IndexPath:Operation]()
    31     public static let storyboardId = "EmailListViewController"
    32     private var lastSelectedIndexPath: IndexPath?
    33     
    34     let searchController = UISearchController(searchResultsController: nil)
    35 
    36     //swipe acctions types
    37     var buttonDisplayMode: ButtonDisplayMode = .titleAndImage
    38     var buttonStyle: ButtonStyle = .backgroundColor
    39 
    40     /// Indicates that we must not trigger reloadData.
    41     private var loadingBlocked = false
    42     
    43     // MARK: - Outlets
    44     
    45     @IBOutlet weak var enableFilterButton: UIBarButtonItem!
    46     @IBOutlet weak var textFilterButton: UIBarButtonItem!
    47     @IBOutlet var showFoldersButton: UIBarButtonItem!
    48     
    49     // MARK: - Life Cycle
    50     
    51     override func viewDidLoad() {
    52         super.viewDidLoad()
    53         
    54         UIHelper.emailListTableHeight(self.tableView)
    55         self.textFilterButton.isEnabled = false
    56 
    57         configureSearchBar()
    58 
    59         if #available(iOS 11.0, *) {
    60             searchController.isActive = false
    61             self.navigationItem.searchController = searchController
    62             self.navigationItem.hidesSearchBarWhenScrolling = true
    63         } else {
    64             addSearchBar()
    65 
    66             if tableView.tableHeaderView == nil {
    67                 tableView.tableHeaderView = searchController.searchBar
    68             }
    69 
    70             // some notifications to control when the app enter and recover from background
    71             NotificationCenter.default.addObserver(
    72                 self,
    73                 selector: #selector(didBecomeActive),
    74                 name: NSNotification.Name.UIApplicationDidBecomeActive,
    75                 object: nil)
    76             NotificationCenter.default.addObserver(
    77                 self,
    78                 selector: #selector(didBecomeInactive),
    79                 name: NSNotification.Name.UIApplicationDidEnterBackground,
    80                 object: nil)
    81         }
    82     }
    83 
    84     deinit {
    85         NotificationCenter.default.removeObserver(
    86             self,
    87             name: NSNotification.Name.UIApplicationDidBecomeActive,
    88             object: nil)
    89         NotificationCenter.default.removeObserver(
    90             self,
    91             name: NSNotification.Name.UIApplicationDidEnterBackground,
    92             object: nil)
    93     }
    94 
    95     /**
    96      Showing the search controller in versions prios to iOS 11.
    97      */
    98     @objc func didBecomeActive() {
    99         if tableView.tableHeaderView == nil {
   100             tableView.tableHeaderView = searchController.searchBar
   101         }
   102     }
   103 
   104     @objc func didBecomeInactive() {
   105         tableView.tableHeaderView = nil
   106     }
   107     
   108     override func viewWillAppear(_ animated: Bool) {
   109         super.viewWillAppear(animated)
   110         guard let vm = model else {
   111             Log.shared.errorAndCrash(component: #function,
   112                                      errorString: "ViewModel can not be nil!")
   113             return
   114         }
   115         self.navigationController?.setToolbarHidden(false, animated: true)
   116         if MiscUtil.isUnitTest() {
   117             return
   118         }
   119 
   120         setDefaultColors()
   121         setup()
   122         
   123         // Mark this folder as having been looked at by the user
   124         vm.updateLastLookAt()
   125         setupFoldersBarButton()
   126         if model != nil {
   127             updateFilterButtonView()
   128         }
   129     }
   130     
   131     // MARK: - NavigationBar
   132     
   133     private func hideFoldersNavigationBarButton() {
   134         self.showFoldersButton.isEnabled = false
   135         self.showFoldersButton.tintColor = UIColor.clear
   136     }
   137     
   138     private func showFoldersNavigationBarButton() {
   139         self.showFoldersButton.isEnabled = true
   140         self.showFoldersButton.tintColor = nil
   141     }
   142     
   143     private func resetModel() {
   144         if folderToShow != nil {
   145             model = EmailListViewModel(delegate: self,
   146                                        messageSyncService: appConfig.messageSyncService,
   147                                        folderToShow: folderToShow)
   148         }
   149     }
   150     
   151     private func setup() {
   152         if noAccountsExist() {
   153             // No account exists. Show account setup.
   154             performSegue(withIdentifier:.segueAddNewAccount, sender: self)
   155         } else if let vm = model {
   156             // We came back from e.g EmailView ...
   157             updateFilterText()
   158             // ... so we want to update "seen" status
   159             vm.reloadData()
   160         } else if folderToShow == nil {
   161             // We have not been created to show a specific folder, thus we show unified inbox
   162             folderToShow = UnifiedInbox()
   163             resetModel()
   164         } else if model == nil {
   165             // We still got no model, because:
   166             // - We are not coming back from a pushed view (for instance ComposeEmailView)
   167             // - We are not a UnifiedInbox
   168             // So we have been created to show a specific folder. Show it!
   169             resetModel()
   170         }
   171 
   172         title = folderToShow?.localizedName
   173     }
   174 
   175     private func weCameBackFromAPushedView() -> Bool {
   176         return model != nil
   177     }
   178     
   179     private func noAccountsExist() -> Bool {
   180         return Account.all().isEmpty
   181     }
   182     
   183     private func setupFoldersBarButton() {
   184         if let size = navigationController?.viewControllers.count, size > 1 {
   185             hideFoldersNavigationBarButton()
   186         } else {
   187             showFoldersNavigationBarButton()
   188         }
   189     }
   190 
   191     private func configureSearchBar() {
   192         searchController.searchResultsUpdater = self
   193         searchController.dimsBackgroundDuringPresentation = false
   194         searchController.delegate = self
   195     }
   196 
   197     private func addSearchBar() {
   198         definesPresentationContext = true
   199         tableView.tableHeaderView = searchController.searchBar
   200         tableView.setContentOffset(CGPoint(x: 0.0,
   201                                            y: searchController.searchBar.frame.size.height),
   202                                    animated: false)
   203     }
   204     
   205     // MARK: - Other
   206 
   207     private func address(forRowAt indexPath: IndexPath) -> String {
   208         guard
   209             let saveModel = model,
   210             let row = saveModel.row(for: indexPath),
   211             let folder = folderToShow
   212             else {
   213                 Log.shared.errorAndCrash(component: #function, errorString: "Invalid state")
   214                 return ""
   215         }
   216         switch folder.folderType {
   217         case .all: fallthrough
   218         case .archive: fallthrough
   219         case .spam: fallthrough
   220         case .trash: fallthrough
   221         case .flagged: fallthrough
   222         case .inbox: fallthrough
   223         case .normal:
   224             return row.from
   225         case .drafts: fallthrough
   226         case .sent:
   227             return row.to
   228         }
   229     }
   230     
   231     private func configure(cell: EmailListViewCell, for indexPath: IndexPath) {
   232         // Configure lightweight stuff on main thread ...
   233         guard let saveModel = model else {
   234             return
   235         }
   236         guard let row = saveModel.row(for: indexPath) else {
   237             Log.shared.errorAndCrash(component: #function, errorString: "We should have a row here")
   238             return
   239         }
   240         cell.addressLabel.text = address(forRowAt: indexPath)
   241         cell.subjectLabel.text = row.subject
   242         cell.summaryLabel.text = row.bodyPeek
   243         cell.isFlagged = row.isFlagged
   244         cell.isSeen = row.isSeen
   245         cell.hasAttachment = row.showAttchmentIcon
   246         cell.dateLabel.text = row.dateText
   247         // Set image from cache if any
   248         cell.setContactImage(image: row.senderContactImage)
   249 
   250         let op = BlockOperation() { [weak self] in
   251             MessageModel.performAndWait {
   252                 // ... and expensive computations in background
   253                 guard let strongSelf = self else {
   254                     // View is gone, nothing to do.
   255                     return
   256                 }
   257 
   258                 if row.senderContactImage == nil {
   259                     // image for identity has not been cached yet
   260                     // Get (and cache) it here in the background ...
   261                     let senderImage = strongSelf.model?.senderImage(forCellAt: indexPath)
   262                     
   263                     // ... and set it on the main queue
   264                     DispatchQueue.main.async {
   265                         if senderImage != nil && senderImage != cell.contactImageView.image {
   266                             cell.contactImageView.image  = senderImage
   267                         }
   268                     }
   269                 }
   270                 
   271                 guard let pEpRatingImage = strongSelf.model?.pEpRatingColorImage(forCellAt: indexPath) else {
   272                     return
   273                 }
   274                 
   275                 // In theory we want to set all data in *one* async call. But as pEpRatingColorImage takes
   276                 // very long, we are setting the sender image seperatelly. //IOS-999: after Engine rewrite and not logging to Engine anymore computing the peprating is way more performant. Try if moving this up in image setting block.
   277                 DispatchQueue.main.async {
   278                     cell.setPepRatingImage(image: pEpRatingImage)
   279                 }
   280             }
   281         }
   282         queue(operation: op, for: indexPath)
   283     }
   284 
   285     private func showComposeView() {
   286         self.performSegue(withIdentifier: SegueIdentifier.segueEditDraft, sender: self)
   287     }
   288 
   289     private func showEmail(forCellAt indexPath: IndexPath) {
   290         performSegue(withIdentifier: SegueIdentifier.segueShowEmail, sender: self)
   291         guard let vm = model else {
   292             Log.shared.errorAndCrash(component: #function, errorString: "No model.")
   293             return
   294         }
   295         vm.markRead(forIndexPath: indexPath)
   296     }
   297     
   298     // MARK: - Action Filter Button
   299     
   300     @IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
   301         guard !loadingBlocked else {
   302             return
   303         }
   304         guard let vm = model else {
   305             Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
   306             return
   307         }
   308         stopLoading()
   309         vm.isFilterEnabled = !vm.isFilterEnabled
   310         updateFilterButtonView()
   311     }
   312     
   313     private func updateFilterButtonView() {
   314         guard let vm = model else {
   315             Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
   316             return
   317         }
   318         
   319         textFilterButton.isEnabled = vm.isFilterEnabled
   320         if textFilterButton.isEnabled {
   321             enableFilterButton.image = UIImage(named: "unread-icon-active")
   322             updateFilterText()
   323         } else {
   324             textFilterButton.title = ""
   325             enableFilterButton.image = UIImage(named: "unread-icon")
   326         }
   327     }
   328     
   329     private func updateFilterText() {
   330         if let vm = model, let txt = vm.activeFilter?.title {
   331             textFilterButton.title = "Filter by: " + txt
   332         }
   333     }
   334     
   335     // MARK: - UITableViewDataSource
   336     
   337     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   338         return model?.rowCount ?? 0
   339     }
   340     
   341     override func tableView(_ tableView: UITableView,
   342                             cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   343         guard let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
   344                                                        for: indexPath) as? EmailListViewCell
   345             else {
   346                 Log.shared.errorAndCrash(component: #function, errorString: "Wrong cell!")
   347                 return UITableViewCell()
   348         }
   349         cell.delegate = self
   350         configure(cell: cell, for: indexPath)
   351         return cell
   352     }
   353     
   354     // MARK: - SwipeTableViewCellDelegate
   355 
   356     func tableView(_ tableView: UITableView,
   357                    editActionsForRowAt
   358         indexPath: IndexPath,
   359                    for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
   360 
   361         // Create swipe actions, taking the currently displayed folder into account
   362         var swipeActions = [SwipeAction]()
   363 
   364         // Get messages parent folder
   365         let parentFolder: Folder
   366         if let folder = folderToShow, !(folder is UnifiedInbox) {
   367             // Do not bother our imperformant MessageModel if we already know the parent folder
   368             parentFolder = folder
   369         } else {
   370             // folderToShow is unified inbox, fetch parent folder from DB.
   371             guard let vm = model,
   372                 let folder = vm.message(representedByRowAt: indexPath)?.parent else {
   373                     Log.shared.errorAndCrash(component: #function, errorString: "Dangling Message")
   374                     return nil
   375             }
   376             parentFolder = folder
   377         }
   378 
   379         // Delete or Archive
   380         let defaultIsArchive = parentFolder.defaultDestructiveActionIsArchive
   381         let titleDestructive = defaultIsArchive ? "Archive" : "Delete"
   382         let descriptorDestructive: ActionDescriptor = defaultIsArchive ? .archive : .trash
   383         let archiveAction =
   384             SwipeAction(style: .destructive, title: titleDestructive) {action, indexPath in
   385                 self.deleteAction(forCellAt: indexPath)
   386         }
   387         configure(action: archiveAction, with: descriptorDestructive)
   388         swipeActions.append(archiveAction)
   389 
   390         // Flag
   391         let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
   392             self.flagAction(forCellAt: indexPath)
   393         }
   394         flagAction.hidesWhenSelected = true
   395         configure(action: flagAction, with: .flag)
   396         swipeActions.append(flagAction)
   397 
   398         // More (reply++)
   399         if parentFolder.folderType != .drafts {
   400             // Do not add "more" actions (reply...) to drafted mails.
   401             let moreAction = SwipeAction(style: .default, title: "More") { action, indexPath in
   402                 self.moreAction(forCellAt: indexPath)
   403             }
   404             moreAction.hidesWhenSelected = true
   405             configure(action: moreAction, with: .more)
   406             swipeActions.append(moreAction)
   407         }
   408         return (orientation == .right ?   swipeActions : nil)
   409     }
   410 
   411     func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
   412         var options = SwipeTableOptions()
   413         options.expansionStyle = .destructive
   414         options.transitionStyle = .border
   415         options.buttonSpacing = 11
   416         return options
   417     }
   418 
   419     func configure(action: SwipeAction, with descriptor: ActionDescriptor) {
   420         action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
   421         action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
   422 
   423         switch buttonStyle {
   424         case .backgroundColor:
   425             action.backgroundColor = descriptor.color
   426         case .circular:
   427             action.backgroundColor = .clear
   428             action.textColor = descriptor.color
   429             action.font = .systemFont(ofSize: 13)
   430             action.transitionDelegate = ScaleTransition.default
   431         }
   432     }
   433     
   434     override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
   435         cancelOperation(for: indexPath)
   436     }
   437     
   438     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   439         guard let folder = folderToShow else {
   440             Log.shared.errorAndCrash(component: #function, errorString: "No folder")
   441             return
   442         }
   443         lastSelectedIndexPath = indexPath
   444         tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
   445 
   446         if folder.folderType == .drafts {
   447             showComposeView()
   448         } else {
   449             showEmail(forCellAt: indexPath)
   450         }
   451     }
   452 
   453     // Implemented to get informed about the scrolling position.
   454     // If the user has scrolled down (almost) to the end, we need to get older emails to display.
   455     override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
   456                             forRowAt indexPath: IndexPath) {
   457         guard let vm = model else {
   458             Log.shared.errorAndCrash(component: #function, errorString: "No model.")
   459             return
   460         }
   461         vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
   462     }
   463 
   464     // MARK: - Queue Handling
   465 
   466     /// Cancels all operations and sets tableView.dataSource to nil.
   467     /// Used to avoid that an operation accesses an outdated view model
   468     private func stopLoading() {
   469         loadingBlocked = true
   470         tableView.dataSource = nil
   471         queue.cancelAllOperations()
   472         queue.waitUntilAllOperationsAreFinished()
   473     }
   474     
   475     private func queue(operation op:Operation, for indexPath: IndexPath) {
   476         operations[indexPath] = op
   477         queue.addOperation(op)
   478     }
   479     
   480     private func cancelOperation(for indexPath:IndexPath) {
   481         guard let op = operations.removeValue(forKey: indexPath) else {
   482             return
   483         }
   484         if !op.isCancelled  {
   485             op.cancel()
   486         }
   487     }
   488 
   489     // MARK: -
   490 
   491     override func didReceiveMemoryWarning() {
   492         model?.freeMemory()
   493     }
   494 }
   495 
   496 // MARK: - UISearchResultsUpdating, UISearchControllerDelegate
   497 
   498 extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
   499     public func updateSearchResults(for searchController: UISearchController) {
   500         guard let vm = model, let searchText = searchController.searchBar.text else {
   501             return
   502         }
   503         vm.setSearchFilter(forSearchText: searchText)
   504     }
   505     
   506     func didDismissSearchController(_ searchController: UISearchController) {
   507         guard let vm = model else {
   508             return
   509         }
   510         vm.removeSearchFilter()
   511     }
   512 }
   513 
   514 // MARK: - EmailListModelDelegate
   515 
   516 extension EmailListViewController: EmailListViewModelDelegate {
   517     func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPath: IndexPath) {
   518         Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
   519         tableView.beginUpdates()
   520         tableView.insertRows(at: [indexPath], with: .automatic)
   521         tableView.endUpdates()
   522     }
   523     
   524     func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPath: IndexPath) {
   525         Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
   526         tableView.beginUpdates()
   527         tableView.deleteRows(at: [indexPath], with: .automatic)
   528         tableView.endUpdates()
   529     }
   530     
   531     func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPath: IndexPath) {
   532         Log.shared.info(component: #function, content: "\(model?.rowCount ?? 0)")
   533         tableView.beginUpdates()
   534         tableView.reloadRows(at: [indexPath], with: .none)
   535         tableView.endUpdates()
   536     }
   537     
   538     func updateView() {
   539         loadingBlocked = false
   540         tableView.dataSource = self
   541         tableView.reloadData()
   542     }
   543 }
   544 
   545 // MARK: - ActionSheet & ActionSheet Actions
   546 
   547 extension EmailListViewController {
   548     func showMoreActionSheet(forRowAt indexPath: IndexPath) {
   549         lastSelectedIndexPath = indexPath
   550         let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
   551         alertControler.view.tintColor = .pEpGreen
   552         let cancelAction = createCancelAction()
   553         let replyAction = createReplyAction()
   554         let replyAllAction = createReplyAllAction()
   555         let forwardAction = createForwardAction()
   556         alertControler.addAction(cancelAction)
   557         alertControler.addAction(replyAction)
   558         alertControler.addAction(replyAllAction)
   559         alertControler.addAction(forwardAction)
   560         if let popoverPresentationController = alertControler.popoverPresentationController {
   561             popoverPresentationController.sourceView = tableView
   562         }
   563         present(alertControler, animated: true, completion: nil)
   564     }
   565     
   566     // MARK: Action Sheet Actions
   567     
   568     func createCancelAction() -> UIAlertAction {
   569         return  UIAlertAction(title: "Cancel", style: .cancel) { (action) in
   570             self.tableView.beginUpdates()
   571             self.tableView.setEditing(false, animated: true)
   572             self.tableView.endUpdates()
   573         }
   574     }
   575     
   576     func createReplyAction() ->  UIAlertAction {
   577         return UIAlertAction(title: "Reply", style: .default) { (action) in
   578             self.performSegue(withIdentifier: .segueReply, sender: self)
   579         }
   580     }
   581     
   582     func createReplyAllAction() ->  UIAlertAction {
   583         return UIAlertAction(title: "Reply All", style: .default) { (action) in
   584             self.performSegue(withIdentifier: .segueReplyAll, sender: self)
   585         }
   586     }
   587     
   588     func createForwardAction() -> UIAlertAction {
   589         return UIAlertAction(title: "Forward", style: .default) { (action) in
   590             self.performSegue(withIdentifier: .segueForward, sender: self)
   591         }
   592     }
   593 }
   594 
   595 // MARK: - TableViewCell Actions
   596 
   597 extension EmailListViewController {
   598     private func createRowAction(image: UIImage?,
   599                                  action: @escaping (UITableViewRowAction, IndexPath) -> Void
   600         ) -> UITableViewRowAction {
   601         let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
   602         if let theImage = image {
   603             let iconColor = UIColor(patternImage: theImage)
   604             rowAction.backgroundColor = iconColor
   605         }
   606         return rowAction
   607     }
   608     
   609     func flagAction(forCellAt indexPath: IndexPath) {
   610         guard let row = model?.row(for: indexPath) else {
   611             Log.shared.errorAndCrash(component: #function, errorString: "No data for indexPath!")
   612             return
   613         }
   614         if row.isFlagged {
   615             model?.unsetFlagged(forIndexPath: indexPath)
   616         } else {
   617             model?.setFlagged(forIndexPath: indexPath)
   618         }
   619     }
   620     
   621     func deleteAction(forCellAt indexPath: IndexPath) {
   622         model?.delete(forIndexPath: indexPath)
   623     }
   624     
   625     func moreAction(forCellAt indexPath: IndexPath) {
   626         self.showMoreActionSheet(forRowAt: indexPath)
   627     }
   628 }
   629 
   630 // MARK: - SegueHandlerType
   631 
   632 extension EmailListViewController: SegueHandlerType {
   633     
   634     enum SegueIdentifier: String {
   635         case segueAddNewAccount
   636         case segueShowEmail
   637         case segueCompose
   638         case segueReply
   639         case segueReplyAll
   640         case segueForward
   641         case segueEditDraft
   642         case segueFilter
   643         case segueFolderViews
   644         case noSegue
   645     }
   646     
   647     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   648         let segueId = segueIdentifier(for: segue)
   649         switch segueId {
   650         case .segueReply,
   651              .segueReplyAll,
   652              .segueForward,
   653              .segueCompose,
   654              .segueEditDraft:
   655             setupComposeViewController(for: segue)
   656         case .segueShowEmail:
   657             guard let vc = segue.destination as? EmailViewController,
   658                 let indexPath = lastSelectedIndexPath,
   659                 let message = model?.message(representedByRowAt: indexPath) else {
   660                     Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   661                     return
   662             }
   663             vc.appConfig = appConfig
   664             vc.message = message
   665             vc.folderShow = folderToShow
   666             vc.messageId = indexPath.row //that looks wrong
   667         case .segueFilter:
   668             guard let destiny = segue.destination as? FilterTableViewController  else {
   669                 Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   670                 return
   671             }
   672             destiny.appConfig = appConfig
   673             destiny.filterDelegate = model
   674             destiny.inFolder = false
   675             destiny.filterEnabled = folderToShow?.filter
   676             destiny.hidesBottomBarWhenPushed = true
   677         case .segueAddNewAccount:
   678             guard let vc = segue.destination as? LoginTableViewController  else {
   679                 Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   680                 return
   681             }
   682             vc.appConfig = appConfig
   683             vc.hidesBottomBarWhenPushed = true
   684             break
   685         case .segueFolderViews:
   686             guard let vC = segue.destination as? FolderTableViewController  else {
   687                 Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
   688                 return
   689             }
   690             vC.appConfig = appConfig
   691             vC.hidesBottomBarWhenPushed = true
   692             break
   693         default:
   694             Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
   695             break
   696         }
   697     }
   698     
   699     @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
   700         // nothing to do.
   701     }
   702 
   703     private func setupComposeViewController(for segue: UIStoryboardSegue) {
   704         let segueId = segueIdentifier(for: segue)
   705         guard
   706             let nav = segue.destination as? UINavigationController,
   707             let composeVc = nav.topViewController as? ComposeTableViewController,
   708             let composeMode = composeMode(for: segueId) else {
   709                 Log.shared.errorAndCrash(component: #function,
   710                                          errorString: "composeViewController setup issue")
   711                 return
   712         }
   713         composeVc.appConfig = appConfig
   714         composeVc.composeMode = composeMode
   715         composeVc.origin = folderToShow?.account.user
   716         if composeMode != .normal {
   717             // This is not a simple compose (but reply, forward or such),
   718             // thus we have to pass the original meaasge.
   719             guard
   720                 let indexPath = lastSelectedIndexPath,
   721                 let message = model?.message(representedByRowAt: indexPath) else {
   722                     Log.shared.errorAndCrash(component: #function,
   723                                              errorString: "No original message")
   724                     return
   725             }
   726             composeVc.originalMessage = message
   727         }
   728     }
   729 
   730     private func composeMode(for segueId: SegueIdentifier) -> ComposeTableViewController.ComposeMode? {
   731         switch segueId {
   732         case .segueReply:
   733             return .replyFrom
   734         case .segueReplyAll:
   735             return .replyAll
   736         case .segueForward:
   737             return .forward
   738         case .segueCompose:
   739             return .normal
   740         case .segueEditDraft:
   741             return .draft
   742         default:
   743             return nil
   744         }
   745     }
   746 }
   747 
   748 //enums to simplify configurations
   749 
   750 enum ActionDescriptor {
   751     case read, more, flag, trash, archive
   752 
   753     func title(forDisplayMode displayMode: ButtonDisplayMode) -> String? {
   754         guard displayMode != .imageOnly else { return nil }
   755 
   756         switch self {
   757         case .read: return NSLocalizedString("Read", comment: "read button in slidw left menu")
   758         case .more: return NSLocalizedString("More", comment: "more button in slidw left menu")
   759         case .flag: return NSLocalizedString("Flag", comment: "read button in slidw left menu")
   760         case .trash: return NSLocalizedString("Trash", comment: "Trash button in slidw left menu")
   761         case .archive: return NSLocalizedString("Archive", comment: "Archive button in slidw left menu")
   762         }
   763     }
   764 
   765     func image(forStyle style: ButtonStyle, displayMode: ButtonDisplayMode) -> UIImage? {
   766         guard displayMode != .titleOnly else { return nil }
   767 
   768         let name: String
   769         switch self {
   770         case .read: name = "read"
   771         case .more: name = "more"
   772         case .flag: name = "flag"
   773         case .trash: name = "trash"
   774         case .archive: name = "trash"
   775         }
   776 
   777         return UIImage(named: "swipe-" + name)
   778     }
   779 
   780     var color: UIColor {
   781         switch self {
   782         case .read: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
   783         case .more: return #colorLiteral(red: 0.7803494334, green: 0.7761332393, blue: 0.7967314124, alpha: 1)
   784         case .flag: return #colorLiteral(red: 1, green: 0.5803921569, blue: 0, alpha: 1)
   785         case .trash: return #colorLiteral(red: 1, green: 0.2352941176, blue: 0.1882352941, alpha: 1)
   786         case .archive: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
   787         }
   788     }
   789 }
   790 
   791 enum ButtonDisplayMode {
   792     case titleAndImage, titleOnly, imageOnly
   793 }
   794 
   795 enum ButtonStyle {
   796     case backgroundColor, circular
   797 }