diff --git a/Signal/src/Profiles/OWSProfileManager.h b/Signal/src/Profiles/OWSProfileManager.h index 57a1eed92..e5b2b9e56 100644 --- a/Signal/src/Profiles/OWSProfileManager.h +++ b/Signal/src/Profiles/OWSProfileManager.h @@ -24,6 +24,7 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; // These two methods should only be called from the main thread. - (NSData *)localProfileKey; +- (BOOL)hasLocalProfile; - (nullable NSString *)localProfileName; - (nullable UIImage *)localProfileAvatarImage; @@ -39,16 +40,14 @@ extern NSString *const kNSNotificationName_OtherUsersProfileDidChange; #pragma mark - Profile Whitelist -- (void)addUserToProfileWhitelist:(NSString *)recipientId; +- (void)addThreadToProfileWhitelist:(TSThread *)thread; -- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; +- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread; -- (void)addGroupIdToProfileWhitelist:(NSData *)groupId; +- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; - (void)setContactRecipientIds:(NSArray *)contactRecipientIds; -- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread; - #pragma mark - Other User's Profiles - (void)setProfileKey:(NSData *)profileKey forRecipientId:(NSString *)recipientId; diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 250b444c3..26bd48789 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -254,6 +254,13 @@ static const NSInteger kProfileKeyLength = 16; return self.localUserProfile.profileKey; } +- (BOOL)hasLocalProfile +{ + OWSAssert([NSThread isMainThread]); + + return (self.localProfileName.length > 0 || self.localProfileAvatarImage != nil); +} + - (nullable NSString *)localProfileName { OWSAssert([NSThread isMainThread]); @@ -489,6 +496,20 @@ static const NSInteger kProfileKeyLength = 16; self.groupProfileWhitelistCache[groupIdKey] = @(YES); } +- (void)addThreadToProfileWhitelist:(TSThread *)thread +{ + OWSAssert(thread); + + if (thread.isGroupThread) { + TSGroupThread *groupThread = (TSGroupThread *)thread; + NSData *groupId = groupThread.groupModel.groupId; + [self addGroupIdToProfileWhitelist:groupId]; + } else { + NSString *recipientId = thread.contactIdentifier; + [self addUserToProfileWhitelist:recipientId]; + } +} + - (BOOL)isGroupIdInProfileWhitelist:(NSData *)groupId { OWSAssert(groupId.length > 0); diff --git a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m index 657049768..672cfc3ba 100644 --- a/Signal/src/ViewControllers/ConversationView/MessagesViewController.m +++ b/Signal/src/ViewControllers/ConversationView/MessagesViewController.m @@ -70,6 +70,7 @@ #import #import #import +#import #import #import #import @@ -2457,6 +2458,11 @@ typedef enum : NSUInteger { OWSAssert([message isKindOfClass:[OWSAddToContactsOfferMessage class]]); [self tappedAddToContactsOfferMessage:(OWSAddToContactsOfferMessage *)message]; return; + case TSInfoMessageAddUserToProfileWhitelistOffer: + case TSInfoMessageAddGroupToProfileWhitelistOffer: + OWSAssert([message isKindOfClass:[OWSAddToProfileWhitelistOfferMessage class]]); + [self tappedAddToProfileWhitelistOfferMessage:(OWSAddToProfileWhitelistOfferMessage *)message]; + return; case TSInfoMessageTypeGroupUpdate: [self showConversationSettings]; return; @@ -2584,7 +2590,7 @@ typedef enum : NSUInteger { [self presentViewController:actionSheetController animated:YES completion:nil]; } -- (void)tappedAddToContactsOfferMessage:(OWSAddToContactsOfferMessage *)errorMessage +- (void)tappedAddToContactsOfferMessage:(OWSAddToContactsOfferMessage *)message { if (!self.contactsManager.supportsContactEditing) { DDLogError(@"%@ Contact editing not supported", self.tag); @@ -2603,6 +2609,32 @@ typedef enum : NSUInteger { editImmediately:YES]; } +- (void)tappedAddToProfileWhitelistOfferMessage:(OWSAddToProfileWhitelistOfferMessage *)message +{ + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *leaveAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE", + @"Button to confirm that user wants to share their profile with a user or group.") + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *_Nonnull action) { + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:self.thread]; + + [self ensureDynamicInteractions]; + }]; + [alertController addAction:leaveAction]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil) + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *_Nonnull action){ + // Do nothing. + }]; + [alertController addAction:cancelAction]; + + [self presentViewController:alertController animated:YES completion:nil]; +} + - (void)handleCallTap:(TSCall *)call { OWSAssert(call); diff --git a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m index 371aae256..de045a625 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m @@ -305,9 +305,7 @@ NS_ASSUME_NONNULL_BEGIN @"Indicates that user's profile has been shared with a user."))iconName :@"table_ic_share_profile"]; } - actionBlock:^{ - [weakSelf showShareProfileAlert]; - }]]; + actionBlock:nil]]; } else { [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ return @@ -801,14 +799,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)shareProfile { - if (self.isGroupThread) { - TSGroupThread *groupThread = (TSGroupThread *)self.thread; - NSData *groupId = groupThread.groupModel.groupId; - [OWSProfileManager.sharedManager addGroupIdToProfileWhitelist:groupId]; - } else { - NSString *recipientId = self.thread.contactIdentifier; - [OWSProfileManager.sharedManager addUserToProfileWhitelist:recipientId]; - } + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:self.thread]; [self updateTableContents]; } diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index d3205b0fb..8083ecc20 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -8,6 +8,7 @@ #import "TSUnreadIndicatorInteraction.h" #import #import +#import #import #import #import @@ -151,6 +152,7 @@ NS_ASSUME_NONNULL_BEGIN // Find any "dynamic" interactions and safety number changes. __block OWSAddToContactsOfferMessage *existingAddToContactsOffer = nil; + __block OWSAddToProfileWhitelistOfferMessage *existingOWSAddToProfileWhitelistOffer = nil; __block OWSUnknownContactBlockOfferMessage *existingBlockOffer = nil; __block TSUnreadIndicatorInteraction *existingUnreadIndicator = nil; NSMutableArray *blockingSafetyNumberChanges = [NSMutableArray new]; @@ -167,6 +169,9 @@ NS_ASSUME_NONNULL_BEGIN } else if ([object isKindOfClass:[OWSAddToContactsOfferMessage class]]) { OWSAssert(!existingAddToContactsOffer); existingAddToContactsOffer = (OWSAddToContactsOfferMessage *)object; + } else if ([object isKindOfClass:[OWSAddToProfileWhitelistOfferMessage class]]) { + OWSAssert(!existingOWSAddToProfileWhitelistOffer); + existingOWSAddToProfileWhitelistOffer = (OWSAddToProfileWhitelistOfferMessage *)object; } else if ([object isKindOfClass:[TSUnreadIndicatorInteraction class]]) { OWSAssert(!existingUnreadIndicator); existingUnreadIndicator = (TSUnreadIndicatorInteraction *)object; @@ -311,6 +316,7 @@ NS_ASSUME_NONNULL_BEGIN BOOL shouldHaveBlockOffer = YES; BOOL shouldHaveAddToContactsOffer = YES; + BOOL shouldHaveAddToProfileWhitelistOffer = YES; BOOL isContactThread = [thread isKindOfClass:[TSContactThread class]]; if (!isContactThread) { @@ -326,6 +332,8 @@ NS_ASSUME_NONNULL_BEGIN 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. @@ -361,7 +369,15 @@ NS_ASSUME_NONNULL_BEGIN shouldHaveBlockOffer = NO; } + if (![OWSProfileManager.sharedManager hasLocalProfile] || + [OWSProfileManager.sharedManager isThreadInProfileWhitelist:thread]) { + // 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; + } + // 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; @@ -371,7 +387,6 @@ NS_ASSUME_NONNULL_BEGIN self.tag, existingBlockOffer.uniqueId, existingBlockOffer.timestampForSorting); - ; [existingBlockOffer removeWithTransaction:transaction]; } else if (!existingBlockOffer && shouldHaveBlockOffer) { DDLogInfo(@"Creating block offer for unknown contact"); @@ -402,7 +417,7 @@ NS_ASSUME_NONNULL_BEGIN [existingAddToContactsOffer removeWithTransaction:transaction]; } else if (!existingAddToContactsOffer && shouldHaveAddToContactsOffer) { - DDLogInfo(@"Creating 'add to contacts' offer for unknown contact"); + DDLogInfo(@"%@ Creating 'add to contacts' offer for unknown contact", self.tag); // We want the offer to be the first interaction in their // conversation's timeline, so we back-date it to slightly before @@ -422,6 +437,32 @@ NS_ASSUME_NONNULL_BEGIN 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); + + // We want the offer to be the first interaction in their + // conversation's timeline, so we back-date it to slightly before + // the first incoming message (which we know is the first message). + uint64_t offerTimestamp + = (uint64_t)((long long)firstMessage.timestampForSorting + kAddToProfileWhitelistOfferOffset); + + TSMessage *offerMessage = + [OWSAddToProfileWhitelistOfferMessage addToProfileWhitelistOfferMessage:offerTimestamp thread:thread]; + [offerMessage saveWithTransaction:transaction]; + + DDLogInfo(@"%@ Creating 'add to profile whitelist' offer: %@ (%llu)", + self.tag, + offerMessage.uniqueId, + offerMessage.timestampForSorting); + } + BOOL shouldHaveUnreadIndicator = (interactionAfterUnreadIndicator && !hideUnreadMessagesIndicator && threadMessageCount > 1); if (!shouldHaveUnreadIndicator) { diff --git a/Signal/src/views/OWSSystemMessageCell.m b/Signal/src/views/OWSSystemMessageCell.m index cf0fd14ba..a0d778811 100644 --- a/Signal/src/views/OWSSystemMessageCell.m +++ b/Signal/src/views/OWSSystemMessageCell.m @@ -144,6 +144,8 @@ NS_ASSUME_NONNULL_BEGIN case TSInfoMessageTypeSessionDidEnd: case TSInfoMessageTypeUnsupportedMessage: case TSInfoMessageAddToContactsOffer: + case TSInfoMessageAddUserToProfileWhitelistOffer: + case TSInfoMessageAddGroupToProfileWhitelistOffer: result = [UIImage imageNamed:@"system_message_info"]; break; case TSInfoMessageTypeGroupUpdate: diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index cd0c2b590..a9162dc0a 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -16,9 +16,15 @@ /* Title for the 'add group member' view. */ "ADD_GROUP_MEMBER_VIEW_TITLE" = "Add Member"; +/* Message shown in conversation view that offers to share your profile with a group. */ +"ADD_GROUP_TO_PROFILE_WHITELIST_OFFER" = "Would you like to share your profile with this group?"; + /* Message shown in conversation view that offers to add an unknown user to your phone's contacts. */ "ADD_TO_CONTACTS_OFFER" = "Would you like to add this user to your contacts?"; +/* Message shown in conversation view that offers to share your profile with a user. */ +"ADD_USER_TO_PROFILE_WHITELIST_OFFER" = "Would you like to share your profile with this user?"; + /* The label for the 'discard' button in alerts and action sheets. */ "ALERT_DISCARD_BUTTON" = "Discard"; @@ -341,10 +347,10 @@ "CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute"; /* Indicates that user's profile has been shared with a group. */ -"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_GROUP" = "Your profile is shared this group."; +"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_GROUP" = "This group can see your profile."; /* Indicates that user's profile has been shared with a user. */ -"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_USER" = "Your profile is shared this user."; +"CONVERSATION_SETTINGS_VIEW_PROFILE_IS_SHARED_WITH_USER" = "This user can see your profile."; /* Button to confirm that user wants to share their profile with a user or group. */ "CONVERSATION_SETTINGS_VIEW_SHARE_PROFILE" = "Share Profile"; diff --git a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h index fa9278e2d..b43d0fe9d 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h @@ -19,6 +19,8 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) { TSInfoMessageTypeDisappearingMessagesUpdate, TSInfoMessageAddToContactsOffer, TSInfoMessageVerificationStateChange, + TSInfoMessageAddUserToProfileWhitelistOffer, + TSInfoMessageAddGroupToProfileWhitelistOffer, }; + (instancetype)userNotRegisteredMessageInThread:(TSThread *)thread; diff --git a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m index d8ae95306..d43ef15fd 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m @@ -95,6 +95,12 @@ NSUInteger TSInfoMessageSchemaVersion = 1; case TSInfoMessageVerificationStateChange: return NSLocalizedString(@"VERIFICATION_STATE_CHANGE_GENERIC", @"Generic message indicating that verification state changed for a given user."); + case TSInfoMessageAddUserToProfileWhitelistOffer: + return NSLocalizedString(@"ADD_USER_TO_PROFILE_WHITELIST_OFFER", + @"Message shown in conversation view that offers to share your profile with a user."); + case TSInfoMessageAddGroupToProfileWhitelistOffer: + return NSLocalizedString(@"ADD_GROUP_TO_PROFILE_WHITELIST_OFFER", + @"Message shown in conversation view that offers to share your profile with a group."); default: break; } diff --git a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h new file mode 100644 index 000000000..0e003ca7a --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "TSInfoMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSAddToProfileWhitelistOfferMessage : TSInfoMessage + ++ (instancetype)addToProfileWhitelistOfferMessage:(uint64_t)timestamp thread:(TSThread *)thread; + +@property (nonatomic, readonly) NSString *contactId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m new file mode 100644 index 000000000..7a59d80ae --- /dev/null +++ b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m @@ -0,0 +1,35 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSAddToProfileWhitelistOfferMessage.h" +#import "TSThread.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSAddToProfileWhitelistOfferMessage + ++ (instancetype)addToProfileWhitelistOfferMessage:(uint64_t)timestamp thread:(TSThread *)thread +{ + return [[OWSAddToProfileWhitelistOfferMessage alloc] + initWithTimestamp:timestamp + inThread:thread + messageType:(thread.isGroupThread ? TSInfoMessageAddGroupToProfileWhitelistOffer + : TSInfoMessageAddUserToProfileWhitelistOffer)]; +} + +- (BOOL)shouldUseReceiptDateForSorting +{ + // Use the timestamp, not the "received at" timestamp to sort, + // since we're creating these interactions after the fact and back-dating them. + return NO; +} + +- (BOOL)isDynamicInteraction +{ + return YES; +} + +@end + +NS_ASSUME_NONNULL_END