pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author Xavier Algarra <xavier@pep-project.org>
Wed, 16 Aug 2017 13:36:24 +0200
changeset 2733 d11889937bcc
parent 2715 50b73f5ea496
child 2804 8f0caacaa004
permissions -rw-r--r--
IOS-132 Mark action removed
     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         guard let folder = config?.folder else {
   116             return
   117         }
   118         self.title = realName(of: folder)
   119     }
   120 
   121     func addSearchBar() {
   122         searchController.searchResultsUpdater = self
   123         searchController.dimsBackgroundDuringPresentation = false
   124         searchController.delegate = self
   125         definesPresentationContext = true
   126         tableView.tableHeaderView = searchController.searchBar
   127         tableView.setContentOffset(CGPoint(x: 0.0, y: 40.0), animated: false)
   128     }
   129 
   130     func updateModel() {
   131         tableView.reloadData()
   132     }
   133 
   134     @IBAction func showUnreadButtonTapped(_ sender: UIBarButtonItem) {
   135         if let vm = viewModel {
   136             if vm.filterEnabled {
   137                 vm.filterEnabled = false
   138                 textFilterButton.title = ""
   139                 enableFilterButton.image = UIImage(named: "unread-icon")
   140                 if config != nil {
   141                     vm.resetFilters()
   142                 }
   143             } else {
   144                 vm.filterEnabled = true
   145                 textFilterButton.title = "Filter by: unread"
   146                 enableFilterButton.image = UIImage(named: "unread-icon-active")
   147                 if config != nil {
   148                     vm.updateFilter(filter: Filter.unread())
   149                 }
   150             }
   151             self.textFilterButton.isEnabled = vm.filterEnabled
   152         }
   153     }
   154 
   155     // MARK: - Private
   156 
   157     private func realName(of folder: Folder) -> String? {
   158         if folder.isUnified {
   159             return folder.name
   160         } else {
   161             return folder.realName
   162         }
   163     }
   164 
   165     // MARK: - UI State
   166 
   167     func updateUI() {
   168         UIApplication.shared.isNetworkActivityIndicatorVisible = state.isSynching
   169         if !state.isSynching {
   170             refreshControl?.endRefreshing()
   171         }
   172     }
   173 
   174     // MARK: - UITableViewDataSource
   175 
   176     override func numberOfSections(in tableView: UITableView) -> Int {
   177         if let _ = viewModel?.folderToShow {
   178             return 1
   179         }
   180         return 0
   181     }
   182 
   183     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   184         if let vm = viewModel {
   185             return vm.count
   186         }
   187         return 0
   188     }
   189 
   190     override func tableView(_ tableView: UITableView,
   191                             cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   192         let cell = tableView.dequeueReusableCell(
   193             withIdentifier: "EmailListViewCell", for: indexPath) as! EmailListViewCell
   194         //mantener el configure cell para tal de no generar un vm para celdas
   195         let _ = cell.configureCell(config: config, indexPath: indexPath)
   196         viewModel?.associate(cell: cell, position: indexPath.row)
   197         return cell
   198     }
   199 
   200     // MARK: - UITableViewDelegate
   201 
   202     override func tableView(_ tableView: UITableView, editActionsForRowAt
   203         indexPath: IndexPath)-> [UITableViewRowAction]? {
   204 
   205         let cell = tableView.cellForRow(at: indexPath) as! EmailListViewCell
   206         if let email = cell.messageAt(indexPath: indexPath, config: config) {
   207             let flagAction = createFlagAction(message: email, cell: cell)
   208             let deleteAction = createDeleteAction(message: email, cell: cell)
   209             let moreAction = createMoreAction(message: email, cell: cell)
   210             return [deleteAction, flagAction, moreAction]
   211         }
   212         return nil
   213     }
   214 
   215     // MARK: - Misc
   216 
   217     func createRowAction(cell: EmailListViewCell,
   218                          image: UIImage?, action: @escaping (UITableViewRowAction, IndexPath) -> Void,
   219                          title: String) -> UITableViewRowAction {
   220         let rowAction = UITableViewRowAction(
   221             style: .normal, title: title, handler: action)
   222 
   223         if let theImage = image {
   224             let iconColor = UIColor(patternImage: theImage)
   225             rowAction.backgroundColor = iconColor
   226         }
   227 
   228         return rowAction
   229     }
   230 
   231     func createFlagAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   232         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   233             if message.imapFlags == nil {
   234                 Log.warn(component: #function, content: "message.imapFlags == nil")
   235             }
   236             if cell.isFlagged(message: message) {
   237                 message.imapFlags?.flagged = false
   238             } else {
   239                 message.imapFlags?.flagged = true
   240             }
   241             message.save()
   242             self.tableView.reloadRows(at: [indexPath], with: .none)
   243         }
   244 
   245         let flagString = NSLocalizedString("Flag", comment: "Message action (on swipe)")
   246         var title = "\n\n\(flagString)"
   247         let unflagString = NSLocalizedString("Unflag", comment: "Message action (on swipe)")
   248         if message.imapFlags?.flagged ?? true {
   249             title = "\n\n\(unflagString)"
   250         }
   251 
   252         return createRowAction(
   253             cell: cell, image: UIImage(named: "swipe-flag"), action: action, title: title)
   254     }
   255 
   256     func createDeleteAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   257         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   258             guard let message = cell.messageAt(indexPath: indexPath, config: self.config) else {
   259                 return
   260             }
   261 
   262             message.delete() // mark for deletion/trash
   263             message.save()
   264             self.tableView.reloadData()
   265         }
   266 
   267         let title = NSLocalizedString("Delete", comment: "Message action (on swipe)")
   268         return createRowAction(
   269             cell: cell, image: UIImage(named: "swipe-trash"), action: action,
   270             title: "\n\n\(title)")
   271     }
   272 
   273     func createMarkAsReadAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   274         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   275             if cell.haveSeen(message: message) {
   276                 message.imapFlags?.seen = false
   277             } else {
   278                 message.imapFlags?.seen = true
   279             }
   280             self.tableView.reloadRows(at: [indexPath], with: .none)
   281         }
   282 
   283         var title = NSLocalizedString(
   284             "Unread", comment: "Message action (on swipe)")
   285         if !cell.haveSeen(message: message) {
   286             title = NSLocalizedString(
   287                 "Read", comment: "Message action (on swipe)")
   288         }
   289 
   290         let isReadAction = createRowAction(cell: cell, image: nil, action: action,
   291                                            title: title)
   292         isReadAction.backgroundColor = UIColor.blue
   293 
   294         return isReadAction
   295     }
   296 
   297     func createMoreAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
   298         func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
   299             self.showMoreActionSheet(cell: cell)
   300         }
   301 
   302         let title = NSLocalizedString("More", comment: "Message action (on swipe)")
   303         return createRowAction(
   304             cell: cell, image: UIImage(named: "swipe-more"), action: action,
   305             title: "\n\n\(title)")
   306     }
   307 
   308     // MARK: - Action Sheet
   309 
   310     func showMoreActionSheet(cell: EmailListViewCell) {
   311         let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
   312         alertControler.view.tintColor = .pEpGreen
   313         let cancelAction = createCancelAction()
   314         let replyAction = createReplyAction(cell: cell)
   315         let replyAllAction = createReplyAllAction(cell: cell)
   316         let forwardAction = createForwardAction(cell: cell)
   317         alertControler.addAction(cancelAction)
   318         alertControler.addAction(replyAction)
   319         alertControler.addAction(replyAllAction)
   320         alertControler.addAction(forwardAction)
   321         if let popoverPresentationController = alertControler.popoverPresentationController {
   322             popoverPresentationController.sourceView = cell
   323         }
   324         present(alertControler, animated: true, completion: nil)
   325     }
   326 
   327     // MARK: - Action Sheet Actions
   328 
   329     func createCancelAction() -> UIAlertAction {
   330         return  UIAlertAction(title: "Cancel", style: .cancel) { (action) in}
   331     }
   332 
   333     func createReplyAction(cell: EmailListViewCell) ->  UIAlertAction {
   334         return UIAlertAction(title: "Reply", style: .default) { (action) in
   335             // self.performSegue(withIdentifier: self.segueCompose, sender: cell)
   336             self.performSegue(withIdentifier: .segueReply, sender: cell)
   337         }
   338     }
   339 
   340     func createReplyAllAction(cell: EmailListViewCell) ->  UIAlertAction {
   341         return UIAlertAction(title: "Reply All", style: .default) { (action) in
   342             self.performSegue(withIdentifier: .segueReplyAll, sender: cell)
   343         }
   344     }
   345 
   346     func createForwardAction(cell: EmailListViewCell) -> UIAlertAction {
   347         return UIAlertAction(title: "Forward", style: .default) { (action) in
   348             self.performSegue(withIdentifier: .segueForward, sender: cell)
   349         }
   350     }
   351 
   352 }
   353 
   354 extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
   355     public func updateSearchResults(for searchController: UISearchController) {
   356         if let vm = viewModel {
   357             vm.filterContentForSearchText(searchText: searchController.searchBar.text!, clear: false)
   358         }
   359     }
   360 
   361     func didDismissSearchController(_ searchController: UISearchController) {
   362         if let vm = viewModel {
   363             vm.filterContentForSearchText(clear: true)
   364         }
   365     }
   366 }
   367 
   368 // MARK: - Navigation
   369 
   370 extension EmailListViewController: SegueHandlerType {
   371 
   372     // MARK: - SegueHandlerType
   373 
   374     enum SegueIdentifier: String {
   375         case segueAddNewAccount
   376         case segueEditAccounts
   377         case segueShowEmail
   378         case segueCompose
   379         case segueReply
   380         case segueReplyAll
   381         case segueForward
   382         case segueFilter
   383         case segueFolderViews
   384         case noSegue
   385     }
   386 
   387     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
   388         switch segueIdentifier(for: segue) {
   389         case .segueReply:
   390             if let nav = segue.destination as? UINavigationController,
   391                 let destination = nav.topViewController as? ComposeTableViewController,
   392                 let cell = sender as? EmailListViewCell,
   393                 let indexPath = self.tableView.indexPath(for: cell),
   394                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   395                 destination.composeMode = .replyFrom
   396                 destination.appConfig = config?.appConfig
   397                 destination.originalMessage = email
   398             }
   399         case .segueReplyAll:
   400             if let nav = segue.destination as? UINavigationController,
   401                 let destination = nav.topViewController as? ComposeTableViewController,
   402                 let cell = sender as? EmailListViewCell,
   403                 let indexPath = self.tableView.indexPath(for: cell),
   404                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   405                 destination.composeMode = .replyAll
   406                 destination.appConfig = config?.appConfig
   407                 destination.originalMessage = email
   408             }
   409             break
   410         case .segueShowEmail:
   411             if let vc = segue.destination as? EmailViewController,
   412                 let cell = sender as? EmailListViewCell,
   413                 let indexPath = self.tableView.indexPath(for: cell),
   414                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   415                 vc.appConfig = config?.appConfig
   416                 vc.message = email
   417                 vc.folderShow = viewModel?.folderToShow
   418                 vc.messageId = indexPath.row
   419             }
   420             break
   421         case .segueForward:
   422             if let nav = segue.destination as? UINavigationController,
   423                 let destination = nav.topViewController as? ComposeTableViewController,
   424                 let cell = sender as? EmailListViewCell,
   425                 let indexPath = self.tableView.indexPath(for: cell),
   426                 let email = cell.messageAt(indexPath: indexPath, config: config) {
   427                 destination.composeMode = .forward
   428                 destination.appConfig = config?.appConfig
   429                 destination.originalMessage = email
   430             }
   431             break
   432         case .segueFilter:
   433             if let destiny = segue.destination as? FilterTableViewController {
   434                 destiny.filterDelegate = self.viewModel
   435                 destiny.inFolder = false
   436                 destiny.filterEnabled = self.viewModel?.folderToShow?.filter
   437                 destiny.hidesBottomBarWhenPushed = true
   438             }
   439             break
   440         case .segueAddNewAccount:
   441             if let vc = segue.destination as? LoginTableViewController {
   442                 vc.appConfig = config?.appConfig
   443                 vc.hidesBottomBarWhenPushed = true
   444             }
   445         case .segueFolderViews:
   446             if let vC = segue.destination as? FolderTableViewController {
   447                 vC.appConfig = config?.appConfig
   448                 vC.hidesBottomBarWhenPushed = true
   449             }
   450         case .segueEditAccounts, .segueCompose, .noSegue:
   451             break
   452         }
   453 
   454     }
   455     
   456     @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
   457     }
   458 
   459     func didChangeInternal(messageFolder: MessageFolder) {
   460         if let folder = config?.folder,
   461             let message = messageFolder as? Message,
   462             folder.contains(message: message, deletedMessagesAreContained: true) {
   463             if message.isOriginal {
   464                 // new message has arrived
   465                 if let index = folder.indexOf(message: message) {
   466                     let ip = IndexPath(row: index, section: 0)
   467                     Log.info(
   468                         component: #function,
   469                         content: "insert message at \(index), \(folder.messageCount()) messages")
   470                     tableView.insertRows(at: [ip], with: .automatic)
   471                 } else {
   472                     tableView.reloadData()
   473                 }
   474             } else if message.isGhost {
   475                 if let vm = viewModel, let cell = vm.cellFor(message: message), let ip = tableView.indexPath(for: cell) {
   476                     Log.info(
   477                         component: #function,
   478                         content: "delete message at \(index), \(folder.messageCount()) messages")
   479                     tableView.deleteRows(at: [ip], with: .automatic)
   480                 } else {
   481                     tableView.reloadData()
   482                 }
   483             } else {
   484                 // other flags than delete must have been changed
   485                 if let vm = viewModel, let cell = vm.cellFor(message: message) {
   486                     cell.updateFlags(message: message)
   487                 } else {
   488                     tableView.reloadData()
   489                 }
   490             }
   491         }
   492     }
   493 
   494 }
   495 
   496 // MARK: - MessageFolderDelegate
   497 
   498 extension EmailListViewController: MessageFolderDelegate {
   499     func didChange(messageFolder: MessageFolder) {
   500         GCD.onMainWait {
   501             self.didChangeInternal(messageFolder: messageFolder)
   502         }
   503     }
   504 }
   505 
   506 extension EmailListViewController: tableViewUpdate {
   507     func updateView() {
   508         self.tableView.reloadData()
   509     }
   510 }