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