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