WIP: group blocking

-[ ] UI
  -[ ] Conversation Settings
    -[x] Show switch for group
    -[ ] localize
    -[ ] migrate existing localizations? (nice to have)
    -[ ] can view conversation settings (but not edit them) in left group
    -[ ] special block copy for groups
    -[ ] special unblock copy for groups
  -[ ] Block List
    -[ ] Group Section
    -[ ] Unblock group
  -[ ] Interstitial interacting with blocked threads (e.g. thread picker)
    -[ ] BlockListUIUtils w/ thread
        -[x] Block
        -[x] Unblock
        -[ ] Replace usages where possible
        -[x] block manager
-[ ] Sync
  -[x] tentative protos
  -[ ] confirm protos w/ team
  -[ ] send new protos
-[ ] Message Processing
  -[ ] Drop messages from blocked groups
This commit is contained in:
Michael Kirk 2018-09-07 15:45:54 -06:00
parent bfe1f38c75
commit 236c17f65e
8 changed files with 387 additions and 87 deletions

View File

@ -617,35 +617,48 @@ const CGFloat kIconViewLength = 24;
= NSLocalizedString(@"MUTE_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of muting a thread.");
[contents addSection:notificationsSection];
// Block user section.
// Block Conversation section.
if (!self.isGroupThread) {
BOOL isBlocked = [[_blockingManager blockedPhoneNumbers] containsObject:self.thread.contactIdentifier];
OWSTableSection *section = [OWSTableSection new];
OWSTableSection *section = [OWSTableSection new];
if (self.thread.isGroupThread) {
section.footerTitle = NSLocalizedString(
@"BLOCK_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking another user.");
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell =
[weakSelf disclosureCellWithName:NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_USER",
@"table cell label in conversation settings")
iconName:@"table_ic_block"];
OWSConversationSettingsViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UISwitch *blockUserSwitch = [UISwitch new];
blockUserSwitch.on = isBlocked;
[blockUserSwitch addTarget:strongSelf
action:@selector(blockUserSwitchDidChange:)
forControlEvents:UIControlEventValueChanged];
cell.accessoryView = blockUserSwitch;
return cell;
}
actionBlock:nil]];
[contents addSection:section];
@"BLOCK_GROUP_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking another user.");
} else {
section.footerTitle = NSLocalizedString(
@"BLOCK_USER_BEHAVIOR_EXPLANATION", @"An explanation of the consequences of blocking a group.");
}
[section addItem:[OWSTableItem
itemWithCustomCellBlock:^{
OWSConversationSettingsViewController *strongSelf = weakSelf;
if (!strongSelf) {
return [UITableViewCell new];
}
NSString *cellTitle;
if (self.thread.isGroupThread) {
cellTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_GROUP",
@"table cell label in conversation settings");
} else {
cellTitle = NSLocalizedString(@"CONVERSATION_SETTINGS_BLOCK_THIS_USER",
@"table cell label in conversation settings");
}
UITableViewCell *cell =
[strongSelf disclosureCellWithName:cellTitle iconName:@"table_ic_block"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UISwitch *blockConversationSwitch = [UISwitch new];
blockConversationSwitch.on = [strongSelf.blockingManager isThreadBlocked:self.thread];
[blockConversationSwitch addTarget:strongSelf
action:@selector(blockConversationSwitchDidChange:)
forControlEvents:UIControlEventValueChanged];
cell.accessoryView = blockConversationSwitch;
return cell;
}
actionBlock:nil]];
[contents addSection:section];
self.contents = contents;
}
@ -1013,43 +1026,41 @@ const CGFloat kIconViewLength = 24;
[self updateTableContents];
}
- (void)blockUserSwitchDidChange:(id)sender
- (void)blockConversationSwitchDidChange:(id)sender
{
OWSAssert(!self.isGroupThread);
if (![sender isKindOfClass:[UISwitch class]]) {
OWSFail(@"%@ Unexpected sender for block user switch: %@", self.logTag, sender);
}
UISwitch *blockUserSwitch = (UISwitch *)sender;
UISwitch *blockConversationSwitch = (UISwitch *)sender;
BOOL isCurrentlyBlocked = [[_blockingManager blockedPhoneNumbers] containsObject:self.thread.contactIdentifier];
BOOL isCurrentlyBlocked = [self.blockingManager isThreadBlocked:self.thread];
if (blockUserSwitch.isOn) {
if (blockConversationSwitch.isOn) {
OWSAssert(!isCurrentlyBlocked);
if (isCurrentlyBlocked) {
return;
}
[BlockListUIUtils showBlockPhoneNumberActionSheet:self.thread.contactIdentifier
fromViewController:self
blockingManager:_blockingManager
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockUserSwitch.on = isBlocked;
}];
[BlockListUIUtils showBlockThreadActionSheet:self.thread
fromViewController:self
blockingManager:_blockingManager
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockConversationSwitch.on = isBlocked;
}];
} else {
OWSAssert(isCurrentlyBlocked);
if (!isCurrentlyBlocked) {
return;
}
[BlockListUIUtils showUnblockPhoneNumberActionSheet:self.thread.contactIdentifier
fromViewController:self
blockingManager:_blockingManager
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockUserSwitch.on = isBlocked;
}];
[BlockListUIUtils showUnblockThreadActionSheet:self.thread
fromViewController:self
blockingManager:_blockingManager
contactsManager:_contactsManager
completionBlock:^(BOOL isBlocked) {
// Update switch state if user cancels action.
blockConversationSwitch.on = isBlocked;
}];
}
}

View File

@ -227,6 +227,9 @@
/* An explanation of the consequences of blocking another user. */
"BLOCK_BEHAVIOR_EXPLANATION" = "Blocked users will not be able to call you or send you messages.";
/* An explanation of the consequences of blocking another user. */
"BLOCK_GROUP_BEHAVIOR_EXPLANATION" = "BLOCK_GROUP_BEHAVIOR_EXPLANATION";
/* Button label for the 'block' button */
"BLOCK_LIST_BLOCK_BUTTON" = "Block";
@ -278,6 +281,9 @@
/* Title format for action sheet that offers to block an unknown user.Embeds {{the unknown user's name or phone number}}. */
"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT" = "Block %@?";
/* An explanation of the consequences of blocking a group. */
"BLOCK_USER_BEHAVIOR_EXPLANATION" = "BLOCK_USER_BEHAVIOR_EXPLANATION";
/* Label for 'continue' button. */
"BUTTON_CONTINUE" = "Continue";
@ -512,6 +518,9 @@
/* Label for 'new contact' button in conversation settings view. */
"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT" = "Add to Existing Contact";
/* table cell label in conversation settings */
"CONVERSATION_SETTINGS_BLOCK_THIS_GROUP" = "CONVERSATION_SETTINGS_BLOCK_THIS_GROUP";
/* table cell label in conversation settings */
"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Block this user";

View File

@ -238,21 +238,19 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
if ([thread isKindOfClass:[TSContactThread class]]) {
BOOL isBlocked = [helper isRecipientIdBlocked:thread.contactIdentifier];
if (isBlocked && ![strongSelf.selectThreadViewDelegate canSelectBlockedContact]) {
[BlockListUIUtils showUnblockPhoneNumberActionSheet:thread.contactIdentifier
fromViewController:strongSelf
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[strongSelf.selectThreadViewDelegate
threadWasSelected:thread];
}
}];
return;
}
BOOL isBlocked = [helper isThreadBlocked:thread];
if (isBlocked && ![strongSelf.selectThreadViewDelegate canSelectBlockedContact]) {
[BlockListUIUtils
showUnblockThreadActionSheet:thread
fromViewController:strongSelf
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[strongSelf.selectThreadViewDelegate threadWasSelected:thread];
}
}];
return;
}
[strongSelf.selectThreadViewDelegate threadWasSelected:thread];

View File

@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN
@class Contact;
@class ContactsViewHelper;
@class SignalAccount;
@class TSThread;
@protocol CNContactViewControllerDelegate;
@ -58,10 +59,13 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable SignalAccount *)fetchSignalAccountForRecipientId:(NSString *)recipientId;
- (SignalAccount *)fetchOrBuildSignalAccountForRecipientId:(NSString *)recipientId;
// MJK TODO Can we remove?
// This method is faster than OWSBlockingManager but
// is only safe to be called on the main thread.
- (BOOL)isRecipientIdBlocked:(NSString *)recipientId;
- (BOOL)isThreadBlocked:(TSThread *)thread;
// NOTE: This method uses a transaction.
- (NSString *)localNumber;

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
@ -7,9 +7,10 @@
NS_ASSUME_NONNULL_BEGIN
@class Contact;
@class SignalAccount;
@class OWSBlockingManager;
@class OWSContactsManager;
@class SignalAccount;
@class TSThread;
typedef void (^BlockActionCompletionBlock)(BOOL isBlocked);
@ -19,6 +20,12 @@ typedef void (^BlockActionCompletionBlock)(BOOL isBlocked);
#pragma mark - Block
+ (void)showBlockThreadActionSheet:(TSThread *)thread
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
contactsManager:(OWSContactsManager *)contactsManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock;
+ (void)showBlockPhoneNumberActionSheet:(NSString *)phoneNumber
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
@ -33,6 +40,12 @@ typedef void (^BlockActionCompletionBlock)(BOOL isBlocked);
#pragma mark - Unblock
+ (void)showUnblockThreadActionSheet:(TSThread *)thread
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
contactsManager:(OWSContactsManager *)contactsManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock;
+ (void)showUnblockPhoneNumberActionSheet:(NSString *)phoneNumber
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "BlockListUIUtils.h"
@ -19,6 +19,31 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
#pragma mark - Block
+ (void)showBlockThreadActionSheet:(TSThread *)thread
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
contactsManager:(OWSContactsManager *)contactsManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
{
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[self showBlockPhoneNumberActionSheet:contactThread.contactIdentifier
fromViewController:fromViewController
blockingManager:blockingManager
contactsManager:contactsManager
completionBlock:completionBlock];
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
[self showBlockGroupActionSheet:groupThread.groupModel
displayName:groupThread.name
fromViewController:fromViewController
blockingManager:blockingManager
completionBlock:completionBlock];
} else {
OWSFail(@"unexpected thread type: %@", thread.class);
}
}
+ (void)showBlockPhoneNumberActionSheet:(NSString *)phoneNumber
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
@ -89,7 +114,7 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
@"An explanation of the consequences of blocking another user.")
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *unblockAction = [UIAlertAction
UIAlertAction *blockAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"BLOCK_LIST_BLOCK_BUTTON", @"Button label for the 'block' button")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
@ -103,7 +128,56 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
}
}];
}];
[actionSheetController addAction:unblockAction];
[actionSheetController addAction:blockAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *_Nonnull action) {
if (completionBlock) {
completionBlock(NO);
}
}];
[actionSheetController addAction:dismissAction];
[fromViewController presentViewController:actionSheetController animated:YES completion:nil];
}
+ (void)showBlockGroupActionSheet:(TSGroupModel *)groupModel
displayName:(NSString *)displayName
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
{
OWSAssert(displayName.length > 0);
OWSAssert(fromViewController);
OWSAssert(blockingManager);
NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_LIST_BLOCK_GROUP_TITLE_FORMAT",
@"A format for the 'block group' action sheet title. Embeds {{the "
@"blocked group's name}}."),
[self formatDisplayNameForAlertTitle:displayName]];
UIAlertController *actionSheetController =
[UIAlertController alertControllerWithTitle:title
message:NSLocalizedString(@"BLOCK_GROUP_BEHAVIOR_EXPLANATION",
@"An explanation of the consequences of blocking a group.")
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *blockAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"BLOCK_LIST_BLOCK_BUTTON", @"Button label for the 'block' button")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[self blockGroup:groupModel
displayName:displayName
fromViewController:fromViewController
blockingManager:blockingManager
completionBlock:^(UIAlertAction *ignore) {
if (completionBlock) {
completionBlock(YES);
}
}];
}];
[actionSheetController addAction:blockAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
@ -144,8 +218,56 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
completionBlock:completionBlock];
}
+ (void)blockGroup:(TSGroupModel *)groupModel
displayName:(NSString *)displayName
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
completionBlock:(BlockAlertCompletionBlock)completionBlock
{
OWSAssert(displayName.length > 0);
OWSAssert(fromViewController);
OWSAssert(blockingManager);
[blockingManager addBlockedGroupId:groupModel.groupId];
[self showOkAlertWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCKED_GROUP_ALERT_TITLE",
@"The title of the 'group blocked' alert.")
message:[NSString
stringWithFormat:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCKED_ALERT_MESSAGE_FORMAT",
@"The message format of the 'user blocked' "
@"alert. Embeds {{the blocked group's name}}."),
[self formatDisplayNameForAlertMessage:displayName]]
fromViewController:fromViewController
completionBlock:completionBlock];
}
#pragma mark - Unblock
+ (void)showUnblockThreadActionSheet:(TSThread *)thread
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
contactsManager:(OWSContactsManager *)contactsManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
{
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[self showUnblockPhoneNumberActionSheet:contactThread.contactIdentifier
fromViewController:fromViewController
blockingManager:blockingManager
contactsManager:contactsManager
completionBlock:completionBlock];
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
[self showUnblockGroupActionSheet:groupThread.groupModel
displayName:groupThread.name
fromViewController:fromViewController
blockingManager:blockingManager
completionBlock:completionBlock];
} else {
OWSFail(@"unexpected thread type: %@", thread.class);
}
}
+ (void)showUnblockPhoneNumberActionSheet:(NSString *)phoneNumber
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
@ -248,28 +370,71 @@ typedef void (^BlockAlertCompletionBlock)(UIAlertAction *action);
completionBlock:completionBlock];
}
+ (void)showBlockFailedAlert:(UIViewController *)fromViewController
completionBlock:(BlockAlertCompletionBlock)completionBlock
+ (void)showUnblockGroupActionSheet:(TSGroupModel *)groupModel
displayName:(NSString *)displayName
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
completionBlock:(nullable BlockActionCompletionBlock)completionBlock
{
OWSAssert(displayName.length > 0);
OWSAssert(fromViewController);
OWSAssert(blockingManager);
[self showOkAlertWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCK_FAILED_ALERT_TITLE",
@"The title of the 'block user failed' alert.")
message:NSLocalizedString(@"BLOCK_LIST_VIEW_BLOCK_FAILED_ALERT_MESSAGE",
@"The title of the 'block user failed' alert.")
fromViewController:fromViewController
completionBlock:completionBlock];
NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_GROUP_TITLE_FORMAT",
@"A format for the 'unblock group' action sheet title. Embeds "
@"{{the blocked groups's name}}."),
[self formatDisplayNameForAlertTitle:displayName]];
UIAlertController *actionSheetController =
[UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *unblockAction = [UIAlertAction
actionWithTitle:NSLocalizedString(@"BLOCK_LIST_UNBLOCK_BUTTON", @"Button label for the 'unblock' button")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[BlockListUIUtils unblockGroup:groupModel
displayName:displayName
fromViewController:fromViewController
blockingManager:blockingManager
completionBlock:^(UIAlertAction *ignore) {
if (completionBlock) {
completionBlock(NO);
}
}];
}];
[actionSheetController addAction:unblockAction];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.cancelButton
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *_Nonnull action) {
if (completionBlock) {
completionBlock(YES);
}
}];
[actionSheetController addAction:dismissAction];
[fromViewController presentViewController:actionSheetController animated:YES completion:nil];
}
+ (void)showUnblockFailedAlert:(UIViewController *)fromViewController
completionBlock:(BlockAlertCompletionBlock)completionBlock
+ (void)unblockGroup:(TSGroupModel *)groupModel
displayName:(NSString *)displayName
fromViewController:(UIViewController *)fromViewController
blockingManager:(OWSBlockingManager *)blockingManager
completionBlock:(BlockAlertCompletionBlock)completionBlock
{
OWSAssert(displayName.length > 0);
OWSAssert(fromViewController);
OWSAssert(blockingManager);
[self showOkAlertWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_UNBLOCK_FAILED_ALERT_TITLE",
@"The title of the 'unblock user failed' alert.")
message:NSLocalizedString(@"BLOCK_LIST_VIEW_UNBLOCK_FAILED_ALERT_MESSAGE",
@"The title of the 'unblock user failed' alert.")
[blockingManager removeBlockedGroupId:groupModel.groupId];
[self showOkAlertWithTitle:NSLocalizedString(@"BLOCK_LIST_VIEW_UNBLOCKED_USER_ALERT_TITLE",
@"The title of the 'group unblocked' alert.")
message:[NSString stringWithFormat:NSLocalizedString(
@"BLOCK_LIST_VIEW_UNBLOCKED_USER_ALERT_MESSAGE_FORMAT",
@"The message format of the 'group unblocked' "
@"alert. Embeds {{the blocked group's name}}."),
[self formatDisplayNameForAlertMessage:displayName]]
fromViewController:fromViewController
completionBlock:completionBlock];
}

View File

@ -4,6 +4,8 @@
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
extern NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange;
// This class can be safely accessed and used from any thread.
@ -21,9 +23,15 @@ extern NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange;
// want to fire a sync message.
- (void)setBlockedPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers sendSyncMessage:(BOOL)sendSyncMessage;
// TODO convert to property
- (NSArray<NSString *> *)blockedPhoneNumbers;
@property (readonly) NSArray<NSData *> *blockedGroupIds;
- (void)addBlockedGroupId:(NSData *)groupId;
- (void)removeBlockedGroupId:(NSData *)groupId;
- (BOOL)isRecipientIdBlocked:(NSString *)recipientId;
- (BOOL)isThreadBlocked:(TSThread *)thread;
- (void)syncBlockedPhoneNumbers;

View File

@ -9,6 +9,8 @@
#import "OWSBlockedPhoneNumbersMessage.h"
#import "OWSMessageSender.h"
#import "OWSPrimaryStorage.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import "TextSecureKitEnv.h"
#import "YapDatabaseConnection+OWS.h"
@ -16,9 +18,13 @@ NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange = @"kNSNotificationName_BlockedPhoneNumbersDidChange";
NSString *const kOWSBlockingManager_BlockedPhoneNumbersCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
NSString *const kOWSBlockingManager_BlockListCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
// This key is used to persist the current "blocked phone numbers" state.
NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey";
NSString *const kOWSBlockingManager_BlockedGroupIdsKey = @"kOWSBlockingManager_BlockedGroupIdsKey";
// This key is used to persist the most recently synced "blocked phone numbers" state.
NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockingManager_SyncedBlockedPhoneNumbersKey";
@ -31,6 +37,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
// consistency issues between clients, but these should all be valid e164
// phone numbers.
@property (atomic, readonly) NSMutableSet<NSString *> *blockedPhoneNumberSet;
@property (atomic, readonly) NSMutableSet<NSData *> *blockedGroupIdSet;
@end
@ -93,6 +100,25 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
object:nil];
}
#pragma mark -
- (BOOL)isThreadBlocked:(TSThread *)thread
{
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
return [self isRecipientIdBlocked:contactThread.contactIdentifier];
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
return [self isGroupIdBlocked:groupThread.groupModel.groupId];
} else {
OWSFail(@"%@ failure unexpected thread type", self.logTag);
return NO;
}
}
#pragma mark - Contact Blocking
- (void)addBlockedPhoneNumber:(NSString *)phoneNumber
{
OWSAssert(phoneNumber.length > 0);
@ -171,6 +197,65 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
return [self.blockedPhoneNumbers containsObject:recipientId];
}
#pragma mark - Group Blocking
- (NSArray<NSData *> *)blockedGroupIds
{
@synchronized(self) {
[self ensureLazyInitialization];
return _blockedGroupIdSet.allObjects;
}
}
- (BOOL)isGroupIdBlocked:(NSData *)groupId
{
return [self.blockedGroupIds containsObject:groupId];
}
- (void)addBlockedGroupId:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
DDLogInfo(@"%@ addBlockedGroupId: %@", self.logTag, groupId);
@synchronized(self) {
[self ensureLazyInitialization];
if ([_blockedGroupIdSet containsObject:groupId]) {
// Ignore redundant changes.
return;
}
[_blockedGroupIdSet addObject:groupId];
}
[self handleUpdate];
}
- (void)removeBlockedGroupId:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
DDLogInfo(@"%@ removeBlockedGroupId: %@", self.logTag, groupId);
@synchronized(self) {
[self ensureLazyInitialization];
if (![_blockedGroupIdSet containsObject:groupId]) {
// Ignore redundant changes.
return;
}
[_blockedGroupIdSet removeObject:groupId];
}
[self handleUpdate];
}
#pragma mark - Updates
// This should be called every time the block list changes.
- (void)handleUpdate
@ -185,7 +270,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
[self.dbConnection setObject:blockedPhoneNumbers
forKey:kOWSBlockingManager_BlockedPhoneNumbersKey
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
inCollection:kOWSBlockingManager_BlockListCollection];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (sendSyncMessage) {
@ -216,15 +301,21 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
- (void)ensureLazyInitialization
{
if (_blockedPhoneNumberSet) {
// _blockedPhoneNumberSet has already been loaded, abort.
OWSAssert(_blockedGroupIdSet);
// already loaded
return;
}
NSArray<NSString *> *blockedPhoneNumbers =
[self.dbConnection objectForKey:kOWSBlockingManager_BlockedPhoneNumbersKey
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
inCollection:kOWSBlockingManager_BlockListCollection];
_blockedPhoneNumberSet = [[NSMutableSet alloc] initWithArray:(blockedPhoneNumbers ?: [NSArray new])];
NSArray<NSData *> *blockedGroupIds = [self.dbConnection objectForKey:kOWSBlockingManager_BlockedGroupIdsKey
inCollection:kOWSBlockingManager_BlockListCollection];
_blockedGroupIdSet = [[NSMutableSet alloc] initWithArray:(blockedGroupIds ?: [NSArray new])];
[self syncBlockedPhoneNumbersIfNecessary];
[self observeNotifications];
}
@ -247,7 +338,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
// try again to sync now.
NSArray<NSString *> *syncedBlockedPhoneNumbers =
[self.dbConnection objectForKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
inCollection:kOWSBlockingManager_BlockListCollection];
NSSet *syncedBlockedPhoneNumberSet = [[NSSet alloc] initWithArray:(syncedBlockedPhoneNumbers ?: [NSArray new])];
if (![_blockedPhoneNumberSet isEqualToSet:syncedBlockedPhoneNumberSet]) {
DDLogInfo(@"%@ retrying sync of blocked phone numbers", self.logTag);
@ -258,6 +349,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
- (void)sendBlockedPhoneNumbersMessage:(NSArray<NSString *> *)blockedPhoneNumbers
{
OWSAssert(blockedPhoneNumbers);
OWSAssert(blockedGroupIds);
OWSBlockedPhoneNumbersMessage *message =
[[OWSBlockedPhoneNumbersMessage alloc] initWithPhoneNumbers:blockedPhoneNumbers];
@ -281,7 +373,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
// Record the last set of "blocked phone numbers" which we successfully synced.
[self.dbConnection setObject:blockedPhoneNumbers
forKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
inCollection:kOWSBlockingManager_BlockListCollection];
}
#pragma mark - Notifications