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