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