pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author Dirk Zimmermann <dirk@pep-project.org>
Tue, 20 Sep 2016 17:14:51 +0200
changeset 745 7131d36d386d
parent 744 de515991129e
child 746 99a786a69567
permissions -rw-r--r--
IOS-198 Factored out NSFetchedResultsControllerDelegate
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@745
    13
class EmailListViewController: FetchTableViewController {
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
dirk@583
    35
    }
dirk@583
    36
    
dirk@273
    37
    let segueShowEmail = "segueShowEmail"
dirk@397
    38
    let segueCompose = "segueCompose"
dirk@397
    39
    let segueUserSettings = "segueUserSettings"
dirk@30
    40
dirk@583
    41
    var config: EmailListConfig!
dirk@583
    42
dirk@212
    43
    var fetchController: NSFetchedResultsController?
dirk@275
    44
    var state = UIState()
dirk@342
    45
    let dateFormatter = UIHelper.dateFormatterEmailList()
dirk@275
    46
dirk@502
    47
    /**
dirk@502
    48
     The default background color for an email cell, as determined the first time a cell is
dirk@502
    49
     created.
dirk@502
    50
     */
dirk@502
    51
    var defaultCellBackgroundColor: UIColor?
dirk@502
    52
dirk@502
    53
    /**
dirk@502
    54
     Indicates whether `defaultCellBackgroundColor` has been determined or not.
dirk@502
    55
     */
dirk@502
    56
    var determinedCellBackgroundColor: Bool = false
dirk@502
    57
dirk@594
    58
    var refreshController: UIRefreshControl!
dirk@594
    59
dirk@705
    60
    /**
dirk@705
    61
     The message that should be saved as a draft when compose gets aborted.
dirk@705
    62
     */
dirk@705
    63
    var draftMessageToStore: IMessage?
dirk@705
    64
dirk@719
    65
    /**
dirk@719
    66
     When the user taps on a draft email, this is the message that was selected
dirk@719
    67
     and should be given to the compose view.
dirk@719
    68
     */
dirk@719
    69
    var draftMessageToCompose: IMessage?
dirk@719
    70
dirk@745
    71
    required init?(coder aDecoder: NSCoder) {
dirk@745
    72
        super.init(coder: aDecoder)
dirk@745
    73
        self.comp = "EmailListViewController"
dirk@745
    74
    }
dirk@745
    75
ana@620
    76
    func isReadedMessage(message: IMessage)-> Bool {
dirk@637
    77
        return message.flagSeen.boolValue
ana@614
    78
    }
ana@614
    79
ana@620
    80
    func isImportantMessage(message: IMessage)-> Bool {
ana@621
    81
        return message.flagFlagged.boolValue
ana@614
    82
    }
ana@614
    83
dirk@275
    84
    override func viewDidLoad() {
dirk@594
    85
        refreshController = UIRefreshControl.init()
dirk@593
    86
        refreshController.addTarget(self, action: #selector(self.fetchMailsRefreshControl(_:)),
dirk@275
    87
                                    forControlEvents: UIControlEvents.ValueChanged)
dirk@356
    88
        UIHelper.variableCellHeightsTableView(self.tableView)
dirk@275
    89
    }
dirk@31
    90
dirk@31
    91
    override func viewWillAppear(animated: Bool) {
dirk@594
    92
        // Disable fetching if there is no account
dirk@594
    93
        if config.account != nil {
dirk@594
    94
            self.refreshControl = refreshController
dirk@594
    95
        } else {
dirk@594
    96
            self.refreshControl = nil
dirk@594
    97
        }
dirk@594
    98
dirk@209
    99
        prepareFetchRequest()
dirk@593
   100
        if config.syncOnAppear {
dirk@593
   101
            fetchMailsRefreshControl()
dirk@593
   102
        }
ana@207
   103
        super.viewWillAppear(animated)
ana@207
   104
    }
ana@152
   105
dirk@275
   106
    func fetchMailsRefreshControl(refreshControl: UIRefreshControl? = nil) {
dirk@583
   107
        if let account = config.account {
dirk@55
   108
            let connectInfo = account.connectInfo
dirk@55
   109
dirk@275
   110
            state.isSynching = true
dirk@588
   111
            updateUI()
dirk@368
   112
dirk@588
   113
            config.appConfig.grandOperator.fetchEmailsAndDecryptConnectInfos(
dirk@590
   114
                [connectInfo], folderName: config.folderName,
dirk@466
   115
                completionBlock: { error in
dirk@466
   116
                    Log.infoComponent(self.comp, "Sync completed, error: \(error)")
dirk@707
   117
                    UIHelper.displayError(error, controller: self)
dirk@583
   118
                    self.config.appConfig.model.save()
dirk@466
   119
                    self.state.isSynching = false
dirk@466
   120
                    refreshControl?.endRefreshing()
dirk@466
   121
                    self.updateUI()
dirk@466
   122
            })
dirk@594
   123
        } else {
dirk@594
   124
            state.isSynching = false
dirk@594
   125
            updateUI()
dirk@55
   126
        }
dirk@31
   127
    }
dirk@31
   128
dirk@452
   129
    @IBAction func mailSentSegue(segue: UIStoryboardSegue) {
dirk@452
   130
    }
dirk@452
   131
dirk@705
   132
    @IBAction func backFromComposeWithoutSavingDraftSegue(segue: UIStoryboardSegue) {
dirk@705
   133
    }
dirk@705
   134
dirk@705
   135
    @IBAction func backFromComposeSaveDraftSegue(segue: UIStoryboardSegue) {
dirk@705
   136
        guard let msg = draftMessageToStore else {
dirk@705
   137
            return
dirk@705
   138
        }
dirk@707
   139
dirk@707
   140
        state.isSynching = true
dirk@707
   141
        updateUI()
dirk@707
   142
dirk@706
   143
        config.appConfig.grandOperator.saveDraftMail(
dirk@706
   144
            msg, account: msg.folder.account, completionBlock: { error in
dirk@707
   145
                GCD.onMain() {
dirk@709
   146
                    UIHelper.displayError(error, controller: self)
dirk@707
   147
                    self.state.isSynching = false
dirk@707
   148
                    self.updateUI()
dirk@707
   149
                }
dirk@706
   150
        })
dirk@705
   151
    }
dirk@705
   152
ana@143
   153
    func prepareFetchRequest() {
dirk@31
   154
        let fetchRequest = NSFetchRequest.init(entityName: Message.entityName())
dirk@583
   155
        fetchRequest.predicate = config.predicate
dirk@583
   156
        fetchRequest.sortDescriptors = config.sortDescriptors
dirk@31
   157
        fetchController = NSFetchedResultsController.init(
dirk@31
   158
            fetchRequest: fetchRequest,
dirk@583
   159
            managedObjectContext: config.appConfig.coreDataUtil.managedObjectContext,
dirk@31
   160
            sectionNameKeyPath: nil, cacheName: nil)
dirk@31
   161
        fetchController?.delegate = self
dirk@31
   162
        do {
dirk@31
   163
            try fetchController?.performFetch()
dirk@31
   164
        } catch let err as NSError {
dirk@414
   165
            Log.errorComponent(comp, error: err)
dirk@31
   166
        }
dirk@31
   167
    }
dirk@31
   168
dirk@58
   169
    // MARK: - UI State
dirk@58
   170
dirk@58
   171
    func updateUI() {
dirk@275
   172
        if state.isSynching {
dirk@58
   173
            UIApplication.sharedApplication().networkActivityIndicatorVisible = true
dirk@58
   174
        } else {
dirk@58
   175
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
dirk@594
   176
            self.refreshControl?.endRefreshing()
dirk@58
   177
        }
dirk@58
   178
    }
dirk@58
   179
dirk@31
   180
    // MARK: - UITableViewDataSource
dirk@31
   181
dirk@31
   182
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
dirk@209
   183
        if let count = fetchController?.sections?.count {
dirk@209
   184
            return count
dirk@209
   185
        } else {
dirk@209
   186
            return 0
dirk@209
   187
        }
dirk@31
   188
    }
dirk@31
   189
dirk@31
   190
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dirk@31
   191
        if fetchController?.sections?.count > 0 {
dirk@31
   192
            if let sections = fetchController?.sections {
dirk@31
   193
                let sectionInfo = sections[section]
dirk@31
   194
                return sectionInfo.numberOfObjects
dirk@31
   195
            }
dirk@31
   196
        }
dirk@31
   197
        return 0
dirk@31
   198
    }
dirk@31
   199
dirk@31
   200
    override func tableView(tableView: UITableView,
dirk@31
   201
                            cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
dirk@209
   202
        let cell = tableView.dequeueReusableCellWithIdentifier(
dirk@209
   203
            "EmailListViewCell", forIndexPath: indexPath) as! EmailListViewCell
dirk@502
   204
        if !determinedCellBackgroundColor {
dirk@502
   205
            defaultCellBackgroundColor = cell.backgroundColor
dirk@502
   206
            determinedCellBackgroundColor = true
dirk@502
   207
        }
dirk@31
   208
        configureCell(cell, indexPath: indexPath)
dirk@31
   209
        return cell
dirk@31
   210
    }
dirk@31
   211
dirk@719
   212
    // MARK: - UITableViewDelegate
dirk@719
   213
dirk@719
   214
    override func tableView(tableView: UITableView,
dirk@719
   215
                            didSelectRowAtIndexPath indexPath: NSIndexPath) {
dirk@719
   216
        draftMessageToCompose = nil
dirk@719
   217
dirk@719
   218
        let cell = tableView.cellForRowAtIndexPath(indexPath)
dirk@719
   219
dirk@719
   220
        if let fn = config.folderName, let ac = config.account,
dirk@719
   221
            let folder = config.appConfig.model.folderByName(fn, email: ac.email) {
dirk@719
   222
            if folder.folderType.integerValue == FolderType.Drafts.rawValue {
dirk@719
   223
                draftMessageToCompose = fetchController?.objectAtIndexPath(indexPath)
dirk@719
   224
                    as? IMessage
dirk@719
   225
                performSegueWithIdentifier(segueCompose, sender: cell)
dirk@719
   226
                return
dirk@719
   227
            }
dirk@719
   228
        }
dirk@719
   229
dirk@719
   230
        performSegueWithIdentifier(segueShowEmail, sender: cell)
dirk@719
   231
    }
dirk@719
   232
dirk@744
   233
    override func tableView(tableView: UITableView, editActionsForRowAtIndexPath
dirk@744
   234
        indexPath: NSIndexPath)-> [UITableViewRowAction]? {
dirk@744
   235
dirk@744
   236
        let cell = tableView.cellForRowAtIndexPath(indexPath) as! EmailListViewCell
dirk@744
   237
        let email = fetchController?.objectAtIndexPath(indexPath) as! Message
dirk@744
   238
dirk@744
   239
        let isFlagAction = createIsFlagAction(email, cell: cell)
dirk@744
   240
        let deleteAction = createDeleteAction(cell)
dirk@744
   241
        let isReadAction = createIsReadAction(email, cell: cell)
dirk@744
   242
        return [deleteAction,isFlagAction,isReadAction]
dirk@744
   243
    }
dirk@744
   244
dirk@719
   245
    // MARK: - Misc
dirk@719
   246
dirk@745
   247
    override func configureCell(theCell: UITableViewCell, indexPath: NSIndexPath) {
dirk@745
   248
        guard let cell = theCell as? EmailListViewCell else {
dirk@745
   249
            return
dirk@745
   250
        }
dirk@31
   251
        if let email = fetchController?.objectAtIndexPath(indexPath) as? Message {
dirk@502
   252
            if let colorRating = PEPUtil.colorRatingFromInt(email.pepColorRating?.integerValue) {
dirk@574
   253
                let privacyColor = PEPUtil.colorFromPepRating(colorRating)
dirk@502
   254
                if let uiColor = UIHelper.textBackgroundUIColorFromPrivacyColor(privacyColor) {
dirk@502
   255
                    cell.backgroundColor = uiColor
dirk@502
   256
                } else {
dirk@502
   257
                    if determinedCellBackgroundColor {
dirk@502
   258
                        cell.backgroundColor = defaultCellBackgroundColor
dirk@502
   259
                    }
dirk@502
   260
                }
dirk@502
   261
            }
dirk@342
   262
            UIHelper.putString(email.from?.displayString(), toLabel: cell.senderLabel)
dirk@342
   263
            UIHelper.putString(email.subject, toLabel: cell.subjectLabel)
dirk@572
   264
dirk@572
   265
            // Snippet
dirk@565
   266
            if let text = email.longMessage {
dirk@572
   267
                let theText = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
dirk@565
   268
                UIHelper.putString(theText, toLabel: cell.summaryLabel)
dirk@572
   269
            } else if let html = email.longMessageFormatted {
dirk@568
   270
                var text = html.extractTextFromHTML()
dirk@572
   271
                text = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
dirk@568
   272
                UIHelper.putString(text, toLabel: cell.summaryLabel)
dirk@568
   273
            } else {
dirk@568
   274
                UIHelper.putString(nil, toLabel: cell.summaryLabel)
dirk@565
   275
            }
dirk@32
   276
dirk@480
   277
            if let receivedDate = email.receivedDate {
dirk@480
   278
                UIHelper.putString(dateFormatter.stringFromDate(receivedDate),
dirk@342
   279
                                   toLabel: cell.dateLabel)
dirk@32
   280
            } else {
dirk@342
   281
                UIHelper.putString(nil, toLabel: cell.dateLabel)
dirk@32
   282
            }
ana@620
   283
ana@656
   284
            if (isImportantMessage(email) && isReadedMessage(email)) {
ana@666
   285
                cell.isImportantImage.hidden = false
ana@666
   286
                cell.isImportantImage.backgroundColor = UIColor.orangeColor()
ana@666
   287
            }
ana@666
   288
            else if (isImportantMessage(email) && !isReadedMessage(email)) {
ana@666
   289
                cell.isImportantImage.hidden = false
ana@656
   290
                cell.isImportantImage.backgroundColor = UIColor.blueColor()
ana@656
   291
                cell.isImportantImage.layer.borderWidth = 2
ana@656
   292
                cell.isImportantImage.layer.borderColor = UIColor.orangeColor().CGColor
ana@666
   293
            } else if (!isImportantMessage(email) && isReadedMessage(email)) {
ana@666
   294
                    cell.isImportantImage.hidden = true
ana@666
   295
            } else if (!isImportantMessage(email) && !isReadedMessage(email)) {
ana@620
   296
                cell.isImportantImage.hidden = false
ana@666
   297
                cell.isImportantImage.backgroundColor = UIColor.blueColor()
ana@620
   298
            }
dirk@31
   299
        }
dirk@30
   300
    }
dirk@30
   301
ana@246
   302
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
dirk@606
   303
        // Make sure the current account is set, if defined
dirk@606
   304
        config.appConfig.currentAccount = config.account
dirk@606
   305
dirk@361
   306
        if segue.identifier == segueCompose {
dirk@397
   307
            let destination = segue.destinationViewController
dirk@434
   308
                as! ComposeViewController
dirk@583
   309
            destination.appConfig = config.appConfig
dirk@719
   310
            if let draft = draftMessageToCompose {
dirk@723
   311
                draft.flagSeen = true
dirk@723
   312
                draft.updateFlags()
dirk@723
   313
                config.appConfig.model.save()
dirk@723
   314
dirk@719
   315
                destination.originalMessage = draft
dirk@719
   316
                destination.composeMode = .ComposeDraft
dirk@719
   317
            }
dirk@273
   318
        } else if segue.identifier == segueShowEmail {
dirk@273
   319
            guard
dirk@273
   320
                let vc = segue.destinationViewController as? EmailViewController,
dirk@273
   321
                let cell = sender as? UITableViewCell,
dirk@273
   322
                let indexPath = self.tableView.indexPathForCell(cell),
dirk@273
   323
                let email = fetchController?.objectAtIndexPath(indexPath) as? Message else {
dirk@273
   324
                    return
dirk@273
   325
            }
dirk@583
   326
            vc.appConfig = config.appConfig
dirk@273
   327
            vc.message = email
ana@246
   328
        }
ana@246
   329
    }
dirk@744
   330
dirk@744
   331
    func syncFlagsToServer(message: IMessage) {
dirk@744
   332
        self.config.appConfig.grandOperator.syncFlagsToServerForFolder(
dirk@744
   333
            message.folder,
dirk@744
   334
            completionBlock: { error in
dirk@744
   335
                UIHelper.displayError(error, controller: self)
dirk@744
   336
        })
dirk@744
   337
    }
dirk@744
   338
dirk@744
   339
    func createIsFlagAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   340
dirk@744
   341
        // preparing the title action to show when user swipe
dirk@744
   342
        var localizedIsFlagTitle = " "
dirk@744
   343
        if (isImportantMessage(message)) {
dirk@744
   344
            localizedIsFlagTitle = NSLocalizedString("Unflag",
dirk@744
   345
                                                     comment: "Unflag button title in swipe action on EmailListViewController")
dirk@744
   346
        } else {
dirk@744
   347
            localizedIsFlagTitle = NSLocalizedString("Flag",
dirk@744
   348
                                                     comment: "Flag button title in swipe action on EmailListViewController")
dirk@744
   349
        }
dirk@744
   350
dirk@744
   351
        // preparing action to trigger when user swipe
dirk@744
   352
        let isFlagCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
dirk@744
   353
            { (action, indexPath) in
dirk@744
   354
                if (self.isImportantMessage(message)) {
dirk@744
   355
                    message.flagFlagged = false
dirk@744
   356
dirk@744
   357
                } else {
dirk@744
   358
                    message.flagFlagged = true
dirk@744
   359
                }
dirk@744
   360
                message.updateFlags()
dirk@744
   361
                self.syncFlagsToServer(message)
dirk@744
   362
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
dirk@744
   363
        }
dirk@744
   364
        // creating the action
dirk@744
   365
        let isFlagAction = UITableViewRowAction(style: .Default, title: localizedIsFlagTitle,
dirk@744
   366
                                                handler: isFlagCompletionHandler)
dirk@744
   367
        // changing default action color
dirk@744
   368
        isFlagAction.backgroundColor = UIColor.orangeColor()
dirk@744
   369
dirk@744
   370
        return isFlagAction
dirk@744
   371
    }
dirk@744
   372
dirk@744
   373
    func createDeleteAction (cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   374
dirk@744
   375
        // preparing the title action to show when user swipe
dirk@744
   376
        let localizedDeleteTitle = NSLocalizedString("Erase",
dirk@744
   377
                                                     comment: "Erase button title in swipe action on EmailListViewController")
dirk@744
   378
dirk@744
   379
        let deleteCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
dirk@744
   380
            { (action, indexPath) in
dirk@744
   381
                let managedObject = self.fetchController?.objectAtIndexPath(indexPath) as? IMessage
dirk@744
   382
                managedObject?.flagDeleted = true
dirk@744
   383
                managedObject?.updateFlags()
dirk@744
   384
                self.syncFlagsToServer(managedObject!)
dirk@744
   385
        }
dirk@744
   386
dirk@744
   387
        // creating the action
dirk@744
   388
        let deleteAction = UITableViewRowAction(style: .Default, title: localizedDeleteTitle,
dirk@744
   389
                                                handler: deleteCompletionHandler)
dirk@744
   390
        return deleteAction
dirk@744
   391
    }
dirk@744
   392
dirk@744
   393
    func createIsReadAction (message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   394
dirk@744
   395
        // preparing the title action to show when user swipe
dirk@744
   396
        var localizedisReadTitle = " "
dirk@744
   397
        if (isReadedMessage(message)) {
dirk@744
   398
            localizedisReadTitle = NSLocalizedString("Unread",
dirk@744
   399
                                                     comment: "Unread button title in swipe action on EmailListViewController")
dirk@744
   400
        } else {
dirk@744
   401
            localizedisReadTitle = NSLocalizedString("Read",
dirk@744
   402
                                                     comment: "Read button title in swipe action on EmailListViewController")
dirk@744
   403
        }
dirk@744
   404
dirk@744
   405
        // creating the action
dirk@744
   406
        let isReadCompletionHandler: (UITableViewRowAction, NSIndexPath) -> Void =
dirk@744
   407
            { (action, indexPath) in
dirk@744
   408
                if (self.isReadedMessage(message)) {
dirk@744
   409
                    message.flagSeen = false
dirk@744
   410
                    message.updateFlags()
dirk@744
   411
                } else {
dirk@744
   412
                    message.flagSeen = true
dirk@744
   413
                    message.updateFlags()
dirk@744
   414
                }
dirk@744
   415
                self.syncFlagsToServer(message)
dirk@744
   416
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
dirk@744
   417
        }
dirk@744
   418
        let isReadAction = UITableViewRowAction(style: .Default, title: localizedisReadTitle,
dirk@744
   419
                                                handler: isReadCompletionHandler)
dirk@744
   420
        isReadAction.backgroundColor = UIColor.blueColor()
dirk@744
   421
dirk@744
   422
        return isReadAction
dirk@744
   423
    }
dirk@30
   424
}