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