diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index c38553cdf..39224f74c 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -177,7 +177,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; // we need to bump this constant. // // We added a number of database views in v2.13.0. - NSString *kLastVersionWithDatabaseViewChange = @"2.16.0"; + NSString *kLastVersionWithDatabaseViewChange = @"2.13.0"; BOOL mayNeedUpgrade = ([TSAccountManager isRegistered] && lastLaunchedAppVersion && (!lastCompletedLaunchAppVersion || [VersionMigrations isVersion:lastCompletedLaunchAppVersion diff --git a/Signal/src/Models/OWSContactOffersInteraction.h b/Signal/src/Models/OWSContactOffersInteraction.h index c8c3ba586..9a4d43ffa 100644 --- a/Signal/src/Models/OWSContactOffersInteraction.h +++ b/Signal/src/Models/OWSContactOffersInteraction.h @@ -8,17 +8,19 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSContactOffersInteraction : TSInteraction -//@property (atomic, readonly) BOOL hasMoreUnseenMessages; -// -//@property (atomic, readonly) NSUInteger missingUnseenSafetyNumberChangeCount; +@property (nonatomic, readonly) BOOL hasBlockOffer; +@property (nonatomic, readonly) BOOL hasAddToContactsOffer; +@property (nonatomic, readonly) BOOL hasAddToProfileWhitelistOffer; +@property (nonatomic, readonly) NSString *contactId; - (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread - // hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages - // missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount - NS_DESIGNATED_INITIALIZER; + hasBlockOffer:(BOOL)hasBlockOffer + hasAddToContactsOffer:(BOOL)hasAddToContactsOffer + hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer + contactId:(NSString *)contactId NS_DESIGNATED_INITIALIZER; @end diff --git a/Signal/src/Models/OWSContactOffersInteraction.m b/Signal/src/Models/OWSContactOffersInteraction.m index 5929c4283..9b8fba55f 100644 --- a/Signal/src/Models/OWSContactOffersInteraction.m +++ b/Signal/src/Models/OWSContactOffersInteraction.m @@ -6,14 +6,6 @@ NS_ASSUME_NONNULL_BEGIN -@interface OWSContactOffersInteraction () - -//@property (atomic) BOOL hasMoreUnseenMessages; - -@end - -#pragma mark - - @implementation OWSContactOffersInteraction - (instancetype)initWithCoder:(NSCoder *)coder @@ -21,9 +13,12 @@ NS_ASSUME_NONNULL_BEGIN return [super initWithCoder:coder]; } -- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread -// hasMoreUnseenMessages:(BOOL)hasMoreUnseenMessages -// missingUnseenSafetyNumberChangeCount:(NSUInteger)missingUnseenSafetyNumberChangeCount +- (instancetype)initWithTimestamp:(uint64_t)timestamp + thread:(TSThread *)thread + hasBlockOffer:(BOOL)hasBlockOffer + hasAddToContactsOffer:(BOOL)hasAddToContactsOffer + hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer + contactId:(NSString *)contactId { self = [super initWithTimestamp:timestamp inThread:thread]; @@ -31,8 +26,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } - // _hasMoreUnseenMessages = hasMoreUnseenMessages; - // _missingUnseenSafetyNumberChangeCount = missingUnseenSafetyNumberChangeCount; + _hasBlockOffer = hasBlockOffer; + _hasAddToContactsOffer = hasAddToContactsOffer; + _hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer; + OWSAssert(contactId.length > 0); + _contactId = contactId; return self; } diff --git a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m index 673170490..9c9dc3056 100644 --- a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m +++ b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m @@ -158,8 +158,8 @@ NS_ASSUME_NONNULL_BEGIN return [cachedSize CGSizeValue]; } - CGSize result = [self.referenceContactOffersCell bubbleSizeForInteraction:interaction - collectionViewWidth:layout.collectionView.width]; + CGSize result = [self.referenceUnreadIndicatorCell bubbleSizeForInteraction:interaction + collectionViewWidth:layout.collectionView.width]; [self.cache setObject:[NSValue valueWithCGSize:result] forKey:cacheKey]; @@ -178,8 +178,8 @@ NS_ASSUME_NONNULL_BEGIN return [cachedSize CGSizeValue]; } - CGSize result = [self.referenceUnreadIndicatorCell bubbleSizeForInteraction:interaction - collectionViewWidth:layout.collectionView.width]; + CGSize result = [self.referenceContactOffersCell bubbleSizeForInteraction:interaction + collectionViewWidth:layout.collectionView.width]; [self.cache setObject:[NSValue valueWithCGSize:result] forKey:cacheKey]; diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index e157da206..d97b4999e 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -153,10 +153,9 @@ NS_ASSUME_NONNULL_BEGIN const int kMaxBlockOfferOutgoingMessageCount = 10; // Find any "dynamic" interactions and safety number changes. - __block OWSAddToContactsOfferMessage *existingAddToContactsOffer = nil; - __block OWSAddToProfileWhitelistOfferMessage *existingOWSAddToProfileWhitelistOffer = nil; - __block OWSUnknownContactBlockOfferMessage *existingBlockOffer = nil; + NSMutableArray *interactionsToDelete = [NSMutableArray new]; __block TSUnreadIndicatorInteraction *existingUnreadIndicator = nil; + __block OWSContactOffersInteraction *existingContactOffers = nil; NSMutableArray *blockingSafetyNumberChanges = [NSMutableArray new]; NSMutableArray *nonBlockingSafetyNumberChanges = [NSMutableArray new]; // We use different views for performance reasons. @@ -166,17 +165,27 @@ NS_ASSUME_NONNULL_BEGIN NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) { if ([object isKindOfClass:[OWSUnknownContactBlockOfferMessage class]]) { - OWSAssert(!existingBlockOffer); - existingBlockOffer = (OWSUnknownContactBlockOfferMessage *)object; + [interactionsToDelete addObject:object]; } else if ([object isKindOfClass:[OWSAddToContactsOfferMessage class]]) { - OWSAssert(!existingAddToContactsOffer); - existingAddToContactsOffer = (OWSAddToContactsOfferMessage *)object; + [interactionsToDelete addObject:object]; } else if ([object isKindOfClass:[OWSAddToProfileWhitelistOfferMessage class]]) { - OWSAssert(!existingOWSAddToProfileWhitelistOffer); - existingOWSAddToProfileWhitelistOffer = (OWSAddToProfileWhitelistOfferMessage *)object; + [interactionsToDelete addObject:object]; } else if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) { OWSAssert(!existingUnreadIndicator); + if (existingUnreadIndicator) { + // There should never be more than one unread indicator in + // a given thread, but if there is, discard all but one. + [interactionsToDelete addObject:existingUnreadIndicator]; + } existingUnreadIndicator = (TSUnreadIndicatorInteraction *)object; + } else if ([object isKindOfClass:[OWSContactOffersInteraction class]]) { + OWSAssert(!existingContactOffers); + if (existingContactOffers) { + // There should never be more than one "contact offers" in + // a given thread, but if there is, discard all but one. + [interactionsToDelete addObject:existingContactOffers]; + } + existingContactOffers = (OWSContactOffersInteraction *)object; } else if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) { [blockingSafetyNumberChanges addObject:object]; } else if ([object isKindOfClass:[TSErrorMessage class]]) { @@ -189,6 +198,11 @@ NS_ASSUME_NONNULL_BEGIN } }]; + for (TSInteraction *interaction in interactionsToDelete) { + DDLogDebug(@"Cleaning up interaction: %@", [interaction class]); + [interaction removeWithTransaction:transaction]; + } + // Determine if there are "unread" messages in this conversation. // If we've been passed a firstUnseenInteractionTimestampParameter, // just use that value in order to preserve continuity of the @@ -326,6 +340,8 @@ NS_ASSUME_NONNULL_BEGIN shouldHaveAddToContactsOffer = NO; // Only create block offers in 1:1 conversations. shouldHaveBlockOffer = NO; + // Only create profile whitelist offers in 1:1 conversations. + shouldHaveAddToProfileWhitelistOffer = NO; } else { NSString *recipientId = ((TSContactThread *)thread).contactIdentifier; @@ -352,6 +368,8 @@ NS_ASSUME_NONNULL_BEGIN shouldHaveAddToContactsOffer = NO; // Only create block offers for non-contacts. shouldHaveBlockOffer = NO; + // Don't create profile whitelist offers for non-contacts. + shouldHaveAddToProfileWhitelistOffer = NO; } } } @@ -359,6 +377,7 @@ NS_ASSUME_NONNULL_BEGIN if (!firstMessage) { shouldHaveAddToContactsOffer = NO; shouldHaveBlockOffer = NO; + shouldHaveAddToProfileWhitelistOffer = NO; } if (outgoingMessageCount > kMaxBlockOfferOutgoingMessageCount) { @@ -393,11 +412,12 @@ NS_ASSUME_NONNULL_BEGIN } } + BOOL shouldHaveContactOffers + = (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer); + // We use these offset to control the ordering of the offers and indicators. - const int kAddToProfileWhitelistOfferOffset = -4; - const int kBlockOfferOffset = -3; - const int kAddToContactsOfferOffset = -2; - const int kUnreadIndicatorOfferOffset = -1; + const int kUnreadIndicatorOffset = -2; + const int kContactOffersOffset = -1; // We want the offers to be the first interactions in their // conversation's timeline, so we back-date them to slightly before @@ -406,74 +426,48 @@ NS_ASSUME_NONNULL_BEGIN long long startOfConversationTimestamp = (long long)(firstMessage ? firstMessage.timestampForSorting : 1000); - if (existingBlockOffer && !shouldHaveBlockOffer) { - DDLogInfo(@"%@ Removing block offer: %@ (%llu)", - self.tag, - existingBlockOffer.uniqueId, - existingBlockOffer.timestampForSorting); - [existingBlockOffer removeWithTransaction:transaction]; - } else if (!existingBlockOffer && shouldHaveBlockOffer) { - DDLogInfo(@"Creating block offer for unknown contact"); - - uint64_t blockOfferTimestamp = (uint64_t)(startOfConversationTimestamp + kBlockOfferOffset); - NSString *recipientId = ((TSContactThread *)thread).contactIdentifier; - - TSMessage *offerMessage = - [OWSUnknownContactBlockOfferMessage unknownContactBlockOfferMessage:blockOfferTimestamp - thread:thread - contactId:recipientId]; - [offerMessage saveWithTransaction:transaction]; + uint64_t contactOffersTimestamp = (uint64_t)(startOfConversationTimestamp + kContactOffersOffset); - DDLogInfo(@"%@ Creating block offer: %@ (%llu)", - self.tag, - offerMessage.uniqueId, - offerMessage.timestampForSorting); + // If the contact offers' properties have changed, discard the current + // one and create a new one. + if (existingContactOffers) { + if (existingContactOffers.hasBlockOffer != shouldHaveBlockOffer + || existingContactOffers.hasAddToContactsOffer != shouldHaveAddToContactsOffer + || existingContactOffers.hasAddToProfileWhitelistOffer != shouldHaveAddToProfileWhitelistOffer) { + DDLogInfo(@"%@ Removing stale contact offers: %@ (%llu)", + self.tag, + existingContactOffers.uniqueId, + existingContactOffers.timestampForSorting); + // Preserve the timestamp of the existing "contact offers" so that + // we replace it in the same position in the timeline. + contactOffersTimestamp = existingContactOffers.timestamp; + [existingContactOffers removeWithTransaction:transaction]; + existingContactOffers = nil; + } } - if (existingAddToContactsOffer && !shouldHaveAddToContactsOffer) { - DDLogInfo(@"%@ Removing 'add to contacts' offer: %@ (%llu)", + if (existingContactOffers && !shouldHaveContactOffers) { + DDLogInfo(@"%@ Removing contact offers: %@ (%llu)", self.tag, - existingAddToContactsOffer.uniqueId, - existingAddToContactsOffer.timestampForSorting); - [existingAddToContactsOffer removeWithTransaction:transaction]; - } else if (!existingAddToContactsOffer && shouldHaveAddToContactsOffer) { - - DDLogInfo(@"%@ Creating 'add to contacts' offer for unknown contact", self.tag); - - uint64_t offerTimestamp = (uint64_t)(startOfConversationTimestamp + kAddToContactsOfferOffset); + existingContactOffers.uniqueId, + existingContactOffers.timestampForSorting); + [existingContactOffers removeWithTransaction:transaction]; + } else if (!existingContactOffers && shouldHaveContactOffers) { NSString *recipientId = ((TSContactThread *)thread).contactIdentifier; - TSMessage *offerMessage = [OWSAddToContactsOfferMessage addToContactsOfferMessage:offerTimestamp - thread:thread - contactId:recipientId]; - [offerMessage saveWithTransaction:transaction]; - - DDLogInfo(@"%@ Creating 'add to contacts' offer: %@ (%llu)", - self.tag, - offerMessage.uniqueId, - offerMessage.timestampForSorting); - } - - if (existingOWSAddToProfileWhitelistOffer && !shouldHaveAddToProfileWhitelistOffer) { - DDLogInfo(@"%@ Removing 'add to profile whitelist' offer: %@ (%llu)", - self.tag, - existingOWSAddToProfileWhitelistOffer.uniqueId, - existingOWSAddToProfileWhitelistOffer.timestampForSorting); - [existingOWSAddToProfileWhitelistOffer removeWithTransaction:transaction]; - } else if (!existingOWSAddToProfileWhitelistOffer && shouldHaveAddToProfileWhitelistOffer) { - - DDLogInfo(@"%@ Creating 'add to profile whitelist' offer", self.tag); - - uint64_t offerTimestamp = (uint64_t)(startOfConversationTimestamp + kAddToProfileWhitelistOfferOffset); - - TSMessage *offerMessage = - [OWSAddToProfileWhitelistOfferMessage addToProfileWhitelistOfferMessage:offerTimestamp thread:thread]; - [offerMessage saveWithTransaction:transaction]; + TSInteraction *offersMessage = + [[OWSContactOffersInteraction alloc] initWithTimestamp:contactOffersTimestamp + thread:thread + hasBlockOffer:shouldHaveBlockOffer + hasAddToContactsOffer:shouldHaveAddToContactsOffer + hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer + contactId:recipientId]; + [offersMessage saveWithTransaction:transaction]; - DDLogInfo(@"%@ Creating 'add to profile whitelist' offer: %@ (%llu)", + DDLogInfo(@"%@ Creating contact offers: %@ (%llu)", self.tag, - offerMessage.uniqueId, - offerMessage.timestampForSorting); + offersMessage.uniqueId, + offersMessage.timestampForSorting); } BOOL shouldHaveUnreadIndicator @@ -490,8 +484,8 @@ NS_ASSUME_NONNULL_BEGIN // message in the conversation timeline... // // ...unless we have a fixed timestamp for the unread indicator. - uint64_t indicatorTimestamp = (uint64_t)( - (long long)interactionAfterUnreadIndicator.timestampForSorting + kUnreadIndicatorOfferOffset); + uint64_t indicatorTimestamp + = (uint64_t)((long long)interactionAfterUnreadIndicator.timestampForSorting + kUnreadIndicatorOffset); if (indicatorTimestamp && existingUnreadIndicator.timestampForSorting == indicatorTimestamp) { // Keep the existing indicator; it is in the correct position. diff --git a/Signal/src/views/OWSContactOffersCell.m b/Signal/src/views/OWSContactOffersCell.m index b66bdf7c4..6f9d0f856 100644 --- a/Signal/src/views/OWSContactOffersCell.m +++ b/Signal/src/views/OWSContactOffersCell.m @@ -115,20 +115,24 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)subtitleForInteraction:(OWSContactOffersInteraction *)interaction { - if (!interaction.hasMoreUnseenMessages) { - return nil; - } - NSString *subtitleFormat = (interaction.missingUnseenSafetyNumberChangeCount > 0 - ? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_FORMAT", - @"Messages that indicates that there are more unseen messages that be revealed by tapping the 'load " - @"earlier messages' button. Embeds {{the name of the 'load earlier messages' button}}") - : NSLocalizedString( - @"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES_FORMAT", - @"Messages that indicates that there are more unseen messages including safety number changes that " - @"be revealed by tapping the 'load earlier messages' button. Embeds {{the name of the 'load earlier " - @"messages' button}}.")); - NSString *loadMoreButtonName = [NSBundle jsq_localizedStringForKey:@"load_earlier_messages"]; - return [NSString stringWithFormat:subtitleFormat, loadMoreButtonName]; + return nil; + // if (!interaction.hasMoreUnseenMessages) { + // return nil; + // } + // NSString *subtitleFormat = (interaction.missingUnseenSafetyNumberChangeCount > 0 + // ? NSLocalizedString(@"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_FORMAT", + // @"Messages that indicates that there are more unseen messages that be revealed by tapping the + // 'load " + // @"earlier messages' button. Embeds {{the name of the 'load earlier messages' button}}") + // : NSLocalizedString( + // @"MESSAGES_VIEW_UNREAD_INDICATOR_HAS_MORE_UNSEEN_MESSAGES_AND_SAFETY_NUMBER_CHANGES_FORMAT", + // @"Messages that indicates that there are more unseen messages including safety number changes + // that " + // @"be revealed by tapping the 'load earlier messages' button. Embeds {{the name of the 'load + // earlier " + // @"messages' button}}.")); + // NSString *loadMoreButtonName = [NSBundle jsq_localizedStringForKey:@"load_earlier_messages"]; + // return [NSString stringWithFormat:subtitleFormat, loadMoreButtonName]; } - (CGFloat)subtitleHMargin diff --git a/SignalServiceKit/src/Storage/TSDatabaseView.m b/SignalServiceKit/src/Storage/TSDatabaseView.m index 5add51cf4..35ed412d5 100644 --- a/SignalServiceKit/src/Storage/TSDatabaseView.m +++ b/SignalServiceKit/src/Storage/TSDatabaseView.m @@ -144,7 +144,7 @@ NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevic [self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName viewGrouping:viewGrouping - version:@"2" + version:@"1" async:YES]; }