session-ios/Session/Home/HomeViewModel.swift

119 lines
4.9 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import DifferenceKit
import SignalUtilitiesKit
public class HomeViewModel {
public enum Section: Differentiable {
case messageRequests
case threads
}
public struct State: Equatable {
let showViewedSeedBanner: Bool
let userProfile: Profile?
let sections: [ArraySection<Section, SessionThreadViewModel>]
func with(
showViewedSeedBanner: Bool? = nil,
userProfile: Profile? = nil,
sections: [ArraySection<Section, SessionThreadViewModel>]? = nil
) -> State {
return State(
showViewedSeedBanner: (showViewedSeedBanner ?? self.showViewedSeedBanner),
userProfile: (userProfile ?? self.userProfile),
sections: (sections ?? self.sections)
)
}
}
/// This value is the current state of the view
public private(set) var state: State = State(
showViewedSeedBanner: !GRDBStorage.shared[.hasViewedSeed],
userProfile: nil,
sections: []
)
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
///
/// **Note:** This observation will be triggered twice immediately (and be de-duped by the `removeDuplicates`)
/// this is due to the behaviour of `ValueConcurrentObserver.asyncStartObservation` which triggers it's own
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
public lazy var observableState = ValueObservation
.tracking(
regions: [
// We explicitly define the regions we want to track as the automatic detection
// seems to include a bunch of columns we will fetch but probably don't need to
// track changes for
SessionThread.select(
.id,
.shouldBeVisible,
.isPinned,
.mutedUntilTimestamp,
.onlyNotifyForMentions
),
Setting.filter(ids: [
Setting.BoolKey.hasHiddenMessageRequests.rawValue,
Setting.BoolKey.hasViewedSeed.rawValue
]),
Contact.select(.isBlocked, .isApproved), // 'isApproved' for message requests
Profile.select(.name, .nickname, .profilePictureFileName),
ClosedGroup.select(.name),
OpenGroup.select(.name, .imageData),
GroupMember.select(.groupId),
Interaction.select(
.body,
.wasRead
),
Attachment.select(.state),
RecipientState.select(.state),
ThreadTypingIndicator.select(.threadId)
],
fetch: { db -> State in
let hasViewedSeed: Bool = db[.hasViewedSeed]
let userProfile: Profile = Profile.fetchOrCreateCurrentUser(db)
let unreadMessageRequestCount: Int = try SessionThread
.unreadMessageRequestsQuery(userPublicKey: userProfile.id)
.fetchCount(db)
let finalUnreadMessageRequestCount: Int = (db[.hasHiddenMessageRequests] ? 0 : unreadMessageRequestCount)
let threads: [SessionThreadViewModel] = try SessionThreadViewModel
.homeQuery(userPublicKey: userProfile.id)
.fetchAll(db)
return State(
showViewedSeedBanner: !hasViewedSeed,
userProfile: userProfile,
sections: [
ArraySection(
model: .messageRequests,
elements: [
// If there are no unread message requests then hide the message request banner
(finalUnreadMessageRequestCount == 0 ?
nil :
SessionThreadViewModel(
unreadCount: UInt(finalUnreadMessageRequestCount)
)
)
].compactMap { $0 }
),
ArraySection(
model: .threads,
elements: threads
)
]
)
}
)
.removeDuplicates()
// MARK: - Functions
public func updateState(_ updatedState: State) {
self.state = updatedState
}
}