pEpForiOS/UI/EmailDisplayList/EmailListViewModel.swift
author Xavier Algarra <xavier@pep-project.org>
Mon, 07 May 2018 11:11:24 +0200
branchIOS-1064
changeset 4550 c04fe6d5c76b
parent 4472 dc539372d7ef
permissions -rw-r--r--
IOS-1064 some changes
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
xavier@2344
    10
import MessageModel
xavier@2344
    11
andreas@3677
    12
protocol EmailListViewModelDelegate: TableViewUpdate {
andreas@3151
    13
    func emailListViewModel(viewModel: EmailListViewModel, didInsertDataAt indexPath: IndexPath)
andreas@3151
    14
    func emailListViewModel(viewModel: EmailListViewModel, didUpdateDataAt indexPath: IndexPath)
andreas@3151
    15
    func emailListViewModel(viewModel: EmailListViewModel, didRemoveDataAt indexPath: IndexPath)
andreas@3151
    16
}
xavier@2362
    17
andreas@3151
    18
// MARK: - FilterUpdateProtocol
xavier@2344
    19
andreas@3151
    20
extension EmailListViewModel: FilterUpdateProtocol {
xavier@3193
    21
    public func addFilter(_ filter: CompositeFilter<FilterBase>) {
andreas@3163
    22
        setFilterViewFilter(filter: filter)
andreas@3151
    23
    }
andreas@3151
    24
}
xavier@2425
    25
andreas@3163
    26
// MARK: - EmailListViewModel
andreas@3163
    27
andreas@3151
    28
class EmailListViewModel {
andreas@3677
    29
    let messageFolderDelegateHandlingQueue = DispatchQueue(label:
andreas@3442
    30
        "net.pep-security-EmailListViewModel-MessageFolderDelegateHandling")
andreas@3677
    31
    let contactImageTool = IdentityImageTool()
andreas@3677
    32
    let messageSyncService: MessageSyncServiceProtocol
andreas@3151
    33
    class Row {
andreas@3151
    34
        var senderContactImage: UIImage?
andreas@3151
    35
        var ratingImage: UIImage?
andreas@3151
    36
        var showAttchmentIcon: Bool = false
andreas@3151
    37
        let from: String
andreas@3522
    38
        let to: String
andreas@3151
    39
        let subject: String
andreas@3151
    40
        let bodyPeek: String
andreas@3151
    41
        var isFlagged: Bool = false
andreas@3151
    42
        var isSeen: Bool = false
andreas@3151
    43
        var dateText: String
andreas@3162
    44
        
andreas@3151
    45
        init(withPreviewMessage pvmsg: PreviewMessage, senderContactImage: UIImage? = nil) {
andreas@3151
    46
            self.senderContactImage = senderContactImage
andreas@3151
    47
            showAttchmentIcon = pvmsg.hasAttachments
andreas@3151
    48
            from = pvmsg.from.userNameOrAddress
andreas@3522
    49
            to = pvmsg.to
andreas@3151
    50
            subject = pvmsg.subject
andreas@3151
    51
            bodyPeek = pvmsg.bodyPeek
andreas@3151
    52
            isFlagged = pvmsg.isFlagged
andreas@3151
    53
            isSeen = pvmsg.isSeen
andreas@3151
    54
            dateText = pvmsg.dateSent.smartString()
xavier@2344
    55
        }
xavier@2344
    56
    }
andreas@3162
    57
    
andreas@3151
    58
    private var messages: SortedSet<PreviewMessage>?
andreas@3678
    59
    private let queue: OperationQueue = {
andreas@3678
    60
        let createe = OperationQueue()
andreas@3678
    61
        createe.qualityOfService = .userInteractive
andreas@3678
    62
        return createe
andreas@3678
    63
    }()
andreas@3677
    64
    public var delegate: EmailListViewModelDelegate?
andreas@3226
    65
    private var folderToShow: Folder?
andreas@3677
    66
    
andreas@3677
    67
    let sortByDateSentAscending: SortedSet<PreviewMessage>.SortBlock =
andreas@3226
    68
    { (pvMsg1: PreviewMessage, pvMsg2: PreviewMessage) -> ComparisonResult in
andreas@3226
    69
        if pvMsg1.dateSent > pvMsg2.dateSent {
andreas@3226
    70
            return .orderedAscending
andreas@3226
    71
        } else if pvMsg1.dateSent < pvMsg2.dateSent {
andreas@3226
    72
            return .orderedDescending
andreas@4066
    73
        } else if pvMsg1.uid > pvMsg2.uid {
andreas@4066
    74
            return .orderedAscending
andreas@4066
    75
        } else if pvMsg1.uid < pvMsg2.uid {
andreas@4066
    76
            return .orderedDescending
andreas@3226
    77
        } else {
andreas@3226
    78
            return .orderedSame
andreas@3151
    79
        }
andreas@3151
    80
    }
andreas@3162
    81
    
andreas@3162
    82
    // MARK: Life Cycle
andreas@3162
    83
    
andreas@3291
    84
    init(delegate: EmailListViewModelDelegate? = nil, messageSyncService: MessageSyncServiceProtocol,
andreas@3291
    85
         folderToShow: Folder? = nil) {
andreas@3226
    86
        self.messages = SortedSet(array: [], sortBlock: sortByDateSentAscending)
andreas@3151
    87
        self.delegate = delegate
andreas@3291
    88
        self.messageSyncService = messageSyncService
andreas@3151
    89
        self.folderToShow = folderToShow
andreas@3226
    90
        resetViewModel()
andreas@3226
    91
    }
andreas@3226
    92
andreas@3226
    93
    private func startListeningToChanges() {
andreas@3151
    94
        MessageModelConfig.messageFolderDelegate = self
andreas@3151
    95
    }
andreas@3226
    96
andreas@3226
    97
    private func stopListeningToChanges() {
andreas@3226
    98
        MessageModelConfig.messageFolderDelegate = nil
andreas@3226
    99
    }
andreas@3162
   100
    
andreas@3162
   101
    private func resetViewModel() {
andreas@3162
   102
        guard let folder = folderToShow else {
andreas@3162
   103
            Log.shared.errorAndCrash(component: #function, errorString: "No data, no cry.")
andreas@3162
   104
            return
andreas@3151
   105
        }
andreas@3677
   106
        // Ignore MessageModelConfig.messageFolderDelegate while reloading.
andreas@3226
   107
        self.stopListeningToChanges()
andreas@3678
   108
        queue.addOperation {
andreas@3226
   109
            let messagesToDisplay = folder.allMessages()
andreas@3226
   110
            let previewMessages = messagesToDisplay.map { PreviewMessage(withMessage: $0) }
andreas@3226
   111
andreas@3677
   112
            self.messages = SortedSet(array: previewMessages, sortBlock: self.sortByDateSentAscending)
andreas@3678
   113
            DispatchQueue.main.async {
andreas@3678
   114
                self.delegate?.updateView()
andreas@3678
   115
                self.startListeningToChanges()
andreas@3678
   116
            }
xavier@2344
   117
        }
xavier@2363
   118
    }
andreas@3162
   119
    
andreas@3162
   120
    // MARK: Internal
andreas@3162
   121
    
andreas@3151
   122
    private func indexOfPreviewMessage(forMessage msg:Message) -> Int? {
andreas@3151
   123
        guard let previewMessages = messages else {
andreas@3151
   124
            Log.shared.errorAndCrash(component: #function, errorString: "No data.")
andreas@3151
   125
            return nil
andreas@3151
   126
        }
andreas@3151
   127
        for i in 0..<previewMessages.count {
andreas@3151
   128
            guard let pvMsg = previewMessages.object(at: i) else {
andreas@3151
   129
                Log.shared.errorAndCrash(component: #function, errorString: "Inconsistant data")
andreas@3151
   130
                return nil
andreas@3151
   131
            }
andreas@3151
   132
            if pvMsg == msg {
andreas@3151
   133
                return i
andreas@3151
   134
            }
andreas@3151
   135
        }
andreas@3151
   136
        return nil
xavier@2344
   137
    }
andreas@3162
   138
    
andreas@3162
   139
    // MARK: Public Data Access & Manipulation
andreas@3162
   140
    
andreas@3162
   141
    func row(for indexPath: IndexPath) -> Row? {
andreas@3162
   142
        guard let previewMessage = messages?.object(at: indexPath.row) else {
andreas@3162
   143
            Log.shared.errorAndCrash(component: #function,
andreas@3162
   144
                                     errorString: "InconsistencyviewModel vs. model")
andreas@3162
   145
            return nil
andreas@3162
   146
        }
andreas@3162
   147
        if let cachedSenderImage = contactImageTool.cachedIdentityImage(forIdentity: previewMessage.from) {
andreas@3162
   148
            return Row(withPreviewMessage: previewMessage, senderContactImage: cachedSenderImage)
andreas@3162
   149
        } else {
andreas@3162
   150
            return Row(withPreviewMessage: previewMessage)
andreas@3162
   151
        }
andreas@3162
   152
    }
andreas@3162
   153
    
andreas@3162
   154
    var rowCount: Int {
andreas@3162
   155
        return messages?.count ?? 0
andreas@3162
   156
    }
xavier@4550
   157
xavier@4550
   158
    func updateLastLookAt() {}
andreas@3162
   159
    
andreas@3151
   160
    /// Returns the senders contact image to display.
andreas@3151
   161
    /// This is a possibly time consuming process and shold not be called from the main thread.
andreas@3151
   162
    ///
andreas@3151
   163
    /// - Parameter indexPath: row indexpath to get the contact image for
andreas@3151
   164
    /// - Returns: contact image to display
andreas@3151
   165
    func senderImage(forCellAt indexPath:IndexPath) -> UIImage? {
andreas@3151
   166
        guard let previewMessage = messages?.object(at: indexPath.row) else {
andreas@3151
   167
            Log.shared.errorAndCrash(component: #function,
andreas@3151
   168
                                     errorString: "InconsistencyviewModel vs. model")
andreas@3151
   169
            return nil
andreas@3151
   170
        }
andreas@3151
   171
        return contactImageTool.identityImage(for: previewMessage.from)
andreas@3151
   172
    }
andreas@3162
   173
    
andreas@3151
   174
    private func cachedSenderImage(forCellAt indexPath:IndexPath) -> UIImage? {
andreas@4384
   175
        guard
andreas@4384
   176
            let msgs = messages,
andreas@4384
   177
            indexPath.row < msgs.count,
andreas@4384
   178
            let previewMessage = messages?.object(at: indexPath.row)
andreas@4384
   179
            else {
andreas@4384
   180
            // The model has been updated.
andreas@3151
   181
            return nil
andreas@3151
   182
        }
andreas@3151
   183
        return contactImageTool.cachedIdentityImage(forIdentity: previewMessage.from)
andreas@3151
   184
    }
andreas@3162
   185
    
andreas@3151
   186
    func pEpRatingColorImage(forCellAt indexPath: IndexPath) -> UIImage? {
andreas@4384
   187
        guard
andreas@4384
   188
            let msgs = messages,
andreas@4384
   189
            indexPath.row < msgs.count,
andreas@4384
   190
            let previewMessage = messages?.object(at: indexPath.row),
andreas@4384
   191
            let message = previewMessage.message()
andreas@4384
   192
            else {
andreas@4384
   193
                // The model has been updated.
andreas@3151
   194
                return nil
andreas@3151
   195
        }
andreas@3321
   196
        let color = PEPUtil.pEpColor(pEpRating: message.pEpRating())
xavier@3384
   197
        let result = color.statusIcon()
andreas@3151
   198
        return result
andreas@3151
   199
    }
andreas@3162
   200
    
andreas@3151
   201
    func setFlagged(forIndexPath indexPath: IndexPath) {
andreas@3950
   202
        setFlaggedValue(forIndexPath: indexPath, newValue: true)
andreas@3151
   203
    }
andreas@3162
   204
    
andreas@3151
   205
    func unsetFlagged(forIndexPath indexPath: IndexPath) {
andreas@3950
   206
        setFlaggedValue(forIndexPath: indexPath, newValue: false)
andreas@3151
   207
    }
andreas@3162
   208
    
andreas@3425
   209
    func markRead(forIndexPath indexPath: IndexPath) {
andreas@3425
   210
        guard let previewMessage = messages?.object(at: indexPath.row) else {
andreas@3425
   211
            return
andreas@3425
   212
        }
andreas@4465
   213
        DispatchQueue.main.async { [weak self] in
andreas@4465
   214
            guard let me = self else {
andreas@4465
   215
                Log.shared.errorAndCrash(component: #function, errorString: "Lost myself")
andreas@4465
   216
                return
andreas@4465
   217
            }
andreas@4465
   218
            previewMessage.isSeen = true
andreas@4465
   219
            me.delegate?.emailListViewModel(viewModel: me, didUpdateDataAt: indexPath)
andreas@4465
   220
        }
andreas@3425
   221
    }
andreas@3425
   222
    
andreas@3151
   223
    func delete(forIndexPath indexPath: IndexPath) {
andreas@3151
   224
        guard let previewMessage = messages?.object(at: indexPath.row),
andreas@3151
   225
            let message = previewMessage.message() else {
andreas@3151
   226
                return
andreas@3151
   227
        }
andreas@3151
   228
        messages?.remove(object: previewMessage)
andreas@3515
   229
        message.imapDelete()
andreas@3151
   230
    }
andreas@3162
   231
    
andreas@3151
   232
    func message(representedByRowAt indexPath: IndexPath) -> Message? {
andreas@3151
   233
        return messages?.object(at: indexPath.row)?.message()
andreas@3151
   234
    }
andreas@3162
   235
    
andreas@3151
   236
    func freeMemory() {
andreas@3151
   237
        contactImageTool.clearCache()
andreas@3151
   238
    }
andreas@3162
   239
    
andreas@3950
   240
    private func setFlaggedValue(forIndexPath indexPath: IndexPath, newValue flagged: Bool) {
andreas@3151
   241
        guard let previewMessage = messages?.object(at: indexPath.row),
andreas@3151
   242
            let message = previewMessage.message() else {
andreas@3151
   243
                return
andreas@3151
   244
        }
andreas@3151
   245
        message.imapFlags?.flagged = flagged
andreas@3503
   246
        didUpdate(messageFolder: message)
andreas@3151
   247
        DispatchQueue.main.async {
andreas@3151
   248
            message.save()
andreas@3151
   249
        }
andreas@3151
   250
    }
andreas@3166
   251
andreas@3169
   252
    public func reloadData() {
andreas@3169
   253
        resetViewModel()
andreas@3169
   254
    }
andreas@3169
   255
andreas@3162
   256
    // MARK: Filter
andreas@3162
   257
    
andreas@3163
   258
    public var isFilterEnabled = false {
andreas@3163
   259
        didSet {
andreas@3163
   260
            handleFilterEnabledSwitch()
andreas@3163
   261
        }
andreas@3163
   262
    }
xavier@3193
   263
    public var activeFilter : CompositeFilter<FilterBase>? {
andreas@3163
   264
        get {
andreas@3163
   265
            guard let folder = folderToShow else {
andreas@3163
   266
                return nil
andreas@3163
   267
            }
andreas@3163
   268
            return folder.filter
andreas@3163
   269
        }
andreas@3163
   270
    }
andreas@3166
   271
dirk@4387
   272
    static let defaultFilterViewFilter = CompositeFilter<FilterBase>.defaultFilter()
xavier@3193
   273
    private var _filterViewFilter: CompositeFilter = defaultFilterViewFilter
xavier@3193
   274
    private var filterViewFilter: CompositeFilter<FilterBase> {
andreas@3163
   275
        get {
andreas@3163
   276
            if _filterViewFilter.isEmpty() {
andreas@3163
   277
                _filterViewFilter = EmailListViewModel.defaultFilterViewFilter
andreas@3163
   278
            }
andreas@3163
   279
            return _filterViewFilter
andreas@3163
   280
        }
andreas@3163
   281
        set {
andreas@3163
   282
            _filterViewFilter = newValue
andreas@3163
   283
        }
andreas@3163
   284
    }
andreas@3163
   285
xavier@3193
   286
    private func setFilterViewFilter(filter: CompositeFilter<FilterBase>) {
andreas@3163
   287
        if isFilterEnabled {
andreas@3163
   288
            let folderFilter = assuredFilterOfFolderToShow()
xavier@3193
   289
            folderFilter.without(filters: filterViewFilter)
dirk@4387
   290
            folderFilter.with(filters: filter)
andreas@3163
   291
            resetViewModel()
andreas@3163
   292
        }
andreas@3163
   293
        filterViewFilter = filter
andreas@3163
   294
    }
andreas@3163
   295
andreas@3163
   296
    private func handleFilterEnabledSwitch() {
andreas@3163
   297
        let folderFilter = assuredFilterOfFolderToShow()
andreas@3163
   298
        if isFilterEnabled {
dirk@4387
   299
            folderFilter.with(filters: filterViewFilter)
andreas@3163
   300
        } else {
xavier@3193
   301
            folderFilter.without(filters: filterViewFilter)
andreas@3163
   302
        }
andreas@3163
   303
        resetViewModel()
andreas@3163
   304
    }
andreas@3162
   305
    
andreas@3167
   306
    public func setSearchFilter(forSearchText txt: String = "") {
andreas@3162
   307
        if txt == "" {
andreas@3167
   308
            assuredFilterOfFolderToShow().removeSearchFilter()
andreas@3167
   309
        } else {
andreas@3167
   310
            let folderFilter = assuredFilterOfFolderToShow()
andreas@3167
   311
            folderFilter.removeSearchFilter()
andreas@4472
   312
            let searchFilter = SearchFilter(searchTerm: txt)
xavier@3193
   313
            folderFilter.add(filter: searchFilter)
andreas@3151
   314
        }
andreas@3162
   315
        resetViewModel()
andreas@3151
   316
    }
andreas@3162
   317
    
andreas@3151
   318
    public func removeSearchFilter() {
andreas@3151
   319
        guard let filter = folderToShow?.filter else {
andreas@3166
   320
            Log.shared.errorAndCrash(component: #function, errorString: "No folder.")
andreas@3151
   321
            return
andreas@3151
   322
        }
andreas@3151
   323
        filter.removeSearchFilter()
andreas@3151
   324
        resetViewModel()
xavier@2425
   325
    }
andreas@3163
   326
xavier@3193
   327
    private func assuredFilterOfFolderToShow() -> CompositeFilter<FilterBase> {
andreas@3163
   328
        guard let folder = folderToShow else {
andreas@3163
   329
            Log.shared.errorAndCrash(component: #function, errorString: "No folder.")
dirk@4387
   330
            return CompositeFilter<FilterBase>.defaultFilter()
andreas@3163
   331
        }
andreas@3163
   332
        if folder.filter == nil{
andreas@3163
   333
            folder.resetFilter()
andreas@3163
   334
        }
andreas@3163
   335
andreas@3163
   336
        guard let folderFilter = folder.filter else {
andreas@3163
   337
            Log.shared.errorAndCrash(component: #function,
andreas@3163
   338
                                     errorString: "We just set the filter but do not have one?")
dirk@4387
   339
            return CompositeFilter<FilterBase>.defaultFilter()
andreas@3163
   340
        }
andreas@3163
   341
        return folderFilter
andreas@3163
   342
    }
andreas@3291
   343
andreas@3291
   344
    // MARK: - Fetch Older Messages
andreas@3291
   345
andreas@3291
   346
    /// The number of rows (not yet displayed to the user) before we want to fetch older messages.
andreas@3291
   347
    /// A balance between good user experience (have data in time, ideally before the user has scrolled
andreas@3291
   348
    /// to the last row) and memory usage has to be found.
andreas@3291
   349
    private let numRowsBeforeLastToTriggerFetchOder = 1
andreas@3291
   350
andreas@3291
   351
    /// Figures out whether or not fetching of older messages should be requested.
andreas@3600
   352
    /// Takes numRowsBeforeLastToTriggerFetchOder into account,
andreas@3291
   353
    ///
andreas@3291
   354
    /// - Parameter row: number of displayed tableView row to base computation on
andreas@3291
   355
    /// - Returns: true if fetch older messages should be requested, false otherwize
andreas@3291
   356
    private func triggerFetchOlder(lastDisplayedRow row: Int) -> Bool {
andreas@3600
   357
        return row >= rowCount - numRowsBeforeLastToTriggerFetchOder
andreas@3291
   358
    }
andreas@3291
   359
andreas@3291
   360
    // Implemented to get informed about the currently visible cells.
andreas@3291
   361
    // If the user has scrolled down (almost) to the end, we ask for older emails.
andreas@3291
   362
andreas@3291
   363
    /// Get informed about the new visible cells.
andreas@3291
   364
    /// If the user has scrolled down (almost) to the end, we ask for older emails.
andreas@3291
   365
    ///
andreas@3291
   366
    /// - Parameter indexPath: indexpath to check need for fetch older for
andreas@3291
   367
    public func fetchOlderMessagesIfRequired(forIndexPath indexPath: IndexPath) {
andreas@3291
   368
        guard let folder = folderToShow else {
andreas@3291
   369
            return
andreas@3291
   370
        }
andreas@3291
   371
        if !triggerFetchOlder(lastDisplayedRow: indexPath.row) {
andreas@3291
   372
            return
andreas@3291
   373
        }
andreas@3291
   374
        if folder is UnifiedInbox {
andreas@3291
   375
            guard let unified = folder as? UnifiedInbox else {
andreas@3291
   376
                Log.shared.errorAndCrash(component: #function, errorString: "Error casting")
andreas@3291
   377
                return
andreas@3291
   378
            }
andreas@3291
   379
            requestFetchOlder(forFolders: unified.folders)
andreas@3291
   380
        } else {
andreas@3291
   381
            requestFetchOlder(forFolders: [folder])
andreas@3291
   382
        }
andreas@3291
   383
    }
andreas@3291
   384
andreas@3291
   385
    private func requestFetchOlder(forFolders folders: [Folder]) {
andreas@3291
   386
        DispatchQueue.main.async { [weak self] in
andreas@3291
   387
            for folder in folders {
andreas@3291
   388
                self?.messageSyncService.requestFetchOlderMessages(inFolder: folder)
andreas@3291
   389
            }
andreas@3291
   390
        }
andreas@3291
   391
    }
andreas@3151
   392
}
andreas@3151
   393
andreas@3151
   394
// MARK: - MessageFolderDelegate
andreas@3151
   395
andreas@3151
   396
extension EmailListViewModel: MessageFolderDelegate {
andreas@3442
   397
andreas@3151
   398
    func didCreate(messageFolder: MessageFolder) {
andreas@3442
   399
        messageFolderDelegateHandlingQueue.async {
andreas@3151
   400
            self.didCreateInternal(messageFolder: messageFolder)
xavier@2425
   401
        }
xavier@2369
   402
    }
andreas@3162
   403
    
andreas@3155
   404
    func didUpdate(messageFolder: MessageFolder) {
andreas@3442
   405
        messageFolderDelegateHandlingQueue.async {
andreas@3155
   406
            self.didUpdateInternal(messageFolder: messageFolder)
andreas@3155
   407
        }
andreas@3155
   408
    }
andreas@3162
   409
    
andreas@3155
   410
    func didDelete(messageFolder: MessageFolder) {
andreas@3442
   411
        messageFolderDelegateHandlingQueue.async {
andreas@3155
   412
            self.didDeleteInternal(messageFolder: messageFolder)
andreas@3155
   413
        }
andreas@3155
   414
    }
andreas@3162
   415
    
andreas@3157
   416
    private func didCreateInternal(messageFolder: MessageFolder) {
andreas@3281
   417
        guard let message = messageFolder as? Message else {
andreas@3281
   418
            // The createe is no message. Ignore.
andreas@3281
   419
            return
andreas@3151
   420
        }
andreas@3543
   421
        if !shouldBeDisplayed(message: message){
andreas@3301
   422
            return
andreas@3301
   423
        }
andreas@3281
   424
        // Is a Message (not a Folder)
andreas@3281
   425
        if let filter = folderToShow?.filter,
dirk@4387
   426
            !filter.fulfillsFilter(message: message) {
andreas@3281
   427
            // The message does not fit in current filter criteria. Ignore- and do not show it.
andreas@3281
   428
            return
andreas@3281
   429
        }
andreas@3281
   430
        let previewMessage = PreviewMessage(withMessage: message)
dirk@3865
   431
dirk@3865
   432
        DispatchQueue.main.async { [weak self] in
dirk@3865
   433
            if let theSelf = self {
dirk@3865
   434
                guard let index = theSelf.messages?.insert(object: previewMessage) else {
dirk@3865
   435
                    Log.shared.errorAndCrash(component: #function,
dirk@3865
   436
                                             errorString: "We should be able to insert.")
dirk@3865
   437
                    return
dirk@3865
   438
                }
dirk@3865
   439
                let indexPath = IndexPath(row: index, section: 0)
dirk@3865
   440
                theSelf.delegate?.emailListViewModel(viewModel: theSelf, didInsertDataAt: indexPath)
dirk@3865
   441
            }
andreas@3442
   442
        }
andreas@3151
   443
    }
andreas@3162
   444
    
andreas@3157
   445
    private func didDeleteInternal(messageFolder: MessageFolder) {
andreas@3301
   446
        // Make sure it is a Message (not a Folder). Flag must have changed
andreas@3301
   447
        guard let message = messageFolder as? Message else {
andreas@3301
   448
            // It is not a Message (probably it is a Folder).
andreas@3301
   449
            return
andreas@3151
   450
        }
andreas@3543
   451
        if !shouldBeDisplayed(message: message){
andreas@3301
   452
            return
andreas@3301
   453
        }
andreas@3301
   454
        // Is a Message (not a Folder)
andreas@3301
   455
        guard let indexExisting = indexOfPreviewMessage(forMessage: message) else {
andreas@3301
   456
            // We do not have this message in our model, so we do not have to remove it
andreas@3301
   457
            return
andreas@3301
   458
        }
andreas@3301
   459
        guard let pvMsgs = messages else {
andreas@3301
   460
            Log.shared.errorAndCrash(component: #function, errorString: "Missing data")
andreas@3301
   461
            return
andreas@3301
   462
        }
andreas@4384
   463
        DispatchQueue.main.async { [weak self] in
andreas@4384
   464
            if let me = self {
andreas@4384
   465
                pvMsgs.removeObject(at: indexExisting)
andreas@4384
   466
                let indexPath = IndexPath(row: indexExisting, section: 0)
andreas@4384
   467
                me.delegate?.emailListViewModel(viewModel: me, didRemoveDataAt: indexPath)
andreas@4384
   468
            }
andreas@3442
   469
        }
andreas@3151
   470
    }
andreas@3162
   471
    
andreas@3157
   472
    private func didUpdateInternal(messageFolder: MessageFolder) {
andreas@3282
   473
        // Make sure it is a Message (not a Folder). Flag must have changed
andreas@3282
   474
        guard let message = messageFolder as? Message else {
andreas@3282
   475
            // It is not a Message (probably it is a Folder).
andreas@3282
   476
            return
andreas@3282
   477
        }
andreas@3543
   478
        if !shouldBeDisplayed(message: message){
andreas@3301
   479
            return
andreas@3301
   480
        }
andreas@3282
   481
        guard let pvMsgs = messages else {
andreas@3282
   482
            Log.shared.errorAndCrash(component: #function, errorString: "Missing data")
andreas@3282
   483
            return
andreas@3282
   484
        }
andreas@3282
   485
andreas@3282
   486
        if indexOfPreviewMessage(forMessage: message) == nil {
andreas@4384
   487
            // We do not have this updated message in our model yet. It might have been updated in
andreas@4384
   488
            // a way, that fulfills the current filters now but did not before the update.
andreas@3282
   489
            // Or it has just been decrypted.
andreas@3282
   490
            // Forward to didCreateInternal to figure out if we want to display it.
andreas@3282
   491
            self.didCreateInternal(messageFolder: messageFolder)
andreas@3282
   492
            return
andreas@3282
   493
        }
andreas@3282
   494
andreas@3282
   495
        // We do have this message in our model, so we do have to update it
andreas@3282
   496
        guard let indexExisting = indexOfPreviewMessage(forMessage: message),
andreas@3282
   497
            let existingMessage = pvMsgs.object(at: indexExisting) else {
andreas@3282
   498
                Log.shared.errorAndCrash(component: #function,
andreas@3282
   499
                                         errorString: "We should have the message at this point")
andreas@3151
   500
                return
andreas@3282
   501
        }
andreas@3282
   502
andreas@3282
   503
        let previewMessage = PreviewMessage(withMessage: message)
andreas@3282
   504
        if !previewMessage.flagsDiffer(previewMessage: existingMessage) {
andreas@3285
   505
            // The only message properties displayed in this view that might be updated are flagged and seen.
andreas@3285
   506
            // We got called even the flaggs did not change. Ignore.
andreas@3282
   507
            return
andreas@3282
   508
        }
andreas@3503
   509
        
andreas@3282
   510
        let indexToRemove = pvMsgs.index(of: existingMessage)
andreas@4384
   511
        DispatchQueue.main.async { [weak self] in
andreas@4384
   512
            if let me = self {
andreas@4384
   513
                pvMsgs.removeObject(at: indexToRemove)
andreas@3503
   514
andreas@4384
   515
                if let filter = me.folderToShow?.filter,
dirk@4387
   516
                    !filter.fulfillsFilter(message: message) {
andreas@4384
   517
                    // The message was included in the model, but does not fulfil the filter criteria
andreas@4384
   518
                    // anymore after it has been updated.
andreas@4384
   519
                    // Remove it.
andreas@4384
   520
                    let indexPath = IndexPath(row: indexToRemove, section: 0)
andreas@4384
   521
                    me.delegate?.emailListViewModel(viewModel: me, didRemoveDataAt: indexPath)
andreas@4384
   522
                    return
andreas@4384
   523
                }
andreas@4384
   524
                // The updated message has to be shown. Add it to the model ...
andreas@4384
   525
                let indexInserted = pvMsgs.insert(object: previewMessage)
andreas@4384
   526
                if indexToRemove != indexInserted  {Log.shared.warn(component: #function,
andreas@4384
   527
                                                                    content:
andreas@4384
   528
                    """
andreas@3503
   529
When updating a message, the the new index of the message must be the same as the old index.
andreas@3503
   530
Something is fishy here.
andreas@3225
   531
"""
andreas@4384
   532
                    )
andreas@4384
   533
                }
andreas@4384
   534
                // ...  and inform the delegate.
andreas@4384
   535
                let indexPath = IndexPath(row: indexInserted, section: 0)
andreas@4384
   536
                me.delegate?.emailListViewModel(viewModel: me, didUpdateDataAt: indexPath)
andreas@4384
   537
            }
andreas@3442
   538
        }
andreas@3151
   539
    }
andreas@3301
   540
andreas@3543
   541
    private func shouldBeDisplayed(message: Message) -> Bool {
andreas@3543
   542
        if !isInFolderToShow(message: message) {
andreas@3543
   543
            return false
andreas@3543
   544
        }
andreas@3543
   545
        if message.isEncrypted {
andreas@3543
   546
            return false
andreas@3543
   547
        }
andreas@3543
   548
        return true
andreas@3543
   549
    }
andreas@3543
   550
andreas@3301
   551
    private func isInFolderToShow(message: Message) -> Bool {
andreas@3303
   552
        if folderToShow is UnifiedInbox {
andreas@3303
   553
            if message.parent.folderType == .inbox {
andreas@3303
   554
                return true
andreas@3303
   555
            }
andreas@3303
   556
        } else {
andreas@3303
   557
            return message.parent == folderToShow
andreas@3303
   558
        }
andreas@3303
   559
        return false
andreas@3301
   560
    }
xavier@2344
   561
}