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