pEpForiOS/UI/EmailDisplayList/EmailListViewModel.swift
author Miguel Berrocal Go?mez <miguel@helm.cat>
Thu, 16 Aug 2018 13:03:38 +0200
changeset 5849 3cee57dd8029
parent 5846 996278780e43
child 5850 11fdaba12c1d
permissions -rw-r--r--
IOS-1559 Update all message properties when receiving a new thread message. New thread messages move top message to top.
xavier@2344
     1
//
xavier@2344
     2
//  EmailListViewModel.swift
xavier@2344
     3
//  pEpForiOS
xavier@2344
     4
//
xavier@2344
     5
//  Created by Xavier Algarra on 23/06/2017.
xavier@2344
     6
//  Copyright © 2017 p≡p Security S.A. All rights reserved.
xavier@2344
     7
//
xavier@2344
     8
xavier@2344
     9
import Foundation
dirk@4984
    10
xavier@2344
    11
import MessageModel
xavier@2344
    12
andreas@3677
    13
protocol EmailListViewModelDelegate: TableViewUpdate {
andreas@5489
    14
    func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPaths: [IndexPath])
andreas@5489
    15
    func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPaths: [IndexPath])
andreas@5489
    16
    func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPaths: [IndexPath])
miguel@5849
    17
    func emailListViewModel(viewModel: EmailListViewModel, didMoveData atIndexPath: IndexPath, toIndexPath: IndexPath)
dirk@5117
    18
    func emailListViewModel(viewModel: EmailListViewModel,
dirk@5117
    19
                            didUpdateUndisplayedMessage message: Message)
xavier@4905
    20
    func toolbarIs(enabled: Bool)
xavier@4892
    21
    func showUnflagButton(enabled: Bool)
xavier@4892
    22
    func showUnreadButton(enabled: Bool)
andreas@3151
    23
}
xavier@2362
    24
andreas@3151
    25
// MARK: - FilterUpdateProtocol
xavier@2344
    26
andreas@3151
    27
extension EmailListViewModel: FilterUpdateProtocol {
xavier@3193
    28
    public func addFilter(_ filter: CompositeFilter<FilterBase>) {
andreas@3163
    29
        setFilterViewFilter(filter: filter)
andreas@3151
    30
    }
andreas@3151
    31
}
xavier@2425
    32
andreas@3163
    33
// MARK: - EmailListViewModel
andreas@3163
    34
andreas@3151
    35
class EmailListViewModel {
andreas@3677
    36
    let messageFolderDelegateHandlingQueue = DispatchQueue(label:
andreas@3442
    37
        "net.pep-security-EmailListViewModel-MessageFolderDelegateHandling")
andreas@3677
    38
    let contactImageTool = IdentityImageTool()
andreas@3677
    39
    let messageSyncService: MessageSyncServiceProtocol
miguel@5738
    40
    internal var messages: SortedSet<MessageViewModel>
andreas@3678
    41
    private let queue: OperationQueue = {
andreas@3678
    42
        let createe = OperationQueue()
andreas@5763
    43
        createe.qualityOfService = .userInitiated
andreas@5762
    44
        createe.maxConcurrentOperationCount = 1
andreas@3678
    45
        return createe
andreas@3678
    46
    }()
dirk@4954
    47
dirk@5009
    48
    public var emailListViewModelDelegate: EmailListViewModelDelegate?
dirk@4954
    49
dirk@4986
    50
    internal let folderToShow: Folder
dirk@4986
    51
    internal let threadedMessageFolder: ThreadedMessageFolderProtocol
borja@4856
    52
borja@4856
    53
    public var currentDisplayedMessage: DisplayedMessage?
borja@5616
    54
    public var screenComposer: ScreenComposerProtocol?
borja@4856
    55
miguel@5738
    56
    let sortByDateSentAscending: SortedSet<MessageViewModel>.SortBlock =
miguel@5738
    57
    { (pvMsg1: MessageViewModel, pvMsg2: MessageViewModel) -> ComparisonResult in
andreas@3226
    58
        if pvMsg1.dateSent > pvMsg2.dateSent {
andreas@3226
    59
            return .orderedAscending
andreas@3226
    60
        } else if pvMsg1.dateSent < pvMsg2.dateSent {
andreas@3226
    61
            return .orderedDescending
andreas@4066
    62
        } else if pvMsg1.uid > pvMsg2.uid {
andreas@4066
    63
            return .orderedAscending
andreas@4066
    64
        } else if pvMsg1.uid < pvMsg2.uid {
andreas@4066
    65
            return .orderedDescending
andreas@3226
    66
        } else {
andreas@3226
    67
            return .orderedSame
andreas@3151
    68
        }
andreas@3151
    69
    }
xavier@4892
    70
xavier@4892
    71
    private var selectedItems: Set<IndexPath>?
dirk@4953
    72
dirk@4953
    73
    weak var updateThreadListDelegate: UpdateThreadListDelegate?
xavier@5560
    74
    var defaultFilter: CompositeFilter<FilterBase>?
xavier@5734
    75
xavier@5734
    76
    var oldThreadSetting : Bool
andreas@3162
    77
    
dirk@4922
    78
    // MARK: - Life Cycle
andreas@3162
    79
    
dirk@5009
    80
    init(emailListViewModelDelegate: EmailListViewModelDelegate? = nil,
dirk@4980
    81
         messageSyncService: MessageSyncServiceProtocol,
dirk@4851
    82
         folderToShow: Folder) {
andreas@3226
    83
        self.messages = SortedSet(array: [], sortBlock: sortByDateSentAscending)
dirk@5009
    84
        self.emailListViewModelDelegate = emailListViewModelDelegate
andreas@3291
    85
        self.messageSyncService = messageSyncService
dirk@4956
    86
andreas@3151
    87
        self.folderToShow = folderToShow
borja@4893
    88
        self.threadedMessageFolder = FolderThreading.makeThreadAware(folder: folderToShow)
xavier@5560
    89
        self.defaultFilter = folderToShow.filter?.clone()
xavier@5734
    90
        self.oldThreadSetting = AppSettings.threadedViewEnabled
xavier@5734
    91
        
andreas@3226
    92
        resetViewModel()
xavier@5734
    93
        
xavier@5734
    94
    }
xavier@5734
    95
    
xavier@5734
    96
    //check if there are some important settings that have changed to force a reload
xavier@5734
    97
    func checkIfSettingsChanged() -> Bool {
xavier@5734
    98
        if AppSettings.threadedViewEnabled != oldThreadSetting {
xavier@5734
    99
            oldThreadSetting = AppSettings.threadedViewEnabled
xavier@5734
   100
            return true
xavier@5734
   101
        }
xavier@5734
   102
        return false
andreas@3226
   103
    }
andreas@3226
   104
miguel@4891
   105
    internal func startListeningToChanges() {
andreas@3151
   106
        MessageModelConfig.messageFolderDelegate = self
andreas@3151
   107
    }
andreas@3226
   108
miguel@4891
   109
    internal func stopListeningToChanges() {
andreas@3226
   110
        MessageModelConfig.messageFolderDelegate = nil
andreas@3226
   111
    }
andreas@5655
   112
andreas@3162
   113
    private func resetViewModel() {
andreas@3677
   114
        // Ignore MessageModelConfig.messageFolderDelegate while reloading.
andreas@3226
   115
        self.stopListeningToChanges()
andreas@5755
   116
andreas@5755
   117
        queue.cancelAllOperations()
andreas@5755
   118
andreas@5755
   119
        let op = BlockOperation()
andreas@5755
   120
        weak var weakOp = op
andreas@5755
   121
        op.addExecutionBlock { [weak self] in
andreas@5755
   122
            guard
andreas@5755
   123
                let me = self,
andreas@5755
   124
                let op = weakOp,
andreas@5755
   125
                !op.isCancelled else {
andreas@5657
   126
                return
andreas@5657
   127
            }
andreas@5657
   128
            let messagesToDisplay = me.folderToShow.allMessages()
andreas@5657
   129
            let previewMessages = messagesToDisplay.map {
miguel@5738
   130
                MessageViewModel(with: $0)
andreas@5657
   131
            }
andreas@5657
   132
            me.messages = SortedSet(array: previewMessages,
andreas@5657
   133
                                    sortBlock: me.sortByDateSentAscending)
andreas@5755
   134
            if op.isCancelled {
andreas@5755
   135
                return
andreas@5755
   136
            }
andreas@5755
   137
            DispatchQueue.main.sync {
andreas@5657
   138
                me.emailListViewModelDelegate?.updateView()
andreas@5657
   139
                me.startListeningToChanges()
andreas@3678
   140
            }
xavier@2344
   141
        }
andreas@5755
   142
        queue.addOperation(op)
xavier@2363
   143
    }
andreas@5655
   144
dirk@4922
   145
    // MARK: - Public Data Access & Manipulation
miguel@4826
   146
dirk@4973
   147
    func index(of message: Message) -> Int? {
miguel@5738
   148
        return messages.index(of: MessageViewModel(with: message))
miguel@4826
   149
    }
andreas@5655
   150
borja@4926
   151
    func viewModel(for index: Int) -> MessageViewModel? {
miguel@5738
   152
        guard let messageViewModel = messages.object(at: index) else {
andreas@3162
   153
            Log.shared.errorAndCrash(component: #function,
andreas@3162
   154
                                     errorString: "InconsistencyviewModel vs. model")
andreas@3162
   155
            return nil
andreas@3162
   156
        }
miguel@5738
   157
        return messageViewModel
andreas@3162
   158
    }
borja@4926
   159
andreas@3162
   160
    var rowCount: Int {
dirk@4982
   161
        return messages.count
andreas@3162
   162
    }
andreas@5655
   163
andreas@3151
   164
    /// Returns the senders contact image to display.
andreas@3151
   165
    /// This is a possibly time consuming process and shold not be called from the main thread.
andreas@3151
   166
    ///
andreas@3151
   167
    /// - Parameter indexPath: row indexpath to get the contact image for
andreas@3151
   168
    /// - Returns: contact image to display
andreas@3151
   169
    func senderImage(forCellAt indexPath:IndexPath) -> UIImage? {
dirk@4982
   170
        guard let previewMessage = messages.object(at: indexPath.row) else {
andreas@3151
   171
            Log.shared.errorAndCrash(component: #function,
andreas@3151
   172
                                     errorString: "InconsistencyviewModel vs. model")
andreas@3151
   173
            return nil
andreas@3151
   174
        }
miguel@5738
   175
        return contactImageTool.identityImage(for: previewMessage.identity)
andreas@3151
   176
    }
andreas@5655
   177
andreas@3151
   178
    private func cachedSenderImage(forCellAt indexPath:IndexPath) -> UIImage? {
andreas@4384
   179
        guard
dirk@4982
   180
            indexPath.row < messages.count,
dirk@4982
   181
            let previewMessage = messages.object(at: indexPath.row)
andreas@4384
   182
            else {
andreas@4384
   183
            // The model has been updated.
andreas@3151
   184
            return nil
andreas@3151
   185
        }
miguel@5738
   186
        return contactImageTool.cachedIdentityImage(forIdentity: previewMessage.identity)
andreas@3151
   187
    }
andreas@5655
   188
andreas@3151
   189
    func pEpRatingColorImage(forCellAt indexPath: IndexPath) -> UIImage? {
andreas@4384
   190
        guard
dirk@4982
   191
            indexPath.row < messages.count,
dirk@4982
   192
            let previewMessage = messages.object(at: indexPath.row),
andreas@4384
   193
            let message = previewMessage.message()
andreas@4384
   194
            else {
andreas@4384
   195
                // The model has been updated.
andreas@3151
   196
                return nil
andreas@3151
   197
        }
andreas@3321
   198
        let color = PEPUtil.pEpColor(pEpRating: message.pEpRating())
dirk@4593
   199
        if color != PEP_color_no_color {
dirk@4593
   200
            return color.statusIcon()
dirk@4593
   201
        } else {
dirk@4593
   202
            return nil
dirk@4593
   203
        }
andreas@3151
   204
    }
xavier@4793
   205
xavier@4793
   206
    //multiple message selection handler
xavier@4793
   207
xavier@4892
   208
    private var unreadMessages = false
xavier@4892
   209
    private var flaggedMessages = false
xavier@4892
   210
xavier@4892
   211
    public func updatedItems(indexPaths: [IndexPath]) {
xavier@4892
   212
        checkUnreadMessages(indexPaths: indexPaths)
xavier@4892
   213
        checkFlaggedMessages(indexPaths: indexPaths)
xavier@4905
   214
        if indexPaths.count > 0 {
dirk@5009
   215
            emailListViewModelDelegate?.toolbarIs(enabled: true)
xavier@4905
   216
        } else {
dirk@5009
   217
            emailListViewModelDelegate?.toolbarIs(enabled: false)
xavier@4905
   218
        }
xavier@4892
   219
    }
xavier@4892
   220
xavier@4892
   221
    public func checkFlaggedMessages(indexPaths: [IndexPath]) {
xavier@4892
   222
        let flagged = indexPaths.filter { (ip) -> Bool in
borja@4926
   223
            if let flag = viewModel(for: ip.row)?.isFlagged {
xavier@4892
   224
                return flag
xavier@4892
   225
            }
xavier@4892
   226
            return false
xavier@4827
   227
        }
xavier@4892
   228
xavier@4892
   229
        if flagged.count == indexPaths.count {
dirk@5009
   230
            emailListViewModelDelegate?.showUnflagButton(enabled: true)
xavier@4897
   231
        } else {
dirk@5009
   232
            emailListViewModelDelegate?.showUnflagButton(enabled: false)
xavier@4827
   233
        }
xavier@4793
   234
    }
xavier@4793
   235
xavier@4892
   236
    public func checkUnreadMessages(indexPaths: [IndexPath]) {
xavier@4892
   237
        let read = indexPaths.filter { (ip) -> Bool in
borja@4926
   238
            if let read = viewModel(for: ip.row)?.isSeen {
xavier@4892
   239
                return read
xavier@4892
   240
            }
xavier@4892
   241
            return false
xavier@4827
   242
        }
xavier@4892
   243
xavier@4892
   244
        if read.count == indexPaths.count {
dirk@5009
   245
            emailListViewModelDelegate?.showUnreadButton(enabled: true)
xavier@4892
   246
        } else {
dirk@5009
   247
            emailListViewModelDelegate?.showUnreadButton(enabled: false)
xavier@4827
   248
        }
xavier@4793
   249
    }
xavier@4793
   250
xavier@4892
   251
    public func markSelectedAsFlagged(indexPaths: [IndexPath]) {
xavier@4892
   252
        indexPaths.forEach { (ip) in
xavier@4793
   253
            setFlagged(forIndexPath: ip)
xavier@4793
   254
        }
xavier@4793
   255
    }
xavier@4793
   256
xavier@4892
   257
    public func markSelectedAsUnFlagged(indexPaths: [IndexPath]) {
xavier@4892
   258
        indexPaths.forEach { (ip) in
xavier@4793
   259
            unsetFlagged(forIndexPath: ip)
xavier@4793
   260
        }
xavier@4793
   261
    }
xavier@4793
   262
xavier@4892
   263
    public func markSelectedAsRead(indexPaths: [IndexPath]) {
xavier@4892
   264
        indexPaths.forEach { (ip) in
xavier@4793
   265
            markRead(forIndexPath: ip)
xavier@4793
   266
        }
xavier@4793
   267
    }
xavier@4853
   268
xavier@4897
   269
    public func markSelectedAsUnread(indexPaths: [IndexPath]) {
xavier@4897
   270
        indexPaths.forEach { (ip) in
xavier@4897
   271
            markUnread(forIndexPath: ip)
xavier@4897
   272
        }
xavier@4897
   273
    }
xavier@4897
   274
xavier@4897
   275
    public func deleteSelected(indexPaths: [IndexPath]) {
miguel@5738
   276
        var deletees = [MessageViewModel]()
xavier@4897
   277
        indexPaths.forEach { (ip) in
andreas@5489
   278
            guard let previewMessage = messages.object(at: ip.row)else {
andreas@5489
   279
                    return
andreas@5489
   280
            }
andreas@5489
   281
            deletees.append(previewMessage)
andreas@5489
   282
        }
xavier@4897
   283
andreas@5489
   284
        for pvm in deletees {
andreas@5489
   285
            guard let message = pvm.message() else {
andreas@5489
   286
                Log.shared.errorAndCrash(component: #function, errorString: "No mesage")
andreas@5489
   287
                return
andreas@5489
   288
            }
andreas@5489
   289
            delete(message: message)
andreas@5489
   290
            messages.remove(object: pvm)
xavier@4897
   291
        }
andreas@5489
   292
        emailListViewModelDelegate?.emailListViewModel(viewModel: self, didRemoveDataAt: indexPaths)
xavier@4897
   293
    }
xavier@4897
   294
xavier@4892
   295
    public func messagesToMove(indexPaths: [IndexPath]) -> [Message?] {
xavier@4853
   296
        var messages : [Message?] = []
xavier@4892
   297
        indexPaths.forEach { (ip) in
xavier@4853
   298
            messages.append(self.message(representedByRowAt: ip))
xavier@4853
   299
        }
xavier@4853
   300
        return messages
xavier@4853
   301
    }
andreas@3162
   302
    
andreas@3151
   303
    func setFlagged(forIndexPath indexPath: IndexPath) {
andreas@3950
   304
        setFlaggedValue(forIndexPath: indexPath, newValue: true)
andreas@3151
   305
    }
andreas@5655
   306
andreas@3151
   307
    func unsetFlagged(forIndexPath indexPath: IndexPath) {
andreas@3950
   308
        setFlaggedValue(forIndexPath: indexPath, newValue: false)
andreas@3151
   309
    }
andreas@3162
   310
    
andreas@3425
   311
    func markRead(forIndexPath indexPath: IndexPath) {
dirk@4982
   312
        guard let previewMessage = messages.object(at: indexPath.row) else {
andreas@3425
   313
            return
andreas@3425
   314
        }
andreas@4465
   315
        DispatchQueue.main.async { [weak self] in
andreas@4465
   316
            guard let me = self else {
andreas@4465
   317
                Log.shared.errorAndCrash(component: #function, errorString: "Lost myself")
andreas@4465
   318
                return
andreas@4465
   319
            }
andreas@4465
   320
            previewMessage.isSeen = true
dirk@5009
   321
            me.emailListViewModelDelegate?.emailListViewModel(viewModel: me,
andreas@5489
   322
                                                                      didUpdateDataAt: [indexPath])
andreas@4465
   323
        }
andreas@3425
   324
    }
xavier@4897
   325
xavier@4897
   326
    func markUnread(forIndexPath indexPath: IndexPath) {
dirk@4982
   327
        guard let previewMessage = messages.object(at: indexPath.row) else {
xavier@4897
   328
            return
xavier@4897
   329
        }
xavier@4897
   330
        DispatchQueue.main.async { [weak self] in
xavier@4897
   331
            guard let me = self else {
xavier@4897
   332
                Log.shared.errorAndCrash(component: #function, errorString: "Lost myself")
xavier@4897
   333
                return
xavier@4897
   334
            }
xavier@4897
   335
            previewMessage.isSeen = false
dirk@5009
   336
            me.emailListViewModelDelegate?.emailListViewModel(viewModel: me,
andreas@5489
   337
                                                                      didUpdateDataAt: [indexPath])
xavier@4897
   338
        }
xavier@4897
   339
    }
andreas@5655
   340
andreas@3151
   341
    func delete(forIndexPath indexPath: IndexPath) {
andreas@5489
   342
        guard let deletedMessage = deleteMessage(at: indexPath) else {
andreas@5489
   343
            Log.shared.errorAndCrash(component: #function,
andreas@5489
   344
                                     errorString: "Not sure if this is a valid case. Remove this " +
andreas@5489
   345
                "log if so.")
andreas@5489
   346
            return
andreas@5489
   347
        }
dirk@5724
   348
        didDelete(messageFolder: deletedMessage, belongingToThread: Set())
andreas@5489
   349
    }
andreas@5489
   350
andreas@5489
   351
    private func deleteMessage(at indexPath: IndexPath) -> Message? {
dirk@4982
   352
        guard let previewMessage = messages.object(at: indexPath.row),
andreas@3151
   353
            let message = previewMessage.message() else {
andreas@5489
   354
                return nil
andreas@3151
   355
        }
andreas@5489
   356
        delete(message: message)
andreas@5489
   357
        return message
andreas@5489
   358
    }
dirk@5150
   359
andreas@5489
   360
    private func delete(message: Message) {
dirk@5150
   361
        // The message to delete might be a single, unthreaded message,
dirk@5150
   362
        // or the tip of a thread. `threadedMessageFolder` will figure it out.
dirk@5150
   363
        threadedMessageFolder.deleteThread(message: message)
andreas@3151
   364
    }
andreas@5655
   365
andreas@3151
   366
    func message(representedByRowAt indexPath: IndexPath) -> Message? {
dirk@4982
   367
        return messages.object(at: indexPath.row)?.message()
andreas@3151
   368
    }
andreas@5655
   369
andreas@3151
   370
    func freeMemory() {
andreas@3151
   371
        contactImageTool.clearCache()
andreas@3151
   372
    }
andreas@3162
   373
    
miguel@4826
   374
    internal func setFlaggedValue(forIndexPath indexPath: IndexPath, newValue flagged: Bool) {
dirk@4982
   375
        guard let previewMessage = messages.object(at: indexPath.row),
andreas@3151
   376
            let message = previewMessage.message() else {
andreas@3151
   377
                return
andreas@3151
   378
        }
andreas@3151
   379
        message.imapFlags?.flagged = flagged
andreas@3503
   380
        didUpdate(messageFolder: message)
andreas@3151
   381
        DispatchQueue.main.async {
andreas@3151
   382
            message.save()
andreas@3151
   383
        }
andreas@3151
   384
    }
andreas@3166
   385
andreas@3169
   386
    public func reloadData() {
andreas@3169
   387
        resetViewModel()
andreas@3169
   388
    }
andreas@3169
   389
dirk@4922
   390
    // MARK: - Filter
andreas@3162
   391
    
andreas@3163
   392
    public var isFilterEnabled = false {
andreas@3163
   393
        didSet {
andreas@3163
   394
            handleFilterEnabledSwitch()
andreas@3163
   395
        }
andreas@3163
   396
    }
xavier@3193
   397
    public var activeFilter : CompositeFilter<FilterBase>? {
dirk@4851
   398
        return folderToShow.filter
andreas@3163
   399
    }
andreas@3166
   400
dirk@4387
   401
    static let defaultFilterViewFilter = CompositeFilter<FilterBase>.defaultFilter()
xavier@3193
   402
    private var _filterViewFilter: CompositeFilter = defaultFilterViewFilter
xavier@3193
   403
    private var filterViewFilter: CompositeFilter<FilterBase> {
andreas@3163
   404
        get {
andreas@3163
   405
            if _filterViewFilter.isEmpty() {
andreas@3163
   406
                _filterViewFilter = EmailListViewModel.defaultFilterViewFilter
andreas@3163
   407
            }
andreas@3163
   408
            return _filterViewFilter
andreas@3163
   409
        }
andreas@3163
   410
        set {
andreas@3163
   411
            _filterViewFilter = newValue
andreas@3163
   412
        }
andreas@3163
   413
    }
andreas@3163
   414
xavier@3193
   415
    private func setFilterViewFilter(filter: CompositeFilter<FilterBase>) {
andreas@3163
   416
        if isFilterEnabled {
andreas@3163
   417
            let folderFilter = assuredFilterOfFolderToShow()
xavier@3193
   418
            folderFilter.without(filters: filterViewFilter)
dirk@4387
   419
            folderFilter.with(filters: filter)
andreas@3163
   420
            resetViewModel()
andreas@3163
   421
        }
andreas@3163
   422
        filterViewFilter = filter
andreas@3163
   423
    }
andreas@3163
   424
andreas@3163
   425
    private func handleFilterEnabledSwitch() {
andreas@3163
   426
        let folderFilter = assuredFilterOfFolderToShow()
andreas@3163
   427
        if isFilterEnabled {
dirk@4387
   428
            folderFilter.with(filters: filterViewFilter)
andreas@3163
   429
        } else {
xavier@5638
   430
            self.folderToShow.filter = defaultFilter?.clone()
andreas@3163
   431
        }
andreas@3163
   432
        resetViewModel()
andreas@3163
   433
    }
andreas@5655
   434
andreas@3167
   435
    public func setSearchFilter(forSearchText txt: String = "") {
andreas@3162
   436
        if txt == "" {
andreas@3167
   437
            assuredFilterOfFolderToShow().removeSearchFilter()
andreas@3167
   438
        } else {
andreas@3167
   439
            let folderFilter = assuredFilterOfFolderToShow()
andreas@3167
   440
            folderFilter.removeSearchFilter()
andreas@4472
   441
            let searchFilter = SearchFilter(searchTerm: txt)
xavier@3193
   442
            folderFilter.add(filter: searchFilter)
andreas@3151
   443
        }
andreas@3162
   444
        resetViewModel()
andreas@3151
   445
    }
andreas@5655
   446
andreas@3151
   447
    public func removeSearchFilter() {
dirk@4851
   448
        guard let filter = folderToShow.filter else {
andreas@3166
   449
            Log.shared.errorAndCrash(component: #function, errorString: "No folder.")
andreas@3151
   450
            return
andreas@3151
   451
        }
andreas@3151
   452
        filter.removeSearchFilter()
andreas@3151
   453
        resetViewModel()
xavier@2425
   454
    }
andreas@3163
   455
xavier@3193
   456
    private func assuredFilterOfFolderToShow() -> CompositeFilter<FilterBase> {
dirk@4851
   457
        if folderToShow.filter == nil {
dirk@4851
   458
            folderToShow.resetFilter()
andreas@3163
   459
        }
dirk@4548
   460
dirk@4851
   461
        guard let folderFilter = folderToShow.filter else {
andreas@3163
   462
            Log.shared.errorAndCrash(component: #function,
andreas@3163
   463
                                     errorString: "We just set the filter but do not have one?")
dirk@4387
   464
            return CompositeFilter<FilterBase>.defaultFilter()
andreas@3163
   465
        }
andreas@3163
   466
        return folderFilter
andreas@3163
   467
    }
andreas@3291
   468
andreas@3291
   469
    // MARK: - Fetch Older Messages
andreas@3291
   470
andreas@3291
   471
    /// The number of rows (not yet displayed to the user) before we want to fetch older messages.
dirk@4980
   472
    /// A balance between good user experience (have data in time,
dirk@4980
   473
    /// ideally before the user has scrolled to the last row) and memory usage has to be found.
andreas@3291
   474
    private let numRowsBeforeLastToTriggerFetchOder = 1
andreas@3291
   475
andreas@3291
   476
    /// Figures out whether or not fetching of older messages should be requested.
andreas@3600
   477
    /// Takes numRowsBeforeLastToTriggerFetchOder into account,
andreas@3291
   478
    ///
andreas@3291
   479
    /// - Parameter row: number of displayed tableView row to base computation on
andreas@3291
   480
    /// - Returns: true if fetch older messages should be requested, false otherwize
andreas@3291
   481
    private func triggerFetchOlder(lastDisplayedRow row: Int) -> Bool {
andreas@3600
   482
        return row >= rowCount - numRowsBeforeLastToTriggerFetchOder
andreas@3291
   483
    }
andreas@3291
   484
andreas@3291
   485
    // Implemented to get informed about the currently visible cells.
andreas@3291
   486
    // If the user has scrolled down (almost) to the end, we ask for older emails.
andreas@3291
   487
andreas@3291
   488
    /// Get informed about the new visible cells.
andreas@3291
   489
    /// If the user has scrolled down (almost) to the end, we ask for older emails.
andreas@3291
   490
    ///
andreas@3291
   491
    /// - Parameter indexPath: indexpath to check need for fetch older for
andreas@3291
   492
    public func fetchOlderMessagesIfRequired(forIndexPath indexPath: IndexPath) {
andreas@3291
   493
        if !triggerFetchOlder(lastDisplayedRow: indexPath.row) {
andreas@3291
   494
            return
andreas@3291
   495
        }
dirk@4851
   496
        if let unifiedFolder = folderToShow as? UnifiedInbox {
dirk@4815
   497
            requestFetchOlder(forFolders: unifiedFolder.folders)
andreas@3291
   498
        } else {
dirk@4851
   499
            requestFetchOlder(forFolders: [folderToShow])
andreas@3291
   500
        }
andreas@3291
   501
    }
andreas@3291
   502
andreas@3291
   503
    private func requestFetchOlder(forFolders folders: [Folder]) {
andreas@3291
   504
        DispatchQueue.main.async { [weak self] in
andreas@3291
   505
            for folder in folders {
andreas@3291
   506
                self?.messageSyncService.requestFetchOlderMessages(inFolder: folder)
andreas@3291
   507
            }
andreas@3291
   508
        }
andreas@3291
   509
    }
andreas@3151
   510
}