// // Copyright 2017 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // #import "TSThread.h" #import "OWSDisappearingMessagesConfiguration.h" #import "OWSReadTracking.h" #import "TSIncomingMessage.h" #import "TSInfoMessage.h" #import "TSInteraction.h" #import "TSInvalidIdentityKeyReceivingErrorMessage.h" #import "TSOutgoingMessage.h" #import @import Intents; NS_ASSUME_NONNULL_BEGIN @interface TSThread () @property (nonatomic, nullable) NSDate *creationDate; @property (nonatomic) BOOL isArchivedObsolete; @property (nonatomic) BOOL isMarkedUnreadObsolete; @property (atomic) uint64_t mutedUntilTimestampObsolete; @property (nonatomic, nullable) NSDate *mutedUntilDateObsolete; @property (nonatomic) uint64_t lastVisibleSortIdObsolete; @property (nonatomic) double lastVisibleSortIdOnScreenPercentageObsolete; @end #pragma mark - @implementation TSThread - (instancetype)initWithUniqueId:(NSString *)uniqueId { self = [super initWithUniqueId:uniqueId]; if (self) { _creationDate = [NSDate date]; _messageDraft = nil; _conversationColorNameObsolete = @"Obsolete"; } return self; } // --- CODE GENERATION MARKER // This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run // `sds_codegen.sh`. // clang-format off - (instancetype)initWithGrdbId:(int64_t)grdbId uniqueId:(NSString *)uniqueId conversationColorNameObsolete:(NSString *)conversationColorNameObsolete creationDate:(nullable NSDate *)creationDate editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp isArchivedObsolete:(BOOL)isArchivedObsolete isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp lastInteractionRowId:(uint64_t)lastInteractionRowId lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode messageDraft:(nullable NSString *)messageDraft messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete shouldThreadBeVisible:(BOOL)shouldThreadBeVisible storyViewMode:(TSThreadStoryViewMode)storyViewMode { self = [super initWithGrdbId:grdbId uniqueId:uniqueId]; if (!self) { return self; } _conversationColorNameObsolete = conversationColorNameObsolete; _creationDate = creationDate; _editTargetTimestamp = editTargetTimestamp; _isArchivedObsolete = isArchivedObsolete; _isMarkedUnreadObsolete = isMarkedUnreadObsolete; _lastDraftInteractionRowId = lastDraftInteractionRowId; _lastDraftUpdateTimestamp = lastDraftUpdateTimestamp; _lastInteractionRowId = lastInteractionRowId; _lastSentStoryTimestamp = lastSentStoryTimestamp; _lastVisibleSortIdObsolete = lastVisibleSortIdObsolete; _lastVisibleSortIdOnScreenPercentageObsolete = lastVisibleSortIdOnScreenPercentageObsolete; _mentionNotificationMode = mentionNotificationMode; _messageDraft = messageDraft; _messageDraftBodyRanges = messageDraftBodyRanges; _mutedUntilDateObsolete = mutedUntilDateObsolete; _mutedUntilTimestampObsolete = mutedUntilTimestampObsolete; _shouldThreadBeVisible = shouldThreadBeVisible; _storyViewMode = storyViewMode; return self; } // clang-format on // --- CODE GENERATION MARKER - (nullable instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (!self) { return self; } // renamed `hasEverHadMessage` -> `shouldThreadBeVisible` if (!_shouldThreadBeVisible) { NSNumber *_Nullable legacy_hasEverHadMessage = [coder decodeObjectForKey:@"hasEverHadMessage"]; if (legacy_hasEverHadMessage != nil) { _shouldThreadBeVisible = legacy_hasEverHadMessage.boolValue; } } if (_conversationColorNameObsolete.length == 0) { _conversationColorNameObsolete = @"Obsolete"; } NSDate *_Nullable lastMessageDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastMessageDate"]; NSDate *_Nullable archivalDate = [coder decodeObjectOfClass:NSDate.class forKey:@"archivalDate"]; _isArchivedByLegacyTimestampForSorting = [self.class legacyIsArchivedWithLastMessageDate:lastMessageDate archivalDate:archivalDate]; if ([coder decodeObjectForKey:@"archivedAsOfMessageSortId"] != nil) { OWSAssertDebug(!_isArchivedObsolete); _isArchivedObsolete = YES; } return self; } - (void)anyDidInsertWithTransaction:(DBWriteTransaction *)transaction { [super anyDidInsertWithTransaction:transaction]; [ThreadAssociatedData createFor:self.uniqueId transaction:transaction]; if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) { [SSKPreferences setHasSavedThread:YES transaction:transaction]; } [self _anyDidInsertWithTx:transaction]; [SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction]; } - (void)anyDidUpdateWithTransaction:(DBWriteTransaction *)transaction { [super anyDidUpdateWithTransaction:transaction]; if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) { [SSKPreferences setHasSavedThread:YES transaction:transaction]; } [SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction]; [PinnedThreadManagerObjcBridge handleUpdatedThread:self transaction:transaction]; } - (BOOL)isNoteToSelf { return NO; } - (NSString *)colorSeed { return self.uniqueId; } #pragma mark - To be subclassed. - (NSArray *)recipientAddressesWithSneakyTransaction { __block NSArray *recipientAddresses; [SSKEnvironment.shared.databaseStorageRef readWithBlock:^( DBReadTransaction *transaction) { recipientAddresses = [self recipientAddressesWithTransaction:transaction]; }]; return recipientAddresses; } - (NSArray *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction { OWSAbstractMethod(); return @[]; } - (BOOL)hasSafetyNumbers { return NO; } #pragma mark - Interactions - (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(DBReadTransaction *)transaction { OWSAssertDebug(transaction); return [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId] mostRecentInteractionForInboxWithTransaction:transaction]; } - (nullable TSInteraction *)firstInteractionAtOrAroundSortId:(uint64_t)sortId transaction:(DBReadTransaction *)transaction { OWSAssertDebug(transaction); return [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId] firstInteractionAtOrAroundSortId:sortId transaction:transaction]; } #pragma mark - Archival + (BOOL)legacyIsArchivedWithLastMessageDate:(nullable NSDate *)lastMessageDate archivalDate:(nullable NSDate *)archivalDate { if (!archivalDate) { return NO; } if (!lastMessageDate) { return YES; } return [archivalDate compare:lastMessageDate] != NSOrderedAscending; } #pragma mark - Merging - (void)mergeFrom:(TSThread *)otherThread { self.shouldThreadBeVisible = self.shouldThreadBeVisible || otherThread.shouldThreadBeVisible; self.lastInteractionRowId = MAX(self.lastInteractionRowId, otherThread.lastInteractionRowId); // Copy the draft if this thread doesn't have one. We always assign both // values if we assign one of them since they're related. if (self.messageDraft == nil) { self.messageDraft = otherThread.messageDraft; self.messageDraftBodyRanges = otherThread.messageDraftBodyRanges; self.lastDraftInteractionRowId = otherThread.lastDraftInteractionRowId; self.lastDraftUpdateTimestamp = otherThread.lastDraftUpdateTimestamp; } } @end NS_ASSUME_NONNULL_END