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