Add "pending member requests" banner.

This commit is contained in:
Matthew Chen 2020-09-03 18:07:50 -03:00
parent 7b47f79e96
commit e578468ca2
4 changed files with 191 additions and 64 deletions

View File

@ -882,6 +882,8 @@ typedef enum : NSUInteger {
return;
}
NSMutableArray<UIView *> *banners = [NSMutableArray new];
NSArray<SignalServiceAddress *> *noLongerVerifiedAddresses = [self noLongerVerifiedAddresses];
if (noLongerVerifiedAddresses.count > 0) {
@ -902,10 +904,9 @@ typedef enum : NSUInteger {
message = [NSString stringWithFormat:format, displayName];
}
[self createBannerWithTitle:message
bannerColor:UIColor.ows_accentRedColor
tapSelector:@selector(noLongerVerifiedBannerViewWasTapped:)];
return;
[banners addObject:[self createBannerWithTitle:message
bannerColor:UIColor.ows_accentRedColor
tapSelector:@selector(noLongerVerifiedBannerViewWasTapped:)]];
}
NSString *blockStateMessage = nil;
@ -924,34 +925,64 @@ typedef enum : NSUInteger {
}
if (blockStateMessage) {
[self createBannerWithTitle:blockStateMessage
bannerColor:UIColor.ows_accentRedColor
tapSelector:@selector(blockBannerViewWasTapped:)];
[banners addObject:[self createBannerWithTitle:blockStateMessage
bannerColor:UIColor.ows_accentRedColor
tapSelector:@selector(blockBannerViewWasTapped:)]];
}
NSUInteger pendingMemberRequestCount = self.pendingMemberRequestCount;
if (pendingMemberRequestCount > 0) {
[banners addObject:[self createPendingJoinReuqestBannerWithCount:pendingMemberRequestCount]];
}
if (banners.count < 1) {
return;
}
UIStackView *bannerView = [[UIStackView alloc] initWithArrangedSubviews:banners];
bannerView.axis = UILayoutConstraintAxisVertical;
bannerView.alignment = UIStackViewAlignmentFill;
[self.view addSubview:bannerView];
[bannerView autoPinToTopLayoutGuideOfViewController:self withInset:0];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[bannerView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
UIView *bannerShadow = [UIView new];
bannerShadow.backgroundColor = Theme.backgroundColor;
// Use a shadow to "pop" the indicator above the other views.
bannerShadow.layer.shadowColor = [UIColor blackColor].CGColor;
bannerShadow.layer.shadowOffset = CGSizeMake(0, 4);
bannerShadow.layer.shadowRadius = 4.f;
bannerShadow.layer.shadowOpacity = 0.15f;
[bannerShadow addRedBorder];
[bannerView addSubview:bannerShadow];
[bannerShadow autoPinEdgesToSuperviewEdges];
[bannerView sendSubviewToBack:bannerShadow];
[self.view layoutSubviews];
self.bannerView = bannerView;
}
- (NSUInteger)pendingMemberRequestCount
{
if ([self.thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)self.thread;
return groupThread.groupMembership.requestingMembers.count;
} else {
return 0;
}
}
- (void)createBannerWithTitle:(NSString *)title bannerColor:(UIColor *)bannerColor tapSelector:(SEL)tapSelector
- (UIView *)createBannerWithTitle:(NSString *)title bannerColor:(UIColor *)bannerColor tapSelector:(SEL)tapSelector
{
OWSAssertDebug(title.length > 0);
OWSAssertDebug(bannerColor);
UIView *bannerView = [UIView containerView];
bannerView.backgroundColor = bannerColor;
bannerView.layer.cornerRadius = 2.5f;
// Use a shadow to "pop" the indicator above the other views.
bannerView.layer.shadowColor = [UIColor blackColor].CGColor;
bannerView.layer.shadowOffset = CGSizeMake(2, 3);
bannerView.layer.shadowRadius = 2.f;
bannerView.layer.shadowOpacity = 0.35f;
UILabel *label = [UILabel new];
label.font = [UIFont ows_semiboldFontWithSize:14.f];
label.text = title;
label.textColor = [UIColor whiteColor];
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
UILabel *label = [self buildBannerLabel:title];
label.textAlignment = NSTextAlignmentCenter;
UIImage *closeIcon = [UIImage imageNamed:@"banner_close"];
@ -974,22 +1005,71 @@ typedef enum : NSUInteger {
[bannerView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:tapSelector]];
bannerView.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"banner_close");
[self.view addSubview:bannerView];
[bannerView autoPinToTopLayoutGuideOfViewController:self withInset:10];
[bannerView autoHCenterInSuperview];
return bannerView;
}
CGFloat labelDesiredWidth = [label sizeThatFits:CGSizeZero].width;
CGFloat bannerDesiredWidth
= (labelDesiredWidth + kBannerHPadding + kBannerHSpacing + closeIcon.size.width + kBannerCloseButtonPadding);
const CGFloat kMinBannerHMargin = 20.f;
if (bannerDesiredWidth + kMinBannerHMargin * 2.f >= self.view.width) {
[bannerView autoPinEdgeToSuperviewSafeArea:ALEdgeLeading withInset:kMinBannerHMargin];
[bannerView autoPinEdgeToSuperviewSafeArea:ALEdgeTrailing withInset:kMinBannerHMargin];
}
- (UILabel *)buildBannerLabel:(NSString *)title
{
UILabel *label = [UILabel new];
label.font = [UIFont ows_dynamicTypeSubheadlineClampedFont].ows_semibold;
label.text = title;
label.textColor = [UIColor whiteColor];
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
return label;
}
[self.view layoutSubviews];
- (UIView *)createPendingJoinReuqestBannerWithCount:(NSUInteger)pendingMemberRequestCount
{
OWSAssertDebug(pendingMemberRequestCount > 0);
self.bannerView = bannerView;
NSString *format = NSLocalizedString(@"PENDING_GROUP_MEMBERS_REQUEST_BANNER_FORMAT",
@"Format for banner indicating that there are pending member requests to join the group. Embeds {{ the number "
@"of pending member requests }}.");
NSString *title = [NSString stringWithFormat:format, [OWSFormat formatUInt:pendingMemberRequestCount]];
UILabel *label = [self buildBannerLabel:title];
label.font = [UIFont ows_dynamicTypeSubheadlineClampedFont];
__weak ConversationViewController *weakSelf = self;
OWSButton *dismissButton = [[OWSButton alloc] initWithTitle:CommonStrings.dismissButton
block:^{ [weakSelf hidePendingMemberRequests]; }];
dismissButton.titleLabel.font = [UIFont ows_dynamicTypeSubheadlineClampedFont].ows_semibold;
NSString *viewRequestsLabel = NSLocalizedString(@"PENDING_GROUP_MEMBERS_REQUEST_BANNER_VIEW_REQUESTS",
@"Label for the 'view requests' button in the pending member requests banner.");
OWSButton *viewRequestsButton = [[OWSButton alloc] initWithTitle:viewRequestsLabel
block:^{ [weakSelf viewMemberRequests]; }];
viewRequestsButton.titleLabel.font = [UIFont ows_dynamicTypeSubheadlineClampedFont].ows_semibold;
UIStackView *buttonRow = [[UIStackView alloc] initWithArrangedSubviews:@[
[UIView hStretchingSpacer],
dismissButton,
viewRequestsButton,
]];
buttonRow.axis = UILayoutConstraintAxisHorizontal;
buttonRow.spacing = 24;
UIStackView *bannerView = [[UIStackView alloc] initWithArrangedSubviews:@[
label,
buttonRow,
]];
bannerView.axis = UILayoutConstraintAxisVertical;
bannerView.alignment = UIStackViewAlignmentFill;
bannerView.spacing = 10;
bannerView.layoutMargins = UIEdgeInsetsMake(14, 16, 14, 16);
[bannerView setLayoutMarginsRelativeArrangement:YES];
[bannerView addBackgroundViewWithBackgroundColor:UIColor.ows_accentBlueColor];
bannerView.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"pending_group_request_banner");
return bannerView;
}
- (void)hidePendingMemberRequests
{
}
- (void)viewMemberRequests
{
[self showConversationSettingsAndShowMemberRequests];
}
- (void)blockBannerViewWasTapped:(UIGestureRecognizer *)sender
@ -1591,7 +1671,7 @@ typedef enum : NSUInteger {
{
NSArray<SignalServiceAddress *> *noLongerVerifiedAddresses = [self noLongerVerifiedAddresses];
if (noLongerVerifiedAddresses.count > 1) {
[self showConversationSettingsAndShowVerification:YES];
[self showConversationSettingsAndShowVerification];
} else if (noLongerVerifiedAddresses.count == 1) {
// Pick one in an arbitrary but deterministic manner.
SignalServiceAddress *address = noLongerVerifiedAddresses.lastObject;
@ -1601,34 +1681,52 @@ typedef enum : NSUInteger {
- (void)showConversationSettings
{
[self showConversationSettingsAndShowVerification:NO];
}
- (UIViewController *)buildConversationSettingsView:(BOOL)showVerificationOnAppear
{
ConversationSettingsViewController *settingsVC =
[[ConversationSettingsViewController alloc] initWithThreadViewModel:self.threadViewModel];
settingsVC.conversationSettingsViewDelegate = self;
return settingsVC;
}
- (void)showConversationSettingsAndShowVerification:(BOOL)showVerification
{
UIViewController *settingsVC = [self buildConversationSettingsView:showVerification];
[self.navigationController setViewControllers:[self.viewControllersUpToSelf arrayByAddingObject:settingsVC]
animated:YES];
[self showConversationSettingsWithMode:ConversationSettingsPresentationModeDefault];
}
- (void)showConversationSettingsAndShowAllMedia
{
UIViewController *settingsVC = [self buildConversationSettingsView:NO];
[self showConversationSettingsWithMode:ConversationSettingsPresentationModeShowAllMedia];
}
MediaTileViewController *allMedia = [[MediaTileViewController alloc] initWithThread:self.thread];
- (void)showConversationSettingsAndShowVerification
{
[self showConversationSettingsWithMode:ConversationSettingsPresentationModeShowVerification];
}
[self.navigationController
setViewControllers:[self.viewControllersUpToSelf arrayByAddingObjectsFromArray:@[ settingsVC, allMedia ]]
animated:YES];
- (void)showConversationSettingsAndShowMemberRequests
{
[self showConversationSettingsWithMode:ConversationSettingsPresentationModeShowMemberRequests];
}
- (void)showConversationSettingsWithMode:(ConversationSettingsPresentationMode)mode
{
NSMutableArray<UIViewController *> *viewControllers = [self.viewControllersUpToSelf mutableCopy];
ConversationSettingsViewController *settingsView =
[[ConversationSettingsViewController alloc] initWithThreadViewModel:self.threadViewModel];
settingsView.conversationSettingsViewDelegate = self;
[viewControllers addObject:settingsView];
switch (mode) {
case ConversationSettingsPresentationModeDefault:
break;
case ConversationSettingsPresentationModeShowVerification:
settingsView.showVerificationOnAppear = YES;
break;
case ConversationSettingsPresentationModeShowMemberRequests: {
UIViewController *_Nullable view = [settingsView buildMemberRequestsAndInvitesView];
if (view != nil) {
[viewControllers addObject:view];
}
break;
}
case ConversationSettingsPresentationModeShowAllMedia:
[viewControllers addObject:[[MediaTileViewController alloc] initWithThread:self.thread]];
break;
}
[self.navigationController setViewControllers:viewControllers animated:YES];
}
- (NSArray<UIViewController *> *)viewControllersUpToSelf
@ -4803,6 +4901,7 @@ typedef enum : NSUInteger {
// viewWillAppear will call resetContentAndLayout.
return;
}
NSUInteger oldPendingMemberRequestCount = self.pendingMemberRequestCount;
TSThread *_Nullable lastestThread = [TSThread anyFetchWithUniqueId:self.thread.uniqueId transaction:transaction];
if (lastestThread == nil) {
lastestThread = self.thread;
@ -4811,6 +4910,11 @@ typedef enum : NSUInteger {
[self updateNavigationBarSubtitleLabel];
[self updateBarButtonItems];
NSUInteger newPendingMemberRequestCount = self.pendingMemberRequestCount;
if (oldPendingMemberRequestCount != newPendingMemberRequestCount) {
[self ensureBannerState];
}
// If the message has been deleted / disappeared, we need to dismiss
[self dismissMessageActionsIfNecessary];

View File

@ -7,6 +7,16 @@ import PromiseKit
import UIKit
import ContactsUI
@objc
public enum ConversationSettingsPresentationMode: UInt {
case `default`
case showVerification
case showMemberRequests
case showAllMedia
}
// MARK: -
@objc
public protocol ConversationSettingsViewDelegate: class {
@ -78,6 +88,7 @@ class ConversationSettingsViewController: OWSTableViewController {
// This is updated as we change group membership, etc.
var currentGroupModel: TSGroupModel?
@objc
public var showVerificationOnAppear = false
var disappearingMessagesConfiguration: OWSDisappearingMessagesConfiguration!
@ -577,14 +588,23 @@ class ConversationSettingsViewController: OWSTableViewController {
}
func showMemberRequestsAndInvitesView() {
guard let groupThread = thread as? TSGroupThread else {
guard let viewController = buildMemberRequestsAndInvitesView() else {
owsFailDebug("Invalid thread.")
return
}
navigationController?.pushViewController(viewController, animated: true)
}
@objc
public func buildMemberRequestsAndInvitesView() -> UIViewController? {
guard let groupThread = thread as? TSGroupThread else {
owsFailDebug("Invalid thread.")
return nil
}
let groupMemberRequestsAndInvitesViewController = GroupMemberRequestsAndInvitesViewController(groupThread: groupThread,
groupViewHelper: groupViewHelper)
groupMemberRequestsAndInvitesViewController.groupMemberRequestsAndInvitesViewControllerDelegate = self
navigationController?.pushViewController(groupMemberRequestsAndInvitesViewController, animated: true)
return groupMemberRequestsAndInvitesViewController
}
func showGroupLinkView() {

View File

@ -32,10 +32,7 @@ public class GroupLinkQRCodeViewController: OWSViewController {
private func createContents() {
let qrCodeView = QRCodeView(useCircularWrapper: true)
let qrCodeViewWrapper = UIStackView(arrangedSubviews: [qrCodeView])
qrCodeViewWrapper.layoutMargins = UIEdgeInsets(top: 0, leading: 40, bottom: 0, trailing: 40)
qrCodeViewWrapper.isLayoutMarginsRelativeArrangement = true
let qrCodeView = QRCodeView()
do {
let inviteLinkUrl = try GroupManager.groupInviteLink(forGroupModelV2: groupModelV2)
@ -66,7 +63,7 @@ public class GroupLinkQRCodeViewController: OWSViewController {
let vSpacer2 = UIView.vStretchingSpacer()
let stackView = UIStackView(arrangedSubviews: [
vSpacer1,
qrCodeViewWrapper,
qrCodeView,
UIView.spacer(withHeight: 24),
descriptionLabel,
vSpacer2,

View File

@ -2893,6 +2893,12 @@
/* Message indicating that a request to join the group was successfully approved. Embeds {{ the name of the approved user }}. */
"PENDING_GROUP_MEMBERS_REQUEST_APPROVED_FORMAT" = "Added “%@“.";
/* Format for banner indicating that there are pending member requests to join the group. Embeds {{ the number of pending member requests }}. */
"PENDING_GROUP_MEMBERS_REQUEST_BANNER_FORMAT" = "%@ pending member requests.";
/* Label for the 'view requests' button in the pending member requests banner. */
"PENDING_GROUP_MEMBERS_REQUEST_BANNER_VIEW_REQUESTS" = "View Requests";
/* Message indicating that a request to join the group was successfully denied. Embeds {{ the name of the denied user }}. */
"PENDING_GROUP_MEMBERS_REQUEST_DENIED_FORMAT" = "Denied “%@“.";