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