pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author Igor Vojinovic <igor.vojinovic@appculture.com>
Thu, 10 Nov 2016 14:16:21 +0100
changeset 1242 4cecb482ae1c
parent 965 baea625419a9
child 1243 2a9465fd0bfc
permissions -rw-r--r--
IOS-143
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@808
    12
dirk@810
    13
import MessageModel
dirk@810
    14
dirk@784
    15
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
dirk@784
    16
  switch (lhs, rhs) {
dirk@784
    17
  case let (l?, r?):
dirk@784
    18
    return l < r
dirk@784
    19
  case (nil, _?):
dirk@784
    20
    return true
dirk@784
    21
  default:
dirk@784
    22
    return false
dirk@784
    23
  }
dirk@784
    24
}
dirk@784
    25
dirk@784
    26
fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
dirk@784
    27
  switch (lhs, rhs) {
dirk@784
    28
  case let (l?, r?):
dirk@784
    29
    return l > r
dirk@784
    30
  default:
dirk@784
    31
    return rhs < lhs
dirk@784
    32
  }
dirk@784
    33
}
dirk@784
    34
dirk@30
    35
dirk@854
    36
class EmailListViewController: UITableViewController {
dirk@591
    37
    struct EmailListConfig {
dirk@591
    38
        let appConfig: AppConfig
dirk@583
    39
dirk@855
    40
        let account: Account?
dirk@855
    41
dirk@854
    42
        /** The folder to display, if it exists */
ylandert@935
    43
        var folder: Folder?
dirk@854
    44
    }
dirk@583
    45
dirk@854
    46
    var comp = "EmailListViewController"
dirk@58
    47
dirk@583
    48
    struct UIState {
dirk@583
    49
        var isSynching: Bool = false
dirk@583
    50
    }
dirk@583
    51
    
dirk@273
    52
    let segueShowEmail = "segueShowEmail"
dirk@397
    53
    let segueCompose = "segueCompose"
dirk@397
    54
    let segueUserSettings = "segueUserSettings"
dirk@30
    55
dirk@583
    56
    var config: EmailListConfig!
dirk@583
    57
dirk@275
    58
    var state = UIState()
dirk@342
    59
    let dateFormatter = UIHelper.dateFormatterEmailList()
dirk@275
    60
dirk@502
    61
    /**
dirk@502
    62
     The default background color for an email cell, as determined the first time a cell is
dirk@502
    63
     created.
dirk@502
    64
     */
dirk@502
    65
    var defaultCellBackgroundColor: UIColor?
dirk@502
    66
dirk@502
    67
    /**
dirk@502
    68
     Indicates whether `defaultCellBackgroundColor` has been determined or not.
dirk@502
    69
     */
dirk@502
    70
    var determinedCellBackgroundColor: Bool = false
dirk@502
    71
dirk@594
    72
    var refreshController: UIRefreshControl!
dirk@594
    73
dirk@705
    74
    /**
dirk@705
    75
     The message that should be saved as a draft when compose gets aborted.
dirk@705
    76
     */
dirk@841
    77
    var draftMessageToStore: Message?
dirk@705
    78
dirk@719
    79
    /**
dirk@719
    80
     When the user taps on a draft email, this is the message that was selected
dirk@719
    81
     and should be given to the compose view.
dirk@719
    82
     */
dirk@841
    83
    var draftMessageToCompose: Message?
dirk@719
    84
dirk@745
    85
    required init?(coder aDecoder: NSCoder) {
dirk@745
    86
        super.init(coder: aDecoder)
dirk@745
    87
        self.comp = "EmailListViewController"
dirk@745
    88
    }
dirk@745
    89
dirk@855
    90
    func isRead(message: Message)-> Bool {
dirk@965
    91
        return message.imapFlags?.seen ?? false
ana@614
    92
    }
ana@614
    93
dirk@855
    94
    func isImportant(message: Message)-> Bool {
dirk@965
    95
        return message.imapFlags?.flagged ?? false
ana@614
    96
    }
ana@614
    97
dirk@275
    98
    override func viewDidLoad() {
ylandert@935
    99
        super.viewDidLoad()
dirk@356
   100
        UIHelper.variableCellHeightsTableView(self.tableView)
dirk@275
   101
    }
dirk@31
   102
dirk@784
   103
    override func viewWillAppear(_ animated: Bool) {
dirk@854
   104
        updateModel()
ana@207
   105
        super.viewWillAppear(animated)
ana@207
   106
    }
ana@152
   107
dirk@784
   108
    @IBAction func mailSentSegue(_ segue: UIStoryboardSegue) {
dirk@452
   109
    }
dirk@452
   110
dirk@784
   111
    @IBAction func backFromComposeWithoutSavingDraftSegue(_ segue: UIStoryboardSegue) {
dirk@705
   112
    }
dirk@705
   113
dirk@784
   114
    @IBAction func backFromComposeSaveDraftSegue(_ segue: UIStoryboardSegue) {
dirk@867
   115
        guard let message = draftMessageToStore else {
dirk@705
   116
            return
dirk@705
   117
        }
dirk@707
   118
dirk@707
   119
        state.isSynching = true
dirk@707
   120
        updateUI()
dirk@707
   121
dirk@965
   122
        message.imapFlags?.draft = true
dirk@867
   123
dirk@854
   124
        // TODO: IOS 222: Save as draft
dirk@867
   125
        if let folder = draftMessageToStore?.parent as? Folder {
dirk@867
   126
            if folder.folderType == .drafts {
dirk@867
   127
                message.save()
dirk@867
   128
                return
dirk@867
   129
            }
dirk@867
   130
        }
dirk@867
   131
dirk@867
   132
        guard let account = config.account else {
dirk@867
   133
            return
dirk@867
   134
        }
xavier@870
   135
        
xavier@870
   136
        if let folder = account.folder(ofType: FolderType.drafts) {
xavier@870
   137
            folder.save(message: message)
xavier@870
   138
            return
xavier@870
   139
        }
dirk@705
   140
    }
dirk@705
   141
igor@941
   142
    
igor@941
   143
    @IBAction func showUnreadButtonTapped(_ sender: UIBarButtonItem) {
igor@941
   144
        
igor@941
   145
    }
igor@941
   146
    
dirk@854
   147
    func updateModel() {
ylandert@935
   148
        config.folder = MockData.createFolder(config.account!)
dirk@31
   149
    }
dirk@31
   150
dirk@58
   151
    // MARK: - UI State
dirk@58
   152
dirk@58
   153
    func updateUI() {
dirk@784
   154
        UIApplication.shared.isNetworkActivityIndicatorVisible = state.isSynching
dirk@748
   155
        if !state.isSynching {
dirk@594
   156
            self.refreshControl?.endRefreshing()
dirk@58
   157
        }
dirk@58
   158
    }
dirk@58
   159
dirk@31
   160
    // MARK: - UITableViewDataSource
dirk@31
   161
dirk@784
   162
    override func numberOfSections(in tableView: UITableView) -> Int {
dirk@854
   163
        if let _ = config.folder {
dirk@854
   164
            return 1
dirk@209
   165
        }
dirk@854
   166
        return 0
dirk@31
   167
    }
dirk@31
   168
dirk@784
   169
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dirk@854
   170
        if let fol = config.folder {
dirk@854
   171
            return fol.messageCount()
dirk@31
   172
        }
dirk@31
   173
        return 0
dirk@31
   174
    }
dirk@31
   175
dirk@784
   176
    override func tableView(_ tableView: UITableView,
dirk@784
   177
                            cellForRowAt indexPath: IndexPath) -> UITableViewCell {
dirk@784
   178
        let cell = tableView.dequeueReusableCell(
dirk@784
   179
            withIdentifier: "EmailListViewCell", for: indexPath) as! EmailListViewCell
dirk@502
   180
        if !determinedCellBackgroundColor {
dirk@502
   181
            defaultCellBackgroundColor = cell.backgroundColor
dirk@502
   182
            determinedCellBackgroundColor = true
dirk@502
   183
        }
dirk@31
   184
        configureCell(cell, indexPath: indexPath)
dirk@31
   185
        return cell
dirk@31
   186
    }
dirk@31
   187
dirk@861
   188
    /**
dirk@861
   189
     The message at the given position.
dirk@861
   190
     */
dirk@861
   191
    func messageAt(indexPath: IndexPath) -> Message? {
dirk@861
   192
        if let fol = config.folder {
dirk@861
   193
            return fol.messageByIndex(indexPath.row)
dirk@861
   194
        }
dirk@861
   195
        return nil
dirk@861
   196
    }
dirk@861
   197
dirk@719
   198
    // MARK: - UITableViewDelegate
dirk@719
   199
dirk@784
   200
    override func tableView(_ tableView: UITableView,
dirk@784
   201
                            didSelectRowAt indexPath: IndexPath) {
dirk@719
   202
        draftMessageToCompose = nil
dirk@719
   203
dirk@784
   204
        let cell = tableView.cellForRow(at: indexPath)
dirk@719
   205
dirk@855
   206
        if let fol = config.folder {
dirk@855
   207
            if fol.folderType == .drafts {
dirk@855
   208
                draftMessageToCompose = messageAt(indexPath: indexPath)
dirk@784
   209
                performSegue(withIdentifier: segueCompose, sender: cell)
dirk@719
   210
                return
dirk@719
   211
            }
dirk@719
   212
        }
dirk@719
   213
dirk@784
   214
        performSegue(withIdentifier: segueShowEmail, sender: cell)
dirk@719
   215
    }
dirk@719
   216
dirk@784
   217
    override func tableView(_ tableView: UITableView, editActionsForRowAt
dirk@784
   218
        indexPath: IndexPath)-> [UITableViewRowAction]? {
dirk@784
   219
        let cell = tableView.cellForRow(at: indexPath) as! EmailListViewCell
dirk@855
   220
        if let email = messageAt(indexPath: indexPath) {
dirk@855
   221
            let isFlagAction = createIsFlagAction(message: email, cell: cell)
dirk@855
   222
            let deleteAction = createDeleteAction(cell)
igor@1242
   223
            //let isReadAction = createIsReadAction(message: email, cell: cell)
igor@1242
   224
            let moreAction = createMoreAction(message: email, cell: cell)
igor@1242
   225
            return [deleteAction,isFlagAction,moreAction]
dirk@855
   226
        }
dirk@855
   227
        return nil
dirk@744
   228
    }
dirk@744
   229
dirk@719
   230
    // MARK: - Misc
dirk@719
   231
dirk@855
   232
    func configureCell(_ theCell: UITableViewCell, indexPath: IndexPath) {
dirk@745
   233
        guard let cell = theCell as? EmailListViewCell else {
dirk@745
   234
            return
dirk@745
   235
        }
dirk@855
   236
        if let email = messageAt(indexPath: indexPath) {
dirk@855
   237
            if let pEpRating = email.pEpRating {
dirk@855
   238
                let privacyColor = PEPUtil.pEpColorFromRating(pEpRating)
dirk@502
   239
                if let uiColor = UIHelper.textBackgroundUIColorFromPrivacyColor(privacyColor) {
dirk@502
   240
                    cell.backgroundColor = uiColor
dirk@502
   241
                } else {
dirk@502
   242
                    if determinedCellBackgroundColor {
dirk@502
   243
                        cell.backgroundColor = defaultCellBackgroundColor
dirk@502
   244
                    }
dirk@502
   245
                }
dirk@502
   246
            }
dirk@855
   247
            UIHelper.putString(email.from?.displayString, toLabel: cell.senderLabel)
dirk@855
   248
            UIHelper.putString(email.shortMessage, toLabel: cell.subjectLabel)
dirk@572
   249
dirk@572
   250
            // Snippet
dirk@565
   251
            if let text = email.longMessage {
dirk@572
   252
                let theText = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
ylandert@935
   253
                UIHelper.putString(UIHelper.cleanHtml(theText), toLabel: cell.summaryLabel)
dirk@572
   254
            } else if let html = email.longMessageFormatted {
dirk@568
   255
                var text = html.extractTextFromHTML()
dirk@572
   256
                text = text.replaceNewLinesWith(" ").trimmedWhiteSpace()
dirk@568
   257
                UIHelper.putString(text, toLabel: cell.summaryLabel)
dirk@568
   258
            } else {
dirk@568
   259
                UIHelper.putString(nil, toLabel: cell.summaryLabel)
dirk@565
   260
            }
dirk@32
   261
dirk@855
   262
            if let receivedDate = email.received {
dirk@788
   263
                UIHelper.putString(dateFormatter.string(from: receivedDate as Date),
dirk@342
   264
                                   toLabel: cell.dateLabel)
dirk@32
   265
            } else {
dirk@342
   266
                UIHelper.putString(nil, toLabel: cell.dateLabel)
dirk@32
   267
            }
ana@620
   268
dirk@855
   269
            if (isImportant(message: email) && isRead(message: email)) {
dirk@784
   270
                cell.isImportantImage.isHidden = false
dirk@784
   271
                cell.isImportantImage.backgroundColor = UIColor.orange
ana@666
   272
            }
dirk@855
   273
            else if (isImportant(message: email) && !isRead(message: email)) {
dirk@784
   274
                cell.isImportantImage.isHidden = false
dirk@784
   275
                cell.isImportantImage.backgroundColor = UIColor.blue
ana@656
   276
                cell.isImportantImage.layer.borderWidth = 2
dirk@784
   277
                cell.isImportantImage.layer.borderColor = UIColor.orange.cgColor
dirk@855
   278
            } else if (!isImportant(message: email) && isRead(message: email)) {
dirk@784
   279
                    cell.isImportantImage.isHidden = true
dirk@855
   280
            } else if (!isImportant(message: email) && !isRead(message: email)) {
dirk@784
   281
                cell.isImportantImage.isHidden = false
dirk@784
   282
                cell.isImportantImage.backgroundColor = UIColor.blue
ana@620
   283
            }
dirk@31
   284
        }
dirk@30
   285
    }
dirk@30
   286
dirk@784
   287
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
dirk@606
   288
        // Make sure the current account is set, if defined
dirk@606
   289
        config.appConfig.currentAccount = config.account
dirk@606
   290
dirk@361
   291
        if segue.identifier == segueCompose {
dirk@784
   292
            let destination = segue.destination
dirk@434
   293
                as! ComposeViewController
dirk@583
   294
            destination.appConfig = config.appConfig
dirk@719
   295
            if let draft = draftMessageToCompose {
dirk@965
   296
                draft.imapFlags?.seen = true
dirk@723
   297
                config.appConfig.model.save()
dirk@723
   298
dirk@719
   299
                destination.originalMessage = draft
dirk@784
   300
                destination.composeMode = .composeDraft
dirk@719
   301
            }
dirk@273
   302
        } else if segue.identifier == segueShowEmail {
dirk@273
   303
            guard
dirk@784
   304
                let vc = segue.destination as? EmailViewController,
dirk@273
   305
                let cell = sender as? UITableViewCell,
dirk@784
   306
                let indexPath = self.tableView.indexPath(for: cell),
dirk@855
   307
                let email = messageAt(indexPath: indexPath) else {
dirk@273
   308
                    return
dirk@273
   309
            }
dirk@583
   310
            vc.appConfig = config.appConfig
dirk@273
   311
            vc.message = email
ana@246
   312
        }
ana@246
   313
    }
dirk@744
   314
dirk@855
   315
    func syncFlagsToServer(message: Message) {
dirk@855
   316
        // TODO: IOS 222: Sync flags back to server
dirk@744
   317
    }
dirk@744
   318
dirk@855
   319
    func createIsFlagAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   320
        // preparing the title action to show when user swipe
igor@1242
   321
//        var localizedIsFlagTitle = " "
igor@1242
   322
//        if (isImportant(message: message)) {
igor@1242
   323
//            localizedIsFlagTitle = NSLocalizedString(
igor@1242
   324
//                "Unflag",
igor@1242
   325
//                comment: "Unflag button title in swipe action on EmailListViewController")
igor@1242
   326
//        } else {
igor@1242
   327
//            localizedIsFlagTitle = NSLocalizedString(
igor@1242
   328
//                "Flag",
igor@1242
   329
//                comment: "Flag button title in swipe action on EmailListViewController")
igor@1242
   330
//        }
dirk@744
   331
dirk@744
   332
        // preparing action to trigger when user swipe
dirk@784
   333
        let isFlagCompletionHandler: (UITableViewRowAction, IndexPath) -> Void =
dirk@744
   334
            { (action, indexPath) in
dirk@855
   335
                if (self.isImportant(message: message)) {
dirk@965
   336
                    message.imapFlags?.flagged = false
dirk@744
   337
dirk@744
   338
                } else {
dirk@965
   339
                    message.imapFlags?.flagged = true
dirk@744
   340
                }
dirk@855
   341
                self.syncFlagsToServer(message: message)
dirk@784
   342
                self.tableView.reloadRows(at: [indexPath], with: .none)
dirk@744
   343
        }
dirk@744
   344
        // creating the action
igor@1242
   345
        let isFlagAction = UITableViewRowAction(style: .normal, title: "          ",
dirk@744
   346
                                                handler: isFlagCompletionHandler)
dirk@744
   347
        // changing default action color
igor@1242
   348
        let swipeFlagImage = UIImage(named: "swipe-flag")
igor@1242
   349
        let flagIconColor = UIColor(patternImage: swipeFlagImage!)
igor@1242
   350
        isFlagAction.backgroundColor = flagIconColor
dirk@744
   351
dirk@744
   352
        return isFlagAction
dirk@744
   353
    }
dirk@744
   354
dirk@784
   355
    func createDeleteAction (_ cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   356
dirk@744
   357
        // preparing the title action to show when user swipe
dirk@744
   358
dirk@784
   359
        let deleteCompletionHandler: (UITableViewRowAction, IndexPath) -> Void =
dirk@744
   360
            { (action, indexPath) in
dirk@855
   361
                let message = self.messageAt(indexPath: indexPath)
dirk@965
   362
                message?.imapFlags?.deleted = true
dirk@855
   363
                self.syncFlagsToServer(message: message!)
dirk@744
   364
        }
dirk@744
   365
dirk@744
   366
        // creating the action
igor@1242
   367
        let deleteAction = UITableViewRowAction(style: .normal, title: "          ",
dirk@744
   368
                                                handler: deleteCompletionHandler)
igor@1242
   369
        let swipeTrashImage = UIImage(named: "swipe-trash")
igor@1242
   370
        let trashIconColor = UIColor(patternImage: swipeTrashImage!)
igor@1242
   371
        deleteAction.backgroundColor = trashIconColor
dirk@744
   372
        return deleteAction
dirk@744
   373
    }
dirk@744
   374
dirk@855
   375
    func createIsReadAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
dirk@744
   376
        // preparing the title action to show when user swipe
dirk@744
   377
        var localizedisReadTitle = " "
dirk@855
   378
        if (isRead(message: message)) {
dirk@855
   379
            localizedisReadTitle = NSLocalizedString(
dirk@855
   380
                "Unread",
dirk@855
   381
                comment: "Unread button title in swipe action on EmailListViewController")
dirk@744
   382
        } else {
dirk@855
   383
            localizedisReadTitle = NSLocalizedString(
dirk@855
   384
                "Read",
dirk@855
   385
                comment: "Read button title in swipe action on EmailListViewController")
dirk@744
   386
        }
dirk@744
   387
dirk@744
   388
        // creating the action
dirk@784
   389
        let isReadCompletionHandler: (UITableViewRowAction, IndexPath) -> Void =
dirk@744
   390
            { (action, indexPath) in
dirk@855
   391
                if (self.isRead(message: message)) {
dirk@965
   392
                    message.imapFlags?.seen = false
dirk@744
   393
                } else {
dirk@965
   394
                    message.imapFlags?.seen = true
dirk@744
   395
                }
dirk@855
   396
                self.syncFlagsToServer(message: message)
dirk@784
   397
                self.tableView.reloadRows(at: [indexPath], with: .none)
dirk@744
   398
        }
dirk@784
   399
        let isReadAction = UITableViewRowAction(style: .default, title: localizedisReadTitle,
dirk@744
   400
                                                handler: isReadCompletionHandler)
dirk@784
   401
        isReadAction.backgroundColor = UIColor.blue
dirk@744
   402
dirk@744
   403
        return isReadAction
dirk@744
   404
    }
igor@1242
   405
    
igor@1242
   406
    func createMoreAction(message: Message, cell: EmailListViewCell) -> UITableViewRowAction {
igor@1242
   407
        let moreCompletitionHandler :(UITableViewRowAction, IndexPath) -> Void = {(action, indexPath) in
igor@1242
   408
            self.showMoreActionSheet(cell: cell)
igor@1242
   409
        }
igor@1242
   410
        let moreAction = UITableViewRowAction(style: .normal, title: "          ", handler: moreCompletitionHandler)
igor@1242
   411
        let swipeMoreImage = UIImage(named: "swipe-more")
igor@1242
   412
        let moreIconColor = UIColor(patternImage: swipeMoreImage!)
igor@1242
   413
        moreAction.backgroundColor = moreIconColor
igor@1242
   414
        return moreAction
igor@1242
   415
    }
igor@1242
   416
    
igor@1242
   417
    // MARK: - Action Sheet
igor@1242
   418
    
igor@1242
   419
    func showMoreActionSheet(cell: EmailListViewCell) {
igor@1242
   420
        let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
igor@1242
   421
        let cancelAction = createCancelAction()
igor@1242
   422
        let replyAction = createReplyAction(cell: cell)
igor@1242
   423
        let forwardAction = createForwardAction(cell: cell)
igor@1242
   424
        let markAction = createMarkAction()
igor@1242
   425
        alertControler.addAction(cancelAction)
igor@1242
   426
        alertControler.addAction(replyAction)
igor@1242
   427
        alertControler.addAction(forwardAction)
igor@1242
   428
        alertControler.addAction(markAction)
igor@1242
   429
        present(alertControler, animated: true, completion: nil)
igor@1242
   430
    }
igor@1242
   431
    
igor@1242
   432
    // MARK: - Action Sheet Actions
igor@1242
   433
igor@1242
   434
    func createCancelAction() -> UIAlertAction {
igor@1242
   435
      return  UIAlertAction(title: "Cancel", style: .cancel) { (action) in}
igor@1242
   436
    }
igor@1242
   437
    
igor@1242
   438
    func createReplyAction(cell: EmailListViewCell) ->  UIAlertAction {
igor@1242
   439
        return UIAlertAction(title: "Reply", style: .default) { (action) in
igor@1242
   440
            self.performSegue(withIdentifier: self.segueCompose, sender: cell)
igor@1242
   441
        }
igor@1242
   442
    }
igor@1242
   443
    
igor@1242
   444
    func createForwardAction(cell: EmailListViewCell) -> UIAlertAction {
igor@1242
   445
        return UIAlertAction(title: "Forward", style: .default) { (action) in
igor@1242
   446
            self.performSegue(withIdentifier: self.segueCompose, sender: cell)
igor@1242
   447
        }
igor@1242
   448
    }
igor@1242
   449
    
igor@1242
   450
    func createMarkAction() -> UIAlertAction {
igor@1242
   451
        return UIAlertAction(title: "Mark", style: .default) { (action) in
igor@1242
   452
        }
igor@1242
   453
    }
dirk@784
   454
}