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