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