pEpForiOS/UI/EmailDisplay/EmailListViewController.swift
author Xavier Algarra <xavier@pep-project.org>
Tue, 07 Nov 2017 12:59:41 +0100
changeset 3368 e79ab6166c5b
parent 3291 e8000000e820
child 3369 e99419ad3c71
permissions -rw-r--r--
IOS-801 fix with big images
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 UIKit
dirk@810
    10
import MessageModel
xavier@3368
    11
import SwipeCellKit
dirk@810
    12
xavier@3368
    13
class EmailListViewController: BaseTableViewController, SwipeTableViewCellDelegate {
andreas@3223
    14
    var folderToShow: Folder?
andreas@3223
    15
andreas@3151
    16
    func updateLastLookAt() {
andreas@3224
    17
        guard let saveFolder = folderToShow else {
andreas@3151
    18
            return
andreas@3151
    19
        }
andreas@3151
    20
        saveFolder.updateLastLookAt()
andreas@3151
    21
    }
andreas@3208
    22
    
andreas@3151
    23
    private var model: EmailListViewModel?
andreas@3208
    24
    
andreas@3151
    25
    private let queue: OperationQueue = {
andreas@3151
    26
        let createe = OperationQueue()
andreas@3151
    27
        createe.qualityOfService = .userInteractive
andreas@3151
    28
        createe.maxConcurrentOperationCount = 10
andreas@3151
    29
        return createe
andreas@3151
    30
    }()
andreas@3151
    31
    private var operations = [IndexPath:Operation]()
andreas@2838
    32
    public static let storyboardId = "EmailListViewController"
andreas@3151
    33
    fileprivate var lastSelectedIndexPath: IndexPath?
andreas@3208
    34
    
igor@1301
    35
    let searchController = UISearchController(searchResultsController: nil)
xavier@3368
    36
xavier@3368
    37
    //swipe acctions types
xavier@3368
    38
    var buttonDisplayMode: ButtonDisplayMode = .titleAndImage
xavier@3368
    39
    var buttonStyle: ButtonStyle = .backgroundColor
andreas@3208
    40
    
andreas@3151
    41
    // MARK: - Outlets
andreas@3208
    42
    
dirk@2067
    43
    @IBOutlet weak var enableFilterButton: UIBarButtonItem!
dirk@2067
    44
    @IBOutlet weak var textFilterButton: UIBarButtonItem!
andreas@3151
    45
    @IBOutlet var showFoldersButton: UIBarButtonItem!
andreas@3208
    46
    
andreas@3151
    47
    // MARK: - Life Cycle
andreas@3208
    48
    
dirk@275
    49
    override func viewDidLoad() {
ylandert@935
    50
        super.viewDidLoad()
dirk@2189
    51
        title = NSLocalizedString("Inbox", comment: "General name for (unified) inbox")
igor@1284
    52
        UIHelper.emailListTableHeight(self.tableView)
andreas@3163
    53
        self.textFilterButton.isEnabled = false
igor@1301
    54
        addSearchBar()
dirk@275
    55
    }
andreas@3208
    56
    
dirk@784
    57
    override func viewWillAppear(_ animated: Bool) {
igor@1301
    58
        super.viewWillAppear(animated)
xavier@2325
    59
        self.navigationController?.setToolbarHidden(false, animated: true)
dirk@1344
    60
        if MiscUtil.isUnitTest() {
dirk@1344
    61
            return
dirk@1344
    62
        }
andreas@3223
    63
dirk@1826
    64
        setDefaultColors()
andreas@3151
    65
        setup()
andreas@3208
    66
        
dirk@2191
    67
        // Mark this folder as having been looked at by the user
andreas@3151
    68
        updateLastLookAt()
andreas@3151
    69
        setupFoldersBarButton()
andreas@3151
    70
    }
andreas@3208
    71
    
andreas@3151
    72
    // MARK: - NavigationBar
andreas@3208
    73
    
andreas@3151
    74
    private func hideFoldersNavigationBarButton() {
andreas@3151
    75
        self.showFoldersButton.isEnabled = false
andreas@3151
    76
        self.showFoldersButton.tintColor = UIColor.clear
andreas@3151
    77
    }
andreas@3208
    78
    
andreas@3151
    79
    private func showFoldersNavigationBarButton() {
andreas@3151
    80
        self.showFoldersButton.isEnabled = true
andreas@3151
    81
        self.showFoldersButton.tintColor = nil
andreas@3151
    82
    }
andreas@3208
    83
    
andreas@3151
    84
    private func resetModel() {
andreas@3223
    85
        if folderToShow != nil {
andreas@3291
    86
            model = EmailListViewModel(delegate: self,
andreas@3291
    87
                                       messageSyncService: appConfig.messageSyncService,
andreas@3291
    88
                                       folderToShow: folderToShow)
xavier@2585
    89
        }
dirk@1348
    90
    }
andreas@3208
    91
    
andreas@3151
    92
    private func setup() {
andreas@3289
    93
        if noAccountsExist() {
andreas@3272
    94
            // No account exists. Show account setup.
andreas@3272
    95
            performSegue(withIdentifier:.segueAddNewAccount, sender: self)
andreas@3289
    96
        } else if let vm = model {
andreas@3223
    97
            // We came back from e.g EmailView ...
andreas@3223
    98
            updateFilterText()
andreas@3223
    99
            // ... so we want to update "seen" status
andreas@3226
   100
            vm.reloadData()
andreas@3272
   101
        } else if folderToShow == nil {
andreas@3223
   102
            // We have not been created to show a specific folder, thus we show unified inbox
andreas@3151
   103
            folderToShow = UnifiedInbox()
andreas@3223
   104
            resetModel()
andreas@3272
   105
        } else if model == nil {
andreas@3223
   106
            // We still got no model, because:
andreas@3223
   107
            // - We are not coming back from a pushed view (for instance ComposeEmailView)
andreas@3223
   108
            // - We are not a UnifiedInbox
andreas@3226
   109
            // So we have been created to show a specific folder. Show it!
andreas@3223
   110
            resetModel()
andreas@3151
   111
        }
andreas@3272
   112
andreas@3151
   113
        self.title = realNameOfFolderToShow()
andreas@2683
   114
    }
andreas@3223
   115
andreas@3223
   116
    private func weCameBackFromAPushedView() -> Bool {
andreas@3223
   117
        return model != nil
andreas@3223
   118
    }
andreas@3208
   119
    
andreas@3151
   120
    private func noAccountsExist() -> Bool {
andreas@3151
   121
        return Account.all().isEmpty
igor@1301
   122
    }
andreas@3208
   123
    
andreas@3151
   124
    private func setupFoldersBarButton() {
andreas@3151
   125
        if let size = navigationController?.viewControllers.count, size > 1 {
andreas@3151
   126
            hideFoldersNavigationBarButton()
andreas@3151
   127
        } else {
andreas@3151
   128
            showFoldersNavigationBarButton()
xavier@2132
   129
        }
igor@1338
   130
    }
andreas@3208
   131
    
andreas@3151
   132
    private func addSearchBar() {
igor@1301
   133
        searchController.searchResultsUpdater = self
igor@1301
   134
        searchController.dimsBackgroundDuringPresentation = false
igor@1301
   135
        searchController.delegate = self
igor@1301
   136
        definesPresentationContext = true
igor@1301
   137
        tableView.tableHeaderView = searchController.searchBar
xavier@3209
   138
        tableView.setContentOffset(CGPoint(x: 0.0, y: searchController.searchBar.frame.size.height), animated: false)
igor@1301
   139
    }
andreas@3208
   140
    
andreas@3151
   141
    // MARK: - Other
andreas@3208
   142
    
andreas@3151
   143
    private func realNameOfFolderToShow() -> String? {
andreas@3224
   144
        return folderToShow?.realName
andreas@2697
   145
    }
andreas@3208
   146
    
andreas@3151
   147
    private func configure(cell: EmailListViewCell, for indexPath: IndexPath) {
andreas@3151
   148
        // Configure lightweight stuff on main thread ...
andreas@3151
   149
        guard let saveModel = model else {
andreas@3151
   150
            return
xavier@2967
   151
        }
andreas@3151
   152
        guard let row = saveModel.row(for: indexPath) else {
andreas@3151
   153
            Log.shared.errorAndCrash(component: #function, errorString: "We should have a row here")
andreas@3151
   154
            return
andreas@3151
   155
        }
andreas@3151
   156
        cell.senderLabel.text = row.from
andreas@3151
   157
        cell.subjectLabel.text = row.subject
andreas@3151
   158
        cell.summaryLabel.text = row.bodyPeek
andreas@3151
   159
        cell.isFlagged = row.isFlagged
andreas@3151
   160
        cell.isSeen = row.isSeen
andreas@3151
   161
        cell.hasAttachment = row.showAttchmentIcon
andreas@3151
   162
        cell.dateLabel.text = row.dateText
andreas@3151
   163
        // Set image from cache if any
andreas@3151
   164
        cell.setContactImage(image: row.senderContactImage)
andreas@3208
   165
        
andreas@3151
   166
        let op = BlockOperation() { [weak self] in
andreas@3151
   167
            // ... and expensive computations in background
andreas@3151
   168
            guard let strongSelf = self else {
andreas@3151
   169
                // View is gone, nothing to do.
andreas@3151
   170
                return
andreas@3151
   171
            }
andreas@3208
   172
            
andreas@3151
   173
            var senderImage: UIImage?
andreas@3151
   174
            if row.senderContactImage == nil {
andreas@3220
   175
                // image for identity has not been cached yet
andreas@3220
   176
                // Get and cache it here in the background ...
andreas@3151
   177
                senderImage = strongSelf.model?.senderImage(forCellAt: indexPath)
andreas@3220
   178
andreas@3220
   179
                // ... and set it on the main queue
andreas@3220
   180
                DispatchQueue.main.async {
andreas@3220
   181
                    if senderImage != nil && senderImage != cell.contactImageView.image {
andreas@3220
   182
                        cell.contactImageView.image  = senderImage
andreas@3220
   183
                    }
andreas@3220
   184
                }
andreas@3151
   185
            }
andreas@3220
   186
andreas@3220
   187
            let pEpRatingImage = strongSelf.model?.pEpRatingColorImage(forCellAt: indexPath)
andreas@3220
   188
andreas@3151
   189
            // In theory we want to set all data in *one* async call. But as pEpRatingColorImage takes
andreas@3151
   190
            // very long, we are setting the sender image seperatelly.
andreas@3151
   191
            DispatchQueue.main.async {
andreas@3151
   192
                if pEpRatingImage != nil {
andreas@3151
   193
                    cell.setPepRatingImage(image: pEpRatingImage)
andreas@3151
   194
                }
andreas@3151
   195
            }
andreas@3151
   196
        }
andreas@3151
   197
        queue(operation: op, for: indexPath)
xavier@2967
   198
    }
andreas@3208
   199
    
andreas@3151
   200
    // MARK: - Actions
andreas@3208
   201
    
andreas@3151
   202
    @IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
andreas@3163
   203
        guard let vm = model else {
andreas@3163
   204
            Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
andreas@3163
   205
            return
andreas@3163
   206
        }
andreas@3163
   207
        vm.isFilterEnabled = !vm.isFilterEnabled
xavier@3193
   208
        updateFilterButtonView()
xavier@1832
   209
    }
andreas@3208
   210
    
xavier@3193
   211
    func updateFilterButtonView() {
andreas@3163
   212
        guard let vm = model else {
andreas@3163
   213
            Log.shared.errorAndCrash(component: #function, errorString: "We should have a model here")
andreas@3163
   214
            return
andreas@3163
   215
        }
andreas@3208
   216
        
andreas@3163
   217
        textFilterButton.isEnabled = vm.isFilterEnabled
andreas@3163
   218
        if textFilterButton.isEnabled {
andreas@3163
   219
            enableFilterButton.image = UIImage(named: "unread-icon-active")
andreas@3163
   220
            updateFilterText()
andreas@3163
   221
        } else {
andreas@3163
   222
            textFilterButton.title = ""
andreas@3163
   223
            enableFilterButton.image = UIImage(named: "unread-icon")
andreas@3163
   224
        }
andreas@3163
   225
    }
andreas@3208
   226
    
andreas@3163
   227
    func updateFilterText() {
xavier@3193
   228
        if let vm = model, let txt = vm.activeFilter?.title {
andreas@3163
   229
            textFilterButton.title = "Filter by: " + txt
andreas@3163
   230
        }
andreas@3151
   231
    }
andreas@3208
   232
    
dirk@31
   233
    // MARK: - UITableViewDataSource
andreas@3208
   234
    
dirk@784
   235
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
andreas@3151
   236
        return model?.rowCount ?? 0
dirk@31
   237
    }
andreas@3208
   238
    
dirk@784
   239
    override func tableView(_ tableView: UITableView,
dirk@784
   240
                            cellForRowAt indexPath: IndexPath) -> UITableViewCell {
andreas@3151
   241
        guard let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
andreas@3151
   242
                                                       for: indexPath) as? EmailListViewCell
andreas@3151
   243
            else {
andreas@3151
   244
                Log.shared.errorAndCrash(component: #function, errorString: "Wrong cell!")
andreas@3151
   245
                return UITableViewCell()
andreas@3151
   246
        }
xavier@3368
   247
        cell.delegate = self
andreas@3151
   248
        configure(cell: cell, for: indexPath)
dirk@31
   249
        return cell
dirk@31
   250
    }
andreas@3208
   251
    
dirk@719
   252
    // MARK: - UITableViewDelegate
xavier@3368
   253
xavier@3368
   254
xavier@3368
   255
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
xavier@3368
   256
        if indexPath.section == 0 {
xavier@3368
   257
            let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
xavier@3368
   258
                //self.viewModel.delete(section: indexPath.section, cell: indexPath.row)
xavier@3368
   259
            }
xavier@3368
   260
            configure(action: deleteAction, with: .trash)
xavier@3368
   261
xavier@3368
   262
            let flagAction = SwipeAction(style: .default, title: "Flag") { action, indexPath in
xavier@3368
   263
                //self.viewModel.delete(section: indexPath.section, cell: indexPath.row)
xavier@3368
   264
            }
xavier@3368
   265
            flagAction.hidesWhenSelected = true
xavier@3368
   266
            configure(action: flagAction, with: .flag)
xavier@3368
   267
xavier@3368
   268
            let moreAction = SwipeAction(style: .default, title: "More") { action, indexPath in
xavier@3368
   269
                //self.viewModel.delete(section: indexPath.section, cell: indexPath.row)
xavier@3368
   270
            }
xavier@3368
   271
            moreAction.hidesWhenSelected = true
xavier@3368
   272
            configure(action: moreAction, with: .more)
xavier@3368
   273
            return (orientation == .right ?   [deleteAction, flagAction, moreAction] : nil)
xavier@3368
   274
        }
xavier@3368
   275
xavier@3368
   276
        return nil
xavier@3368
   277
    }
xavier@3368
   278
xavier@3368
   279
    func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
xavier@3368
   280
        var options = SwipeTableOptions()
xavier@3368
   281
        options.expansionStyle = .destructive
xavier@3368
   282
        options.transitionStyle = .border
xavier@3368
   283
        options.buttonSpacing = 11
xavier@3368
   284
        return options
xavier@3368
   285
    }
xavier@3368
   286
xavier@3368
   287
    func configure(action: SwipeAction, with descriptor: ActionDescriptor) {
xavier@3368
   288
        action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
xavier@3368
   289
        action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
xavier@3368
   290
xavier@3368
   291
        switch buttonStyle {
xavier@3368
   292
        case .backgroundColor:
xavier@3368
   293
            action.backgroundColor = descriptor.color
xavier@3368
   294
        case .circular:
xavier@3368
   295
            action.backgroundColor = .clear
xavier@3368
   296
            action.textColor = descriptor.color
xavier@3368
   297
            action.font = .systemFont(ofSize: 13)
xavier@3368
   298
            action.transitionDelegate = ScaleTransition.default
xavier@3368
   299
        }
xavier@3368
   300
    }
xavier@3368
   301
dirk@784
   302
    override func tableView(_ tableView: UITableView, editActionsForRowAt
dirk@784
   303
        indexPath: IndexPath)-> [UITableViewRowAction]? {
andreas@3151
   304
        guard let flagAction = createFlagAction(forCellAt: indexPath),
andreas@3151
   305
            let deleteAction = createDeleteAction(forCellAt: indexPath),
andreas@3151
   306
            let moreAction = createMoreAction(forCellAt: indexPath) else {
andreas@3151
   307
                Log.shared.errorAndCrash(component: #function, errorString: "Error creating action.")
andreas@3151
   308
                return nil
dirk@855
   309
        }
andreas@3151
   310
        return [deleteAction, flagAction, moreAction]
dirk@744
   311
    }
andreas@3208
   312
    
andreas@3151
   313
    override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
andreas@3151
   314
        cancelOperation(for: indexPath)
dirk@744
   315
    }
andreas@3208
   316
    
andreas@3151
   317
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
andreas@3151
   318
        lastSelectedIndexPath = indexPath
andreas@3151
   319
        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
andreas@3151
   320
        performSegue(withIdentifier: SegueIdentifier.segueShowEmail, sender: self)
dirk@744
   321
    }
andreas@3278
   322
andreas@3291
   323
    // Implemented to get informed about the scrolling position.
andreas@3291
   324
    // If the user has scrolled down (almost) to the end, we need to get older emails to display.
andreas@3291
   325
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
andreas@3291
   326
                            forRowAt indexPath: IndexPath) {
andreas@3278
   327
        guard let vm = model else {
andreas@3291
   328
            Log.shared.errorAndCrash(component: #function, errorString: "No model.")
andreas@3291
   329
            return
andreas@3278
   330
        }
andreas@3291
   331
        vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
andreas@3278
   332
    }
andreas@3278
   333
andreas@3151
   334
    // MARK: - Queue Handling
andreas@3208
   335
    
andreas@3151
   336
    private func queue(operation op:Operation, for indexPath: IndexPath) {
andreas@3151
   337
        operations[indexPath] = op
andreas@3151
   338
        queue.addOperation(op)
dirk@1507
   339
    }
andreas@3208
   340
    
andreas@3151
   341
    private func cancelOperation(for indexPath:IndexPath) {
andreas@3151
   342
        guard let op = operations.removeValue(forKey: indexPath) else {
andreas@3151
   343
            return
dirk@744
   344
        }
andreas@3151
   345
        if !op.isCancelled  {
andreas@3151
   346
            op.cancel()
dirk@1507
   347
        }
dirk@744
   348
    }
andreas@3291
   349
andreas@3291
   350
    // MARK: -
andreas@3291
   351
andreas@3151
   352
    override func didReceiveMemoryWarning() {
andreas@3151
   353
        model?.freeMemory()
andreas@3151
   354
    }
andreas@3151
   355
}
andreas@3151
   356
andreas@3151
   357
// MARK: - UISearchResultsUpdating, UISearchControllerDelegate
andreas@3151
   358
andreas@3151
   359
extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
andreas@3151
   360
    public func updateSearchResults(for searchController: UISearchController) {
andreas@3151
   361
        guard let vm = model, let searchText = searchController.searchBar.text else {
andreas@3151
   362
            return
igor@1242
   363
        }
andreas@3163
   364
        vm.setSearchFilter(forSearchText: searchText)
igor@1242
   365
    }
andreas@3208
   366
    
andreas@3151
   367
    func didDismissSearchController(_ searchController: UISearchController) {
andreas@3151
   368
        guard let vm = model else {
andreas@3151
   369
            return
andreas@3151
   370
        }
andreas@3151
   371
        vm.removeSearchFilter()
andreas@3151
   372
    }
andreas@3151
   373
}
xavier@1623
   374
andreas@3151
   375
// MARK: - EmailListModelDelegate
andreas@3151
   376
andreas@3151
   377
extension EmailListViewController: EmailListViewModelDelegate {
andreas@3151
   378
    func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPath: IndexPath) {
andreas@3166
   379
        tableView.beginUpdates()
andreas@3151
   380
        tableView.insertRows(at: [indexPath], with: .automatic)
andreas@3151
   381
        tableView.endUpdates()
andreas@3151
   382
    }
andreas@3208
   383
    
andreas@3151
   384
    func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPath: IndexPath) {
andreas@3151
   385
        tableView.beginUpdates()
andreas@3151
   386
        tableView.deleteRows(at: [indexPath], with: .automatic)
andreas@3151
   387
        tableView.endUpdates()
andreas@3151
   388
    }
andreas@3208
   389
    
andreas@3151
   390
    func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPath: IndexPath) {
andreas@3151
   391
        tableView.beginUpdates()
andreas@3151
   392
        tableView.reloadRows(at: [indexPath], with: .none)
andreas@3151
   393
        tableView.endUpdates()
andreas@3151
   394
    }
andreas@3208
   395
    
andreas@3151
   396
    func updateView() {
andreas@3151
   397
        self.tableView.reloadData()
andreas@3151
   398
    }
andreas@3151
   399
}
andreas@3151
   400
andreas@3151
   401
// MARK: - ActionSheet & ActionSheet Actions
andreas@3151
   402
andreas@3151
   403
extension EmailListViewController {
andreas@3166
   404
    func showMoreActionSheet(forRowAt indexPath: IndexPath) {
andreas@3151
   405
        lastSelectedIndexPath = indexPath
igor@1242
   406
        let alertControler = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
igor@1438
   407
        alertControler.view.tintColor = .pEpGreen
igor@1242
   408
        let cancelAction = createCancelAction()
andreas@3151
   409
        let replyAction = createReplyAction()
andreas@3151
   410
        let replyAllAction = createReplyAllAction()
andreas@3151
   411
        let forwardAction = createForwardAction()
igor@1242
   412
        alertControler.addAction(cancelAction)
igor@1242
   413
        alertControler.addAction(replyAction)
xavier@1620
   414
        alertControler.addAction(replyAllAction)
igor@1242
   415
        alertControler.addAction(forwardAction)
igor@1414
   416
        if let popoverPresentationController = alertControler.popoverPresentationController {
andreas@3151
   417
            popoverPresentationController.sourceView = tableView
igor@1414
   418
        }
igor@1242
   419
        present(alertControler, animated: true, completion: nil)
igor@1242
   420
    }
andreas@3208
   421
    
andreas@3151
   422
    // MARK: Action Sheet Actions
andreas@3208
   423
    
igor@1242
   424
    func createCancelAction() -> UIAlertAction {
andreas@3151
   425
        return  UIAlertAction(title: "Cancel", style: .cancel) { (action) in
andreas@3151
   426
            self.tableView.beginUpdates()
andreas@3151
   427
            self.tableView.setEditing(false, animated: true)
andreas@3151
   428
            self.tableView.endUpdates()
igor@1242
   429
        }
igor@1242
   430
    }
andreas@3208
   431
    
andreas@3151
   432
    func createReplyAction() ->  UIAlertAction {
andreas@3151
   433
        return UIAlertAction(title: "Reply", style: .default) { (action) in
andreas@3151
   434
            self.performSegue(withIdentifier: .segueReply, sender: self)
xavier@1620
   435
        }
xavier@1620
   436
    }
andreas@3208
   437
    
andreas@3151
   438
    func createReplyAllAction() ->  UIAlertAction {
andreas@3151
   439
        return UIAlertAction(title: "Reply All", style: .default) { (action) in
andreas@3151
   440
            self.performSegue(withIdentifier: .segueReplyAll, sender: self)
igor@1242
   441
        }
igor@1242
   442
    }
andreas@3208
   443
    
andreas@3151
   444
    func createForwardAction() -> UIAlertAction {
andreas@3151
   445
        return UIAlertAction(title: "Forward", style: .default) { (action) in
andreas@3151
   446
            self.performSegue(withIdentifier: .segueForward, sender: self)
xavier@2369
   447
        }
igor@1301
   448
    }
igor@1301
   449
}
igor@1338
   450
andreas@3151
   451
// MARK: - TableViewCell Actions
andreas@3151
   452
andreas@3151
   453
extension EmailListViewController {
andreas@3151
   454
    private func createRowAction(image: UIImage?,
xavier@3266
   455
                                 action: @escaping (UITableViewRowAction, IndexPath) -> Void
xavier@3266
   456
        ) -> UITableViewRowAction {
xavier@3266
   457
        let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
andreas@3151
   458
        if let theImage = image {
andreas@3151
   459
            let iconColor = UIColor(patternImage: theImage)
andreas@3151
   460
            rowAction.backgroundColor = iconColor
andreas@3151
   461
        }
andreas@3151
   462
        return rowAction
andreas@3151
   463
    }
andreas@3208
   464
    
andreas@3151
   465
    func createFlagAction(forCellAt indexPath: IndexPath) -> UITableViewRowAction? {
andreas@3151
   466
        guard let row = model?.row(for: indexPath) else {
andreas@3151
   467
            Log.shared.errorAndCrash(component: #function, errorString: "No data for indexPath!")
andreas@3151
   468
            return nil
andreas@3151
   469
        }
andreas@3151
   470
        func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
andreas@3151
   471
            if row.isFlagged {
andreas@3151
   472
                model?.unsetFlagged(forIndexPath: indexPath)
andreas@3151
   473
            } else {
andreas@3151
   474
                model?.setFlagged(forIndexPath: indexPath)
andreas@3151
   475
            }
andreas@3151
   476
            tableView.beginUpdates()
andreas@3151
   477
            tableView.setEditing(false, animated: true)
andreas@3166
   478
            tableView.reloadRows(at: [indexPath], with: .none)
andreas@3151
   479
            tableView.endUpdates()
andreas@3151
   480
        }
xavier@3266
   481
        return createRowAction(image: UIImage(named: "swipe-flag"), action: action)
andreas@3151
   482
    }
andreas@3208
   483
    
andreas@3151
   484
    func createDeleteAction(forCellAt indexPath: IndexPath) -> UITableViewRowAction? {
andreas@3151
   485
        func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
andreas@3151
   486
            tableView.beginUpdates()
andreas@3151
   487
            model?.delete(forIndexPath: indexPath) // mark for deletion/trash
andreas@3151
   488
            tableView.deleteRows(at: [indexPath], with: .none)
andreas@3151
   489
            tableView.endUpdates()
andreas@3151
   490
        }
xavier@3266
   491
xavier@3266
   492
        return createRowAction(image: UIImage(named: "swipe-trash"), action: action)
andreas@3151
   493
    }
andreas@3208
   494
    
andreas@3151
   495
    func createMoreAction(forCellAt indexPath: IndexPath) -> UITableViewRowAction? {
andreas@3151
   496
        func action(action: UITableViewRowAction, indexPath: IndexPath) -> Void {
andreas@3151
   497
            self.showMoreActionSheet(forRowAt: indexPath)
andreas@3151
   498
        }
xavier@3266
   499
andreas@3151
   500
        return createRowAction(image: UIImage(named: "swipe-more"),
xavier@3266
   501
                               action: action)
andreas@3151
   502
    }
andreas@3151
   503
}
andreas@3151
   504
andreas@3151
   505
// MARK: - SegueHandlerType
igor@1338
   506
igor@1338
   507
extension EmailListViewController: SegueHandlerType {
andreas@3208
   508
    
igor@1338
   509
    enum SegueIdentifier: String {
igor@1338
   510
        case segueAddNewAccount
igor@1338
   511
        case segueShowEmail
igor@1338
   512
        case segueCompose
xavier@2715
   513
        case segueReply
xavier@1623
   514
        case segueReplyAll
xavier@1664
   515
        case segueForward
xavier@1865
   516
        case segueFilter
xavier@2248
   517
        case segueFolderViews
igor@1338
   518
        case noSegue
igor@1338
   519
    }
andreas@3208
   520
    
andreas@3110
   521
    private func setup(composeViewController vc: ComposeTableViewController,
andreas@3110
   522
                       composeMode: ComposeTableViewController.ComposeMode = .normal,
andreas@3110
   523
                       originalMessage: Message? = nil) {
andreas@3110
   524
        vc.appConfig = appConfig
andreas@3110
   525
        vc.composeMode = composeMode
andreas@3110
   526
        vc.originalMessage = originalMessage
andreas@3223
   527
        vc.origin = folderToShow?.account.user
andreas@3110
   528
    }
andreas@3208
   529
    
igor@1338
   530
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
igor@1338
   531
        switch segueIdentifier(for: segue) {
xavier@2715
   532
        case .segueReply:
andreas@2850
   533
            guard let nav = segue.destination as? UINavigationController,
xavier@2715
   534
                let destination = nav.topViewController as? ComposeTableViewController,
andreas@3151
   535
                let indexPath = lastSelectedIndexPath,
andreas@3151
   536
                let message = model?.message(representedByRowAt: indexPath) else {
andreas@2850
   537
                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   538
                    return
xavier@2715
   539
            }
andreas@3110
   540
            setup(composeViewController: destination, composeMode: .replyFrom,
andreas@3151
   541
                  originalMessage: message)
xavier@1623
   542
        case .segueReplyAll:
andreas@2850
   543
            guard let nav = segue.destination as? UINavigationController,
xavier@1623
   544
                let destination = nav.topViewController as? ComposeTableViewController,
andreas@3151
   545
                let indexPath = lastSelectedIndexPath,
andreas@3151
   546
                let message = model?.message(representedByRowAt: indexPath) else {
andreas@2850
   547
                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   548
                    return
xavier@1623
   549
            }
andreas@3110
   550
            setup(composeViewController: destination, composeMode: .replyAll,
andreas@3151
   551
                  originalMessage: message)
andreas@3111
   552
        case .segueShowEmail:
andreas@2850
   553
            guard let vc = segue.destination as? EmailViewController,
andreas@3151
   554
                let indexPath = lastSelectedIndexPath,
andreas@3208
   555
                let message = model?.message(representedByRowAt: indexPath) else {
andreas@2850
   556
                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   557
                    return
igor@1338
   558
            }
andreas@2850
   559
            vc.appConfig = appConfig
andreas@3151
   560
            vc.message = message
andreas@3223
   561
            vc.folderShow = folderToShow
andreas@3166
   562
            vc.messageId = indexPath.row //that looks wrong
xavier@1664
   563
        case .segueForward:
andreas@2850
   564
            guard let nav = segue.destination as? UINavigationController,
xavier@1664
   565
                let destination = nav.topViewController as? ComposeTableViewController,
andreas@3151
   566
                let indexPath = lastSelectedIndexPath,
andreas@3151
   567
                let message = model?.message(representedByRowAt: indexPath) else {
andreas@2850
   568
                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   569
                    return
xavier@1664
   570
            }
andreas@3110
   571
            setup(composeViewController: destination, composeMode: .forward,
andreas@3151
   572
                  originalMessage: message)
andreas@2850
   573
        case .segueFilter:
andreas@2850
   574
            guard let destiny = segue.destination as? FilterTableViewController  else {
andreas@2850
   575
                Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   576
                return
andreas@2850
   577
            }
andreas@2850
   578
            destiny.appConfig = appConfig
andreas@3163
   579
            destiny.filterDelegate = model
andreas@2850
   580
            destiny.inFolder = false
andreas@3223
   581
            destiny.filterEnabled = folderToShow?.filter
andreas@2850
   582
            destiny.hidesBottomBarWhenPushed = true
andreas@2850
   583
        case .segueAddNewAccount:
andreas@2850
   584
            guard let vc = segue.destination as? LoginTableViewController  else {
andreas@2850
   585
                Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   586
                return
andreas@2850
   587
            }
andreas@2850
   588
            vc.appConfig = appConfig
andreas@2850
   589
            vc.hidesBottomBarWhenPushed = true
xavier@1664
   590
            break
andreas@2850
   591
        case .segueFolderViews:
andreas@2850
   592
            guard let vC = segue.destination as? FolderTableViewController  else {
andreas@2850
   593
                Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   594
                return
xavier@1865
   595
            }
andreas@2850
   596
            vC.appConfig = appConfig
andreas@2850
   597
            vC.hidesBottomBarWhenPushed = true
xavier@1865
   598
            break
andreas@2850
   599
        case .segueCompose:
andreas@2850
   600
            guard let nav = segue.destination as? UINavigationController,
dirk@2957
   601
                let destination = nav.rootViewController as? ComposeTableViewController else {
andreas@2850
   602
                    Log.shared.errorAndCrash(component: #function, errorString: "Segue issue")
andreas@2850
   603
                    return
dirk@2240
   604
            }
andreas@3110
   605
            setup(composeViewController: destination)
andreas@2850
   606
        default:
andreas@2850
   607
            Log.shared.errorAndCrash(component: #function, errorString: "Unhandled segue")
dirk@1694
   608
            break
igor@1338
   609
        }
andreas@2838
   610
    }
andreas@3208
   611
    
andreas@3166
   612
    @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
andreas@3151
   613
        // nothing to do.
andreas@3150
   614
    }
dirk@1348
   615
}
xavier@3368
   616
xavier@3368
   617
//enums to simplify configurations
xavier@3368
   618
xavier@3368
   619
enum ActionDescriptor {
xavier@3368
   620
    case read, more, flag, trash
xavier@3368
   621
xavier@3368
   622
    func title(forDisplayMode displayMode: ButtonDisplayMode) -> String? {
xavier@3368
   623
        guard displayMode != .imageOnly else { return nil }
xavier@3368
   624
xavier@3368
   625
        switch self {
xavier@3368
   626
        case .read: return NSLocalizedString("Read", comment: "read button")
xavier@3368
   627
        case .more: return NSLocalizedString("More", comment: "more button")
xavier@3368
   628
        case .flag: return NSLocalizedString("Flag", comment: "read button")
xavier@3368
   629
        case .trash: return NSLocalizedString("Trash", comment: "Trash button")
xavier@3368
   630
        }
xavier@3368
   631
    }
xavier@3368
   632
xavier@3368
   633
    func image(forStyle style: ButtonStyle, displayMode: ButtonDisplayMode) -> UIImage? {
xavier@3368
   634
        guard displayMode != .titleOnly else { return nil }
xavier@3368
   635
xavier@3368
   636
        let name: String
xavier@3368
   637
        switch self {
xavier@3368
   638
        case .read: name = "read"
xavier@3368
   639
        case .more: name = "more"
xavier@3368
   640
        case .flag: name = "flag"
xavier@3368
   641
        case .trash: name = "trash"
xavier@3368
   642
        }
xavier@3368
   643
xavier@3368
   644
        return UIImage(named: "swipe-" + name)
xavier@3368
   645
    }
xavier@3368
   646
xavier@3368
   647
    var color: UIColor {
xavier@3368
   648
        switch self {
xavier@3368
   649
        case .read: return #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1)
xavier@3368
   650
        case .more: return #colorLiteral(red: 0.7803494334, green: 0.7761332393, blue: 0.7967314124, alpha: 1)
xavier@3368
   651
        case .flag: return #colorLiteral(red: 1, green: 0.5803921569, blue: 0, alpha: 1)
xavier@3368
   652
        case .trash: return #colorLiteral(red: 1, green: 0.2352941176, blue: 0.1882352941, alpha: 1)
xavier@3368
   653
        }
xavier@3368
   654
    }
xavier@3368
   655
}
xavier@3368
   656
xavier@3368
   657
enum ButtonDisplayMode {
xavier@3368
   658
    case titleAndImage, titleOnly, imageOnly
xavier@3368
   659
}
xavier@3368
   660
xavier@3368
   661
enum ButtonStyle {
xavier@3368
   662
    case backgroundColor, circular
xavier@3368
   663
}