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