pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author Ana Rebollo <ana@pep-project.org>
Mon, 05 Sep 2016 11:11:00 +0200
changeset 653 7327ad9ed52b
parent 651 0b02a3fd1961
child 654 8a9bdb53501c
permissions -rw-r--r--
IOS-101: Avoiding to chall to times the server actions
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@30
    12
dirk@591
    13
class EmailListViewController: UITableViewController {
dirk@591
    14
    struct EmailListConfig {
dirk@591
    15
        let appConfig: AppConfig
dirk@583
    16
dirk@591
    17
        /** Set to whatever criteria you want to have mails displayed */
dirk@591
    18
        let predicate: NSPredicate?
dirk@583
    19
dirk@591
    20
        /** The sort descriptors to be used for displaying emails */
dirk@591
    21
        let sortDescriptors: [NSSortDescriptor]?
dirk@583
    22
dirk@606
    23
        /** If applicable, the account to refresh from */
dirk@591
    24
        let account: IAccount?
dirk@585
    25
dirk@591
    26
        /** If applicable, the folder name to sync */
dirk@591
    27
        let folderName: String?
dirk@593
    28
dirk@593
    29
        /** Should there be a sync directly when the view appears? */
dirk@593
    30
        let syncOnAppear: Bool
dirk@591
    31
    }
dirk@58
    32
dirk@583
    33
    struct UIState {
dirk@583
    34
        var isSynching: Bool = false
ana@653
    35
        var isSynchingFlags: Bool = false
dirk@583
    36
    }
dirk@583
    37
    
dirk@273
    38
    let comp = "EmailListViewController"
dirk@361
    39
dirk@273
    40
    let segueShowEmail = "segueShowEmail"
dirk@397
    41
    let segueCompose = "segueCompose"
dirk@397
    42
    let segueUserSettings = "segueUserSettings"
dirk@30
    43
dirk@583
    44
    var config: EmailListConfig!
dirk@583
    45
dirk@212
    46
    var fetchController: NSFetchedResultsController?
dirk@275
    47
    var state = UIState()
dirk@342
    48
    let dateFormatter = UIHelper.dateFormatterEmailList()
dirk@275
    49
dirk@502
    50
    /**
dirk@502
    51
     The default background color for an email cell, as determined the first time a cell is
dirk@502
    52
     created.
dirk@502
    53
     */
dirk@502
    54
    var defaultCellBackgroundColor: UIColor?
dirk@502
    55
dirk@502
    56
    /**
dirk@502
    57
     Indicates whether `defaultCellBackgroundColor` has been determined or not.
dirk@502
    58
     */
dirk@502
    59
    var determinedCellBackgroundColor: Bool = false
dirk@502
    60
dirk@594
    61
    var refreshController: UIRefreshControl!
dirk@594
    62
ana@620
    63
    func isReadedMessage(message: IMessage)-> Bool {
dirk@637
    64
        return message.flagSeen.boolValue
ana@614
    65
    }
ana@614
    66
ana@620
    67
    func isImportantMessage(message: IMessage)-> Bool {
ana@621
    68
        return message.flagFlagged.boolValue
ana@614
    69
    }
ana@614
    70
dirk@275
    71
    override func viewDidLoad() {
dirk@594
    72
        refreshController = UIRefreshControl.init()
dirk@593
    73
        refreshController.addTarget(self, action: #selector(self.fetchMailsRefreshControl(_:)),
dirk@275
    74
                                    forControlEvents: UIControlEvents.ValueChanged)
dirk@356
    75
        UIHelper.variableCellHeightsTableView(self.tableView)
dirk@275
    76
    }
dirk@31
    77
dirk@31
    78
    override func viewWillAppear(animated: Bool) {
dirk@594
    79
        // Disable fetching if there is no account
dirk@594
    80
        if config.account != nil {
dirk@594
    81
            self.refreshControl = refreshController
dirk@594
    82
        } else {
dirk@594
    83
            self.refreshControl = nil
dirk@594
    84
        }
dirk@594
    85
dirk@209
    86
        prepareFetchRequest()
dirk@593
    87
        if config.syncOnAppear {
dirk@593
    88
            fetchMailsRefreshControl()
dirk@593
    89
        }
ana@207
    90
        super.viewWillAppear(animated)
ana@207
    91
    }
ana@152
    92
dirk@275
    93
    func fetchMailsRefreshControl(refreshControl: UIRefreshControl? = nil) {
dirk@583
    94
        if let account = config.account {
dirk@55
    95
            let connectInfo = account.connectInfo
dirk@55
    96
dirk@275
    97
            state.isSynching = true
dirk@588
    98
            updateUI()
dirk@368
    99
dirk@588
   100
            config.appConfig.grandOperator.fetchEmailsAndDecryptConnectInfos(
dirk@590
   101
                [connectInfo], folderName: config.folderName,
dirk@466
   102
                completionBlock: { error in
dirk@466
   103
                    Log.infoComponent(self.comp, "Sync completed, error: \(error)")
dirk@585
   104
                    if let err = error {
dirk@585
   105
                        UIHelper.displayError(err, controller: self)
dirk@585
   106
                    }
dirk@583
   107
                    self.config.appConfig.model.save()
dirk@466
   108
                    self.state.isSynching = false
dirk@466
   109
                    refreshControl?.endRefreshing()
dirk@466
   110
                    self.updateUI()
dirk@466
   111
            })
dirk@594
   112
        } else {
dirk@594
   113
            state.isSynching = false
dirk@594
   114
            updateUI()
dirk@55
   115
        }
dirk@31
   116
    }
dirk@31
   117
dirk@452
   118
    @IBAction func mailSentSegue(segue: UIStoryboardSegue) {
dirk@452
   119
    }
dirk@452
   120
ana@143
   121
    func prepareFetchRequest() {
dirk@31
   122
        let fetchRequest = NSFetchRequest.init(entityName: Message.entityName())
dirk@583
   123
        fetchRequest.predicate = config.predicate
dirk@583
   124
        fetchRequest.sortDescriptors = config.sortDescriptors
dirk@31
   125
        fetchController = NSFetchedResultsController.init(
dirk@31
   126
            fetchRequest: fetchRequest,
dirk@583
   127
            managedObjectContext: config.appConfig.coreDataUtil.managedObjectContext,
dirk@31
   128
            sectionNameKeyPath: nil, cacheName: nil)
dirk@31
   129
        fetchController?.delegate = self
dirk@31
   130
        do {
dirk@31
   131
            try fetchController?.performFetch()
dirk@31
   132
        } catch let err as NSError {
dirk@414
   133
            Log.errorComponent(comp, error: err)
dirk@31
   134
        }
dirk@31
   135
    }
dirk@31
   136
dirk@58
   137
    // MARK: - UI State
dirk@58
   138
dirk@58
   139
    func updateUI() {
dirk@275
   140
        if state.isSynching {
dirk@58
   141
            UIApplication.sharedApplication().networkActivityIndicatorVisible = true
dirk@58
   142
        } else {
dirk@58
   143
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
dirk@594
   144
            self.refreshControl?.endRefreshing()
dirk@58
   145
        }
dirk@58
   146
    }
dirk@58
   147
dirk@31
   148
    // MARK: - UITableViewDataSource
dirk@31
   149
dirk@31
   150
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
dirk@209
   151
        if let count = fetchController?.sections?.count {
dirk@209
   152
            return count
dirk@209
   153
        } else {
dirk@209
   154
            return 0
dirk@209
   155
        }
dirk@31
   156
    }
dirk@31
   157
dirk@31
   158
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dirk@31
   159
        if fetchController?.sections?.count > 0 {
dirk@31
   160
            if let sections = fetchController?.sections {
dirk@31
   161
                let sectionInfo = sections[section]
dirk@31
   162
                return sectionInfo.numberOfObjects
dirk@31
   163
            }
dirk@31
   164
        }
dirk@31
   165
        return 0
dirk@31
   166
    }
dirk@31
   167
dirk@31
   168
    override func tableView(tableView: UITableView,
dirk@31
   169
                            cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
dirk@209
   170
        let cell = tableView.dequeueReusableCellWithIdentifier(
dirk@209
   171
            "EmailListViewCell", forIndexPath: indexPath) as! EmailListViewCell
dirk@502
   172
        if !determinedCellBackgroundColor {
dirk@502
   173
            defaultCellBackgroundColor = cell.backgroundColor
dirk@502
   174
            determinedCellBackgroundColor = true
dirk@502
   175
        }
dirk@31
   176
        configureCell(cell, indexPath: indexPath)
dirk@31
   177
        return cell
dirk@31
   178
    }
dirk@31
   179
dirk@31
   180
    func configureCell(cell: EmailListViewCell, indexPath: NSIndexPath) {
ana@620
   181
        cell.isImportantImage.hidden = true
dirk@31
   182
        if let email = fetchController?.objectAtIndexPath(indexPath) as? Message {
dirk@502
   183
            if let colorRating = PEPUtil.colorRatingFromInt(email.pepColorRating?.integerValue) {
dirk@574
   184
                let privacyColor = PEPUtil.colorFromPepRating(colorRating)
dirk@502
   185
                if let uiColor = UIHelper.textBackgroundUIColorFromPrivacyColor(privacyColor) {
dirk@502
   186
                    cell.backgroundColor = uiColor
dirk@502
   187
                } else {
dirk@502
   188
                    if determinedCellBackgroundColor {
dirk@502
   189
                        cell.backgroundColor = defaultCellBackgroundColor
dirk@502
   190
                    }
dirk@502
   191
                }
dirk@502
   192
            }
dirk@342
   193
            UIHelper.putString(email.from?.displayString(), toLabel: cell.senderLabel)
dirk@342
   194
            UIHelper.putString(email.subject, toLabel: cell.subjectLabel)
dirk@572
   195
dirk@572
   196
            // Snippet
dirk@565
   197
            if let text = email.longMessage {
dirk@572
   198
                let theText = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
dirk@565
   199
                UIHelper.putString(theText, toLabel: cell.summaryLabel)
dirk@572
   200
            } else if let html = email.longMessageFormatted {
dirk@568
   201
                var text = html.extractTextFromHTML()
dirk@572
   202
                text = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
dirk@568
   203
                UIHelper.putString(text, toLabel: cell.summaryLabel)
dirk@568
   204
            } else {
dirk@568
   205
                UIHelper.putString(nil, toLabel: cell.summaryLabel)
dirk@565
   206
            }
dirk@32
   207
dirk@480
   208
            if let receivedDate = email.receivedDate {
dirk@480
   209
                UIHelper.putString(dateFormatter.stringFromDate(receivedDate),
dirk@342
   210
                                   toLabel: cell.dateLabel)
dirk@32
   211
            } else {
dirk@342
   212
                UIHelper.putString(nil, toLabel: cell.dateLabel)
dirk@32
   213
            }
ana@620
   214
ana@620
   215
            if (isImportantMessage(email)) {
ana@620
   216
                cell.isImportantImage.hidden = false
ana@620
   217
                cell.isImportantImage.backgroundColor = UIColor.orangeColor()
ana@620
   218
            }
ana@620
   219
            if (isReadedMessage(email)) {
ana@620
   220
                cell.isImportantImage.hidden = false
ana@620
   221
                cell.isImportantImage.backgroundColor = UIColor.blueColor()
ana@620
   222
            }
dirk@31
   223
        }
dirk@30
   224
    }
dirk@30
   225
ana@246
   226
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
dirk@606
   227
        // Make sure the current account is set, if defined
dirk@606
   228
        config.appConfig.currentAccount = config.account
dirk@606
   229
dirk@361
   230
        if segue.identifier == segueCompose {
dirk@397
   231
            let destination = segue.destinationViewController
dirk@434
   232
                as! ComposeViewController
dirk@583
   233
            destination.appConfig = config.appConfig
dirk@273
   234
        } else if segue.identifier == segueShowEmail {
dirk@273
   235
            guard
dirk@273
   236
                let vc = segue.destinationViewController as? EmailViewController,
dirk@273
   237
                let cell = sender as? UITableViewCell,
dirk@273
   238
                let indexPath = self.tableView.indexPathForCell(cell),
dirk@273
   239
                let email = fetchController?.objectAtIndexPath(indexPath) as? Message else {
dirk@273
   240
                    return
dirk@273
   241
            }
dirk@583
   242
            vc.appConfig = config.appConfig
dirk@273
   243
            vc.message = email
ana@246
   244
        }
ana@246
   245
    }
dirk@30
   246
}
dirk@30
   247
dirk@31
   248
extension EmailListViewController: NSFetchedResultsControllerDelegate {
dirk@31
   249
    func controllerWillChangeContent(controller: NSFetchedResultsController) {
dirk@31
   250
        tableView.beginUpdates()
dirk@30
   251
    }
dirk@30
   252
dirk@31
   253
    func controller(controller: NSFetchedResultsController,
dirk@31
   254
                    didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
dirk@31
   255
                                     atIndex sectionIndex: Int,
dirk@31
   256
                                             forChangeType type: NSFetchedResultsChangeType) {
dirk@31
   257
        switch (type) {
dirk@31
   258
        case .Insert:
dirk@31
   259
            tableView.insertSections(NSIndexSet.init(index: sectionIndex),
dirk@31
   260
                                     withRowAnimation: .Fade)
dirk@31
   261
        case .Delete:
dirk@31
   262
            tableView.deleteSections(NSIndexSet.init(index: sectionIndex),
dirk@31
   263
                                     withRowAnimation: .Fade)
dirk@31
   264
        default:
dirk@414
   265
            Log.infoComponent(comp, "unhandled changeSectionType: \(type)")
dirk@31
   266
        }
dirk@30
   267
    }
dirk@30
   268
dirk@31
   269
    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject,
dirk@31
   270
                    atIndexPath indexPath: NSIndexPath?,
dirk@31
   271
                                forChangeType type: NSFetchedResultsChangeType,
dirk@31
   272
                                              newIndexPath: NSIndexPath?) {
dirk@40
   273
        switch type {
dirk@40
   274
        case .Insert:
dirk@40
   275
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
dirk@40
   276
        case .Delete:
dirk@40
   277
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
dirk@40
   278
        case .Update:
dirk@40
   279
            if let cell = tableView.cellForRowAtIndexPath(indexPath!) {
dirk@40
   280
                self.configureCell(cell as! EmailListViewCell, indexPath: indexPath!)
dirk@40
   281
            } else {
dirk@414
   282
                Log.warnComponent(comp, "Could not find cell for changed indexPath: \(indexPath!)")
dirk@31
   283
            }
dirk@40
   284
        case .Move:
dirk@40
   285
            if newIndexPath != indexPath {
dirk@40
   286
                tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
dirk@40
   287
                tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
dirk@40
   288
            }
dirk@31
   289
        }
dirk@31
   290
    }
dirk@31
   291
dirk@31
   292
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
dirk@31
   293
        tableView.endUpdates()
dirk@31
   294
    }
ana@562
   295
ana@653
   296
    func syncFlagsToServer(message: IMessage) {
ana@653
   297
        if (self.state.isSynching == false) {
ana@653
   298
            self.state.isSynching = true
ana@653
   299
            self.config.appConfig.grandOperator.syncFlagsToServerForFolder(
ana@653
   300
                message.folder,
ana@653
   301
                completionBlock: { error in})
ana@653
   302
        }
ana@653
   303
    }
ana@653
   304
ana@621
   305
    func createIsFlagAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
ana@599
   306
ana@620
   307
        // preparing the title action to show when user swipe
ana@620
   308
        var localizedIsFlagTitle = " "
ana@621
   309
        if (isImportantMessage(message)) {
ana@651
   310
            localizedIsFlagTitle = NSLocalizedString("Unflag",
ana@651
   311
            comment: "Unflag button title in swipe action on EmailListViewController")
ana@651
   312
        } else {
ana@620
   313
            localizedIsFlagTitle = NSLocalizedString("Flag",
ana@620
   314
            comment: "Flag button title in swipe action on EmailListViewController")
ana@620
   315
        }
ana@620
   316
ana@620
   317
        // preparing action to trigger when user swipe
ana@614
   318
        let isFlagCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
ana@601
   319
            { (action, indexPath) in
ana@651
   320
                if (self.isImportantMessage(message)) {
ana@651
   321
                    message.flagFlagged = false
ana@651
   322
                    message.updateFlags()
ana@651
   323
                } else {
ana@651
   324
                    message.flagFlagged = true
ana@651
   325
                    message.updateFlags()
ana@651
   326
                }
ana@653
   327
                self.syncFlagsToServer(message)
ana@614
   328
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
ana@601
   329
            }
ana@620
   330
        // creating the action
ana@620
   331
        let isFlagAction = UITableViewRowAction(style: .Default, title: localizedIsFlagTitle,
ana@620
   332
                                                handler: isFlagCompletionHandler)
ana@620
   333
        // changing default action color
ana@620
   334
        isFlagAction.backgroundColor = UIColor.orangeColor()
ana@602
   335
ana@620
   336
        return isFlagAction
ana@620
   337
    }
ana@620
   338
ana@620
   339
    func createDeleteAction (cell: EmailListViewCell) -> UITableViewRowAction {
ana@620
   340
ana@620
   341
        // preparing the title action to show when user swipe
ana@620
   342
        let localizedDeleteTitle = NSLocalizedString("Erase",
ana@620
   343
        comment: "Erase button title in swipe action on EmailListViewController")
ana@599
   344
ana@601
   345
        let deleteCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
ana@601
   346
            { (action, indexPath) in
ana@650
   347
                let managedObject = self.fetchController?.objectAtIndexPath(indexPath) as? IMessage
ana@650
   348
                managedObject?.flagDeleted = true
ana@651
   349
                managedObject?.updateFlags()
ana@653
   350
                self.syncFlagsToServer(managedObject!)
ana@601
   351
            }
ana@620
   352
ana@620
   353
        // creating the action
ana@601
   354
        let deleteAction = UITableViewRowAction(style: .Default, title: localizedDeleteTitle,
ana@601
   355
                                                handler: deleteCompletionHandler)
ana@614
   356
ana@620
   357
        return deleteAction
ana@620
   358
    }
ana@620
   359
ana@621
   360
    func createIsReadAction (message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
ana@620
   361
ana@620
   362
        // preparing the title action to show when user swipe
ana@620
   363
        var localizedisReadTitle = " "
ana@621
   364
        if (isReadedMessage(message)) {
ana@651
   365
            localizedisReadTitle = NSLocalizedString("Unread",
ana@651
   366
            comment: "Unread button title in swipe action on EmailListViewController")
ana@651
   367
        } else {
ana@620
   368
            localizedisReadTitle = NSLocalizedString("Read",
ana@620
   369
            comment: "Read button title in swipe action on EmailListViewController")
ana@620
   370
        }
ana@620
   371
ana@620
   372
        // creating the action
ana@614
   373
        let isReadCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
ana@614
   374
            { (action, indexPath) in
ana@651
   375
                if (self.isReadedMessage(message)) {
ana@651
   376
                    message.flagSeen = false
ana@651
   377
                    message.updateFlags()
ana@651
   378
                } else {
ana@651
   379
                    message.flagSeen = true
ana@651
   380
                    message.updateFlags()
ana@651
   381
                }
ana@653
   382
                self.syncFlagsToServer(message)
ana@614
   383
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
ana@614
   384
            }
ana@614
   385
        let isReadAction = UITableViewRowAction(style: .Default, title: localizedisReadTitle,
ana@614
   386
                                                handler: isReadCompletionHandler)
ana@614
   387
        isReadAction.backgroundColor = UIColor.blueColor()
ana@620
   388
ana@620
   389
        return isReadAction
ana@620
   390
    }
ana@620
   391
ana@620
   392
    override func tableView(tableView: UITableView, editActionsForRowAtIndexPath
ana@620
   393
                  indexPath: NSIndexPath)-> [UITableViewRowAction]? {
ana@620
   394
ana@620
   395
        let cell = tableView.cellForRowAtIndexPath(indexPath) as! EmailListViewCell
ana@620
   396
        let email = fetchController?.objectAtIndexPath(indexPath) as! Message
ana@621
   397
ana@621
   398
        let isFlagAction = createIsFlagAction(email, cell: cell)
ana@620
   399
        let deleteAction = createDeleteAction(cell)
ana@621
   400
        let isReadAction = createIsReadAction(email, cell: cell)
ana@614
   401
        return [deleteAction,isFlagAction,isReadAction]
ana@599
   402
    }
dirk@30
   403
}