pEpForiOS/UI/EmailDisplayList/EmailListViewController.swift
author Xavier Algarra <xavier@pep-project.org>
Mon, 05 Aug 2019 15:16:02 +0200
branchIOS-1737
changeset 9535 cfc972409286
parent 9534 d401fb21020b
child 9558 2f954ef518ef
permissions -rw-r--r--
IOS-1737 comments
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
xavier@3368
    10
import SwipeCellKit
xavier@7644
    11
import pEpIOSToolbox
dirk@810
    12
xavier@3368
    13
class EmailListViewController: BaseTableViewController, SwipeTableViewCellDelegate {
borja@6346
    14
    static let FILTER_TITLE_MAX_XAR = 20
borja@6346
    15
borja@6836
    16
    var model: EmailListViewModel? {
borja@6761
    17
        didSet {
borja@6761
    18
            model?.emailListViewModelDelegate = self
borja@6761
    19
        }
borja@6761
    20
    }
andreas@6307
    21
andreas@2838
    22
    public static let storyboardId = "EmailListViewController"
andreas@4425
    23
    private var lastSelectedIndexPath: IndexPath?
andreas@6307
    24
igor@1301
    25
    let searchController = UISearchController(searchResultsController: nil)
xavier@3368
    26
xavier@3368
    27
    //swipe acctions types
xavier@3368
    28
    var buttonDisplayMode: ButtonDisplayMode = .titleAndImage
xavier@3368
    29
    var buttonStyle: ButtonStyle = .backgroundColor
andreas@3678
    30
borja@4839
    31
    private var swipeDelete : SwipeAction? = nil
borja@4838
    32
andreas@3151
    33
    // MARK: - Outlets
andreas@3208
    34
    
dirk@2067
    35
    @IBOutlet weak var enableFilterButton: UIBarButtonItem!
xavier@5634
    36
andreas@6307
    37
    var textFilterButton: UIBarButtonItem = UIBarButtonItem(title: "",
andreas@6307
    38
                                                            style: .plain,
andreas@6307
    39
                                                            target: nil,
andreas@6307
    40
                                                            action: nil)
andreas@6307
    41
andreas@3151
    42
    // MARK: - Life Cycle
xavier@5634
    43
dirk@275
    44
    override func viewDidLoad() {
ylandert@935
    45
        super.viewDidLoad()
borja@5616
    46
xavier@4757
    47
        tableView.allowsMultipleSelectionDuringEditing = true
andreas@5828
    48
borja@5705
    49
        if #available(iOS 10.0, *) {
borja@5705
    50
            tableView.prefetchDataSource = self
borja@5705
    51
        }
andreas@5828
    52
        setupSearchBar()
borja@4859
    53
        setup()
xavier@4317
    54
    }
xavier@4317
    55
andreas@4518
    56
    override func viewWillAppear(_ animated: Bool) {
andreas@4518
    57
        super.viewWillAppear(animated)
andreas@6158
    58
        navigationController?.setToolbarHidden(false, animated: true)
andreas@4518
    59
        if MiscUtil.isUnitTest() {
andreas@4518
    60
            return
andreas@4518
    61
        }
xavier@8788
    62
        lastSelectedIndexPath = nil
andreas@4518
    63
xavier@5634
    64
        setUpTextFilter()
dirk@4650
    65
andreas@8231
    66
        guard let vm = model else {
andreas@8525
    67
            Log.shared.errorAndCrash("No VM")
andreas@8231
    68
            return
andreas@8231
    69
        }
andreas@8231
    70
andreas@8231
    71
        if !vm.showLoginView {
andreas@4518
    72
            updateFilterButtonView()
andreas@8228
    73
            vm.startMonitoring() //???: should UI know about startMonitoring?
andreas@8228
    74
andreas@8228
    75
            // Threading feature is currently non-existing. Keep this code, might help later.
andreas@8231
    76
            //            if vm.checkIfSettingsChanged() {
andreas@8231
    77
            //                settingsChanged()
andreas@8231
    78
            //            }
andreas@4518
    79
        }
andreas@4518
    80
    }
andreas@4518
    81
xavier@8790
    82
    override func viewWillDisappear(_ animated: Bool) {
xavier@8790
    83
        guard let isIphone = splitViewController?.isCollapsed, let last = lastSelectedIndexPath else {
xavier@8790
    84
            return
xavier@8790
    85
        }
xavier@8790
    86
        if !isIphone {
xavier@8790
    87
            performSegue(withIdentifier: "showNoMessage", sender: nil)
xavier@8790
    88
        }
xavier@8790
    89
    }
xavier@8790
    90
xavier@4347
    91
    deinit {
andreas@5828
    92
         NotificationCenter.default.removeObserver(self)
xavier@4347
    93
    }
andreas@6307
    94
xavier@8030
    95
    private func showLoginScreen() {
xavier@8030
    96
        performSegue(withIdentifier:.segueAddNewAccount, sender: self)
xavier@8030
    97
        return
xavier@8030
    98
    }
xavier@8030
    99
andreas@5829
   100
    // MARK: - Setup
andreas@6307
   101
andreas@3151
   102
    private func setup() {
borja@6855
   103
andreas@8231
   104
        guard let vm = model else {
andreas@8525
   105
            Log.shared.errorAndCrash("No VM")
xavier@5505
   106
            return
borja@6761
   107
        }
andreas@3272
   108
andreas@8231
   109
        if vm.showLoginView {
andreas@8231
   110
            showLoginScreen()
xavier@8723
   111
            return
agp@8149
   112
        }
agp@8149
   113
xavier@8413
   114
        ///if we are in setup and the folder is unifiedInbox
xavier@8413
   115
        ///we have to reload the unifiedInbox to ensure that all the accounts are present.
xavier@8413
   116
        if vm.folderToShow is UnifiedInbox {
xavier@8413
   117
            model = EmailListViewModel(emailListViewModelDelegate: self,
xavier@8413
   118
                                       folderToShow: UnifiedInbox())
xavier@8413
   119
        }
xavier@8413
   120
andreas@8231
   121
        title = model?.folderName
dirk@7189
   122
        let item = UIBarButtonItem.getPEPButton(
dirk@7189
   123
            action: #selector(showSettingsViewController),
dirk@7189
   124
            target: self)
xavier@5501
   125
        let flexibleSpace: UIBarButtonItem = UIBarButtonItem(
andreas@8116
   126
            barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
xavier@5501
   127
            target: nil,
xavier@5501
   128
            action: nil)
andreas@6158
   129
        toolbarItems?.append(contentsOf: [flexibleSpace,item])
andreas@6158
   130
        navigationController?.title = title
andreas@2683
   131
    }
andreas@3223
   132
andreas@5829
   133
    private func setUpTextFilter() {
andreas@6158
   134
        textFilterButton.isEnabled = false
andreas@6158
   135
        textFilterButton.action = #selector(showFilterOptions(_:))
andreas@6158
   136
        textFilterButton.target = self
andreas@5829
   137
andreas@5829
   138
        let fontSize:CGFloat = 10;
andreas@5829
   139
        let font:UIFont = UIFont.boldSystemFont(ofSize: fontSize);
andreas@8116
   140
        let attributes = [NSAttributedString.Key.font: font];
andreas@5829
   141
andreas@8116
   142
        textFilterButton.setTitleTextAttributes(attributes, for: UIControl.State.normal)
andreas@3223
   143
    }
andreas@5829
   144
andreas@5829
   145
    // MARK: - Search Bar
andreas@5829
   146
andreas@5829
   147
    private func setupSearchBar() {
andreas@5854
   148
        definesPresentationContext = true
andreas@5829
   149
        configureSearchBar()
andreas@5829
   150
        if #available(iOS 11.0, *) {
andreas@5829
   151
            searchController.isActive = false
andreas@5854
   152
            navigationItem.searchController = searchController
andreas@5854
   153
            navigationItem.hidesSearchBarWhenScrolling = true
andreas@5829
   154
        } else {
andreas@5829
   155
            addSearchBar10()
andreas@5829
   156
andreas@5829
   157
            if tableView.tableHeaderView == nil {
andreas@5829
   158
                tableView.tableHeaderView = searchController.searchBar
andreas@5829
   159
            }
andreas@5829
   160
andreas@5829
   161
            // some notifications to control when the app enter and recover from background
andreas@5829
   162
            NotificationCenter.default.addObserver(
andreas@5829
   163
                self,
andreas@5829
   164
                selector: #selector(didBecomeActiveInstallSearchBar10),
andreas@8116
   165
                name: UIApplication.didBecomeActiveNotification,
andreas@5829
   166
                object: nil)
andreas@5829
   167
            NotificationCenter.default.addObserver(
andreas@5829
   168
                self,
andreas@5829
   169
                selector: #selector(didBecomeInactiveUninstallSearchbar10),
andreas@8116
   170
                name: UIApplication.didEnterBackgroundNotification,
andreas@5829
   171
                object: nil)
andreas@5829
   172
        }
igor@1301
   173
    }
dirk@4418
   174
dirk@4574
   175
    /**
dirk@4574
   176
     Configure the search controller, shared between iOS versions 11 and earlier.
dirk@4574
   177
     */
dirk@4418
   178
    private func configureSearchBar() {
igor@1301
   179
        searchController.searchResultsUpdater = self
igor@1301
   180
        searchController.dimsBackgroundDuringPresentation = false
igor@1301
   181
        searchController.delegate = self
dirk@4418
   182
    }
dirk@4418
   183
dirk@4574
   184
    /**
andreas@5829
   185
     Showing the search controller in versions iOS 10 and earlier.
andreas@5829
   186
     */
andreas@5829
   187
    @objc func didBecomeActiveInstallSearchBar10() {
andreas@5829
   188
        if tableView.tableHeaderView == nil {
andreas@5829
   189
            tableView.tableHeaderView = searchController.searchBar
andreas@5829
   190
        }
andreas@5829
   191
    }
andreas@5829
   192
andreas@5829
   193
    /**
andreas@5829
   194
     Hide/remove the search controller in versions iOS 10 and earlier.
andreas@5829
   195
     */
andreas@5829
   196
    @objc func didBecomeInactiveUninstallSearchbar10() {
andreas@5829
   197
        tableView.tableHeaderView = nil
andreas@5829
   198
    }
andreas@5829
   199
andreas@5829
   200
    /**
dirk@4574
   201
     Add the search bar when running on iOS 10 or earlier.
dirk@4574
   202
     */
dirk@4574
   203
    private func addSearchBar10() {
igor@1301
   204
        tableView.tableHeaderView = searchController.searchBar
dirk@4418
   205
        tableView.setContentOffset(CGPoint(x: 0.0,
dirk@4418
   206
                                           y: searchController.searchBar.frame.size.height),
dirk@4418
   207
                                   animated: false)
igor@1301
   208
    }
andreas@6307
   209
andreas@3151
   210
    // MARK: - Other
borja@6762
   211
andreas@5829
   212
    private func weCameBackFromAPushedView() -> Bool {
andreas@5829
   213
        return model != nil
andreas@5829
   214
    }
andreas@5829
   215
andreas@3505
   216
    private func showComposeView() {
andreas@6158
   217
        performSegue(withIdentifier: SegueIdentifier.segueEditDraft, sender: self)
andreas@3505
   218
    }
andreas@3505
   219
xavier@9535
   220
    /// we have to handle the ipad/iphone segue in a different way. see IOS-1737
andreas@3505
   221
    private func showEmail(forCellAt indexPath: IndexPath) {
xavier@9534
   222
        guard let splitViewController = self.splitViewController else {
xavier@9534
   223
            return
xavier@9534
   224
        }
xavier@9534
   225
        if splitViewController.isCollapsed {
xavier@9534
   226
            performSegue(withIdentifier: SegueIdentifier.segueShowEmailIphone, sender: self)
xavier@9534
   227
        } else {
xavier@9534
   228
            performSegue(withIdentifier: SegueIdentifier.segueShowEmailIpad, sender: self)
xavier@9534
   229
        }
andreas@3505
   230
    }
xavier@4752
   231
andreas@5656
   232
    private func showNoMessageSelectedIfNeeded() {
miguel@4826
   233
        guard let splitViewController = self.splitViewController else {
miguel@4826
   234
            return
miguel@4826
   235
        }
miguel@4826
   236
        if splitViewController.isCollapsed {
andreas@6305
   237
            guard let vm = model else {
agp@8234
   238
                Log.shared.errorAndCrash("Invalid state")
andreas@6305
   239
                return
andreas@6305
   240
            }
borja@6829
   241
            let unreadFilterActive = vm.unreadFilterEnabled()
andreas@6305
   242
            if navigationController?.topViewController != self && !unreadFilterActive {
borja@4861
   243
                navigationController?.popViewController(animated: true)
borja@4861
   244
            }
miguel@4826
   245
        } else {
andreas@6158
   246
            performSegue(withIdentifier: "showNoMessage", sender: nil)
miguel@4826
   247
        }
miguel@4826
   248
    }
miguel@4826
   249
xavier@5732
   250
    @objc private func settingsChanged() {
andreas@8228
   251
        model?.informDelegateToReloadData()
andreas@6158
   252
        tableView.reloadData()
xavier@5732
   253
    }
xavier@5732
   254
xavier@4752
   255
    // MARK: - Action Edit Button
xavier@4752
   256
andreas@5658
   257
    private var tempToolbarItems: [UIBarButtonItem]?
xavier@4753
   258
    private var editRightButton: UIBarButtonItem?
andreas@6306
   259
    var flagToolbarButton: UIBarButtonItem?
andreas@6306
   260
    var unflagToolbarButton: UIBarButtonItem?
andreas@6306
   261
    var readToolbarButton: UIBarButtonItem?
andreas@6306
   262
    var unreadToolbarButton: UIBarButtonItem?
andreas@6306
   263
    var deleteToolbarButton: UIBarButtonItem?
andreas@6306
   264
    var moveToolbarButton: UIBarButtonItem?
xavier@4752
   265
xavier@4752
   266
    @IBAction func Edit(_ sender: Any) {
xavier@4752
   267
xavier@4753
   268
        showEditToolbar()
xavier@4757
   269
        tableView.setEditing(true, animated: true)
xavier@4752
   270
    }
xavier@4752
   271
xavier@4752
   272
    private func showEditToolbar() {
xavier@4753
   273
xavier@4752
   274
        tempToolbarItems = toolbarItems
xavier@4753
   275
xavier@4753
   276
        // Flexible Space separation between the buttons
xavier@4852
   277
        let flexibleSpace: UIBarButtonItem = UIBarButtonItem(
andreas@8116
   278
            barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
xavier@4852
   279
            target: nil,
xavier@4852
   280
            action: nil)
xavier@4753
   281
xavier@4892
   282
        var img = UIImage(named: "icon-flagged")
xavier@4827
   283
xavier@4897
   284
        flagToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   285
                                   style: UIBarButtonItem.Style.plain,
xavier@4827
   286
                                   target: self,
andreas@6158
   287
                                   action: #selector(flagToolbar(_:)))
xavier@4905
   288
        flagToolbarButton?.isEnabled = false
xavier@4827
   289
xavier@4897
   290
        img = UIImage(named: "icon-unflagged")
xavier@4827
   291
xavier@4897
   292
        unflagToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   293
                                            style: UIBarButtonItem.Style.plain,
xavier@4897
   294
                                            target: self,
andreas@6158
   295
                                            action: #selector(unflagToolbar(_:)))
xavier@4905
   296
        unflagToolbarButton?.isEnabled = false
xavier@4897
   297
xavier@4897
   298
        img = UIImage(named: "icon-read")
xavier@4897
   299
xavier@4897
   300
        readToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   301
                                   style: UIBarButtonItem.Style.plain,
xavier@4753
   302
                                   target: self,
andreas@6158
   303
                                   action: #selector(readToolbar(_:)))
xavier@4905
   304
        readToolbarButton?.isEnabled = false
xavier@4827
   305
xavier@4897
   306
        img = UIImage(named: "icon-unread")
xavier@4897
   307
xavier@4897
   308
        unreadToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   309
                                            style: UIBarButtonItem.Style.plain,
xavier@4897
   310
                                            target: self,
andreas@6158
   311
                                            action: #selector(unreadToolbar(_:)))
xavier@4905
   312
        unreadToolbarButton?.isEnabled = false
xavier@4897
   313
xavier@4827
   314
        img = UIImage(named: "folders-icon-trash")
xavier@4827
   315
xavier@4905
   316
        deleteToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   317
                                     style: UIBarButtonItem.Style.plain,
xavier@4753
   318
                                     target: self,
andreas@6158
   319
                                     action: #selector(deleteToolbar(_:)))
xavier@4753
   320
xavier@4905
   321
        deleteToolbarButton?.isEnabled = false
xavier@4905
   322
xavier@4827
   323
        img = UIImage(named: "swipe-archive")
xavier@4827
   324
xavier@4905
   325
        moveToolbarButton = UIBarButtonItem(image: img,
andreas@8116
   326
                                     style: UIBarButtonItem.Style.plain,
xavier@4827
   327
                                     target: self,
andreas@6158
   328
                                     action: #selector(moveToolbar(_:)))
xavier@4827
   329
xavier@4905
   330
        moveToolbarButton?.isEnabled = false
xavier@4827
   331
dirk@7189
   332
        let pEp = UIBarButtonItem.getPEPButton(
dirk@7189
   333
            action: #selector(showSettingsViewController),
dirk@7189
   334
            target: self)
xavier@4897
   335
        toolbarItems = [flagToolbarButton, flexibleSpace, readToolbarButton,
xavier@4905
   336
                        flexibleSpace, deleteToolbarButton, flexibleSpace,
xavier@4905
   337
                        moveToolbarButton, flexibleSpace, pEp] as? [UIBarButtonItem]
xavier@4753
   338
xavier@4753
   339
xavier@4753
   340
        //right navigation button to ensure the logic
xavier@4753
   341
        let cancel = UIBarButtonItem(title: "Cancel",
andreas@8116
   342
                                     style: UIBarButtonItem.Style.plain,
xavier@4753
   343
                                     target: self,
andreas@6158
   344
                                     action: #selector(cancelToolbar(_:)))
xavier@4753
   345
andreas@6158
   346
        editRightButton = navigationItem.rightBarButtonItem
andreas@6158
   347
        navigationItem.rightBarButtonItem = cancel
xavier@4752
   348
xavier@4752
   349
    }
xavier@4752
   350
andreas@5913
   351
    @objc private func showSettingsViewController() {
andreas@5913
   352
        UIUtils.presentSettings(on: self, appConfig: appConfig)
andreas@5913
   353
    }
andreas@5913
   354
xavier@5634
   355
    @IBAction func showFilterOptions(_ sender: UIBarButtonItem!) {
xavier@5634
   356
        performSegue(withIdentifier: .segueShowFilter, sender: self)
xavier@5634
   357
    }
xavier@5634
   358
xavier@4753
   359
    @IBAction func cancelToolbar(_ sender:UIBarButtonItem!) {
xavier@4753
   360
        showStandardToolbar()
xavier@8973
   361
        lastSelectedIndexPath = nil
xavier@4757
   362
        tableView.setEditing(false, animated: true)
xavier@4752
   363
    }
xavier@4752
   364
xavier@4852
   365
    @IBAction func flagToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   366
        if let selectedItems = tableView.indexPathsForSelectedRows {
xavier@4892
   367
            model?.markSelectedAsFlagged(indexPaths: selectedItems)
xavier@6919
   368
            selectedItems.forEach { (ip) in
xavier@6919
   369
                if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
xavier@6919
   370
                    cell.isFlagged = true
xavier@6919
   371
                }
xavier@6919
   372
            }
xavier@4892
   373
        }
xavier@4852
   374
        cancelToolbar(sender)
xavier@4852
   375
    }
xavier@4753
   376
xavier@4897
   377
    @IBAction func unflagToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   378
        if let selectedItems = tableView.indexPathsForSelectedRows {
xavier@4897
   379
            model?.markSelectedAsUnFlagged(indexPaths: selectedItems)
xavier@6919
   380
            selectedItems.forEach { (ip) in
xavier@6919
   381
                if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
xavier@6919
   382
                    cell.isFlagged = false
xavier@6919
   383
                }
xavier@6919
   384
            }
xavier@4897
   385
        }
xavier@4897
   386
        cancelToolbar(sender)
xavier@4897
   387
    }
xavier@4897
   388
xavier@4852
   389
    @IBAction func readToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   390
        if let selectedItems = tableView.indexPathsForSelectedRows {
xavier@4892
   391
            model?.markSelectedAsRead(indexPaths: selectedItems)
xavier@6919
   392
            selectedItems.forEach { (ip) in
xavier@6919
   393
                if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
xavier@6919
   394
                    cell.isSeen = true
xavier@6919
   395
                }
xavier@6919
   396
            }
xavier@4892
   397
        }
xavier@4852
   398
        cancelToolbar(sender)
xavier@4753
   399
    }
xavier@4753
   400
xavier@4897
   401
    @IBAction func unreadToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   402
        if let selectedItems = tableView.indexPathsForSelectedRows {
xavier@4897
   403
            model?.markSelectedAsUnread(indexPaths: selectedItems)
xavier@6919
   404
            selectedItems.forEach { (ip) in
xavier@6919
   405
                if let cell = self.tableView.cellForRow(at: ip) as? EmailListViewCell {
xavier@6919
   406
                    cell.isSeen = false
xavier@6919
   407
                }
xavier@6919
   408
            }
xavier@4897
   409
        }
xavier@4897
   410
        cancelToolbar(sender)
xavier@4897
   411
    }
xavier@4897
   412
xavier@4753
   413
    @IBAction func moveToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   414
        performSegue(withIdentifier: .segueShowMoveToFolder, sender: self)
xavier@4950
   415
        cancelToolbar(sender)
xavier@4827
   416
    }
xavier@4827
   417
xavier@4753
   418
    @IBAction func deleteToolbar(_ sender:UIBarButtonItem!) {
andreas@6158
   419
        if let vm = model, let selectedIndexPaths = tableView.indexPathsForSelectedRows {
xavier@4892
   420
            vm.deleteSelected(indexPaths: selectedIndexPaths)
xavier@4896
   421
        }
xavier@4794
   422
        cancelToolbar(sender)
xavier@4753
   423
    }
xavier@4753
   424
xavier@4753
   425
    //recover the original toolbar and right button
xavier@4752
   426
    private func showStandardToolbar() {
xavier@4752
   427
        toolbarItems = tempToolbarItems
andreas@6158
   428
        navigationItem.rightBarButtonItem = editRightButton
xavier@4752
   429
    }
xavier@4752
   430
miguel@5849
   431
    private func moveSelectionIfNeeded(fromIndexPath: IndexPath, toIndexPath: IndexPath) {
miguel@5849
   432
        if lastSelectedIndexPath == fromIndexPath {
miguel@5849
   433
            lastSelectedIndexPath = toIndexPath
miguel@5849
   434
            resetSelection()
miguel@5849
   435
        }
miguel@5849
   436
    }
miguel@5849
   437
miguel@4797
   438
    private func resetSelectionIfNeeded(for indexPath: IndexPath) {
miguel@4797
   439
        if lastSelectedIndexPath == indexPath {
miguel@4797
   440
            resetSelection()
miguel@4797
   441
        }
miguel@4797
   442
    }
miguel@4797
   443
miguel@4797
   444
    private func resetSelection() {
miguel@4797
   445
        tableView.selectRow(at: lastSelectedIndexPath, animated: false, scrollPosition: .none)
miguel@4797
   446
    }
miguel@4797
   447
andreas@3678
   448
    // MARK: - Action Filter Button
andreas@3208
   449
    
andreas@3151
   450
    @IBAction func filterButtonHasBeenPressed(_ sender: UIBarButtonItem) {
andreas@3163
   451
        guard let vm = model else {
agp@8234
   452
            Log.shared.errorAndCrash("We should have a model here")
andreas@3163
   453
            return
andreas@3163
   454
        }
andreas@3163
   455
        vm.isFilterEnabled = !vm.isFilterEnabled
xavier@5634
   456
        if vm.isFilterEnabled {
xavier@5634
   457
            let flexibleSpace: UIBarButtonItem = UIBarButtonItem(
andreas@8116
   458
                barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
xavier@5634
   459
                target: nil,
xavier@5634
   460
                action: nil)
andreas@6158
   461
            toolbarItems?.insert(textFilterButton, at: 1)
andreas@6158
   462
            toolbarItems?.insert(flexibleSpace, at: 1)
xavier@5634
   463
        } else {
andreas@6158
   464
            toolbarItems?.remove(at: 1)
andreas@6158
   465
            toolbarItems?.remove(at: 1)
xavier@5634
   466
xavier@5634
   467
        }
xavier@3193
   468
        updateFilterButtonView()
xavier@1832
   469
    }
andreas@6307
   470
andreas@3678
   471
    private func updateFilterButtonView() {
andreas@3163
   472
        guard let vm = model else {
agp@8234
   473
            Log.shared.errorAndCrash("We should have a model here")
andreas@3163
   474
            return
andreas@3163
   475
        }
andreas@6307
   476
andreas@3163
   477
        textFilterButton.isEnabled = vm.isFilterEnabled
andreas@3163
   478
        if textFilterButton.isEnabled {
andreas@3163
   479
            enableFilterButton.image = UIImage(named: "unread-icon-active")
andreas@3163
   480
            updateFilterText()
andreas@3163
   481
        } else {
andreas@3163
   482
            textFilterButton.title = ""
andreas@3163
   483
            enableFilterButton.image = UIImage(named: "unread-icon")
andreas@3163
   484
        }
andreas@3163
   485
    }
andreas@3208
   486
    
andreas@3678
   487
    private func updateFilterText() {
xavier@8475
   488
        if let vm = model {
xavier@8475
   489
            var txt = vm.currentFilter.getFilterText()
borja@6346
   490
            if(txt.count > EmailListViewController.FILTER_TITLE_MAX_XAR){
borja@6346
   491
                let prefix = txt.prefix(ofLength: EmailListViewController.FILTER_TITLE_MAX_XAR)
borja@6346
   492
                txt = String(prefix)
borja@6346
   493
                txt += "..."
borja@6346
   494
            }
borja@6347
   495
            if txt.isEmpty {
borja@6347
   496
                txt = "none"
borja@6347
   497
            }
andreas@3163
   498
            textFilterButton.title = "Filter by: " + txt
andreas@3163
   499
        }
andreas@3151
   500
    }
andreas@5658
   501
dirk@31
   502
    // MARK: - UITableViewDataSource
andreas@3208
   503
    
dirk@784
   504
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
andreas@3151
   505
        return model?.rowCount ?? 0
dirk@31
   506
    }
andreas@5658
   507
dirk@784
   508
    override func tableView(_ tableView: UITableView,
dirk@784
   509
                            cellForRowAt indexPath: IndexPath) -> UITableViewCell {
xavier@8687
   510
        if lastSelectedIndexPath == indexPath {
xavier@8687
   511
            defer {
xavier@8687
   512
                tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
xavier@8687
   513
            }
xavier@8687
   514
        }
xavier@8687
   515
andreas@5656
   516
        let cell = tableView.dequeueReusableCell(withIdentifier: EmailListViewCell.storyboardId,
andreas@5656
   517
                                                 for: indexPath)
dirk@4576
   518
        if let theCell = cell as? EmailListViewCell {
dirk@4576
   519
            theCell.delegate = self
borja@5706
   520
miguel@5727
   521
            guard let viewModel = model?.viewModel(for: indexPath.row) else {
borja@4926
   522
                return cell
borja@4926
   523
            }
borja@4926
   524
            theCell.configure(for:viewModel)
dirk@4576
   525
        } else {
agp@8234
   526
            Log.shared.errorAndCrash("dequeued wrong cell")
andreas@3151
   527
        }
dirk@4576
   528
dirk@31
   529
        return cell
dirk@31
   530
    }
dirk@4923
   531
dirk@4923
   532
    // MARK: - UITableViewDelegate
andreas@6307
   533
andreas@3911
   534
    func tableView(_ tableView: UITableView,
andreas@3911
   535
                   editActionsForRowAt
andreas@3911
   536
        indexPath: IndexPath,
andreas@3911
   537
                   for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
dirk@7190
   538
        if model == nil {
andreas@6707
   539
            return nil
andreas@6707
   540
        }
dirk@7190
   541
andreas@3909
   542
        // Create swipe actions, taking the currently displayed folder into account
andreas@3514
   543
        var swipeActions = [SwipeAction]()
andreas@3911
   544
borja@6811
   545
        guard let model = model else {
agp@8234
   546
            Log.shared.errorAndCrash("Should have VM")
borja@6811
   547
            return nil
andreas@3917
   548
        }
andreas@3917
   549
andreas@3911
   550
        // Delete or Archive
borja@6811
   551
        let destructiveAction = model.getDestructiveActtion(forMessageAt: indexPath.row)
andreas@3911
   552
        let archiveAction =
borja@6811
   553
            SwipeAction(style: .destructive,
borja@6811
   554
                        title: destructiveAction.title(forDisplayMode: .titleAndImage)) {
xavier@8624
   555
                            [weak self] action, indexPath in
xavier@8624
   556
                            guard let me = self else {
xavier@8624
   557
                                Log.shared.errorAndCrash("Lost MySelf")
xavier@8624
   558
                                return
xavier@8624
   559
                            }
xavier@8665
   560
                            me.swipeDelete = action
xavier@8624
   561
                            me.deleteAction(forCellAt: indexPath)
xavier@8665
   562
andreas@3514
   563
        }
borja@6811
   564
        configure(action: archiveAction, with: destructiveAction)
andreas@3911
   565
        swipeActions.append(archiveAction)
andreas@3514
   566
andreas@3909
   567
        // Flag
borja@6811
   568
        let flagActionDescription = model.getFlagAction(forMessageAt: indexPath.row)
borja@6811
   569
        if let flagActionDescription = flagActionDescription {
andreas@6159
   570
            let flagAction = SwipeAction(style: .default, title: "Flag") {
andreas@6159
   571
                [weak self] action, indexPath in
andreas@6159
   572
                guard let me = self else {
agp@8234
   573
                    Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   574
                    return
andreas@6159
   575
                }
andreas@6159
   576
                me.flagAction(forCellAt: indexPath)
xavier@4703
   577
            }
borja@5624
   578
xavier@4703
   579
            flagAction.hidesWhenSelected = true
borja@5624
   580
borja@6811
   581
            configure(action: flagAction, with: flagActionDescription)
xavier@4703
   582
            swipeActions.append(flagAction)
andreas@3514
   583
        }
andreas@3514
   584
andreas@3909
   585
        // More (reply++)
borja@6811
   586
        let moreActionDescription = model.getMoreAction(forMessageAt: indexPath.row)
borja@6811
   587
borja@6811
   588
        if let moreActionDescription = moreActionDescription {
andreas@6186
   589
            // Do not add "more" actions (reply...) to drafts or outbox.
andreas@6159
   590
            let moreAction = SwipeAction(style: .default, title: "More") {
andreas@6159
   591
                [weak self] action, indexPath in
andreas@6159
   592
                guard let me = self else {
agp@8234
   593
                    Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   594
                    return
andreas@6159
   595
                }
andreas@6159
   596
                me.moreAction(forCellAt: indexPath)
xavier@3368
   597
            }
andreas@3514
   598
            moreAction.hidesWhenSelected = true
borja@6811
   599
            configure(action: moreAction, with: moreActionDescription)
andreas@3514
   600
            swipeActions.append(moreAction)
xavier@3368
   601
        }
andreas@3514
   602
        return (orientation == .right ?   swipeActions : nil)
xavier@3368
   603
    }
xavier@3368
   604
andreas@6200
   605
    func tableView(_ tableView: UITableView,
andreas@6200
   606
                   editActionsOptionsForRowAt indexPath: IndexPath,
andreas@6200
   607
                   for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
xavier@3368
   608
        var options = SwipeTableOptions()
xavier@3368
   609
        options.transitionStyle = .border
xavier@3368
   610
        options.buttonSpacing = 11
borja@4838
   611
        options.expansionStyle = .destructive(automaticallyDelete: false)
xavier@3368
   612
        return options
xavier@3368
   613
    }
xavier@3368
   614
andreas@3151
   615
    override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
miguel@5727
   616
        guard let cell = cell as? EmailListViewCell else {
miguel@5727
   617
            return
miguel@5727
   618
        }
miguel@5727
   619
        cell.clear()
dirk@744
   620
    }
xavier@4793
   621
andreas@3151
   622
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
xavier@4758
   623
        if tableView.isEditing {
xavier@4892
   624
            if let vm = model, let selectedIndexPaths = tableView.indexPathsForSelectedRows {
xavier@4897
   625
                vm.updatedItems(indexPaths: selectedIndexPaths)
xavier@4793
   626
            }
dirk@6345
   627
        } else {
borja@6800
   628
            guard let model = model else {
agp@8234
   629
                Log.shared.errorAndCrash("No folder")
dirk@6345
   630
                return
dirk@6345
   631
            }
xavier@9138
   632
            if model.isSelectable(messageAt: indexPath) {
xavier@9079
   633
                lastSelectedIndexPath = indexPath
xavier@9079
   634
                tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
xavier@9138
   635
                if model.isEditable(messageAt: indexPath) {
xavier@9079
   636
                    showComposeView()
xavier@9079
   637
                } else {
xavier@9079
   638
                    showEmail(forCellAt: indexPath)
xavier@9079
   639
                }
dirk@6345
   640
            } else {
xavier@9125
   641
                tableView.deselectRow(at: indexPath, animated: true)
dirk@6345
   642
            }
andreas@3425
   643
        }
dirk@744
   644
    }
andreas@3278
   645
dirk@4923
   646
    override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
dirk@4923
   647
        if tableView.isEditing, let vm = model {
dirk@4923
   648
            if let selectedIndexPaths = tableView.indexPathsForSelectedRows {
dirk@4923
   649
                vm.updatedItems(indexPaths: selectedIndexPaths)
dirk@4923
   650
            } else {
dirk@4923
   651
                vm.updatedItems(indexPaths: [])
dirk@4923
   652
            }
dirk@4923
   653
        }
dirk@4923
   654
    }
dirk@4923
   655
andreas@3291
   656
    // Implemented to get informed about the scrolling position.
andreas@3291
   657
    // If the user has scrolled down (almost) to the end, we need to get older emails to display.
andreas@3291
   658
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell,
andreas@3291
   659
                            forRowAt indexPath: IndexPath) {
andreas@3278
   660
        guard let vm = model else {
agp@8234
   661
            Log.shared.errorAndCrash("No model.")
andreas@3291
   662
            return
andreas@3278
   663
        }
andreas@3291
   664
        vm.fetchOlderMessagesIfRequired(forIndexPath: indexPath)
andreas@3278
   665
    }
andreas@3278
   666
dirk@4923
   667
    // MARK: - SwipeTableViewCellDelegate
dirk@4923
   668
dirk@4923
   669
    func configure(action: SwipeAction, with descriptor: SwipeActionDescriptor) {
dirk@4923
   670
        action.title = descriptor.title(forDisplayMode: buttonDisplayMode)
dirk@4923
   671
        action.image = descriptor.image(forStyle: buttonStyle, displayMode: buttonDisplayMode)
dirk@4923
   672
dirk@4923
   673
        switch buttonStyle {
dirk@4923
   674
        case .backgroundColor:
dirk@4923
   675
            action.backgroundColor = descriptor.color
dirk@4923
   676
        case .circular:
dirk@4923
   677
            action.backgroundColor = .clear
dirk@4923
   678
            action.textColor = descriptor.color
dirk@4923
   679
            action.font = .systemFont(ofSize: 13)
dirk@4923
   680
            action.transitionDelegate = ScaleTransition.default
dirk@4923
   681
        }
dirk@4923
   682
    }
dirk@4923
   683
andreas@3291
   684
    // MARK: -
andreas@3291
   685
andreas@3151
   686
    override func didReceiveMemoryWarning() {
andreas@3151
   687
        model?.freeMemory()
andreas@3151
   688
    }
andreas@3151
   689
}
andreas@3151
   690
andreas@3151
   691
// MARK: - UISearchResultsUpdating, UISearchControllerDelegate
andreas@3151
   692
andreas@3151
   693
extension EmailListViewController: UISearchResultsUpdating, UISearchControllerDelegate {
andreas@5754
   694
andreas@3151
   695
    public func updateSearchResults(for searchController: UISearchController) {
andreas@8228
   696
        guard
andreas@8228
   697
            let vm = model,
andreas@8228
   698
            let searchText = searchController.searchBar.text
andreas@8228
   699
            else {
andreas@8228
   700
                return
igor@1242
   701
        }
xavier@8211
   702
        vm.setSearch(forSearchText: searchText)
igor@1242
   703
    }
andreas@5754
   704
andreas@3151
   705
    func didDismissSearchController(_ searchController: UISearchController) {
andreas@3151
   706
        guard let vm = model else {
agp@8234
   707
            Log.shared.errorAndCrash("No chance to remove filter, sorry.")
andreas@3151
   708
            return
andreas@3151
   709
        }
xavier@8211
   710
        vm.removeSearch()
andreas@3151
   711
    }
andreas@3151
   712
}
xavier@1623
   713
andreas@6529
   714
// MARK: - EmailListViewModelDelegate
andreas@3151
   715
andreas@3151
   716
extension EmailListViewController: EmailListViewModelDelegate {
xavier@8716
   717
    func checkIfSplitNeedsUpdate(indexpath: [IndexPath]) {
xavier@8716
   718
        guard let isIphone = splitViewController?.isCollapsed, let last = lastSelectedIndexPath else {
xavier@8716
   719
            return
xavier@8716
   720
        }
xavier@8716
   721
        if !isIphone && indexpath.contains(last) {
xavier@8716
   722
            showEmail(forCellAt: last)
xavier@8716
   723
        }
xavier@8716
   724
    }
xavier@8716
   725
xavier@8059
   726
    func reloadData(viewModel: EmailListViewModel) {
xavier@8059
   727
        tableView.reloadData()
xavier@8059
   728
    }
xavier@8059
   729
xavier@7983
   730
    func willReceiveUpdates(viewModel: EmailListViewModel) {
xavier@7983
   731
        tableView.beginUpdates()
xavier@7983
   732
    }
xavier@7983
   733
xavier@7983
   734
    func allUpdatesReceived(viewModel: EmailListViewModel) {
xavier@7983
   735
        tableView.endUpdates()
xavier@7983
   736
    }
xavier@7983
   737
borja@5409
   738
    func showThreadView(for indexPath: IndexPath) {
borja@6812
   739
       /* guard let splitViewController = splitViewController else {
borja@5521
   740
            return
borja@5521
   741
        }
borja@5521
   742
borja@5521
   743
        let storyboard = UIStoryboard(name: "Thread", bundle: nil)
borja@5521
   744
        if splitViewController.isCollapsed {
borja@5521
   745
            guard let message = model?.message(representedByRowAt: indexPath),
borja@5521
   746
                let folder = folderToShow,
borja@5521
   747
                let nav = navigationController,
borja@5521
   748
                let vc: ThreadViewController =
borja@5521
   749
                storyboard.instantiateViewController(withIdentifier: "threadViewController")
borja@5521
   750
                    as? ThreadViewController
borja@5521
   751
                else {
agp@8234
   752
                    Log.shared.errorAndCrash("Segue issue")
borja@5521
   753
                    return
borja@5521
   754
            }
borja@5521
   755
borja@5521
   756
            vc.appConfig = appConfig
borja@5521
   757
            let viewModel = ThreadedEmailViewModel(tip:message, folder: folder)
andreas@6158
   758
            viewModel.emailDisplayDelegate = model
borja@5521
   759
            vc.model = viewModel
borja@5521
   760
            model?.currentDisplayedMessage = viewModel
borja@5521
   761
            model?.updateThreadListDelegate = viewModel
borja@5521
   762
            nav.viewControllers[nav.viewControllers.count - 1] = vc
borja@5521
   763
        } else {
borja@5521
   764
            showEmail(forCellAt: indexPath)
borja@6812
   765
        }*/
borja@5409
   766
    }
borja@5409
   767
xavier@4905
   768
    func toolbarIs(enabled: Bool) {
borja@6813
   769
        if model?.shouldShowToolbarEditButtons() ?? true {
andreas@6187
   770
            // Never enable those for outbox
andreas@6187
   771
            flagToolbarButton?.isEnabled = enabled
andreas@6187
   772
            unflagToolbarButton?.isEnabled = enabled
andreas@6187
   773
            readToolbarButton?.isEnabled = enabled
andreas@6187
   774
            unreadToolbarButton?.isEnabled = enabled
andreas@6187
   775
            moveToolbarButton?.isEnabled = enabled
andreas@6187
   776
        }
xavier@4905
   777
        deleteToolbarButton?.isEnabled = enabled
xavier@4905
   778
    }
xavier@4905
   779
xavier@4892
   780
    func showUnflagButton(enabled: Bool) {
xavier@4892
   781
        if enabled {
xavier@4897
   782
xavier@4897
   783
            if let button = unflagToolbarButton {
xavier@4897
   784
                toolbarItems?.remove(at: 0)
xavier@4897
   785
                toolbarItems?.insert(button, at: 0)
xavier@4897
   786
            }
xavier@4897
   787
xavier@4827
   788
        } else {
xavier@4897
   789
            if let button = flagToolbarButton {
xavier@4897
   790
                toolbarItems?.remove(at: 0)
xavier@4897
   791
                toolbarItems?.insert(button, at: 0)
xavier@4897
   792
            }
xavier@4892
   793
        }
xavier@4892
   794
    }
xavier@4892
   795
xavier@4892
   796
    func showUnreadButton(enabled: Bool) {
xavier@4892
   797
        if enabled {
xavier@4897
   798
            if let button = unreadToolbarButton {
xavier@4897
   799
                toolbarItems?.remove(at: 2)
xavier@4897
   800
                toolbarItems?.insert(button, at: 2)
xavier@4897
   801
            }
xavier@4892
   802
        } else {
xavier@4897
   803
            if let button = readToolbarButton {
xavier@4897
   804
                toolbarItems?.remove(at: 2)
xavier@4897
   805
                toolbarItems?.insert(button, at: 2)
xavier@4897
   806
            }
xavier@4827
   807
        }
xavier@4827
   808
    }
xavier@4827
   809
andreas@5489
   810
    func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPaths: [IndexPath]) {
miguel@4826
   811
        lastSelectedIndexPath = tableView.indexPathForSelectedRow
andreas@5489
   812
        tableView.insertRows(at: indexPaths, with: .automatic)
andreas@3151
   813
    }
andreas@7391
   814
andreas@5489
   815
    func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPaths: [IndexPath]) {
borja@4839
   816
        lastSelectedIndexPath = tableView.indexPathForSelectedRow ?? lastSelectedIndexPath
borja@4839
   817
andreas@6158
   818
        if let swipeDelete = self.swipeDelete {
borja@4839
   819
            swipeDelete.fulfill(with: .delete)
dirk@5873
   820
            self.swipeDelete = nil
borja@4839
   821
        } else {
andreas@5489
   822
            tableView.deleteRows(at: indexPaths, with: .automatic)
borja@4838
   823
        }
andreas@5489
   824
        if let lastSelectedIndexPath = lastSelectedIndexPath,
andreas@5489
   825
            indexPaths.contains(lastSelectedIndexPath) {
andreas@5656
   826
            showNoMessageSelectedIfNeeded()
miguel@4826
   827
        }
andreas@3151
   828
    }
xavier@8688
   829
    //!!!: comented code probably not needed anymore. if something strange appears, check this.
xavier@8688
   830
    //!!!: the reselection of the cell is performed in the cell for row. 
andreas@5489
   831
    func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPaths: [IndexPath]) {
miguel@4826
   832
        lastSelectedIndexPath = tableView.indexPathForSelectedRow
andreas@5489
   833
        tableView.reloadRows(at: indexPaths, with: .none)
xavier@8688
   834
//        for indexPath in indexPaths {
xavier@8688
   835
//            resetSelectionIfNeeded(for: indexPath)
xavier@8688
   836
//        }
andreas@3151
   837
    }
borja@5409
   838
xavier@7988
   839
    func emailListViewModel(viewModel: EmailListViewModel, didMoveData atIndexPath: IndexPath, toIndexPath: IndexPath) {
xavier@7988
   840
        lastSelectedIndexPath = tableView.indexPathForSelectedRow
xavier@7988
   841
        tableView.moveRow(at: atIndexPath, to: toIndexPath)
xavier@7988
   842
        moveSelectionIfNeeded(fromIndexPath: atIndexPath, toIndexPath: toIndexPath)
xavier@7988
   843
    }
xavier@7988
   844
andreas@6304
   845
    func emailListViewModel(viewModel: EmailListViewModel,
andreas@6304
   846
                            didChangeSeenStateForDataAt indexPaths: [IndexPath]) {
andreas@6304
   847
        guard let isIphone = splitViewController?.isCollapsed, let vm = model else {
agp@8234
   848
            Log.shared.errorAndCrash("Invalid state")
andreas@6304
   849
            return
andreas@6300
   850
        }
dirk@6355
   851
borja@6829
   852
        let unreadFilterActive = vm.unreadFilterEnabled()
andreas@6305
   853
andreas@6305
   854
        // If unread filter is active, /seen state updates require special handling ...
andreas@6305
   855
andreas@6305
   856
        if !isIphone && unreadFilterActive {
andreas@6304
   857
            // We do not update the seen status when both spitview views are shown and the list is
andreas@6304
   858
            // currently filtered by unread.
andreas@6304
   859
            return
andreas@6305
   860
        } else if isIphone && unreadFilterActive {
andreas@8228
   861
            vm.informDelegateToReloadData()
andreas@6305
   862
        } else {
dirk@6355
   863
            //  ... otherwize we forward to update
dirk@6355
   864
            emailListViewModel(viewModel: viewModel, didUpdateDataAt: indexPaths)
andreas@6304
   865
        }
andreas@6300
   866
    }
andreas@6300
   867
andreas@3677
   868
    func updateView() {
andreas@3678
   869
        tableView.dataSource = self
andreas@3678
   870
        tableView.reloadData()
andreas@5656
   871
        showNoMessageSelectedIfNeeded()
andreas@3151
   872
    }
andreas@3151
   873
}
andreas@3151
   874
andreas@3151
   875
// MARK: - ActionSheet & ActionSheet Actions
andreas@3151
   876
andreas@3151
   877
extension EmailListViewController {
andreas@3166
   878
    func showMoreActionSheet(forRowAt indexPath: IndexPath) {
andreas@3151
   879
        lastSelectedIndexPath = indexPath
dirk@4628
   880
        let alertControler = UIAlertController.pEpAlertController(
dirk@4628
   881
            title: nil, message: nil, preferredStyle: .actionSheet)
igor@1242
   882
        let cancelAction = createCancelAction()
andreas@3151
   883
        let replyAction = createReplyAction()
dirk@6098
   884
dirk@6098
   885
        let replyAllAction = createReplyAllAction(forRowAt: indexPath)
xavier@9182
   886
        let readAction = createReadOrUnReadAction(forRowAt: indexPath)
dirk@6098
   887
andreas@3151
   888
        let forwardAction = createForwardAction()
andreas@4615
   889
        let moveToFolderAction = createMoveToFolderAction()
dirk@6087
   890
igor@1242
   891
        alertControler.addAction(cancelAction)
igor@1242
   892
        alertControler.addAction(replyAction)
dirk@6087
   893
dirk@6087
   894
        if let theReplyAllAction = replyAllAction {
dirk@6087
   895
            alertControler.addAction(theReplyAllAction)
dirk@6087
   896
        }
dirk@6087
   897
igor@1242
   898
        alertControler.addAction(forwardAction)
andreas@4615
   899
        alertControler.addAction(moveToFolderAction)
xavier@9182
   900
        alertControler.addAction(readAction)
dirk@6087
   901
igor@1414
   902
        if let popoverPresentationController = alertControler.popoverPresentationController {
andreas@3151
   903
            popoverPresentationController.sourceView = tableView
borja@4816
   904
            let cellFrame = tableView.rectForRow(at: indexPath)
andreas@6158
   905
            let sourceRect = view.convert(cellFrame, from: tableView)
borja@4816
   906
            popoverPresentationController.sourceRect = sourceRect
borja@4816
   907
igor@1414
   908
        }
igor@1242
   909
        present(alertControler, animated: true, completion: nil)
igor@1242
   910
    }
andreas@5658
   911
andreas@3151
   912
    // MARK: Action Sheet Actions
andreas@4615
   913
andreas@4615
   914
    private func createMoveToFolderAction() -> UIAlertAction {
andreas@4616
   915
        let title = NSLocalizedString("Move to Folder", comment: "EmailList action title")
andreas@6159
   916
        return UIAlertAction(title: title, style: .default) { [weak self] action in
andreas@6159
   917
            guard let me = self else {
agp@8234
   918
                Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   919
                return
andreas@6159
   920
            }
andreas@6159
   921
            me.performSegue(withIdentifier: .segueShowMoveToFolder, sender: me)
andreas@4615
   922
        }
andreas@4615
   923
    }
andreas@5658
   924
xavier@9182
   925
    private func createReadOrUnReadAction(forRowAt indexPath: IndexPath) -> UIAlertAction {
xavier@9218
   926
        let seenState = model?.viewModel(for: indexPath.row)?.isSeen ?? false
xavier@9182
   927
xavier@9182
   928
        var title = ""
xavier@9218
   929
        if seenState {
xavier@9218
   930
            title = NSLocalizedString("Mark as unread", comment: "EmailList action title")
xavier@9182
   931
        } else {
xavier@9218
   932
            title = NSLocalizedString("Mark as Read", comment: "EmailList action title")
xavier@9182
   933
        }
xavier@9182
   934
xavier@9182
   935
        return UIAlertAction(title: title, style: .default) { [weak self] action in
xavier@9357
   936
            guard let me = self else {
xavier@9182
   937
                Log.shared.errorAndCrash("Lost MySelf")
xavier@9182
   938
                return
xavier@9182
   939
            }
xavier@9357
   940
            guard let cell = me.tableView.cellForRow(at: indexPath) as? EmailListViewCell else {
xavier@9357
   941
                Log.shared.errorAndCrash(message: "Cell type is wrong")
xavier@9357
   942
                return
xavier@9357
   943
            }
xavier@9303
   944
            cell.isSeen = !seenState
xavier@9218
   945
            if seenState {
xavier@9182
   946
                me.model?.markSelectedAsUnread(indexPaths: [indexPath])
xavier@9182
   947
            } else {
xavier@9182
   948
                me.model?.markSelectedAsRead(indexPaths: [indexPath])
xavier@9182
   949
            }
xavier@9182
   950
        }
xavier@9182
   951
    }
xavier@9182
   952
igor@1242
   953
    func createCancelAction() -> UIAlertAction {
andreas@4616
   954
        let title = NSLocalizedString("Cancel", comment: "EmailList action title")
andreas@6159
   955
        return  UIAlertAction(title: title, style: .cancel) {
andreas@6159
   956
            [weak self] action in
andreas@6159
   957
            guard let me = self else {
agp@8234
   958
                Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   959
                return
andreas@6159
   960
            }
andreas@6159
   961
            me.tableView.beginUpdates()
andreas@6159
   962
            me.tableView.setEditing(false, animated: true)
andreas@6159
   963
            me.tableView.endUpdates()
igor@1242
   964
        }
igor@1242
   965
    }
andreas@5658
   966
andreas@3151
   967
    func createReplyAction() ->  UIAlertAction {
andreas@4616
   968
        let title = NSLocalizedString("Reply", comment: "EmailList action title")
andreas@6159
   969
        return UIAlertAction(title: title, style: .default) {
andreas@6159
   970
            [weak self] action in
andreas@6159
   971
            guard let me = self else {
agp@8234
   972
                Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   973
                return
andreas@6159
   974
            }
andreas@6159
   975
            me.performSegue(withIdentifier: .segueReply, sender: me)
xavier@1620
   976
        }
xavier@1620
   977
    }
andreas@5658
   978
dirk@6098
   979
    func createReplyAllAction(forRowAt indexPath: IndexPath) ->  UIAlertAction? {
dirk@6099
   980
        if (model?.isReplyAllPossible(forRowAt: indexPath) ?? false) {
dirk@6098
   981
            let title = NSLocalizedString("Reply All", comment: "EmailList action title")
andreas@6159
   982
            return UIAlertAction(title: title, style: .default) {
andreas@6159
   983
                [weak self] action in
andreas@6159
   984
                guard let me = self else {
agp@8234
   985
                    Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
   986
                    return
andreas@6159
   987
                }
andreas@6159
   988
                me.performSegue(withIdentifier: .segueReplyAll, sender: me)
dirk@6098
   989
            }
dirk@6098
   990
        } else {
dirk@6098
   991
            return nil
igor@1242
   992
        }
igor@1242
   993
    }
andreas@5658
   994
andreas@3151
   995
    func createForwardAction() -> UIAlertAction {
andreas@4616
   996
        let title = NSLocalizedString("Forward", comment: "EmailList action title")
andreas@6159
   997
        return UIAlertAction(title: title, style: .default) {
andreas@6159
   998
            [weak self] action in
andreas@6159
   999
            guard let me = self else {
agp@8234
  1000
                Log.shared.errorAndCrash("Lost MySelf")
andreas@6159
  1001
                return
andreas@6159
  1002
            }
andreas@6159
  1003
            me.performSegue(withIdentifier: .segueForward, sender: me)
xavier@2369
  1004
        }
igor@1301
  1005
    }
igor@1301
  1006
}
igor@1338
  1007
andreas@3151
  1008
// MARK: - TableViewCell Actions
andreas@3151
  1009
andreas@3151
  1010
extension EmailListViewController {
andreas@3151
  1011
    private func createRowAction(image: UIImage?,
xavier@3266
  1012
                                 action: @escaping (UITableViewRowAction, IndexPath) -> Void
xavier@3266
  1013
        ) -> UITableViewRowAction {
xavier@3266
  1014
        let rowAction = UITableViewRowAction(style: .normal, title: nil, handler: action)
andreas@3151
  1015
        if let theImage = image {
andreas@3151
  1016
            let iconColor = UIColor(patternImage: theImage)
andreas@3151
  1017
            rowAction.backgroundColor = iconColor
andreas@3151
  1018
        }
andreas@3151
  1019
        return rowAction
andreas@3151
  1020
    }
andreas@5658
  1021
xavier@3369
  1022
    func flagAction(forCellAt indexPath: IndexPath) {
borja@4926
  1023
        guard let row = model?.viewModel(for: indexPath.row) else {
agp@8234
  1024
            Log.shared.errorAndCrash("No data for indexPath!")
xavier@3369
  1025
            return
andreas@3151
  1026
        }
xavier@6919
  1027
        guard let cell = self.tableView.cellForRow(at: indexPath) as? EmailListViewCell else {
agp@8234
  1028
            Log.shared.errorAndCrash("No cell for indexPath!")
xavier@6919
  1029
            return
xavier@6919
  1030
        }
xavier@3369
  1031
        if row.isFlagged {
agp@8730
  1032
            model?.unsetFlagged(forIndexPath: [indexPath])
xavier@6919
  1033
            cell.isFlagged = false
xavier@3369
  1034
        } else {
agp@8730
  1035
            model?.setFlagged(forIndexPath: [indexPath])
xavier@6919
  1036
            cell.isFlagged = true
andreas@3151
  1037
        }
andreas@3151
  1038
    }
miguel@4826
  1039
xavier@3369
  1040
    func deleteAction(forCellAt indexPath: IndexPath) {
andreas@3911
  1041
        model?.delete(forIndexPath: indexPath)
andreas@3151
  1042
    }
andreas@5658
  1043
xavier@3369
  1044
    func moreAction(forCellAt indexPath: IndexPath) {
andreas@6158
  1045
        showMoreActionSheet(forRowAt: indexPath)
andreas@3151
  1046
    }
andreas@3151
  1047
}
andreas@3151
  1048
dirk@5481
  1049
// MARK: - Segue handling
dirk@5481
  1050
dirk@5481
  1051
extension EmailListViewController {
dirk@5481
  1052
    /**
dirk@5481
  1053
     Enables manual account setup to unwind to the unified inbox.
dirk@5481
  1054
     */
dirk@5593
  1055
    @IBAction func segueUnwindAfterAccountCreation(segue:UIStoryboardSegue) {
borja@6781
  1056
        setup()
dirk@5482
  1057
    }
dirk@5481
  1058
}
dirk@5481
  1059
andreas@3151
  1060
// MARK: - SegueHandlerType
igor@1338
  1061
igor@1338
  1062
extension EmailListViewController: SegueHandlerType {
andreas@3208
  1063
    
igor@1338
  1064
    enum SegueIdentifier: String {
igor@1338
  1065
        case segueAddNewAccount
xavier@9534
  1066
        case segueShowEmailIpad
xavier@9534
  1067
        case segueShowEmailIphone
igor@1338
  1068
        case segueCompose
xavier@2715
  1069
        case segueReply
xavier@1623
  1070
        case segueReplyAll
xavier@1664
  1071
        case segueForward
andreas@3505
  1072
        case segueEditDraft
xavier@5634
  1073
        case segueShowFilter
xavier@2248
  1074
        case segueFolderViews
andreas@4615
  1075
        case segueShowMoveToFolder
borja@4800
  1076
        case showNoMessage
borja@4906
  1077
        case segueShowThreadedEmail
igor@1338
  1078
        case noSegue
igor@1338
  1079
    }
andreas@3208
  1080
    
igor@1338
  1081
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
andreas@3505
  1082
        let segueId = segueIdentifier(for: segue)
andreas@3505
  1083
        switch segueId {
andreas@3505
  1084
        case .segueReply,
andreas@3505
  1085
             .segueReplyAll,
andreas@3505
  1086
             .segueForward,
andreas@3505
  1087
             .segueCompose,
andreas@3505
  1088
             .segueEditDraft:
andreas@3505
  1089
            setupComposeViewController(for: segue)
xavier@9534
  1090
        case .segueShowEmailIpad:
borja@4755
  1091
            guard let nav = segue.destination as? UINavigationController,
borja@4755
  1092
                let vc = nav.rootViewController as? EmailViewController,
andreas@3151
  1093
                let indexPath = lastSelectedIndexPath,
andreas@3208
  1094
                let message = model?.message(representedByRowAt: indexPath) else {
agp@8234
  1095
                    Log.shared.errorAndCrash("Segue issue")
andreas@2850
  1096
                    return
igor@1338
  1097
            }
andreas@2850
  1098
            vc.appConfig = appConfig
andreas@3151
  1099
            vc.message = message
xavier@8140
  1100
            //vc.folderShow = model?.getFolderToShow()
andreas@8476
  1101
            vc.messageId = indexPath.row //!!!: that looks wrong
miguel@4826
  1102
            vc.delegate = model
borja@4856
  1103
            model?.currentDisplayedMessage = vc
xavier@8068
  1104
            model?.indexPathShown = indexPath
xavier@9534
  1105
        case .segueShowEmailIphone:
xavier@9534
  1106
            guard let vc = segue.destination as? EmailViewController,
xavier@9534
  1107
                let indexPath = lastSelectedIndexPath,
xavier@9534
  1108
                let message = model?.message(representedByRowAt: indexPath) else {
xavier@9534
  1109
                    Log.shared.errorAndCrash("Segue issue")
xavier@9534
  1110
                    return
xavier@9534
  1111
            }
xavier@9534
  1112
            vc.appConfig = appConfig
xavier@9534
  1113
            vc.message = message
xavier@9534
  1114
            //vc.folderShow = model?.getFolderToShow()
xavier@9534
  1115
            vc.messageId = indexPath.row //!!!: that looks wrong
xavier@9534
  1116
            vc.delegate = model
xavier@9534
  1117
            model?.currentDisplayedMessage = vc
xavier@9534
  1118
            model?.indexPathShown = indexPath
xavier@9534
  1119
borja@6816
  1120
      //  case .segueShowThreadedEmail:
borja@6816
  1121
        /*    guard let nav = segue.destination as? UINavigationController,
borja@4906
  1122
                let vc = nav.rootViewController as? ThreadViewController,
borja@4906
  1123
                let indexPath = lastSelectedIndexPath,
borja@4906
  1124
                let folder = folderToShow else {
borja@4906
  1125
                    return
borja@4906
  1126
            }
borja@5020
  1127
            guard let message = model?.message(representedByRowAt: indexPath) else {
agp@8234
  1128
                Log.shared.errorAndCrash("Segue issue")
borja@5020
  1129
                return
borja@5020
  1130
            }
borja@4906
  1131
            vc.appConfig = appConfig
borja@5124
  1132
            let viewModel = ThreadedEmailViewModel(tip:message, folder: folder)
andreas@6158
  1133
            viewModel.emailDisplayDelegate = model
borja@5124
  1134
            vc.model = viewModel
borja@5164
  1135
            model?.currentDisplayedMessage = viewModel
borja@6816
  1136
            model?.updateThreadListDelegate = viewModel*/
xavier@5634
  1137
        case .segueShowFilter:
andreas@2850
  1138
            guard let destiny = segue.destination as? FilterTableViewController  else {
agp@8234
  1139
                Log.shared.errorAndCrash("Segue issue")
andreas@2850
  1140
                return
andreas@2850
  1141
            }
andreas@8250
  1142
            guard let vm = model else {
andreas@8525
  1143
                Log.shared.errorAndCrash("No VM")
andreas@8250
  1144
                return
andreas@8250
  1145
            }
andreas@2850
  1146
            destiny.appConfig = appConfig
andreas@8250
  1147
            destiny.filterDelegate = vm
andreas@8250
  1148
            destiny.filterEnabled = vm.currentFilter
andreas@2850
  1149
            destiny.hidesBottomBarWhenPushed = true
andreas@2850
  1150
        case .segueAddNewAccount:
andreas@4918
  1151
            guard
andreas@4918
  1152
                let nav = segue.destination as? UINavigationController,
miguel@5469
  1153
                let vc = nav.rootViewController as? LoginViewController else {
agp@8234
  1154
                    Log.shared.errorAndCrash("Segue issue")
andreas@4918
  1155
                    return
andreas@2850
  1156
            }
andreas@2850
  1157
            vc.appConfig = appConfig
andreas@4918
  1158
            vc.delegate = self
andreas@2850
  1159
            vc.hidesBottomBarWhenPushed = true
xavier@1664
  1160
            break
andreas@2850
  1161
        case .segueFolderViews:
andreas@2850
  1162
            guard let vC = segue.destination as? FolderTableViewController  else {
agp@8234
  1163
                Log.shared.errorAndCrash("Segue issue")
andreas@2850
  1164
                return
xavier@1865
  1165
            }
andreas@2850
  1166
            vC.appConfig = appConfig
andreas@8250
  1167
            //!!!: was commented. Is this dead code? if so, rm!
xavier@5501
  1168
            //vC.hidesBottomBarWhenPushed = true
xavier@1865
  1169
            break
andreas@4615
  1170
        case .segueShowMoveToFolder:
xavier@4892
  1171
            var selectedRows: [IndexPath] = []
xavier@4892
  1172
andreas@6158
  1173
            if let selectedItems = tableView.indexPathsForSelectedRows {
xavier@4892
  1174
                selectedRows = selectedItems
andreas@5489
  1175
            } else if let last = lastSelectedIndexPath {
xavier@4892
  1176
                selectedRows.append(last)
xavier@4892
  1177
            }
xavier@4892
  1178
andreas@4615
  1179
            guard  let nav = segue.destination as? UINavigationController,
borja@6836
  1180
                let destination = nav.topViewController as? MoveToAccountViewController
andreas@4615
  1181
                else {
agp@8234
  1182
                    Log.shared.errorAndCrash("No DVC?")
andreas@4615
  1183
                    break
andreas@4615
  1184
            }
borja@6836
  1185
borja@6836
  1186
            destination.viewModel
borja@6836
  1187
                = model?.getMoveToFolderViewModel(forSelectedMessages: selectedRows)
xavier@5509
  1188
            destination.delegate = model
andreas@4615
  1189
            destination.appConfig = appConfig
borja@4800
  1190
            break
borja@4800
  1191
        case .showNoMessage:
borja@4800
  1192
            //No initialization needed
borja@4800
  1193
            break
andreas@2850
  1194
        default:
agp@8234
  1195
            Log.shared.errorAndCrash("Unhandled segue")
dirk@1694
  1196
            break
igor@1338
  1197
        }
andreas@2838
  1198
    }
andreas@5658
  1199
andreas@3166
  1200
    @IBAction func segueUnwindAccountAdded(segue: UIStoryboardSegue) {
andreas@3151
  1201
        // nothing to do.
andreas@3150
  1202
    }
andreas@3505
  1203
andreas@3505
  1204
    private func setupComposeViewController(for segue: UIStoryboardSegue) {
andreas@3505
  1205
        let segueId = segueIdentifier(for: segue)
andreas@3505
  1206
        guard
andreas@3505
  1207
            let nav = segue.destination as? UINavigationController,
andreas@3505
  1208
            let composeVc = nav.topViewController as? ComposeTableViewController,
miguel@7252
  1209
            let composeMode = composeMode(for: segueId),
miguel@7252
  1210
            let vm = model else {
agp@8234
  1211
                Log.shared.errorAndCrash("composeViewController setup issue")
andreas@3505
  1212
                return
andreas@3505
  1213
        }
andreas@3505
  1214
        composeVc.appConfig = appConfig
miguel@7252
  1215
andreas@5543
  1216
        if segueId != .segueCompose {
andreas@3505
  1217
            // This is not a simple compose (but reply, forward or such),
miguel@5126
  1218
            // thus we have to pass the original message.
miguel@7252
  1219
            guard let indexPath = lastSelectedIndexPath else {
andreas@8805
  1220
                    Log.shared.info("Can happen if the message the user wanted to reply to has been deleted in between performeSeque and here")
andreas@3505
  1221
                    return
andreas@3505
  1222
            }
andreas@6707
  1223
andreas@6767
  1224
            composeVc.viewModel = vm.composeViewModel(withOriginalMessageAt: indexPath,
andreas@6767
  1225
                                                      composeMode: composeMode)
xavier@8208
  1226
        } else {
miguel@7252
  1227
            composeVc.viewModel = vm.composeViewModelForNewMessage()
miguel@7252
  1228
        }
andreas@3505
  1229
    }
andreas@3505
  1230
andreas@5538
  1231
    private func composeMode(for segueId: SegueIdentifier) -> ComposeUtil.ComposeMode? {
andreas@3505
  1232
        switch segueId {
andreas@3505
  1233
        case .segueReply:
andreas@3505
  1234
            return .replyFrom
andreas@3505
  1235
        case .segueReplyAll:
andreas@3505
  1236
            return .replyAll
andreas@3505
  1237
        case .segueForward:
andreas@3505
  1238
            return .forward
andreas@3505
  1239
        case .segueCompose:
andreas@3505
  1240
            return .normal
andreas@3505
  1241
        case .segueEditDraft:
andreas@5539
  1242
            return .normal
andreas@3505
  1243
        default:
andreas@3505
  1244
            return nil
andreas@3505
  1245
        }
andreas@3505
  1246
    }
dirk@1348
  1247
}
xavier@3368
  1248
miguel@5549
  1249
// MARK: - LoginViewControllerDelegate
andreas@4918
  1250
miguel@5469
  1251
extension EmailListViewController: LoginViewControllerDelegate {
andreas@6295
  1252
    func loginViewControllerDidCreateNewAccount(_ loginViewController: LoginViewController) {
andreas@4918
  1253
        // Setup model after initial account setup
andreas@4918
  1254
        setup()
andreas@4918
  1255
    }
andreas@4918
  1256
}
andreas@4918
  1257