pEpForiOS/UI/EmailDisplayList/EmailListViewController.swift
author Dirk Zimmermann <dz@pep.security>
Tue, 10 Dec 2019 17:06:58 +0100
branchIOS-2021
changeset 11219 ab9412c8782b
parent 11146 4894d19920e0
permissions -rw-r--r--
IOS-2021 Only search for auto-consume messages
     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 SwipeCellKit
    11 import pEpIOSToolbox
    12 
    13 class EmailListViewController: BaseTableViewController, SwipeTableViewCellDelegate {
    14     static let FILTER_TITLE_MAX_XAR = 20
    15 
    16     var model: EmailListViewModel? {
    17         didSet {
    18             model?.emailListViewModelDelegate = self
    19         }
    20     }
    21 
    22     public static let storyboardId = "EmailListViewController"
    23     /// This is used to handle the selection row when it recives an update
    24     /// and also when swipeCellAction is performed to store from which cell the action is done.
    25     private var lastSelectedIndexPath: IndexPath?
    26 
    27     let searchController = UISearchController(searchResultsController: nil)
    28 
    29     //swipe acctions types
    30     var buttonDisplayMode: ButtonDisplayMode = .titleAndImage
    31     var buttonStyle: ButtonStyle = .backgroundColor
    32 
    33     private var swipeDelete : SwipeAction? = nil
    34 
    35     // MARK: - Outlets
    36     
    37     @IBOutlet weak var enableFilterButton: UIBarButtonItem!
    38 
    39     private let refreshController = UIRefreshControl()
    40 
    41     var textFilterButton: UIBarButtonItem = UIBarButtonItem(title: "",
    42                                                             style: .plain,
    43                                                             target: nil,
    44                                                             action: nil)
    45 
    46     // MARK: - Life Cycle
    47 
    48     override func viewDidLoad() {
    49         super.viewDidLoad()
    50 
    51         tableView.allowsMultipleSelectionDuringEditing = true
    52         setupSearchBar()
    53         setup()
    54     }
    55 
    56     override func viewWillAppear(_ animated: Bool) {
    57         super.viewWillAppear(animated)
    58         navigationController?.setToolbarHidden(false, animated: true)
    59         if MiscUtil.isUnitTest() {
    60             return
    61         }
    62 
    63         lastSelectedIndexPath = nil
    64 
    65         setUpTextFilter()
    66 
    67         guard let vm = model else {
    68             Log.shared.errorAndCrash("No VM")
    69             return
    70         }
    71 
    72         showNoMessageSelectedIfNeeded()
    73 
    74         if !vm.showLoginView {
    75             updateFilterButtonView()
    76             vm.startMonitoring() //???: should UI know about startMonitoring?
    77 
    78             // Threading feature is currently non-existing. Keep this code, might help later.
    79             //            if vm.checkIfSettingsChanged() {
    80             //                settingsChanged()
    81             //            }
    82 
    83             checkSplitViewState()
    84             watchDetailView()
    85         }
    86     }
    87 
    88     override func viewWillDisappear(_ animated: Bool) {
    89         super.viewWillAppear(animated)
    90         unwatchDetailView()
    91     }
    92 
    93     deinit {
    94          NotificationCenter.default.removeObserver(self)
    95     }
    96 
    97     private func showLoginScreen() {
    98         performSegue(withIdentifier:.segueAddNewAccount, sender: self)
    99         return
   100     }
   101 
   102     // MARK: - Setup
   103 
   104     private func setup() {
   105 
   106         guard let vm = model else {
   107             Log.shared.errorAndCrash("No VM")
   108             return
   109         }
   110 
   111         if vm.showLoginView {
   112             showLoginScreen()
   113             return
   114         }
   115 
   116         if vm.shouldShowTutorialWizard() {
   117             TutorialWizardViewController.presentTutorialWizard(viewController: self)
   118             vm.didShowTutorialWizard()
   119         }
   120 
   121         ///if we are in setup and the folder is unifiedInbox
   122         ///we have to reload the unifiedInbox to ensure that all the accounts are present.
   123         if vm.folderToShow is UnifiedInbox {
   124             model = EmailListViewModel(emailListViewModelDelegate: self,
   125                                        folderToShow: UnifiedInbox())
   126         }
   127 
   128         ///the refresh controller is configured and added to the tableview
   129         refreshController.tintColor = UIColor.pEpGreen
   130         refreshController.addTarget(self, action: #selector(self.refreshView(_:)), for: .valueChanged)
   131         tableView.refreshControl = refreshController
   132 
   133         title = model?.folderName
   134         navigationController?.title = title
   135 
   136         let flexibleSpace = createFlexibleBarButtonItem()
   137         toolbarItems?.append(contentsOf: [flexibleSpace, createPepBarButtonItem()])
   138     }
   139 
   140     private func setUpTextFilter() {
   141         textFilterButton.isEnabled = false
   142         textFilterButton.action = #selector(showFilterOptions(_:))
   143         textFilterButton.target = self
   144 
   145         let fontSize:CGFloat = 10;
   146         let font:UIFont = UIFont.boldSystemFont(ofSize: fontSize);
   147         let attributes = [NSAttributedString.Key.font: font];
   148 
   149         textFilterButton.setTitleTextAttributes(attributes, for: UIControl.State.normal)
   150     }
   151 
   152     // MARK: - Search Bar
   153 
   154     private func setupSearchBar() {
   155         definesPresentationContext = true
   156         configureSearchBar()
   157         if #available(iOS 11.0, *) {
   158             searchController.isActive = false
   159             navigationItem.searchController = searchController
   160             navigationItem.hidesSearchBarWhenScrolling = true
   161         } else {
   162             addSearchBar10()
   163 
   164             if tableView.tableHeaderView == nil {
   165                 tableView.tableHeaderView = searchController.searchBar
   166             }
   167 
   168             // some notifications to control when the app enter and recover from background
   169             NotificationCenter.default.addObserver(
   170                 self,
   171                 selector: #selector(didBecomeActiveInstallSearchBar10),
   172                 name: UIApplication.didBecomeActiveNotification,
   173                 object: nil)
   174             NotificationCenter.default.addObserver(
   175                 self,
   176                 selector: #selector(didBecomeInactiveUninstallSearchbar10),
   177                 name: UIApplication.didEnterBackgroundNotification,
   178                 object: nil)
   179         }
   180     }
   181 
   182     ///called when the view is scrolled down to
   183     @objc private func refreshView(_ sender: Any) {
   184         model?.fetchNewMessages() { [weak self] in
   185             // Loosing self is a valid use case here. We might have been dismissed.
   186             DispatchQueue.main.async {
   187                 self?.refreshControl?.endRefreshing()
   188             }
   189         }
   190     }
   191 
   192     /**
   193      Configure the search controller, shared between iOS versions 11 and earlier.
   194      */
   195     private func configureSearchBar() {
   196         searchController.searchResultsUpdater = self
   197         searchController.dimsBackgroundDuringPresentation = false
   198         searchController.delegate = self
   199     }
   200 
   201     /**
   202      Showing the search controller in versions iOS 10 and earlier.
   203      */
   204     @objc func didBecomeActiveInstallSearchBar10() {
   205         if tableView.tableHeaderView == nil {
   206             tableView.tableHeaderView = searchController.searchBar
   207         }
   208     }
   209 
   210     /**
   211      Hide/remove the search controller in versions iOS 10 and earlier.
   212      */
   213     @objc func didBecomeInactiveUninstallSearchbar10() {
   214         tableView.tableHeaderView = nil
   215     }
   216 
   217     /**
   218      Add the search bar when running on iOS 10 or earlier.
   219      */
   220     private func addSearchBar10() {
   221         tableView.tableHeaderView = searchController.searchBar
   222         tableView.setContentOffset(CGPoint(x: 0.0,
   223                                            y: searchController.searchBar.frame.size.height),
   224                                    animated: false)
   225     }
   226 
   227     // MARK: - Other
   228 
   229     private func showComposeView() {
   230         performSegue(withIdentifier: SegueIdentifier.segueEditDraft, sender: self)
   231     }
   232 
   233     /// we have to handle the ipad/iphone segue in a different way. see IOS-1737
   234     private func showEmail(forCellAt indexPath: IndexPath) {
   235         guard let splitViewController = self.splitViewController else {
   236             return
   237         }
   238         if onlySplitViewMasterIsShown {
   239             performSegue(withIdentifier: SegueIdentifier.segueShowEmailNotSplitView, sender: self)
   240         } else {
   241             performSegue(withIdentifier: SegueIdentifier.segueShowEmailSplitView, sender: self)
   242         }
   243     }
   244 
   245     private func showNoMessageSelectedIfNeeded() {
   246         showEmptyDetailViewIfApplicable(
   247             message: NSLocalizedString(
   248                 "Please chose a message",
   249                 comment: "No messages has been selected for detail view"))
   250     }
   251 
   252     @objc private func settingsChanged() {
   253         model?.informDelegateToReloadData()
   254         tableView.reloadData()
   255     }
   256 
   257     // MARK: - Action Edit Button
   258 
   259     private var tempToolbarItems: [UIBarButtonItem]?
   260     private var editRightButton: UIBarButtonItem?
   261     var flagToolbarButton: UIBarButtonItem?
   262     var unflagToolbarButton: UIBarButtonItem?
   263     var readToolbarButton: UIBarButtonItem?
   264     var unreadToolbarButton: UIBarButtonItem?
   265     var deleteToolbarButton: UIBarButtonItem?
   266     var moveToolbarButton: UIBarButtonItem?
   267 
   268     @IBAction func Edit(_ sender: Any) {
   269 
   270         showEditToolbar()
   271         tableView.setEditing(true, animated: true)
   272     }
   273 
   274     private func showEditToolbar() {
   275 
   276         tempToolbarItems = toolbarItems
   277 
   278         // Flexible Space separation between the buttons
   279         let flexibleSpace = createFlexibleBarButtonItem()
   280 
   281         var img = UIImage(named: "icon-flagged")
   282 
   283         flagToolbarButton = UIBarButtonItem(image: img,
   284                                    style: UIBarButtonItem.Style.plain,
   285                                    target: self,
   286                                    action: #selector(flagToolbar(_:)))
   287         flagToolbarButton?.isEnabled = false
   288 
   289         img = UIImage(named: "icon-unflagged")
   290 
   291         unflagToolbarButton = UIBarButtonItem(image: img,
   292                                             style: UIBarButtonItem.Style.plain,
   293                                             target: self,
   294                                             action: #selector(unflagToolbar(_:)))
   295         unflagToolbarButton?.isEnabled = false
   296 
   297         img = UIImage(named: "icon-read")
   298 
   299         readToolbarButton = UIBarButtonItem(image: img,
   300                                    style: UIBarButtonItem.Style.plain,
   301                                    target: self,
   302                                    action: #selector(readToolbar(_:)))
   303         readToolbarButton?.isEnabled = false
   304 
   305         img = UIImage(named: "icon-unread")
   306 
   307         unreadToolbarButton = UIBarButtonItem(image: img,
   308                                             style: UIBarButtonItem.Style.plain,
   309                                             target: self,
   310                                             action: #selector(unreadToolbar(_:)))
   311         unreadToolbarButton?.isEnabled = false
   312 
   313         img = UIImage(named: "folders-icon-trash")
   314 
   315         deleteToolbarButton = UIBarButtonItem(image: img,
   316                                      style: UIBarButtonItem.Style.plain,
   317                                      target: self,
   318                                      action: #selector(deleteToolbar(_:)))
   319 
   320         deleteToolbarButton?.isEnabled = false
   321 
   322         img = UIImage(named: "swipe-archive")
   323 
   324         moveToolbarButton = UIBarButtonItem(image: img,
   325                                      style: UIBarButtonItem.Style.plain,
   326                                      target: self,
   327                                      action: #selector(moveToolbar(_:)))
   328 
   329         moveToolbarButton?.isEnabled = false
   330 
   331         if var newToolbarItems = [flagToolbarButton, flexibleSpace, readToolbarButton,
   332                                   flexibleSpace, deleteToolbarButton, flexibleSpace,
   333                                   moveToolbarButton, flexibleSpace] as? [UIBarButtonItem] {
   334             if shouldShowPepButtonInMasterToolbar {
   335                 newToolbarItems.append(createPepBarButtonItem())
   336             }
   337             toolbarItems = newToolbarItems
   338         }
   339 
   340 
   341         //right navigation button to ensure the logic
   342         let cancel = UIBarButtonItem(title: "Cancel",
   343                                      style: UIBarButtonItem.Style.plain,
   344                                      target: self,
   345                                      action: #selector(cancelToolbar(_:)))
   346 
   347         editRightButton = navigationItem.rightBarButtonItem
   348         navigationItem.rightBarButtonItem = cancel
   349 
   350     }
   351 
   352     @objc private func showSettingsViewController() {
   353         UIUtils.presentSettings(on: self, appConfig: appConfig)
   354     }
   355 
   356     @IBAction func showFilterOptions(_ sender: UIBarButtonItem!) {
   357         performSegue(withIdentifier: .segueShowFilter, sender: self)
   358     }
   359 
   360     @IBAction func cancelToolbar(_ sender:UIBarButtonItem!) {
   361         showStandardToolbar()
   362         lastSelectedIndexPath = nil
   363         tableView.setEditing(false, animated: true)
   364     }
   365 
   366     @IBAction func flagToolbar(_ sender:UIBarButtonItem!) {
   367         if let selectedItems = tableView.indexPathsForSelectedRows {
   368             model?.markSelectedAsFlagged(indexPaths: selectedItems)
   369             selectedItems.forEach { (ip) in
   370                 if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
   371                     cell.isFlagged = true
   372                 }
   373             }
   374         }
   375         cancelToolbar(sender)
   376     }
   377 
   378     @IBAction func unflagToolbar(_ sender:UIBarButtonItem!) {
   379         if let selectedItems = tableView.indexPathsForSelectedRows {
   380             model?.markSelectedAsUnFlagged(indexPaths: selectedItems)
   381             selectedItems.forEach { (ip) in
   382                 if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
   383                     cell.isFlagged = false
   384                 }
   385             }
   386         }
   387         cancelToolbar(sender)
   388     }
   389 
   390     @IBAction func readToolbar(_ sender:UIBarButtonItem!) {
   391         if let selectedItems = tableView.indexPathsForSelectedRows {
   392             selectedItems.forEach { (ip) in
   393                 if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
   394                     cell.isSeen = true
   395                 }
   396             }
   397             model?.markSelectedAsRead(indexPaths: selectedItems)
   398         }
   399         cancelToolbar(sender)
   400     }
   401 
   402     @IBAction func unreadToolbar(_ sender:UIBarButtonItem!) {
   403         if let selectedItems = tableView.indexPathsForSelectedRows {
   404             model?.markSelectedAsUnread(indexPaths: selectedItems)
   405             selectedItems.forEach { (ip) in
   406                 if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
   407                     cell.isSeen = false
   408                 }
   409             }
   410         }
   411         cancelToolbar(sender)
   412     }
   413 
   414     @IBAction func moveToolbar(_ sender:UIBarButtonItem!) {
   415         performSegue(withIdentifier: .segueShowMoveToFolder, sender: self)
   416         cancelToolbar(sender)
   417     }
   418 
   419     @IBAction func deleteToolbar(_ sender:UIBarButtonItem!) {
   420         if let vm = model, let selectedIndexPaths = tableView.indexPathsForSelectedRows {
   421             vm.deleteSelected(indexPaths: selectedIndexPaths)
   422         }
   423         cancelToolbar(sender)
   424     }
   425 
   426     //recover the original toolbar and right button
   427     private func showStandardToolbar() {
   428         toolbarItems = tempToolbarItems
   429         navigationItem.rightBarButtonItem = editRightButton
   430     }
   431 
   432     private func moveSelectionIfNeeded(fromIndexPath: IndexPath, toIndexPath: IndexPath) {
   433         if lastSelectedIndexPath == fromIndexPath {
   434             lastSelectedIndexPath = toIndexPath
   435             resetSelection()
   436         }
   437     }
   438 
   439     private func resetSelection() {
   440         tableView.selectRow(at: lastSelectedIndexPath, animated: false, scrollPosition: .none)
   441     }
   442 
   443     // MARK: - Action Filter Button
   444     
   445     @IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
   446         guard let vm = model else {
   447             Log.shared.errorAndCrash("We should have a model here")
   448             return
   449         }
   450         vm.isFilterEnabled = !vm.isFilterEnabled
   451         if vm.isFilterEnabled {
   452             let flexibleSpace = createFlexibleBarButtonItem()
   453             toolbarItems?.insert(textFilterButton, at: 1)
   454             toolbarItems?.insert(flexibleSpace, at: 1)
   455         } else {
   456             toolbarItems?.remove(at: 1)
   457             toolbarItems?.remove(at: 1)
   458 
   459         }
   460         updateFilterButtonView()
   461     }
   462 
   463     private func updateFilterButtonView() {
   464         guard let vm = model else {
   465             Log.shared.errorAndCrash("We should have a model here")
   466             return
   467         }
   468 
   469         textFilterButton.isEnabled = vm.isFilterEnabled
   470         if textFilterButton.isEnabled {
   471             enableFilterButton.image = UIImage(named: "unread-icon-active")
   472             updateFilterText()
   473         } else {
   474             textFilterButton.title = ""
   475             enableFilterButton.image = UIImage(named: "unread-icon")
   476         }
   477     }
   478     
   479     private func updateFilterText() {
   480         if let vm = model {
   481             var txt = vm.currentFilter.getFilterText()
   482             if(txt.count > EmailListViewController.FILTER_TITLE_MAX_XAR){
   483                 let prefix = txt.prefix(ofLength: EmailListViewController.FILTER_TITLE_MAX_XAR)
   484                 txt = String(prefix)
   485                 txt += "..."
   486             }
   487             if txt.isEmpty {
   488                 txt = "none"
   489             }
   490             textFilterButton.title = "Filter by: " + txt
   491         }
   492     }
   493 
   494     // MARK: - UITableViewDataSource
   495     
   496     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   497         return model?.rowCount ?? 0
   498     }
   499 
   500     override func tableView(_ tableView: UITableView,
   501                             cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   502 
   503         let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
   504                                                  for: indexPath)
   505         if let theCell = cell as? EmailListViewCell {
   506             theCell.delegate = self
   507 
   508             guard let viewModel = model?.viewModel(for: indexPath.row) else {
   509                 return cell
   510             }
   511             theCell.configure(for: viewModel)
   512         } else {
   513             Log.shared.errorAndCrash("dequeued wrong cell")
   514         }
   515 
   516         //restores selection state for updated or replaced cells.
   517         if lastSelectedIndexPath == indexPath {
   518             tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
   519         }
   520 
   521         return cell
   522     }
   523 
   524     // MARK: - UITableViewDelegate
   525 
   526     func tableView(_ tableView: UITableView,
   527                    editActionsForRowAt
   528         indexPath: IndexPath,
   529                    for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
   530         if model == nil {
   531             return nil
   532         }
   533 
   534         // Create swipe actions, taking the currently displayed folder into account
   535         var swipeActions = [SwipeAction]()
   536 
   537         guard let model = model else {
   538             Log.shared.errorAndCrash("Should have VM")
   539             return nil
   540         }
   541 
   542         // Delete or Archive
   543         let destructiveAction = model.getDestructiveActtion(forMessageAt: indexPath.row)
   544         let archiveAction =
   545             SwipeAction(style: .destructive,
   546                         title: destructiveAction.title(forDisplayMode: .titleAndImage)) {
   547                             [weak self] action, indexPath in
   548                             guard let me = self else {
   549                                 Log.shared.errorAndCrash("Lost MySelf")
   550                                 return
   551                             }
   552                             me.swipeDelete = action
   553                             me.deleteAction(forCellAt: indexPath)
   554 
   555         }
   556         configure(action: archiveAction, with: destructiveAction)
   557         swipeActions.append(archiveAction)
   558 
   559         // Flag
   560         let flagActionDescription = model.getFlagAction(forMessageAt: indexPath.row)
   561         if let flagActionDescription = flagActionDescription {
   562             let flagAction = SwipeAction(style: .default, title: "Flag") {
   563                 [weak self] action, indexPath in
   564                 guard let me = self else {
   565                     Log.shared.errorAndCrash("Lost MySelf")
   566                     return
   567                 }
   568                 me.flagAction(forCellAt: indexPath)
   569             }
   570 
   571             flagAction.hidesWhenSelected = true
   572 
   573             configure(action: flagAction, with: flagActionDescription)
   574             swipeActions.append(flagAction)
   575         }
   576 
   577         // More (reply++)
   578         let moreActionDescription = model.getMoreAction(forMessageAt: indexPath.row)
   579 
   580         if let moreActionDescription = moreActionDescription {
   581             // Do not add "more" actions (reply...) to drafts or outbox.
   582             let moreAction = SwipeAction(style: .default, title: "More") {
   583                 [weak self] action, indexPath in
   584                 guard let me = self else {
   585                     Log.shared.errorAndCrash("Lost MySelf")
   586                     return
   587                 }
   588                 me.moreAction(forCellAt: indexPath)
   589             }
   590             moreAction.hidesWhenSelected = true
   591             configure(action: moreAction, with: moreActionDescription)
   592             swipeActions.append(moreAction)
   593         }
   594         return (orientation == .right ?   swipeActions : nil)
   595     }
   596 
   597     func tableView(_ tableView: UITableView,
   598                    editActionsOptionsForRowAt indexPath: IndexPath,
   599                    for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
   600         var options = SwipeTableOptions()
   601         options.transitionStyle = .border
   602         options.buttonSpacing = 11
   603         options.expansionStyle = .destructive(automaticallyDelete: false)
   604         return options
   605     }
   606 
   607     override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
   608         guard let cell = cell as? EmailListViewCell else {
   609             return
   610         }
   611         cell.clear()
   612     }
   613 
   614     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   615         if tableView.isEditing {
   616             if let vm = model, let selectedIndexPaths = tableView.indexPathsForSelectedRows {
   617                 vm.updatedItems(indexPaths: selectedIndexPaths)
   618             }
   619         } else {
   620             guard let model = model else {
   621                 Log.shared.errorAndCrash("No folder")
   622                 return
   623             }
   624             if model.isSelectable(messageAt: indexPath) {
   625                 lastSelectedIndexPath = indexPath
   626                 tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
   627                 if model.isEditable(messageAt: indexPath) {
   628                     showComposeView()
   629                 } else {
   630                     showEmail(forCellAt: indexPath)
   631                 }
   632             } else {
   633                 tableView.deselectRow(at: indexPath, animated: true)
   634             }
   635         }
   636     }
   637 
   638     override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
   639         if tableView.isEditing, let vm = model {
   640             if let selectedIndexPaths = tableView.indexPathsForSelectedRows {
   641                 vm.updatedItems(indexPaths: selectedIndexPaths)
   642             } else {
   643                 vm.updatedItems(indexPaths: [])
   644             }
   645         }
   646     }
   647 
   648     // Implemented to get informed about the scrolling position.
   649     // If the user has scrolled down (almost) to the end, we need to get older emails to display.
   650     override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
   651                             forRowAt indexPath: IndexPath) {
   652         guard let vm = model else {
   653             Log.shared.errorAndCrash("No model.")
   654             return
   655         }
   656         vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
   657     }
   658 
   659     // MARK: - SwipeTableViewCellDelegate
   660 
   661     func configure(action: SwipeAction, with descriptor: SwipeActionDescriptor) {
   662         action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
   663         action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
   664 
   665         switch buttonStyle {
   666         case .backgroundColor:
   667             action.backgroundColor = descriptor.color
   668         case .circular:
   669             action.backgroundColor = .clear
   670             action.textColor = descriptor.color
   671             action.font = .systemFont(ofSize: 13)
   672             action.transitionDelegate = ScaleTransition.default
   673         }
   674     }
   675 
   676     // MARK: - Manipulating the (master) bottom toolbar
   677 
   678     /// With this tag we recognize the pEp button item, for easy removal later.
   679     let pEpButtonItemTag = 7
   680 
   681     /// With this tag we recognize our own created flexible space buttons, for easy removal later.
   682     let flexibleSpaceButtonItemTag = 77
   683 
   684     /// True if the pEp button on the left/master side should be shown.
   685     var shouldShowPepButtonInMasterToolbar = true
   686 
   687     /// Our own factory method for creating pEp bar button items,
   688     /// tagged so we recognize them later, for easy removal.
   689     private func createPepBarButtonItem() -> UIBarButtonItem {
   690         let item = UIBarButtonItem.getPEPButton(
   691             action: #selector(showSettingsViewController),
   692             target: self)
   693         item.tag = pEpButtonItemTag
   694         return item
   695     }
   696 
   697     /// Our own factory method for creating flexible space bar button items,
   698     /// tagged so we recognize them later, for easy removal.
   699     private func createFlexibleBarButtonItem() -> UIBarButtonItem {
   700         let item = UIBarButtonItem(
   701             barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
   702             target: nil,
   703             action: nil)
   704         item.tag = flexibleSpaceButtonItemTag
   705         return item
   706     }
   707 
   708     /// - Returns: A new array of `UIBarButtonItem`s with trailing flexible whitespace
   709     /// removed (at least the ones created by our own factory method).
   710     /// - Parameter barButtonItems: The bar button items to remove from.
   711     private func trailingFlexibleSpaceRemoved(barButtonItems: [UIBarButtonItem]) -> [UIBarButtonItem] {
   712         var theItems = barButtonItems
   713         while true {
   714             if let lastItem = theItems.last {
   715                 if lastItem.tag == flexibleSpaceButtonItemTag {
   716                     theItems.removeLast()
   717                 } else {
   718                     break
   719                 }
   720             } else {
   721                 break
   722             }
   723         }
   724         return theItems
   725     }
   726 
   727     /// Shows the pEp logo (leading to the settings) in the master view bottom toolbar,
   728     /// or not, depending on `show`.
   729     private func showLogoInMasterToolbar(show: Bool) {
   730         // persist this state
   731         shouldShowPepButtonInMasterToolbar = show
   732 
   733         if show {
   734             if var barItems = toolbarItems {
   735                 if let lastItem = barItems.last, lastItem.tag == pEpButtonItemTag {
   736                     // already there
   737                     return
   738                 } else {
   739                     barItems.append(contentsOf: [createFlexibleBarButtonItem(),
   740                                                  createPepBarButtonItem()])
   741                 }
   742                 toolbarItems = barItems
   743             } else {
   744                 toolbarItems = [createPepBarButtonItem()]
   745             }
   746         } else {
   747             if var barItems = toolbarItems {
   748                 if let lastItem = barItems.last, lastItem.tag == pEpButtonItemTag {
   749                     barItems.removeLast()
   750                 }
   751                 toolbarItems = trailingFlexibleSpaceRemoved(barButtonItems: barItems)
   752             }
   753         }
   754     }
   755 
   756     /// Tries to deduce from the split view arrangement whether to show the
   757     /// master toolbar pEp logo or not.
   758     private func checkSplitViewState() {
   759         if let spvc = splitViewController {
   760             if spvc.viewControllers.count == 1 {
   761                 // only master is shown
   762                 showLogoInMasterToolbar(show: true)
   763             } else if spvc.viewControllers.count == 2 {
   764                 // detail is shown, check further
   765                 var showMasterLogo = true
   766                 if let vc = spvc.viewControllers[safe: 1] {
   767                     if vc is NothingSelectedViewController {
   768                         showMasterLogo = true
   769                     } else {
   770                         showMasterLogo = false
   771                     }
   772                 }
   773                 showLogoInMasterToolbar(show: showMasterLogo)
   774             }
   775         }
   776     }
   777 
   778     // MARK: - Observing the split view controller
   779 
   780     /// The key path for observing the view controllers of the split view controller,
   781     /// compatible with Objective-C.
   782     private let splitViewObserverKeyPath = #keyPath(UISplitViewController.viewControllers)
   783 
   784     /// With KVO we have to keep our books lest not to remove an observer without
   785     /// observing first.
   786     private var observingSplitViewControllers = false
   787 
   788     /// Start observing the view controllers in the split view.
   789     private func watchDetailView() {
   790         if !observingSplitViewControllers, let spvc = splitViewController {
   791             spvc.addObserver(self,
   792                              forKeyPath: splitViewObserverKeyPath,
   793                              options: [],
   794                              context: nil)
   795             observingSplitViewControllers = true
   796         }
   797     }
   798 
   799     /// Stop listening for changes in the view controllers in the split view.
   800     private func unwatchDetailView() {
   801         if observingSplitViewControllers, let spvc = splitViewController {
   802             spvc.removeObserver(self, forKeyPath: splitViewObserverKeyPath)
   803             observingSplitViewControllers = false
   804         }
   805     }
   806 
   807     /// React to changes to the view controllers of our split view controller.
   808     override func observeValue(forKeyPath keyPath: String?,
   809                                of object: Any?,
   810                                change: [NSKeyValueChangeKey : Any]?,
   811                                context: UnsafeMutableRawPointer?) {
   812         if keyPath != splitViewObserverKeyPath {
   813             super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
   814             return
   815         }
   816 
   817         checkSplitViewState()
   818     }
   819 
   820     // MARK: -
   821 
   822     override func didReceiveMemoryWarning() {
   823         model?.freeMemory()
   824     }
   825 }
   826 
   827 // MARK: - UISearchResultsUpdating, UISearchControllerDelegate
   828 
   829 extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
   830 
   831     public func updateSearchResults(for searchController: UISearchController) {
   832         guard
   833             let vm = model,
   834             let searchText = searchController.searchBar.text
   835             else {
   836                 return
   837         }
   838         vm.setSearch(forSearchText: searchText)
   839     }
   840 
   841     func didDismissSearchController(_ searchController: UISearchController) {
   842         guard let vm = model else {
   843             Log.shared.errorAndCrash("No chance to remove filter, sorry.")
   844             return
   845         }
   846         vm.removeSearch()
   847     }
   848 }
   849 
   850 // MARK: - EmailListViewModelDelegate
   851 
   852 extension EmailListViewController: EmailListViewModelDelegate {
   853 
   854     func checkIfSplitNeedsUpdate(indexpath: [IndexPath]) {
   855         guard let last = lastSelectedIndexPath else {
   856             return
   857         }
   858         if !onlySplitViewMasterIsShown && indexpath.contains(last) {
   859             showEmail(forCellAt: last)
   860         }
   861     }
   862 
   863     func reloadData(viewModel: EmailListViewModel) {
   864         tableView.reloadData()
   865     }
   866 
   867     func willReceiveUpdates(viewModel: EmailListViewModel) {
   868         tableView.beginUpdates()
   869     }
   870 
   871     func allUpdatesReceived(viewModel: EmailListViewModel) {
   872         tableView.endUpdates()
   873     }
   874 
   875 //    func showThreadView(for indexPath: IndexPath) {
   876        /* guard let splitViewController = splitViewController else {
   877             return
   878         }
   879 
   880         let storyboard = UIStoryboard(name: "Thread", bundle: nil)
   881         if splitViewController.isCollapsed {
   882             guard let message = model?.message(representedByRowAt: indexPath),
   883                 let folder = folderToShow,
   884                 let nav = navigationController,
   885                 let vc: ThreadViewController =
   886                 storyboard.instantiateViewController(withIdentifier: "threadViewController")
   887                     as? ThreadViewController
   888                 else {
   889                     Log.shared.errorAndCrash("Segue issue")
   890                     return
   891             }
   892 
   893             vc.appConfig = appConfig
   894             let viewModel = ThreadedEmailViewModel(tip:message, folder: folder)
   895             viewModel.emailDisplayDelegate = model
   896             vc.model = viewModel
   897             model?.currentDisplayedMessage = viewModel
   898             model?.updateThreadListDelegate = viewModel
   899             nav.viewControllers[nav.viewControllers.count - 1] = vc
   900         } else {
   901             showEmail(forCellAt: indexPath)
   902         }*/
   903 //    }
   904 
   905     func toolbarIs(enabled: Bool) {
   906         if model?.shouldShowToolbarEditButtons() ?? true {
   907             // Never enable those for outbox
   908             flagToolbarButton?.isEnabled = enabled
   909             unflagToolbarButton?.isEnabled = enabled
   910             readToolbarButton?.isEnabled = enabled
   911             unreadToolbarButton?.isEnabled = enabled
   912             moveToolbarButton?.isEnabled = enabled
   913         }
   914         deleteToolbarButton?.isEnabled = enabled
   915     }
   916 
   917     func showUnflagButton(enabled: Bool) {
   918         if enabled {
   919 
   920             if let button = unflagToolbarButton {
   921                 toolbarItems?.remove(at: 0)
   922                 toolbarItems?.insert(button, at: 0)
   923             }
   924 
   925         } else {
   926             if let button = flagToolbarButton {
   927                 toolbarItems?.remove(at: 0)
   928                 toolbarItems?.insert(button, at: 0)
   929             }
   930         }
   931     }
   932 
   933     func showUnreadButton(enabled: Bool) {
   934         if enabled {
   935             if let button = unreadToolbarButton {
   936                 toolbarItems?.remove(at: 2)
   937                 toolbarItems?.insert(button, at: 2)
   938             }
   939         } else {
   940             if let button = readToolbarButton {
   941                 toolbarItems?.remove(at: 2)
   942                 toolbarItems?.insert(button, at: 2)
   943             }
   944         }
   945     }
   946 
   947     func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPaths: [IndexPath]) {
   948         lastSelectedIndexPath = nil
   949         tableView.insertRows(at: indexPaths, with: .automatic)
   950     }
   951 
   952     func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPaths: [IndexPath]) {
   953         lastSelectedIndexPath = tableView.indexPathForSelectedRow ?? lastSelectedIndexPath
   954 
   955         if let swipeDelete = self.swipeDelete {
   956             swipeDelete.fulfill(with: .delete)
   957             self.swipeDelete = nil
   958         } else {
   959             tableView.deleteRows(at: indexPaths, with: .automatic)
   960         }
   961         if let lastSelectedIndexPath = lastSelectedIndexPath,
   962             indexPaths.contains(lastSelectedIndexPath) {
   963             showNoMessageSelectedIfNeeded()
   964         }
   965     }
   966     func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPaths: [IndexPath]) {
   967         lastSelectedIndexPath = tableView.indexPathForSelectedRow
   968         tableView.reloadRows(at: indexPaths, with: .none)
   969     }
   970 
   971     func emailListViewModel(viewModel: EmailListViewModel, didMoveData atIndexPath: IndexPath, toIndexPath: IndexPath) {
   972         lastSelectedIndexPath = tableView.indexPathForSelectedRow
   973         tableView.moveRow(at: atIndexPath, to: toIndexPath)
   974         moveSelectionIfNeeded(fromIndexPath: atIndexPath, toIndexPath: toIndexPath)
   975     }
   976 
   977     func emailListViewModel(viewModel: EmailListViewModel,
   978                             didChangeSeenStateForDataAt indexPaths: [IndexPath]) {
   979         guard let vm = model else {
   980             Log.shared.errorAndCrash("Invalid state")
   981             return
   982         }
   983 
   984         let unreadFilterActive = vm.unreadFilterEnabled()
   985 
   986         // If unread filter is active, /seen state updates require special handling ...
   987 
   988         if !onlySplitViewMasterIsShown && unreadFilterActive {
   989             // We do not update the seen status when both spitview views are shown and the list is
   990             // currently filtered by unread.
   991             return
   992         } else if onlySplitViewMasterIsShown && unreadFilterActive {
   993             vm.informDelegateToReloadData()
   994         } else {
   995             //  ... otherwize we forward to update
   996             emailListViewModel(viewModel: viewModel, didUpdateDataAt: indexPaths)
   997         }
   998     }
   999 
  1000     func updateView() {
  1001         tableView.dataSource = self
  1002         tableView.reloadData()
  1003         showNoMessageSelectedIfNeeded()
  1004     }
  1005 }
  1006 
  1007 // MARK: - ActionSheet & ActionSheet Actions
  1008 
  1009 extension EmailListViewController {
  1010     func showMoreActionSheet(forRowAt indexPath: IndexPath) {
  1011         lastSelectedIndexPath = indexPath
  1012         let alertControler = UIAlertController.pEpAlertController(
  1013             title: nil, message: nil, preferredStyle: .actionSheet)
  1014         let cancelAction = createCancelAction()
  1015         let replyAction = createReplyAction()
  1016 
  1017         let replyAllAction = createReplyAllAction(forRowAt: indexPath)
  1018         let readAction = createReadOrUnReadAction(forRowAt: indexPath)
  1019 
  1020         let forwardAction = createForwardAction()
  1021         let moveToFolderAction = createMoveToFolderAction()
  1022 
  1023         alertControler.addAction(cancelAction)
  1024         alertControler.addAction(replyAction)
  1025 
  1026         if let theReplyAllAction = replyAllAction {
  1027             alertControler.addAction(theReplyAllAction)
  1028         }
  1029 
  1030         alertControler.addAction(forwardAction)
  1031         alertControler.addAction(moveToFolderAction)
  1032         alertControler.addAction(readAction)
  1033 
  1034         if let popoverPresentationController = alertControler.popoverPresentationController {
  1035             popoverPresentationController.sourceView = tableView
  1036             let cellFrame = tableView.rectForRow(at: indexPath)
  1037             let sourceRect = view.convert(cellFrame, from: tableView)
  1038             popoverPresentationController.sourceRect = sourceRect
  1039 
  1040         }
  1041         present(alertControler, animated: true, completion: nil)
  1042     }
  1043 
  1044     // MARK: Action Sheet Actions
  1045 
  1046     private func createMoveToFolderAction() -> UIAlertAction {
  1047         let title = NSLocalizedString("Move to Folder", comment: "EmailList action title")
  1048         return UIAlertAction(title: title, style: .default) { [weak self] action in
  1049             guard let me = self else {
  1050                 Log.shared.errorAndCrash("Lost MySelf")
  1051                 return
  1052             }
  1053             me.performSegue(withIdentifier: .segueShowMoveToFolder, sender: me)
  1054         }
  1055     }
  1056 
  1057     private func createReadOrUnReadAction(forRowAt indexPath: IndexPath) -> UIAlertAction {
  1058         let seenState = model?.viewModel(for: indexPath.row)?.isSeen ?? false
  1059 
  1060         var title = ""
  1061         if seenState {
  1062             title = NSLocalizedString("Mark as unread", comment: "EmailList action title")
  1063         } else {
  1064             title = NSLocalizedString("Mark as Read", comment: "EmailList action title")
  1065         }
  1066 
  1067         return UIAlertAction(title: title, style: .default) { [weak self] action in
  1068             guard let me = self else {
  1069                 Log.shared.errorAndCrash("Lost MySelf")
  1070                 return
  1071             }
  1072             guard let cell = me.tableView.cellForRow(at: indexPath) as? EmailListViewCell else {
  1073                 Log.shared.errorAndCrash(message: "Cell type is wrong")
  1074                 return
  1075             }
  1076             cell.isSeen = !seenState
  1077             if seenState {
  1078                 me.model?.markSelectedAsUnread(indexPaths: [indexPath])
  1079             } else {
  1080                 me.model?.markSelectedAsRead(indexPaths: [indexPath])
  1081             }
  1082         }
  1083     }
  1084 
  1085     func createCancelAction() -> UIAlertAction {
  1086         let title = NSLocalizedString("Cancel", comment: "EmailList action title")
  1087         return  UIAlertAction(title: title, style: .cancel) {
  1088             [weak self] action in
  1089             guard let me = self else {
  1090                 Log.shared.errorAndCrash("Lost MySelf")
  1091                 return
  1092             }
  1093             me.tableView.beginUpdates()
  1094             me.tableView.setEditing(false, animated: true)
  1095             me.tableView.endUpdates()
  1096         }
  1097     }
  1098 
  1099     func createReplyAction() ->  UIAlertAction {
  1100         let title = NSLocalizedString("Reply", comment: "EmailList action title")
  1101         return UIAlertAction(title: title, style: .default) {
  1102             [weak self] action in
  1103             guard let me = self else {
  1104                 Log.shared.errorAndCrash("Lost MySelf")
  1105                 return
  1106             }
  1107             me.performSegue(withIdentifier: .segueReply, sender: me)
  1108         }
  1109     }
  1110 
  1111     func createReplyAllAction(forRowAt indexPath: IndexPath) ->  UIAlertAction? {
  1112         if (model?.isReplyAllPossible(forRowAt: indexPath) ?? false) {
  1113             let title = NSLocalizedString("Reply All", comment: "EmailList action title")
  1114             return UIAlertAction(title: title, style: .default) {
  1115                 [weak self] action in
  1116                 guard let me = self else {
  1117                     Log.shared.errorAndCrash("Lost MySelf")
  1118                     return
  1119                 }
  1120                 me.performSegue(withIdentifier: .segueReplyAll, sender: me)
  1121             }
  1122         } else {
  1123             return nil
  1124         }
  1125     }
  1126 
  1127     func createForwardAction() -> UIAlertAction {
  1128         let title = NSLocalizedString("Forward", comment: "EmailList action title")
  1129         return UIAlertAction(title: title, style: .default) {
  1130             [weak self] action in
  1131             guard let me = self else {
  1132                 Log.shared.errorAndCrash("Lost MySelf")
  1133                 return
  1134             }
  1135             me.performSegue(withIdentifier: .segueForward, sender: me)
  1136         }
  1137     }
  1138 }
  1139 
  1140 // MARK: - TableViewCell Actions
  1141 
  1142 extension EmailListViewController {
  1143     private func createRowAction(image: UIImage?,
  1144                                  action: @escaping (UITableViewRowAction, IndexPath) -> Void
  1145         ) -> UITableViewRowAction {
  1146         let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
  1147         if let theImage = image {
  1148             let iconColor = UIColor(patternImage: theImage)
  1149             rowAction.backgroundColor = iconColor
  1150         }
  1151         return rowAction
  1152     }
  1153 
  1154     func flagAction(forCellAt indexPath: IndexPath) {
  1155         guard let row = model?.viewModel(for: indexPath.row) else {
  1156             Log.shared.errorAndCrash("No data for indexPath!")
  1157             return
  1158         }
  1159         guard let cell = self.tableView.cellForRow(at: indexPath) as? EmailListViewCell else {
  1160             Log.shared.errorAndCrash("No cell for indexPath!")
  1161             return
  1162         }
  1163         if row.isFlagged {
  1164             model?.unsetFlagged(forIndexPath: [indexPath])
  1165             cell.isFlagged = false
  1166         } else {
  1167             model?.setFlagged(forIndexPath: [indexPath])
  1168             cell.isFlagged = true
  1169         }
  1170     }
  1171 
  1172     func deleteAction(forCellAt indexPath: IndexPath) {
  1173         model?.delete(forIndexPath: indexPath)
  1174     }
  1175 
  1176     func moreAction(forCellAt indexPath: IndexPath) {
  1177         showMoreActionSheet(forRowAt: indexPath)
  1178     }
  1179 }
  1180 
  1181 // MARK: - Segue handling
  1182 
  1183 extension EmailListViewController {
  1184     /**
  1185      Enables manual account setup to unwind to the unified inbox.
  1186      */
  1187     @IBAction func segueUnwindAfterAccountCreation(segue:UIStoryboardSegue) {
  1188         setup()
  1189     }
  1190 }
  1191 
  1192 // MARK: - SegueHandlerType
  1193 
  1194 extension EmailListViewController: SegueHandlerType {
  1195     
  1196     enum SegueIdentifier: String {
  1197         case segueAddNewAccount
  1198         case segueShowEmailSplitView
  1199         case segueShowEmailNotSplitView
  1200         case segueCompose
  1201         case segueReply
  1202         case segueReplyAll
  1203         case segueForward
  1204         case segueEditDraft
  1205         case segueShowFilter
  1206         case segueFolderViews
  1207         case segueShowMoveToFolder
  1208         case segueShowThreadedEmail
  1209         case noSegue
  1210     }
  1211     
  1212     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  1213         let segueId = segueIdentifier(for: segue)
  1214         switch segueId {
  1215         case .segueReply,
  1216              .segueReplyAll,
  1217              .segueForward,
  1218              .segueCompose,
  1219              .segueEditDraft:
  1220             setupComposeViewController(for: segue)
  1221         case .segueShowEmailSplitView:
  1222             guard let nav = segue.destination as? UINavigationController,
  1223                 let vc = nav.rootViewController as? EmailViewController,
  1224                 let indexPath = lastSelectedIndexPath,
  1225                 let message = model?.message(representedByRowAt: indexPath) else {
  1226                     Log.shared.errorAndCrash("Segue issue")
  1227                     return
  1228             }
  1229             vc.appConfig = appConfig
  1230             vc.message = message
  1231             ///This is commented as we "disabled" the feature in the message of
  1232             ///showing next and previous directly from the emailView, that is needed for that feature
  1233             //vc.folderShow = model?.getFolderToShow()
  1234             vc.messageId = indexPath.row //!!!: that looks wrong
  1235             model?.indexPathShown = indexPath
  1236         case .segueShowEmailNotSplitView:
  1237             guard let vc = segue.destination as? EmailViewController,
  1238                 let indexPath = lastSelectedIndexPath,
  1239                 let message = model?.message(representedByRowAt: indexPath) else {
  1240                     Log.shared.errorAndCrash("Segue issue")
  1241                     return
  1242             }
  1243             vc.appConfig = appConfig
  1244             vc.message = message
  1245             ///This is commented as we "disabled" the feature in the message of
  1246             ///showing next and previous directly from the emailView, that is needed for that feature
  1247             //vc.folderShow = model?.getFolderToShow()
  1248             vc.messageId = indexPath.row //!!!: that looks wrong
  1249             model?.indexPathShown = indexPath
  1250 
  1251       //  case .segueShowThreadedEmail:
  1252         /*    guard let nav = segue.destination as? UINavigationController,
  1253                 let vc = nav.rootViewController as? ThreadViewController,
  1254                 let indexPath = lastSelectedIndexPath,
  1255                 let folder = folderToShow else {
  1256                     return
  1257             }
  1258             guard let message = model?.message(representedByRowAt: indexPath) else {
  1259                 Log.shared.errorAndCrash("Segue issue")
  1260                 return
  1261             }
  1262             vc.appConfig = appConfig
  1263             let viewModel = ThreadedEmailViewModel(tip:message, folder: folder)
  1264             viewModel.emailDisplayDelegate = model
  1265             vc.model = viewModel
  1266             model?.currentDisplayedMessage = viewModel
  1267             model?.updateThreadListDelegate = viewModel*/
  1268         case .segueShowFilter:
  1269             guard let destiny = segue.destination as? FilterTableViewController  else {
  1270                 Log.shared.errorAndCrash("Segue issue")
  1271                 return
  1272             }
  1273             guard let vm = model else {
  1274                 Log.shared.errorAndCrash("No VM")
  1275                 return
  1276             }
  1277             destiny.appConfig = appConfig
  1278             destiny.filterDelegate = vm
  1279             destiny.filterEnabled = vm.currentFilter
  1280             destiny.hidesBottomBarWhenPushed = true
  1281         case .segueAddNewAccount:
  1282             guard
  1283                 let nav = segue.destination as? UINavigationController,
  1284                 let vc = nav.rootViewController as? LoginViewController else {
  1285                     Log.shared.errorAndCrash("Segue issue")
  1286                     return
  1287             }
  1288             vc.appConfig = appConfig
  1289             vc.delegate = self
  1290             vc.hidesBottomBarWhenPushed = true
  1291             break
  1292         case .segueFolderViews:
  1293             guard let vC = segue.destination as? FolderTableViewController  else {
  1294                 Log.shared.errorAndCrash("Segue issue")
  1295                 return
  1296             }
  1297             vC.appConfig = appConfig
  1298             break
  1299         case .segueShowMoveToFolder:
  1300             var selectedRows: [IndexPath] = []
  1301 
  1302             if let selectedItems = tableView.indexPathsForSelectedRows {
  1303                 selectedRows = selectedItems
  1304             } else if let last = lastSelectedIndexPath {
  1305                 selectedRows.append(last)
  1306             }
  1307 
  1308             guard  let nav = segue.destination as? UINavigationController,
  1309                 let destination = nav.topViewController as? MoveToAccountViewController
  1310                 else {
  1311                     Log.shared.errorAndCrash("No DVC?")
  1312                     break
  1313             }
  1314 
  1315             destination.viewModel
  1316                 = model?.getMoveToFolderViewModel(forSelectedMessages: selectedRows)
  1317             destination.appConfig = appConfig
  1318             break
  1319         default:
  1320             Log.shared.errorAndCrash("Unhandled segue")
  1321             break
  1322         }
  1323     }
  1324 
  1325     @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
  1326         // nothing to do.
  1327     }
  1328 
  1329     private func setupComposeViewController(for segue: UIStoryboardSegue) {
  1330         let segueId = segueIdentifier(for: segue)
  1331         guard
  1332             let nav = segue.destination as? UINavigationController,
  1333             let composeVc = nav.topViewController as? ComposeTableViewController,
  1334             let composeMode = composeMode(for: segueId),
  1335             let vm = model else {
  1336                 Log.shared.errorAndCrash("composeViewController setup issue")
  1337                 return
  1338         }
  1339         composeVc.appConfig = appConfig
  1340 
  1341         if segueId != .segueCompose {
  1342             // This is not a simple compose (but reply, forward or such),
  1343             // thus we have to pass the original message.
  1344             guard let indexPath = lastSelectedIndexPath else {
  1345                     Log.shared.info("Can happen if the message the user wanted to reply to has been deleted in between performeSeque and here")
  1346                     return
  1347             }
  1348 
  1349             composeVc.viewModel = vm.composeViewModel(withOriginalMessageAt: indexPath,
  1350                                                       composeMode: composeMode)
  1351         } else {
  1352             composeVc.viewModel = vm.composeViewModelForNewMessage()
  1353         }
  1354     }
  1355 
  1356     private func composeMode(for segueId: SegueIdentifier) -> ComposeUtil.ComposeMode? {
  1357         switch segueId {
  1358         case .segueReply:
  1359             return .replyFrom
  1360         case .segueReplyAll:
  1361             return .replyAll
  1362         case .segueForward:
  1363             return .forward
  1364         case .segueCompose:
  1365             return .normal
  1366         case .segueEditDraft:
  1367             return .normal
  1368         default:
  1369             return nil
  1370         }
  1371     }
  1372 }
  1373 
  1374 // MARK: - LoginViewControllerDelegate
  1375 
  1376 extension EmailListViewController: LoginViewControllerDelegate {
  1377     func loginViewControllerDidCreateNewAccount(_ loginViewController: LoginViewController) {
  1378         // Setup model after initial account setup
  1379         setup()
  1380     }
  1381 }
  1382