mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Move contact offers to Conversation view model.
This commit is contained in:
parent
98210e92d8
commit
fea40d571c
5 changed files with 306 additions and 226 deletions
|
@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic) UIButton *blockButton;
|
||||
@property (nonatomic) NSArray<NSLayoutConstraint *> *layoutConstraints;
|
||||
@property (nonatomic) UIStackView *stackView;
|
||||
@property (nonatomic) UIStackView *buttonStackView;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -65,13 +66,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@"Message shown in conversation view that offers to block an unknown user.")
|
||||
selector:@selector(block)];
|
||||
|
||||
UIStackView *buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.addToContactsButton,
|
||||
self.addToProfileWhitelistButton,
|
||||
self.blockButton,
|
||||
]];
|
||||
UIStackView *buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:self.buttons];
|
||||
buttonStackView.axis = UILayoutConstraintAxisVertical;
|
||||
buttonStackView.spacing = self.vSpacing;
|
||||
self.buttonStackView = buttonStackView;
|
||||
|
||||
self.stackView = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
self.titleLabel,
|
||||
|
@ -121,11 +119,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[self configureFonts];
|
||||
|
||||
self.titleLabel.textColor = Theme.secondaryColor;
|
||||
for (UIButton *button in @[
|
||||
self.addToContactsButton,
|
||||
self.addToProfileWhitelistButton,
|
||||
self.blockButton,
|
||||
]) {
|
||||
for (UIButton *button in self.buttons) {
|
||||
[button setTitleColor:[UIColor ows_signalBlueColor] forState:UIControlStateNormal];
|
||||
[button setBackgroundColor:Theme.conversationButtonBackgroundColor];
|
||||
}
|
||||
|
@ -152,6 +146,35 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[self.stackView autoPinEdgeToSuperviewEdge:ALEdgeTrailing
|
||||
withInset:self.conversationStyle.fullWidthGutterTrailing],
|
||||
];
|
||||
|
||||
// This hack fixes a bug that I don't understand.
|
||||
//
|
||||
// On an iPhone 5C running iOS 10.3.3,
|
||||
//
|
||||
// * Alice is a contact for which we should show some but not all contact offer buttons.
|
||||
// * Delete thread with Alice.
|
||||
// * Send yourself a message from Alice.
|
||||
// * Open conversation with Alice.
|
||||
//
|
||||
// Expected: Some (but not all) offer buttons are displayed.
|
||||
// Observed: All offer buttons are displayed, in a cramped layout.
|
||||
for (UIButton *button in self.buttons) {
|
||||
[button removeFromSuperview];
|
||||
}
|
||||
for (UIButton *button in self.buttons) {
|
||||
if (!button.hidden) {
|
||||
[self.buttonStackView addArrangedSubview:button];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<UIButton *> *)buttons
|
||||
{
|
||||
return @[
|
||||
self.addToContactsButton,
|
||||
self.addToProfileWhitelistButton,
|
||||
self.blockButton,
|
||||
];
|
||||
}
|
||||
|
||||
- (CGFloat)topVMargin
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#import "OWSQuotedReplyModel.h"
|
||||
#import "Signal-Swift.h"
|
||||
#import <SignalCoreKit/NSDate+OWS.h>
|
||||
#import <SignalMessaging/OWSContactOffersInteraction.h>
|
||||
#import <SignalMessaging/OWSContactsManager.h>
|
||||
#import <SignalMessaging/OWSUnreadIndicator.h>
|
||||
#import <SignalMessaging/ThreadUtil.h>
|
||||
|
@ -206,7 +207,19 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
return SSKEnvironment.shared.typingIndicators;
|
||||
}
|
||||
|
||||
#pragma mark
|
||||
- (TSAccountManager *)tsAccountManager
|
||||
{
|
||||
OWSAssertDebug(SSKEnvironment.shared.tsAccountManager);
|
||||
|
||||
return SSKEnvironment.shared.tsAccountManager;
|
||||
}
|
||||
|
||||
- (OWSProfileManager *)profileManager
|
||||
{
|
||||
return [OWSProfileManager sharedManager];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)addNotificationListeners
|
||||
{
|
||||
|
@ -222,6 +235,14 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
selector:@selector(typingIndicatorStateDidChange:)
|
||||
name:[OWSTypingIndicatorsImpl typingIndicatorStateDidChange]
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(profileWhitelistDidChange:)
|
||||
name:kNSNotificationName_ProfileWhitelistDidChange
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(blockListDidChange:)
|
||||
name:kNSNotificationName_BlockListDidChange
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)signalAccountsDidChange:(NSNotification *)notification
|
||||
|
@ -231,6 +252,30 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
[self ensureDynamicInteractions];
|
||||
}
|
||||
|
||||
- (void)profileWhitelistDidChange:(NSNotification *)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
// If profile whitelist just changed, we may want to hide a profile whitelist offer.
|
||||
NSString *_Nullable recipientId = notification.userInfo[kNSNotificationKey_ProfileRecipientId];
|
||||
NSData *_Nullable groupId = notification.userInfo[kNSNotificationKey_ProfileGroupId];
|
||||
if (recipientId.length > 0 && [self.thread.recipientIdentifiers containsObject:recipientId]) {
|
||||
[self updateForTransientItems];
|
||||
} else if (groupId.length > 0 && self.thread.isGroupThread) {
|
||||
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
|
||||
if ([groupThread.groupModel.groupId isEqualToData:groupId]) {
|
||||
[self updateForTransientItems];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)blockListDidChange:(id)notification
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[self updateForTransientItems];
|
||||
}
|
||||
|
||||
- (void)configure
|
||||
{
|
||||
OWSLogInfo(@"");
|
||||
|
@ -868,6 +913,165 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
|
||||
#pragma mark - View Items
|
||||
|
||||
- (nullable OWSContactOffersInteraction *)tryToBuildContactOffersInteraction
|
||||
{
|
||||
|
||||
// Many OWSProfileManager methods aren't safe to call from inside a database
|
||||
// transaction, so do this work now.
|
||||
//
|
||||
// TODO: It'd be nice if these methods took a transaction.
|
||||
BOOL hasLocalProfile = [self.profileManager hasLocalProfile];
|
||||
BOOL isThreadInProfileWhitelist = [self.profileManager isThreadInProfileWhitelist:self.thread];
|
||||
BOOL hasUnwhitelistedMember = NO;
|
||||
for (NSString *recipientId in self.thread.recipientIdentifiers) {
|
||||
if (![self.profileManager isUserInProfileWhitelist:recipientId]) {
|
||||
hasUnwhitelistedMember = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__block OWSContactOffersInteraction *_Nullable offers = nil;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
offers = [self tryToBuildContactOffersInteractionWithTransaction:transaction
|
||||
hasLocalProfile:hasLocalProfile
|
||||
isThreadInProfileWhitelist:isThreadInProfileWhitelist
|
||||
hasUnwhitelistedMember:hasUnwhitelistedMember];
|
||||
}];
|
||||
return offers;
|
||||
}
|
||||
|
||||
- (nullable OWSContactOffersInteraction *)
|
||||
tryToBuildContactOffersInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
hasLocalProfile:(BOOL)hasLocalProfile
|
||||
isThreadInProfileWhitelist:(BOOL)isThreadInProfileWhitelist
|
||||
hasUnwhitelistedMember:(BOOL)hasUnwhitelistedMember
|
||||
{
|
||||
OWSAssertDebug(transaction);
|
||||
|
||||
TSThread *thread = self.thread;
|
||||
BOOL isContactThread = [thread isKindOfClass:[TSContactThread class]];
|
||||
if (!isContactThread) {
|
||||
return nil;
|
||||
}
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
if (contactThread.hasDismissedOffers) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *localNumber = [self.tsAccountManager localNumber];
|
||||
OWSAssertDebug(localNumber.length > 0);
|
||||
|
||||
const int kMaxBlockOfferOutgoingMessageCount = 10;
|
||||
|
||||
__block TSInteraction *firstCallOrMessage = nil;
|
||||
[[transaction ext:TSMessageDatabaseViewExtensionName]
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
usingBlock:^(
|
||||
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
|
||||
OWSAssertDebug([object isKindOfClass:[TSInteraction class]]);
|
||||
|
||||
if ([object isKindOfClass:[TSIncomingMessage class]] ||
|
||||
[object isKindOfClass:[TSOutgoingMessage class]] || [object isKindOfClass:[TSCall class]]) {
|
||||
firstCallOrMessage = object;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
if (!firstCallOrMessage) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSUInteger outgoingMessageCount =
|
||||
[[TSDatabaseView threadOutgoingMessageDatabaseView:transaction] numberOfItemsInGroup:thread.uniqueId];
|
||||
|
||||
BOOL shouldHaveBlockOffer = YES;
|
||||
BOOL shouldHaveAddToContactsOffer = YES;
|
||||
BOOL shouldHaveAddToProfileWhitelistOffer = YES;
|
||||
|
||||
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
|
||||
|
||||
if ([recipientId isEqualToString:localNumber]) {
|
||||
// Don't add self to contacts.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Don't bother to block self.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't bother adding self to profile whitelist.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
} else {
|
||||
if ([[self.blockingManager blockedPhoneNumbers] containsObject:recipientId]) {
|
||||
// Only create "add to contacts" offers for users which are not already blocked.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Only create block offers for users which are not already blocked.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't create profile whitelist offers for users which are not already blocked.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
|
||||
if ([self.contactsManager hasSignalAccountForRecipientId:recipientId]) {
|
||||
// Only create "add to contacts" offers for non-contacts.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Only create block offers for non-contacts.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't create profile whitelist offers for non-contacts.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (outgoingMessageCount > kMaxBlockOfferOutgoingMessageCount) {
|
||||
// If the user has sent more than N messages, don't show a block offer.
|
||||
shouldHaveBlockOffer = NO;
|
||||
}
|
||||
|
||||
BOOL hasOutgoingBeforeIncomingInteraction = [firstCallOrMessage isKindOfClass:[TSOutgoingMessage class]];
|
||||
if ([firstCallOrMessage isKindOfClass:[TSCall class]]) {
|
||||
TSCall *call = (TSCall *)firstCallOrMessage;
|
||||
hasOutgoingBeforeIncomingInteraction
|
||||
= (call.callType == RPRecentCallTypeOutgoing || call.callType == RPRecentCallTypeOutgoingIncomplete);
|
||||
}
|
||||
if (hasOutgoingBeforeIncomingInteraction) {
|
||||
// If there is an outgoing message before an incoming message
|
||||
// the local user initiated this conversation, don't show a block offer.
|
||||
shouldHaveBlockOffer = NO;
|
||||
}
|
||||
|
||||
if (!hasLocalProfile || isThreadInProfileWhitelist) {
|
||||
// Don't show offer if thread is local user hasn't configured their profile.
|
||||
// Don't show offer if thread is already in profile whitelist.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
} else if (thread.isGroupThread && !hasUnwhitelistedMember) {
|
||||
// Don't show offer in group thread if all members are already individually
|
||||
// whitelisted.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
|
||||
BOOL shouldHaveContactOffers
|
||||
= (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer);
|
||||
if (!shouldHaveContactOffers) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// We want the offers to be the first interactions in their
|
||||
// conversation's timeline, so we back-date them to slightly before
|
||||
// the first message - or at an arbitrary old timestamp if the
|
||||
// conversation has no messages.
|
||||
uint64_t contactOffersTimestamp = firstCallOrMessage.timestamp - 1;
|
||||
// This view model uses the "unique id" to identify this interaction,
|
||||
// but the interaction is never saved in the database so the specific
|
||||
// value doesn't matter.
|
||||
NSString *uniqueId = @"contact-offers";
|
||||
OWSContactOffersInteraction *offersMessage =
|
||||
[[OWSContactOffersInteraction alloc] initInteractionWithUniqueId:uniqueId
|
||||
timestamp:contactOffersTimestamp
|
||||
thread:thread
|
||||
hasBlockOffer:shouldHaveBlockOffer
|
||||
hasAddToContactsOffer:shouldHaveAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer
|
||||
recipientId:recipientId
|
||||
beforeInteractionId:firstCallOrMessage.uniqueId];
|
||||
|
||||
OWSLogInfo(@"Creating contact offers: %@ (%llu)", offersMessage.uniqueId, offersMessage.timestampForSorting);
|
||||
return offersMessage;
|
||||
}
|
||||
|
||||
// This is a key method. It builds or rebuilds the list of
|
||||
// cell view models.
|
||||
//
|
||||
|
@ -881,8 +1085,29 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
BOOL isGroupThread = self.thread.isGroupThread;
|
||||
ConversationStyle *conversationStyle = self.delegate.conversationStyle;
|
||||
|
||||
OWSContactOffersInteraction *_Nullable offers = [self tryToBuildContactOffersInteraction];
|
||||
|
||||
__block BOOL hasError = NO;
|
||||
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
id<ConversationViewItem> (^tryToAddViewItem)(TSInteraction *) = ^(TSInteraction *interaction) {
|
||||
OWSAssertDebug(interaction.uniqueId.length > 0);
|
||||
|
||||
id<ConversationViewItem> _Nullable viewItem = self.viewItemCache[interaction.uniqueId];
|
||||
if (!viewItem) {
|
||||
viewItem = [[ConversationInteractionViewItem alloc] initWithInteraction:interaction
|
||||
isGroupThread:isGroupThread
|
||||
transaction:transaction
|
||||
conversationStyle:conversationStyle];
|
||||
}
|
||||
[viewItems addObject:viewItem];
|
||||
OWSAssertDebug(!viewItemCache[interaction.uniqueId]);
|
||||
viewItemCache[interaction.uniqueId] = viewItem;
|
||||
return viewItem;
|
||||
};
|
||||
|
||||
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
|
||||
NSMutableSet<NSString *> *interactionIds = [NSMutableSet new];
|
||||
|
||||
YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName];
|
||||
OWSAssertDebug(viewTransaction);
|
||||
for (NSUInteger row = 0; row < count; row++) {
|
||||
|
@ -904,17 +1129,27 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
hasError = YES;
|
||||
continue;
|
||||
}
|
||||
[interactions addObject:interaction];
|
||||
[interactionIds addObject:interaction.uniqueId];
|
||||
}
|
||||
|
||||
id<ConversationViewItem> _Nullable viewItem = self.viewItemCache[interaction.uniqueId];
|
||||
if (!viewItem) {
|
||||
viewItem = [[ConversationInteractionViewItem alloc] initWithInteraction:interaction
|
||||
isGroupThread:isGroupThread
|
||||
transaction:transaction
|
||||
conversationStyle:conversationStyle];
|
||||
if (offers && [interactionIds containsObject:offers.beforeInteractionId]) {
|
||||
id<ConversationViewItem> offersItem = tryToAddViewItem(offers);
|
||||
if ([offersItem.interaction isKindOfClass:[OWSContactOffersInteraction class]]) {
|
||||
OWSContactOffersInteraction *oldOffers = (OWSContactOffersInteraction *)offersItem.interaction;
|
||||
BOOL didChange = (oldOffers.hasBlockOffer != offers.hasBlockOffer
|
||||
|| oldOffers.hasAddToContactsOffer != offers.hasAddToContactsOffer
|
||||
|| oldOffers.hasAddToProfileWhitelistOffer != offers.hasAddToProfileWhitelistOffer);
|
||||
if (didChange) {
|
||||
[offersItem clearCachedLayoutState];
|
||||
}
|
||||
} else {
|
||||
OWSFailDebug(@"Unexpected offers item: %@", offersItem.interaction.class);
|
||||
}
|
||||
[viewItems addObject:viewItem];
|
||||
OWSAssertDebug(!viewItemCache[interaction.uniqueId]);
|
||||
viewItemCache[interaction.uniqueId] = viewItem;
|
||||
}
|
||||
|
||||
for (TSInteraction *interaction in interactions) {
|
||||
tryToAddViewItem(interaction);
|
||||
}
|
||||
|
||||
if (self.typingIndicatorsSender) {
|
||||
|
@ -924,16 +1159,7 @@ static const int kYapDatabaseRangeMinLength = 0;
|
|||
[[OWSTypingIndicatorInteraction alloc] initWithThread:self.thread
|
||||
timestamp:typingIndicatorTimestamp
|
||||
recipientId:self.typingIndicatorsSender];
|
||||
id<ConversationViewItem> _Nullable viewItem = self.viewItemCache[interaction.uniqueId];
|
||||
if (!viewItem) {
|
||||
viewItem = [[ConversationInteractionViewItem alloc] initWithInteraction:interaction
|
||||
isGroupThread:isGroupThread
|
||||
transaction:transaction
|
||||
conversationStyle:conversationStyle];
|
||||
}
|
||||
[viewItems addObject:viewItem];
|
||||
OWSAssertDebug(!viewItemCache[interaction.uniqueId]);
|
||||
viewItemCache[interaction.uniqueId] = viewItem;
|
||||
tryToAddViewItem(interaction);
|
||||
}
|
||||
}];
|
||||
|
||||
|
|
|
@ -12,17 +12,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic, readonly) BOOL hasAddToContactsOffer;
|
||||
@property (nonatomic, readonly) BOOL hasAddToProfileWhitelistOffer;
|
||||
@property (nonatomic, readonly) NSString *recipientId;
|
||||
@property (nonatomic, readonly) NSString *beforeInteractionId;
|
||||
|
||||
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initContactOffersWithTimestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasBlockOffer:(BOOL)hasBlockOffer
|
||||
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
|
||||
recipientId:(NSString *)recipientId NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initInteractionWithUniqueId:(NSString *)uniqueId
|
||||
timestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasBlockOffer:(BOOL)hasBlockOffer
|
||||
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
|
||||
recipientId:(NSString *)recipientId
|
||||
beforeInteractionId:(NSString *)beforeInteractionId NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -13,14 +13,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [super initWithCoder:coder];
|
||||
}
|
||||
|
||||
- (instancetype)initContactOffersWithTimestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasBlockOffer:(BOOL)hasBlockOffer
|
||||
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
|
||||
recipientId:(NSString *)recipientId
|
||||
- (instancetype)initInteractionWithUniqueId:(NSString *)uniqueId
|
||||
timestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
hasBlockOffer:(BOOL)hasBlockOffer
|
||||
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
|
||||
recipientId:(NSString *)recipientId
|
||||
beforeInteractionId:(NSString *)beforeInteractionId
|
||||
{
|
||||
self = [super initInteractionWithTimestamp:timestamp inThread:thread];
|
||||
self = [super initInteractionWithUniqueId:uniqueId timestamp:timestamp inThread:thread];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
|
@ -31,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer;
|
||||
OWSAssertDebug(recipientId.length > 0);
|
||||
_recipientId = recipientId;
|
||||
_beforeInteractionId = beforeInteractionId;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -52,6 +55,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return OWSInteractionType_Offer;
|
||||
}
|
||||
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSFailDebug(@"This interaction should never be saved to the database.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -336,31 +336,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAssertDebug(blockingManager);
|
||||
OWSAssertDebug(maxRangeSize > 0);
|
||||
|
||||
NSString *localNumber = [TSAccountManager localNumber];
|
||||
OWSAssertDebug(localNumber.length > 0);
|
||||
|
||||
// Many OWSProfileManager methods aren't safe to call from inside a database
|
||||
// transaction, so do this work now.
|
||||
OWSProfileManager *profileManager = OWSProfileManager.sharedManager;
|
||||
BOOL hasLocalProfile = [profileManager hasLocalProfile];
|
||||
BOOL isThreadInProfileWhitelist = [profileManager isThreadInProfileWhitelist:thread];
|
||||
BOOL hasUnwhitelistedMember = NO;
|
||||
for (NSString *recipientId in thread.recipientIdentifiers) {
|
||||
if (![profileManager isUserInProfileWhitelist:recipientId]) {
|
||||
hasUnwhitelistedMember = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadDynamicInteractions *result = [ThreadDynamicInteractions new];
|
||||
|
||||
[dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
const int kMaxBlockOfferOutgoingMessageCount = 10;
|
||||
|
||||
// Find any "dynamic" interactions and safety number changes.
|
||||
//
|
||||
// We use different views for performance reasons.
|
||||
__block OWSContactOffersInteraction *existingContactOffers = nil;
|
||||
NSMutableArray<TSInvalidIdentityKeyErrorMessage *> *blockingSafetyNumberChanges = [NSMutableArray new];
|
||||
NSMutableArray<TSInteraction *> *nonBlockingSafetyNumberChanges = [NSMutableArray new];
|
||||
// We want to delete legacy and duplicate interactions.
|
||||
|
@ -383,16 +364,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// the OWSContactOffersInteraction.
|
||||
[interactionsToDelete addObject:object];
|
||||
} else if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) {
|
||||
// Remove obsolete unread indicator interactions;
|
||||
// Remove obsolete unread indicator interactions.
|
||||
[interactionsToDelete addObject:object];
|
||||
} else if ([object isKindOfClass:[OWSContactOffersInteraction class]]) {
|
||||
OWSAssertDebug(!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;
|
||||
// Remove obsolete contact offers.
|
||||
[interactionsToDelete addObject:object];
|
||||
} else if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) {
|
||||
[blockingSafetyNumberChanges addObject:object];
|
||||
} else if ([object isKindOfClass:[TSErrorMessage class]]) {
|
||||
|
@ -427,161 +403,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
}
|
||||
|
||||
__block TSInteraction *firstCallOrMessage = nil;
|
||||
[[transaction ext:TSMessageDatabaseViewExtensionName]
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
usingBlock:^(
|
||||
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
|
||||
|
||||
OWSAssertDebug([object isKindOfClass:[TSInteraction class]]);
|
||||
|
||||
if ([object isKindOfClass:[TSIncomingMessage class]] ||
|
||||
[object isKindOfClass:[TSOutgoingMessage class]] ||
|
||||
[object isKindOfClass:[TSCall class]]) {
|
||||
firstCallOrMessage = object;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
NSUInteger outgoingMessageCount =
|
||||
[[TSDatabaseView threadOutgoingMessageDatabaseView:transaction] numberOfItemsInGroup:thread.uniqueId];
|
||||
|
||||
BOOL shouldHaveBlockOffer = YES;
|
||||
BOOL shouldHaveAddToContactsOffer = YES;
|
||||
BOOL shouldHaveAddToProfileWhitelistOffer = YES;
|
||||
|
||||
BOOL isContactThread = [thread isKindOfClass:[TSContactThread class]];
|
||||
if (!isContactThread) {
|
||||
// Only create "add to contacts" offers in 1:1 conversations.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Only create block offers in 1:1 conversations.
|
||||
shouldHaveBlockOffer = NO;
|
||||
|
||||
// MJK TODO - any conditions under which we'd make a block offer for groups?
|
||||
|
||||
// Only create profile whitelist offers in 1:1 conversations.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
} else {
|
||||
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
|
||||
|
||||
if ([recipientId isEqualToString:localNumber]) {
|
||||
// Don't add self to contacts.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Don't bother to block self.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't bother adding self to profile whitelist.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
} else {
|
||||
if ([[blockingManager blockedPhoneNumbers] containsObject:recipientId]) {
|
||||
// Only create "add to contacts" offers for users which are not already blocked.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Only create block offers for users which are not already blocked.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't create profile whitelist offers for users which are not already blocked.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
|
||||
if ([contactsManager hasSignalAccountForRecipientId:recipientId]) {
|
||||
// Only create "add to contacts" offers for non-contacts.
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
// Only create block offers for non-contacts.
|
||||
shouldHaveBlockOffer = NO;
|
||||
// Don't create profile whitelist offers for non-contacts.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstCallOrMessage) {
|
||||
shouldHaveAddToContactsOffer = NO;
|
||||
shouldHaveBlockOffer = NO;
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
|
||||
if (outgoingMessageCount > kMaxBlockOfferOutgoingMessageCount) {
|
||||
// If the user has sent more than N messages, don't show a block offer.
|
||||
shouldHaveBlockOffer = NO;
|
||||
}
|
||||
|
||||
BOOL hasOutgoingBeforeIncomingInteraction = [firstCallOrMessage isKindOfClass:[TSOutgoingMessage class]];
|
||||
if ([firstCallOrMessage isKindOfClass:[TSCall class]]) {
|
||||
TSCall *call = (TSCall *)firstCallOrMessage;
|
||||
hasOutgoingBeforeIncomingInteraction
|
||||
= (call.callType == RPRecentCallTypeOutgoing || call.callType == RPRecentCallTypeOutgoingIncomplete);
|
||||
}
|
||||
if (hasOutgoingBeforeIncomingInteraction) {
|
||||
// If there is an outgoing message before an incoming message
|
||||
// the local user initiated this conversation, don't show a block offer.
|
||||
shouldHaveBlockOffer = NO;
|
||||
}
|
||||
|
||||
if (!hasLocalProfile || isThreadInProfileWhitelist) {
|
||||
// Don't show offer if thread is local user hasn't configured their profile.
|
||||
// Don't show offer if thread is already in profile whitelist.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
} else if (thread.isGroupThread && !hasUnwhitelistedMember) {
|
||||
// Don't show offer in group thread if all members are already individually
|
||||
// whitelisted.
|
||||
shouldHaveAddToProfileWhitelistOffer = NO;
|
||||
}
|
||||
|
||||
BOOL shouldHaveContactOffers
|
||||
= (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer);
|
||||
if (isContactThread) {
|
||||
TSContactThread *contactThread = (TSContactThread *)thread;
|
||||
if (contactThread.hasDismissedOffers) {
|
||||
shouldHaveContactOffers = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// We want the offers to be the first interactions in their
|
||||
// conversation's timeline, so we back-date them to slightly before
|
||||
// the first message - or at an aribtrary old timestamp if the
|
||||
// conversation has no messages.
|
||||
uint64_t contactOffersTimestamp = [NSDate ows_millisecondTimeStamp];
|
||||
|
||||
// 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) {
|
||||
OWSLogInfo(@"Removing stale contact offers: %@ (%llu)",
|
||||
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 (existingContactOffers && !shouldHaveContactOffers) {
|
||||
OWSLogInfo(@"Removing contact offers: %@ (%llu)",
|
||||
existingContactOffers.uniqueId,
|
||||
existingContactOffers.timestampForSorting);
|
||||
[existingContactOffers removeWithTransaction:transaction];
|
||||
} else if (!existingContactOffers && shouldHaveContactOffers) {
|
||||
NSString *recipientId = ((TSContactThread *)thread).contactIdentifier;
|
||||
|
||||
TSInteraction *offersMessage =
|
||||
[[OWSContactOffersInteraction alloc] initContactOffersWithTimestamp:contactOffersTimestamp
|
||||
thread:thread
|
||||
hasBlockOffer:shouldHaveBlockOffer
|
||||
hasAddToContactsOffer:shouldHaveAddToContactsOffer
|
||||
hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer
|
||||
recipientId:recipientId];
|
||||
[offersMessage saveWithTransaction:transaction];
|
||||
|
||||
OWSLogInfo(
|
||||
@"Creating contact offers: %@ (%llu)", offersMessage.uniqueId, offersMessage.timestampForSorting);
|
||||
}
|
||||
|
||||
[self ensureUnreadIndicator:result
|
||||
thread:thread
|
||||
transaction:transaction
|
||||
shouldHaveContactOffers:shouldHaveContactOffers
|
||||
maxRangeSize:maxRangeSize
|
||||
blockingSafetyNumberChanges:blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:nonBlockingSafetyNumberChanges
|
||||
|
@ -602,7 +426,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (void)ensureUnreadIndicator:(ThreadDynamicInteractions *)dynamicInteractions
|
||||
thread:(TSThread *)thread
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
shouldHaveContactOffers:(BOOL)shouldHaveContactOffers
|
||||
maxRangeSize:(int)maxRangeSize
|
||||
blockingSafetyNumberChanges:(NSArray<TSInvalidIdentityKeyErrorMessage *> *)blockingSafetyNumberChanges
|
||||
nonBlockingSafetyNumberChanges:(NSArray<TSInteraction *> *)nonBlockingSafetyNumberChanges
|
||||
|
@ -717,9 +540,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
|
||||
NSInteger unreadIndicatorPosition = visibleUnseenMessageCount;
|
||||
if (shouldHaveContactOffers) {
|
||||
unreadIndicatorPosition++;
|
||||
}
|
||||
|
||||
dynamicInteractions.unreadIndicator = [[OWSUnreadIndicator alloc]
|
||||
initUnreadIndicatorWithTimestamp:interactionAfterUnreadIndicator.timestampForSorting
|
||||
|
|
Loading…
Reference in a new issue