pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author buff <andreas@pep-project.org>
Wed, 09 Aug 2017 19:14:25 +0200
branchIOS-615-duplicated-first-mail
changeset 2687 bb1dcce7a0d6
parent 2683 ef2249f630b0
child 2697 a9130d482e40
permissions -rw-r--r--
IOS-615 cleanup
     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 Foundation
    10 import UIKit
    11 import CoreData
    12 import MessageModel
    13 
    14 struct EmailListConfig {
    15     var appConfig: AppConfig?
    16 
    17     /** The folder to display, if it exists */
    18     var folder: Folder?
    19 
    20     let imageProvider = IdentityImageProvider()
    21 }
    22 
    23 class EmailListViewController: UITableViewController {
    24     struct UIState {
    25         var isSynching: Bool = false
    26     }
    27 
    28     var config: EmailListConfig?
    29     var viewModel: EmailListViewModel?
    30     var state = UIState()
    31     let searchController = UISearchController(searchResultsController: nil)
    32     //let cellsInUse = NSCache<NSString, EmailListViewCell>()
    33 
    34     /**
    35      After trustwords have been invoked, this will be the partner identity that
    36      was either confirmed or mistrusted.
    37      */
    38     var partnerIdentity: Identity?
    39 
    40     @IBOutlet weak var enableFilterButton: UIBarButtonItem!
    41     @IBOutlet weak var textFilterButton: UIBarButtonItem!
    42 
    43     @IBOutlet var showFoldersButton: UIBarButtonItem!
    44     //private var filterEnabled = false
    45 
    46     override func viewDidLoad() {
    47         super.viewDidLoad()
    48 
    49         title = NSLocalizedString("Inbox", comment: "General name for (unified) inbox")
    50         UIHelper.emailListTableHeight(self.tableView)
    51         addSearchBar()
    52     }
    53 
    54     override func viewWillAppear(_ animated: Bool) {
    55         super.viewWillAppear(animated)
    56         self.navigationController?.setToolbarHidden(false, animated: true)
    57         if MiscUtil.isUnitTest() {
    58             return
    59         }
    60 
    61         if let vm = viewModel {
    62             self.textFilterButton.isEnabled = vm.filterEnabled
    63         } else {
    64             self.textFilterButton.isEnabled = false
    65         }
    66 
    67         setDefaultColors()
    68         setupConfig()
    69         updateModel()
    70 
    71         // Mark this folder as having been looked at by the user
    72         if let folder = config?.folder {
    73             updateLastLookAt(on: folder)
    74         }
    75         if viewModel == nil {
    76             viewModel = EmailListViewModel(config: config, delegate: self)
    77         }
    78         MessageModelConfig.messageFolderDelegate = self
    79 
    80         if let size = navigationController?.viewControllers.count, size > 1 {
    81             self.showFoldersButton.isEnabled = false
    82         } else {
    83             self.showFoldersButton.isEnabled = true
    84         }
    85         
    86     }
    87 
    88     private func updateLastLookAt(on folder: Folder) {
    89         if folder.isUnified {
    90             folder.updateLastLookAt()
    91         } else {
    92             folder.updateLastLookAtAndSave()
    93         }
    94     }
    95 
    96     override func viewWillDisappear(_ animated: Bool) {
    97         super.viewWillDisappear(animated)
    98         MessageModelConfig.messageFolderDelegate = nil
    99     }
   100 
   101     func setupConfig() {
   102         guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
   103             Log.shared.errorAndCrash(component: #function, errorString: "No AppDelegate?")
   104             return
   105         }
   106         if config == nil {
   107             config = EmailListConfig(appConfig: appDelegate.appConfig,
   108                                      folder: Folder.unifiedInbox())
   109         }
   110 
   111         if Account.all().isEmpty {
   112             performSegue(withIdentifier:.segueAddNewAccount, sender: self)
   113         }
   114 
   115         self.title = config?.folder?.realName
   116     }
   117 
   118     func addSearchBar() {
   119         searchController.searchResultsUpdater = self
   120         searchController.dimsBackgroundDuringPresentation = false
   121         searchController.delegate = self
   122         definesPresentationContext = true
   123         tableView.tableHeaderView = searchController.searchBar
   124         tableView.setContentOffset(CGPoint(x: 0.0, y: 40.0), animated: false)
   125     }
   126 
   127     func updateModel() {
   128         tableView.reloadData()
   129     }
   130 
   131 
   132     @IBAction func showUnreadButtonTapped(_ sender: UIBarButtonItem) {
   133         if let vm = viewModel {
   134             if vm.filterEnabled {
   135                 vm.filterEnabled = false
   136                 textFilterButton.title = ""
   137                 enableFilterButton.image = UIImage(named: "unread-icon")
   138                 if config != nil {
   139                     vm.resetFilters()
   140                 }
   141             } else {
   142                 vm.filterEnabled = true
   143                 textFilterButton.title = "Filter by: unread"
   144                 enableFilterButton.image = UIImage(named: "unread-icon-active")
   145                 if config != nil {
   146                     vm.updateFilter(filter: Filter.unread())
   147                 }
   148             }
   149             self.textFilterButton.isEnabled = vm.filterEnabled
   150         }
   151 
   152 
   153     }
   154 
   155     // MARK: - UI State
   156 
   157     func updateUI() {
   158         UIApplication.shared.isNetworkActivityIndicatorVisible = state.isSynching
   159         if !state.isSynching {
   160             refreshControl?.endRefreshing()
   161         }
   162     }
   163 
   164     // MARK: - UITableViewDataSource
   165 
   166     override func numberOfSections(in tableView: UITableView) -> Int {
   167         if let _ = viewModel?.folderToShow {
   168             return 1
   169         }
   170         return 0
   171     }
   172 
   173     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   174         if let vm = viewModel {
   175             return vm.count
   176         }
   177         return 0
   178     }
   179 
   180     override func tableView(_ tableView: UITableView,
   181                             cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   182         let cell = tableView.dequeueReusableCell(
   183             withIdentifier: "EmailListViewCell", for: indexPath) as! EmailListViewCell
   184         //mantener el configure cell para tal de no generar un vm para celdas
   185         let _ = cell.configureCell(config: config, indexPath: indexPath)
   186         viewModel?.associate(cell: cell, position: indexPath.row)
   187         return cell
   188     }
   189 
   190     // MARK: - UITableViewDelegate
   191 
   192     override func tableView(_ tableView: UITableView, editActionsForRowAt
   193         indexPath: IndexPath)-> [UITableViewRowAction]? {
   194 
   195         let cell = tableView.cellForRow(at: indexPath) as! EmailListViewCell
   196         if let email = cell.messageAt(indexPath: indexPath, config: config) {
   197             let flagAction = createFlagAction(message: email, cell: cell)
   198             let deleteAction = createDeleteAction(message: email, cell: cell)
   199             let moreAction = createMoreAction(message: email, cell: cell)
   200             return [deleteAction, flagAction, moreAction]
   201         }
   202         return nil
   203     }
   204 
   205     // MARK: - Misc
   206 
   207     func createRowAction(cell: EmailListViewCell,
   208                          image: UIImage?, action: @escaping (UITableViewRowAction, IndexPath) -> Void,
   209                          title: String) -> UITableViewRowAction {
   210         let rowAction = UITableViewRowAction(
   211             style: .normal, title: title, handler: action)
   212 
   213         if let theImage = image {
   214             let iconColor = UIColor(patternImage: theImage)
   215             rowAction.backgroundColor = iconColor
   216         }
   217 
   218         return rowAction
   219     }
   220 
   221     func createFlagAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   222         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   223             if message.imapFlags == nil {
   224                 Log.warn(component: #function, content: "message.imapFlags == nil")
   225             }
   226             if cell.isFlagged(message: message) {
   227                 message.imapFlags?.flagged = false
   228             } else {
   229                 message.imapFlags?.flagged = true
   230             }
   231             message.save()
   232             self.tableView.reloadRows(at: [indexPath], with: .none)
   233         }
   234 
   235         let flagString = NSLocalizedString("Flag", comment: "Message action (on swipe)")
   236         var title = "\n\n\(flagString)"
   237         let unflagString = NSLocalizedString("Unflag", comment: "Message action (on swipe)")
   238         if message.imapFlags?.flagged ?? true {
   239             title = "\n\n\(unflagString)"
   240         }
   241 
   242         return createRowAction(
   243             cell: cell, image: UIImage(named: "swipe-flag"), action: action, title: title)
   244     }
   245 
   246     func createDeleteAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   247         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   248             guard let message = cell.messageAt(indexPath: indexPath, config: self.config) else {
   249                 return
   250             }
   251 
   252             message.delete() // mark for deletion/trash
   253             message.save()
   254             self.tableView.reloadData()
   255         }
   256 
   257         let title = NSLocalizedString("Delete", comment: "Message action (on swipe)")
   258         return createRowAction(
   259             cell: cell, image: UIImage(named: "swipe-trash"), action: action,
   260             title: "\n\n\(title)")
   261     }
   262 
   263     func createMarkAsReadAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   264         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   265             if cell.haveSeen(message: message) {
   266                 message.imapFlags?.seen = false
   267             } else {
   268                 message.imapFlags?.seen = true
   269             }
   270             self.tableView.reloadRows(at: [indexPath], with: .none)
   271         }
   272 
   273         var title = NSLocalizedString(
   274             "Unread", comment: "Message action (on swipe)")
   275         if !cell.haveSeen(message: message) {
   276             title = NSLocalizedString(
   277                 "Read", comment: "Message action (on swipe)")
   278         }
   279 
   280         let isReadAction = createRowAction(cell: cell, image: nil, action: action,
   281                                            title: title)
   282         isReadAction.backgroundColor = UIColor.blue
   283 
   284         return isReadAction
   285     }
   286 
   287     func createMoreAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   288         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   289             self.showMoreActionSheet(cell: cell)
   290         }
   291 
   292         let title = NSLocalizedString("More", comment: "Message action (on swipe)")
   293         return createRowAction(
   294             cell: cell, image: UIImage(named: "swipe-more"), action: action,
   295             title: "\n\n\(title)")
   296     }
   297 
   298     // MARK: - Action Sheet
   299 
   300     func showMoreActionSheet(cell: EmailListViewCell) {
   301         let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
   302         alertControler.view.tintColor = .pEpGreen
   303         let cancelAction = createCancelAction()
   304         let replyAction = createReplyAction(cell: cell)
   305         let replyAllAction = createReplyAllAction(cell: cell)
   306         let forwardAction = createForwardAction(cell: cell)
   307         let markAction = createMarkAction()
   308         alertControler.addAction(cancelAction)
   309         alertControler.addAction(replyAction)
   310         alertControler.addAction(replyAllAction)
   311         alertControler.addAction(forwardAction)
   312         alertControler.addAction(markAction)
   313         if let popoverPresentationController = alertControler.popoverPresentationController {
   314             popoverPresentationController.sourceView = cell
   315         }
   316         present(alertControler, animated: true, completion: nil)
   317     }
   318 
   319     // MARK: - Action Sheet Actions
   320 
   321     func createCancelAction() -> UIAlertAction {
   322         return  UIAlertAction(title: "Cancel", style: .cancel) { (action) in}
   323     }
   324 
   325     func createReplyAction(cell: EmailListViewCell) ->  UIAlertAction {
   326         return UIAlertAction(title: "Reply", style: .default) { (action) in
   327             // self.performSegue(withIdentifier: self.segueCompose, sender: cell)
   328             self.performSegue(withIdentifier: .segueCompose, sender: cell)
   329         }
   330     }
   331 
   332     func createReplyAllAction(cell: EmailListViewCell) ->  UIAlertAction {
   333         return UIAlertAction(title: "Reply All", style: .default) { (action) in
   334             self.performSegue(withIdentifier: .segueReplyAll, sender: cell)
   335         }
   336     }
   337 
   338     func createForwardAction(cell: EmailListViewCell) -> UIAlertAction {
   339         return UIAlertAction(title: "Forward", style: .default) { (action) in
   340             self.performSegue(withIdentifier: .segueForward, sender: cell)
   341         }
   342     }
   343 
   344     func createMarkAction() -> UIAlertAction {
   345         return UIAlertAction(title: "Mark", style: .default) { (action) in
   346         }
   347     }
   348 
   349 }
   350 
   351 extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
   352     public func updateSearchResults(for searchController: UISearchController) {
   353         if let vm = viewModel {
   354             vm.filterContentForSearchText(searchText: searchController.searchBar.text!, clear: false)
   355         }
   356     }
   357 
   358     func didDismissSearchController(_ searchController: UISearchController) {
   359         if let vm = viewModel {
   360             vm.filterContentForSearchText(clear: true)
   361         }
   362     }
   363 }
   364 
   365 // MARK: - Navigation
   366 
   367 extension EmailListViewController: SegueHandlerType {
   368 
   369     // MARK: - SegueHandlerType
   370 
   371     enum SegueIdentifier: String {
   372         case segueAddNewAccount
   373         case segueEditAccounts
   374         case segueShowEmail
   375         case segueCompose
   376         case segueReplyAll
   377         case segueForward
   378         case segueFilter
   379         case segueFolderViews
   380         case noSegue
   381     }
   382 
   383     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   384         switch segueIdentifier(for: segue) {
   385         case .segueReplyAll:
   386             if let nav = segue.destination as? UINavigationController,
   387                 let destination = nav.topViewController as? ComposeTableViewController,
   388                 let cell = sender as? EmailListViewCell,
   389                 let indexPath = self.tableView.indexPath(for: cell),
   390                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   391                 destination.composeMode = .replyAll
   392                 destination.appConfig = config?.appConfig
   393                 destination.originalMessage = email
   394             }
   395             break
   396         case .segueShowEmail:
   397             if let vc = segue.destination as? EmailViewController,
   398                 let cell = sender as? EmailListViewCell,
   399                 let indexPath = self.tableView.indexPath(for: cell),
   400                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   401                 vc.appConfig = config?.appConfig
   402                 vc.message = email
   403                 vc.folderShow = viewModel?.folderToShow
   404                 vc.messageId = indexPath.row
   405             }
   406             break
   407         case .segueForward:
   408             if let nav = segue.destination as? UINavigationController,
   409                 let destination = nav.topViewController as? ComposeTableViewController,
   410                 let cell = sender as? EmailListViewCell,
   411                 let indexPath = self.tableView.indexPath(for: cell),
   412                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   413                 destination.composeMode = .forward
   414                 destination.appConfig = config?.appConfig
   415                 destination.originalMessage = email
   416             }
   417             break
   418         case .segueFilter:
   419             if let destiny = segue.destination as? FilterTableViewController {
   420                 destiny.filterDelegate = self.viewModel
   421                 destiny.inFolder = false
   422                 destiny.filterEnabled = self.viewModel?.folderToShow?.filter
   423                 destiny.hidesBottomBarWhenPushed = true
   424             }
   425             break
   426         case .segueAddNewAccount:
   427             if let vc = segue.destination as? LoginTableViewController {
   428                 vc.appConfig = config?.appConfig
   429                 vc.hidesBottomBarWhenPushed = true
   430             }
   431         case .segueFolderViews:
   432             if let vC = segue.destination as? FolderTableViewController {
   433                 vC.appConfig = config?.appConfig
   434                 vC.hidesBottomBarWhenPushed = true
   435             }
   436         case .segueEditAccounts, .segueCompose, .noSegue:
   437             break
   438         }
   439 
   440     }
   441     
   442     @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
   443     }
   444 
   445     func didChangeInternal(messageFolder: MessageFolder) {
   446         if let folder = config?.folder,
   447             let message = messageFolder as? Message,
   448             folder.contains(message: message, deletedMessagesAreContained: true) {
   449             if message.isOriginal {
   450                 // new message has arrived
   451                 if let index = folder.indexOf(message: message) {
   452                     let ip = IndexPath(row: index, section: 0)
   453                     Log.info(
   454                         component: #function,
   455                         content: "insert message at \(index), \(folder.messageCount()) messages")
   456                     tableView.insertRows(at: [ip], with: .automatic)
   457                 } else {
   458                     tableView.reloadData()
   459                 }
   460             } else if message.isGhost {
   461                 if let vm = viewModel, let cell = vm.cellFor(message: message), let ip = tableView.indexPath(for: cell) {
   462                     Log.info(
   463                         component: #function,
   464                         content: "delete message at \(index), \(folder.messageCount()) messages")
   465                     tableView.deleteRows(at: [ip], with: .automatic)
   466                 } else {
   467                     tableView.reloadData()
   468                 }
   469             } else {
   470                 // other flags than delete must have been changed
   471                 if let vm = viewModel, let cell = vm.cellFor(message: message) {
   472                     cell.updateFlags(message: message)
   473                 } else {
   474                     tableView.reloadData()
   475                 }
   476             }
   477         }
   478     }
   479 
   480 }
   481 
   482 // MARK: - MessageFolderDelegate
   483 
   484 extension EmailListViewController: MessageFolderDelegate {
   485     func didChange(messageFolder: MessageFolder) {
   486         GCD.onMainWait {
   487             self.didChangeInternal(messageFolder: messageFolder)
   488         }
   489     }
   490 }
   491 
   492 extension EmailListViewController: tableViewUpdate {
   493     func updateView() {
   494         self.tableView.reloadData()
   495     }
   496 }