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