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