session-ios/Signal/src/ViewControllers/NewGroupViewController.m

665 lines
28 KiB
Mathematica
Raw Normal View History

2014-10-29 21:58:58 +01:00
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
2014-10-29 21:58:58 +01:00
//
2016-09-02 16:22:06 +02:00
#import "NewGroupViewController.h"
#import "AddToGroupViewController.h"
#import "AvatarViewHelper.h"
#import "OWSNavigationController.h"
#import "Signal-Swift.h"
#import "SignalApp.h"
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalCoreKit/Randomness.h>
2017-12-08 17:50:35 +01:00
#import <SignalMessaging/BlockListUIUtils.h>
#import <SignalMessaging/ContactTableViewCell.h>
#import <SignalMessaging/ContactsViewHelper.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/NSString+OWS.h>
#import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSTableViewController.h>
#import <SignalMessaging/SignalKeyingStorage.h>
#import <SignalMessaging/UIUtil.h>
#import <SignalMessaging/UIView+OWS.h>
#import <SignalMessaging/UIViewController+OWS.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/SignalAccount.h>
#import <SignalServiceKit/TSGroupModel.h>
#import <SignalServiceKit/TSGroupThread.h>
2017-12-08 17:50:35 +01:00
#import <SignalServiceKit/TSOutgoingMessage.h>
2014-10-29 21:58:58 +01:00
NS_ASSUME_NONNULL_BEGIN
@interface NewGroupViewController () <UIImagePickerControllerDelegate,
UITextFieldDelegate,
ContactsViewHelperDelegate,
AvatarViewHelperDelegate,
AddToGroupViewControllerDelegate,
OWSTableViewControllerDelegate,
UINavigationControllerDelegate,
OWSNavigationView>
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) AvatarImageView *avatarView;
@property (nonatomic, readonly) UITextField *groupNameTextField;
2014-10-29 21:58:58 +01:00
2018-09-27 15:07:03 +02:00
@property (nonatomic, readonly) NSData *groupId;
@property (nonatomic, nullable) UIImage *groupAvatar;
2017-05-02 16:54:07 +02:00
@property (nonatomic) NSMutableSet<NSString *> *memberRecipientIds;
@property (nonatomic) BOOL hasUnsavedChanges;
2017-05-02 18:30:53 +02:00
@property (nonatomic) BOOL hasAppeared;
2014-10-29 21:58:58 +01:00
@end
#pragma mark -
2014-10-29 21:58:58 +01:00
@implementation NewGroupViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_groupId = [Randomness generateRandomBytes:kGroupIdLength];
2018-09-17 15:27:58 +02:00
_messageSender = SSKEnvironment.shared.messageSender;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
self.memberRecipientIds = [NSMutableSet new];
}
#pragma mark - View Lifecycle
- (void)loadView
{
[super loadView];
self.title = [MessageStrings newGroupDefaultTitle];
2018-07-24 00:19:23 +02:00
self.view.backgroundColor = Theme.backgroundColor;
2018-07-23 08:26:32 +02:00
2017-05-02 18:30:53 +02:00
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"NEW_GROUP_CREATE_BUTTON", @"The title for the 'create group' button.")
style:UIBarButtonItemStylePlain
target:self
action:@selector(createGroup)];
self.navigationItem.rightBarButtonItem.imageInsets = UIEdgeInsetsMake(0, -10, 0, 10);
self.navigationItem.rightBarButtonItem.accessibilityLabel
= NSLocalizedString(@"FINISH_GROUP_CREATION_LABEL", @"Accessibility label for finishing new group");
// First section.
UIView *firstSection = [self firstSectionHeader];
[self.view addSubview:firstSection];
[firstSection autoSetDimension:ALDimensionHeight toSize:100.f];
[firstSection autoPinWidthToSuperview];
[firstSection autoPinToTopLayoutGuideOfViewController:self withInset:0];
_tableViewController = [OWSTableViewController new];
_tableViewController.delegate = self;
[self.view addSubview:self.tableViewController.view];
[_tableViewController.view autoPinWidthToSuperview];
[_tableViewController.view autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:firstSection];
2018-08-08 21:49:22 +02:00
[self autoPinViewToBottomOfViewControllerOrKeyboard:self.tableViewController.view avoidNotch:NO];
2018-06-15 17:15:21 +02:00
self.tableViewController.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableViewController.tableView.estimatedRowHeight = 60;
[self updateTableContents];
}
- (UIView *)firstSectionHeader
{
UIView *firstSectionHeader = [UIView new];
firstSectionHeader.userInteractionEnabled = YES;
[firstSectionHeader
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headerWasTapped:)]];
2018-07-13 15:50:49 +02:00
firstSectionHeader.backgroundColor = [Theme backgroundColor];
UIView *threadInfoView = [UIView new];
[firstSectionHeader addSubview:threadInfoView];
[threadInfoView autoPinWidthToSuperviewWithMargin:16.f];
[threadInfoView autoPinHeightToSuperviewWithMargin:16.f];
AvatarImageView *avatarView = [AvatarImageView new];
_avatarView = avatarView;
[threadInfoView addSubview:avatarView];
[avatarView autoVCenterInSuperview];
[avatarView autoPinLeadingToSuperviewMargin];
[avatarView autoSetDimension:ALDimensionWidth toSize:kLargeAvatarSize];
[avatarView autoSetDimension:ALDimensionHeight toSize:kLargeAvatarSize];
[self updateAvatarView];
2018-08-15 23:09:59 +02:00
UITextField *groupNameTextField = [OWSTextField new];
_groupNameTextField = groupNameTextField;
2018-08-15 22:29:13 +02:00
groupNameTextField.textColor = Theme.primaryColor;
groupNameTextField.font = [UIFont ows_dynamicTypeTitle2Font];
2018-08-15 22:29:13 +02:00
groupNameTextField.attributedPlaceholder =
[[NSAttributedString alloc] initWithString:NSLocalizedString(@"NEW_GROUP_NAMEGROUP_REQUEST_DEFAULT",
@"Placeholder text for group name field")
attributes:@{
NSForegroundColorAttributeName : Theme.secondaryColor,
}];
groupNameTextField.delegate = self;
[groupNameTextField addTarget:self
action:@selector(groupNameDidChange:)
forControlEvents:UIControlEventEditingChanged];
[threadInfoView addSubview:groupNameTextField];
[groupNameTextField autoVCenterInSuperview];
[groupNameTextField autoPinTrailingToSuperviewMargin];
[groupNameTextField autoPinLeadingToTrailingEdgeOfView:avatarView offset:16.f];
2017-05-02 18:30:53 +02:00
[avatarView
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTouched:)]];
avatarView.userInteractionEnabled = YES;
return firstSectionHeader;
}
- (void)headerWasTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.groupNameTextField becomeFirstResponder];
}
}
- (void)avatarTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self showChangeAvatarUI];
}
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak NewGroupViewController *weakSelf = self;
2017-05-02 16:54:07 +02:00
ContactsViewHelper *contactsViewHelper = self.contactsViewHelper;
2017-05-01 18:51:59 +02:00
NSArray<SignalAccount *> *signalAccounts = self.contactsViewHelper.signalAccounts;
NSMutableSet *nonContactMemberRecipientIds = [self.memberRecipientIds mutableCopy];
2017-05-01 18:51:59 +02:00
for (SignalAccount *signalAccount in signalAccounts) {
[nonContactMemberRecipientIds removeObject:signalAccount.recipientId];
}
// Non-contact Members
2017-05-01 18:51:59 +02:00
if (nonContactMemberRecipientIds.count > 0 || signalAccounts.count < 1) {
OWSTableSection *nonContactsSection = [OWSTableSection new];
nonContactsSection.headerTitle = NSLocalizedString(
@"NEW_GROUP_NON_CONTACTS_SECTION_TITLE", @"a title for the non-contacts section of the 'new group' view.");
[nonContactsSection addItem:[self createAddNonContactItem]];
for (NSString *recipientId in
[nonContactMemberRecipientIds.allObjects sortedArrayUsingSelector:@selector(compare:)]) {
[nonContactsSection
2018-06-13 22:22:03 +02:00
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
NewGroupViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
2018-06-13 22:22:03 +02:00
ContactTableViewCell *cell = [ContactTableViewCell new];
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
// In the "contacts" section, we label members as such when editing an existing
// group.
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
@"An indicator that a user is a member of the new group.");
} else if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
}
2018-10-25 15:35:08 +02:00
[cell configureWithRecipientId:recipientId];
2018-06-13 22:22:03 +02:00
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
[weakSelf removeRecipientId:recipientId];
} else if (isBlocked) {
[BlockListUIUtils
showUnblockPhoneNumberActionSheet:recipientId
fromViewController:weakSelf
blockingManager:contactsViewHelper.blockingManager
contactsManager:contactsViewHelper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[weakSelf addRecipientId:recipientId];
}
}];
} else {
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:recipientId
confirmationText:NSLocalizedString(
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
@"ADD_TO_GROUP_ACTION",
@"button title to confirm adding "
@"a recipient to a group when "
@"their safety "
@"number has recently changed")
contactsManager:contactsViewHelper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addRecipientId:recipientId];
}
}];
if (didShowSNAlert) {
return;
}
[weakSelf addRecipientId:recipientId];
}
}]];
}
[contents addSection:nonContactsSection];
}
// Contacts
2017-05-01 18:51:59 +02:00
OWSTableSection *signalAccountSection = [OWSTableSection new];
signalAccountSection.headerTitle = NSLocalizedString(
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
2017-05-01 18:51:59 +02:00
if (signalAccounts.count > 0) {
if (nonContactMemberRecipientIds.count < 1) {
2017-05-02 16:54:07 +02:00
// If the group contains any non-contacts or has not contacts,
// the "add non-contact user" will show up in the previous section
// of the table. However, it's more attractive to hide that section
// for the common case where people want to create a group from just
// their contacts. Therefore, when that section is hidden, we want
// to allow people to add non-contacts.
2017-05-01 18:51:59 +02:00
[signalAccountSection addItem:[self createAddNonContactItem]];
}
2017-05-01 18:51:59 +02:00
for (SignalAccount *signalAccount in signalAccounts) {
[signalAccountSection
2018-06-13 22:22:03 +02:00
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
NewGroupViewController *strongSelf = weakSelf;
OWSCAssertDebug(strongSelf);
2018-06-13 22:22:03 +02:00
ContactTableViewCell *cell = [ContactTableViewCell new];
NSString *recipientId = signalAccount.recipientId;
BOOL isCurrentMember = [strongSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
// In the "contacts" section, we label members as such when editing an existing
// group.
cell.accessoryMessage = NSLocalizedString(@"NEW_GROUP_MEMBER_LABEL",
@"An indicator that a user is a member of the new group.");
} else if (isBlocked) {
cell.accessoryMessage = NSLocalizedString(
@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
}
2018-10-25 15:35:08 +02:00
[cell configureWithRecipientId:signalAccount.recipientId];
2018-06-13 22:22:03 +02:00
return cell;
}
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
2017-05-01 18:51:59 +02:00
NSString *recipientId = signalAccount.recipientId;
BOOL isCurrentMember = [weakSelf.memberRecipientIds containsObject:recipientId];
BOOL isBlocked = [contactsViewHelper isRecipientIdBlocked:recipientId];
if (isCurrentMember) {
2017-05-02 16:54:07 +02:00
[weakSelf removeRecipientId:recipientId];
} else if (isBlocked) {
[BlockListUIUtils
showUnblockSignalAccountActionSheet:signalAccount
fromViewController:weakSelf
blockingManager:contactsViewHelper.blockingManager
contactsManager:contactsViewHelper.contactsManager
completionBlock:^(BOOL isStillBlocked) {
if (!isStillBlocked) {
[weakSelf addRecipientId:recipientId];
}
}];
} else {
BOOL didShowSNAlert = [SafetyNumberConfirmationAlert
presentAlertIfNecessaryWithRecipientId:signalAccount.recipientId
confirmationText:NSLocalizedString(
@"SAFETY_NUMBER_CHANGED_CONFIRM_"
@"ADD_TO_GROUP_ACTION",
@"button title to confirm adding "
@"a recipient to a group when "
@"their safety "
@"number has recently changed")
contactsManager:contactsViewHelper.contactsManager
completion:^(BOOL didConfirmIdentity) {
if (didConfirmIdentity) {
[weakSelf addRecipientId:recipientId];
}
}];
if (didShowSNAlert) {
return;
}
[weakSelf addRecipientId:recipientId];
}
}]];
}
} else {
[signalAccountSection
addItem:[OWSTableItem
softCenterLabelItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS",
@"A label that indicates the user has no Signal contacts.")]];
}
2017-05-01 18:51:59 +02:00
[contents addSection:signalAccountSection];
self.tableViewController.contents = contents;
}
- (OWSTableItem *)createAddNonContactItem
{
__weak NewGroupViewController *weakSelf = self;
return [OWSTableItem
disclosureItemWithText:NSLocalizedString(@"NEW_GROUP_ADD_NON_CONTACT",
@"A label for the cell that lets you add a new non-contact member to a group.")
2018-06-13 22:22:03 +02:00
customRowHeight:UITableViewAutomaticDimension
actionBlock:^{
AddToGroupViewController *viewController = [AddToGroupViewController new];
viewController.addToGroupDelegate = weakSelf;
viewController.hideContacts = YES;
[weakSelf.navigationController pushViewController:viewController animated:YES];
}];
}
- (void)removeRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.memberRecipientIds removeObject:recipientId];
[self updateTableContents];
}
- (void)addRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
[self.memberRecipientIds addObject:recipientId];
self.hasUnsavedChanges = YES;
[self updateTableContents];
}
#pragma mark - Methods
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
2017-05-02 18:30:53 +02:00
if (!self.hasAppeared) {
[self.groupNameTextField becomeFirstResponder];
2017-05-02 18:30:53 +02:00
self.hasAppeared = YES;
}
}
2014-10-29 21:58:58 +01:00
#pragma mark - Actions
- (void)createGroup
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
TSGroupModel *model = [self makeGroup];
__block TSGroupThread *thread;
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:model transaction:transaction];
}];
OWSAssertDebug(thread);
2017-08-04 16:38:00 +02:00
[OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread];
2017-11-08 20:04:51 +01:00
void (^successHandler)(void) = ^{
OWSLogError(@"Group creation successful.");
dispatch_async(dispatch_get_main_queue(), ^{
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
});
};
void (^failureHandler)(NSError *error) = ^(NSError *error) {
OWSLogError(@"Group creation failed: %@", error);
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
// Add an error message to the new group indicating
// that group creation didn't succeed.
TSErrorMessage *errorMessage = [[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
failedMessageType:TSErrorMessageGroupCreationFailed];
[errorMessage save];
dispatch_async(dispatch_get_main_queue(), ^{
Faster conversation presentation. There are multiple places in the codebase we present a conversation. We used to have some very conservative machinery around how this was done, for fear of failing to present the call view controller, which would have left a hidden call in the background. We've since addressed that concern more thoroughly via the separate calling UIWindow. As such, the remaining presentation machinery is overly complex and inflexible for what we need. Sometimes we want to animate-push the conversation. (tap on home, tap on "send message" in contact card/group members) Sometimes we want to dismiss a modal, to reveal the conversation behind it (contact picker, group creation) Sometimes we want to present the conversation with no animation (becoming active from a notification) We also want to ensure that we're never pushing more than one conversation view controller, which was previously a problem since we were "pushing" a newly constructed VC in response to these myriad actions. It turned out there were certain code paths that caused multiple actions to be fired in rapid succession which pushed multiple ConversationVC's. The built-in method: `setViewControllers:animated` easily ensures we only have one ConversationVC on the stack, while being composable enough to faciliate the various more efficient animations we desire. The only thing lost with the complex methods is that the naive `presentViewController:` can fail, e.g. if another view is already presented. E.g. if an alert appears *just* before the user taps compose, the contact picker will fail to present. Since we no longer depend on this for presenting the CallViewController, this isn't catostrophic, and in fact, arguable preferable, since we want the user to read and dismiss any alert explicitly. // FREEBIE
2018-08-18 22:54:35 +02:00
[SignalApp.sharedApp presentConversationForThread:thread action:ConversationViewActionCompose animated:NO];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
});
};
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:NO
2017-09-18 21:35:14 +02:00
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:thread
2018-08-31 18:43:05 +02:00
groupMetaMessage:TSGroupMetaMessageNew
expiresInSeconds:0];
2017-09-18 21:35:14 +02:00
[message updateWithCustomMessage:NSLocalizedString(@"GROUP_CREATED", nil)];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2017-09-18 21:35:14 +02:00
if (model.groupImage) {
NSData *data = UIImagePNGRepresentation(model.groupImage);
2018-07-30 17:00:56 +02:00
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:data fileExtension:@"png"];
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
// an error in the thread if group creation fails
[self.messageSender sendTemporaryAttachment:dataSource
contentType:OWSMimeTypeImagePng
inMessage:message
success:successHandler
failure:failureHandler];
2017-09-18 21:35:14 +02:00
} else {
// CLEANUP DURABLE - Replace with a durable operation e.g. `GroupCreateJob`, which creates
// an error in the thread if group creation fails
[self.messageSender sendMessage:message success:successHandler failure:failureHandler];
2017-09-18 21:35:14 +02:00
}
});
}];
}
- (TSGroupModel *)makeGroup
{
2017-10-18 20:53:31 +02:00
NSString *groupName = [self.groupNameTextField.text ows_stripped];
2017-05-02 17:16:34 +02:00
NSMutableArray<NSString *> *recipientIds = [self.memberRecipientIds.allObjects mutableCopy];
[recipientIds addObject:[self.contactsViewHelper localNumber]];
return [[TSGroupModel alloc] initWithTitle:groupName
memberIds:recipientIds
image:self.groupAvatar
groupId:self.groupId];
2014-10-29 21:58:58 +01:00
}
#pragma mark - Group Avatar
- (void)showChangeAvatarUI
{
[self.avatarViewHelper showChangeAvatarUI];
2014-10-29 21:58:58 +01:00
}
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
2014-10-29 21:58:58 +01:00
_groupAvatar = groupAvatar;
2014-10-29 21:58:58 +01:00
self.hasUnsavedChanges = YES;
[self updateAvatarView];
2014-10-29 21:58:58 +01:00
}
- (void)updateAvatarView
{
UIImage *_Nullable groupAvatar = self.groupAvatar;
if (!groupAvatar) {
NSString *conversationColorName = [TSGroupThread defaultConversationColorNameForGroupId:self.groupId];
groupAvatar = [OWSGroupAvatarBuilder defaultAvatarForGroupId:self.groupId
conversationColorName:conversationColorName
diameter:kLargeAvatarSize];
}
self.avatarView.image = groupAvatar;
2014-10-29 21:58:58 +01:00
}
#pragma mark - Event Handling
2014-10-29 21:58:58 +01:00
- (void)backButtonPressed
{
[self.groupNameTextField resignFirstResponder];
2014-10-29 21:58:58 +01:00
if (!self.hasUnsavedChanges) {
// If user made no changes, return to conversation settings view.
[self.navigationController popViewControllerAnimated:YES];
return;
2014-10-29 21:58:58 +01:00
}
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
@"The alert title if user tries to exit the new group view without saving changes.")
message:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
@"The alert message if user tries to exit the new group view without saving changes.")
preferredStyle:UIAlertControllerStyleAlert];
[controller
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
@"The label for the 'discard' button in alerts and action sheets.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
}]];
[controller addAction:[OWSAlerts cancelAction]];
[self presentViewController:controller animated:YES completion:nil];
}
- (void)groupNameDidChange:(id)sender
{
self.hasUnsavedChanges = YES;
}
#pragma mark - Text Field Delegate
2014-10-29 21:58:58 +01:00
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.groupNameTextField resignFirstResponder];
return NO;
2014-10-29 21:58:58 +01:00
}
#pragma mark - OWSTableViewControllerDelegate
- (void)tableViewWillBeginDragging
{
[self.groupNameTextField resignFirstResponder];
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return YES;
2014-10-29 21:58:58 +01:00
}
#pragma mark - AvatarViewHelperDelegate
2014-10-29 21:58:58 +01:00
- (NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssertDebug(image);
2014-10-29 21:58:58 +01:00
self.groupAvatar = image;
2014-10-29 21:58:58 +01:00
}
- (UIViewController *)fromViewController
{
return self;
2014-10-29 21:58:58 +01:00
}
- (BOOL)hasClearAvatarAction
{
return NO;
}
#pragma mark - AddToGroupViewControllerDelegate
- (void)recipientIdWasAdded:(NSString *)recipientId
{
[self addRecipientId:recipientId];
}
2017-05-02 16:54:07 +02:00
- (BOOL)isRecipientGroupMember:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
2017-05-02 16:54:07 +02:00
return [self.memberRecipientIds containsObject:recipientId];
}
#pragma mark - OWSNavigationView
- (BOOL)shouldCancelNavigationBack
{
BOOL result = self.hasUnsavedChanges;
if (self.hasUnsavedChanges) {
[self backButtonPressed];
}
return result;
}
2014-10-29 21:58:58 +01:00
@end
NS_ASSUME_NONNULL_END