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