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