Signal-iOS/SignalServiceKit/Contacts/TSThread.m
2026-01-28 13:17:42 -06:00

305 lines
11 KiB
Objective-C

//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "TSThread.h"
#import "OWSReadTracking.h"
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInteraction.h"
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "TSOutgoingMessage.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@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
- (NSUInteger)hash
{
NSUInteger result = [super hash];
result ^= self.conversationColorNameObsolete.hash;
result ^= self.creationDate.hash;
result ^= self.editTargetTimestamp.hash;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
result ^= self.isArchivedByLegacyTimestampForSorting;
#pragma clang diagnostic pop
result ^= self.isArchivedObsolete;
result ^= self.isMarkedUnreadObsolete;
result ^= self.lastDraftInteractionRowId;
result ^= self.lastDraftUpdateTimestamp;
result ^= self.lastInteractionRowId;
result ^= self.lastSentStoryTimestamp.hash;
result ^= self.lastVisibleSortIdObsolete;
result ^= @(self.lastVisibleSortIdOnScreenPercentageObsolete).stringValue.hash;
result ^= self.mentionNotificationMode;
result ^= self.messageDraft.hash;
result ^= self.messageDraftBodyRanges.hash;
result ^= self.mutedUntilDateObsolete.hash;
result ^= self.mutedUntilTimestampObsolete;
result ^= self.shouldThreadBeVisible;
result ^= self.storyViewMode;
return result;
}
- (BOOL)isEqual:(id)other
{
if (![super isEqual:other]) {
return NO;
}
TSThread *typedOther = (TSThread *)other;
if (![NSObject isObject:self.conversationColorNameObsolete
equalToObject:typedOther.conversationColorNameObsolete]) {
return NO;
}
if (![NSObject isObject:self.creationDate equalToObject:typedOther.creationDate]) {
return NO;
}
if (![NSObject isObject:self.editTargetTimestamp equalToObject:typedOther.editTargetTimestamp]) {
return NO;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.isArchivedByLegacyTimestampForSorting != typedOther.isArchivedByLegacyTimestampForSorting) {
return NO;
}
#pragma clang diagnostic pop
if (self.isArchivedObsolete != typedOther.isArchivedObsolete) {
return NO;
}
if (self.isMarkedUnreadObsolete != typedOther.isMarkedUnreadObsolete) {
return NO;
}
if (self.lastDraftInteractionRowId != typedOther.lastDraftInteractionRowId) {
return NO;
}
if (self.lastDraftUpdateTimestamp != typedOther.lastDraftUpdateTimestamp) {
return NO;
}
if (self.lastInteractionRowId != typedOther.lastInteractionRowId) {
return NO;
}
if (![NSObject isObject:self.lastSentStoryTimestamp equalToObject:typedOther.lastSentStoryTimestamp]) {
return NO;
}
if (self.lastVisibleSortIdObsolete != typedOther.lastVisibleSortIdObsolete) {
return NO;
}
if (self.lastVisibleSortIdOnScreenPercentageObsolete != typedOther.lastVisibleSortIdOnScreenPercentageObsolete) {
return NO;
}
if (self.mentionNotificationMode != typedOther.mentionNotificationMode) {
return NO;
}
if (![NSObject isObject:self.messageDraft equalToObject:typedOther.messageDraft]) {
return NO;
}
if (![NSObject isObject:self.messageDraftBodyRanges equalToObject:typedOther.messageDraftBodyRanges]) {
return NO;
}
if (![NSObject isObject:self.mutedUntilDateObsolete equalToObject:typedOther.mutedUntilDateObsolete]) {
return NO;
}
if (self.mutedUntilTimestampObsolete != typedOther.mutedUntilTimestampObsolete) {
return NO;
}
if (self.shouldThreadBeVisible != typedOther.shouldThreadBeVisible) {
return NO;
}
if (self.storyViewMode != typedOther.storyViewMode) {
return NO;
}
return YES;
}
- (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;
}
#pragma mark - To be subclassed.
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithSneakyTransaction
{
__block NSArray<SignalServiceAddress *> *recipientAddresses;
[SSKEnvironment.shared.databaseStorageRef readWithBlock:^(
DBReadTransaction *transaction) { recipientAddresses = [self recipientAddressesWithTransaction:transaction]; }];
return recipientAddresses;
}
- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction
{
OWSAbstractMethod();
return @[];
}
- (BOOL)hasSafetyNumbers
{
return NO;
}
#pragma mark - Interactions
- (nullable TSInteraction *)lastInteractionForInboxForChatListSorting:(BOOL)isForSorting
transaction:(DBReadTransaction *)transaction
{
OWSAssertDebug(transaction);
return [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId]
mostRecentInteractionForInboxForChatListSorting:isForSorting
transaction:transaction];
}
- (nullable TSInteraction *)firstInteractionAtOrAroundSortId:(uint64_t)sortId
transaction:(DBReadTransaction *)transaction
{
OWSAssertDebug(transaction);
return
[[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId] firstInteractionAtOrAroundSortId:sortId
transaction:transaction];
}
#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