|
|
@ -49,11 +49,13 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
private var markAsReadPublisher: AnyPublisher<Void, Never>?
|
|
|
|
private var markAsReadPublisher: AnyPublisher<Void, Never>?
|
|
|
|
|
|
|
|
|
|
|
|
public lazy var blockedBannerMessage: String = {
|
|
|
|
public lazy var blockedBannerMessage: String = {
|
|
|
|
switch self.threadData.threadVariant {
|
|
|
|
let threadData: SessionThreadViewModel = self._threadData.wrappedValue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch threadData.threadVariant {
|
|
|
|
case .contact:
|
|
|
|
case .contact:
|
|
|
|
let name: String = Profile.displayName(
|
|
|
|
let name: String = Profile.displayName(
|
|
|
|
id: self.threadData.threadId,
|
|
|
|
id: threadData.threadId,
|
|
|
|
threadVariant: self.threadData.threadVariant
|
|
|
|
threadVariant: threadData.threadVariant
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return "\(name) is blocked. Unblock them?"
|
|
|
|
return "\(name) is blocked. Unblock them?"
|
|
|
@ -140,16 +142,18 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
self.focusedInteractionInfo = (focusedInteractionInfo ?? initialData?.initialUnreadInteractionInfo)
|
|
|
|
self.focusedInteractionInfo = (focusedInteractionInfo ?? initialData?.initialUnreadInteractionInfo)
|
|
|
|
self.focusBehaviour = (focusedInteractionInfo == nil ? .none : .highlight)
|
|
|
|
self.focusBehaviour = (focusedInteractionInfo == nil ? .none : .highlight)
|
|
|
|
self.initialUnreadInteractionId = initialData?.initialUnreadInteractionInfo?.id
|
|
|
|
self.initialUnreadInteractionId = initialData?.initialUnreadInteractionInfo?.id
|
|
|
|
self.threadData = SessionThreadViewModel(
|
|
|
|
self._threadData = Atomic(
|
|
|
|
threadId: threadId,
|
|
|
|
SessionThreadViewModel(
|
|
|
|
threadVariant: threadVariant,
|
|
|
|
threadId: threadId,
|
|
|
|
threadIsNoteToSelf: (initialData?.currentUserPublicKey == threadId),
|
|
|
|
threadVariant: threadVariant,
|
|
|
|
threadIsBlocked: initialData?.threadIsBlocked,
|
|
|
|
threadIsNoteToSelf: (initialData?.currentUserPublicKey == threadId),
|
|
|
|
currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember,
|
|
|
|
threadIsBlocked: initialData?.threadIsBlocked,
|
|
|
|
openGroupPermissions: initialData?.openGroupPermissions
|
|
|
|
currentUserIsClosedGroupMember: initialData?.currentUserIsClosedGroupMember,
|
|
|
|
).populatingCurrentUserBlindedKeys(
|
|
|
|
openGroupPermissions: initialData?.openGroupPermissions
|
|
|
|
currentUserBlinded15PublicKeyForThisThread: initialData?.blinded15Key,
|
|
|
|
).populatingCurrentUserBlindedKeys(
|
|
|
|
currentUserBlinded25PublicKeyForThisThread: initialData?.blinded25Key
|
|
|
|
currentUserBlinded15PublicKeyForThisThread: initialData?.blinded15Key,
|
|
|
|
|
|
|
|
currentUserBlinded25PublicKeyForThisThread: initialData?.blinded25Key
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.pagedDataObserver = nil
|
|
|
|
self.pagedDataObserver = nil
|
|
|
|
|
|
|
|
|
|
|
@ -179,8 +183,10 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Thread Data
|
|
|
|
// MARK: - Thread Data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private var _threadData: Atomic<SessionThreadViewModel>
|
|
|
|
|
|
|
|
|
|
|
|
/// This value is the current state of the view
|
|
|
|
/// This value is the current state of the view
|
|
|
|
public private(set) var threadData: SessionThreadViewModel
|
|
|
|
public var threadData: SessionThreadViewModel { _threadData.wrappedValue }
|
|
|
|
|
|
|
|
|
|
|
|
/// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise
|
|
|
|
/// 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
|
|
|
|
/// performance https://github.com/groue/GRDB.swift#valueobservation-performance
|
|
|
@ -200,6 +206,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
.trackingConstantRegion { [weak self] db -> SessionThreadViewModel? in
|
|
|
|
.trackingConstantRegion { [weak self] db -> SessionThreadViewModel? in
|
|
|
|
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
|
|
|
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
|
|
|
let recentReactionEmoji: [String] = try Emoji.getRecent(db, withDefaultEmoji: true)
|
|
|
|
let recentReactionEmoji: [String] = try Emoji.getRecent(db, withDefaultEmoji: true)
|
|
|
|
|
|
|
|
let oldThreadData: SessionThreadViewModel? = self?._threadData.wrappedValue
|
|
|
|
let threadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
|
|
|
let threadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
|
|
|
.conversationQuery(threadId: threadId, userPublicKey: userPublicKey)
|
|
|
|
.conversationQuery(threadId: threadId, userPublicKey: userPublicKey)
|
|
|
|
.fetchOne(db)
|
|
|
|
.fetchOne(db)
|
|
|
@ -209,8 +216,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
.map { viewModel -> SessionThreadViewModel in
|
|
|
|
.map { viewModel -> SessionThreadViewModel in
|
|
|
|
viewModel.populatingCurrentUserBlindedKeys(
|
|
|
|
viewModel.populatingCurrentUserBlindedKeys(
|
|
|
|
db,
|
|
|
|
db,
|
|
|
|
currentUserBlinded15PublicKeyForThisThread: self?.threadData.currentUserBlinded15PublicKey,
|
|
|
|
currentUserBlinded15PublicKeyForThisThread: oldThreadData?.currentUserBlinded15PublicKey,
|
|
|
|
currentUserBlinded25PublicKeyForThisThread: self?.threadData.currentUserBlinded25PublicKey
|
|
|
|
currentUserBlinded25PublicKeyForThisThread: oldThreadData?.currentUserBlinded25PublicKey
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -219,7 +226,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func updateThreadData(_ updatedData: SessionThreadViewModel) {
|
|
|
|
public func updateThreadData(_ updatedData: SessionThreadViewModel) {
|
|
|
|
self.threadData = updatedData
|
|
|
|
self._threadData.mutate { $0 = updatedData }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Interaction Data
|
|
|
|
// MARK: - Interaction Data
|
|
|
@ -393,6 +400,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
optimisticMessages: [MessageViewModel]?,
|
|
|
|
optimisticMessages: [MessageViewModel]?,
|
|
|
|
initialUnreadInteractionId: Int64?
|
|
|
|
initialUnreadInteractionId: Int64?
|
|
|
|
) -> [SectionModel] {
|
|
|
|
) -> [SectionModel] {
|
|
|
|
|
|
|
|
let threadData: SessionThreadViewModel = self._threadData.wrappedValue
|
|
|
|
let typingIndicator: MessageViewModel? = data.first(where: { $0.isTypingIndicator == true })
|
|
|
|
let typingIndicator: MessageViewModel? = data.first(where: { $0.isTypingIndicator == true })
|
|
|
|
let sortedData: [MessageViewModel] = data
|
|
|
|
let sortedData: [MessageViewModel] = data
|
|
|
|
.filter { $0.id != MessageViewModel.optimisticUpdateId } // Remove old optimistic updates
|
|
|
|
.filter { $0.id != MessageViewModel.optimisticUpdateId } // Remove old optimistic updates
|
|
|
@ -498,6 +506,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
) -> OptimisticMessageData {
|
|
|
|
) -> OptimisticMessageData {
|
|
|
|
// Generate the optimistic data
|
|
|
|
// Generate the optimistic data
|
|
|
|
let optimisticMessageId: UUID = UUID()
|
|
|
|
let optimisticMessageId: UUID = UUID()
|
|
|
|
|
|
|
|
let threadData: SessionThreadViewModel = self._threadData.wrappedValue
|
|
|
|
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser()
|
|
|
|
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser()
|
|
|
|
let interaction: Interaction = Interaction(
|
|
|
|
let interaction: Interaction = Interaction(
|
|
|
|
threadId: threadData.threadId,
|
|
|
|
threadId: threadData.threadId,
|
|
|
@ -658,7 +667,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
// MARK: - Mentions
|
|
|
|
// MARK: - Mentions
|
|
|
|
|
|
|
|
|
|
|
|
public func mentions(for query: String = "") -> [MentionInfo] {
|
|
|
|
public func mentions(for query: String = "") -> [MentionInfo] {
|
|
|
|
let threadData: SessionThreadViewModel = self.threadData
|
|
|
|
let threadData: SessionThreadViewModel = self._threadData.wrappedValue
|
|
|
|
|
|
|
|
|
|
|
|
return Storage.shared
|
|
|
|
return Storage.shared
|
|
|
|
.read { db -> [MentionInfo] in
|
|
|
|
.read { db -> [MentionInfo] in
|
|
|
@ -733,15 +742,17 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.global(qos: .userInitiated), latest: true)
|
|
|
|
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.global(qos: .userInitiated), latest: true)
|
|
|
|
.handleEvents(
|
|
|
|
.handleEvents(
|
|
|
|
receiveOutput: { [weak self] target, timestampMs in
|
|
|
|
receiveOutput: { [weak self] target, timestampMs in
|
|
|
|
|
|
|
|
let threadData: SessionThreadViewModel? = self?._threadData.wrappedValue
|
|
|
|
|
|
|
|
|
|
|
|
switch target {
|
|
|
|
switch target {
|
|
|
|
case .thread: self?.threadData.markAsRead(target: target)
|
|
|
|
case .thread: threadData?.markAsRead(target: target)
|
|
|
|
case .threadAndInteractions(let interactionId):
|
|
|
|
case .threadAndInteractions(let interactionId):
|
|
|
|
guard
|
|
|
|
guard
|
|
|
|
timestampMs == nil ||
|
|
|
|
timestampMs == nil ||
|
|
|
|
(self?.lastInteractionTimestampMsMarkedAsRead ?? 0) < (timestampMs ?? 0) ||
|
|
|
|
(self?.lastInteractionTimestampMsMarkedAsRead ?? 0) < (timestampMs ?? 0) ||
|
|
|
|
(self?.lastInteractionIdMarkedAsRead ?? 0) < (interactionId ?? 0)
|
|
|
|
(self?.lastInteractionIdMarkedAsRead ?? 0) < (interactionId ?? 0)
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
self?.threadData.markAsRead(target: .thread)
|
|
|
|
threadData?.markAsRead(target: .thread)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -751,8 +762,8 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
self?.lastInteractionTimestampMsMarkedAsRead = timestampMs
|
|
|
|
self?.lastInteractionTimestampMsMarkedAsRead = timestampMs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self?.lastInteractionIdMarkedAsRead = (interactionId ?? self?.threadData.interactionId)
|
|
|
|
self?.lastInteractionIdMarkedAsRead = (interactionId ?? threadData?.interactionId)
|
|
|
|
self?.threadData.markAsRead(target: target)
|
|
|
|
threadData?.markAsRead(target: target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
@ -791,7 +802,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func trustContact() {
|
|
|
|
public func trustContact() {
|
|
|
|
guard self.threadData.threadVariant == .contact else { return }
|
|
|
|
guard self._threadData.wrappedValue.threadVariant == .contact else { return }
|
|
|
|
|
|
|
|
|
|
|
|
let threadId: String = self.threadId
|
|
|
|
let threadId: String = self.threadId
|
|
|
|
|
|
|
|
|
|
|
@ -822,7 +833,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func unblockContact() {
|
|
|
|
public func unblockContact() {
|
|
|
|
guard self.threadData.threadVariant == .contact else { return }
|
|
|
|
guard self._threadData.wrappedValue.threadVariant == .contact else { return }
|
|
|
|
|
|
|
|
|
|
|
|
let threadId: String = self.threadId
|
|
|
|
let threadId: String = self.threadId
|
|
|
|
|
|
|
|
|
|
|
|