319 lines
12 KiB
Swift
319 lines
12 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
extension TSThread {
|
|
public typealias RowId = Int64
|
|
|
|
public var logString: String {
|
|
return (self as? TSGroupThread)?.groupId.toHex() ?? self.uniqueId
|
|
}
|
|
|
|
// MARK: - updateWith...
|
|
|
|
public func updateWithDraft(
|
|
draftMessageBody: MessageBody?,
|
|
replyInfo: ThreadReplyInfo?,
|
|
editTargetTimestamp: UInt64?,
|
|
transaction tx: DBWriteTransaction
|
|
) {
|
|
let mostRecentInteractionID = InteractionFinder.maxInteractionRowId(transaction: tx)
|
|
|
|
anyUpdate(transaction: tx) { thread in
|
|
thread.messageDraft = draftMessageBody?.text
|
|
thread.messageDraftBodyRanges = draftMessageBody?.ranges
|
|
thread.editTargetTimestamp = editTargetTimestamp.map { NSNumber(value: $0) }
|
|
|
|
if draftMessageBody?.text.nilIfEmpty == nil {
|
|
// 0 makes these values effectively irrelevant since they will
|
|
// be compared to the lastInteractionRowId, which will always be > 0.
|
|
thread.lastDraftInteractionRowId = 0
|
|
thread.lastDraftUpdateTimestamp = 0
|
|
} else {
|
|
thread.lastDraftInteractionRowId = mostRecentInteractionID
|
|
thread.lastDraftUpdateTimestamp = Date().ows_millisecondsSince1970
|
|
}
|
|
}
|
|
|
|
if let replyInfo {
|
|
DependenciesBridge.shared.threadReplyInfoStore
|
|
.save(replyInfo, for: uniqueId, tx: tx)
|
|
} else {
|
|
DependenciesBridge.shared.threadReplyInfoStore
|
|
.remove(for: uniqueId, tx: tx)
|
|
}
|
|
}
|
|
|
|
public func updateWithMentionNotificationMode(
|
|
_ mentionNotificationMode: TSThreadMentionNotificationMode,
|
|
wasLocallyInitiated: Bool,
|
|
transaction tx: DBWriteTransaction
|
|
) {
|
|
anyUpdate(transaction: tx) { thread in
|
|
thread.mentionNotificationMode = mentionNotificationMode
|
|
}
|
|
|
|
if
|
|
wasLocallyInitiated,
|
|
let groupThread = self as? TSGroupThread,
|
|
groupThread.isGroupV2Thread
|
|
{
|
|
SSKEnvironment.shared.storageServiceManagerRef.recordPendingUpdates(
|
|
groupModel: groupThread.groupModel
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Updates `shouldThreadBeVisible`.
|
|
public func updateWithShouldThreadBeVisible(
|
|
_ shouldThreadBeVisible: Bool,
|
|
transaction tx: DBWriteTransaction
|
|
) {
|
|
anyUpdate(transaction: tx) { thread in
|
|
thread.shouldThreadBeVisible = true
|
|
}
|
|
}
|
|
|
|
public func updateWithLastSentStoryTimestamp(
|
|
_ lastSentStoryTimestamp: UInt64,
|
|
transaction tx: DBWriteTransaction
|
|
) {
|
|
anyUpdate(transaction: tx) { thread in
|
|
if lastSentStoryTimestamp > (thread.lastSentStoryTimestamp?.uint64Value ?? 0) {
|
|
thread.lastSentStoryTimestamp = NSNumber(value: lastSentStoryTimestamp)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
public func updateWithInsertedInteraction(_ interaction: TSInteraction, tx: DBWriteTransaction) {
|
|
updateWithInteraction(interaction, wasInteractionInserted: true, tx: tx)
|
|
}
|
|
|
|
public func updateWithUpdatedInteraction(_ interaction: TSInteraction, tx: DBWriteTransaction, ) {
|
|
updateWithInteraction(interaction, wasInteractionInserted: false, tx: tx)
|
|
}
|
|
|
|
private func updateWithInteraction(_ interaction: TSInteraction, wasInteractionInserted: Bool, tx: DBWriteTransaction, ) {
|
|
let db = DependenciesBridge.shared.db
|
|
|
|
let hasLastVisibleInteraction = hasLastVisibleInteraction(transaction: tx)
|
|
let needsToClearLastVisibleSortId = hasLastVisibleInteraction && wasInteractionInserted
|
|
|
|
if !interaction.shouldAppearInInbox(transaction: tx) {
|
|
// We want to clear the last visible sort ID on any new message,
|
|
// even if the message doesn't appear in the inbox view.
|
|
if needsToClearLastVisibleSortId {
|
|
clearLastVisibleInteraction(transaction: tx)
|
|
}
|
|
scheduleTouchFinalization(transaction: tx)
|
|
return
|
|
}
|
|
|
|
let interactionRowId = UInt64(interaction.sqliteRowId ?? 0)
|
|
let needsToMarkAsVisible = !shouldThreadBeVisible
|
|
let threadAssociatedData = ThreadAssociatedData.fetchOrDefault(for: self, transaction: tx)
|
|
let needsToClearArchived = shouldClearArchivedStatusWhenUpdatingWithInteraction(
|
|
interaction,
|
|
wasInteractionInserted: wasInteractionInserted,
|
|
threadAssociatedData: threadAssociatedData,
|
|
tx: tx
|
|
)
|
|
let needsToUpdateLastInteractionRowId = interactionRowId > lastInteractionRowId
|
|
let needsToClearIsMarkedUnread = threadAssociatedData.isMarkedUnread && wasInteractionInserted
|
|
|
|
if
|
|
needsToMarkAsVisible
|
|
|| needsToClearArchived
|
|
|| needsToUpdateLastInteractionRowId
|
|
|| needsToClearLastVisibleSortId
|
|
|| needsToClearIsMarkedUnread
|
|
{
|
|
anyUpdate(transaction: tx) { thread in
|
|
thread.shouldThreadBeVisible = true
|
|
thread.lastInteractionRowId = max(thread.lastInteractionRowId, interactionRowId)
|
|
}
|
|
|
|
threadAssociatedData.clear(
|
|
isArchived: needsToClearArchived,
|
|
isMarkedUnread: needsToClearIsMarkedUnread,
|
|
updateStorageService: true,
|
|
transaction: tx
|
|
)
|
|
|
|
if needsToMarkAsVisible {
|
|
// Non-visible threads don't get indexed, so if we're becoming
|
|
// visible for the first time...
|
|
db.touch(
|
|
thread: self,
|
|
shouldReindex: true,
|
|
shouldUpdateChatListUi: true,
|
|
tx: tx
|
|
)
|
|
}
|
|
|
|
if needsToClearLastVisibleSortId {
|
|
clearLastVisibleInteraction(transaction: tx)
|
|
}
|
|
} else {
|
|
scheduleTouchFinalization(transaction: tx)
|
|
}
|
|
}
|
|
|
|
private func shouldClearArchivedStatusWhenUpdatingWithInteraction(
|
|
_ interaction: TSInteraction,
|
|
wasInteractionInserted: Bool,
|
|
threadAssociatedData: ThreadAssociatedData,
|
|
tx: DBReadTransaction
|
|
) -> Bool {
|
|
var needsToClearArchived = threadAssociatedData.isArchived && wasInteractionInserted
|
|
|
|
// I'm not sure, at the time I am migrating this to Swift, if this is
|
|
// a load-bearing check of some sort. Perhaps in the future, we can
|
|
// more confidently remove this.
|
|
if
|
|
!CurrentAppContext().isRunningTests,
|
|
!AppReadinessObjcBridge.isAppReady
|
|
{
|
|
needsToClearArchived = false
|
|
}
|
|
|
|
if let infoMessage = interaction as? TSInfoMessage {
|
|
switch infoMessage.messageType {
|
|
case
|
|
.syncedThread,
|
|
.threadMerge:
|
|
needsToClearArchived = false
|
|
case
|
|
.typeLocalUserEndedSession,
|
|
.typeRemoteUserEndedSession,
|
|
.userNotRegistered,
|
|
.typeUnsupportedMessage,
|
|
.typeGroupUpdate,
|
|
.typeGroupQuit,
|
|
.typeDisappearingMessagesUpdate,
|
|
.addToContactsOffer,
|
|
.verificationStateChange,
|
|
.addUserToProfileWhitelistOffer,
|
|
.addGroupToProfileWhitelistOffer,
|
|
.unknownProtocolVersion,
|
|
.userJoinedSignal,
|
|
.profileUpdate,
|
|
.phoneNumberChange,
|
|
.recipientHidden,
|
|
.paymentsActivationRequest,
|
|
.paymentsActivated,
|
|
.sessionSwitchover,
|
|
.reportedSpam,
|
|
.learnedProfileName,
|
|
.blockedOtherUser,
|
|
.blockedGroup,
|
|
.unblockedOtherUser,
|
|
.unblockedGroup,
|
|
.acceptedMessageRequest:
|
|
break
|
|
}
|
|
}
|
|
|
|
// Shouldn't clear archived if:
|
|
// - The thread is muted.
|
|
// - The user has requested we keep muted chats archived.
|
|
// - The message was sent by someone other than the current user. (If the
|
|
// current user sent the message, we should clear archived.)
|
|
let wasMessageSentByUs = interaction is TSOutgoingMessage
|
|
if
|
|
threadAssociatedData.isMuted,
|
|
SSKPreferences.shouldKeepMutedChatsArchived(transaction: tx),
|
|
!wasMessageSentByUs
|
|
{
|
|
needsToClearArchived = false
|
|
}
|
|
|
|
return needsToClearArchived
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public func updateWithRemovedInteraction(
|
|
_ interaction: TSInteraction,
|
|
tx: DBWriteTransaction
|
|
) {
|
|
let interactionRowId = interaction.sqliteRowId ?? 0
|
|
let needsToUpdateLastInteractionRowId = interactionRowId == lastInteractionRowId
|
|
|
|
let lastVisibleSortId = lastVisibleSortId(transaction: tx) ?? 0
|
|
let needsToUpdateLastVisibleSortId = lastVisibleSortId > 0 && lastVisibleSortId == interactionRowId
|
|
|
|
updateOnInteractionsRemoved(
|
|
needsToUpdateLastInteractionRowId: needsToUpdateLastInteractionRowId,
|
|
needsToUpdateLastVisibleSortId: needsToUpdateLastVisibleSortId,
|
|
lastVisibleSortId: lastVisibleSortId,
|
|
tx: tx
|
|
)
|
|
}
|
|
|
|
public func updateOnInteractionsRemoved(
|
|
needsToUpdateLastInteractionRowId: Bool,
|
|
needsToUpdateLastVisibleSortId: Bool,
|
|
tx: DBWriteTransaction,
|
|
) {
|
|
updateOnInteractionsRemoved(
|
|
needsToUpdateLastInteractionRowId: needsToUpdateLastInteractionRowId,
|
|
needsToUpdateLastVisibleSortId: needsToUpdateLastVisibleSortId,
|
|
lastVisibleSortId: lastVisibleSortId(transaction: tx) ?? 0,
|
|
tx: tx
|
|
)
|
|
}
|
|
|
|
private func updateOnInteractionsRemoved(
|
|
needsToUpdateLastInteractionRowId: Bool,
|
|
needsToUpdateLastVisibleSortId: Bool,
|
|
lastVisibleSortId: UInt64,
|
|
tx: DBWriteTransaction,
|
|
) {
|
|
if needsToUpdateLastInteractionRowId || needsToUpdateLastVisibleSortId {
|
|
anyUpdate(transaction: tx) { thread in
|
|
if needsToUpdateLastInteractionRowId {
|
|
let lastInteraction = thread.lastInteractionForInbox(transaction: tx)
|
|
thread.lastInteractionRowId = lastInteraction?.sortId ?? 0
|
|
}
|
|
}
|
|
|
|
if needsToUpdateLastVisibleSortId {
|
|
if let interactionBeforeRemovedInteraction = firstInteraction(
|
|
atOrAroundSortId: lastVisibleSortId,
|
|
transaction: tx
|
|
) {
|
|
setLastVisibleInteraction(
|
|
sortId: interactionBeforeRemovedInteraction.sortId,
|
|
onScreenPercentage: 1.0,
|
|
transaction: tx
|
|
)
|
|
} else {
|
|
clearLastVisibleInteraction(transaction: tx)
|
|
}
|
|
}
|
|
} else {
|
|
scheduleTouchFinalization(transaction: tx)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
func scheduleTouchFinalization(transaction tx: DBWriteTransaction) {
|
|
tx.addFinalizationBlock(key: uniqueId) { tx in
|
|
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
|
|
|
|
guard let selfThread = Self.anyFetch(uniqueId: self.uniqueId, transaction: tx) else {
|
|
return
|
|
}
|
|
|
|
databaseStorage.touch(thread: selfThread, shouldReindex: false, tx: tx)
|
|
}
|
|
}
|
|
}
|